Merge pull request #21 from jepler/document-data-sources

Update & document data sources
This commit is contained in:
Jeff Epler 2024-07-17 22:03:30 -05:00 committed by GitHub
commit 9817b72ffd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 76 additions and 14 deletions

View file

@ -67,7 +67,7 @@ jobs:
run: make mypy
- name: Test
run: python -mcoverage run --branch -m unittest testleapseconddata.py && python -mcoverage report --fail-under=100 && python -mcoverage xml
run: python -X tracemalloc=3 -mcoverage run --branch -m unittest testleapseconddata.py && python -mcoverage report --fail-under=100 && python -mcoverage xml
pre-commit:
runs-on: ubuntu-latest

View file

@ -51,7 +51,7 @@ templates_path = ['_templates']
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '_env']
# -- Options for HTML output -------------------------------------------------

View file

@ -30,7 +30,7 @@ import pathlib
import re
import urllib.request
from dataclasses import dataclass, field
from typing import BinaryIO
from typing import BinaryIO, ClassVar
tai = datetime.timezone(datetime.timedelta(0), "TAI")
@ -76,6 +76,49 @@ class LeapSecondData:
:param Optional[datetime.datetime] updated: The last update time of the data
"""
standard_file_sources: ClassVar[list[str]] = [
"file:///usr/share/zoneinfo/leap-seconds.list", # Debian Linux
"file:///var/db/ntpd.leap-seconds.list", # FreeBSD
]
"""When using `LeapSecondData.from_standard_source`, these local sources are checked first.
Locations for Debian Linux & FreeBSD are supported."""
standard_network_sources: ClassVar[list[str]] = [
"https://hpiers.obspm.fr/iers/bul/bulc/ntp/leap-seconds.list",
"https://data.iana.org/time-zones/tzdb/leap-seconds.list",
"https://raw.githubusercontent.com/eggert/tz/main/leap-seconds.list",
"ftp://ftp.boulder.nist.gov/pub/time/leap-seconds.list",
"https://www.meinberg.de/download/ntp/leap-seconds.list",
]
"""When using `LeapSecondData.from_standard_source`, these network sources are checked second.
Remote sources are checked in the following order until a suitable file is found:
* The `International Earth Rotation Service (IERS)
<https://www.iers.org/IERS/EN/Home/home_node.html>`_ is the international
body charged with various duties including scheduling leap seconds.
* The `Internet Assigned Numbers Authority (IANA)
<https://www.iana.org/>`_ publishes the IANA timezone database, used by
many major operating sytsems for handling the world's time zones. As part
of this activity they publish a version of the leap second list.
* `eggert/tz <https://github.com/eggert/tz>`_ is the canonical github home
of the IANA timezone database, and updated versions of the leap second
list can appear here before they are part of an official IANA timezone
database release.
* `The National Institute of Standards and Technology (NIST)'s Time
Realization and Distribution Group
<https://www.nist.gov/pml/time-and-frequency-division/time-distribution/internet-time-service-its>`_
is a US federal organization that publishes a version of the leap second
database.
* `Meinberg Funkuhren GmbH & Co. KG
<https://www.meinbergglobal.com/english/company/>`_ is a Germany-based
company that published a `helpful article in its knowledge base
<https://kb.meinbergglobal.com/kb/time_sync/ntp/configuration/ntp_leap_second_file>`_
including URLs of sites that disseminate the leap second list. They state
that the version they distribute is frequently more up to date than other
sources, including IANA, NIST, and tzdb."""
leap_seconds: list[LeapSecondInfo]
"""All known and scheduled leap seconds"""
@ -204,12 +247,7 @@ class LeapSecondData:
leap-second.list data valid for the given timestamp, or the current
time (if unspecified)
"""
for location in [ # pragma no branch
"file:///usr/share/zoneinfo/leap-seconds.list", # Debian Linux
"file:///var/db/ntpd.leap-seconds.list", # FreeBSD
"https://raw.githubusercontent.com/eggert/tz/main/leap-seconds.list",
"https://www.meinberg.de/download/ntp/leap-seconds.list",
]:
for location in cls.standard_file_sources + cls.standard_network_sources:
logging.debug("Trying leap second data from %s", location)
try:
candidate = cls.from_url(location, check_hash=check_hash)
@ -244,14 +282,13 @@ class LeapSecondData:
@classmethod
def from_url(
cls,
url: str = "https://raw.githubusercontent.com/eggert/tz/main/leap-seconds.list",
url: str,
*,
check_hash: bool = True,
) -> LeapSecondData | None:
"""Retrieve the leap second list from a local file
:param filename: URL to read leap second data from. The
default is maintained by the tzdata authors
:param filename: URL to read leap second data from
:param check_hash: Whether to check the embedded hash
"""
try:

View file

@ -13,7 +13,7 @@ from dataclasses import dataclass
import click
from . import LeapSecondData, tai
from . import InvalidHashError, LeapSecondData, tai
utc = datetime.timezone.utc
@ -165,5 +165,29 @@ def table(ctx: click.Context, *, start: datetime.datetime, end: datetime.datetim
print(f"{leap_second.start:%Y-%m-%d}: {leap_second.tai_offset.total_seconds():.0f}")
@cli.command
def sources() -> None:
"""Print information about leap-second.list data sources"""
first = True
for location in LeapSecondData.standard_file_sources + LeapSecondData.standard_network_sources:
if not first:
print()
first = False
try:
leap_second_data = LeapSecondData.from_url(location, check_hash=True)
except InvalidHashError: # pragma no coverage
print(f"{location}: Invalid hash")
leap_second_data = LeapSecondData.from_url(location, check_hash=False)
except Exception as e: # pragma no coverage # noqa: BLE001
print(f"{location}: {e}")
leap_second_data = None
if leap_second_data is not None:
print(f"{location}: Last updated {leap_second_data.last_updated}")
print(f"{location}: Valid until {leap_second_data.valid_until}")
else:
print(f"{location}: Could not be read")
if __name__ == "__main__": # pragma no cover
cli()

View file

@ -19,7 +19,7 @@ write_to = "leapseconddata/__version__.py"
line-length=120
[tool.ruff.lint]
select = ["E", "F", "D", "I", "N", "UP", "YTT", "BLE", "B", "FBT", "A", "COM", "C4", "DTZ", "FA", "ISC", "ICN", "PIE", "PYI", "Q", "RET", "SIM", "TID", "TCH", "ARG", "PTH", "C", "R", "W", "FLY", "RUF", "PL"]
ignore = ["D203", "D213", "D400", "D415", "ISC001"]
ignore = ["D203", "D213", "D400", "D415", "ISC001", "COM812"]
[project]
name = "leapseconddata"
authors = [{name = "Jeff Epler", email = "jepler@gmail.com"}]

View file

@ -41,6 +41,7 @@ class LeapSecondDataTest(unittest.TestCase):
self.run_main("next-leapsecond", "2100-2-2")
self.run_main("previous-leapsecond", "2009-2-2")
self.run_main("previous-leapsecond", "1960-2-2")
self.run_main("sources")
def test_corrupt(self) -> None:
self.assertRaises(