wwvb 6.0.1__tar.gz → 8.0.0rc1__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 (76) hide show
  1. wwvb-8.0.0rc1/.forgejo/workflows/ci.yml +65 -0
  2. wwvb-8.0.0rc1/.forgejo/workflows/cron.yml +37 -0
  3. {wwvb-6.0.1/.github → wwvb-8.0.0rc1/.forgejo}/workflows/release.yml +8 -13
  4. {wwvb-6.0.1 → wwvb-8.0.0rc1}/.gitignore +1 -0
  5. {wwvb-6.0.1 → wwvb-8.0.0rc1}/.pre-commit-config.yaml +9 -3
  6. {wwvb-6.0.1 → wwvb-8.0.0rc1}/Makefile +11 -2
  7. {wwvb-6.0.1/src/wwvb.egg-info → wwvb-8.0.0rc1}/PKG-INFO +4 -8
  8. {wwvb-6.0.1 → wwvb-8.0.0rc1}/README.md +1 -5
  9. {wwvb-6.0.1 → wwvb-8.0.0rc1}/pyproject.toml +5 -4
  10. {wwvb-6.0.1 → wwvb-8.0.0rc1}/requirements-dev.txt +5 -2
  11. {wwvb-6.0.1 → wwvb-8.0.0rc1}/src/wwvb/__init__.py +74 -47
  12. wwvb-8.0.0rc1/src/wwvb/__version__.py +34 -0
  13. {wwvb-6.0.1 → wwvb-8.0.0rc1}/src/wwvb/decode.py +1 -1
  14. {wwvb-6.0.1 → wwvb-8.0.0rc1}/src/wwvb/gen.py +6 -3
  15. {wwvb-6.0.1 → wwvb-8.0.0rc1}/src/wwvb/iersdata.json +1 -1
  16. {wwvb-6.0.1 → wwvb-8.0.0rc1}/src/wwvb/updateiers.py +4 -0
  17. {wwvb-6.0.1 → wwvb-8.0.0rc1}/src/wwvb/wwvbtk.py +7 -5
  18. {wwvb-6.0.1 → wwvb-8.0.0rc1/src/wwvb.egg-info}/PKG-INFO +4 -8
  19. {wwvb-6.0.1 → wwvb-8.0.0rc1}/src/wwvb.egg-info/SOURCES.txt +3 -4
  20. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/testcli.py +12 -6
  21. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/testls.py +0 -4
  22. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/testpm.py +0 -4
  23. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/testuwwvb.py +1 -5
  24. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/testwwvb.py +13 -8
  25. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/enddst-phase +8 -1
  26. wwvb-6.0.1/.github/workflows/cron.yml +0 -48
  27. wwvb-6.0.1/.github/workflows/test.yml +0 -126
  28. wwvb-6.0.1/codecov.yml +0 -0
  29. wwvb-6.0.1/src/wwvb/__version__.py +0 -21
  30. {wwvb-6.0.1 → wwvb-8.0.0rc1}/.readthedocs.yaml +0 -0
  31. {wwvb-6.0.1 → wwvb-8.0.0rc1}/LICENSES/CC0-1.0.txt +0 -0
  32. {wwvb-6.0.1 → wwvb-8.0.0rc1}/LICENSES/GPL-3.0-only.txt +0 -0
  33. {wwvb-6.0.1 → wwvb-8.0.0rc1}/LICENSES/Unlicense.txt +0 -0
  34. {wwvb-6.0.1 → wwvb-8.0.0rc1}/doc/_static/.empty +0 -0
  35. {wwvb-6.0.1 → wwvb-8.0.0rc1}/doc/conf.py +0 -0
  36. {wwvb-6.0.1 → wwvb-8.0.0rc1}/doc/index.rst +0 -0
  37. {wwvb-6.0.1 → wwvb-8.0.0rc1}/requirements.txt +0 -0
  38. {wwvb-6.0.1 → wwvb-8.0.0rc1}/setup.cfg +0 -0
  39. {wwvb-6.0.1 → wwvb-8.0.0rc1}/src/uwwvb.py +0 -0
  40. {wwvb-6.0.1 → wwvb-8.0.0rc1}/src/wwvb/dut1table.py +0 -0
  41. {wwvb-6.0.1 → wwvb-8.0.0rc1}/src/wwvb/iersdata.json.license +0 -0
  42. {wwvb-6.0.1 → wwvb-8.0.0rc1}/src/wwvb/iersdata.py +0 -0
  43. {wwvb-6.0.1 → wwvb-8.0.0rc1}/src/wwvb/py.typed +0 -0
  44. {wwvb-6.0.1 → wwvb-8.0.0rc1}/src/wwvb/tz.py +0 -0
  45. {wwvb-6.0.1 → wwvb-8.0.0rc1}/src/wwvb.egg-info/dependency_links.txt +0 -0
  46. {wwvb-6.0.1 → wwvb-8.0.0rc1}/src/wwvb.egg-info/entry_points.txt +0 -0
  47. {wwvb-6.0.1 → wwvb-8.0.0rc1}/src/wwvb.egg-info/requires.txt +0 -0
  48. {wwvb-6.0.1 → wwvb-8.0.0rc1}/src/wwvb.egg-info/top_level.txt +0 -0
  49. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/testdaylight.py +0 -0
  50. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/1998leapsecond +0 -0
  51. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/2012leapsecond +0 -0
  52. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/all-headers +0 -0
  53. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/bar +0 -0
  54. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/both +0 -0
  55. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/cradek +0 -0
  56. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/duration +0 -0
  57. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/enddst-phase-2 +0 -0
  58. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/endleapyear +0 -0
  59. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/leapday1 +0 -0
  60. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/leapday28 +0 -0
  61. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/leapday29 +0 -0
  62. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/negleapsecond +0 -0
  63. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/nextdst +0 -0
  64. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/nextst +0 -0
  65. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/nonleapday1 +0 -0
  66. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/nonleapday28 +0 -0
  67. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/phase +0 -0
  68. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/startdst +0 -0
  69. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/startdst-phase +0 -0
  70. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/startdst-phase-2 +0 -0
  71. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/startleapyear +0 -0
  72. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/startst +0 -0
  73. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/y2k +0 -0
  74. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/y2k-1 +0 -0
  75. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/y2k1 +0 -0
  76. {wwvb-6.0.1 → wwvb-8.0.0rc1}/test/wwvbgen_testcases/y2k1-1 +0 -0
@@ -0,0 +1,65 @@
1
+ # SPDX-FileCopyrightText: 2021-2025 Jeff Epler
2
+ #
3
+ # SPDX-License-Identifier: CC0-1.0
4
+
5
+ name: Test wwvbpy
6
+
7
+ on:
8
+ push:
9
+ pull_request:
10
+ release:
11
+ types:
12
+ - created # forgejo does not document the value "created"
13
+ - published
14
+
15
+ # (may not be used by forgejo, but is ignored for now)
16
+ concurrency:
17
+ group: ${{ forge.workflow }}-${{ forge.ref_name }}-${{ forge.event_name }}
18
+ cancel-in-progress: true
19
+
20
+ jobs:
21
+ build:
22
+ runs-on: codeberg-tiny
23
+ container:
24
+ image: docker.io/library/python:3-trixie
25
+ steps:
26
+ - name: Set up node
27
+ run: apt-get update && apt-get install -y --no-install-recommends nodejs
28
+
29
+ - uses: actions/checkout@v4
30
+
31
+ - name: Set up uv
32
+ run: pip install --break-system-packages uv
33
+
34
+ - name: Run main tests
35
+ run: |
36
+ for version in 3.9 3.10 3.11 3.12 3.13; do
37
+ echo "::group::Test with Python $version"
38
+ uv venv --python $version _venv_${version}
39
+ . _venv_${version}/bin/activate
40
+ uv pip install -r requirements-dev.txt
41
+ env PYTHONPATH=src python -mcoverage run -p -m unittest discover -s test
42
+ deactivate
43
+ echo "::endgroup::"
44
+ done
45
+
46
+ for version in 3.13; do
47
+ echo "::group::Aggregate coverage and run final checks"
48
+ . _venv_${version}/bin/activate
49
+ make -j $(nproc) -O mypy test_venv
50
+ python -mcoverage combine -q
51
+ python -mcoverage json --include "src/**/*.py"
52
+ python -mcoverage report --fail-under=100 --include "src/**/*.py"
53
+ prek --all-files
54
+ deactivate
55
+ echo "::endgroup::"
56
+ done
57
+
58
+ - name: Upload Coverage as artifact
59
+ if: always()
60
+ uses: actions/upload-artifact@v3
61
+ with:
62
+ path: coverage.json
63
+
64
+ - name: Check build directory size
65
+ run: du -shx
@@ -0,0 +1,37 @@
1
+ # SPDX-FileCopyrightText: 2021-2024 Jeff Epler
2
+ #
3
+ # SPDX-License-Identifier: CC0-1.0
4
+
5
+ name: Update DUT1 data
6
+
7
+ on:
8
+ schedule:
9
+ - cron: '0 10 2 * *'
10
+ workflow_dispatch:
11
+
12
+ jobs:
13
+ update-dut1:
14
+ permissions: write-all
15
+ runs-on: codeberg-tiny
16
+ container:
17
+ image: docker.io/library/python:3-trixie
18
+ steps:
19
+ - name: Set up node
20
+ run: apt-get update && apt-get install -y --no-install-recommends nodejs
21
+
22
+ - uses: actions/checkout@v4
23
+
24
+ - name: Install dependencies
25
+ run: pip install -e .
26
+
27
+ - name: Update DUT1 data
28
+ run: python -m wwvb.updateiers --dist
29
+
30
+ - name: Test
31
+ run: python -munittest discover -s test
32
+
33
+ - name: Commit updates
34
+ run: |
35
+ git config user.name "${FORGEJO_ACTOR} (actions cron)"
36
+ git config user.email "${FORGEJO_ACTOR}@noreply.invalid"
37
+ if git commit -m"update iersdata" src/wwvb/iersdata.json; then git push origin; fi
@@ -10,30 +10,25 @@ on:
10
10
 
11
11
  jobs:
12
12
  release:
13
-
14
- runs-on: ubuntu-24.04
13
+ permissions: write-all
14
+ runs-on: codeberg-tiny
15
+ container:
16
+ image: docker.io/library/python:3-trixie
15
17
  steps:
16
- - name: Dump GitHub context
17
- env:
18
- GITHUB_CONTEXT: ${{ toJson(github) }}
19
- run: echo "$GITHUB_CONTEXT"
18
+ - name: Set up node
19
+ run: apt-get update && apt-get install -y --no-install-recommends nodejs
20
20
 
21
21
  - uses: actions/checkout@v4
22
22
  with:
23
23
  persist-credentials: false
24
24
 
25
- - name: Set up Python
26
- uses: actions/setup-python@v5
27
- with:
28
- python-version: 3.9
29
-
30
25
  - name: Install deps
31
26
  run: |
32
27
  python -mpip install wheel
33
28
  python -mpip install -r requirements-dev.txt
34
29
 
35
30
  - name: Test
36
- run: make coverage
31
+ run: make coverage mypy
37
32
 
38
33
  - name: Build release
39
34
  run: python -mbuild
@@ -42,4 +37,4 @@ jobs:
42
37
  run: twine upload -u "$TWINE_USERNAME" -p "$TWINE_PASSWORD" dist/*
43
38
  env:
44
39
  TWINE_USERNAME: __token__
45
- TWINE_PASSWORD: ${{ secrets.pypi_token }}
40
+ TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
@@ -9,6 +9,7 @@
9
9
  /build
10
10
  /_build
11
11
  /coverage.xml
12
+ /coverage.json
12
13
  /dist
13
14
  /finals2000A.all.csv
14
15
  /htmlcov
@@ -8,7 +8,7 @@ default_language_version:
8
8
 
9
9
  repos:
10
10
  - repo: https://github.com/pre-commit/pre-commit-hooks
11
- rev: v5.0.0
11
+ rev: v6.0.0
12
12
  hooks:
13
13
  - id: check-yaml
14
14
  - id: end-of-file-fixer
@@ -21,10 +21,16 @@ repos:
21
21
  - id: reuse
22
22
  - repo: https://github.com/astral-sh/ruff-pre-commit
23
23
  # Ruff version.
24
- rev: v0.12.4
24
+ rev: v0.12.11
25
25
  hooks:
26
26
  # Run the linter.
27
- - id: ruff
27
+ - id: ruff-check
28
28
  args: [ --fix ]
29
29
  # Run the formatter.
30
30
  - id: ruff-format
31
+ - repo: https://github.com/asottile/pyupgrade
32
+ rev: v3.20.0
33
+ hooks:
34
+ - id: pyupgrade
35
+ args: [ --py39-plus ]
36
+ exclude: src/uwwvb.py # CircuitPython prevailing standard!
@@ -24,13 +24,13 @@ ENVPYTHON ?= _env/bin/python3
24
24
  endif
25
25
 
26
26
  .PHONY: default
27
- default: coverage mypy
27
+ default: coverage mypy pyright pyrefly
28
28
 
29
29
  COVERAGE_INCLUDE=--include "src/**/*.py"
30
30
  .PHONY: coverage
31
31
  coverage:
32
32
  $(Q)$(PYTHON) -mcoverage erase
33
- $(Q)env PYTHONPATH=src $(PYTHON) -mcoverage run --branch -p -m unittest discover -s test
33
+ $(Q)env PYTHONPATH=src $(PYTHON) -mcoverage run -p -m unittest discover -s test
34
34
  $(Q)$(PYTHON) -mcoverage combine -q
35
35
  $(Q)$(PYTHON) -mcoverage html $(COVERAGE_INCLUDE)
36
36
  $(Q)$(PYTHON) -mcoverage xml $(COVERAGE_INCLUDE)
@@ -46,6 +46,15 @@ test_venv:
46
46
  mypy:
47
47
  $(Q)mypy --strict --no-warn-unused-ignores src test
48
48
 
49
+ .PHONY: pyright
50
+ pyright:
51
+ $(Q)pyright src test
52
+
53
+ .PHONY: pyrefly
54
+ pyrefly:
55
+ $(Q)pyrefly check src test
56
+
57
+
49
58
  .PHONY: update
50
59
  update:
51
60
  $(Q)env PYTHONPATH=src $(PYTHON) -mwwvb.updateiers --dist
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wwvb
3
- Version: 6.0.1
3
+ Version: 8.0.0rc1
4
4
  Summary: Generate WWVB timecodes for any desired time
5
5
  Author-email: Jeff Epler <jepler@gmail.com>
6
- Project-URL: Source, https://github.com/jepler/wwvbpy
7
- Project-URL: Documentation, https://github.com/jepler/wwvbpy
6
+ Project-URL: Source, https://codeberg.org/jepler/wwvbpy
7
+ Project-URL: Documentation, https://codeberg.org/jepler/wwvbpy
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: Programming Language :: Python :: 3.9
10
10
  Classifier: Programming Language :: Python :: 3.10
@@ -29,11 +29,7 @@ SPDX-FileCopyrightText: 2021-2024 Jeff Epler
29
29
 
30
30
  SPDX-License-Identifier: GPL-3.0-only
31
31
  -->
32
- [![Test wwvbgen](https://github.com/jepler/wwvbpy/actions/workflows/test.yml/badge.svg)](https://github.com/jepler/wwvbpy/actions/workflows/test.yml)
33
- [![codecov](https://codecov.io/gh/jepler/wwvbpy/branch/main/graph/badge.svg?token=Exx0c3Gp65)](https://codecov.io/gh/jepler/wwvbpy)
34
- [![Update DUT1 data](https://github.com/jepler/wwvbpy/actions/workflows/cron.yml/badge.svg)](https://github.com/jepler/wwvbpy/actions/workflows/cron.yml)
35
32
  [![PyPI](https://img.shields.io/pypi/v/wwvb)](https://pypi.org/project/wwvb)
36
- [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/jepler/wwvbpy/main.svg)](https://results.pre-commit.ci/latest/github/jepler/wwvbpy/main)
37
33
 
38
34
  # Purpose
39
35
 
@@ -63,7 +59,7 @@ The package includes:
63
59
 
64
60
  # Development status
65
61
 
66
- The author ([@jepler](https://github.com/jepler)) occasionally develops and maintains this project, but
62
+ The author ([@jepler](https://unpythonic.net)) occasionally develops and maintains this project, but
67
63
  issues are not likely to be acted on. They would be interested in adding
68
64
  co-maintainer(s).
69
65
 
@@ -3,11 +3,7 @@ SPDX-FileCopyrightText: 2021-2024 Jeff Epler
3
3
 
4
4
  SPDX-License-Identifier: GPL-3.0-only
5
5
  -->
6
- [![Test wwvbgen](https://github.com/jepler/wwvbpy/actions/workflows/test.yml/badge.svg)](https://github.com/jepler/wwvbpy/actions/workflows/test.yml)
7
- [![codecov](https://codecov.io/gh/jepler/wwvbpy/branch/main/graph/badge.svg?token=Exx0c3Gp65)](https://codecov.io/gh/jepler/wwvbpy)
8
- [![Update DUT1 data](https://github.com/jepler/wwvbpy/actions/workflows/cron.yml/badge.svg)](https://github.com/jepler/wwvbpy/actions/workflows/cron.yml)
9
6
  [![PyPI](https://img.shields.io/pypi/v/wwvb)](https://pypi.org/project/wwvb)
10
- [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/jepler/wwvbpy/main.svg)](https://results.pre-commit.ci/latest/github/jepler/wwvbpy/main)
11
7
 
12
8
  # Purpose
13
9
 
@@ -37,7 +33,7 @@ The package includes:
37
33
 
38
34
  # Development status
39
35
 
40
- The author ([@jepler](https://github.com/jepler)) occasionally develops and maintains this project, but
36
+ The author ([@jepler](https://unpythonic.net)) occasionally develops and maintains this project, but
41
37
  issues are not likely to be acted on. They would be interested in adding
42
38
  co-maintainer(s).
43
39
 
@@ -39,8 +39,8 @@ classifiers = [
39
39
  ]
40
40
  requires-python = ">=3.9"
41
41
  [project.urls]
42
- Source = "https://github.com/jepler/wwvbpy"
43
- Documentation = "https://github.com/jepler/wwvbpy"
42
+ Source = "https://codeberg.org/jepler/wwvbpy"
43
+ Documentation = "https://codeberg.org/jepler/wwvbpy"
44
44
  [project.scripts]
45
45
  wwvbgen = "wwvb.gen:main"
46
46
  wwvbdecode = "wwvb.decode:main"
@@ -51,5 +51,6 @@ wwvbtk = "wwvb.wwvbtk:main"
51
51
  [[tool.mypy.overrides]]
52
52
  module = ["adafruit_datetime"]
53
53
  follow_untyped_imports = true
54
- [tool.coverage.report]
55
- exclude_also=["if TYPE_CHECKING:"]
54
+ [tool.coverage.run]
55
+ patch=["subprocess"]
56
+ branch=true
@@ -5,12 +5,14 @@ adafruit-circuitpython-datetime
5
5
  beautifulsoup4
6
6
  build
7
7
  click
8
- coverage >= 7.1.0
8
+ coverage >= 7.10.3
9
9
  mypy; implementation_name=="cpython"
10
+ pyright; implementation_name=="cpython"
11
+ pyrefly; implementation_name=="cpython"
10
12
  click>=8.1.5; implementation_name=="cpython"
11
13
  leapseconddata
12
14
  platformdirs
13
- pre-commit
15
+ prek
14
16
  python-dateutil
15
17
  requests; implementation_name=="cpython"
16
18
  setuptools>=68; implementation_name=="cpython"
@@ -22,5 +24,6 @@ twine; implementation_name=="cpython"
22
24
  types-beautifulsoup4; implementation_name=="cpython"
23
25
  types-python-dateutil; implementation_name=="cpython"
24
26
  types-requests; implementation_name=="cpython"
27
+ typing-extensions; implementation_name=="cpython"
25
28
  tzdata
26
29
  wheel
@@ -19,23 +19,36 @@ import datetime
19
19
  import enum
20
20
  import json
21
21
  import warnings
22
- from typing import TYPE_CHECKING, Any, NamedTuple, TextIO, TypeVar
22
+ from dataclasses import dataclass
23
+ from typing import ClassVar, Literal
23
24
 
24
25
  from . import iersdata
25
26
  from .tz import Mountain
26
27
 
28
+ WWVBChannel = Literal["amplitude", "phase", "both"]
29
+
30
+ TYPE_CHECKING = False
27
31
  if TYPE_CHECKING:
28
32
  from collections.abc import Generator
33
+ from typing import NotRequired, Self, TextIO, TypedDict, TypeVar
29
34
 
30
- HOUR = datetime.timedelta(seconds=3600)
31
- SECOND = datetime.timedelta(seconds=1)
32
- T = TypeVar("T")
35
+ class JsonMinute(TypedDict):
36
+ """Implementation detail
33
37
 
38
+ This is the Python object type that is serialized by `print_timecodes_json`
39
+ """
34
40
 
35
- def _removeprefix(s: str, p: str) -> str:
36
- if s.startswith(p):
37
- return s[len(p) :]
38
- return s
41
+ year: int
42
+ days: int
43
+ hour: int
44
+ minute: int
45
+ amplitude: NotRequired[str]
46
+ phase: NotRequired[str]
47
+
48
+ T = TypeVar("T")
49
+
50
+ HOUR = datetime.timedelta(seconds=3600)
51
+ SECOND = datetime.timedelta(seconds=1)
39
52
 
40
53
 
41
54
  def _date(dt: datetime.date) -> datetime.date:
@@ -339,8 +352,13 @@ class DstStatus(enum.IntEnum):
339
352
  """DST in effect all day today"""
340
353
 
341
354
 
342
- class _WWVBMinute(NamedTuple):
343
- """(implementation detail)"""
355
+ @dataclass(frozen=True)
356
+ class WWVBMinute:
357
+ """Uniquely identifies a minute of time in the WWVB system.
358
+
359
+ To use ``ut1`` and ``ls`` information from IERS, create a `WWVBMinuteIERS`
360
+ object instead.
361
+ """
344
362
 
345
363
  year: int
346
364
  """2-digit year within the WWVB epoch"""
@@ -351,7 +369,7 @@ class _WWVBMinute(NamedTuple):
351
369
  hour: int
352
370
  """UTC hour of day"""
353
371
 
354
- min: int
372
+ minute: int
355
373
  """Minute of hour"""
356
374
 
357
375
  dst: DstStatus
@@ -366,18 +384,10 @@ class _WWVBMinute(NamedTuple):
366
384
  ly: bool
367
385
  """Leap year flag"""
368
386
 
387
+ epoch: ClassVar[int] = 1970
369
388
 
370
- class WWVBMinute(_WWVBMinute):
371
- """Uniquely identifies a minute of time in the WWVB system.
372
-
373
- To use ``ut1`` and ``ls`` information from IERS, create a `WWVBMinuteIERS`
374
- object instead.
375
- """
376
-
377
- epoch: int = 1970
378
-
379
- def __new__( # noqa: PYI034
380
- cls,
389
+ def __init__(
390
+ self,
381
391
  year: int,
382
392
  days: int,
383
393
  hour: int,
@@ -387,7 +397,7 @@ class WWVBMinute(_WWVBMinute):
387
397
  *,
388
398
  ls: bool | None = None,
389
399
  ly: bool | None = None,
390
- ) -> WWVBMinute:
400
+ ) -> None:
391
401
  """Construct a WWVBMinute
392
402
 
393
403
  :param year: The 2- or 4-digit year. This parameter is converted by the `full_year` method.
@@ -401,15 +411,23 @@ class WWVBMinute(_WWVBMinute):
401
411
  :param ls: Leap second warning flag
402
412
  :param ly: Leap year flag
403
413
  """
404
- dst = cls.get_dst(year, days) if dst is None else DstStatus(dst)
414
+ dst = self.get_dst(year, days) if dst is None else DstStatus(dst)
405
415
  if ut1 is None and ls is None:
406
- ut1, ls = cls._get_dut1_info(year, days)
416
+ ut1, ls = self._get_dut1_info(year, days)
407
417
  elif ut1 is None or ls is None:
408
418
  raise ValueError("sepecify both ut1 and ls or neither one")
409
- year = cls.full_year(year)
419
+ year = self.full_year(year)
410
420
  if ly is None:
411
421
  ly = isly(year)
412
- return _WWVBMinute.__new__(cls, year, days, hour, minute, dst, ut1, ls, ly)
422
+
423
+ super().__setattr__("year", year)
424
+ super().__setattr__("days", days)
425
+ super().__setattr__("hour", hour)
426
+ super().__setattr__("minute", minute)
427
+ super().__setattr__("dst", dst)
428
+ super().__setattr__("ut1", ut1)
429
+ super().__setattr__("ls", ls)
430
+ super().__setattr__("ly", ly)
413
431
 
414
432
  @classmethod
415
433
  def full_year(cls, year: int) -> int:
@@ -443,7 +461,7 @@ class WWVBMinute(_WWVBMinute):
443
461
  """Implement str()"""
444
462
  return (
445
463
  f"year={self.year:4d} days={self.days:03d} hour={self.hour:02d} "
446
- f"min={self.min:02d} dst={self.dst} ut1={self.ut1} ly={int(self.ly)} "
464
+ f"min={self.minute:02d} dst={self.dst} ut1={self.ut1} ly={int(self.ly)} "
447
465
  f"ls={int(self.ls)}"
448
466
  )
449
467
 
@@ -453,7 +471,7 @@ class WWVBMinute(_WWVBMinute):
453
471
  The returned object has ``tzinfo=datetime.timezone.utc``.
454
472
  """
455
473
  d = datetime.datetime(self.year, 1, 1, tzinfo=datetime.timezone.utc)
456
- d += datetime.timedelta(self.days - 1, self.hour * 3600 + self.min * 60)
474
+ d += datetime.timedelta(self.days - 1, self.hour * 3600 + self.minute * 60)
457
475
  return d
458
476
 
459
477
  as_datetime = as_datetime_utc
@@ -507,7 +525,7 @@ class WWVBMinute(_WWVBMinute):
507
525
  return 60
508
526
  if not self._is_end_of_month():
509
527
  return 60
510
- if self.hour != 23 or self.min != 59:
528
+ if self.hour != 23 or self.minute != 59:
511
529
  return 60
512
530
  if self.ut1 > 0:
513
531
  return 59
@@ -551,7 +569,7 @@ class WWVBMinute(_WWVBMinute):
551
569
  t.am[60] = AmplitudeModulation.MARK
552
570
  for i in [4, 10, 11, 14, 20, 21, 24, 34, 35, 44, 54]:
553
571
  t.am[i] = AmplitudeModulation.ZERO
554
- t._put_am_bcd(self.min, 1, 2, 3, 5, 6, 7, 8)
572
+ t._put_am_bcd(self.minute, 1, 2, 3, 5, 6, 7, 8)
555
573
  t._put_am_bcd(self.hour, 12, 13, 15, 16, 17, 18)
556
574
  t._put_am_bcd(self.days, 22, 23, 25, 26, 27, 28, 30, 31, 32, 33)
557
575
  ut1_sign = self.ut1 >= 0
@@ -565,14 +583,14 @@ class WWVBMinute(_WWVBMinute):
565
583
 
566
584
  def _fill_pm_timecode_extended(self, t: WWVBTimecode) -> None:
567
585
  """During minutes 10..15 and 40..45, the amplitude signal holds 'extended information'"""
568
- assert 10 <= self.min < 16 or 40 <= self.min < 46
569
- minno = self.min % 10
586
+ assert 10 <= self.minute < 16 or 40 <= self.minute < 46
587
+ minno = self.minute % 10
570
588
  assert minno < 6
571
589
 
572
590
  dst = self.dst
573
591
  # Note that these are 1 different than Table 11
574
592
  # because our LFSR sequence is zero-based
575
- seqno = (self.min // 30) * 2
593
+ seqno = (self.minute // 30) * 2
576
594
  if dst == 0:
577
595
  pass
578
596
  elif dst == 3:
@@ -655,17 +673,17 @@ class WWVBMinute(_WWVBMinute):
655
673
 
656
674
  def _fill_pm_timecode(self, t: WWVBTimecode) -> None:
657
675
  """Fill the phase portion of a timecode object"""
658
- if 10 <= self.min < 16 or 40 <= self.min < 46:
676
+ if 10 <= self.minute < 16 or 40 <= self.minute < 46:
659
677
  self._fill_pm_timecode_extended(t)
660
678
  else:
661
679
  self._fill_pm_timecode_regular(t)
662
680
 
663
- def next_minute(self, *, newut1: int | None = None, newls: bool | None = None) -> WWVBMinute:
681
+ def next_minute(self, *, newut1: int | None = None, newls: bool | None = None) -> Self:
664
682
  """Return an object representing the next minute"""
665
683
  d = self.as_datetime() + datetime.timedelta(minutes=1)
666
684
  return self.from_datetime(d, newut1=newut1, newls=newls, old_time=self)
667
685
 
668
- def previous_minute(self, *, newut1: int | None = None, newls: bool | None = None) -> WWVBMinute:
686
+ def previous_minute(self, *, newut1: int | None = None, newls: bool | None = None) -> Self:
669
687
  """Return an object representing the previous minute"""
670
688
  d = self.as_datetime() - datetime.timedelta(minutes=1)
671
689
  return self.from_datetime(d, newut1=newut1, newls=newls, old_time=self)
@@ -684,9 +702,9 @@ class WWVBMinute(_WWVBMinute):
684
702
  return 0, False
685
703
 
686
704
  @classmethod
687
- def fromstring(cls, s: str) -> WWVBMinute:
705
+ def fromstring(cls, s: str) -> Self:
688
706
  """Construct a WWVBMinute from a string representation created by print_timecodes"""
689
- s = _removeprefix(s, "WWVB timecode: ")
707
+ s = s.removeprefix("WWVB timecode: ")
690
708
  d: dict[str, int] = {}
691
709
  for part in s.split():
692
710
  k, v = part.split("=")
@@ -700,7 +718,7 @@ class WWVBMinute(_WWVBMinute):
700
718
  dst = d.pop("dst", None)
701
719
  ut1 = d.pop("ut1", None)
702
720
  ls = d.pop("ls", None)
703
- d.pop("ly", None)
721
+ d.pop("ly", None) # Always use calculated ly flag
704
722
  if d:
705
723
  raise ValueError(f"Invalid options: {d}")
706
724
  return cls(year, days, hour, minute, dst, ut1=ut1, ls=None if ls is None else bool(ls))
@@ -713,7 +731,7 @@ class WWVBMinute(_WWVBMinute):
713
731
  newut1: int | None = None,
714
732
  newls: bool | None = None,
715
733
  old_time: WWVBMinute | None = None,
716
- ) -> WWVBMinute:
734
+ ) -> Self:
717
735
  """Construct a WWVBMinute from a datetime, possibly specifying ut1/ls data or propagating it from an old time"""
718
736
  u = d.utctimetuple()
719
737
  if newls is None and newut1 is None:
@@ -721,7 +739,7 @@ class WWVBMinute(_WWVBMinute):
721
739
  return cls(u.tm_year, u.tm_yday, u.tm_hour, u.tm_min, ut1=newut1, ls=newls)
722
740
 
723
741
  @classmethod
724
- def from_timecode_am(cls, t: WWVBTimecode) -> WWVBMinute | None: # noqa: PLR0912
742
+ def from_timecode_am(cls, t: WWVBTimecode) -> Self | None: # noqa: PLR0912
725
743
  """Construct a WWVBMinute from a WWVBTimecode"""
726
744
  for i in (0, 9, 19, 29, 39, 49, 59):
727
745
  if t.am[i] != AmplitudeModulation.MARK:
@@ -764,6 +782,15 @@ class WWVBMinute(_WWVBMinute):
764
782
  return None
765
783
  return cls(year, days, hour, minute, dst, ut1, ls=ls, ly=ly)
766
784
 
785
+ @property
786
+ def min(self) -> int:
787
+ """Deprecated alias for `WWVBMinute.minute`
788
+
789
+ Update your code to use the `minute` property instead of the `min` property.
790
+ """
791
+ warnings.warn("WWVBMinute.min property is deprecated", category=DeprecationWarning, stacklevel=1)
792
+ return self.minute
793
+
767
794
 
768
795
  class WWVBMinuteIERS(WWVBMinute):
769
796
  """A WWVBMinute that uses a database of DUT1 information"""
@@ -915,7 +942,7 @@ styles = {
915
942
  def print_timecodes(
916
943
  w: WWVBMinute,
917
944
  minutes: int,
918
- channel: str,
945
+ channel: WWVBChannel,
919
946
  style: str,
920
947
  file: TextIO,
921
948
  *,
@@ -934,7 +961,7 @@ def print_timecodes(
934
961
  print(file=file)
935
962
  print(f"WWVB timecode: {w!s}{channel_text}{style_text}", file=file)
936
963
  first = False
937
- pfx = f"{w.year:04d}-{w.days:03d} {w.hour:02d}:{w.min:02d} "
964
+ pfx = f"{w.year:04d}-{w.days:03d} {w.hour:02d}:{w.minute:02d} "
938
965
  tc = w.as_timecode()
939
966
  if len(style_chars) == 6:
940
967
  print(f"{pfx} {tc.to_both_string(style_chars)}", file=file)
@@ -952,7 +979,7 @@ def print_timecodes(
952
979
  def print_timecodes_json(
953
980
  w: WWVBMinute,
954
981
  minutes: int,
955
- channel: str,
982
+ channel: WWVBChannel,
956
983
  file: TextIO,
957
984
  ) -> None:
958
985
  """Print a range of timecodes in JSON format.
@@ -972,11 +999,11 @@ def print_timecodes_json(
972
999
  """
973
1000
  result = []
974
1001
  for _ in range(minutes):
975
- data: dict[str, Any] = {
1002
+ data: JsonMinute = {
976
1003
  "year": w.year,
977
1004
  "days": w.days,
978
1005
  "hour": w.hour,
979
- "minute": w.min,
1006
+ "minute": w.minute,
980
1007
  }
981
1008
 
982
1009
  tc = w.as_timecode()
@@ -0,0 +1,34 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
12
+
13
+ TYPE_CHECKING = False
14
+ if TYPE_CHECKING:
15
+ from typing import Tuple
16
+ from typing import Union
17
+
18
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
20
+ else:
21
+ VERSION_TUPLE = object
22
+ COMMIT_ID = object
23
+
24
+ version: str
25
+ __version__: str
26
+ __version_tuple__: VERSION_TUPLE
27
+ version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
30
+
31
+ __version__ = version = '8.0.0rc1'
32
+ __version_tuple__ = version_tuple = (8, 0, 0, 'rc1')
33
+
34
+ __commit_id__ = commit_id = 'g5b40d06db'
@@ -6,10 +6,10 @@
6
6
  from __future__ import annotations
7
7
 
8
8
  import sys
9
- from typing import TYPE_CHECKING
10
9
 
11
10
  import wwvb
12
11
 
12
+ TYPE_CHECKING = False
13
13
  if TYPE_CHECKING:
14
14
  from collections.abc import Generator
15
15