Skip to content
19 changes: 14 additions & 5 deletions src/snapred/meta/Time.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import time
import warnings

import numpy as np

Expand All @@ -18,8 +19,13 @@ def timestamp(ensureUnique: bool = False) -> float:

def parseTimestamp(ts: float | str | int) -> float:
if isinstance(ts, str):
# Convert ISO format string to timestamp
return np.datetime64(ts).astype(int) / 1e9 # convert to seconds
# Convert string to timestamp -- the strings into this method are not always ISO format
# NOTE np.datetime64 throws an obnoxious warning if there is a timezone offset
# the warning is purely a nuissance and can be ignored
# note there is an alternative solutiob using the python-dateutil library to handle strings
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=UserWarning)
return float(np.datetime64(ts).astype(int) / 1e9) # convert to seconds
if isinstance(ts, int):
# DEPRECIATED: support legacy integer encoding
return float(ts) / 1000.0
Expand All @@ -31,6 +37,9 @@ def parseTimestamp(ts: float | str | int) -> float:
def isoFromTimestamp(ts: float) -> str:
# Convert float timestamp (seconds) to integer nanoseconds
ts_ns = int(ts * 1e9)
npDatetime = np.datetime64(ts_ns, "ns")
iso = np.datetime_as_string(npDatetime, timezone="local", unit="ns")
return iso
# NOTE np.datetime64 throws an obnoxious warning if there is a timezone offset
# it likely does not occur in this method, but it is safe to ignore it just in case
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=UserWarning)
npDatetime = np.datetime64(ts_ns, "ns")
return str(np.datetime_as_string(npDatetime, timezone="local", unit="ns"))
45 changes: 45 additions & 0 deletions tests/unit/meta/test_Time.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import time
from unittest import TestCase

import pytest

from snapred.meta.Time import isoFromTimestamp, parseTimestamp, timestamp


class TestTime(TestCase):
def test_timestamp(self):
t1 = timestamp()
iso = isoFromTimestamp(t1)
t2 = parseTimestamp(iso)
assert t1 == t2

def test_timestamp_order(self):
t1 = timestamp()
t2 = timestamp()
assert t2 >= t1

def test_timestamp_ensureUnique(self):
t1 = timestamp(ensureUnique=True)
t2 = timestamp(ensureUnique=True)
assert t2 > t1

def test_parseTimestamp(self):
ts = "2024-06-01T03:34:56.789011968-0400"
expected = 1717227296.789012
assert parseTimestamp(ts) == expected

def test_parseTimestamp_error(self):
with pytest.raises(ValueError, match="Timestamp must be a float, int, or ISO format string"):
parseTimestamp(None)
with pytest.raises(ValueError, match="Error parsing datetime string"):
parseTimestamp("invalid timestamp")
obj = {"x": 2}
with pytest.raises(ValueError, match="Timestamp must be a float, int, or ISO format string"):
parseTimestamp(obj)

def test_isoFromTimestamp(self):
ts = 1717227296.789012
localTimeZone = time.strftime("%z", time.localtime())
offsetHours = int(localTimeZone[:3])
expected = f"2024-06-01T{(7 + offsetHours):02d}:34:56.789011968" + localTimeZone
assert isoFromTimestamp(ts) == expected
Loading