diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..009b16c --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2024 Jeff Epler +# +# SPDX-License-Identifier: GPL-3.0-only + +version: 2 + +build: + os: ubuntu-lts-latest + tools: + python: "3" + +sphinx: + configuration: doc/conf.py + +python: + install: + - requirements: requirements-dev.txt diff --git a/README.md b/README.md index f352a29..2a46366 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,7 @@ SPDX-License-Identifier: GPL-3.0-only # Purpose -wwvbpy generates WWVB timecodes for any desired time. These timecodes -may be useful in testing WWVB decoder software. +Python package and command line programs for interacting with WWVB timecodes. Where possible, wwvbpy uses existing facilities for calendar and time manipulation (datetime and dateutil). @@ -38,7 +37,7 @@ The package includes: # Development status -The author (@jepler) occasionally develops and maintains this project, but +The author ([@jepler](https://github.com/jepler)) occasionally develops and maintains this project, but issues are not likely to be acted on. They would be interested in adding co-maintainer(s). @@ -66,7 +65,7 @@ channel. # Usage ~~~~ -Usage: python -m wwvb.gen [OPTIONS] [TIMESPEC]... +Usage: wwvbgen [OPTIONS] [TIMESPEC]... Generate WWVB timecodes @@ -96,7 +95,7 @@ Options: For example, to display the leap second that occurred at the end of 1998, ~~~~ -$ python wwvbgen.py -m 7 1998 365 23 56 +$ wwvbgen -m 7 1998 365 23 56 WWVB timecode: year=98 days=365 hour=23 min=56 dst=0 ut1=-300 ly=0 ls=1 '98+365 23:56 210100110200100001120011001102010100010200110100121000001002 '98+365 23:57 210100111200100001120011001102010100010200110100121000001002 @@ -118,7 +117,7 @@ The letters `a` through `u` represent offsets of -1.0s through +1.0s in 0.1s increments; `k` represents 0s. (In practice, only a smaller range of values, typically -0.7s to +0.8s, is seen) -For 2001 through 2019, NIST has published the actual DUT1 values broadcast, +For 2001 through 2024, NIST has published the actual DUT1 values broadcast, and the date of each change, though it in the format of an HTML table and not designed for machine readability: diff --git a/doc/conf.py b/doc/conf.py index 3a7f17f..418c18e 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -56,6 +56,7 @@ version = release = final_version # ones. extensions = [ "sphinx.ext.autodoc", + "sphinx_mdinclude", ] # Add any paths that contain templates here, relative to this directory. @@ -82,6 +83,10 @@ html_static_path = ["_static"] autodoc_typehints = "description" autodoc_class_signature = "separated" +default_role = "any" + +intersphinx_mapping = {'py': ('https://docs.python.org/3', None)} + # SPDX-FileCopyrightText: 2021-2024 Jeff Epler # # SPDX-License-Identifier: GPL-3.0-only diff --git a/doc/index.rst b/doc/index.rst index 0bdd7d1..df709d4 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -2,17 +2,10 @@ .. .. SPDX-License-Identifier: GPL-3.0-only -wwvbpy -====== - -.. image:: https://github.com/jepler/wwvbpy/actions/workflows/test.yml/badge.svg - :target: https://github.com/jepler/wwvbpy/actions/workflows/test.yml - :alt: Test wwvbpy - -.. image:: https://img.shields.io/pypi/v/wwvb - :target: https://pypi.org/project/wwvb - :alt: PyPI +wwvbpy |version| +================ +.. mdinclude:: ../README.md .. toctree:: :maxdepth: 2 diff --git a/requirements-dev.txt b/requirements-dev.txt index 7538bb4..bb8fe6c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -14,9 +14,10 @@ pre-commit python-dateutil requests; implementation_name=="cpython" setuptools>=68; implementation_name=="cpython" -sphinx>=7,<8 +sphinx sphinx-autodoc-typehints sphinx-rtd-theme +sphinx-mdinclude twine; implementation_name=="cpython" types-beautifulsoup4; implementation_name=="cpython" types-python-dateutil; implementation_name=="cpython" diff --git a/src/uwwvb.py b/src/uwwvb.py index 76c252a..d5b5164 100644 --- a/src/uwwvb.py +++ b/src/uwwvb.py @@ -4,7 +4,10 @@ # ruff: noqa: C405 PYI024 PLR2004 FBT001 FBT002 -"""Implementation of a WWVB state machine & decoder for resource-constrained systems""" +"""Implementation of a WWVB state machine & decoder for resource-constrained systems + +This version is intended for use with MicroPython & CircuitPython. +""" from __future__ import annotations diff --git a/src/wwvb/__init__.py b/src/wwvb/__init__.py index 314aa88..4018564 100644 --- a/src/wwvb/__init__.py +++ b/src/wwvb/__init__.py @@ -1,5 +1,13 @@ #!/usr/bin/python3 -"""A library for WWVB timecodes""" +"""A package and CLI for WWVB timecodes + +This is the full featured library suitable for use on 'real computers'. +For a reduced version suitable for use on MicroPython & CircuitPython, +see `uwwvb`. + +This package also includes the commandline programs listed above, +perhaps most importantly ``wwvbgen`` for generating WWVB timecodes. +""" # SPDX-FileCopyrightText: 2011-2024 Jeff Epler # @@ -322,16 +330,17 @@ class DstStatus(enum.IntEnum): """Constants that describe the DST status of a minute""" DST_NOT_IN_EFFECT = 0b00 + """DST not in effect today""" DST_STARTS_TODAY = 0b01 + """DST starts today at 0200 local standard time""" DST_ENDS_TODAY = 0b10 + """DST ends today at 0200 local standard time""" DST_IN_EFFECT = 0b11 + """DST in effect all day today""" class _WWVBMinute(NamedTuple): - """Uniquely identifies a minute of time in the WWVB system. - - To use ut1 and ls information from IERS, create a WWVBMinuteIERS value instead. - """ + """(implementation detail)""" year: int """2-digit year within the WWVB epoch""" @@ -361,7 +370,8 @@ class _WWVBMinute(NamedTuple): class WWVBMinute(_WWVBMinute): """Uniquely identifies a minute of time in the WWVB system. - To use ut1 and ls information from IERS, create a WWVBMinuteIERS value instead. + To use ``ut1`` and ``ls`` information from IERS, create a `WWVBMinuteIERS` + object instead. """ epoch: int = 1970 @@ -377,7 +387,19 @@ class WWVBMinute(_WWVBMinute): ls: bool | None = None, ly: bool | None = None, ) -> WWVBMinute: - """Construct a WWVBMinute""" + """Construct a WWVBMinute + + :param year: The 2- or 4-digit year. This parameter is converted by the `full_year` method. + :param days: 1-based day of year + + :param hour: UTC hour of day + + :param minute: Minute of hour + :param dst: 2-bit DST code + :param ut1: UT1 offset in units of 100ms, range -900 to +900ms + :param ls: Leap second warning flag + :param ly: Leap year flag + """ dst = cls.get_dst(year, days) if dst is None else DstStatus(dst) if ut1 is None and ls is None: ut1, ls = cls._get_dut1_info(year, days) @@ -425,7 +447,10 @@ class WWVBMinute(_WWVBMinute): ) def as_datetime_utc(self) -> datetime.datetime: - """Convert to a UTC datetime""" + """Convert to a UTC datetime + + The returned object has ``tzinfo=datetime.timezone.utc``. + """ d = datetime.datetime(self.year, 1, 1, tzinfo=datetime.timezone.utc) d += datetime.timedelta(self.days - 1, self.hour * 3600 + self.min * 60) return d @@ -438,7 +463,18 @@ class WWVBMinute(_WWVBMinute): *, dst_observed: bool = True, ) -> datetime.datetime: - """Convert to a local datetime according to the DST bits""" + """Convert to a local datetime according to the DST bits + + The returned object has ``tz=datetime.timezone(computed_offset)``. + + :param standard_time_offset: The UTC offset of local standard time, in seconds west of UTC. + The default value, ``7 * 3600``, is for Colorado, the source of the WWVB broadcast. + + :param dst_observed: If ``True`` then the locale observes DST, and a + one hour offset is applied according to WWVB rules. If ``False``, then + the standard time offset is used at all times. + + """ u = self.as_datetime_utc() offset = datetime.timedelta(seconds=-standard_time_offset) d = u - datetime.timedelta(seconds=standard_time_offset) @@ -750,9 +786,13 @@ class AmplitudeModulation(enum.IntEnum): """Constants that describe an Amplitude Modulation value""" ZERO = 0 + """A zero bit (reduced carrier during the first 200ms of the second)""" ONE = 1 + """A one bit (reduced carrier during the first 500ms of the second)""" MARK = 2 + """A mark bit (reduced carrier during the first 800ms of the second)""" UNSET = -1 + """An unset or unknown amplitude modulation value""" @enum.unique @@ -760,8 +800,11 @@ class PhaseModulation(enum.IntEnum): """Constants that describe a Phase Modulation value""" ZERO = 0 + """A one bit (180° phase shift during the second)""" ONE = 1 + """A zero bit (No phase shift during the second)""" UNSET = -1 + """An unset or unknown phase modulation value""" class WWVBTimecode: