Commit aa8cbbc5 authored by M. Huang's avatar M. Huang
Browse files

leap seconds, FineTime toString and implementation improvements

parent 4f2ace2e
PYEXE = python3
info:
$(PYEXE) -c "import sys; print('sys.hash_info.width', sys.hash_info.width)"
$(PYEXE) -c "import sys, time; print('sys.hash_info.width', sys.hash_info.width, 'epoch', time.gmtime(0))"
PRODUCT = Product
B_PRODUCT = BaseProduct
......
......@@ -15,27 +15,6 @@ logger = logging.getLogger(__name__)
# logger.debug('level %d' % (logger.getEffectiveLevel()))
# A UTC class.
class UTC(datetime.tzinfo):
"""UTC
https://docs.python.org/2.7/library/datetime.html?highlight=datetime#datetime.tzinfo
"""
ZERO = datetime.timedelta(0)
HOUR = datetime.timedelta(hours=1)
def utcoffset(self, dt):
return UTC.ZERO
def tzname(self, dt):
return "UTC"
def dst(self, dt):
return UTC.ZERO
utcobj = datetime.timezone.utc
......@@ -56,6 +35,9 @@ class FineTime(Copyable, DeepEqual, Serializable):
""" number of TAI units in a second """
RESOLUTION = 1000000 # microsecond
""" """
UTC_LOW_LIMIT = EPOCH # datetime.datetime(1972, 1, 1, 0, 0, 0, tzinfo=utcobj)
""" """
DEFAULT_FORMAT = '%Y-%m-%dT%H:%M:%S.%f' # ISO
......@@ -66,12 +48,11 @@ class FineTime(Copyable, DeepEqual, Serializable):
def __init__(self, date=None, format=None, **kwds):
""" Initiate with a UTC date or an integer TAI"""
leapsec = 0 # leap seconds to be implemented
self.format = self.DEFAULT_FORMAT if format is None else format
self.format = FineTime.DEFAULT_FORMAT if format is None else format
self.setTime(date)
# logger.debug('date= %s TAI = %d' % (str(date), self.tai))
super(FineTime, self).__init__(**kwds)
super().__init__(**kwds)
@property
def time(self):
......@@ -142,22 +123,29 @@ class FineTime(Copyable, DeepEqual, Serializable):
in microseconds. """
return self.tai - time.tai
def datetimeToFineTime(self, dtm):
""" Return given Python Datetime as a FineTime to the precision of the input. Rounded to the last digit. Unit is decided by RESOLUTION."""
leapsec = 0 # leap seconds to be implemented
return int(self.RESOLUTION *
((dtm - self.EPOCH).total_seconds() + leapsec)+0.5)
@classmethod
def datetimeToFineTime(cls, dtm):
""" Return given Python Datetime in FineTime to the precision of the input. Rounded to the last digit. Unit is decided by RESOLUTION."""
if dtm < cls.UTC_LOW_LIMIT:
raise NotImplemented(
'UTC before %s not working yet.' % str(cls.UTC_LOW_LIMIT))
leapsec = leapseconds.dTAI_UTC_from_utc(dtm)
sec = cls.RESOLUTION * ((dtm - cls.EPOCH + leapsec).total_seconds())
return int(sec+0.5)
def toDatetime(self, tai=None):
""" Return given FineTime as a Python Datetime.
tai: if not given or given as `None`, return this object's time as a Python Datetime.
"""
leapsec = 0 # leap seconds to be implemented
if tai is None:
tai = self.tai
return datetime.timedelta(seconds=(float(tai) / self.RESOLUTION - leapsec))\
+ self.EPOCH
tai_time = datetime.timedelta(seconds=(float(tai) / FineTime.RESOLUTION)) \
+ FineTime.EPOCH
# leapseconds is offset-native
leapsec = leapseconds.dTAI_UTC_from_tai(tai_time)
return tai_time - leapsec
# HCSS compatibility
toDate = toDatetime
......@@ -166,8 +154,7 @@ class FineTime(Copyable, DeepEqual, Serializable):
""" Returns a String representation of this objet in ISO format without timezone. sub-second set to TIMESPEC.
format: time format. default '%Y-%m-%dT%H:%M:%S' prints like 2019-02-17T12:43:04.577000 """
s = (self.tai + 0.0) / self.RESOLUTION
dt = datetime.timedelta(seconds=s) + self.EPOCH
dt = self.toDatetime(self.tai)
return dt.strftime(format)
def toString(self, level=0, width=0, **kwds):
......@@ -202,8 +189,65 @@ class FineTime(Copyable, DeepEqual, Serializable):
s = tais
return s
def __repr__(self):
return self.toString(level=2)
def __bool__(self):
""" for `if` etc """
return self.tai
def __int__(self):
return self.tai
__index__ = __int__
def __add__(self, obj):
""" can add an integer as a TAI directly and return a new instance."""
oc = obj.__class__
sc = self.__class__
if issubclass(oc, int):
return sc(self.tai+obj, format=self.format)
else:
raise TypeError(
f'{sc.__name__} cannot add/minus {oc.__name__} {obj}')
def __sub__(self, obj):
""" can minus an integer as a TAI directly and return a new instance,
or subtract another FineTime instance and returns TAI difference in microseconds.
"""
oc = obj.__class__
sc = self.__class__
if issubclass(oc, int):
return sc(self.tai-obj, format=self.format)
elif issubclass(oc, sc):
return self.tai - obj.tai
else:
raise TypeError(f'{sc.__name__} cannot minus {oc.__name__} {obj}')
def __iadd__(self, obj):
""" can add an integer as a TAI directly to self like ```v += 3```."""
oc = obj.__class__
sc = self.__class__
if issubclass(oc, int):
self.tai += obj
else:
raise TypeError(f'{sc.__name__} cannot add/minus {oc} {obj}')
def __isub__(self, obj):
""" can subtract an integer as a TAI directly from self like ```v -= 3```."""
oc = obj.__class__
sc = self.__class__
if issubclass(oc, int):
self.tai -= obj
else:
raise TypeError(f'{sc.__name__} cannot add/minus {oc} {obj}')
def __lt__(self, obj):
""" can compare TAI directly """
if 1:
# if type(obj).__name__ in ParameterTypes.values():
return self.tai < obj
......@@ -234,14 +278,6 @@ class FineTime(Copyable, DeepEqual, Serializable):
else:
return super(FineTime, self).__ge__(obj)
def __repr__(self):
return self.toString(level=2)
co = ', '.join(str(k)+'=' + ('"'+v+'"' if issubclass(v.__class__, str)
else str(v)) for k, v in self.__getstate__().items())
return '<'+self.__class__.__name__ + ' ' + co + '>'
# __str__ = toString
def __getstate__(self):
""" Can be encoded with serializableEncoder """
return OrderedDict(tai=self.tai,
......@@ -255,3 +291,23 @@ class FineTime1(FineTime):
RESOLUTION = 1000 # millisecond
RETURNFMT = '%s.%03d'
TIMESPEC = 'milliseconds'
TAI_AT_EPOCH = FineTime.datetimeToFineTime(EPOCH)
def __init__(self, *args, **kwds):
self.relative_res = FineTime.RESOLUTION / float(self.RESOLUTION)
super().__init__(*args, **kwds)
@classmethod
def datetimeToFineTime(cls, dtm):
sec = (FineTime.datetimeToFineTime(dtm) -
cls.TAI_AT_EPOCH) / FineTime.RESOLUTION
# for subclasses with a different epoch
return int(sec * cls.RESOLUTION + 0.5)
def toDatetime(self, tai=None):
if tai is None:
tai = self.tai
return super().toDatetime(tai * self.relative_res + self.TAI_AT_EPOCH)
......@@ -194,7 +194,7 @@ a 3.4 None float (0, 31): valid 2.0
string.
b xy (2019-02-19T01:02 finetime (0, 9876543210123456 1958-01-01T00:00: Q date param
:03.456789 ): xy 00.000099
1929229323456789) 99
1929229360456789) 99
c Invalid (IJK) string '': empty cliche B this is a string para
meter. but only "" is
allowed.
......@@ -210,7 +210,7 @@ meta= {
------------- -------------------------- ----------------
a= 3.4 b= xy (2019-02-19T01:02:03 c= Invalid (IJK)
.456789
1929229323456789)
1929229360456789)
d= off (0b00)
------------- -------------------------- ----------------
MetaData-listeners = ListnerSet{}
......@@ -246,7 +246,7 @@ a 3.4 None float (0, 31): valid 2
string.
b xy (2019-02-19T01:02 finetime (0, 9876543210123456 1958-01-01T00:00: Q date param
:03.456789 ): xy 00.000099
1929229323456789) 99
1929229360456789) 99
c Invalid (IJK) string '': empty cliche B this is a string para
meter. but only "" is
allowed.
......@@ -308,7 +308,7 @@ er AD
typecode= UNKNOWN version= 0.1 FORMATV= 1.6.0.1
a= 3.4 b= xy (2019-02-19T01:02:03 c= Invalid (IJK)
.456789
1929229323456789)
1929229360456789)
d= off (0b00) added_parameter= 42
-------------------------- -------------------------- ----------------
MetaData-listeners = ListnerSet{}
......@@ -383,7 +383,7 @@ a 3.4 None float (0, 31): valid 2
string.
b xy (2019-02-19T01:02 finetime (0, 9876543210123456 1958-01-01T00:00: Q date param
:03.456789 ): xy 00.000099
1929229323456789) 99
1929229360456789) 99
c Invalid (IJK) string '': empty cliche B this is a string para
meter. but only "" is
allowed.
......@@ -410,7 +410,7 @@ meta= {
description= UNKNOWN version= 0.1 FORMATV= 1.6.0.1
a= 3.4 b= xy (2019-02-19T01:02:03 c= Invalid (IJK)
.456789
1929229323456789)
1929229360456789)
d= off (0b00) added_parameter= 42
-------------------- -------------------------- ----------------
MetaData-listeners = ListnerSet{}
......@@ -445,7 +445,7 @@ a 3.4 None float (0, 31): valid 2.0
string.
b xy (2019-02-19T01:02 finetime (0, 9876543210123456 1958-01-01T00:00: Q date param
:03.456789 ): xy 00.000099
1929229323456789) 99
1929229360456789) 99
c Invalid (IJK) string '': empty cliche B this is a string para
meter. but only "" is
allowed.
......@@ -510,7 +510,7 @@ meta= {
------------- -------------------------- ----------------
a= 3.4 b= xy (2019-02-19T01:02:03 c= Invalid (IJK)
.456789
1929229323456789)
1929229360456789)
d= off (0b00) m1= 2.3
------------- -------------------------- ----------------
MetaData-listeners = ListnerSet{}
......@@ -554,13 +554,13 @@ CompositeDataset-datasets =
out_FineTime = """toString test
=========== format: "%Y-%m-%dT%H:%M:%S.%f" =======
FineTime
level=0 width=0: 2019-02-19T01:02:03.456789 TAI(1929229323456789) format=%Y-%m-%dT%H:%M:%S.%f
level=1 width=0: 2019-02-19T01:02:03.456789 (1929229323456789)
level=0 width=0: 2019-02-19T01:02:03.456789 TAI(1929229360456789) format=%Y-%m-%dT%H:%M:%S.%f
level=1 width=0: 2019-02-19T01:02:03.456789 (1929229360456789)
level=2 width=0: FineTime(2019-02-19T01:02:03.456789)
level=0 width=1: 2019-02-19T01:02:03.456789
1929229323456789
1929229360456789
level=1 width=1: 2019-02-19T01:02:03.456789
1929229323456789
1929229360456789
level=2 width=1: FineTime(2019-02-19
01:02:03.456789)
FineTime1
......@@ -575,13 +575,13 @@ level=2 width=1: FineTime1(2019-02-19
01:02:03.457000)
=========== format: "%Y" =======
FineTime
level=0 width=0: 2019 TAI(1929229323456789) format=%Y
level=1 width=0: 2019 (1929229323456789)
level=0 width=0: 2019 TAI(1929229360456789) format=%Y
level=1 width=0: 2019 (1929229360456789)
level=2 width=0: FineTime(2019)
level=0 width=1: 2019
1929229323456789
1929229360456789
level=1 width=1: 2019
1929229323456789
1929229360456789
level=2 width=1: FineTime(2019)
FineTime1
level=0 width=0: 2019 TAI(67309323457) format=%Y
......
......@@ -996,7 +996,7 @@ def test_DateParameter():
assert v.value == FineTime(a8)
v = DateParameter('2019-02-19T01:02:03.457')
assert v.value.tai == 1929229323457000
assert v.value.tai == 1929229360457000
v.value.format = '%Y'
print('********', v.value.isoutc(), ' ** ', v, '*******', v.toString(1))
......@@ -2121,10 +2121,50 @@ def test_FineTime():
# at TAI = 1, UTC ...
v = FineTime(1)
assert v.toDatetime().microsecond == 1
# comparison
v1 = FineTime(12345678901234)
v2 = FineTime(12345678901234)
v3 = FineTime(1234567890123)
assert id(v1) != id(v2)
assert v1 == v2
assert v1 >= v2
assert v2 <= v1
assert v3 != v2
assert v3 < v2
assert v2 > v3
assert v2 > 5
assert v3 <= v2
assert v2 >= v3
# arithemetics
v = FineTime(1234567)
assert v + 1 == FineTime(1234568)
assert v - 1 == FineTime(1234566)
assert v - FineTime(1) == 1234566
# two FineTime instances cannot add
with pytest.raises(TypeError):
assert v + FineTime(1) == FineTime(1234568)
with pytest.raises(TypeError):
1 + v
# leap seconds
v1 = FineTime(datetime.datetime(2015, 6, 30, 23, 59, 59))
#v2 = FineTime(datetime.datetime(2015, 6, 30, 23, 59, 60))
v3 = FineTime(datetime.datetime(2015, 7, 1, 0, 0, 0))
assert v3-v1 == 2000000 # leapsecond added
# assert v3-v2 == 1000000 # 59:60 != 00:00
dt0 = datetime.datetime(
2019, 2, 19, 1, 2, 3, 456789, tzinfo=timezone.utc)
posix_epoch = datetime.datetime(1970, 1, 1, 0, 0, 0, tzinfo=timezone.utc)
# unix seconds has no leap seconds
unsec = (dt0 - FineTime.EPOCH).total_seconds()
assert dt0.timestamp() == unsec - (posix_epoch - FineTime.EPOCH).total_seconds()
v = FineTime(dt0)
assert v.tai == 1929229323456789
assert unsec == 1929229323456789/1000000
assert v.tai == 1929229360456789
assert v.tai/1000000 - unsec == 37
dt = v.toDatetime()
assert int(dt.timestamp()) == int(dt0.timestamp())
# So that timezone won't show on the left below
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment