wwvb 6.0.1__py3-none-any.whl → 8.0.0rc1__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.
- wwvb/__init__.py +74 -47
- wwvb/__version__.py +16 -3
- wwvb/decode.py +1 -1
- wwvb/gen.py +6 -3
- wwvb/iersdata.json +1 -1
- wwvb/updateiers.py +4 -0
- wwvb/wwvbtk.py +7 -5
- {wwvb-6.0.1.dist-info → wwvb-8.0.0rc1.dist-info}/METADATA +4 -8
- wwvb-8.0.0rc1.dist-info/RECORD +18 -0
- wwvb-6.0.1.dist-info/RECORD +0 -18
- {wwvb-6.0.1.dist-info → wwvb-8.0.0rc1.dist-info}/WHEEL +0 -0
- {wwvb-6.0.1.dist-info → wwvb-8.0.0rc1.dist-info}/entry_points.txt +0 -0
- {wwvb-6.0.1.dist-info → wwvb-8.0.0rc1.dist-info}/top_level.txt +0 -0
wwvb/__init__.py
CHANGED
@@ -19,23 +19,36 @@ import datetime
|
|
19
19
|
import enum
|
20
20
|
import json
|
21
21
|
import warnings
|
22
|
-
from
|
22
|
+
from dataclasses import dataclass
|
23
|
+
from typing import ClassVar, Literal
|
23
24
|
|
24
25
|
from . import iersdata
|
25
26
|
from .tz import Mountain
|
26
27
|
|
28
|
+
WWVBChannel = Literal["amplitude", "phase", "both"]
|
29
|
+
|
30
|
+
TYPE_CHECKING = False
|
27
31
|
if TYPE_CHECKING:
|
28
32
|
from collections.abc import Generator
|
33
|
+
from typing import NotRequired, Self, TextIO, TypedDict, TypeVar
|
29
34
|
|
30
|
-
|
31
|
-
|
32
|
-
T = TypeVar("T")
|
35
|
+
class JsonMinute(TypedDict):
|
36
|
+
"""Implementation detail
|
33
37
|
|
38
|
+
This is the Python object type that is serialized by `print_timecodes_json`
|
39
|
+
"""
|
34
40
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
41
|
+
year: int
|
42
|
+
days: int
|
43
|
+
hour: int
|
44
|
+
minute: int
|
45
|
+
amplitude: NotRequired[str]
|
46
|
+
phase: NotRequired[str]
|
47
|
+
|
48
|
+
T = TypeVar("T")
|
49
|
+
|
50
|
+
HOUR = datetime.timedelta(seconds=3600)
|
51
|
+
SECOND = datetime.timedelta(seconds=1)
|
39
52
|
|
40
53
|
|
41
54
|
def _date(dt: datetime.date) -> datetime.date:
|
@@ -339,8 +352,13 @@ class DstStatus(enum.IntEnum):
|
|
339
352
|
"""DST in effect all day today"""
|
340
353
|
|
341
354
|
|
342
|
-
|
343
|
-
|
355
|
+
@dataclass(frozen=True)
|
356
|
+
class WWVBMinute:
|
357
|
+
"""Uniquely identifies a minute of time in the WWVB system.
|
358
|
+
|
359
|
+
To use ``ut1`` and ``ls`` information from IERS, create a `WWVBMinuteIERS`
|
360
|
+
object instead.
|
361
|
+
"""
|
344
362
|
|
345
363
|
year: int
|
346
364
|
"""2-digit year within the WWVB epoch"""
|
@@ -351,7 +369,7 @@ class _WWVBMinute(NamedTuple):
|
|
351
369
|
hour: int
|
352
370
|
"""UTC hour of day"""
|
353
371
|
|
354
|
-
|
372
|
+
minute: int
|
355
373
|
"""Minute of hour"""
|
356
374
|
|
357
375
|
dst: DstStatus
|
@@ -366,18 +384,10 @@ class _WWVBMinute(NamedTuple):
|
|
366
384
|
ly: bool
|
367
385
|
"""Leap year flag"""
|
368
386
|
|
387
|
+
epoch: ClassVar[int] = 1970
|
369
388
|
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
To use ``ut1`` and ``ls`` information from IERS, create a `WWVBMinuteIERS`
|
374
|
-
object instead.
|
375
|
-
"""
|
376
|
-
|
377
|
-
epoch: int = 1970
|
378
|
-
|
379
|
-
def __new__( # noqa: PYI034
|
380
|
-
cls,
|
389
|
+
def __init__(
|
390
|
+
self,
|
381
391
|
year: int,
|
382
392
|
days: int,
|
383
393
|
hour: int,
|
@@ -387,7 +397,7 @@ class WWVBMinute(_WWVBMinute):
|
|
387
397
|
*,
|
388
398
|
ls: bool | None = None,
|
389
399
|
ly: bool | None = None,
|
390
|
-
) ->
|
400
|
+
) -> None:
|
391
401
|
"""Construct a WWVBMinute
|
392
402
|
|
393
403
|
:param year: The 2- or 4-digit year. This parameter is converted by the `full_year` method.
|
@@ -401,15 +411,23 @@ class WWVBMinute(_WWVBMinute):
|
|
401
411
|
:param ls: Leap second warning flag
|
402
412
|
:param ly: Leap year flag
|
403
413
|
"""
|
404
|
-
dst =
|
414
|
+
dst = self.get_dst(year, days) if dst is None else DstStatus(dst)
|
405
415
|
if ut1 is None and ls is None:
|
406
|
-
ut1, ls =
|
416
|
+
ut1, ls = self._get_dut1_info(year, days)
|
407
417
|
elif ut1 is None or ls is None:
|
408
418
|
raise ValueError("sepecify both ut1 and ls or neither one")
|
409
|
-
year =
|
419
|
+
year = self.full_year(year)
|
410
420
|
if ly is None:
|
411
421
|
ly = isly(year)
|
412
|
-
|
422
|
+
|
423
|
+
super().__setattr__("year", year)
|
424
|
+
super().__setattr__("days", days)
|
425
|
+
super().__setattr__("hour", hour)
|
426
|
+
super().__setattr__("minute", minute)
|
427
|
+
super().__setattr__("dst", dst)
|
428
|
+
super().__setattr__("ut1", ut1)
|
429
|
+
super().__setattr__("ls", ls)
|
430
|
+
super().__setattr__("ly", ly)
|
413
431
|
|
414
432
|
@classmethod
|
415
433
|
def full_year(cls, year: int) -> int:
|
@@ -443,7 +461,7 @@ class WWVBMinute(_WWVBMinute):
|
|
443
461
|
"""Implement str()"""
|
444
462
|
return (
|
445
463
|
f"year={self.year:4d} days={self.days:03d} hour={self.hour:02d} "
|
446
|
-
f"min={self.
|
464
|
+
f"min={self.minute:02d} dst={self.dst} ut1={self.ut1} ly={int(self.ly)} "
|
447
465
|
f"ls={int(self.ls)}"
|
448
466
|
)
|
449
467
|
|
@@ -453,7 +471,7 @@ class WWVBMinute(_WWVBMinute):
|
|
453
471
|
The returned object has ``tzinfo=datetime.timezone.utc``.
|
454
472
|
"""
|
455
473
|
d = datetime.datetime(self.year, 1, 1, tzinfo=datetime.timezone.utc)
|
456
|
-
d += datetime.timedelta(self.days - 1, self.hour * 3600 + self.
|
474
|
+
d += datetime.timedelta(self.days - 1, self.hour * 3600 + self.minute * 60)
|
457
475
|
return d
|
458
476
|
|
459
477
|
as_datetime = as_datetime_utc
|
@@ -507,7 +525,7 @@ class WWVBMinute(_WWVBMinute):
|
|
507
525
|
return 60
|
508
526
|
if not self._is_end_of_month():
|
509
527
|
return 60
|
510
|
-
if self.hour != 23 or self.
|
528
|
+
if self.hour != 23 or self.minute != 59:
|
511
529
|
return 60
|
512
530
|
if self.ut1 > 0:
|
513
531
|
return 59
|
@@ -551,7 +569,7 @@ class WWVBMinute(_WWVBMinute):
|
|
551
569
|
t.am[60] = AmplitudeModulation.MARK
|
552
570
|
for i in [4, 10, 11, 14, 20, 21, 24, 34, 35, 44, 54]:
|
553
571
|
t.am[i] = AmplitudeModulation.ZERO
|
554
|
-
t._put_am_bcd(self.
|
572
|
+
t._put_am_bcd(self.minute, 1, 2, 3, 5, 6, 7, 8)
|
555
573
|
t._put_am_bcd(self.hour, 12, 13, 15, 16, 17, 18)
|
556
574
|
t._put_am_bcd(self.days, 22, 23, 25, 26, 27, 28, 30, 31, 32, 33)
|
557
575
|
ut1_sign = self.ut1 >= 0
|
@@ -565,14 +583,14 @@ class WWVBMinute(_WWVBMinute):
|
|
565
583
|
|
566
584
|
def _fill_pm_timecode_extended(self, t: WWVBTimecode) -> None:
|
567
585
|
"""During minutes 10..15 and 40..45, the amplitude signal holds 'extended information'"""
|
568
|
-
assert 10 <= self.
|
569
|
-
minno = self.
|
586
|
+
assert 10 <= self.minute < 16 or 40 <= self.minute < 46
|
587
|
+
minno = self.minute % 10
|
570
588
|
assert minno < 6
|
571
589
|
|
572
590
|
dst = self.dst
|
573
591
|
# Note that these are 1 different than Table 11
|
574
592
|
# because our LFSR sequence is zero-based
|
575
|
-
seqno = (self.
|
593
|
+
seqno = (self.minute // 30) * 2
|
576
594
|
if dst == 0:
|
577
595
|
pass
|
578
596
|
elif dst == 3:
|
@@ -655,17 +673,17 @@ class WWVBMinute(_WWVBMinute):
|
|
655
673
|
|
656
674
|
def _fill_pm_timecode(self, t: WWVBTimecode) -> None:
|
657
675
|
"""Fill the phase portion of a timecode object"""
|
658
|
-
if 10 <= self.
|
676
|
+
if 10 <= self.minute < 16 or 40 <= self.minute < 46:
|
659
677
|
self._fill_pm_timecode_extended(t)
|
660
678
|
else:
|
661
679
|
self._fill_pm_timecode_regular(t)
|
662
680
|
|
663
|
-
def next_minute(self, *, newut1: int | None = None, newls: bool | None = None) ->
|
681
|
+
def next_minute(self, *, newut1: int | None = None, newls: bool | None = None) -> Self:
|
664
682
|
"""Return an object representing the next minute"""
|
665
683
|
d = self.as_datetime() + datetime.timedelta(minutes=1)
|
666
684
|
return self.from_datetime(d, newut1=newut1, newls=newls, old_time=self)
|
667
685
|
|
668
|
-
def previous_minute(self, *, newut1: int | None = None, newls: bool | None = None) ->
|
686
|
+
def previous_minute(self, *, newut1: int | None = None, newls: bool | None = None) -> Self:
|
669
687
|
"""Return an object representing the previous minute"""
|
670
688
|
d = self.as_datetime() - datetime.timedelta(minutes=1)
|
671
689
|
return self.from_datetime(d, newut1=newut1, newls=newls, old_time=self)
|
@@ -684,9 +702,9 @@ class WWVBMinute(_WWVBMinute):
|
|
684
702
|
return 0, False
|
685
703
|
|
686
704
|
@classmethod
|
687
|
-
def fromstring(cls, s: str) ->
|
705
|
+
def fromstring(cls, s: str) -> Self:
|
688
706
|
"""Construct a WWVBMinute from a string representation created by print_timecodes"""
|
689
|
-
s =
|
707
|
+
s = s.removeprefix("WWVB timecode: ")
|
690
708
|
d: dict[str, int] = {}
|
691
709
|
for part in s.split():
|
692
710
|
k, v = part.split("=")
|
@@ -700,7 +718,7 @@ class WWVBMinute(_WWVBMinute):
|
|
700
718
|
dst = d.pop("dst", None)
|
701
719
|
ut1 = d.pop("ut1", None)
|
702
720
|
ls = d.pop("ls", None)
|
703
|
-
d.pop("ly", None)
|
721
|
+
d.pop("ly", None) # Always use calculated ly flag
|
704
722
|
if d:
|
705
723
|
raise ValueError(f"Invalid options: {d}")
|
706
724
|
return cls(year, days, hour, minute, dst, ut1=ut1, ls=None if ls is None else bool(ls))
|
@@ -713,7 +731,7 @@ class WWVBMinute(_WWVBMinute):
|
|
713
731
|
newut1: int | None = None,
|
714
732
|
newls: bool | None = None,
|
715
733
|
old_time: WWVBMinute | None = None,
|
716
|
-
) ->
|
734
|
+
) -> Self:
|
717
735
|
"""Construct a WWVBMinute from a datetime, possibly specifying ut1/ls data or propagating it from an old time"""
|
718
736
|
u = d.utctimetuple()
|
719
737
|
if newls is None and newut1 is None:
|
@@ -721,7 +739,7 @@ class WWVBMinute(_WWVBMinute):
|
|
721
739
|
return cls(u.tm_year, u.tm_yday, u.tm_hour, u.tm_min, ut1=newut1, ls=newls)
|
722
740
|
|
723
741
|
@classmethod
|
724
|
-
def from_timecode_am(cls, t: WWVBTimecode) ->
|
742
|
+
def from_timecode_am(cls, t: WWVBTimecode) -> Self | None: # noqa: PLR0912
|
725
743
|
"""Construct a WWVBMinute from a WWVBTimecode"""
|
726
744
|
for i in (0, 9, 19, 29, 39, 49, 59):
|
727
745
|
if t.am[i] != AmplitudeModulation.MARK:
|
@@ -764,6 +782,15 @@ class WWVBMinute(_WWVBMinute):
|
|
764
782
|
return None
|
765
783
|
return cls(year, days, hour, minute, dst, ut1, ls=ls, ly=ly)
|
766
784
|
|
785
|
+
@property
|
786
|
+
def min(self) -> int:
|
787
|
+
"""Deprecated alias for `WWVBMinute.minute`
|
788
|
+
|
789
|
+
Update your code to use the `minute` property instead of the `min` property.
|
790
|
+
"""
|
791
|
+
warnings.warn("WWVBMinute.min property is deprecated", category=DeprecationWarning, stacklevel=1)
|
792
|
+
return self.minute
|
793
|
+
|
767
794
|
|
768
795
|
class WWVBMinuteIERS(WWVBMinute):
|
769
796
|
"""A WWVBMinute that uses a database of DUT1 information"""
|
@@ -915,7 +942,7 @@ styles = {
|
|
915
942
|
def print_timecodes(
|
916
943
|
w: WWVBMinute,
|
917
944
|
minutes: int,
|
918
|
-
channel:
|
945
|
+
channel: WWVBChannel,
|
919
946
|
style: str,
|
920
947
|
file: TextIO,
|
921
948
|
*,
|
@@ -934,7 +961,7 @@ def print_timecodes(
|
|
934
961
|
print(file=file)
|
935
962
|
print(f"WWVB timecode: {w!s}{channel_text}{style_text}", file=file)
|
936
963
|
first = False
|
937
|
-
pfx = f"{w.year:04d}-{w.days:03d} {w.hour:02d}:{w.
|
964
|
+
pfx = f"{w.year:04d}-{w.days:03d} {w.hour:02d}:{w.minute:02d} "
|
938
965
|
tc = w.as_timecode()
|
939
966
|
if len(style_chars) == 6:
|
940
967
|
print(f"{pfx} {tc.to_both_string(style_chars)}", file=file)
|
@@ -952,7 +979,7 @@ def print_timecodes(
|
|
952
979
|
def print_timecodes_json(
|
953
980
|
w: WWVBMinute,
|
954
981
|
minutes: int,
|
955
|
-
channel:
|
982
|
+
channel: WWVBChannel,
|
956
983
|
file: TextIO,
|
957
984
|
) -> None:
|
958
985
|
"""Print a range of timecodes in JSON format.
|
@@ -972,11 +999,11 @@ def print_timecodes_json(
|
|
972
999
|
"""
|
973
1000
|
result = []
|
974
1001
|
for _ in range(minutes):
|
975
|
-
data:
|
1002
|
+
data: JsonMinute = {
|
976
1003
|
"year": w.year,
|
977
1004
|
"days": w.days,
|
978
1005
|
"hour": w.hour,
|
979
|
-
"minute": w.
|
1006
|
+
"minute": w.minute,
|
980
1007
|
}
|
981
1008
|
|
982
1009
|
tc = w.as_timecode()
|
wwvb/__version__.py
CHANGED
@@ -1,7 +1,14 @@
|
|
1
1
|
# file generated by setuptools-scm
|
2
2
|
# don't change, don't track in version control
|
3
3
|
|
4
|
-
__all__ = [
|
4
|
+
__all__ = [
|
5
|
+
"__version__",
|
6
|
+
"__version_tuple__",
|
7
|
+
"version",
|
8
|
+
"version_tuple",
|
9
|
+
"__commit_id__",
|
10
|
+
"commit_id",
|
11
|
+
]
|
5
12
|
|
6
13
|
TYPE_CHECKING = False
|
7
14
|
if TYPE_CHECKING:
|
@@ -9,13 +16,19 @@ if TYPE_CHECKING:
|
|
9
16
|
from typing import Union
|
10
17
|
|
11
18
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
19
|
+
COMMIT_ID = Union[str, None]
|
12
20
|
else:
|
13
21
|
VERSION_TUPLE = object
|
22
|
+
COMMIT_ID = object
|
14
23
|
|
15
24
|
version: str
|
16
25
|
__version__: str
|
17
26
|
__version_tuple__: VERSION_TUPLE
|
18
27
|
version_tuple: VERSION_TUPLE
|
28
|
+
commit_id: COMMIT_ID
|
29
|
+
__commit_id__: COMMIT_ID
|
19
30
|
|
20
|
-
__version__ = version = '
|
21
|
-
__version_tuple__ = version_tuple = (
|
31
|
+
__version__ = version = '8.0.0rc1'
|
32
|
+
__version_tuple__ = version_tuple = (8, 0, 0, 'rc1')
|
33
|
+
|
34
|
+
__commit_id__ = commit_id = None
|
wwvb/decode.py
CHANGED
wwvb/gen.py
CHANGED
@@ -9,15 +9,18 @@ from __future__ import annotations
|
|
9
9
|
|
10
10
|
import datetime
|
11
11
|
import sys
|
12
|
-
from typing import Any
|
13
12
|
|
14
13
|
import click
|
15
14
|
import dateutil.parser
|
16
15
|
|
17
16
|
from . import WWVBMinute, WWVBMinuteIERS, print_timecodes, print_timecodes_json, styles
|
18
17
|
|
18
|
+
TYPE_CHECKING = False
|
19
|
+
if TYPE_CHECKING:
|
20
|
+
from . import WWVBChannel
|
19
21
|
|
20
|
-
|
22
|
+
|
23
|
+
def parse_timespec(ctx: click.Context, param: click.Parameter, value: list[str]) -> datetime.datetime: # noqa: ARG001
|
21
24
|
"""Parse a time specifier from the commandline"""
|
22
25
|
try:
|
23
26
|
if len(value) == 5:
|
@@ -95,7 +98,7 @@ def main(
|
|
95
98
|
dut1: int,
|
96
99
|
minutes: int,
|
97
100
|
style: str,
|
98
|
-
channel:
|
101
|
+
channel: WWVBChannel,
|
99
102
|
all_timecodes: bool,
|
100
103
|
timespec: datetime.datetime,
|
101
104
|
) -> None:
|
wwvb/iersdata.json
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"START": "1972-01-01", "OFFSETS_GZ": "
|
1
|
+
{"START": "1972-01-01", "OFFSETS_GZ": "H4sIAJLBtmgC/+2aa3LDMAiEL5uHLTuxnN5/pn/aTmfSSiAWhGy+E2SWZQE58zwiH/1YivB/96vMXiIX2Io8CTyIrDSWGqlMRdrpDa6aJFnr0m4wYZkCE2UmSF0V+13vBveStK6JTfQyW3O86HLJf0RvDgy5u4FCI+WVKTsVoUdHzsrRoWRfYHIItZ5EEgu0Beu58EgEpMpO9zf4/s3iNO4y7/hqEwOZIPu3+PuO2T7Ic5E8GxsnZHvUYOtELxW1WP+0yx/caFxpyAooq6lq06UEr+UkLeXOIDPZ6EBrqb5K8Tvu6/B9CdnZqFQL05s2KauWy/IeF/tJGAisjK9MgGyDuUkRq4G1gRE+VjA30uZNPsdantkgMq58QO4fw+sqzj+A2/16mmvnyy9UzDvMktDgKYlnkFeB2rx+wNANG40aA4OgsY03AWoDCVs/XMmkyQ0+0jWaUqPdwA0m/MRuccGjCwirHToWzbcs8P7U1nZZLSYdHapWu5HqVg1YjK2fPEwvPZPzLPUF848tyid2u7dh8B7h+wVQ923Q+kqxZe3JclSSB+YTM3nnHrjgFth/vzgZzw6cbOMYa4bHFPU/DR3mp/ubKM4cgwMnHZW4GFxFprOVcevAKGva6oExn1MOmyGDJQPm0rpU8bjqdOo993O6Xz9ofToZela5vwrWoTn4l4o5CIIaKejCEgSnJv784V+z0yyz2F/zCTF9VskETgAA"}
|
wwvb/updateiers.py
CHANGED
@@ -47,6 +47,7 @@ def update_iersdata( # noqa: PLR0915
|
|
47
47
|
"""Update iersdata.py"""
|
48
48
|
offsets: list[int] = []
|
49
49
|
iersdata_text = _get_text(IERS_URL)
|
50
|
+
table_start: datetime.date | None = None
|
50
51
|
for r in csv.DictReader(io.StringIO(iersdata_text), delimiter=";"):
|
51
52
|
jd = float(r["MJD"])
|
52
53
|
offs_str = r["UT1-UTC"]
|
@@ -79,6 +80,8 @@ def update_iersdata( # noqa: PLR0915
|
|
79
80
|
|
80
81
|
offsets.append(offs)
|
81
82
|
|
83
|
+
assert table_start is not None
|
84
|
+
|
82
85
|
wwvb_text = _get_text(NIST_URL)
|
83
86
|
wwvb_data = bs4.BeautifulSoup(wwvb_text, features="html.parser")
|
84
87
|
wwvb_dut1_table = wwvb_data.findAll("table")[2]
|
@@ -88,6 +91,7 @@ def update_iersdata( # noqa: PLR0915
|
|
88
91
|
wwvb_data_stamp = datetime.datetime.fromisoformat(meta.attrs["content"]).replace(tzinfo=None).date()
|
89
92
|
|
90
93
|
def patch(patch_start: datetime.date, patch_end: datetime.date, val: int) -> None:
|
94
|
+
assert table_start is not None
|
91
95
|
off_start = (patch_start - table_start).days
|
92
96
|
off_end = (patch_end - table_start).days
|
93
97
|
offsets[off_start:off_end] = [val] * (off_end - off_start)
|
wwvb/wwvbtk.py
CHANGED
@@ -8,13 +8,13 @@ from __future__ import annotations
|
|
8
8
|
|
9
9
|
import datetime
|
10
10
|
import functools
|
11
|
-
from tkinter import Canvas, TclError, Tk
|
12
|
-
from typing import TYPE_CHECKING, Any
|
11
|
+
from tkinter import Canvas, Event, TclError, Tk
|
13
12
|
|
14
13
|
import click
|
15
14
|
|
16
15
|
import wwvb
|
17
16
|
|
17
|
+
TYPE_CHECKING = False
|
18
18
|
if TYPE_CHECKING:
|
19
19
|
from collections.abc import Generator
|
20
20
|
|
@@ -25,7 +25,7 @@ def _app() -> Tk:
|
|
25
25
|
return Tk()
|
26
26
|
|
27
27
|
|
28
|
-
def validate_colors(ctx:
|
28
|
+
def validate_colors(ctx: click.Context, param: click.Parameter, value: str) -> list[str]: # noqa: ARG001
|
29
29
|
"""Check that all colors in a string are valid, splitting it to a list"""
|
30
30
|
app = _app()
|
31
31
|
colors = value.split()
|
@@ -69,7 +69,7 @@ def main(colors: list[str], size: int, min_size: int | None) -> None: # noqa: P
|
|
69
69
|
def deadline_ms(deadline: datetime.datetime) -> int:
|
70
70
|
"""Compute the number of ms until a deadline"""
|
71
71
|
now = datetime.datetime.now(datetime.timezone.utc)
|
72
|
-
return int(max(0, (deadline - now).total_seconds()) * 1000)
|
72
|
+
return int(max(0.0, (deadline - now).total_seconds()) * 1000)
|
73
73
|
|
74
74
|
def wwvbtick() -> Generator[tuple[datetime.datetime, wwvb.AmplitudeModulation]]:
|
75
75
|
"""Yield consecutive values of the WWVB amplitude signal, going from minute to minute"""
|
@@ -106,7 +106,7 @@ def main(colors: list[str], size: int, min_size: int | None) -> None: # noqa: P
|
|
106
106
|
canvas.pack(fill="both", expand=True)
|
107
107
|
app.wm_deiconify()
|
108
108
|
|
109
|
-
def resize_canvas(event:
|
109
|
+
def resize_canvas(event: Event) -> None:
|
110
110
|
"""Keep the circle filling the window when it is resized"""
|
111
111
|
sz = min(event.width, event.height) - 8
|
112
112
|
if sz < 0:
|
@@ -141,10 +141,12 @@ def main(colors: list[str], size: int, min_size: int | None) -> None: # noqa: P
|
|
141
141
|
|
142
142
|
controller = controller_func().__next__
|
143
143
|
|
144
|
+
# pyrefly: ignore # bad-assignment
|
144
145
|
def after_func() -> None:
|
145
146
|
"""Repeatedly run the controller after the desired interval"""
|
146
147
|
app.after(controller(), after_func)
|
147
148
|
|
149
|
+
# pyrefly: ignore # bad-argument-type
|
148
150
|
app.after_idle(after_func)
|
149
151
|
app.mainloop()
|
150
152
|
|
@@ -1,10 +1,10 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: wwvb
|
3
|
-
Version:
|
3
|
+
Version: 8.0.0rc1
|
4
4
|
Summary: Generate WWVB timecodes for any desired time
|
5
5
|
Author-email: Jeff Epler <jepler@gmail.com>
|
6
|
-
Project-URL: Source, https://
|
7
|
-
Project-URL: Documentation, https://
|
6
|
+
Project-URL: Source, https://codeberg.org/jepler/wwvbpy
|
7
|
+
Project-URL: Documentation, https://codeberg.org/jepler/wwvbpy
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
9
9
|
Classifier: Programming Language :: Python :: 3.9
|
10
10
|
Classifier: Programming Language :: Python :: 3.10
|
@@ -29,11 +29,7 @@ SPDX-FileCopyrightText: 2021-2024 Jeff Epler
|
|
29
29
|
|
30
30
|
SPDX-License-Identifier: GPL-3.0-only
|
31
31
|
-->
|
32
|
-
[](https://github.com/jepler/wwvbpy/actions/workflows/test.yml)
|
33
|
-
[](https://codecov.io/gh/jepler/wwvbpy)
|
34
|
-
[](https://github.com/jepler/wwvbpy/actions/workflows/cron.yml)
|
35
32
|
[](https://pypi.org/project/wwvb)
|
36
|
-
[](https://results.pre-commit.ci/latest/github/jepler/wwvbpy/main)
|
37
33
|
|
38
34
|
# Purpose
|
39
35
|
|
@@ -63,7 +59,7 @@ The package includes:
|
|
63
59
|
|
64
60
|
# Development status
|
65
61
|
|
66
|
-
The author ([@jepler](https://
|
62
|
+
The author ([@jepler](https://unpythonic.net)) occasionally develops and maintains this project, but
|
67
63
|
issues are not likely to be acted on. They would be interested in adding
|
68
64
|
co-maintainer(s).
|
69
65
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
uwwvb.py,sha256=Ybf8DeOZ5VjfqRGtO90_oEEW6HhGaztIVlh4L423MdQ,5801
|
2
|
+
wwvb/__init__.py,sha256=8QRSFHrGEEnjvalE_mtcVCGvEzPwUl7Os9srr1V5nY0,33832
|
3
|
+
wwvb/__version__.py,sha256=Rclf3PGSRaeFMb7D6Z8qQ4TeuY5MHu6vmzjC3bfviAU,714
|
4
|
+
wwvb/decode.py,sha256=JQw8XT9AoXlJAl16JRZzjECUPfA7jLhDxI-_XSibzbc,2748
|
5
|
+
wwvb/dut1table.py,sha256=HVX1338RlQzAQ-bsMPEdmCqoyIxSWoJSoRu1YGyaJO4,875
|
6
|
+
wwvb/gen.py,sha256=iMTYLtyy3ItC4AplTakkOhHNHzDFVmApjlVwYIK72nI,3798
|
7
|
+
wwvb/iersdata.json,sha256=lUMrygiih4_ItKX_t8Hqnx5bt9_1D1jocPycbtzOfhE,769
|
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=q3QY--fj06HJ9wCRCmVQK7pCpdXhCudP8BdUPG5WWOA,5781
|
13
|
+
wwvb/wwvbtk.py,sha256=cFBUduN8rQEdrpB0NtHK5r8HNDYfg_3XD7mbhYrVSrk,5284
|
14
|
+
wwvb-8.0.0rc1.dist-info/METADATA,sha256=f_izwyK9BqO8nSNAzycXQHzMsGSFi1oCsh1T1fHdzOg,9612
|
15
|
+
wwvb-8.0.0rc1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
16
|
+
wwvb-8.0.0rc1.dist-info/entry_points.txt,sha256=KSevvHWLEKxOxUQ-L-OQidD4Sj2BPEfhZ2TQhOgyys4,179
|
17
|
+
wwvb-8.0.0rc1.dist-info/top_level.txt,sha256=0IYdkhEAMgurpv_F-76rlyn4GdxepGFzG99tivVdQVU,11
|
18
|
+
wwvb-8.0.0rc1.dist-info/RECORD,,
|
wwvb-6.0.1.dist-info/RECORD
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
uwwvb.py,sha256=Ybf8DeOZ5VjfqRGtO90_oEEW6HhGaztIVlh4L423MdQ,5801
|
2
|
-
wwvb/__init__.py,sha256=MRuk6nzitxbIcUfqVLkx-EzGTYYxm96ItfBlR4BSZuQ,32922
|
3
|
-
wwvb/__version__.py,sha256=Csuu0TOiQZOQ062iwCy2GXvCmrtL4NFhA6E0Bwg71sw,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=jKYjjxlDTqY7hDhPmYYZqbb_ZCNtMFvsQlUu1JRnlgg,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-6.0.1.dist-info/METADATA,sha256=ghsmh_zcHo7cqytknevo4D_Ot4O1tOt5mZ3aacbJnFI,10201
|
15
|
-
wwvb-6.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
16
|
-
wwvb-6.0.1.dist-info/entry_points.txt,sha256=KSevvHWLEKxOxUQ-L-OQidD4Sj2BPEfhZ2TQhOgyys4,179
|
17
|
-
wwvb-6.0.1.dist-info/top_level.txt,sha256=0IYdkhEAMgurpv_F-76rlyn4GdxepGFzG99tivVdQVU,11
|
18
|
-
wwvb-6.0.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|