diff --git a/Makefile b/Makefile index b630b67..45f3f14 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,15 @@ test_venv: mypy: $(Q)mypy --strict --no-warn-unused-ignores src test +.PHONY: pyright +pyright: + $(Q)pyright src test + +.PHONY: pyrefly +pyrefly: + $(Q)pyrefly check src test + + .PHONY: update update: $(Q)env PYTHONPATH=src $(PYTHON) -mwwvb.updateiers --dist diff --git a/src/wwvb/__init__.py b/src/wwvb/__init__.py index 46467dd..517c76f 100644 --- a/src/wwvb/__init__.py +++ b/src/wwvb/__init__.py @@ -411,7 +411,14 @@ class WWVBMinute(_WWVBMinute): year = cls.full_year(year) if ly is None: ly = isly(year) - return _WWVBMinute.__new__(cls, year, days, hour, minute, dst, ut1, ls, ly) + return super().__new__(cls, year, days, hour, minute, dst, ut1, ls, ly) + + def __init__(self, *args: Any, **kw: Any) -> None: + """Do-nothing function. + + Instance initialization is performed in __new__. This implementation of __init__ + works around a pyrefly bug. + """ @classmethod def full_year(cls, year: int) -> int: @@ -662,12 +669,12 @@ class WWVBMinute(_WWVBMinute): else: self._fill_pm_timecode_regular(t) - def next_minute(self, *, newut1: int | None = None, newls: bool | None = None) -> WWVBMinute: + def next_minute(self, *, newut1: int | None = None, newls: bool | None = None) -> Self: """Return an object representing the next minute""" d = self.as_datetime() + datetime.timedelta(minutes=1) return self.from_datetime(d, newut1=newut1, newls=newls, old_time=self) - def previous_minute(self, *, newut1: int | None = None, newls: bool | None = None) -> WWVBMinute: + def previous_minute(self, *, newut1: int | None = None, newls: bool | None = None) -> Self: """Return an object representing the previous minute""" d = self.as_datetime() - datetime.timedelta(minutes=1) return self.from_datetime(d, newut1=newut1, newls=newls, old_time=self) @@ -686,7 +693,7 @@ class WWVBMinute(_WWVBMinute): return 0, False @classmethod - def fromstring(cls, s: str) -> WWVBMinute: + def fromstring(cls, s: str) -> Self: """Construct a WWVBMinute from a string representation created by print_timecodes""" s = _removeprefix(s, "WWVB timecode: ") d: dict[str, int] = {} @@ -702,7 +709,7 @@ class WWVBMinute(_WWVBMinute): dst = d.pop("dst", None) ut1 = d.pop("ut1", None) ls = d.pop("ls", None) - d.pop("ly", None) + d.pop("ly", None) # Always use calculated ly flag if d: raise ValueError(f"Invalid options: {d}") return cls(year, days, hour, minute, dst, ut1=ut1, ls=None if ls is None else bool(ls)) @@ -715,7 +722,7 @@ class WWVBMinute(_WWVBMinute): newut1: int | None = None, newls: bool | None = None, old_time: WWVBMinute | None = None, - ) -> WWVBMinute: + ) -> Self: """Construct a WWVBMinute from a datetime, possibly specifying ut1/ls data or propagating it from an old time""" u = d.utctimetuple() if newls is None and newut1 is None: @@ -723,7 +730,7 @@ class WWVBMinute(_WWVBMinute): return cls(u.tm_year, u.tm_yday, u.tm_hour, u.tm_min, ut1=newut1, ls=newls) @classmethod - def from_timecode_am(cls, t: WWVBTimecode) -> WWVBMinute | None: # noqa: PLR0912 + def from_timecode_am(cls, t: WWVBTimecode) -> Self | None: # noqa: PLR0912 """Construct a WWVBMinute from a WWVBTimecode""" for i in (0, 9, 19, 29, 39, 49, 59): if t.am[i] != AmplitudeModulation.MARK: diff --git a/src/wwvb/updateiers.py b/src/wwvb/updateiers.py index 733e352..11357c5 100755 --- a/src/wwvb/updateiers.py +++ b/src/wwvb/updateiers.py @@ -47,6 +47,7 @@ def update_iersdata( # noqa: PLR0915 """Update iersdata.py""" offsets: list[int] = [] iersdata_text = _get_text(IERS_URL) + table_start: datetime.date | None = None for r in csv.DictReader(io.StringIO(iersdata_text), delimiter=";"): jd = float(r["MJD"]) offs_str = r["UT1-UTC"] @@ -79,6 +80,8 @@ def update_iersdata( # noqa: PLR0915 offsets.append(offs) + assert table_start is not None + wwvb_text = _get_text(NIST_URL) wwvb_data = bs4.BeautifulSoup(wwvb_text, features="html.parser") wwvb_dut1_table = wwvb_data.findAll("table")[2] diff --git a/src/wwvb/wwvbtk.py b/src/wwvb/wwvbtk.py index 2e68605..ef96892 100755 --- a/src/wwvb/wwvbtk.py +++ b/src/wwvb/wwvbtk.py @@ -69,7 +69,7 @@ def main(colors: list[str], size: int, min_size: int | None) -> None: # noqa: P def deadline_ms(deadline: datetime.datetime) -> int: """Compute the number of ms until a deadline""" now = datetime.datetime.now(datetime.timezone.utc) - return int(max(0, (deadline - now).total_seconds()) * 1000) + return int(max(0.0, (deadline - now).total_seconds()) * 1000) def wwvbtick() -> Generator[tuple[datetime.datetime, wwvb.AmplitudeModulation]]: """Yield consecutive values of the WWVB amplitude signal, going from minute to minute""" diff --git a/test/wwvbgen_testcases/enddst-phase b/test/wwvbgen_testcases/enddst-phase index c675b11..5649606 100644 --- a/test/wwvbgen_testcases/enddst-phase +++ b/test/wwvbgen_testcases/enddst-phase @@ -1,7 +1,14 @@ # SPDX-FileCopyrightText: 2021 Jeff Epler # # SPDX-License-Identifier: CC0-1.0 - +# +# "For six minutes each half hour, from 10–16 and 40–46 minutes past each hour, +# one-minute frames are replaced by a special extended time frame. Rather than +# transmitting 35 bits of information in one minute, this transmits 7 bits +# (time of day and DST status only) over 6 minutes, giving 30 times as much +# energy per transmitted bit, a 14.8 dB improvement in the link budget compared +# to the standard one-minute time code." (wikipedia) +# WWVB timecode: year=2021 days=311 hour=08 min=10 dst=1 ut1=-100 ly=0 ls=0 --channel=phase 2021-311 08:10 010000110100000111110110000001010110111111100110110101010001 2021-311 08:11 001001100111100011101110101111010010110010100111001000110001