Skip to content

Instantly share code, notes, and snippets.

@apg
Last active August 28, 2024 17:55
Show Gist options
  • Save apg/bc793d42f38d73a7d84ffa0fc309419f to your computer and use it in GitHub Desktop.
Save apg/bc793d42f38d73a7d84ffa0fc309419f to your computer and use it in GitHub Desktop.
"""
This is a reference implementation for the proposed datetime RFC
for the Cedar Policy Language. The RFC is currently being discussed
at https://github.com/cedar-policy/rfcs/pull/80
>>> duration("1h")
3600000ms
>>> datetime("1970-01-01T00:00:00Z")
0ms
>>> datetime("1969-12-31T23:59:59Z")
-1000ms
>>> datetime("1970-01-01T00:00:00Z").offset(duration("1m"))
60000ms
>>> datetime("1970-01-01T00:00:00Z").offset(duration("-1m"))
-60000ms
>>> datetime("1980-01-01T00:00:00Z") > datetime("1970-01-01T00:00:00Z")
True
>>> datetime("1980-01-01T00:00:00Z") >= datetime("1970-01-01T00:00:00Z")
True
>>> datetime("1970-01-01T00:00:00Z") < datetime("1980-01-01T00:00:00Z")
True
>>> datetime("1969-12-31T23:59:59Z") < datetime("1970-01-01T00:00:00Z")
True
>>> datetime("1969-12-31T23:59:58Z") < datetime("1969-12-31T23:59:59Z")
True
>>> datetime("1970-01-01T00:00:01Z") < datetime("1969-12-31T23:59:59Z")
False
>>> datetime("1970-01-01T00:00:00Z") <= datetime("1980-01-01T00:00:00Z")
True
>>> datetime("1970-01-01T00:00:00Z") == datetime("1970-01-01T00:00:00Z")
True
>>> datetime("1970-01-01T00:00:00Z") != datetime("1980-01-01T00:00:00Z")
True
>>> datetime("1970-01-01T12:00:00Z").toDate() == datetime("1970-01-01T00:00:00Z")
True
>>> datetime("1970-01-01T12:00:00Z").toTime() == duration("12h")
True
>>> datetime("1970-01-01T00:00:00Z").durationSince(datetime("1970-01-01T00:00:00Z"))
0ms
>>> datetime("1970-01-01T00:00:00Z").durationSince(datetime("1970-01-01T00:00:01Z"))
-1000ms
>>> datetime("1969-12-31T23:59:59Z").durationSince(datetime("1970-01-01T00:00:00Z"))
-1000ms
>>> datetime("1969-12-31T23:59:58Z").durationSince(datetime("1969-12-31T23:59:59Z"))
-1000ms
>>> datetime("1970-01-01T00:00:01Z").durationSince(datetime("1969-12-31T23:59:59Z"))
2000ms
>>> datetime("1970-01-01T00:00:01Z").offset(datetime("1969-12-31T23:59:58Z").durationSince(datetime("1970-01-01T00:00:01Z"))) == datetime("1969-12-31T23:59:58Z")
True
>>> datetime("1970-01-01T00:00:01Z").offset(datetime("1970-01-01T00:00:03Z").durationSince(datetime("1970-01-01T00:00:01Z"))) == datetime("1970-01-01Z00:00:03Z")
True
>>> duration("1d") == duration("86400000ms")
True
>>> duration("1d") != duration("1d1ms")
True
>>> duration("2d") > duration("86400000ms")
True
>>> duration("2d") >= duration("86400000ms")
True
>>> duration("1d") < duration("2d")
True
>>> duration("1d") <= duration("2d")
True
>>> duration("1d").toMilliseconds()
86400000
>>> duration("1d").toSeconds()
86400
>>> duration("1d").toMinutes()
1440
>>> duration("1d").toHours()
24
>>> duration("1d").toDays()
1
"""
import datetime as rdt
def parseDuration(s):
"""
Nu, where N is positive or negative integer, and u is one of:
d, h, m, s, ms
These are combined into a single string, and returned as milliseconds
"""
totalMS = 0
quantity = 0
negative = False
unit = ""
i = 0
while i < len(s):
if s[i] == '-' and quantity == 0 and not negative:
negative = True
i += 1
elif s[i].isdigit():
quantity = (quantity * 10) + int(s[i])
i += 1
elif s[i] in "dhms":
if s[i] == "m" and i+1 < len(s) and s[i+1] == "s":
unit = "ms"
i += 2
else:
unit = s[i]
i += 1
negate = -1 if negative else 1
if unit == "ms":
totalMS = totalMS + (quantity * negate)
elif unit == "s":
totalMS = totalMS + (quantity * 1000 * negate)
elif unit == "m":
totalMS = totalMS + (quantity * 60 * 1000 * negate)
elif unit == "h":
totalMS = totalMS + (quantity * 60 * 60 * 1000 * negate)
elif unit == "d":
totalMS = totalMS + (quantity * 24 * 60 * 60 * 1000 * negate)
# reset
quantity = 0
negative = False
unit = ""
else:
raise TypeError("not a valid duration string")
return totalMS
class duration:
def __init__(self, s):
self.ms = parseDuration(s)
def __gt__(self, other):
return self.ms > other.ms
def __ge__(self, other):
return self.ms >= other.ms
def __lt__(self, other):
return self.ms < other.ms
def __le__(self, other):
return self.ms <= other.ms
def __eq__(self, other):
return self.ms == other.ms
def __ne__(self, other):
return self.ms != other.ms
def toMilliseconds(self):
return self.ms
def toSeconds(self):
return self.ms // 1000
def toMinutes(self):
return self.ms // (1000 * 60)
def toHours(self):
return self.ms // (1000 * 60 * 60)
def toDays(self):
return self.ms // (1000 * 60 * 60 * 24)
def __repr__(self):
return str(self.ms) + "ms"
class datetime:
def __init__(self, s):
self.ts = int(rdt.datetime.fromisoformat(s).timestamp() * 1000)
def __gt__(self, other):
return self.ts > other.ts
def __ge__(self, other):
return self.ts >= other.ts
def __lt__(self, other):
return self.ts < other.ts
def __le__(self, other):
return self.ts <= other.ts
def __eq__(self, other):
return self.ts == other.ts
def __ne__(self, other):
return self.ts != other.ts
def durationSince(self, other):
d = duration("0ms")
d.ms = self.ts - other.ts
return d
def offset(self, dur):
d = zerodate()
d.ts = self.ts + dur.ms
return d
def toDate(self):
d = zerodate()
d.ts = self.ts - (self.ts % 86400000)
return d
def toTime(self):
return self.durationSince(self.toDate())
def __repr__(self):
return str(self.ts) + "ms"
def zerodate():
return datetime("1970-01-01T00:00:00Z")
if __name__ == "__main__":
import doctest
doctest.testmod()
zero = datetime("1970-01-01T00:00:00Z")
plus3 = datetime("1970-01-01T00:00:03Z")
plus5 = datetime("1970-01-01T00:00:05Z")
minus3 = datetime("1969-12-31T23:59:57Z")
minus5 = datetime("1969-12-31T23:59:55Z")
xs = [zero, plus3, plus5, minus3, minus5]
ys = [zero, plus3, plus5, minus3, minus5]
for x in xs:
for y in ys:
dur = x.durationSince(y)
print(x, y, "durationSince:", dur, y.offset(dur) == x)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment