wwvb 5.0.1__py3-none-any.whl → 5.0.3__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 +4 -1
- wwvb/__init__.py +81 -29
- wwvb/__version__.py +9 -4
- wwvb/iersdata.json +1 -1
- wwvb/updateiers.py +2 -2
- wwvb/wwvbtk.py +35 -28
- {wwvb-5.0.1.dist-info → wwvb-5.0.3.dist-info}/METADATA +7 -9
- wwvb-5.0.3.dist-info/RECORD +18 -0
- {wwvb-5.0.1.dist-info → wwvb-5.0.3.dist-info}/WHEEL +1 -1
- wwvb-5.0.1.dist-info/RECORD +0 -18
- {wwvb-5.0.1.dist-info → wwvb-5.0.3.dist-info}/entry_points.txt +0 -0
- {wwvb-5.0.1.dist-info → wwvb-5.0.3.dist-info}/top_level.txt +0 -0
uwwvb.py
CHANGED
@@ -4,7 +4,10 @@
|
|
4
4
|
|
5
5
|
# ruff: noqa: C405 PYI024 PLR2004 FBT001 FBT002
|
6
6
|
|
7
|
-
"""Implementation of a WWVB state machine & decoder for resource-constrained systems
|
7
|
+
"""Implementation of a WWVB state machine & decoder for resource-constrained systems
|
8
|
+
|
9
|
+
This version is intended for use with MicroPython & CircuitPython.
|
10
|
+
"""
|
8
11
|
|
9
12
|
from __future__ import annotations
|
10
13
|
|
wwvb/__init__.py
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
#!/usr/bin/python3
|
2
|
-
"""A
|
2
|
+
"""A package and CLI for WWVB timecodes
|
3
|
+
|
4
|
+
This is the full featured library suitable for use on 'real computers'.
|
5
|
+
For a reduced version suitable for use on MicroPython & CircuitPython,
|
6
|
+
see `uwwvb`.
|
7
|
+
|
8
|
+
This package also includes the commandline programs listed above,
|
9
|
+
perhaps most importantly ``wwvbgen`` for generating WWVB timecodes.
|
10
|
+
"""
|
3
11
|
|
4
12
|
# SPDX-FileCopyrightText: 2011-2024 Jeff Epler
|
5
13
|
#
|
@@ -24,12 +32,6 @@ SECOND = datetime.timedelta(seconds=1)
|
|
24
32
|
T = TypeVar("T")
|
25
33
|
|
26
34
|
|
27
|
-
def _require(x: T | None) -> T:
|
28
|
-
"""Check an Optional item is not None."""
|
29
|
-
assert x is not None
|
30
|
-
return x
|
31
|
-
|
32
|
-
|
33
35
|
def _removeprefix(s: str, p: str) -> str:
|
34
36
|
if s.startswith(p):
|
35
37
|
return s[len(p) :]
|
@@ -49,7 +51,6 @@ def _maybe_warn_update(dt: datetime.date, stacklevel: int = 1) -> None:
|
|
49
51
|
# If the date is less than 300 days after today, there should be (possibly)
|
50
52
|
# prospective available now.
|
51
53
|
today = datetime.datetime.now(tz=datetime.timezone.utc).date()
|
52
|
-
print(f"_mwu {today=!r} {dt=!r} {iersdata.end=!r}")
|
53
54
|
if _date(dt) < today + datetime.timedelta(days=330):
|
54
55
|
warnings.warn(
|
55
56
|
"Note: Running `updateiers` may provide better DUT1 and LS information",
|
@@ -324,11 +325,22 @@ _dst_ls_lut = [
|
|
324
325
|
]
|
325
326
|
|
326
327
|
|
327
|
-
|
328
|
-
|
328
|
+
@enum.unique
|
329
|
+
class DstStatus(enum.IntEnum):
|
330
|
+
"""Constants that describe the DST status of a minute"""
|
329
331
|
|
330
|
-
|
331
|
-
"""
|
332
|
+
DST_NOT_IN_EFFECT = 0b00
|
333
|
+
"""DST not in effect today"""
|
334
|
+
DST_STARTS_TODAY = 0b01
|
335
|
+
"""DST starts today at 0200 local standard time"""
|
336
|
+
DST_ENDS_TODAY = 0b10
|
337
|
+
"""DST ends today at 0200 local standard time"""
|
338
|
+
DST_IN_EFFECT = 0b11
|
339
|
+
"""DST in effect all day today"""
|
340
|
+
|
341
|
+
|
342
|
+
class _WWVBMinute(NamedTuple):
|
343
|
+
"""(implementation detail)"""
|
332
344
|
|
333
345
|
year: int
|
334
346
|
"""2-digit year within the WWVB epoch"""
|
@@ -342,7 +354,7 @@ class _WWVBMinute(NamedTuple):
|
|
342
354
|
min: int
|
343
355
|
"""Minute of hour"""
|
344
356
|
|
345
|
-
dst:
|
357
|
+
dst: DstStatus
|
346
358
|
"""2-bit DST code """
|
347
359
|
|
348
360
|
ut1: int
|
@@ -358,7 +370,8 @@ class _WWVBMinute(NamedTuple):
|
|
358
370
|
class WWVBMinute(_WWVBMinute):
|
359
371
|
"""Uniquely identifies a minute of time in the WWVB system.
|
360
372
|
|
361
|
-
To use ut1 and ls information from IERS, create a WWVBMinuteIERS
|
373
|
+
To use ``ut1`` and ``ls`` information from IERS, create a `WWVBMinuteIERS`
|
374
|
+
object instead.
|
362
375
|
"""
|
363
376
|
|
364
377
|
epoch: int = 1970
|
@@ -369,16 +382,25 @@ class WWVBMinute(_WWVBMinute):
|
|
369
382
|
days: int,
|
370
383
|
hour: int,
|
371
384
|
minute: int,
|
372
|
-
dst: int | None = None,
|
385
|
+
dst: DstStatus | int | None = None,
|
373
386
|
ut1: int | None = None,
|
374
387
|
ls: bool | None = None,
|
375
388
|
ly: bool | None = None,
|
376
389
|
) -> WWVBMinute:
|
377
|
-
"""Construct a WWVBMinute
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
390
|
+
"""Construct a WWVBMinute
|
391
|
+
|
392
|
+
:param year: The 2- or 4-digit year. This parameter is converted by the `full_year` method.
|
393
|
+
:param days: 1-based day of year
|
394
|
+
|
395
|
+
:param hour: UTC hour of day
|
396
|
+
|
397
|
+
:param minute: Minute of hour
|
398
|
+
:param dst: 2-bit DST code
|
399
|
+
:param ut1: UT1 offset in units of 100ms, range -900 to +900ms
|
400
|
+
:param ls: Leap second warning flag
|
401
|
+
:param ly: Leap year flag
|
402
|
+
"""
|
403
|
+
dst = cls.get_dst(year, days) if dst is None else DstStatus(dst)
|
382
404
|
if ut1 is None and ls is None:
|
383
405
|
ut1, ls = cls._get_dut1_info(year, days)
|
384
406
|
elif ut1 is None or ls is None:
|
@@ -408,13 +430,13 @@ class WWVBMinute(_WWVBMinute):
|
|
408
430
|
return year
|
409
431
|
|
410
432
|
@staticmethod
|
411
|
-
def get_dst(year: int, days: int) ->
|
433
|
+
def get_dst(year: int, days: int) -> DstStatus:
|
412
434
|
"""Get the 2-bit WWVB DST value for the given day"""
|
413
435
|
d0 = datetime.datetime(year, 1, 1, tzinfo=datetime.timezone.utc) + datetime.timedelta(days - 1)
|
414
436
|
d1 = d0 + datetime.timedelta(1)
|
415
437
|
dst0 = isdst(d0)
|
416
438
|
dst1 = isdst(d1)
|
417
|
-
return dst1 * 2 + dst0
|
439
|
+
return DstStatus(dst1 * 2 + dst0)
|
418
440
|
|
419
441
|
def __str__(self) -> str:
|
420
442
|
"""Implement str()"""
|
@@ -425,7 +447,10 @@ class WWVBMinute(_WWVBMinute):
|
|
425
447
|
)
|
426
448
|
|
427
449
|
def as_datetime_utc(self) -> datetime.datetime:
|
428
|
-
"""Convert to a UTC datetime
|
450
|
+
"""Convert to a UTC datetime
|
451
|
+
|
452
|
+
The returned object has ``tzinfo=datetime.timezone.utc``.
|
453
|
+
"""
|
429
454
|
d = datetime.datetime(self.year, 1, 1, tzinfo=datetime.timezone.utc)
|
430
455
|
d += datetime.timedelta(self.days - 1, self.hour * 3600 + self.min * 60)
|
431
456
|
return d
|
@@ -438,7 +463,18 @@ class WWVBMinute(_WWVBMinute):
|
|
438
463
|
*,
|
439
464
|
dst_observed: bool = True,
|
440
465
|
) -> datetime.datetime:
|
441
|
-
"""Convert to a local datetime according to the DST bits
|
466
|
+
"""Convert to a local datetime according to the DST bits
|
467
|
+
|
468
|
+
The returned object has ``tz=datetime.timezone(computed_offset)``.
|
469
|
+
|
470
|
+
:param standard_time_offset: The UTC offset of local standard time, in seconds west of UTC.
|
471
|
+
The default value, ``7 * 3600``, is for Colorado, the source of the WWVB broadcast.
|
472
|
+
|
473
|
+
:param dst_observed: If ``True`` then the locale observes DST, and a
|
474
|
+
one hour offset is applied according to WWVB rules. If ``False``, then
|
475
|
+
the standard time offset is used at all times.
|
476
|
+
|
477
|
+
"""
|
442
478
|
u = self.as_datetime_utc()
|
443
479
|
offset = datetime.timedelta(seconds=-standard_time_offset)
|
444
480
|
d = u - datetime.timedelta(seconds=standard_time_offset)
|
@@ -683,7 +719,7 @@ class WWVBMinute(_WWVBMinute):
|
|
683
719
|
return cls(u.tm_year, u.tm_yday, u.tm_hour, u.tm_min, ut1=newut1, ls=newls)
|
684
720
|
|
685
721
|
@classmethod
|
686
|
-
def from_timecode_am(cls, t: WWVBTimecode) -> WWVBMinute | None:
|
722
|
+
def from_timecode_am(cls, t: WWVBTimecode) -> WWVBMinute | None: # noqa: PLR0912
|
687
723
|
"""Construct a WWVBMinute from a WWVBTimecode"""
|
688
724
|
for i in (0, 9, 19, 29, 39, 49, 59):
|
689
725
|
if t.am[i] != AmplitudeModulation.MARK:
|
@@ -698,9 +734,13 @@ class WWVBMinute(_WWVBMinute):
|
|
698
734
|
minute = t._get_am_bcd(1, 2, 3, 5, 6, 7, 8)
|
699
735
|
if minute is None:
|
700
736
|
return None
|
737
|
+
if minute >= 60:
|
738
|
+
return None
|
701
739
|
hour = t._get_am_bcd(12, 13, 15, 16, 17, 18)
|
702
740
|
if hour is None:
|
703
741
|
return None
|
742
|
+
if hour >= 24:
|
743
|
+
return None
|
704
744
|
days = t._get_am_bcd(22, 23, 25, 26, 27, 28, 30, 31, 32, 33)
|
705
745
|
if days is None:
|
706
746
|
return None
|
@@ -717,7 +757,9 @@ class WWVBMinute(_WWVBMinute):
|
|
717
757
|
if days > 366 or (not ly and days > 365):
|
718
758
|
return None
|
719
759
|
ls = bool(t.am[56])
|
720
|
-
dst =
|
760
|
+
dst = t._get_am_bcd(57, 58)
|
761
|
+
if dst is None:
|
762
|
+
return None
|
721
763
|
return cls(year, days, hour, minute, dst, ut1, ls, ly)
|
722
764
|
|
723
765
|
|
@@ -727,10 +769,10 @@ class WWVBMinuteIERS(WWVBMinute):
|
|
727
769
|
@classmethod
|
728
770
|
def _get_dut1_info(cls, year: int, days: int, old_time: WWVBMinute | None = None) -> tuple[int, bool]: # noqa: ARG003
|
729
771
|
d = datetime.datetime(year, 1, 1, tzinfo=datetime.timezone.utc) + datetime.timedelta(days - 1)
|
730
|
-
return
|
772
|
+
return round(get_dut1(d) * 10) * 100, isls(d)
|
731
773
|
|
732
774
|
|
733
|
-
def _bcd_bits(n: int) -> Generator[bool
|
775
|
+
def _bcd_bits(n: int) -> Generator[bool]:
|
734
776
|
"""Return the bcd representation of n, starting with the least significant bit"""
|
735
777
|
while True:
|
736
778
|
d = n % 10
|
@@ -744,9 +786,13 @@ class AmplitudeModulation(enum.IntEnum):
|
|
744
786
|
"""Constants that describe an Amplitude Modulation value"""
|
745
787
|
|
746
788
|
ZERO = 0
|
789
|
+
"""A zero bit (reduced carrier during the first 200ms of the second)"""
|
747
790
|
ONE = 1
|
791
|
+
"""A one bit (reduced carrier during the first 500ms of the second)"""
|
748
792
|
MARK = 2
|
793
|
+
"""A mark bit (reduced carrier during the first 800ms of the second)"""
|
749
794
|
UNSET = -1
|
795
|
+
"""An unset or unknown amplitude modulation value"""
|
750
796
|
|
751
797
|
|
752
798
|
@enum.unique
|
@@ -754,8 +800,11 @@ class PhaseModulation(enum.IntEnum):
|
|
754
800
|
"""Constants that describe a Phase Modulation value"""
|
755
801
|
|
756
802
|
ZERO = 0
|
803
|
+
"""A one bit (180° phase shift during the second)"""
|
757
804
|
ONE = 1
|
805
|
+
"""A zero bit (No phase shift during the second)"""
|
758
806
|
UNSET = -1
|
807
|
+
"""An unset or unknown phase modulation value"""
|
759
808
|
|
760
809
|
|
761
810
|
class WWVBTimecode:
|
@@ -778,7 +827,10 @@ class WWVBTimecode:
|
|
778
827
|
The the bits ``self.am[poslist[i]]`` in MSB order are converted from
|
779
828
|
BCD to integer
|
780
829
|
"""
|
781
|
-
pos =
|
830
|
+
pos = list(poslist)[::-1]
|
831
|
+
for p in pos:
|
832
|
+
if self.am[p] not in {AmplitudeModulation.ZERO, AmplitudeModulation.ONE}:
|
833
|
+
return None
|
782
834
|
val = [bool(self.am[p]) for p in pos]
|
783
835
|
result = 0
|
784
836
|
base = 1
|
wwvb/__version__.py
CHANGED
@@ -1,8 +1,13 @@
|
|
1
|
-
# file generated by
|
1
|
+
# file generated by setuptools-scm
|
2
2
|
# don't change, don't track in version control
|
3
|
+
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
5
|
+
|
3
6
|
TYPE_CHECKING = False
|
4
7
|
if TYPE_CHECKING:
|
5
|
-
from typing import Tuple
|
8
|
+
from typing import Tuple
|
9
|
+
from typing import Union
|
10
|
+
|
6
11
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
7
12
|
else:
|
8
13
|
VERSION_TUPLE = object
|
@@ -12,5 +17,5 @@ __version__: str
|
|
12
17
|
__version_tuple__: VERSION_TUPLE
|
13
18
|
version_tuple: VERSION_TUPLE
|
14
19
|
|
15
|
-
__version__ = version = '5.0.
|
16
|
-
__version_tuple__ = version_tuple = (5, 0,
|
20
|
+
__version__ = version = '5.0.3'
|
21
|
+
__version_tuple__ = version_tuple = (5, 0, 3)
|
wwvb/iersdata.json
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"START": "1972-01-01", "OFFSETS_GZ": "
|
1
|
+
{"START": "1972-01-01", "OFFSETS_GZ": "H4sIAHTtFGgC/+2aa3LDMAiEL5uHLDuxnN5/pn/aTmfSSiAWhGR9J8gsywJylqVHPtqxZuH/7leeI0fKsGd5EngQ2WisJWKegrThDa6aJFnL0u4wYZkCE2UmSF0U+13vCveStC6JTfQyW3O86HLJf0SvDgy5u4FCI+WVKRuy0KMjJeXoULIvMDmEWgeRxAJtwXquPCIBqbLh/gbfv0mcxk3mHV9tYiATZP8W/zgw2wd5LpJnY+WErI8abJ3opaIW6592+YMbjSsNWQFlNVVtuhjhtQzSUh4MEpOdDrSW6qsUv+O+Dt+XkIONSrUwvWmTsmq5LO9xsZ+EgcDK+MIESDaYmxSxGlgbGOFjBXMjbV7lc6zlmQ0i48oH5P4+vK7i/AHc7tfTXDtffqFi3m6WhApPSTyDvArU5vUDhm7YaNQYGASVbbwLUBtI2PrhSiZNbvCRrtGUGu0GbjDhJ3aLCx5dQFjt0LFovmWB96e6tktqMenoULXajVS3asBibP3kYXrpmZxnsS2Yf2xRPrHbvQ2D9wjfL4C6b4PWV4otW0vWUYkeWE5M8M594oLbxP77xcl4NuBkG0dfM3xOUf/T0GF+ur+J5pljcODEUZkXg6vIdLYy7g3oZU3bPNDnc8qwGdJZMmAurUsRj6tOo95zP6fb9YPWp5OuZ5X7q2DrmsG/VCyTyaREnDRhnUxOzfzzhzuJhuMTQw5vlI1NAAA="}
|
wwvb/updateiers.py
CHANGED
@@ -52,7 +52,7 @@ def update_iersdata( # noqa: PLR0915
|
|
52
52
|
offs_str = r["UT1-UTC"]
|
53
53
|
if not offs_str:
|
54
54
|
break
|
55
|
-
offs =
|
55
|
+
offs = round(float(offs_str) * 10)
|
56
56
|
if not offsets:
|
57
57
|
table_start = datetime.date(1858, 11, 17) + datetime.timedelta(jd)
|
58
58
|
|
@@ -98,7 +98,7 @@ def update_iersdata( # noqa: PLR0915
|
|
98
98
|
cells = row.findAll("td")
|
99
99
|
when = datetime.datetime.strptime(cells[0].text + "+0000", "%Y-%m-%d%z").date()
|
100
100
|
dut1 = cells[2].text.replace("s", "").replace(" ", "")
|
101
|
-
dut1 =
|
101
|
+
dut1 = round(float(dut1) * 10)
|
102
102
|
if wwvb_dut1 is not None:
|
103
103
|
assert wwvb_start is not None
|
104
104
|
patch(wwvb_start, when, wwvb_dut1)
|
wwvb/wwvbtk.py
CHANGED
@@ -6,9 +6,8 @@
|
|
6
6
|
# SPDX-License-Identifier: GPL-3.0-only
|
7
7
|
from __future__ import annotations
|
8
8
|
|
9
|
+
import datetime
|
9
10
|
import functools
|
10
|
-
import threading
|
11
|
-
import time
|
12
11
|
from tkinter import Canvas, TclError, Tk
|
13
12
|
from typing import TYPE_CHECKING, Any
|
14
13
|
|
@@ -31,7 +30,7 @@ def validate_colors(ctx: Any, param: Any, value: str) -> list[str]: # noqa: ARG
|
|
31
30
|
app = _app()
|
32
31
|
colors = value.split()
|
33
32
|
if len(colors) not in (2, 3, 4, 6):
|
34
|
-
raise click.BadParameter(f"Give 2, 3, 4 or 6 colors (not {len(colors)}")
|
33
|
+
raise click.BadParameter(f"Give 2, 3, 4 or 6 colors (not {len(colors)})")
|
35
34
|
for c in colors:
|
36
35
|
try:
|
37
36
|
app.winfo_rgb(c)
|
@@ -53,33 +52,36 @@ DEFAULT_COLORS = "#3c3c3c #3c3c3c #3c3c3c #cc3c3c #88883c #3ccc3c"
|
|
53
52
|
|
54
53
|
|
55
54
|
@click.command
|
56
|
-
@click.option(
|
57
|
-
|
58
|
-
|
55
|
+
@click.option(
|
56
|
+
"--colors",
|
57
|
+
callback=validate_colors,
|
58
|
+
default=DEFAULT_COLORS,
|
59
|
+
metavar="COLORS",
|
60
|
+
help="2, 3, 4, or 6 Tk color values",
|
61
|
+
)
|
62
|
+
@click.option("--size", default=48, help="initial size in pixels")
|
63
|
+
@click.option("--min-size", default=None, type=int, help="minimum size in pixels (default: same as initial size)")
|
59
64
|
def main(colors: list[str], size: int, min_size: int | None) -> None: # noqa: PLR0915
|
60
65
|
"""Visualize the WWVB signal in realtime"""
|
61
66
|
if min_size is None:
|
62
67
|
min_size = size
|
63
68
|
|
64
|
-
def
|
65
|
-
"""
|
66
|
-
now =
|
67
|
-
|
68
|
-
time.sleep(deadline - now)
|
69
|
+
def deadline_ms(deadline: datetime.datetime) -> int:
|
70
|
+
"""Compute the number of ms until a deadline"""
|
71
|
+
now = datetime.datetime.now(datetime.timezone.utc)
|
72
|
+
return int(max(0, (deadline - now).total_seconds()) * 1000)
|
69
73
|
|
70
|
-
def wwvbtick() -> Generator[tuple[
|
74
|
+
def wwvbtick() -> Generator[tuple[datetime.datetime, wwvb.AmplitudeModulation]]:
|
71
75
|
"""Yield consecutive values of the WWVB amplitude signal, going from minute to minute"""
|
72
|
-
timestamp =
|
76
|
+
timestamp = datetime.datetime.now(datetime.timezone.utc).replace(second=0, microsecond=0)
|
73
77
|
|
74
78
|
while True:
|
75
|
-
|
76
|
-
key = tt.tm_year, tt.tm_yday, tt.tm_hour, tt.tm_min
|
77
|
-
timecode = wwvb.WWVBMinuteIERS(*key).as_timecode()
|
79
|
+
timecode = wwvb.WWVBMinuteIERS.from_datetime(timestamp).as_timecode()
|
78
80
|
for i, code in enumerate(timecode.am):
|
79
|
-
yield timestamp + i, code
|
80
|
-
timestamp = timestamp + 60
|
81
|
+
yield timestamp + datetime.timedelta(seconds=i), code
|
82
|
+
timestamp = timestamp + datetime.timedelta(seconds=60)
|
81
83
|
|
82
|
-
def wwvbsmarttick() -> Generator[tuple[
|
84
|
+
def wwvbsmarttick() -> Generator[tuple[datetime.datetime, wwvb.AmplitudeModulation]]:
|
83
85
|
"""Yield consecutive values of the WWVB amplitude signal
|
84
86
|
|
85
87
|
.. but deal with time progressing unexpectedly, such as when the
|
@@ -90,10 +92,10 @@ def main(colors: list[str], size: int, min_size: int | None) -> None: # noqa: P
|
|
90
92
|
"""
|
91
93
|
while True:
|
92
94
|
for stamp, code in wwvbtick():
|
93
|
-
now =
|
94
|
-
if stamp < now - 60:
|
95
|
+
now = datetime.datetime.now(datetime.timezone.utc)
|
96
|
+
if stamp < now - datetime.timedelta(seconds=60):
|
95
97
|
break
|
96
|
-
if stamp < now - 1:
|
98
|
+
if stamp < now - datetime.timedelta(seconds=1):
|
97
99
|
continue
|
98
100
|
yield stamp, code
|
99
101
|
|
@@ -127,18 +129,23 @@ def main(colors: list[str], size: int, min_size: int | None) -> None: # noqa: P
|
|
127
129
|
"""Turn the canvas's virtual LED off"""
|
128
130
|
canvas.itemconfigure(circle, fill=colors[i])
|
129
131
|
|
130
|
-
def
|
131
|
-
"""Update the canvas virtual LED"""
|
132
|
+
def controller_func() -> Generator[int]:
|
133
|
+
"""Update the canvas virtual LED, yielding the number of ms until the next change"""
|
132
134
|
for stamp, code in wwvbsmarttick():
|
133
|
-
|
135
|
+
yield deadline_ms(stamp)
|
134
136
|
led_on(code)
|
135
137
|
app.update()
|
136
|
-
|
138
|
+
yield deadline_ms(stamp + datetime.timedelta(seconds=0.2 + 0.3 * int(code)))
|
137
139
|
led_off(code)
|
138
140
|
app.update()
|
139
141
|
|
140
|
-
|
141
|
-
|
142
|
+
controller = controller_func().__next__
|
143
|
+
|
144
|
+
def after_func() -> None:
|
145
|
+
"""Repeatedly run the controller after the desired interval"""
|
146
|
+
app.after(controller(), after_func)
|
147
|
+
|
148
|
+
app.after_idle(after_func)
|
142
149
|
app.mainloop()
|
143
150
|
|
144
151
|
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: wwvb
|
3
|
-
Version: 5.0.
|
3
|
+
Version: 5.0.3
|
4
4
|
Summary: Generate WWVB timecodes for any desired time
|
5
5
|
Author-email: Jeff Epler <jepler@gmail.com>
|
6
6
|
Project-URL: Source, https://github.com/jepler/wwvbpy
|
@@ -33,13 +33,11 @@ SPDX-License-Identifier: GPL-3.0-only
|
|
33
33
|
[](https://codecov.io/gh/jepler/wwvbpy)
|
34
34
|
[](https://github.com/jepler/wwvbpy/actions/workflows/cron.yml)
|
35
35
|
[](https://pypi.org/project/wwvb)
|
36
|
-
[](https://github.com/jepler/wwvbpy/actions/workflows/codeql.yml)
|
37
36
|
[](https://results.pre-commit.ci/latest/github/jepler/wwvbpy/main)
|
38
37
|
|
39
38
|
# Purpose
|
40
39
|
|
41
|
-
|
42
|
-
may be useful in testing WWVB decoder software.
|
40
|
+
Python package and command line programs for interacting with WWVB timecodes.
|
43
41
|
|
44
42
|
Where possible, wwvbpy uses existing facilities for calendar and time
|
45
43
|
manipulation (datetime and dateutil).
|
@@ -65,7 +63,7 @@ The package includes:
|
|
65
63
|
|
66
64
|
# Development status
|
67
65
|
|
68
|
-
The author (@jepler) occasionally develops and maintains this project, but
|
66
|
+
The author ([@jepler](https://github.com/jepler)) occasionally develops and maintains this project, but
|
69
67
|
issues are not likely to be acted on. They would be interested in adding
|
70
68
|
co-maintainer(s).
|
71
69
|
|
@@ -93,7 +91,7 @@ channel.
|
|
93
91
|
# Usage
|
94
92
|
|
95
93
|
~~~~
|
96
|
-
Usage:
|
94
|
+
Usage: wwvbgen [OPTIONS] [TIMESPEC]...
|
97
95
|
|
98
96
|
Generate WWVB timecodes
|
99
97
|
|
@@ -123,7 +121,7 @@ Options:
|
|
123
121
|
|
124
122
|
For example, to display the leap second that occurred at the end of 1998,
|
125
123
|
~~~~
|
126
|
-
$
|
124
|
+
$ wwvbgen -m 7 1998 365 23 56
|
127
125
|
WWVB timecode: year=98 days=365 hour=23 min=56 dst=0 ut1=-300 ly=0 ls=1
|
128
126
|
'98+365 23:56 210100110200100001120011001102010100010200110100121000001002
|
129
127
|
'98+365 23:57 210100111200100001120011001102010100010200110100121000001002
|
@@ -145,7 +143,7 @@ The letters `a` through `u` represent offsets of -1.0s through +1.0s
|
|
145
143
|
in 0.1s increments; `k` represents 0s. (In practice, only a smaller range
|
146
144
|
of values, typically -0.7s to +0.8s, is seen)
|
147
145
|
|
148
|
-
For 2001 through
|
146
|
+
For 2001 through 2024, NIST has published the actual DUT1 values broadcast,
|
149
147
|
and the date of each change, though it in the format of an HTML
|
150
148
|
table and not designed for machine readability:
|
151
149
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
uwwvb.py,sha256=KIx4eYsLVu6KzF-Pzr-0htDWbNK9r0W-V6eK-CvHctQ,5806
|
2
|
+
wwvb/__init__.py,sha256=hnIha8mfZfD5l-0YhDf4Hz3YSs-idvHgKh3pd5PHBhI,32868
|
3
|
+
wwvb/__version__.py,sha256=Go8qgjS_vxKNujIcp3hLHHuyaklRsxOHDXpF1FhhwKo,511
|
4
|
+
wwvb/decode.py,sha256=llTLKBW49nl6COheM90NsyMnTNeVApl2oeCHtl6Tf3w,2759
|
5
|
+
wwvb/dut1table.py,sha256=HVX1338RlQzAQ-bsMPEdmCqoyIxSWoJSoRu1YGyaJO4,875
|
6
|
+
wwvb/gen.py,sha256=_fpUypu_2nZfG5Vjnya0B8C26nk1WOhnLMTCXwskAHs,3720
|
7
|
+
wwvb/iersdata.json,sha256=VwE_jHstK9FpcfW-Txqh7CMd_w0hH0G5FEUVhNZ3biQ,765
|
8
|
+
wwvb/iersdata.json.license,sha256=1k5fhRCuOn0yXbwHtB21G0Nntnf0qMxstflMHuK3-Js,71
|
9
|
+
wwvb/iersdata.py,sha256=nMqA1xcE-iPtmi9m5qcTIJwfsCxZwoNsxHfM-wvooMQ,1210
|
10
|
+
wwvb/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
|
+
wwvb/tz.py,sha256=nlxKnzFPmqLLtC-cEDhWaJ3v3GCSPfqzVtUMf8EEdZ0,248
|
12
|
+
wwvb/updateiers.py,sha256=Zemj6m0hwPLP31y8ODAS7sqLeB8SSHvv833pfUPHMUo,5661
|
13
|
+
wwvb/wwvbtk.py,sha256=9RcSssben6CEsX7P3Rmphvmlnp8HoAb9gMFcCP35ryk,5184
|
14
|
+
wwvb-5.0.3.dist-info/METADATA,sha256=Nq0GMmxrFBXRlCBnznV3BoiyWZSAm8Y8n7JKI4BAjug,10201
|
15
|
+
wwvb-5.0.3.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
16
|
+
wwvb-5.0.3.dist-info/entry_points.txt,sha256=KSevvHWLEKxOxUQ-L-OQidD4Sj2BPEfhZ2TQhOgyys4,179
|
17
|
+
wwvb-5.0.3.dist-info/top_level.txt,sha256=0IYdkhEAMgurpv_F-76rlyn4GdxepGFzG99tivVdQVU,11
|
18
|
+
wwvb-5.0.3.dist-info/RECORD,,
|
wwvb-5.0.1.dist-info/RECORD
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
uwwvb.py,sha256=dyYlxZ6tIlcbKsWExNHKA8OtRfbziaCy5Xcj_97bGtA,5737
|
2
|
-
wwvb/__init__.py,sha256=rDpr-XLoMld473qP-ufDunQxiDbLQDeS6eN7NOBtW84,30790
|
3
|
-
wwvb/__version__.py,sha256=JvW7iE7HU5WJ7w8sJRljbNkHyU5yXDp5FspBFoPj9uI,411
|
4
|
-
wwvb/decode.py,sha256=llTLKBW49nl6COheM90NsyMnTNeVApl2oeCHtl6Tf3w,2759
|
5
|
-
wwvb/dut1table.py,sha256=HVX1338RlQzAQ-bsMPEdmCqoyIxSWoJSoRu1YGyaJO4,875
|
6
|
-
wwvb/gen.py,sha256=_fpUypu_2nZfG5Vjnya0B8C26nk1WOhnLMTCXwskAHs,3720
|
7
|
-
wwvb/iersdata.json,sha256=6t7eLeCeORPc43rhp0y-85TpomvjKdYu4W4sXwq0_6I,765
|
8
|
-
wwvb/iersdata.json.license,sha256=1k5fhRCuOn0yXbwHtB21G0Nntnf0qMxstflMHuK3-Js,71
|
9
|
-
wwvb/iersdata.py,sha256=nMqA1xcE-iPtmi9m5qcTIJwfsCxZwoNsxHfM-wvooMQ,1210
|
10
|
-
wwvb/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
|
-
wwvb/tz.py,sha256=nlxKnzFPmqLLtC-cEDhWaJ3v3GCSPfqzVtUMf8EEdZ0,248
|
12
|
-
wwvb/updateiers.py,sha256=2kIzgLD_109EpapdscZj7dGt8z-KzE7Yp4LhRi5NW7Q,5671
|
13
|
-
wwvb/wwvbtk.py,sha256=QBJntkgLJPPjsxYC0szDZZXajijUbuwEmU_mjGEJ4GI,4599
|
14
|
-
wwvb-5.0.1.dist-info/METADATA,sha256=3v5sc8NTLWq5SwMHSAlyKQDbWcfnX_NvvvjPBTn7G8U,10382
|
15
|
-
wwvb-5.0.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
16
|
-
wwvb-5.0.1.dist-info/entry_points.txt,sha256=KSevvHWLEKxOxUQ-L-OQidD4Sj2BPEfhZ2TQhOgyys4,179
|
17
|
-
wwvb-5.0.1.dist-info/top_level.txt,sha256=0IYdkhEAMgurpv_F-76rlyn4GdxepGFzG99tivVdQVU,11
|
18
|
-
wwvb-5.0.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|