wwvb 4.0.0a0__py3-none-any.whl → 4.1.0a0__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.
- leapseconddata/__init__.py +342 -0
- leapseconddata/__main__.py +169 -0
- {wwvb → leapseconddata}/__version__.py +2 -2
- wwvb-4.1.0a0.dist-info/METADATA +60 -0
- wwvb-4.1.0a0.dist-info/RECORD +9 -0
- wwvb-4.1.0a0.dist-info/entry_points.txt +2 -0
- wwvb-4.1.0a0.dist-info/top_level.txt +1 -0
- uwwvb.py +0 -193
- wwvb/__init__.py +0 -935
- wwvb/decode.py +0 -90
- wwvb/dut1table.py +0 -32
- wwvb/gen.py +0 -128
- wwvb/iersdata.py +0 -28
- 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/tz.py +0 -13
- wwvb/updateiers.py +0 -199
- wwvb/wwvbtk.py +0 -144
- wwvb-4.0.0a0.dist-info/METADATA +0 -199
- wwvb-4.0.0a0.dist-info/RECORD +0 -23
- wwvb-4.0.0a0.dist-info/entry_points.txt +0 -8
- wwvb-4.0.0a0.dist-info/top_level.txt +0 -2
- {wwvb → leapseconddata}/py.typed +0 -0
- {wwvb-4.0.0a0.dist-info → wwvb-4.1.0a0.dist-info}/WHEEL +0 -0
wwvb/decode.py
DELETED
@@ -1,90 +0,0 @@
|
|
1
|
-
# SPDX-FileCopyrightText: 2021 Jeff Epler
|
2
|
-
#
|
3
|
-
# SPDX-License-Identifier: GPL-3.0-only
|
4
|
-
"""A stateful decoder of WWVB signals"""
|
5
|
-
|
6
|
-
from __future__ import annotations
|
7
|
-
|
8
|
-
import sys
|
9
|
-
from typing import Generator
|
10
|
-
|
11
|
-
import wwvb
|
12
|
-
|
13
|
-
# State 1: Unsync'd
|
14
|
-
# Marker: State 2
|
15
|
-
# Other: State 1
|
16
|
-
# State 2: One marker
|
17
|
-
# Marker: State 3
|
18
|
-
# Other: State 1
|
19
|
-
# State 3: Two markers
|
20
|
-
# Marker: State 3
|
21
|
-
# Other: State 4
|
22
|
-
# State 4: Decoding a minute, starting in second 1
|
23
|
-
# Second
|
24
|
-
|
25
|
-
always_zero = {4, 10, 11, 14, 20, 21, 34, 35, 44, 54}
|
26
|
-
|
27
|
-
|
28
|
-
def wwvbreceive() -> Generator[wwvb.WWVBTimecode | None, wwvb.AmplitudeModulation, None]:
|
29
|
-
"""Decode WWVB signals statefully."""
|
30
|
-
minute: list[wwvb.AmplitudeModulation] = []
|
31
|
-
state = 1
|
32
|
-
|
33
|
-
value = yield None
|
34
|
-
while True:
|
35
|
-
# print(state, value, len(minute), "".join(str(int(i)) for i in minute))
|
36
|
-
if state == 1:
|
37
|
-
minute = []
|
38
|
-
if value == wwvb.AmplitudeModulation.MARK:
|
39
|
-
state = 2
|
40
|
-
value = yield None
|
41
|
-
|
42
|
-
elif state == 2:
|
43
|
-
state = 3 if value == wwvb.AmplitudeModulation.MARK else 1
|
44
|
-
value = yield None
|
45
|
-
|
46
|
-
elif state == 3:
|
47
|
-
if value != wwvb.AmplitudeModulation.MARK:
|
48
|
-
state = 4
|
49
|
-
minute = [wwvb.AmplitudeModulation.MARK, value]
|
50
|
-
value = yield None
|
51
|
-
|
52
|
-
else: # state == 4:
|
53
|
-
minute.append(value)
|
54
|
-
if len(minute) % 10 == 0 and value != wwvb.AmplitudeModulation.MARK:
|
55
|
-
# print("MISSING MARK", len(minute), "".join(str(int(i)) for i in minute))
|
56
|
-
state = 1
|
57
|
-
elif len(minute) % 10 and value == wwvb.AmplitudeModulation.MARK:
|
58
|
-
# print("UNEXPECTED MARK")
|
59
|
-
state = 1
|
60
|
-
elif len(minute) - 1 in always_zero and value != wwvb.AmplitudeModulation.ZERO:
|
61
|
-
# print("UNEXPECTED NONZERO")
|
62
|
-
state = 1
|
63
|
-
elif len(minute) == 60:
|
64
|
-
# print("FULL MINUTE")
|
65
|
-
tc = wwvb.WWVBTimecode(60)
|
66
|
-
tc.am[:] = minute
|
67
|
-
minute = []
|
68
|
-
state = 2
|
69
|
-
value = yield tc
|
70
|
-
else:
|
71
|
-
value = yield None
|
72
|
-
|
73
|
-
|
74
|
-
def main() -> None:
|
75
|
-
"""Read symbols on stdin and print any successfully-decoded minutes"""
|
76
|
-
decoder = wwvbreceive()
|
77
|
-
next(decoder)
|
78
|
-
decoder.send(wwvb.AmplitudeModulation.MARK)
|
79
|
-
for s in sys.argv[1:]:
|
80
|
-
for c in s:
|
81
|
-
decoded = decoder.send(wwvb.AmplitudeModulation(int(c)))
|
82
|
-
if decoded:
|
83
|
-
print(decoded)
|
84
|
-
w = wwvb.WWVBMinute.from_timecode_am(decoded)
|
85
|
-
if w:
|
86
|
-
print(w)
|
87
|
-
|
88
|
-
|
89
|
-
if __name__ == "__main__": # pragma no cover
|
90
|
-
main()
|
wwvb/dut1table.py
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
|
3
|
-
# SPDX-FileCopyrightText: 2021 Jeff Epler
|
4
|
-
#
|
5
|
-
# SPDX-License-Identifier: GPL-3.0-only
|
6
|
-
|
7
|
-
"""Print the table of historical DUT1 values"""
|
8
|
-
|
9
|
-
from datetime import timedelta
|
10
|
-
from itertools import groupby
|
11
|
-
|
12
|
-
import wwvb
|
13
|
-
|
14
|
-
from .iersdata import DUT1_DATA_START, DUT1_OFFSETS
|
15
|
-
|
16
|
-
|
17
|
-
def main() -> None:
|
18
|
-
"""Print the table of historical DUT1 values"""
|
19
|
-
date = DUT1_DATA_START
|
20
|
-
for key, it in groupby(DUT1_OFFSETS):
|
21
|
-
dut1_ms = (ord(key) - ord("k")) / 10.0
|
22
|
-
count = len(list(it))
|
23
|
-
end = date + timedelta(days=count - 1)
|
24
|
-
dut1_next = wwvb.get_dut1(date + timedelta(days=count), warn_outdated=False)
|
25
|
-
ls = f" LS on {end:%F} 23:59:60 UTC" if dut1_ms * dut1_next < 0 else ""
|
26
|
-
print(f"{date:%F} {dut1_ms: 3.1f} {count:4d}{ls}")
|
27
|
-
date += timedelta(days=count)
|
28
|
-
print(date)
|
29
|
-
|
30
|
-
|
31
|
-
if __name__ == "__main__": # pragma no branch
|
32
|
-
main()
|
wwvb/gen.py
DELETED
@@ -1,128 +0,0 @@
|
|
1
|
-
#!/usr/bin/python3
|
2
|
-
"""A command-line program for generating wwvb timecodes"""
|
3
|
-
|
4
|
-
# Copyright (C) 2011-2020 Jeff Epler <jepler@gmail.com>
|
5
|
-
# SPDX-FileCopyrightText: 2021 Jeff Epler
|
6
|
-
#
|
7
|
-
# SPDX-License-Identifier: GPL-3.0-only
|
8
|
-
|
9
|
-
from __future__ import annotations
|
10
|
-
|
11
|
-
import datetime
|
12
|
-
import sys
|
13
|
-
from typing import Any
|
14
|
-
|
15
|
-
import click
|
16
|
-
import dateutil.parser
|
17
|
-
|
18
|
-
from . import WWVBMinute, WWVBMinuteIERS, print_timecodes, print_timecodes_json, styles
|
19
|
-
|
20
|
-
|
21
|
-
def parse_timespec(ctx: Any, param: Any, value: list[str]) -> datetime.datetime: # noqa: ARG001
|
22
|
-
"""Parse a time specifier from the commandline"""
|
23
|
-
try:
|
24
|
-
if len(value) == 5:
|
25
|
-
year, month, day, hour, minute = map(int, value)
|
26
|
-
return datetime.datetime(year, month, day, hour, minute, tzinfo=datetime.timezone.utc)
|
27
|
-
if len(value) == 4:
|
28
|
-
year, yday, hour, minute = map(int, value)
|
29
|
-
return datetime.datetime(year, 1, 1, hour, minute, tzinfo=datetime.timezone.utc) + datetime.timedelta(
|
30
|
-
days=yday - 1,
|
31
|
-
)
|
32
|
-
if len(value) == 1:
|
33
|
-
return dateutil.parser.parse(value[0])
|
34
|
-
if len(value) == 0:
|
35
|
-
return datetime.datetime.now(datetime.timezone.utc)
|
36
|
-
raise ValueError("Unexpected number of arguments")
|
37
|
-
except ValueError as e:
|
38
|
-
raise click.UsageError(f"Could not parse timespec: {e}") from e
|
39
|
-
|
40
|
-
|
41
|
-
@click.command()
|
42
|
-
@click.option(
|
43
|
-
"--iers/--no-iers",
|
44
|
-
"-i/-I",
|
45
|
-
default=True,
|
46
|
-
help="Whether to use IESR data for DUT1 and LS. (Default: --iers)",
|
47
|
-
)
|
48
|
-
@click.option(
|
49
|
-
"--leap-second",
|
50
|
-
"-s",
|
51
|
-
"leap_second",
|
52
|
-
flag_value=1,
|
53
|
-
default=None,
|
54
|
-
help="Force a positive leap second at the end of the GMT month (Implies --no-iers)",
|
55
|
-
)
|
56
|
-
@click.option(
|
57
|
-
"--negative-leap-second",
|
58
|
-
"-n",
|
59
|
-
"leap_second",
|
60
|
-
flag_value=-1,
|
61
|
-
help="Force a negative leap second at the end of the GMT month (Implies --no-iers)",
|
62
|
-
)
|
63
|
-
@click.option(
|
64
|
-
"--no-leap-second",
|
65
|
-
"-S",
|
66
|
-
"leap_second",
|
67
|
-
flag_value=0,
|
68
|
-
help="Force no leap second at the end of the month (Implies --no-iers)",
|
69
|
-
)
|
70
|
-
@click.option("--dut1", "-d", type=int, help="Force the DUT1 value (Implies --no-iers)")
|
71
|
-
@click.option("--minutes", "-m", default=10, help="Number of minutes to show (default: 10)")
|
72
|
-
@click.option(
|
73
|
-
"--style",
|
74
|
-
default="default",
|
75
|
-
type=click.Choice(sorted(["json", *list(styles.keys())])),
|
76
|
-
help="Style of output",
|
77
|
-
)
|
78
|
-
@click.option(
|
79
|
-
"--all-timecodes/--no-all-timecodes",
|
80
|
-
"-t/-T",
|
81
|
-
default=False,
|
82
|
-
type=bool,
|
83
|
-
help="Show the 'WWVB timecode' line before each minute",
|
84
|
-
)
|
85
|
-
@click.option(
|
86
|
-
"--channel",
|
87
|
-
type=click.Choice(["amplitude", "phase", "both"]),
|
88
|
-
default="amplitude",
|
89
|
-
help="Modulation to show (default: amplitude)",
|
90
|
-
)
|
91
|
-
@click.argument("timespec", type=str, nargs=-1, callback=parse_timespec)
|
92
|
-
def main(
|
93
|
-
*,
|
94
|
-
iers: bool,
|
95
|
-
leap_second: bool,
|
96
|
-
dut1: int,
|
97
|
-
minutes: int,
|
98
|
-
style: str,
|
99
|
-
channel: str,
|
100
|
-
all_timecodes: bool,
|
101
|
-
timespec: datetime.datetime,
|
102
|
-
) -> None:
|
103
|
-
"""Generate WWVB timecodes
|
104
|
-
|
105
|
-
TIMESPEC: one of "year yday hour minute" or "year month day hour minute", or else the current minute
|
106
|
-
"""
|
107
|
-
if (leap_second is not None) or (dut1 is not None):
|
108
|
-
iers = False
|
109
|
-
|
110
|
-
newut1 = None
|
111
|
-
newls = None
|
112
|
-
|
113
|
-
if iers:
|
114
|
-
constructor: type[WWVBMinute] = WWVBMinuteIERS
|
115
|
-
else:
|
116
|
-
constructor = WWVBMinute
|
117
|
-
newut1 = -500 * (leap_second or 0) if dut1 is None else dut1
|
118
|
-
newls = bool(leap_second)
|
119
|
-
|
120
|
-
w = constructor.from_datetime(timespec, newls=newls, newut1=newut1)
|
121
|
-
if style == "json":
|
122
|
-
print_timecodes_json(w, minutes, channel, file=sys.stdout)
|
123
|
-
else:
|
124
|
-
print_timecodes(w, minutes, channel, style, all_timecodes=all_timecodes, file=sys.stdout)
|
125
|
-
|
126
|
-
|
127
|
-
if __name__ == "__main__": # pragma no branch
|
128
|
-
main()
|
wwvb/iersdata.py
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
# -*- python3 -*-
|
2
|
-
"""Retrieve iers data, possibly from user or site data or from the wwvbpy distribution"""
|
3
|
-
|
4
|
-
# Copyright (C) 2021 Jeff Epler <jepler@gmail.com>
|
5
|
-
# SPDX-FileCopyrightText: 2021 Jeff Epler
|
6
|
-
#
|
7
|
-
# SPDX-License-Identifier: GPL-3.0-only
|
8
|
-
|
9
|
-
import datetime
|
10
|
-
import pathlib
|
11
|
-
|
12
|
-
import platformdirs
|
13
|
-
|
14
|
-
__all__ = ["DUT1_DATA_START", "DUT1_OFFSETS", "start", "span", "end"]
|
15
|
-
from .iersdata_dist import DUT1_DATA_START, DUT1_OFFSETS
|
16
|
-
|
17
|
-
for location in [
|
18
|
-
platformdirs.user_data_dir("wwvbpy", "unpythonic.net"),
|
19
|
-
platformdirs.site_data_dir("wwvbpy", "unpythonic.net"),
|
20
|
-
]: # pragma no cover
|
21
|
-
path = pathlib.Path(location) / "wwvbpy_iersdata.py"
|
22
|
-
if path.exists():
|
23
|
-
exec(path.read_text(encoding="utf-8"), globals(), globals())
|
24
|
-
break
|
25
|
-
|
26
|
-
start = datetime.datetime.combine(DUT1_DATA_START, datetime.time()).replace(tzinfo=datetime.timezone.utc)
|
27
|
-
span = datetime.timedelta(days=len(DUT1_OFFSETS))
|
28
|
-
end = start + span
|
wwvb/iersdata_dist.py
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
# -*- python3 -*-
|
2
|
-
# fmt: off
|
3
|
-
"""File generated from public data - not subject to copyright"""
|
4
|
-
# SPDX-FileCopyrightText: Public domain
|
5
|
-
# SPDX-License-Identifier: CC0-1.0
|
6
|
-
# isort: skip_file
|
7
|
-
import datetime
|
8
|
-
__all__ = ["DUT1_DATA_START", "DUT1_OFFSETS"]
|
9
|
-
DUT1_DATA_START = datetime.date(1972, 1, 1)
|
10
|
-
d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s = tuple("defghijklmnopqrs")
|
11
|
-
DUT1_OFFSETS = str( # 19720101
|
12
|
-
i*182+s*123+k*30+i*31+s*19+r*31+q*29+p*28+o*30+n*36+m*40 # 19730909
|
13
|
-
+l*39+k*33+j*31+i*31+h*18+r*19+q*38+p*32+o*31+n*33+m*48+l*45 # 19741010
|
14
|
-
+k*37+j*33+i*34+h*15+r*22+q*34+p*33+o*34+n*37+m*49+l*45+k*36 # 19751118
|
15
|
-
+j*32+i*36+h*7+r*28+q*33+p*32+o*30+n*33+m*42+l*42+k*34+j*29 # 19761201
|
16
|
-
+i*33+h*30+r*6+q*36+p*34+o*31+n*32+m*42+l*51+k*37+j*32+i*33 # 19771231
|
17
|
-
+h*31+q*32+p*29+o*29+n*30+m*32+l*47+k*47+j*36+i*33+h*32+g*18 # 19790116
|
18
|
-
+q*16+p*35+o*33+n*32+m*35+l*45+k*51+j*39+i*39+h*38+g*2+q*40 # 19800319
|
19
|
-
+p*39+o*38+n*43+m*57+l*50+k*39+j*42+i*41+h*43+g*37+f*39+e*39 # 19810719
|
20
|
-
+o*19+n*62+m*43+l*45+k*48+j*44+i*39+h*44+g*21+q*44+p*48+o*43 # 19821223
|
21
|
-
+n*41+m*36+l*34+k*34+j*38+i*47+s+r*64+q*50+p*42+o*56+n*57 # 19840517
|
22
|
-
+m*52+l*100+k*61+j*62+i*66+h*52+g*67+f+p*103+o*56+n*68+m*69 # 19860807
|
23
|
-
+l*107+k*82+j*72+i*67+h*63+g*113+f*63+e*51+o*11+n*60+m*59 # 19880907
|
24
|
-
+l*121+k*71+j*71+i*67+h*57+g*93+f*61+e*48+d*12+n*41+m*44 # 19900511
|
25
|
-
+l*46+k*61+j*66+i*47+h*45+g*15+q*32+p*44+o*41+n*48+m*74+l*49 # 19911129
|
26
|
-
+k*45+j*44+i*40+h*37+g*38+f*50+e*5+o*60+n*49+m*40+l*40+k*38 # 19930322
|
27
|
-
+j*38+i*36+h*39+g*25+q*31+p*50+o*41+n*41+m*43+l*41+k*39+j*40 # 19940630
|
28
|
-
+i*39+s*24+r*57+q*43+p*41+o*39+n*38+m*35+l*37+k*43+j*69+i*44 # 19951124
|
29
|
-
+h*42+g*37+q*4+p*51+o*45+n*44+m*69+l*70+k*50+j*54+i*53+h*40 # 19970612
|
30
|
-
+g*49+f*18+p*59+o*53+n*52+m*57+l*48+k*53+j*127+i*70+h*30 # 19990303
|
31
|
-
+r*62+q*79+p*152+o*82+n*106+m*184+l*125+k*217+j*133+i*252 # 20030402
|
32
|
-
+h*161+g*392+f*322+e*290+n*116+m*154+l*85+k*83+j*91+i*168 # 20080312
|
33
|
-
+h*105+g*147+f*105+e*42+o*70+n*91+m*154+l*119+k*84+j*217 # 20110511
|
34
|
-
+i*126+h*176+g*97+f*91+e*52+o*116+n*98+m*70+l*133+k*91+j*91 # 20140507
|
35
|
-
+i*77+h*140+g*91+f*84+e*70+d*34+n*72+m*76+l*66+k*53+j*56 # 20160831
|
36
|
-
+i*105+h*77+g*45+q*25+p*63+o*91+n*154+m*105+l*190+k*118 # 20190501
|
37
|
-
+j*105+i*807+j*376+k*768+l*47+k*3+l*4+k*252, # 20250705
|
38
|
-
)
|
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
|
-
)
|