wwvb 3.0.6__tar.gz → 3.0.8__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.
- {wwvb-3.0.6 → wwvb-3.0.8}/.github/workflows/test.yml +9 -2
- {wwvb-3.0.6 → wwvb-3.0.8}/.pre-commit-config.yaml +9 -15
- {wwvb-3.0.6 → wwvb-3.0.8}/Makefile +2 -0
- {wwvb-3.0.6/src/wwvb.egg-info → wwvb-3.0.8}/PKG-INFO +1 -1
- {wwvb-3.0.6 → wwvb-3.0.8}/adafruit_datetime.pyi +18 -34
- {wwvb-3.0.6 → wwvb-3.0.8}/conf.py +0 -1
- {wwvb-3.0.6 → wwvb-3.0.8}/pyproject.toml +5 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/src/uwwvb.py +5 -5
- {wwvb-3.0.6 → wwvb-3.0.8}/src/wwvb/__init__.py +35 -49
- {wwvb-3.0.6 → wwvb-3.0.8}/src/wwvb/__version__.py +2 -2
- {wwvb-3.0.6 → wwvb-3.0.8}/src/wwvb/decode.py +2 -7
- {wwvb-3.0.6 → wwvb-3.0.8}/src/wwvb/dut1table.py +1 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/src/wwvb/gen.py +4 -13
- {wwvb-3.0.6 → wwvb-3.0.8}/src/wwvb/iersdata.py +2 -4
- {wwvb-3.0.6 → wwvb-3.0.8}/src/wwvb/iersdata_dist.py +2 -3
- {wwvb-3.0.6 → wwvb-3.0.8}/src/wwvb/testcli.py +87 -41
- {wwvb-3.0.6 → wwvb-3.0.8}/src/wwvb/testdaylight.py +4 -12
- {wwvb-3.0.6 → wwvb-3.0.8}/src/wwvb/testls.py +1 -3
- {wwvb-3.0.6 → wwvb-3.0.8}/src/wwvb/testpm.py +3 -17
- {wwvb-3.0.6 → wwvb-3.0.8}/src/wwvb/testuwwvb.py +11 -25
- {wwvb-3.0.6 → wwvb-3.0.8}/src/wwvb/testwwvb.py +17 -52
- {wwvb-3.0.6 → wwvb-3.0.8}/src/wwvb/updateiers.py +8 -20
- {wwvb-3.0.6 → wwvb-3.0.8}/src/wwvb/wwvbtk.py +3 -7
- {wwvb-3.0.6 → wwvb-3.0.8/src/wwvb.egg-info}/PKG-INFO +1 -1
- {wwvb-3.0.6 → wwvb-3.0.8}/.coveragerc +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/.github/workflows/codeql.yml +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/.github/workflows/cron.yml +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/.github/workflows/release.yml +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/.gitignore +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/.pylintrc +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/LICENSES/Apache-2.0.txt +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/LICENSES/CC0-1.0.txt +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/LICENSES/GPL-3.0-only.txt +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/LICENSES/Unlicense.txt +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/README.md +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/_static/.empty +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/codecov.yml +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/requirements-dev.txt +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/setup.cfg +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/src/wwvb/py.typed +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/src/wwvb/tz.py +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/src/wwvb.egg-info/SOURCES.txt +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/src/wwvb.egg-info/dependency_links.txt +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/src/wwvb.egg-info/entry_points.txt +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/src/wwvb.egg-info/requires.txt +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/src/wwvb.egg-info/top_level.txt +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/1998leapsecond +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/2012leapsecond +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/all-headers +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/bar +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/both +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/cradek +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/duration +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/enddst-phase +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/enddst-phase-2 +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/endleapyear +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/leapday1 +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/leapday28 +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/leapday29 +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/negleapsecond +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/nextdst +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/nextst +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/nonleapday1 +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/nonleapday28 +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/phase +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/startdst +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/startdst-phase +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/startdst-phase-2 +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/startleapyear +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/startst +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/y2k +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/y2k-1 +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/y2k1 +0 -0
- {wwvb-3.0.6 → wwvb-3.0.8}/tests/y2k1-1 +0 -0
@@ -23,14 +23,21 @@ jobs:
|
|
23
23
|
- '3.11'
|
24
24
|
- '3.12'
|
25
25
|
- '3.13.0-alpha.0 - 3.13'
|
26
|
-
- 'pypy-3.9'
|
27
26
|
os-version:
|
28
27
|
- 'ubuntu-latest'
|
28
|
+
coverage-core:
|
29
|
+
- 'ctrace'
|
29
30
|
include:
|
30
31
|
- os-version: 'macos-latest'
|
31
32
|
python-version: '3.x'
|
32
33
|
- os-version: 'windows-latest'
|
33
34
|
python-version: '3.x'
|
35
|
+
- os-version: 'ubuntu-latest'
|
36
|
+
python-version: '3.12'
|
37
|
+
coverage-core: 'sysmon'
|
38
|
+
- os-version: 'ubuntu-latest'
|
39
|
+
python-version: 'pypy-3.10'
|
40
|
+
coverage-core: 'pytrace'
|
34
41
|
|
35
42
|
runs-on: ${{ matrix.os-version }}
|
36
43
|
steps:
|
@@ -51,7 +58,7 @@ jobs:
|
|
51
58
|
run: make mypy PYTHON=python
|
52
59
|
|
53
60
|
- name: Test
|
54
|
-
run: make coverage PYTHON=python
|
61
|
+
run: make coverage PYTHON=python COVERAGE_CORE=${{ matrix.coverage-core }}
|
55
62
|
|
56
63
|
- name: Upload Coverage as artifact
|
57
64
|
if: always()
|
@@ -6,10 +6,6 @@ default_language_version:
|
|
6
6
|
python: python3
|
7
7
|
|
8
8
|
repos:
|
9
|
-
- repo: https://github.com/psf/black
|
10
|
-
rev: 23.11.0
|
11
|
-
hooks:
|
12
|
-
- id: black
|
13
9
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
14
10
|
rev: v4.5.0
|
15
11
|
hooks:
|
@@ -19,17 +15,15 @@ repos:
|
|
19
15
|
- id: trailing-whitespace
|
20
16
|
exclude: tests
|
21
17
|
- repo: https://github.com/fsfe/reuse-tool
|
22
|
-
rev: v2.1.0
|
23
|
-
hooks:
|
24
|
-
- id: reuse
|
25
|
-
- repo: https://github.com/pycqa/pylint
|
26
18
|
rev: v3.0.1
|
27
19
|
hooks:
|
28
|
-
- id:
|
29
|
-
|
30
|
-
|
31
|
-
rev:
|
20
|
+
- id: reuse
|
21
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
22
|
+
# Ruff version.
|
23
|
+
rev: v0.3.5
|
32
24
|
hooks:
|
33
|
-
|
34
|
-
|
35
|
-
args: [
|
25
|
+
# Run the linter.
|
26
|
+
- id: ruff
|
27
|
+
args: [ --fix ]
|
28
|
+
# Run the formatter.
|
29
|
+
- id: ruff-format
|
@@ -55,6 +55,8 @@ BUILDDIR = _build
|
|
55
55
|
html:
|
56
56
|
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
57
57
|
|
58
|
+
# Pass the desired coverage tracer name to subprocesses
|
59
|
+
export COVERAGE_CORE
|
58
60
|
|
59
61
|
# Copyright (C) 2021 Jeff Epler <jepler@gmail.com>
|
60
62
|
# SPDX-FileCopyrightText: 2021 Jeff Epler
|
@@ -13,17 +13,11 @@ from typing import (
|
|
13
13
|
Tuple,
|
14
14
|
Type,
|
15
15
|
TypeVar,
|
16
|
-
Union,
|
17
16
|
overload,
|
18
17
|
)
|
19
18
|
|
20
19
|
_S = TypeVar("_S")
|
21
20
|
|
22
|
-
if sys.version_info >= (3,):
|
23
|
-
_Text = str
|
24
|
-
else:
|
25
|
-
_Text = Union[str, unicode]
|
26
|
-
|
27
21
|
MINYEAR: int
|
28
22
|
MAXYEAR: int
|
29
23
|
|
@@ -33,19 +27,17 @@ class tzinfo:
|
|
33
27
|
def dst(self, dt: Optional[datetime]) -> Optional[timedelta]: ...
|
34
28
|
def fromutc(self, dt: datetime) -> datetime: ...
|
35
29
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
def __hash__(self) -> int: ...
|
30
|
+
class timezone(tzinfo):
|
31
|
+
utc: ClassVar[timezone]
|
32
|
+
min: ClassVar[timezone]
|
33
|
+
max: ClassVar[timezone]
|
34
|
+
def __init__(self, offset: timedelta, name: str = ...) -> None: ...
|
35
|
+
def __hash__(self) -> int: ...
|
43
36
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
weekday: int
|
37
|
+
class _IsoCalendarDate(NamedTuple):
|
38
|
+
year: int
|
39
|
+
week: int
|
40
|
+
weekday: int
|
49
41
|
|
50
42
|
_tzinfo = tzinfo
|
51
43
|
|
@@ -74,7 +66,7 @@ class date:
|
|
74
66
|
@property
|
75
67
|
def day(self) -> int: ...
|
76
68
|
def ctime(self) -> str: ...
|
77
|
-
def strftime(self, fmt:
|
69
|
+
def strftime(self, fmt: str) -> str: ...
|
78
70
|
if sys.version_info >= (3,):
|
79
71
|
def __format__(self, fmt: str) -> str: ...
|
80
72
|
else:
|
@@ -102,10 +94,7 @@ class date:
|
|
102
94
|
def __hash__(self) -> int: ...
|
103
95
|
def weekday(self) -> int: ...
|
104
96
|
def isoweekday(self) -> int: ...
|
105
|
-
|
106
|
-
def isocalendar(self) -> _IsoCalendarDate: ...
|
107
|
-
else:
|
108
|
-
def isocalendar(self) -> Tuple[int, int, int]: ...
|
97
|
+
def isocalendar(self) -> _IsoCalendarDate: ...
|
109
98
|
|
110
99
|
class time:
|
111
100
|
min: ClassVar[time]
|
@@ -160,7 +149,7 @@ class time:
|
|
160
149
|
@classmethod
|
161
150
|
def fromisoformat(cls: Type[_S], time_string: str) -> _S: ...
|
162
151
|
|
163
|
-
def strftime(self, fmt:
|
152
|
+
def strftime(self, fmt: str) -> str: ...
|
164
153
|
if sys.version_info >= (3,):
|
165
154
|
def __format__(self, fmt: str) -> str: ...
|
166
155
|
else:
|
@@ -193,7 +182,7 @@ class time:
|
|
193
182
|
_date = date
|
194
183
|
_time = time
|
195
184
|
|
196
|
-
class timedelta(SupportsAbs[timedelta]):
|
185
|
+
class timedelta(SupportsAbs["timedelta"]):
|
197
186
|
min: ClassVar[timedelta]
|
198
187
|
max: ClassVar[timedelta]
|
199
188
|
resolution: ClassVar[timedelta]
|
@@ -337,9 +326,7 @@ class datetime(date):
|
|
337
326
|
def utcnow(cls: Type[_S]) -> _S: ...
|
338
327
|
if sys.version_info >= (3, 6):
|
339
328
|
@classmethod
|
340
|
-
def combine(
|
341
|
-
cls, date: _date, time: _time, tzinfo: Optional[_tzinfo] = ...
|
342
|
-
) -> datetime: ...
|
329
|
+
def combine(cls, date: _date, time: _time, tzinfo: Optional[_tzinfo] = ...) -> datetime: ...
|
343
330
|
else:
|
344
331
|
@classmethod
|
345
332
|
def combine(cls, date: _date, time: _time) -> datetime: ...
|
@@ -347,7 +334,7 @@ class datetime(date):
|
|
347
334
|
@classmethod
|
348
335
|
def fromisoformat(cls: Type[_S], date_string: str) -> _S: ...
|
349
336
|
|
350
|
-
def strftime(self, fmt:
|
337
|
+
def strftime(self, fmt: str) -> str: ...
|
351
338
|
if sys.version_info >= (3,):
|
352
339
|
def __format__(self, fmt: str) -> str: ...
|
353
340
|
else:
|
@@ -402,7 +389,7 @@ class datetime(date):
|
|
402
389
|
def isoformat(self, sep: str = ...) -> str: ...
|
403
390
|
|
404
391
|
@classmethod
|
405
|
-
def strptime(cls, date_string:
|
392
|
+
def strptime(cls, date_string: str, format: str) -> datetime: ...
|
406
393
|
def utcoffset(self) -> Optional[timedelta]: ...
|
407
394
|
def tzname(self) -> Optional[str]: ...
|
408
395
|
def dst(self) -> Optional[timedelta]: ...
|
@@ -424,7 +411,4 @@ class datetime(date):
|
|
424
411
|
def __hash__(self) -> int: ...
|
425
412
|
def weekday(self) -> int: ...
|
426
413
|
def isoweekday(self) -> int: ...
|
427
|
-
|
428
|
-
def isocalendar(self) -> _IsoCalendarDate: ...
|
429
|
-
else:
|
430
|
-
def isocalendar(self) -> Tuple[int, int, int]: ...
|
414
|
+
def isocalendar(self) -> _IsoCalendarDate: ...
|
@@ -16,9 +16,7 @@ always_mark = set((0, 9, 19, 29, 39, 49, 59))
|
|
16
16
|
always_zero = set((4, 10, 11, 14, 20, 21, 34, 35, 44, 54))
|
17
17
|
bcd_weights = (1, 2, 4, 8, 10, 20, 40, 80, 100, 200, 400, 800)
|
18
18
|
|
19
|
-
WWVBMinute = namedtuple(
|
20
|
-
"WWVBMinute", ["year", "days", "hour", "minute", "dst", "ut1", "ls", "ly"]
|
21
|
-
)
|
19
|
+
WWVBMinute = namedtuple("WWVBMinute", ["year", "days", "hour", "minute", "dst", "ut1", "ls", "ly"])
|
22
20
|
|
23
21
|
|
24
22
|
class WWVBDecoder:
|
@@ -30,7 +28,9 @@ class WWVBDecoder:
|
|
30
28
|
self.state = 1
|
31
29
|
|
32
30
|
def update(self, value: int) -> list[int] | None:
|
33
|
-
"""Update the _state machine when a new symbol is received.
|
31
|
+
"""Update the _state machine when a new symbol is received.
|
32
|
+
|
33
|
+
If a possible complete _minute is received, return it; otherwise, return None"""
|
34
34
|
result = None
|
35
35
|
if self.state == 1:
|
36
36
|
self.minute = []
|
@@ -87,7 +87,7 @@ def get_am_bcd(seq: list[int], *poslist: int) -> int | None:
|
|
87
87
|
return result
|
88
88
|
|
89
89
|
|
90
|
-
def decode_wwvb(
|
90
|
+
def decode_wwvb(
|
91
91
|
t: list[int] | None,
|
92
92
|
) -> WWVBMinute | None:
|
93
93
|
"""Convert a received minute of wwvb symbols to a WWVBMinute. Returns None if any error is detected."""
|
@@ -19,7 +19,7 @@ from .tz import Mountain
|
|
19
19
|
HOUR = datetime.timedelta(seconds=3600)
|
20
20
|
SECOND = datetime.timedelta(seconds=1)
|
21
21
|
DateOrDatetime = TypeVar("DateOrDatetime", datetime.date, datetime.datetime)
|
22
|
-
T = TypeVar("T")
|
22
|
+
T = TypeVar("T")
|
23
23
|
|
24
24
|
|
25
25
|
def require(x: Optional[T]) -> T:
|
@@ -49,9 +49,7 @@ def _maybe_warn_update(dt: datetime.date) -> None:
|
|
49
49
|
# prospective available now.
|
50
50
|
today = datetime.date.today()
|
51
51
|
if _date(dt) < today + datetime.timedelta(days=330):
|
52
|
-
warnings.warn(
|
53
|
-
"Note: Running `updateiers` may provide better DUT1 and LS information"
|
54
|
-
)
|
52
|
+
warnings.warn("Note: Running `updateiers` may provide better DUT1 and LS information")
|
55
53
|
|
56
54
|
|
57
55
|
def get_dut1(dt: DateOrDatetime, *, warn_outdated: bool = True) -> float:
|
@@ -110,9 +108,7 @@ def is_dst_change_day(t: datetime.date, tz: datetime.tzinfo = Mountain) -> bool:
|
|
110
108
|
return isdst(t, tz) != isdst(t + datetime.timedelta(1), tz)
|
111
109
|
|
112
110
|
|
113
|
-
def get_dst_change_hour(
|
114
|
-
t: DateOrDatetime, tz: datetime.tzinfo = Mountain
|
115
|
-
) -> Optional[int]:
|
111
|
+
def get_dst_change_hour(t: DateOrDatetime, tz: datetime.tzinfo = Mountain) -> Optional[int]:
|
116
112
|
"""Return the hour when DST changes"""
|
117
113
|
lt0 = datetime.datetime(t.year, t.month, t.day, hour=0, tzinfo=tz)
|
118
114
|
dst0 = lt0.dst()
|
@@ -291,7 +287,9 @@ def extract_bit(v: int, p: int) -> bool:
|
|
291
287
|
|
292
288
|
|
293
289
|
def hamming_parity(value: int) -> int:
|
294
|
-
"""Compute the "hamming parity" of a 26-bit number, such as the minute-of-century
|
290
|
+
"""Compute the "hamming parity" of a 26-bit number, such as the minute-of-century
|
291
|
+
|
292
|
+
For more details, see Enhanced WWVB Broadcast Format 4.3"""
|
295
293
|
parity = 0
|
296
294
|
for i in range(4, -1, -1):
|
297
295
|
bit = 0
|
@@ -324,7 +322,9 @@ _WWVBMinute = collections.namedtuple("_WWVBMinute", "year days hour min dst ut1
|
|
324
322
|
|
325
323
|
|
326
324
|
class WWVBMinute(_WWVBMinute):
|
327
|
-
"""Uniquely identifies a minute of time in the WWVB system.
|
325
|
+
"""Uniquely identifies a minute of time in the WWVB system.
|
326
|
+
|
327
|
+
To use ut1 and ls information from IERS, create a WWVBMinuteIERS value instead."""
|
328
328
|
|
329
329
|
year: int
|
330
330
|
hour: int
|
@@ -336,7 +336,7 @@ class WWVBMinute(_WWVBMinute):
|
|
336
336
|
|
337
337
|
epoch: int = 1970
|
338
338
|
|
339
|
-
def __new__(
|
339
|
+
def __new__(
|
340
340
|
cls,
|
341
341
|
year: int,
|
342
342
|
days: int,
|
@@ -391,7 +391,11 @@ class WWVBMinute(_WWVBMinute):
|
|
391
391
|
|
392
392
|
def __str__(self) -> str:
|
393
393
|
"""Implement str()"""
|
394
|
-
return
|
394
|
+
return (
|
395
|
+
f"year={self.year:4d} days={self.days:03d} hour={self.hour:02d} "
|
396
|
+
f"min={self.min:02d} dst={self.dst} ut1={self.ut1} ly={int(self.ly)} "
|
397
|
+
f"ls={int(self.ls)}"
|
398
|
+
)
|
395
399
|
|
396
400
|
def as_datetime_utc(self) -> datetime.datetime:
|
397
401
|
"""Convert to a UTC datetime"""
|
@@ -401,9 +405,7 @@ class WWVBMinute(_WWVBMinute):
|
|
401
405
|
|
402
406
|
as_datetime = as_datetime_utc
|
403
407
|
|
404
|
-
def as_datetime_local(
|
405
|
-
self, standard_time_offset: int = 7 * 3600, dst_observed: bool = True
|
406
|
-
) -> datetime.datetime:
|
408
|
+
def as_datetime_local(self, standard_time_offset: int = 7 * 3600, dst_observed: bool = True) -> datetime.datetime:
|
407
409
|
"""Convert to a local datetime according to the DST bits"""
|
408
410
|
u = self.as_datetime_utc()
|
409
411
|
offset = datetime.timedelta(seconds=-standard_time_offset)
|
@@ -471,12 +473,7 @@ class WWVBMinute(_WWVBMinute):
|
|
471
473
|
century = (self.year // 100) * 100
|
472
474
|
# note: This relies on timedelta seconds never including leapseconds!
|
473
475
|
return (
|
474
|
-
int(
|
475
|
-
(
|
476
|
-
self.as_datetime()
|
477
|
-
- datetime.datetime(century, 1, 1, tzinfo=datetime.timezone.utc)
|
478
|
-
).total_seconds()
|
479
|
-
)
|
476
|
+
int((self.as_datetime() - datetime.datetime(century, 1, 1, tzinfo=datetime.timezone.utc)).total_seconds())
|
480
477
|
// 60
|
481
478
|
)
|
482
479
|
|
@@ -497,9 +494,7 @@ class WWVBMinute(_WWVBMinute):
|
|
497
494
|
t.am[36] = t.am[38] = AmplitudeModulation(ut1_sign)
|
498
495
|
t.am[37] = AmplitudeModulation(not ut1_sign)
|
499
496
|
t._put_am_bcd(abs(self.ut1) // 100, 40, 41, 42, 43)
|
500
|
-
t._put_am_bcd(
|
501
|
-
self.year, 45, 46, 47, 48, 50, 51, 52, 53
|
502
|
-
) # Implicitly discards all but lowest 2 digits of year
|
497
|
+
t._put_am_bcd(self.year, 45, 46, 47, 48, 50, 51, 52, 53) # Implicitly discards all but lowest 2 digits of year
|
503
498
|
t.am[55] = AmplitudeModulation(self.ly)
|
504
499
|
t.am[56] = AmplitudeModulation(self.ls)
|
505
500
|
t._put_am_bcd(self.dst, 57, 58)
|
@@ -539,9 +534,7 @@ class WWVBMinute(_WWVBMinute):
|
|
539
534
|
for i in range(60):
|
540
535
|
t._put_pm_bit(i, full_seq[i + offset])
|
541
536
|
|
542
|
-
def fill_pm_timecode_regular(
|
543
|
-
self, t: "WWVBTimecode"
|
544
|
-
) -> None:
|
537
|
+
def fill_pm_timecode_regular(self, t: "WWVBTimecode") -> None:
|
545
538
|
"""Except during minutes 10..15 and 40..45, the amplitude signal holds 'regular information'"""
|
546
539
|
t._put_pm_bin(0, 13, SYNC_T)
|
547
540
|
|
@@ -604,24 +597,18 @@ class WWVBMinute(_WWVBMinute):
|
|
604
597
|
else:
|
605
598
|
self.fill_pm_timecode_regular(t)
|
606
599
|
|
607
|
-
def next_minute(
|
608
|
-
self, newut1: Optional[int] = None, newls: Optional[bool] = None
|
609
|
-
) -> "WWVBMinute":
|
600
|
+
def next_minute(self, newut1: Optional[int] = None, newls: Optional[bool] = None) -> "WWVBMinute":
|
610
601
|
"""Return an object representing the next minute"""
|
611
602
|
d = self.as_datetime() + datetime.timedelta(minutes=1)
|
612
603
|
return self.from_datetime(d, newut1, newls, self)
|
613
604
|
|
614
|
-
def previous_minute(
|
615
|
-
self, newut1: Optional[int] = None, newls: Optional[bool] = None
|
616
|
-
) -> "WWVBMinute":
|
605
|
+
def previous_minute(self, newut1: Optional[int] = None, newls: Optional[bool] = None) -> "WWVBMinute":
|
617
606
|
"""Return an object representing the previous minute"""
|
618
607
|
d = self.as_datetime() - datetime.timedelta(minutes=1)
|
619
608
|
return self.from_datetime(d, newut1, newls, self)
|
620
609
|
|
621
610
|
@classmethod
|
622
|
-
def _get_dut1_info(
|
623
|
-
cls: type, year: int, days: int, old_time: "Optional[WWVBMinute]" = None
|
624
|
-
) -> Tuple[int, bool]:
|
611
|
+
def _get_dut1_info(cls: type, year: int, days: int, old_time: "Optional[WWVBMinute]" = None) -> Tuple[int, bool]:
|
625
612
|
"""Return the DUT1 information for a given day, possibly propagating information from a previous timestamp"""
|
626
613
|
if old_time is not None:
|
627
614
|
if old_time.minute_length() != 60:
|
@@ -673,9 +660,7 @@ class WWVBMinute(_WWVBMinute):
|
|
673
660
|
return cls(u.tm_year, u.tm_yday, u.tm_hour, u.tm_min, ut1=newut1, ls=newls)
|
674
661
|
|
675
662
|
@classmethod
|
676
|
-
def from_timecode_am(
|
677
|
-
cls, t: "WWVBTimecode"
|
678
|
-
) -> Optional["WWVBMinute"]:
|
663
|
+
def from_timecode_am(cls, t: "WWVBTimecode") -> Optional["WWVBMinute"]:
|
679
664
|
"""Construct a WWVBMinute from a WWVBTimecode"""
|
680
665
|
for i in (0, 9, 19, 29, 39, 49, 59):
|
681
666
|
if t.am[i] != AmplitudeModulation.MARK:
|
@@ -717,9 +702,7 @@ class WWVBMinuteIERS(WWVBMinute):
|
|
717
702
|
"""A WWVBMinute that uses a database of DUT1 information"""
|
718
703
|
|
719
704
|
@classmethod
|
720
|
-
def _get_dut1_info(
|
721
|
-
cls, year: int, days: int, old_time: Optional[WWVBMinute] = None
|
722
|
-
) -> Tuple[int, bool]:
|
705
|
+
def _get_dut1_info(cls, year: int, days: int, old_time: Optional[WWVBMinute] = None) -> Tuple[int, bool]:
|
723
706
|
d = datetime.datetime(year, 1, 1) + datetime.timedelta(days - 1)
|
724
707
|
return int(round(get_dut1(d) * 10)) * 100, isls(d)
|
725
708
|
|
@@ -759,11 +742,14 @@ class WWVBTimecode:
|
|
759
742
|
phase: List[PhaseModulation]
|
760
743
|
|
761
744
|
def __init__(self, sz: int) -> None:
|
762
|
-
self.am = [AmplitudeModulation.UNSET] * sz
|
745
|
+
self.am = [AmplitudeModulation.UNSET] * sz
|
763
746
|
self.phase = [PhaseModulation.UNSET] * sz
|
764
747
|
|
765
748
|
def _get_am_bcd(self, *poslist: int) -> Optional[int]:
|
766
|
-
"""Convert
|
749
|
+
"""Convert AM data to BCD
|
750
|
+
|
751
|
+
The the bits ``self.am[poslist[i]]`` in MSB order are converted from
|
752
|
+
BCD to integer"""
|
767
753
|
pos = reversed(poslist)
|
768
754
|
val = [bool(self.am[p]) for p in pos]
|
769
755
|
result = 0
|
@@ -779,7 +765,11 @@ class WWVBTimecode:
|
|
779
765
|
return result
|
780
766
|
|
781
767
|
def _put_am_bcd(self, v: int, *poslist: int) -> None:
|
782
|
-
"""
|
768
|
+
"""Insert BCD coded data into the AM signal
|
769
|
+
|
770
|
+
The bits at ``self.am[poslist[i]]`` in MSB order are filled with
|
771
|
+
the conversion of `v` to BCD
|
772
|
+
Treating 'poslist' as a sequence of indices, update the AM signal with the value as a BCD number"""
|
783
773
|
pos = list(poslist)[::-1]
|
784
774
|
for p, b in zip(pos, bcd_bits(v)):
|
785
775
|
if b:
|
@@ -798,9 +788,7 @@ class WWVBTimecode:
|
|
798
788
|
|
799
789
|
def __str__(self) -> str:
|
800
790
|
"""implement str()"""
|
801
|
-
undefined = [
|
802
|
-
i for i in range(len(self.am)) if self.am[i] == AmplitudeModulation.UNSET
|
803
|
-
]
|
791
|
+
undefined = [i for i in range(len(self.am)) if self.am[i] == AmplitudeModulation.UNSET]
|
804
792
|
if undefined:
|
805
793
|
warnings.warn(f"am{undefined} is unset")
|
806
794
|
|
@@ -841,7 +829,6 @@ styles = {
|
|
841
829
|
}
|
842
830
|
|
843
831
|
|
844
|
-
# pylint: disable=too-many-arguments
|
845
832
|
def print_timecodes(
|
846
833
|
w: WWVBMinute,
|
847
834
|
minutes: int,
|
@@ -879,7 +866,6 @@ def print_timecodes(
|
|
879
866
|
w = w.next_minute()
|
880
867
|
|
881
868
|
|
882
|
-
# pylint: disable=too-many-arguments
|
883
869
|
def print_timecodes_json(
|
884
870
|
w: WWVBMinute,
|
885
871
|
minutes: int,
|
@@ -23,9 +23,7 @@ import wwvb
|
|
23
23
|
always_zero = set((4, 10, 11, 14, 20, 21, 34, 35, 44, 54))
|
24
24
|
|
25
25
|
|
26
|
-
def wwvbreceive() ->
|
27
|
-
Generator[Optional[wwvb.WWVBTimecode], wwvb.AmplitudeModulation, None]
|
28
|
-
): # pylint: disable=too-many-branches
|
26
|
+
def wwvbreceive() -> Generator[Optional[wwvb.WWVBTimecode], wwvb.AmplitudeModulation, None]:
|
29
27
|
"""A stateful decoder of WWVB signals"""
|
30
28
|
minute: List[wwvb.AmplitudeModulation] = []
|
31
29
|
state = 1
|
@@ -60,10 +58,7 @@ def wwvbreceive() -> (
|
|
60
58
|
elif len(minute) % 10 and value == wwvb.AmplitudeModulation.MARK:
|
61
59
|
# print("UNEXPECTED MARK")
|
62
60
|
state = 1
|
63
|
-
elif (
|
64
|
-
len(minute) - 1 in always_zero
|
65
|
-
and value != wwvb.AmplitudeModulation.ZERO
|
66
|
-
):
|
61
|
+
elif len(minute) - 1 in always_zero and value != wwvb.AmplitudeModulation.ZERO:
|
67
62
|
# print("UNEXPECTED NONZERO")
|
68
63
|
state = 1
|
69
64
|
elif len(minute) == 60:
|
@@ -16,9 +16,7 @@ import dateutil.parser
|
|
16
16
|
from . import WWVBMinute, WWVBMinuteIERS, print_timecodes, print_timecodes_json, styles
|
17
17
|
|
18
18
|
|
19
|
-
def parse_timespec(
|
20
|
-
ctx: Any, param: Any, value: List[str]
|
21
|
-
) -> datetime.datetime:
|
19
|
+
def parse_timespec(ctx: Any, param: Any, value: List[str]) -> datetime.datetime:
|
22
20
|
"""Parse a time specifier from the commandline"""
|
23
21
|
try:
|
24
22
|
if len(value) == 5:
|
@@ -26,9 +24,7 @@ def parse_timespec( # pylint: disable=unused-argument
|
|
26
24
|
return datetime.datetime(year, month, day, hour, minute)
|
27
25
|
if len(value) == 4:
|
28
26
|
year, yday, hour, minute = map(int, value)
|
29
|
-
return datetime.datetime(year, 1, 1, hour, minute) + datetime.timedelta(
|
30
|
-
days=yday - 1
|
31
|
-
)
|
27
|
+
return datetime.datetime(year, 1, 1, hour, minute) + datetime.timedelta(days=yday - 1)
|
32
28
|
if len(value) == 1:
|
33
29
|
return dateutil.parser.parse(value[0])
|
34
30
|
if len(value) == 0:
|
@@ -68,9 +64,7 @@ def parse_timespec( # pylint: disable=unused-argument
|
|
68
64
|
help="Force no leap second at the end of the month (Implies --no-iers)",
|
69
65
|
)
|
70
66
|
@click.option("--dut1", "-d", type=int, help="Force the DUT1 value (Implies --no-iers)")
|
71
|
-
@click.option(
|
72
|
-
"--minutes", "-m", default=10, help="Number of minutes to show (default: 10)"
|
73
|
-
)
|
67
|
+
@click.option("--minutes", "-m", default=10, help="Number of minutes to show (default: 10)")
|
74
68
|
@click.option(
|
75
69
|
"--style",
|
76
70
|
default="default",
|
@@ -91,7 +85,6 @@ def parse_timespec( # pylint: disable=unused-argument
|
|
91
85
|
help="Modulation to show (default: amplitude)",
|
92
86
|
)
|
93
87
|
@click.argument("timespec", type=str, nargs=-1, callback=parse_timespec)
|
94
|
-
# pylint: disable=too-many-arguments, too-many-locals
|
95
88
|
def main(
|
96
89
|
iers: bool,
|
97
90
|
leap_second: bool,
|
@@ -127,9 +120,7 @@ def main(
|
|
127
120
|
if style == "json":
|
128
121
|
print_timecodes_json(w, minutes, channel, file=sys.stdout)
|
129
122
|
else:
|
130
|
-
print_timecodes(
|
131
|
-
w, minutes, channel, style, all_timecodes=all_timecodes, file=sys.stdout
|
132
|
-
)
|
123
|
+
print_timecodes(w, minutes, channel, style, all_timecodes=all_timecodes, file=sys.stdout)
|
133
124
|
|
134
125
|
|
135
126
|
if __name__ == "__main__": # pragma no branch
|
@@ -21,11 +21,9 @@ for location in [
|
|
21
21
|
filename = os.path.join(location, "wwvbpy_iersdata.py")
|
22
22
|
if os.path.exists(filename):
|
23
23
|
with open(filename, encoding="utf-8") as f:
|
24
|
-
exec(f.read(), globals(), globals())
|
24
|
+
exec(f.read(), globals(), globals())
|
25
25
|
break
|
26
26
|
|
27
|
-
start = datetime.datetime.combine(DUT1_DATA_START, datetime.time()).replace(
|
28
|
-
tzinfo=datetime.timezone.utc
|
29
|
-
)
|
27
|
+
start = datetime.datetime.combine(DUT1_DATA_START, datetime.time()).replace(tzinfo=datetime.timezone.utc)
|
30
28
|
span = datetime.timedelta(days=len(DUT1_OFFSETS))
|
31
29
|
end = start + span
|
@@ -1,10 +1,9 @@
|
|
1
1
|
# -*- python3 -*-
|
2
|
+
# fmt: off
|
2
3
|
"""File generated from public data - not subject to copyright"""
|
3
4
|
# SPDX-FileCopyrightText: Public domain
|
4
5
|
# SPDX-License-Identifier: CC0-1.0
|
5
|
-
# fmt: off
|
6
6
|
# isort: skip_file
|
7
|
-
# pylint: disable=invalid-name
|
8
7
|
import datetime
|
9
8
|
__all__ = ['DUT1_DATA_START', 'DUT1_OFFSETS']
|
10
9
|
DUT1_DATA_START = datetime.date(1972, 1, 1)
|
@@ -35,5 +34,5 @@ DUT1_OFFSETS = str( # 19720101
|
|
35
34
|
+i*126+h*176+g*97+f*91+e*52+o*116+n*98+m*70+l*133+k*91+j*91 # 20140507
|
36
35
|
+i*77+h*140+g*91+f*84+e*70+d*34+n*72+m*76+l*66+k*53+j*56 # 20160831
|
37
36
|
+i*105+h*77+g*45+q*25+p*63+o*91+n*154+m*105+l*190+k*118 # 20190501
|
38
|
-
+j*105+i*807+j*376+k*
|
37
|
+
+j*105+i*807+j*376+k*775+l*67+k*2+l*6+k*133 # 20250405
|
39
38
|
)
|