wwvb 4.0.0a0__py3-none-any.whl → 5.0.0__py3-none-any.whl
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.
- uwwvb.py +1 -1
- wwvb/__init__.py +7 -5
- wwvb/__version__.py +2 -2
- wwvb/decode.py +5 -2
- wwvb/dut1table.py +2 -2
- wwvb/gen.py +2 -3
- wwvb/iersdata.json +1 -0
- wwvb/iersdata.json.license +2 -0
- wwvb/iersdata.py +20 -11
- wwvb/tz.py +1 -2
- wwvb/updateiers.py +19 -57
- wwvb/wwvbtk.py +5 -3
- {wwvb-4.0.0a0.dist-info → wwvb-5.0.0.dist-info}/METADATA +6 -5
- wwvb-5.0.0.dist-info/RECORD +18 -0
- {wwvb-4.0.0a0.dist-info → wwvb-5.0.0.dist-info}/WHEEL +1 -1
- wwvb/iersdata_dist.py +0 -38
- wwvb/testcli.py +0 -291
- wwvb/testdaylight.py +0 -60
- wwvb/testls.py +0 -63
- wwvb/testpm.py +0 -33
- wwvb/testuwwvb.py +0 -221
- wwvb/testwwvb.py +0 -403
- wwvb-4.0.0a0.dist-info/RECORD +0 -23
- {wwvb-4.0.0a0.dist-info → wwvb-5.0.0.dist-info}/entry_points.txt +0 -0
- {wwvb-4.0.0a0.dist-info → wwvb-5.0.0.dist-info}/top_level.txt +0 -0
wwvb/testcli.py
DELETED
@@ -1,291 +0,0 @@
|
|
1
|
-
#!/usr/bin/python3
|
2
|
-
"""Test most wwvblib commandline programs"""
|
3
|
-
|
4
|
-
# ruff: noqa: N802 D102
|
5
|
-
# Copyright (C) 2011-2020 Jeff Epler <jepler@gmail.com>
|
6
|
-
# SPDX-FileCopyrightText: 2021 Jeff Epler
|
7
|
-
#
|
8
|
-
# SPDX-License-Identifier: GPL-3.0-only
|
9
|
-
|
10
|
-
import json
|
11
|
-
import os
|
12
|
-
import subprocess
|
13
|
-
import sys
|
14
|
-
import unittest
|
15
|
-
from typing import Any, Sequence
|
16
|
-
|
17
|
-
coverage_add = ("-m", "coverage", "run", "--branch", "-p") if "COVERAGE_RUN" in os.environ else ()
|
18
|
-
|
19
|
-
|
20
|
-
class CLITestCase(unittest.TestCase):
|
21
|
-
"""Test various CLI commands within wwvbpy"""
|
22
|
-
|
23
|
-
def programOutput(self, *args: str) -> str:
|
24
|
-
env = os.environ.copy()
|
25
|
-
env["PYTHONIOENCODING"] = "utf-8"
|
26
|
-
return subprocess.check_output(args, stdin=subprocess.DEVNULL, encoding="utf-8", env=env)
|
27
|
-
|
28
|
-
def moduleArgs(self, *args: str) -> Sequence[str]:
|
29
|
-
return (sys.executable, *coverage_add, "-m", *args)
|
30
|
-
|
31
|
-
def moduleOutput(self, *args: str) -> str:
|
32
|
-
return self.programOutput(sys.executable, *coverage_add, "-m", *args)
|
33
|
-
|
34
|
-
def assertProgramOutput(self, expected: str, *args: str) -> None:
|
35
|
-
"""Check the output from invoking a program matches the expected"""
|
36
|
-
actual = self.programOutput(*args)
|
37
|
-
self.assertMultiLineEqual(expected, actual, f"args={args}")
|
38
|
-
|
39
|
-
def assertProgramOutputStarts(self, expected: str, *args: str) -> None:
|
40
|
-
"""Check the output from invoking a program matches the expected"""
|
41
|
-
actual = self.programOutput(*args)
|
42
|
-
self.assertMultiLineEqual(expected, actual[: len(expected)], f"args={args}")
|
43
|
-
|
44
|
-
def assertModuleOutput(self, expected: str, *args: str) -> None:
|
45
|
-
"""Check the output from invoking a `python -m modulename` program matches the expected"""
|
46
|
-
actual = self.moduleOutput(*args)
|
47
|
-
self.assertMultiLineEqual(expected, actual, f"args={args}")
|
48
|
-
|
49
|
-
def assertStarts(self, expected: str, actual: str, *args: str) -> None:
|
50
|
-
self.assertMultiLineEqual(expected, actual[: len(expected)], f"args={args}")
|
51
|
-
|
52
|
-
def assertModuleJson(self, expected: Any, *args: str) -> None:
|
53
|
-
"""Check the output from invoking a `python -m modulename` program matches the expected"""
|
54
|
-
actual = self.moduleOutput(*args)
|
55
|
-
self.assertEqual(json.loads(actual), expected)
|
56
|
-
|
57
|
-
def assertModuleOutputStarts(self, expected: str, *args: str) -> None:
|
58
|
-
"""Check the output from invoking a `python -m modulename` program matches the expected"""
|
59
|
-
actual = self.moduleOutput(*args)
|
60
|
-
self.assertStarts(expected, actual, *args)
|
61
|
-
|
62
|
-
def assertProgramError(self, *args: str) -> None:
|
63
|
-
"""Check the output from invoking a program fails"""
|
64
|
-
env = os.environ.copy()
|
65
|
-
env["PYTHONIOENCODING"] = "utf-8"
|
66
|
-
with self.assertRaises(subprocess.SubprocessError):
|
67
|
-
subprocess.check_output(
|
68
|
-
args,
|
69
|
-
stdin=subprocess.DEVNULL,
|
70
|
-
stderr=subprocess.DEVNULL,
|
71
|
-
encoding="utf-8",
|
72
|
-
env=env,
|
73
|
-
)
|
74
|
-
|
75
|
-
def assertModuleError(self, *args: str) -> None:
|
76
|
-
"""Check the output from invoking a `python -m modulename` program fails"""
|
77
|
-
self.assertProgramError(*self.moduleArgs(*args))
|
78
|
-
|
79
|
-
def test_gen(self) -> None:
|
80
|
-
"""Test wwvb.gen"""
|
81
|
-
self.assertModuleOutput(
|
82
|
-
"""\
|
83
|
-
WWVB timecode: year=2020 days=001 hour=12 min=30 dst=0 ut1=-200 ly=1 ls=0
|
84
|
-
2020-001 12:30 201100000200010001020000000002000100010200100001020000010002
|
85
|
-
""",
|
86
|
-
"wwvb.gen",
|
87
|
-
"-m",
|
88
|
-
"1",
|
89
|
-
"2020-1-1 12:30",
|
90
|
-
)
|
91
|
-
|
92
|
-
self.assertModuleOutput(
|
93
|
-
"""\
|
94
|
-
WWVB timecode: year=2020 days=001 hour=12 min=30 dst=0 ut1=-200 ly=1 ls=0
|
95
|
-
2020-001 12:30 201100000200010001020000000002000100010200100001020000010002
|
96
|
-
""",
|
97
|
-
"wwvb.gen",
|
98
|
-
"-m",
|
99
|
-
"1",
|
100
|
-
"2020",
|
101
|
-
"1",
|
102
|
-
"12",
|
103
|
-
"30",
|
104
|
-
)
|
105
|
-
|
106
|
-
self.assertModuleOutput(
|
107
|
-
"""\
|
108
|
-
WWVB timecode: year=2020 days=001 hour=12 min=30 dst=0 ut1=-200 ly=1 ls=0
|
109
|
-
2020-001 12:30 201100000200010001020000000002000100010200100001020000010002
|
110
|
-
""",
|
111
|
-
"wwvb.gen",
|
112
|
-
"-m",
|
113
|
-
"1",
|
114
|
-
"2020",
|
115
|
-
"1",
|
116
|
-
"1",
|
117
|
-
"12",
|
118
|
-
"30",
|
119
|
-
)
|
120
|
-
|
121
|
-
self.assertModuleError("wwvb.gen", "-m", "1", "2021", "7")
|
122
|
-
|
123
|
-
# Asserting a leap second
|
124
|
-
self.assertModuleOutput(
|
125
|
-
"""\
|
126
|
-
WWVB timecode: year=2020 days=001 hour=12 min=30 dst=0 ut1=-500 ly=1 ls=1
|
127
|
-
2020-001 12:30 201100000200010001020000000002000100010201010001020000011002
|
128
|
-
""",
|
129
|
-
"wwvb.gen",
|
130
|
-
"-m",
|
131
|
-
"1",
|
132
|
-
"-s",
|
133
|
-
"2020-1-1 12:30",
|
134
|
-
)
|
135
|
-
|
136
|
-
# Asserting a different ut1 value
|
137
|
-
self.assertModuleOutput(
|
138
|
-
"""\
|
139
|
-
WWVB timecode: year=2020 days=001 hour=12 min=30 dst=0 ut1=-300 ly=1 ls=0
|
140
|
-
2020-001 12:30 201100000200010001020000000002000100010200110001020000010002
|
141
|
-
""",
|
142
|
-
"wwvb.gen",
|
143
|
-
"-m",
|
144
|
-
"1",
|
145
|
-
"-d",
|
146
|
-
"-300",
|
147
|
-
"2020-1-1 12:30",
|
148
|
-
)
|
149
|
-
|
150
|
-
def test_dut1table(self) -> None:
|
151
|
-
"""Test the dut1table program"""
|
152
|
-
self.assertModuleOutputStarts(
|
153
|
-
"""\
|
154
|
-
1972-01-01 -0.2 182 LS on 1972-06-30 23:59:60 UTC
|
155
|
-
1972-07-01 0.8 123
|
156
|
-
1972-11-01 0.0 30
|
157
|
-
1972-12-01 -0.2 31 LS on 1972-12-31 23:59:60 UTC
|
158
|
-
""",
|
159
|
-
"wwvb.dut1table",
|
160
|
-
)
|
161
|
-
|
162
|
-
def test_json(self) -> None:
|
163
|
-
"""Test the JSON output format"""
|
164
|
-
self.assertModuleJson(
|
165
|
-
[
|
166
|
-
{
|
167
|
-
"year": 2021,
|
168
|
-
"days": 340,
|
169
|
-
"hour": 3,
|
170
|
-
"minute": 40,
|
171
|
-
"amplitude": "210000000200000001120011001002000000010200010001020001000002",
|
172
|
-
"phase": "111110011011010101000100100110011110001110111010111101001011",
|
173
|
-
},
|
174
|
-
{
|
175
|
-
"year": 2021,
|
176
|
-
"days": 340,
|
177
|
-
"hour": 3,
|
178
|
-
"minute": 41,
|
179
|
-
"amplitude": "210000001200000001120011001002000000010200010001020001000002",
|
180
|
-
"phase": "001010011100100011000101110000100001101000001111101100000010",
|
181
|
-
},
|
182
|
-
],
|
183
|
-
"wwvb.gen",
|
184
|
-
"-m",
|
185
|
-
"2",
|
186
|
-
"--style",
|
187
|
-
"json",
|
188
|
-
"--channel",
|
189
|
-
"both",
|
190
|
-
"2021-12-6 3:40",
|
191
|
-
)
|
192
|
-
self.assertModuleJson(
|
193
|
-
[
|
194
|
-
{
|
195
|
-
"year": 2021,
|
196
|
-
"days": 340,
|
197
|
-
"hour": 3,
|
198
|
-
"minute": 40,
|
199
|
-
"amplitude": "210000000200000001120011001002000000010200010001020001000002",
|
200
|
-
},
|
201
|
-
{
|
202
|
-
"year": 2021,
|
203
|
-
"days": 340,
|
204
|
-
"hour": 3,
|
205
|
-
"minute": 41,
|
206
|
-
"amplitude": "210000001200000001120011001002000000010200010001020001000002",
|
207
|
-
},
|
208
|
-
],
|
209
|
-
"wwvb.gen",
|
210
|
-
"-m",
|
211
|
-
"2",
|
212
|
-
"--style",
|
213
|
-
"json",
|
214
|
-
"--channel",
|
215
|
-
"amplitude",
|
216
|
-
"2021-12-6 3:40",
|
217
|
-
)
|
218
|
-
self.assertModuleJson(
|
219
|
-
[
|
220
|
-
{
|
221
|
-
"year": 2021,
|
222
|
-
"days": 340,
|
223
|
-
"hour": 3,
|
224
|
-
"minute": 40,
|
225
|
-
"phase": "111110011011010101000100100110011110001110111010111101001011",
|
226
|
-
},
|
227
|
-
{
|
228
|
-
"year": 2021,
|
229
|
-
"days": 340,
|
230
|
-
"hour": 3,
|
231
|
-
"minute": 41,
|
232
|
-
"phase": "001010011100100011000101110000100001101000001111101100000010",
|
233
|
-
},
|
234
|
-
],
|
235
|
-
"wwvb.gen",
|
236
|
-
"-m",
|
237
|
-
"2",
|
238
|
-
"--style",
|
239
|
-
"json",
|
240
|
-
"--channel",
|
241
|
-
"phase",
|
242
|
-
"2021-12-6 3:40",
|
243
|
-
)
|
244
|
-
|
245
|
-
def test_sextant(self) -> None:
|
246
|
-
"""Test the sextant output format"""
|
247
|
-
self.assertModuleOutput(
|
248
|
-
"""\
|
249
|
-
WWVB timecode: year=2021 days=340 hour=03 min=40 dst=0 ut1=-100 ly=0 ls=0 --style=sextant
|
250
|
-
2021-340 03:40 \
|
251
|
-
🬋🬩🬋🬹🬩🬹🬩🬹🬩🬹🬍🬎🬍🬎🬩🬹🬩🬹🬋🬍🬩🬹🬩🬹🬍🬎🬩🬹🬍🬎🬩🬹🬍🬎🬋🬹🬋🬎🬋🬍🬍🬎🬩🬹🬋🬎🬋🬎🬩🬹🬍🬎🬋🬎🬩🬹🬩🬹🬋🬍🬍🬎🬩🬹🬩🬹🬩🬹🬩🬹🬍🬎🬍🬎🬋🬎🬩🬹🬋🬩🬩🬹🬍🬎🬩🬹🬋🬹🬩🬹🬍🬎🬩🬹🬋🬎🬩🬹🬋🬩🬩🬹🬩🬹🬍🬎🬋🬹🬍🬎🬍🬎🬩🬹🬍🬎🬩🬹🬋🬩
|
252
|
-
|
253
|
-
2021-340 03:41 \
|
254
|
-
🬋🬍🬋🬎🬩🬹🬍🬎🬩🬹🬍🬎🬍🬎🬩🬹🬋🬹🬋🬩🬍🬎🬍🬎🬩🬹🬍🬎🬍🬎🬍🬎🬩🬹🬋🬹🬋🬎🬋🬍🬍🬎🬩🬹🬋🬎🬋🬹🬩🬹🬩🬹🬋🬎🬍🬎🬍🬎🬋🬍🬩🬹🬍🬎🬍🬎🬍🬎🬍🬎🬩🬹🬩🬹🬋🬎🬩🬹🬋🬍🬍🬎🬍🬎🬍🬎🬋🬎🬩🬹🬩🬹🬩🬹🬋🬹🬩🬹🬋🬍🬩🬹🬩🬹🬍🬎🬋🬎🬍🬎🬍🬎🬍🬎🬍🬎🬩🬹🬋🬍
|
255
|
-
|
256
|
-
""",
|
257
|
-
"wwvb.gen",
|
258
|
-
"-m",
|
259
|
-
"2",
|
260
|
-
"--style",
|
261
|
-
"sextant",
|
262
|
-
"2021-12-6 3:40",
|
263
|
-
)
|
264
|
-
|
265
|
-
def test_now(self) -> None:
|
266
|
-
"""Test outputting timecodes for 'now'"""
|
267
|
-
self.assertModuleOutputStarts(
|
268
|
-
"WWVB timecode: year=",
|
269
|
-
"wwvb.gen",
|
270
|
-
"-m",
|
271
|
-
"1",
|
272
|
-
)
|
273
|
-
|
274
|
-
def test_decode(self) -> None:
|
275
|
-
"""Test the commandline decoder"""
|
276
|
-
self.assertModuleOutput(
|
277
|
-
"""\
|
278
|
-
201100000200100001020011001012000000010200010001020001000002
|
279
|
-
year=2021 days=350 hour=22 min=30 dst=0 ut1=-100 ly=0 ls=0
|
280
|
-
""",
|
281
|
-
"wwvb.decode",
|
282
|
-
"201100000200100001020011001012000000010200010001020001000002",
|
283
|
-
)
|
284
|
-
|
285
|
-
self.assertModuleOutput(
|
286
|
-
"""\
|
287
|
-
201101111200100001020011001012000000010200010001020001000002
|
288
|
-
""",
|
289
|
-
"wwvb.decode",
|
290
|
-
"201101111200100001020011001012000000010200010001020001000002",
|
291
|
-
)
|
wwvb/testdaylight.py
DELETED
@@ -1,60 +0,0 @@
|
|
1
|
-
#!/usr/bin/python3
|
2
|
-
"""Test of daylight saving time calculations"""
|
3
|
-
|
4
|
-
# SPDX-FileCopyrightText: 2021 Jeff Epler
|
5
|
-
#
|
6
|
-
# SPDX-License-Identifier: GPL-3.0-only
|
7
|
-
|
8
|
-
import datetime
|
9
|
-
import unittest
|
10
|
-
|
11
|
-
import wwvb
|
12
|
-
from wwvb.tz import Mountain
|
13
|
-
|
14
|
-
|
15
|
-
class TestDaylight(unittest.TestCase):
|
16
|
-
"""Test of daylight saving time calculations"""
|
17
|
-
|
18
|
-
def test_onset(self) -> None:
|
19
|
-
"""Test that the onset of DST is the same in Mountain and WWVBMinute (which uses ls bits)"""
|
20
|
-
for h in [8, 9, 10]:
|
21
|
-
for dm in range(-1441, 1442):
|
22
|
-
d = datetime.datetime(2021, 3, 14, h, 0, tzinfo=datetime.timezone.utc) + datetime.timedelta(minutes=dm)
|
23
|
-
m = wwvb.WWVBMinute.from_datetime(d)
|
24
|
-
self.assertEqual(
|
25
|
-
m.as_datetime_local().replace(tzinfo=Mountain),
|
26
|
-
d.astimezone(Mountain),
|
27
|
-
)
|
28
|
-
|
29
|
-
def test_end(self) -> None:
|
30
|
-
"""Test that the end of DST is the same in Mountain and WWVBMinute (which uses ls bits)"""
|
31
|
-
for h in [7, 8, 9]:
|
32
|
-
for dm in range(-1441, 1442):
|
33
|
-
d = datetime.datetime(2021, 11, 7, h, 0, tzinfo=datetime.timezone.utc) + datetime.timedelta(minutes=dm)
|
34
|
-
m = wwvb.WWVBMinute.from_datetime(d)
|
35
|
-
self.assertEqual(
|
36
|
-
m.as_datetime_local().replace(tzinfo=Mountain),
|
37
|
-
d.astimezone(Mountain),
|
38
|
-
)
|
39
|
-
|
40
|
-
def test_midsummer(self) -> None:
|
41
|
-
"""Test that middle of DST is the same in Mountain and WWVBMinute (which uses ls bits)"""
|
42
|
-
for h in [7, 8, 9]:
|
43
|
-
for dm in (-1, 0, 1):
|
44
|
-
d = datetime.datetime(2021, 7, 7, h, 0, tzinfo=datetime.timezone.utc) + datetime.timedelta(minutes=dm)
|
45
|
-
m = wwvb.WWVBMinute.from_datetime(d)
|
46
|
-
self.assertEqual(
|
47
|
-
m.as_datetime_local().replace(tzinfo=Mountain),
|
48
|
-
d.astimezone(Mountain),
|
49
|
-
)
|
50
|
-
|
51
|
-
def test_midwinter(self) -> None:
|
52
|
-
"""Test that middle of standard time is the same in Mountain and WWVBMinute (which uses ls bits)"""
|
53
|
-
for h in [7, 8, 9]:
|
54
|
-
for dm in (-1, 0, 1):
|
55
|
-
d = datetime.datetime(2021, 12, 25, h, 0, tzinfo=datetime.timezone.utc) + datetime.timedelta(minutes=dm)
|
56
|
-
m = wwvb.WWVBMinute.from_datetime(d)
|
57
|
-
self.assertEqual(
|
58
|
-
m.as_datetime_local().replace(tzinfo=Mountain),
|
59
|
-
d.astimezone(Mountain),
|
60
|
-
)
|
wwvb/testls.py
DELETED
@@ -1,63 +0,0 @@
|
|
1
|
-
#!/usr/bin/python3
|
2
|
-
"""Leap seconds tests"""
|
3
|
-
|
4
|
-
# SPDX-FileCopyrightText: 2021 Jeff Epler
|
5
|
-
#
|
6
|
-
# SPDX-License-Identifier: GPL-3.0-only
|
7
|
-
|
8
|
-
import datetime
|
9
|
-
import unittest
|
10
|
-
|
11
|
-
import leapseconddata
|
12
|
-
|
13
|
-
import wwvb
|
14
|
-
|
15
|
-
from . import iersdata
|
16
|
-
|
17
|
-
ONE_DAY = datetime.timedelta(days=1)
|
18
|
-
|
19
|
-
|
20
|
-
def next_month(d: datetime.date) -> datetime.date:
|
21
|
-
"""Return the start of the next month after the day 'd'"""
|
22
|
-
d = d.replace(day=28)
|
23
|
-
while True:
|
24
|
-
d0 = d
|
25
|
-
d = d + ONE_DAY
|
26
|
-
if d.month != d0.month:
|
27
|
-
return d
|
28
|
-
|
29
|
-
|
30
|
-
class TestLeapSecond(unittest.TestCase):
|
31
|
-
"""Leap second tests"""
|
32
|
-
|
33
|
-
maxDiff = 9999
|
34
|
-
|
35
|
-
def test_leap(self) -> None:
|
36
|
-
"""Tests that the expected leap seconds all occur."""
|
37
|
-
ls = leapseconddata.LeapSecondData.from_standard_source()
|
38
|
-
assert ls.valid_until is not None
|
39
|
-
|
40
|
-
d = iersdata.start
|
41
|
-
e = min(iersdata.end, ls.valid_until)
|
42
|
-
bench = [ts.start for ts in ls.leap_seconds[1:]]
|
43
|
-
bench = [ts for ts in bench if d <= ts < e]
|
44
|
-
leap = []
|
45
|
-
while d < e:
|
46
|
-
nm = next_month(d)
|
47
|
-
eom = nm - ONE_DAY
|
48
|
-
month_ends_dut1 = wwvb.get_dut1(eom)
|
49
|
-
month_starts_dut1 = wwvb.get_dut1(nm)
|
50
|
-
our_is_ls = month_ends_dut1 * month_starts_dut1 < 0
|
51
|
-
if wwvb.isls(eom):
|
52
|
-
assert our_is_ls
|
53
|
-
self.assertLess(month_ends_dut1, 0)
|
54
|
-
self.assertGreater(month_starts_dut1, 0)
|
55
|
-
leap.append(nm)
|
56
|
-
else:
|
57
|
-
assert not our_is_ls
|
58
|
-
d = datetime.datetime.combine(nm, datetime.time()).replace(tzinfo=datetime.timezone.utc)
|
59
|
-
self.assertEqual(leap, bench)
|
60
|
-
|
61
|
-
|
62
|
-
if __name__ == "__main__": # pragma: no cover
|
63
|
-
unittest.main()
|
wwvb/testpm.py
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
#!/usr/bin/python3
|
2
|
-
"""Test Phase Modulation Signal"""
|
3
|
-
|
4
|
-
# SPDX-FileCopyrightText: 2021 Jeff Epler
|
5
|
-
#
|
6
|
-
# SPDX-License-Identifier: GPL-3.0-only
|
7
|
-
|
8
|
-
import unittest
|
9
|
-
|
10
|
-
import wwvb
|
11
|
-
|
12
|
-
|
13
|
-
class TestPhaseModulation(unittest.TestCase):
|
14
|
-
"""Test Phase Modulation Signal"""
|
15
|
-
|
16
|
-
def test_pm(self) -> None:
|
17
|
-
"""Compare the generated signal from a reference minute in NIST docs"""
|
18
|
-
ref_am = "201100000200010011120001010002011000101201000000120010010112"
|
19
|
-
|
20
|
-
ref_pm = "001110110100010010000011001000011000110100110100010110110110"
|
21
|
-
|
22
|
-
ref_minute = wwvb.WWVBMinuteIERS(2012, 186, 17, 30, dst=3)
|
23
|
-
ref_time = ref_minute.as_timecode()
|
24
|
-
|
25
|
-
test_am = ref_time.to_am_string(["0", "1", "2"])
|
26
|
-
test_pm = ref_time.to_pm_string(["0", "1"])
|
27
|
-
|
28
|
-
self.assertEqual(ref_am, test_am)
|
29
|
-
self.assertEqual(ref_pm, test_pm)
|
30
|
-
|
31
|
-
|
32
|
-
if __name__ == "__main__": # pragma: no cover
|
33
|
-
unittest.main()
|
wwvb/testuwwvb.py
DELETED
@@ -1,221 +0,0 @@
|
|
1
|
-
#!/usr/bin/python3
|
2
|
-
"""Test of uwwvb.py"""
|
3
|
-
# SPDX-FileCopyrightText: 2021 Jeff Epler
|
4
|
-
#
|
5
|
-
# SPDX-License-Identifier: GPL-3.0-only
|
6
|
-
|
7
|
-
# ruff: noqa: N802 D102
|
8
|
-
import datetime
|
9
|
-
import random
|
10
|
-
import sys
|
11
|
-
import unittest
|
12
|
-
from typing import Union
|
13
|
-
|
14
|
-
import adafruit_datetime
|
15
|
-
import uwwvb
|
16
|
-
import zoneinfo
|
17
|
-
|
18
|
-
import wwvb
|
19
|
-
|
20
|
-
EitherDatetimeOrNone = Union[None, datetime.datetime, adafruit_datetime.datetime]
|
21
|
-
|
22
|
-
|
23
|
-
class WWVBRoundtrip(unittest.TestCase):
|
24
|
-
"""tests of uwwvb.py"""
|
25
|
-
|
26
|
-
def assertDateTimeEqualExceptTzInfo(self, a: EitherDatetimeOrNone, b: EitherDatetimeOrNone) -> None:
|
27
|
-
"""Test two datetime objects for equality
|
28
|
-
|
29
|
-
This equality test excludes tzinfo, and allows adafruit_datetime and core datetime modules to compare equal
|
30
|
-
"""
|
31
|
-
assert a
|
32
|
-
assert b
|
33
|
-
self.assertEqual(
|
34
|
-
(a.year, a.month, a.day, a.hour, a.minute, a.second, a.microsecond),
|
35
|
-
(b.year, b.month, b.day, b.hour, b.minute, b.second, b.microsecond),
|
36
|
-
)
|
37
|
-
|
38
|
-
def test_decode(self) -> None:
|
39
|
-
"""Test decoding of some minutes including a leap second.
|
40
|
-
|
41
|
-
Each minute must decode and match the primary decoder.
|
42
|
-
"""
|
43
|
-
minute = wwvb.WWVBMinuteIERS.from_datetime(datetime.datetime(2012, 6, 30, 23, 50, tzinfo=datetime.timezone.utc))
|
44
|
-
assert minute
|
45
|
-
decoder = uwwvb.WWVBDecoder()
|
46
|
-
decoder.update(uwwvb.MARK)
|
47
|
-
any_leap_second = False
|
48
|
-
for _ in range(20):
|
49
|
-
timecode = minute.as_timecode()
|
50
|
-
decoded = None
|
51
|
-
if len(timecode.am) == 61:
|
52
|
-
any_leap_second = True
|
53
|
-
for code in timecode.am:
|
54
|
-
decoded = uwwvb.decode_wwvb(decoder.update(int(code))) or decoded
|
55
|
-
assert decoded
|
56
|
-
self.assertDateTimeEqualExceptTzInfo(
|
57
|
-
minute.as_datetime_utc(),
|
58
|
-
uwwvb.as_datetime_utc(decoded),
|
59
|
-
)
|
60
|
-
minute = minute.next_minute()
|
61
|
-
self.assertTrue(any_leap_second)
|
62
|
-
|
63
|
-
def test_roundtrip(self) -> None:
|
64
|
-
"""Test that some big range of times all decode the same as the primary decoder"""
|
65
|
-
dt = datetime.datetime(2002, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)
|
66
|
-
delta = datetime.timedelta(minutes=7182 if sys.implementation.name == "cpython" else 86400 - 7182)
|
67
|
-
while dt.year < 2013:
|
68
|
-
minute = wwvb.WWVBMinuteIERS.from_datetime(dt)
|
69
|
-
assert minute
|
70
|
-
decoded = uwwvb.decode_wwvb([int(i) for i in minute.as_timecode().am])
|
71
|
-
assert decoded
|
72
|
-
self.assertDateTimeEqualExceptTzInfo(minute.as_datetime_utc(), uwwvb.as_datetime_utc(decoded))
|
73
|
-
dt = dt + delta
|
74
|
-
|
75
|
-
def test_dst(self) -> None:
|
76
|
-
"""Test of DST as handled by the small decoder"""
|
77
|
-
for dt in (
|
78
|
-
datetime.datetime(2021, 3, 14, 8, 59, tzinfo=datetime.timezone.utc),
|
79
|
-
datetime.datetime(2021, 3, 14, 9, 00, tzinfo=datetime.timezone.utc),
|
80
|
-
datetime.datetime(2021, 3, 14, 9, 1, tzinfo=datetime.timezone.utc),
|
81
|
-
datetime.datetime(2021, 3, 15, 8, 59, tzinfo=datetime.timezone.utc),
|
82
|
-
datetime.datetime(2021, 3, 15, 9, 00, tzinfo=datetime.timezone.utc),
|
83
|
-
datetime.datetime(2021, 3, 15, 9, 1, tzinfo=datetime.timezone.utc),
|
84
|
-
datetime.datetime(2021, 11, 7, 8, 59, tzinfo=datetime.timezone.utc),
|
85
|
-
datetime.datetime(2021, 11, 7, 9, 00, tzinfo=datetime.timezone.utc),
|
86
|
-
datetime.datetime(2021, 11, 7, 9, 1, tzinfo=datetime.timezone.utc),
|
87
|
-
datetime.datetime(2021, 11, 8, 8, 59, tzinfo=datetime.timezone.utc),
|
88
|
-
datetime.datetime(2021, 11, 8, 9, 00, tzinfo=datetime.timezone.utc),
|
89
|
-
datetime.datetime(2021, 11, 8, 9, 1, tzinfo=datetime.timezone.utc),
|
90
|
-
datetime.datetime(2021, 7, 7, 9, 1, tzinfo=datetime.timezone.utc),
|
91
|
-
):
|
92
|
-
minute = wwvb.WWVBMinuteIERS.from_datetime(dt)
|
93
|
-
decoded = uwwvb.decode_wwvb([int(i) for i in minute.as_timecode().am])
|
94
|
-
assert decoded
|
95
|
-
self.assertDateTimeEqualExceptTzInfo(minute.as_datetime_local(), uwwvb.as_datetime_local(decoded))
|
96
|
-
|
97
|
-
decoded = uwwvb.decode_wwvb([int(i) for i in minute.as_timecode().am])
|
98
|
-
assert decoded
|
99
|
-
self.assertDateTimeEqualExceptTzInfo(
|
100
|
-
minute.as_datetime_local(dst_observed=False),
|
101
|
-
uwwvb.as_datetime_local(decoded, dst_observed=False),
|
102
|
-
)
|
103
|
-
|
104
|
-
def test_noise(self) -> None:
|
105
|
-
"""Test of the state-machine decoder when faced with pseudorandom noise"""
|
106
|
-
minute = wwvb.WWVBMinuteIERS.from_datetime(
|
107
|
-
datetime.datetime(2012, 6, 30, 23, 50, tzinfo=datetime.timezone.utc),
|
108
|
-
)
|
109
|
-
r = random.Random(408)
|
110
|
-
junk = [
|
111
|
-
r.choice(
|
112
|
-
[
|
113
|
-
wwvb.AmplitudeModulation.MARK,
|
114
|
-
wwvb.AmplitudeModulation.ONE,
|
115
|
-
wwvb.AmplitudeModulation.ZERO,
|
116
|
-
],
|
117
|
-
)
|
118
|
-
for _ in range(480)
|
119
|
-
]
|
120
|
-
timecode = minute.as_timecode()
|
121
|
-
test_input = [*junk, wwvb.AmplitudeModulation.MARK, *timecode.am]
|
122
|
-
decoder = uwwvb.WWVBDecoder()
|
123
|
-
for code in test_input[:-1]:
|
124
|
-
decoded = decoder.update(code)
|
125
|
-
self.assertIsNone(decoded)
|
126
|
-
minute_maybe = decoder.update(wwvb.AmplitudeModulation.MARK)
|
127
|
-
assert minute_maybe
|
128
|
-
decoded_minute = uwwvb.decode_wwvb(minute_maybe)
|
129
|
-
assert decoded_minute
|
130
|
-
self.assertDateTimeEqualExceptTzInfo(
|
131
|
-
minute.as_datetime_utc(),
|
132
|
-
uwwvb.as_datetime_utc(decoded_minute),
|
133
|
-
)
|
134
|
-
self.assertDateTimeEqualExceptTzInfo(
|
135
|
-
minute.as_datetime_local(),
|
136
|
-
uwwvb.as_datetime_local(decoded_minute),
|
137
|
-
)
|
138
|
-
|
139
|
-
def test_noise2(self) -> None:
|
140
|
-
"""Test of the full minute decoder with targeted errors to get full coverage"""
|
141
|
-
minute = wwvb.WWVBMinuteIERS.from_datetime(
|
142
|
-
datetime.datetime(2012, 6, 30, 23, 50, tzinfo=datetime.timezone.utc),
|
143
|
-
)
|
144
|
-
timecode = minute.as_timecode()
|
145
|
-
decoded = uwwvb.decode_wwvb([int(i) for i in timecode.am])
|
146
|
-
self.assertIsNotNone(decoded)
|
147
|
-
for position in uwwvb.always_mark:
|
148
|
-
test_input = [int(i) for i in timecode.am]
|
149
|
-
for noise in (0, 1):
|
150
|
-
test_input[position] = noise
|
151
|
-
decoded = uwwvb.decode_wwvb(test_input)
|
152
|
-
self.assertIsNone(decoded)
|
153
|
-
for position in uwwvb.always_zero:
|
154
|
-
test_input = [int(i) for i in timecode.am]
|
155
|
-
for noise in (1, 2):
|
156
|
-
test_input[position] = noise
|
157
|
-
decoded = uwwvb.decode_wwvb(test_input)
|
158
|
-
self.assertIsNone(decoded)
|
159
|
-
for i in range(8):
|
160
|
-
if i in (0b101, 0b010): # Test the 6 impossible bit-combos
|
161
|
-
continue
|
162
|
-
test_input = [int(i) for i in timecode.am]
|
163
|
-
test_input[36] = i & 1
|
164
|
-
test_input[37] = (i >> 1) & 1
|
165
|
-
test_input[38] = (i >> 2) & 1
|
166
|
-
decoded = uwwvb.decode_wwvb(test_input)
|
167
|
-
self.assertIsNone(decoded)
|
168
|
-
# Invalid year-day
|
169
|
-
test_input = [int(i) for i in timecode.am]
|
170
|
-
test_input[22] = 1
|
171
|
-
test_input[23] = 1
|
172
|
-
test_input[25] = 1
|
173
|
-
decoded = uwwvb.decode_wwvb(test_input)
|
174
|
-
self.assertIsNone(decoded)
|
175
|
-
|
176
|
-
def test_noise3(self) -> None:
|
177
|
-
"""Test impossible BCD values"""
|
178
|
-
minute = wwvb.WWVBMinuteIERS.from_datetime(
|
179
|
-
datetime.datetime(2012, 6, 30, 23, 50, tzinfo=datetime.timezone.utc),
|
180
|
-
)
|
181
|
-
timecode = minute.as_timecode()
|
182
|
-
|
183
|
-
for poslist in [
|
184
|
-
[1, 2, 3, 4], # tens minutes
|
185
|
-
[5, 6, 7, 8], # ones minutes
|
186
|
-
[15, 16, 17, 18], # tens hours
|
187
|
-
[25, 26, 27, 28], # tens days
|
188
|
-
[30, 31, 32, 33], # ones days
|
189
|
-
[40, 41, 42, 43], # tens years
|
190
|
-
[45, 46, 47, 48], # ones years
|
191
|
-
[50, 51, 52, 53], # ones dut1
|
192
|
-
]:
|
193
|
-
with self.subTest(test=poslist):
|
194
|
-
test_input = [int(i) for i in timecode.am]
|
195
|
-
for pi in poslist:
|
196
|
-
test_input[pi] = 1
|
197
|
-
decoded = uwwvb.decode_wwvb(test_input)
|
198
|
-
self.assertIsNone(decoded)
|
199
|
-
|
200
|
-
def test_str(self) -> None:
|
201
|
-
"""Test the str() of a WWVBDecoder"""
|
202
|
-
self.assertEqual(str(uwwvb.WWVBDecoder()), "<WWVBDecoder 1 []>")
|
203
|
-
|
204
|
-
def test_near_year_bug(self) -> None:
|
205
|
-
"""Test for a bug seen in another WWVB implementaiton
|
206
|
-
|
207
|
-
.. in which the hours after UTC midnight on 12-31 of a leap year would
|
208
|
-
be shown incorrectly. Check that we don't have that bug.
|
209
|
-
"""
|
210
|
-
minute = wwvb.WWVBMinuteIERS.from_datetime(datetime.datetime(2021, 1, 1, 0, 0, tzinfo=datetime.timezone.utc))
|
211
|
-
timecode = minute.as_timecode()
|
212
|
-
decoded = uwwvb.decode_wwvb([int(i) for i in timecode.am])
|
213
|
-
assert decoded
|
214
|
-
self.assertDateTimeEqualExceptTzInfo(
|
215
|
-
datetime.datetime(2020, 12, 31, 17, 00, tzinfo=zoneinfo.ZoneInfo("America/Denver")), # Mountain time!
|
216
|
-
uwwvb.as_datetime_local(decoded),
|
217
|
-
)
|
218
|
-
|
219
|
-
|
220
|
-
if __name__ == "__main__": # pragma no cover
|
221
|
-
unittest.main()
|