isoperiod 1.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. isoperiod-1.2.1/.editorconfig +33 -0
  2. isoperiod-1.2.1/.githooks/pre-commit +47 -0
  3. isoperiod-1.2.1/.github/ISSUE_TEMPLATE/bug_report.yml +61 -0
  4. isoperiod-1.2.1/.github/ISSUE_TEMPLATE/config.yml +1 -0
  5. isoperiod-1.2.1/.github/ISSUE_TEMPLATE/feature_request.yml +30 -0
  6. isoperiod-1.2.1/.github/dependabot.yml +23 -0
  7. isoperiod-1.2.1/.github/pull_request_template.md +9 -0
  8. isoperiod-1.2.1/.github/workflows/pipeline.yml +152 -0
  9. isoperiod-1.2.1/.gitignore +167 -0
  10. isoperiod-1.2.1/CHANGELOG/1.0.0.md +38 -0
  11. isoperiod-1.2.1/CHANGELOG/1.1.0.md +48 -0
  12. isoperiod-1.2.1/CHANGELOG/1.2.0.md +23 -0
  13. isoperiod-1.2.1/CHANGELOG/1.2.1.md +30 -0
  14. isoperiod-1.2.1/CITATION.cff +29 -0
  15. isoperiod-1.2.1/CONTRIBUTING.md +166 -0
  16. isoperiod-1.2.1/LICENSE +21 -0
  17. isoperiod-1.2.1/Makefile +82 -0
  18. isoperiod-1.2.1/PKG-INFO +171 -0
  19. isoperiod-1.2.1/README.md +129 -0
  20. isoperiod-1.2.1/conftest.py +6 -0
  21. isoperiod-1.2.1/docs/Makefile +23 -0
  22. isoperiod-1.2.1/docs/source/_static/UKCEH_Logo_Master_Black.png +0 -0
  23. isoperiod-1.2.1/docs/source/_static/UKCEH_Logo_Tagline_Black_Data.png +0 -0
  24. isoperiod-1.2.1/docs/source/_static/css/custom.css +55 -0
  25. isoperiod-1.2.1/docs/source/_static/fdri_logo.png +0 -0
  26. isoperiod-1.2.1/docs/source/_static/fdri_logo_cropped.jpg +0 -0
  27. isoperiod-1.2.1/docs/source/_templates/autosummary/attribute.rst +7 -0
  28. isoperiod-1.2.1/docs/source/_templates/autosummary/method.rst +7 -0
  29. isoperiod-1.2.1/docs/source/_templates/autosummary/property.rst +7 -0
  30. isoperiod-1.2.1/docs/source/_templates/partials/nav-socials.html +11 -0
  31. isoperiod-1.2.1/docs/source/api/exceptions.rst +26 -0
  32. isoperiod-1.2.1/docs/source/api/period.rst +107 -0
  33. isoperiod-1.2.1/docs/source/conf.py +98 -0
  34. isoperiod-1.2.1/docs/source/developer/contributing.rst +133 -0
  35. isoperiod-1.2.1/docs/source/developer/documentation.rst +121 -0
  36. isoperiod-1.2.1/docs/source/examples/__init__.py +0 -0
  37. isoperiod-1.2.1/docs/source/examples/comparing.py +102 -0
  38. isoperiod-1.2.1/docs/source/examples/concepts.py +35 -0
  39. isoperiod-1.2.1/docs/source/examples/creating.py +91 -0
  40. isoperiod-1.2.1/docs/source/examples/describing.py +48 -0
  41. isoperiod-1.2.1/docs/source/examples/interop.py +86 -0
  42. isoperiod-1.2.1/docs/source/examples/intervals.py +168 -0
  43. isoperiod-1.2.1/docs/source/examples/offsets.py +102 -0
  44. isoperiod-1.2.1/docs/source/examples/quick_start.py +85 -0
  45. isoperiod-1.2.1/docs/source/examples/timezones.py +66 -0
  46. isoperiod-1.2.1/docs/source/getting_started/concepts.rst +206 -0
  47. isoperiod-1.2.1/docs/source/getting_started/installation.rst +46 -0
  48. isoperiod-1.2.1/docs/source/getting_started/quick-start.rst +163 -0
  49. isoperiod-1.2.1/docs/source/index.rst +135 -0
  50. isoperiod-1.2.1/docs/source/user_guide/comparing.rst +173 -0
  51. isoperiod-1.2.1/docs/source/user_guide/creating.rst +196 -0
  52. isoperiod-1.2.1/docs/source/user_guide/describing.rst +111 -0
  53. isoperiod-1.2.1/docs/source/user_guide/interop.rst +144 -0
  54. isoperiod-1.2.1/docs/source/user_guide/intervals.rst +256 -0
  55. isoperiod-1.2.1/docs/source/user_guide/offsets.rst +192 -0
  56. isoperiod-1.2.1/docs/source/user_guide/timezones.rst +127 -0
  57. isoperiod-1.2.1/pyproject.toml +121 -0
  58. isoperiod-1.2.1/scripts/bump.py +84 -0
  59. isoperiod-1.2.1/scripts/release.py +98 -0
  60. isoperiod-1.2.1/src/isoperiod/__init__.py +17 -0
  61. isoperiod-1.2.1/src/isoperiod/enums.py +13 -0
  62. isoperiod-1.2.1/src/isoperiod/exceptions.py +25 -0
  63. isoperiod-1.2.1/src/isoperiod/iso.py +233 -0
  64. isoperiod-1.2.1/src/isoperiod/parsing.py +256 -0
  65. isoperiod-1.2.1/src/isoperiod/periods.py +1170 -0
  66. isoperiod-1.2.1/src/isoperiod/properties.py +805 -0
  67. isoperiod-1.2.1/src/isoperiod/py.typed +0 -0
  68. isoperiod-1.2.1/src/isoperiod/timeline.py +124 -0
  69. isoperiod-1.2.1/src/isoperiod/words.py +181 -0
  70. isoperiod-1.2.1/tests/isoperiod/test_examples.py +49 -0
  71. isoperiod-1.2.1/tests/isoperiod/test_invariants.py +159 -0
  72. isoperiod-1.2.1/tests/isoperiod/test_iso.py +147 -0
  73. isoperiod-1.2.1/tests/isoperiod/test_parsing.py +320 -0
  74. isoperiod-1.2.1/tests/isoperiod/test_periods.py +1103 -0
  75. isoperiod-1.2.1/tests/isoperiod/test_properties.py +562 -0
  76. isoperiod-1.2.1/tests/isoperiod/test_timeline.py +202 -0
  77. isoperiod-1.2.1/tests/isoperiod/test_words.py +141 -0
  78. isoperiod-1.2.1/uv.lock +2043 -0
@@ -0,0 +1,33 @@
1
+ # https://editorconfig.org
2
+ root = true
3
+
4
+ [*]
5
+ charset = utf-8
6
+ end_of_line = lf
7
+ indent_style = space
8
+ indent_size = 4
9
+ trim_trailing_whitespace = true
10
+ insert_final_newline = true
11
+
12
+ [*.{html,css,js,json,sh,yml,yaml}]
13
+ indent_size = 2
14
+
15
+ [*.bat]
16
+ indent_style = tab
17
+ end_of_line = crlf
18
+
19
+ [LICENSE]
20
+ insert_final_newline = false
21
+
22
+ # Ignore binary or generated files
23
+ [*.{png,jpg,gif,ico,woff,woff2,ttf,eot,svg,pdf}]
24
+ charset = unset
25
+ end_of_line = unset
26
+ indent_style = unset
27
+ indent_size = unset
28
+ trim_trailing_whitespace = unset
29
+ insert_final_newline = unset
30
+ max_line_length = unset
31
+
32
+ [*.{diff,patch}]
33
+ trim_trailing_whitespace = false
@@ -0,0 +1,47 @@
1
+ #!/bin/sh
2
+ #
3
+ # Pre-commit hook: runs ruff format and lint checks via uv.
4
+ # Requires uv on PATH: https://docs.astral.sh/uv/getting-started/installation/
5
+ #
6
+ # Installed automatically by post-generation hook via:
7
+ # git config core.hooksPath .githooks
8
+ #
9
+ # To install manually in an existing clone:
10
+ # make install-hooks
11
+
12
+ # Redirect output to stderr.
13
+ exec 1>&2
14
+
15
+ RED='\033[0;31m'
16
+ NC='\033[0m' # No Color
17
+ format_err=0
18
+ check_err=0
19
+ err=0
20
+
21
+ echo 'Running "uv run ruff format --check"'
22
+ uv run ruff format --check
23
+
24
+ if [ "$?" -eq 1 ]; then
25
+ format_err=1
26
+ err=1
27
+ fi
28
+
29
+ echo 'Running "uv run ruff check"'
30
+ uv run ruff check
31
+
32
+ if [ "$?" -eq 1 ]; then
33
+ check_err=1
34
+ err=1
35
+ fi
36
+
37
+ if [ "${format_err}" -eq 1 ] ; then
38
+ echo "${RED}Run \"ruff format\" before committing${NC}"
39
+ fi
40
+
41
+ if [ "${check_err}" -eq 1 ] ; then
42
+ echo "${RED}Correct ruff errors before committing${NC}"
43
+ fi
44
+
45
+ if [ "${err}" -eq 1 ] ; then
46
+ exit 1
47
+ fi
@@ -0,0 +1,61 @@
1
+ name: Bug Report
2
+ description: Something isn't working as expected.
3
+ title: "BUG: "
4
+ labels: [bug]
5
+ body:
6
+ - type: textarea
7
+ id: description
8
+ attributes:
9
+ label: Describe the bug
10
+ description: What happened? What did you expect to happen?
11
+ validations:
12
+ required: true
13
+ - type: textarea
14
+ id: steps
15
+ attributes:
16
+ label: Steps to reproduce
17
+ description: Minimal steps to trigger the bug.
18
+ placeholder: |
19
+ 1. Install with `uv pip install isoperiod`
20
+ 2. Run `...`
21
+ 3. See error
22
+ validations:
23
+ required: true
24
+ - type: textarea
25
+ id: expected
26
+ attributes:
27
+ label: Expected behavior
28
+ validations:
29
+ required: true
30
+ - type: input
31
+ id: version
32
+ attributes:
33
+ label: "isoperiod version"
34
+ placeholder: "0.1.0"
35
+ validations:
36
+ required: true
37
+ - type: input
38
+ id: python-version
39
+ attributes:
40
+ label: Python version
41
+ placeholder: "3.13"
42
+ validations:
43
+ required: true
44
+ - type: dropdown
45
+ id: os
46
+ attributes:
47
+ label: Operating system
48
+ options:
49
+ - macOS
50
+ - Linux
51
+ - Windows
52
+ - Other
53
+ validations:
54
+ required: true
55
+ - type: textarea
56
+ id: context
57
+ attributes:
58
+ label: Additional context
59
+ description: Logs, tracebacks, screenshots, or anything else that helps.
60
+ validations:
61
+ required: false
@@ -0,0 +1 @@
1
+ blank_issues_enabled: true
@@ -0,0 +1,30 @@
1
+ name: Feature Request
2
+ description: Suggest something new.
3
+ title: "FEAT: "
4
+ labels: [enhancement]
5
+ body:
6
+ - type: textarea
7
+ id: description
8
+ attributes:
9
+ label: Describe the feature
10
+ validations:
11
+ required: true
12
+ - type: textarea
13
+ id: motivation
14
+ attributes:
15
+ label: Motivation
16
+ description: What problem does this solve? What's the use case?
17
+ validations:
18
+ required: true
19
+ - type: textarea
20
+ id: alternatives
21
+ attributes:
22
+ label: Alternatives considered
23
+ validations:
24
+ required: false
25
+ - type: textarea
26
+ id: context
27
+ attributes:
28
+ label: Additional context
29
+ validations:
30
+ required: false
@@ -0,0 +1,23 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: github-actions
4
+ directory: /
5
+ schedule:
6
+ interval: weekly
7
+ cooldown:
8
+ default-days: 7
9
+ open-pull-requests-limit: 5
10
+ groups:
11
+ github-actions:
12
+ patterns:
13
+ - "*"
14
+
15
+ - package-ecosystem: uv
16
+ directory: /
17
+ schedule:
18
+ interval: weekly
19
+ open-pull-requests-limit: 5
20
+ groups:
21
+ all-dependencies:
22
+ patterns:
23
+ - "*"
@@ -0,0 +1,9 @@
1
+ ## Issue ticket number and link
2
+
3
+ ## Overview
4
+
5
+ <!-- 1-3 sentences of problem this PR addresses -->
6
+
7
+ ## Main changes
8
+
9
+ <!-- List of the core changes to point reviewers towards -->
@@ -0,0 +1,152 @@
1
+ name: Pipeline
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - main
8
+ - develop
9
+ workflow_dispatch:
10
+
11
+ permissions:
12
+ contents: read
13
+ actions: read
14
+
15
+ pages: write
16
+ id-token: write
17
+
18
+
19
+ jobs:
20
+
21
+ test-python:
22
+ uses: NERC-CEH/dri-cicd/.github/workflows/test-python.yml@main
23
+ with:
24
+ use_uv: true
25
+ use_pyright: true
26
+ coverage_threshold: 90
27
+
28
+
29
+ build-docs:
30
+ uses: NERC-CEH/dri-cicd/.github/workflows/build-docs.yml@main
31
+ with:
32
+ use_uv: true
33
+ pages_artifact_name: pages-artifact
34
+
35
+ deploy-docs:
36
+ needs: [build-docs]
37
+ if: github.ref == 'refs/heads/main'
38
+ uses: NERC-CEH/dri-cicd/.github/workflows/deploy-docs.yml@main
39
+ with:
40
+ pages_artifact_name: pages-artifact
41
+
42
+
43
+ release-ready:
44
+ if: github.event_name == 'pull_request' && github.base_ref == 'main'
45
+ runs-on: ubuntu-latest
46
+ steps:
47
+ - uses: actions/checkout@v7
48
+ with:
49
+ fetch-depth: 0
50
+
51
+ - name: Check version bump and changelog
52
+ run: |
53
+ BASE_VER=$(git show origin/main:pyproject.toml | grep '^version' | sed 's/.*"\(.*\)"/\1/')
54
+ NEW_VER=$(grep '^version' pyproject.toml | sed 's/.*"\(.*\)"/\1/')
55
+
56
+ if [ "$BASE_VER" = "$NEW_VER" ]; then
57
+ echo "ERROR: merges to main require a version bump (current: $NEW_VER)"
58
+ exit 1
59
+ fi
60
+
61
+ echo "Version bumped: $BASE_VER -> $NEW_VER"
62
+
63
+ CHANGELOG="CHANGELOG/${NEW_VER}.md"
64
+
65
+ if [ ! -f "$CHANGELOG" ]; then
66
+ echo "ERROR: $CHANGELOG not found"
67
+ exit 1
68
+ fi
69
+
70
+ if grep -q "<!-- Add release notes here -->" "$CHANGELOG"; then
71
+ echo "ERROR: $CHANGELOG still contains the stub placeholder"
72
+ exit 1
73
+ fi
74
+
75
+ LINES=$(tail -n +2 "$CHANGELOG" | grep -vc '^$')
76
+ if [ "$LINES" -eq 0 ]; then
77
+ echo "ERROR: $CHANGELOG has no content beyond the heading"
78
+ exit 1
79
+ fi
80
+
81
+ echo "Changelog looks good"
82
+
83
+ release:
84
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
85
+ needs: [test-python]
86
+ runs-on: ubuntu-latest
87
+ permissions:
88
+ contents: write
89
+ outputs:
90
+ released: ${{ steps.release.outputs.released }}
91
+ steps:
92
+ - uses: actions/checkout@v7
93
+
94
+ - uses: astral-sh/setup-uv@v7
95
+
96
+ - name: Configure git identity
97
+ run: |
98
+ git config user.email "github-actions[bot]@users.noreply.github.com"
99
+ git config user.name "github-actions[bot]"
100
+
101
+ - name: Release
102
+ id: release
103
+ env:
104
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
105
+ run: |
106
+ VERSION=$(grep '^version' pyproject.toml | sed 's/.*"\(.*\)"/\1/')
107
+ if git ls-remote --tags origin | grep -q "refs/tags/v$VERSION$"; then
108
+ echo "Tag v$VERSION already exists - skipping release"
109
+ exit 0
110
+ fi
111
+ make release
112
+ echo "released=true" >> "$GITHUB_OUTPUT"
113
+
114
+ build-dist:
115
+ needs: [release]
116
+ if: needs.release.outputs.released == 'true'
117
+ runs-on: ubuntu-latest
118
+ permissions:
119
+ contents: read
120
+ steps:
121
+ - uses: actions/checkout@v7
122
+ with:
123
+ persist-credentials: false
124
+
125
+ - uses: astral-sh/setup-uv@v7
126
+ with:
127
+ enable-cache: false
128
+
129
+ - name: Build
130
+ run: uv build
131
+
132
+ - uses: actions/upload-artifact@v6
133
+ with:
134
+ name: dist
135
+ path: dist/
136
+
137
+ publish-pypi:
138
+ needs: [build-dist]
139
+ runs-on: ubuntu-latest
140
+ permissions:
141
+ id-token: write
142
+ environment:
143
+ name: pypi
144
+ url: https://pypi.org/p/isoperiod
145
+ steps:
146
+ - uses: actions/download-artifact@v6
147
+ with:
148
+ name: dist
149
+ path: dist/
150
+
151
+ - name: Publish to PyPI
152
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,167 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+ .benchmarks/
54
+ benchmark*.svg
55
+
56
+ # Translations
57
+ *.mo
58
+ *.pot
59
+
60
+ # Django stuff:
61
+ *.log
62
+ local_settings.py
63
+ db.sqlite3
64
+ db.sqlite3-journal
65
+
66
+ # Flask stuff:
67
+ instance/
68
+ .webassets-cache
69
+
70
+ # Scrapy stuff:
71
+ .scrapy
72
+
73
+ # Sphinx documentation
74
+ docs/_build/
75
+ # Per-member API pages generated by sphinx.ext.autosummary at build time.
76
+ docs/source/api/_api/
77
+ # Using the plot directive from code in src/examples will copy outputs to docs/src/examples. No need to commit this.
78
+ docs/src
79
+
80
+ # PyBuilder
81
+ .pybuilder/
82
+ target/
83
+
84
+ # Jupyter Notebook
85
+ .ipynb_checkpoints
86
+
87
+ # IPython
88
+ profile_default/
89
+ ipython_config.py
90
+
91
+ # pyenv
92
+ # For a library or package, you might want to ignore these files since the code is
93
+ # intended to run in multiple environments; otherwise, check them in:
94
+ # .python-version
95
+
96
+ # pipenv
97
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
98
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
99
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
100
+ # install all needed dependencies.
101
+ #Pipfile.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ #pdm.lock
113
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
+ # in version control.
115
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
+ .pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
+ __pypackages__/
122
+
123
+ # Celery stuff
124
+ celerybeat-schedule
125
+ celerybeat.pid
126
+
127
+ # SageMath parsed files
128
+ *.sage.py
129
+
130
+ # Environments
131
+ .env
132
+ .venv
133
+ env/
134
+ venv/
135
+ ENV/
136
+ env.bak/
137
+ venv.bak/
138
+
139
+ # Spyder project settings
140
+ .spyderproject
141
+ .spyproject
142
+
143
+ # Rope project settings
144
+ .ropeproject
145
+
146
+ # mkdocs documentation
147
+ /site
148
+
149
+ # mypy
150
+ .mypy_cache/
151
+ .dmypy.json
152
+ dmypy.json
153
+
154
+ # Pyre type checker
155
+ .pyre/
156
+
157
+ # pytype static type analyzer
158
+ .pytype/
159
+
160
+ # Cython debug symbols
161
+ cython_debug/
162
+
163
+ # PyCharm
164
+ .idea/
165
+
166
+ # VSCode
167
+ .vscode
@@ -0,0 +1,38 @@
1
+ # 1.0.0
2
+
3
+ The first stable, versioned public release of `isoperiod`. Developed within UKCEH's FDRI programme, the library
4
+ provides ISO 8601 period and duration arithmetic for environmental timeseries data, making the grid a timeseries is
5
+ measured on (i.e. its sampling resolution and periodicity) an object that can be compared and validated.
6
+
7
+ ## Features
8
+
9
+ ### Core
10
+
11
+ - **`Period`** - the central object representing a repeating period that splits the timeline into consecutive, numbered
12
+ intervals, covering both fixed-length (microsecond through to multi-day) and calendar-aligned (monthly, annual)
13
+ periods.
14
+ - Built from an ISO 8601 duration string (`"PT15M"`, `"P1D"`, `"P1Y"`), from a named unit (`Period.of_days(1)`), or
15
+ from a `datetime.timedelta`.
16
+ - Immutable, hashable and sortable, so periods work as dictionary keys, in sets, and in order of length.
17
+
18
+ ### Offsets and origins
19
+
20
+ - **Offsets** shift every interval boundary off its natural position, written as `<duration>+<offset>` - an
21
+ extension of the ISO 8601 duration format - so a hydrological day (`"P1D+T9H"`) or a water year (`"P1Y+9MT9H"`)
22
+ becomes a value that can be stored and passed around.
23
+ - **Origins** pin the grid to a chosen datetime, with the `"2024-01-01/P7D"` form.
24
+
25
+ ### Intervals and ordinals
26
+
27
+ - Every interval on the timeline carries an integer **ordinal**
28
+ - `ordinal()` and `datetime()` map between timestamps and intervals, and are inverses of one another.
29
+ - `floor()` snaps a timestamp to its interval start, `interval()` gives that interval's half-open bounds, and
30
+ `range()` walks every interval overlapping a window of time.
31
+
32
+ ### Comparison and alignment
33
+
34
+ - `is_aligned()` checks that a timestamp sits exactly on a period boundary - the check to run on incoming data
35
+ before trusting its claimed resolution.
36
+ - `is_subperiod_of()` reports whether one period's intervals nest cleanly inside another's, and `count()` gives how
37
+ many fit where that number is constant.
38
+ - `is_epoch_agnostic()` identifies periods that split the timeline the same way whatever epoch is used.
@@ -0,0 +1,48 @@
1
+ # 1.1.0
2
+
3
+ A minor release covering how a `Period` renders itself. `str()` now represents periods that carry an origin,
4
+ and `repr()` reports a time zone accurately.
5
+
6
+ The documentation was also reworked so that every example is executed and its real output published alongside it.
7
+
8
+ ## Changed
9
+
10
+ ### `str()` format
11
+
12
+ A period built with an origin previously rendered as its duration and derived offset, which discarded the origin and so
13
+ could not be parsed back. It now renders in the `<origin>/<duration>` form it was built from:
14
+
15
+ | | 1.0.0 | 1.1.0 |
16
+ |--------------------------------------------|-------------|-------------------------------|
17
+ | `str(Period.of("2024-01-01/P7D"))` | `"P7D+1D"` | `"2024-01-01/P7D"` |
18
+ | `str(Period.of("1883-01-01T09:00/P1D"))` | `"P1D+T9H"` | `"1883-01-01T09:00:00/P1D"` |
19
+ | `str(Period.of("2024-02-29T09:30/PT15M"))` | `"PT15M"` | `"2024-02-29T09:30:00/PT15M"` |
20
+
21
+ `Period.of(str(period)) == period` now holds for every period.
22
+
23
+ ### `repr()` is the string form plus the time zone
24
+
25
+ `repr()` now adds nothing to `str()` beyond the time zone, and only when there is one:
26
+
27
+ | | 1.0.0 | 1.1.0 |
28
+ |----------------|-------------------|------------------|
29
+ | naive | `PT15M[]` | `PT15M` |
30
+ | UTC | `PT1H[Z]` | `PT1H[Z]` |
31
+ | fixed offset | `PT1H[+05:30]` | `PT1H[+05:30]` |
32
+ | with an origin | `P7D+1D[]-105555` | `2024-01-01/P7D` |
33
+
34
+ ## Fixed
35
+
36
+ - **A named time zone reported the offset it used in the nineteenth century.** The offset was sampled at
37
+ `datetime.min`, so `repr()` of a period at `Europe/London` gave `P1D[-00:01:15]`, its Local Mean Time before standard
38
+ time was adopted. A `zoneinfo.ZoneInfo` is now identified by its key: `P1D[Europe/London]`.
39
+ - **Two different periods could share a `repr()`.** A `tzinfo` whose `utcoffset()` returns `None` - which
40
+ `datetime` permits - rendered as `PT1S[]`, identical to a naive period. Such a zone is now named after its class, so
41
+ no two unequal periods render alike.
42
+
43
+ ## Documentation
44
+
45
+ - Every example in the user guide is now executed when the documentation is built, and the output shown beneath it is
46
+ what that code produced rather than something written by hand. The code lives in `docs/source/examples/`, is linted
47
+ and type checked with the rest of the project, and is run by the test suite.
48
+ - Docstring examples use standard `>>>` doctests, collected by pytest, so a changed return value fails the build.
@@ -0,0 +1,23 @@
1
+ # 1.2.0
2
+
3
+ A minor release adding two properties for rendering a `Period` in English words.
4
+
5
+ ## Features
6
+
7
+ ### `verbose` and `descriptive`
8
+
9
+ `Period.verbose` spells a period's duration out in full, appending its offset or origin in brackets. `descriptive`
10
+ names the period's frequency with a common word where one exists (e.g. "Daily", "Yearly"), falling back to the plain
11
+ duration otherwise, and recognises two grids by name: the UK Water Day (`P1D+T9H`) and the UK Water Year (`P1Y+9MT9H`).
12
+
13
+ Examples:
14
+
15
+ | | `verbose` | `descriptive` |
16
+ |-------------------------------|------------------------------|------------------------------|
17
+ | `Period.of_years(1)` | `"1 year"` | `"Yearly"` |
18
+ | `Period.of_minutes(15)` | `"15 minutes"` | `"15 minutes"` |
19
+ | `Period.of("P1D+T9H")` | `"1 day (+9 hours)"` | `"Daily (UK Water Day)"` |
20
+ | `Period.of("2024-01-01/P7D")` | `"7 days (from 2024-01-01)"` | `"Weekly (from 2024-01-01)"` |
21
+
22
+ Both are for display only: neither is accepted by `Period.of` or any other constructor, and `str()` remains the
23
+ parseable form.
@@ -0,0 +1,30 @@
1
+ # 1.2.1
2
+
3
+ A patch release with no changes to the library. It is the first release published to [PyPI](https://pypi.org/project/isoperiod/).
4
+
5
+ ## Installation
6
+
7
+ isoperiod can now be installed from PyPI:
8
+
9
+ ```bash
10
+ pip install isoperiod
11
+ ```
12
+
13
+ or with uv:
14
+
15
+ ```bash
16
+ uv add isoperiod
17
+ ```
18
+
19
+ Installing from the git repository still works, but is no longer needed.
20
+
21
+ ## Packaging
22
+
23
+ - Each release merged to `main` is now built and published to PyPI automatically, using trusted publishing.
24
+ - Package metadata now includes keywords and classifiers for the supported Python versions (3.12 and 3.13) and for
25
+ the bundled type hints (`Typing :: Typed`).
26
+ - The README links to `LICENSE` and `CITATION.cff` are now full URLs, so they work on the PyPI project page.
27
+
28
+ ## Documentation
29
+
30
+ - The installation guide now installs from PyPI instead of the git repository.