diff --git a/.github/workflows/checkpr.yml b/.github/workflows/checkpr.yml index 250fdc2..7f177ad 100644 --- a/.github/workflows/checkpr.yml +++ b/.github/workflows/checkpr.yml @@ -8,12 +8,12 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: [ "3.10", "3.11", "3.12", "3.13", "3.14" ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Install deploy dependencies diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 441b0b0..50f886b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -12,9 +12,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: 3.13 @@ -24,7 +24,7 @@ jobs: python -m build - name: Upload distributions # upload build artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: release-dists path: dist/ @@ -41,7 +41,7 @@ jobs: steps: - name: Download all the dists # download build artifacts saved in previous job - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: release-dists path: dist/ diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ce12857..9ca5298 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,12 +10,12 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: [ "3.10", "3.11", "3.12", "3.13", "3.14" ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Install deploy dependencies @@ -29,7 +29,7 @@ jobs: - name: Generate coverage report run: pytest - name: "Upload coverage to Codecov" - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} # supposedly not required for public repos fail_ci_if_error: true diff --git a/.vscode/settings.json b/.vscode/settings.json index fc577e7..ed1e3ac 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -21,7 +21,13 @@ "telemetry.feedback.enabled": false, "python.analysis.addHoverSummaries": false, "python-envs.defaultEnvManager": "ms-python.python:venv", - "python-envs.pythonProjects": [], + "python-envs.pythonProjects": [ + { + "path": ".", + "envManager": "ms-python.python:venv", + "packageManager": "ms-python.python:pip" + } + ], "python.defaultInterpreterPath": "${userHome}/pygpsclient/bin/python3", "python.testing.unittestEnabled": true, "python.testing.unittestArgs": [ diff --git a/README.md b/README.md index c79472a..700ddf2 100644 --- a/README.md +++ b/README.md @@ -329,9 +329,9 @@ b'$EIGNQ,RMC*24\r\n' - `planar` - finds planar distance in m between two sets of (lat, lon) coordinates - `bearing` - finds bearing in degrees between two sets of (lat, lon) coordinates - `area` - finds spherical area bounded by two sets of (lat, lon) coordinates - - `leapsecond` - find GPS UTC leapsecond offset for a given effective date - - `utc2wnotow` - converts UTC datetime to GPS WNO (week number), TOW (time of week in milliseconds) and Leapsecond offset - - `wnotow2utc` - converts GPS WNO, TOW and Leapsecond offset to UTC datetime + - `leapsecond` - find UTC leapsecond offset for a given effective date (epoch) and GNSS time system (n/a for GLONASS) + - `utc2wnotow` - converts UTC datetime to WNO (week number), TOW (time of week in milliseconds) and Leapsecond offset for various GNSS time systems (n/a for GLONASS). + - `wnotow2utc` - converts WNO (week number), TOW (time of week in milliseconds) and Leapsecond offset to UTC datetime for various GNSS time systems (n/a for GLONASS). See [Sphinx documentation](https://www.semuconsulting.com/pynmeagps/pynmeagps.html#module-pynmeagaps.nmeahelpers) for details. diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 07b8d6a..8bf2142 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,9 @@ # pynmeagps Release Notes +### RELEASE 1.1.3 + +1. Update wnotow2utc, utc2wnotow and leapsecond helper functions to accommodate all GNSS time systems. wnotow2utc method also adds an 'autoroll' argument which, if True, will automatically roll forward modular GNSS week numbers to the latest date less than the current date - see API docs for details. + ### RELEASE 1.1.2 1. Add workaround for Unicore UM9* firmware error, which creates malformed NMEA GLL sentences (-ve latitude). diff --git a/src/pynmeagps/_version.py b/src/pynmeagps/_version.py index 2628226..c6ef92b 100644 --- a/src/pynmeagps/_version.py +++ b/src/pynmeagps/_version.py @@ -8,4 +8,4 @@ :license: BSD 3-Clause """ -__version__ = "1.1.2" +__version__ = "1.1.3" diff --git a/src/pynmeagps/nmeahelpers.py b/src/pynmeagps/nmeahelpers.py index a80c435..051340e 100644 --- a/src/pynmeagps/nmeahelpers.py +++ b/src/pynmeagps/nmeahelpers.py @@ -13,16 +13,24 @@ import re from datetime import datetime, timedelta, timezone -from math import acos, asin, atan2, cos, pi, sin, sqrt +from math import acos, asin, atan2, cos, floor, pi, sin, sqrt from types import NoneType from typing import Literal import pynmeagps.exceptions as nme from pynmeagps.nmeatypes_core import ( + BDS, DM, DT, DTL, - GPSEPOCH0, + EPOCH0_BEIDOU, + EPOCH0_GAL, + EPOCH0_GPS, + EPOCH0_IRN, + GAL, + GLO, + GPS, + IRN, LA, LN, NMEA_MSGIDS, @@ -65,9 +73,9 @@ (3692217600, 37), # 1 Jan 2017 ] """ -Leapsecond list from NTC - Updated through IERS Bulletin C69 +Leapsecond reference table from NTC - Updated through IERS Bulletin C69 https://hpiers.obspm.fr/iers/bul/bulc/ntp/leap-seconds.list -File expires on: 28 June 2026 +File expires on: 28 December 2026 """ @@ -415,26 +423,6 @@ def generate_checksum(talker: str, msgID: str, payload: list) -> str: return calc_checksum(content) -def get_gpswnotow(dat: datetime) -> tuple[int, int]: - """ - Get GPS Week number (Wno) and Time of Week (Tow) - for midnight on given UTC date. If supplied date - is timezone-naive, UTC will be inferred. - - GPS Epoch 0 = 6th Jan 1980 - - :param datetime dat: calendar date - :return: wno, tow in seconds - :rtype: tuple[int, int] - """ - - if dat.tzinfo is None: - dat = dat.replace(tzinfo=timezone.utc) - - wno, tow, _ = utc2wnotow(dat) - return wno, int(tow / 1000) - - def get_parts(message: object) -> tuple: """ Get content, talker, msgid, payload and checksum of raw NMEA message. @@ -553,9 +541,9 @@ def latlon2dmm(lat: float, lon: float) -> tuple[str, str]: :rtype: tuple[str, str] """ - lat = deg2dmm(lat, LA) - lon = deg2dmm(lon, LN) - return lat, lon + latd = deg2dmm(lat, LA) + lond = deg2dmm(lon, LN) + return latd, lond def latlon2dms(lat: float, lon: float) -> tuple[str, str]: @@ -568,38 +556,44 @@ def latlon2dms(lat: float, lon: float) -> tuple[str, str]: :rtype: tuple[str, str] """ - lat = deg2dms(lat, LA) - lon = deg2dms(lon, LN) - return lat, lon + latd = deg2dms(lat, LA) + lond = deg2dms(lon, LN) + return latd, lond -def leapsecond(dat: datetime) -> int: +def leapsecond(dat: datetime, gnss: Literal["G", "E", "C", "J", "I"] = GPS) -> int: """ - Get GPS UTC leapsecond offset effective at date. - If supplied datetime is timezone naive, UTC will be inferred. + Get leapsecond offset effective at given GNSS epoch and + time system. If supplied datetime is timezone naive, + UTC will be inferred. + + G = GPS, E = Galileo, C = Beidou, J = QZSS, I = IRNSS (NavIC) - Refer to LEAPSECONDS constant. + Note: GLONASS is a non-continuous time system which already + takes into account UTC leapseconds. - - LEAPS0 = 1900-01-01 00:00:00 UTC - - GPSEPOCH0 = 1980-01-06: 00:00:00 UTC + Refer to LEAPSECONDS reference table. :param datetime.datetime dat: effective date + :param Literal["G","E","C","J","I"] gnss: GNSS time system e.g. "G" :return: leapsecond offset :rtype: int """ + def totsec(dt) -> int: + return (dt - LEAPS0).total_seconds() + if dat.tzinfo is None: dat = dat.replace(tzinfo=timezone.utc) - def totsec(dt) -> int: - return (dt - LEAPS0).total_seconds() + ep0 = EPOCH0_BEIDOU if gnss == BDS else EPOCH0_GPS if totsec(dat) < LEAPSECONDS[0][0]: return 0 dts = [] - for dt in (dat, GPSEPOCH0): - dts.append(max((val for key, val in LEAPSECONDS if key < totsec(dt)))) + for dt in (dat, ep0): + dts.append(max((val for key, val in LEAPSECONDS if key <= totsec(dt)))) return dts[0] - dts[1] @@ -704,7 +698,7 @@ def planar( return dist -def time2str(tim: datetime.time) -> str: +def time2str(tim: datetime) -> str: """ Convert datetime.time to NMEA formatted string. @@ -719,7 +713,7 @@ def time2str(tim: datetime.time) -> str: return "" -def time2utc(times: str) -> datetime.time: +def time2utc(times: str) -> datetime | str: """ Convert NMEA Time to UTC datetime. @@ -737,15 +731,23 @@ def time2utc(times: str) -> datetime.time: return "" -def utc2wnotow(utc: datetime | NoneType = None) -> tuple[int, int, int]: +def utc2wnotow( + utc: datetime | NoneType = None, + gnss: Literal["G", "E", "C", "J", "I"] = GPS, +) -> tuple[int, int, int]: """ - Get GPS Week number (wno), Time of Week (tow) in milliseconds - and leapsecond offset for given UTC datetime. If datetime is None, - will default to current datetime. + Get Week number (wno), Time of Week (tow) in milliseconds + and leapsecond offset for given GNSS UTC epoch and time system. - GPS Epoch 0 = 6th Jan 1980 + G = GPS, E = Galileo, C = Beidou, J = QZSS, I = IRNSS (NavIC) + + Note: GLONASS does not use GPS-style wno or tow. - :param datetime | NoneType utc: UTC datetime + If `utc` is None, will default to current UTC datetime. + + :param datetime | NoneType utc: UTC epoch + :param Literal["G","E","C","J","I"] = GPS) gnss: \ + GNSS time system (GPS) :return: wno, tow, leapsecond :rtype: tuple[int, int, int] """ @@ -755,31 +757,92 @@ def utc2wnotow(utc: datetime | NoneType = None) -> tuple[int, int, int]: if utc.tzinfo is None: utc = utc.replace(tzinfo=timezone.utc) - ls = leapsecond(utc) - ts = ((utc - GPSEPOCH0).total_seconds() + ls) * 1000 - wno = int((utc - GPSEPOCH0).days / 7) + if gnss == BDS: + ep0 = EPOCH0_BEIDOU + rollover = 8192 + elif gnss == GAL: + ep0 = EPOCH0_GAL + rollover = 4096 + elif gnss == IRN: + ep0 = EPOCH0_IRN + rollover = 1024 + else: + ep0 = EPOCH0_GPS + rollover = 1024 + + ls = 0 if gnss == GLO else leapsecond(utc, gnss) + ts = ((utc - ep0).total_seconds() + ls) * 1000 + wno = floor((utc - ep0).days / 7) tow = int(ts - wno * 604800000) - return wno, tow, ls + return wno % rollover, tow, ls -def wnotow2utc(wno: int, tow: int, ls: int | NoneType = None) -> datetime: +def wnotow2utc( + wno: int, + tow: int, + ls: int | NoneType = None, + gnss: Literal["G", "E", "C", "J", "I"] = GPS, + autoroll: bool = False, +) -> datetime: """ - Get UTC datetime from GPS Week number (wno), Time of Week (tow) - in milliseconds and leapsecond offset. If leapsecond is None, it - will default to the leapsecond offset applicable on the given date. + Convert week number and seconds of week (expressed as + milliseconds) for given GNSS epoch to UTC time. + + G = GPS, E = Galileo, C = Beidou, J = QZSS, I = IRNSS (NavIC) + + Note: GLONASS does not use GPS-style wno or tow. - GPS Epoch 0 = 6th Jan 1980 + If `ls` is None (the default), leapsecond offset will be + automatically derived from NTC reference table. - :param int wno: week number - :param int tow: time of week in ms - :param int ls: leapsecond offset - :return: datetime + If `autoroll` is True, rolls over modular wno to LATEST + date less than current date. + + e.g. if current date is 2026-04-17: + + wno=366, tow=381600000, gnss=GPS gives... + + - autoroll on: 2026-04-16 09:59:42+00:00 + - autoroll off: 1987-01-15 09:59:56+00:00 + + wno=368, tow=610000000, gnss=GPS gives... + + - autoroll on: 2006-09-17 01:26:26+00:00 + - autoroll off: 1987-02-01 01:26:36+00:00 + + :param int wno: week number (modular or mon-modular) + :param int tow: time of week in milliseconds (mod 604,800,000) + :param int | NoneType ls: leapsecond offset (will be derived if None) (None) + :param Literal["G","E","C","J","I"] = GPS) gnss: GNSS time system (GPS) + :param bool autoroll: automatic rollover (False) + :return: GNSS epoch as UTC datetime :rtype: datetime """ - utc = GPSEPOCH0 + timedelta(days=wno * 7) - utc += timedelta(milliseconds=tow) - if ls is None: - ls = leapsecond(utc) - utc -= timedelta(seconds=ls) + if gnss == BDS: + ep0 = EPOCH0_BEIDOU + rollover = 8192 + elif gnss == GAL: + ep0 = EPOCH0_GAL + rollover = 4096 + elif gnss == IRN: + ep0 = EPOCH0_IRN + rollover = 1024 + else: + ep0 = EPOCH0_GPS + rollover = 1024 + wno %= rollover + tow %= 604800000 + current = datetime.now(timezone.utc) + i = 0 + while True: + utc = ep0 + timedelta(days=(wno + (i * rollover)) * 7, milliseconds=tow) + if gnss != GLO: # apply leapsecond offset + lps = leapsecond(utc, gnss) if ls is None else ls + utc -= timedelta(seconds=lps) + if not autoroll: + break + if utc + timedelta(days=rollover * 7) > current: + break + i += 1 return utc diff --git a/src/pynmeagps/nmeatypes_core.py b/src/pynmeagps/nmeatypes_core.py index b130efe..efd71ed 100644 --- a/src/pynmeagps/nmeatypes_core.py +++ b/src/pynmeagps/nmeatypes_core.py @@ -65,9 +65,16 @@ ENCODE_DEFLATE = 8 """deflate socket encoding""" -# GPSEPOCH0 = datetime(1980, 1, 6) -GPSEPOCH0 = datetime(1980, 1, 6, tzinfo=timezone.utc) -"""GPS epoch base date""" +EPOCH0_BEIDOU = datetime(2006, 1, 1, 0, 0, 0, tzinfo=timezone.utc) +"""Base epoch for Beidou time system (BDT)""" +EPOCH0_GAL = datetime(1999, 8, 22, 0, 0, 0, tzinfo=timezone.utc) +"""Base epoch for Galileo time system""" +EPOCH0_GPS = datetime(1980, 1, 6, 0, 0, 0, tzinfo=timezone.utc) +"""Base epoch for GPS time system""" +EPOCH0_IRN = datetime(1999, 8, 22, 0, 0, 0, tzinfo=timezone.utc) +"""Base epoch for IRNSS/NAVIC time system""" +GPSEPOCH0 = EPOCH0_GPS # deprecated, for backward compatibility + # Geodetic datum spheroid values: # WGS84, ETRS89, EPSG4326 WGS84 = "WGS_84" @@ -77,6 +84,14 @@ WGS84_FLATTENING = 298.257223563 # flattening """WGS84 flattening""" +BDS = "C" +GAL = "E" +GLO = "R" +GPS = "G" +IRN = "I" +QZS = "J" +SBA = "S" + # *************************************************** # THESE ARE THE NMEA PROTOCOL PAYLOAD ATTRIBUTE TYPES # *************************************************** diff --git a/src/pynmeagps/nmeatypes_decodes.py b/src/pynmeagps/nmeatypes_decodes.py index 173f046..2c6ebcf 100644 --- a/src/pynmeagps/nmeatypes_decodes.py +++ b/src/pynmeagps/nmeatypes_decodes.py @@ -23,6 +23,16 @@ } """GGA quality/fix type values""" +POSMODE = { + "A": "2D", + "N": "N/A", + "E": "DR", + "D": "3D", + "F": "RTK FLOAT", + "R": "RTK FIXED", +} +"""RMC, GNS, GLL posmode values""" + GNSSLIST = { 0: "GPS", 1: "SBAS", diff --git a/src/pynmeagps/nmeatypes_get.py b/src/pynmeagps/nmeatypes_get.py index a174464..de4aee2 100644 --- a/src/pynmeagps/nmeatypes_get.py +++ b/src/pynmeagps/nmeatypes_get.py @@ -1312,6 +1312,11 @@ { "tdrtype": CH, "data": DE, + # Units of measurement A = Amperes B = Bars B = Binary + # C = Celsius D = Degrees H = Hertz I = liters/second + # K = Kelvin K = kg/m3 M = Meters M = cubic Meters + # N = Newton P = % of full range P = Pascal R = RPM + # S = Parts per thousand V = Volts "dataunit": CH, "tdrid": ST, }, diff --git a/tests/pygpsdata-maritime.log b/tests/pygpsdata-maritime.log index 7ca42e2..420892c 100644 --- a/tests/pygpsdata-maritime.log +++ b/tests/pygpsdata-maritime.log @@ -12,3 +12,4 @@ $CSSMB,008,001,0,123456,FROM:Maritime Rescue Coordination Centre xxx^0D)0ATO:*48 $IISPW,EPV,211000001,2,SESAME*1A $IIEPV,C,AI,211000001,111,HEUREKA143*55 $AIEPV,R,AI,211000001,111,HEUREKA143*4C +$HCXDR,A,171,D,PITCH,A,-37,D,ROLL,G,367,,MAGX,G,2420,,MAGY,G,-8984,,MAGZ*41 diff --git a/tests/test_static.py b/tests/test_static.py index 8c95d42..5781372 100644 --- a/tests/test_static.py +++ b/tests/test_static.py @@ -22,7 +22,12 @@ NMEA_PAYLOADS_GET, NMEA_PAYLOADS_POLL, NMEA_PAYLOADS_SET, - GPSEPOCH0, + EPOCH0_GPS, + EPOCH0_BEIDOU, + BDS, + GPS, + GAL, + IRN, ) from pynmeagps.nmeahelpers import ( area, @@ -38,7 +43,6 @@ ecef2llh, hex2str, generate_checksum, - get_gpswnotow, get_parts, groupsize, haversine, @@ -585,27 +589,6 @@ def testarea(self): res = area(-12.645, 34.867, -34.1745, 48.27846) self.assertAlmostEqual(res, 3264291.8230, 4) - def testgpsweek(self): - dats = [ - (2023, 1, 1), - (2005, 11, 5), - (2020, 8, 20), - (2014, 3, 16), - (2023, 5, 21), - (2023, 5, 27), - ] - vals = [ - (2243, 18), - (1347, 518413), - (2119, 345618), - (1784, 16), - (2263, 18), - (2263, 518418), - ] - for i, dat in enumerate(dats): - y, m, d = dat - self.assertEqual(get_gpswnotow(datetime(y, m, d)), vals[i]) - def testhex2str(self): hex = 0x1234ABCD self.assertEqual(hex2str(hex, 8), "1234ABCD") @@ -622,15 +605,6 @@ def testdecodes(self): self.assertEqual(SIGNALID[("1", "5")], "GPS L2 CM") self.assertEqual(SYSTEMID["4"], "Beidou") - def testleapsecond(self): - self.assertEqual(leapsecond(GPSEPOCH0), 0) - self.assertEqual(leapsecond(datetime(1985, 1, 1, 0, 0, 0)), 3) - self.assertEqual(leapsecond(datetime(1997, 8, 1, 0, 0, 0)), 12) - self.assertEqual(leapsecond(datetime(2025, 9, 18, 16, 51, 34)), 18) - self.assertEqual(leapsecond(datetime(2025, 9, 18, 16, 51, 34, tzinfo=timezone.utc)), 18) - self.assertEqual(leapsecond(datetime(1974, 9, 18, 16, 51, 34)), -6) - self.assertEqual(leapsecond(datetime(1971, 9, 18, 16, 51, 34)), 0) - def testmaxidx(self): PYLD1 = {"svid_01": 7, "svid_02": 8, "elv_03": 15, "svid_04": 23} self.assertEqual(groupsize(**PYLD1), 4) @@ -641,26 +615,91 @@ def testmaxidx(self): PYLD4 = {} self.assertEqual(groupsize(**PYLD4), 0) - def testutc2wnotow(self): - dat = datetime(2026, 2, 20, 23, 21, 36, 123000, tzinfo=timezone.utc) - wno, tow, ls = utc2wnotow(dat) - # print(wno, tow, ls) - self.assertEqual((wno, tow), (2406, 516114123)) - dat = datetime(2026, 2, 20, 23, 21, 36, 123000) - wno, tow, ls = utc2wnotow(dat) - # print(wno, tow, ls) - self.assertEqual((wno, tow), (2406, 516114123)) - wno, tow, ls = utc2wnotow() - # print(wno, tow, ls) - self.assertIsInstance(wno, int) - self.assertIsInstance(tow, int) - self.assertIsInstance(ls, int) - - def testwnotow2utc(self): - utc = wnotow2utc(2406,516114123,18) - self.assertEqual(utc, datetime(2026, 2, 20, 23, 21, 36, 123000, tzinfo=timezone.utc)) - utc = wnotow2utc(2406,516114000) - self.assertEqual((utc.year, utc.month, utc.day, utc.hour), (2026, 2, 20, 23)) + # def testutc2wnotow(self): + # dat = datetime(2026, 2, 20, 23, 21, 36, 123000, tzinfo=timezone.utc) + # wno, tow, ls = utc2wnotow(dat) + # # print(wno, tow, ls) + # self.assertEqual((wno, tow), (358, 516114123)) + # dat = datetime(2026, 2, 20, 23, 21, 36, 123000) + # wno, tow, ls = utc2wnotow(dat) + # # print(wno, tow, ls) + # self.assertEqual((wno, tow), (2406, 516114123)) + # wno, tow, ls = utc2wnotow() + # # print(wno, tow, ls) + # self.assertIsInstance(wno, int) + # self.assertIsInstance(tow, int) + # self.assertIsInstance(ls, int) + + def testwnotow2utcGPS(self): + res = wnotow2utc(2406, 516114123, None) + # print(res) + self.assertEqual( + res, datetime(1986, 11, 21, 23, 21, 50, 123000, tzinfo=timezone.utc) + ) + utc = wnotow2utc(2406, 516114000) + self.assertEqual( + (utc.year, utc.month, utc.day, utc.hour, utc.minute, utc.second), + (1986, 11, 21, 23, 21, 50), + ) + res = wnotow2utc(366, 381600000, None, GPS, False) + # print(res) + self.assertEqual(str(res), "1987-01-15 09:59:56+00:00") + res = wnotow2utc(1390, 381600000, None, GPS, False) + # print(res) + self.assertEqual(str(res), "1987-01-15 09:59:56+00:00") + + def testwnotow2utcBDS(self): + res = wnotow2utc(1390, 381600000, None, BDS, False) + # print(res) + self.assertEqual(str(res), "2032-08-26 09:59:56+00:00") + + def testwnotow2utcGAL(self): + res = wnotow2utc(1390, 381600000, None, GAL, False) + # print(res) + self.assertEqual(str(res), "2026-04-16 09:59:42+00:00") + + def testwnotow2utcIRN(self): + res = wnotow2utc(1390, 381600000, None, IRN, False) + # print(res) + self.assertEqual(str(res), "2006-08-31 09:59:46+00:00") + + def testleapsecondGPS(self): + self.assertEqual(leapsecond(EPOCH0_GPS, "G"), 0) + self.assertEqual(leapsecond(datetime(1985, 1, 1, 0, 0, 0)), 3) + self.assertEqual(leapsecond(datetime(1997, 8, 1, 0, 0, 0)), 12) + self.assertEqual(leapsecond(datetime(2025, 9, 18, 16, 51, 34)), 18) + self.assertEqual( + leapsecond(datetime(2025, 9, 18, 16, 51, 34, tzinfo=timezone.utc)), 18 + ) + self.assertEqual(leapsecond(datetime(1974, 9, 18, 16, 51, 34)), -6) + self.assertEqual(leapsecond(datetime(1971, 9, 18, 16, 51, 34)), 0) + + def testleapsecondBDS(self): + self.assertEqual(leapsecond(EPOCH0_BEIDOU, "C"), 0) + self.assertEqual(leapsecond(datetime(2025, 1, 1, 16, 51, 34), BDS), 4) + + def testleapsecondGAL(self): + self.assertEqual(leapsecond(datetime(2025, 1, 1, 16, 51, 34), GAL), 18) + + def testleapsecondIRN(self): + self.assertEqual(leapsecond(datetime(2025, 1, 1, 16, 51, 34), IRN), 18) + + def testtimeconv(self): + + for gnss in ("G", "E", "C", "I"): + for wno, tow in ( + (203, 162864000), + (1023, 472651000), + (366, 111222000), + (986, 1945000), + (666, 162864000), + (888, 472651000), + (420, 111222000), + (54, 1945000), + ): + utc = wnotow2utc(wno, tow, None, gnss) + wno2, tow2, ls = utc2wnotow(utc, gnss) + self.assertEqual((wno, tow), (wno2, tow2)) if __name__ == "__main__": diff --git a/tests/test_stream.py b/tests/test_stream.py index b84066b..3b8455c 100644 --- a/tests/test_stream.py +++ b/tests/test_stream.py @@ -258,6 +258,7 @@ def testNMEAMARITIME(self): # test NMEA maritime messages "", "", "", + "", ) i = 0 @@ -269,7 +270,7 @@ def testNMEAMARITIME(self): # test NMEA maritime messages # print(f'"{parsed}",') self.assertEqual(str(parsed), EXPECTED_RESULTS[i]) i += 1 - self.assertEqual(i, 14) + self.assertEqual(i, len(EXPECTED_RESULTS)) def testNMEAITER(self): # NMEAReader iterator EXPECTED_RESULTS = (