Generalized Time parser and tests, for use in krbPasswordExpiration

This commit is contained in:
Kevin McCarthy 2007-09-04 13:44:59 -07:00
parent 584baa7ee2
commit 3afd023c3a
2 changed files with 195 additions and 0 deletions

View File

@ -30,6 +30,7 @@ import stat
from string import lower
import re
import xmlrpclib
import datetime
def realm_to_suffix(realm_name):
s = realm_name.split(".")
@ -233,3 +234,100 @@ def unwrap_binary_data(data):
else:
return data
class GeneralizedTimeZone(datetime.tzinfo):
"""This class is a basic timezone wrapper for the offset specified
in a Generalized Time. It is dst-ignorant."""
def __init__(self,offsetstr="Z"):
super(GeneralizedTimeZone, self).__init__()
self.name = offsetstr
self.houroffset = 0
self.minoffset = 0
if offsetstr == "Z":
self.houroffset = 0
self.minoffset = 0
else:
if (len(offsetstr) >= 3) and re.match(r'[-+]\d\d', offsetstr):
self.houroffset = int(offsetstr[0:3])
offsetstr = offsetstr[3:]
if (len(offsetstr) >= 2) and re.match(r'\d\d', offsetstr):
self.minoffset = int(offsetstr[0:2])
offsetstr = offsetstr[2:]
if len(offsetstr) > 0:
raise ValueError()
if self.houroffset < 0:
self.minoffset *= -1
def utcoffset(self, dt):
return datetime.timedelta(hours=self.houroffset, minutes=self.minoffset)
def dst(self, dt):
return datetime.timedelta(0)
def tzname(self, dt):
return self.name
def parse_generalized_time(timestr):
"""Parses are Generalized Time string (as specified in X.680),
returning a datetime object. Generalized Times are stored inside
the krbPasswordExpiration attribute in LDAP.
This method doesn't attempt to be perfect wrt timezones. If python
can't be bothered to implement them, how can we..."""
if len(timestr) < 8:
return None
try:
date = timestr[:8]
time = timestr[8:]
year = int(date[:4])
month = int(date[4:6])
day = int(date[6:8])
hour = min = sec = msec = 0
tzone = None
if (len(time) >= 2) and re.match(r'\d', time[0]):
hour = int(time[:2])
time = time[2:]
if len(time) >= 2 and (time[0] == "," or time[0] == "."):
hour_fraction = "."
time = time[1:]
while (len(time) > 0) and re.match(r'\d', time[0]):
hour_fraction += time[0]
time = time[1:]
total_secs = int(float(hour_fraction) * 3600)
min, sec = divmod(total_secs, 60)
if (len(time) >= 2) and re.match(r'\d', time[0]):
min = int(time[:2])
time = time[2:]
if len(time) >= 2 and (time[0] == "," or time[0] == "."):
min_fraction = "."
time = time[1:]
while (len(time) > 0) and re.match(r'\d', time[0]):
min_fraction += time[0]
time = time[1:]
sec = int(float(min_fraction) * 60)
if (len(time) >= 2) and re.match(r'\d', time[0]):
sec = int(time[:2])
time = time[2:]
if len(time) >= 2 and (time[0] == "," or time[0] == "."):
sec_fraction = "."
time = time[1:]
while (len(time) > 0) and re.match(r'\d', time[0]):
sec_fraction += time[0]
time = time[1:]
msec = int(float(sec_fraction) * 1000000)
if (len(time) > 0):
tzone = GeneralizedTimeZone(time)
return datetime.datetime(year, month, day, hour, min, sec, msec, tzone)
except ValueError:
return None

View File

@ -21,6 +21,7 @@ import sys
sys.path.insert(0, ".")
import unittest
import datetime
import ipautil
@ -207,6 +208,102 @@ class TestCIDict(unittest.TestCase):
self.assert_(item in items)
items.discard(item)
class TestTimeParser(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
pass
def testSimple(self):
timestr = "20070803"
time = ipautil.parse_generalized_time(timestr)
self.assertEqual(2007, time.year)
self.assertEqual(8, time.month)
self.assertEqual(3, time.day)
self.assertEqual(0, time.hour)
self.assertEqual(0, time.minute)
self.assertEqual(0, time.second)
def testHourMinSec(self):
timestr = "20051213141205"
time = ipautil.parse_generalized_time(timestr)
self.assertEqual(2005, time.year)
self.assertEqual(12, time.month)
self.assertEqual(13, time.day)
self.assertEqual(14, time.hour)
self.assertEqual(12, time.minute)
self.assertEqual(5, time.second)
def testFractions(self):
timestr = "2003092208.5"
time = ipautil.parse_generalized_time(timestr)
self.assertEqual(2003, time.year)
self.assertEqual(9, time.month)
self.assertEqual(22, time.day)
self.assertEqual(8, time.hour)
self.assertEqual(30, time.minute)
self.assertEqual(0, time.second)
timestr = "199203301544,25"
time = ipautil.parse_generalized_time(timestr)
self.assertEqual(1992, time.year)
self.assertEqual(3, time.month)
self.assertEqual(30, time.day)
self.assertEqual(15, time.hour)
self.assertEqual(44, time.minute)
self.assertEqual(15, time.second)
timestr = "20060401185912,8"
time = ipautil.parse_generalized_time(timestr)
self.assertEqual(2006, time.year)
self.assertEqual(4, time.month)
self.assertEqual(1, time.day)
self.assertEqual(18, time.hour)
self.assertEqual(59, time.minute)
self.assertEqual(12, time.second)
self.assertEqual(800000, time.microsecond)
def testTimeZones(self):
timestr = "20051213141205Z"
time = ipautil.parse_generalized_time(timestr)
self.assertEqual(0, time.tzinfo.houroffset)
self.assertEqual(0, time.tzinfo.minoffset)
offset = time.tzinfo.utcoffset(None)
self.assertEqual(0, offset.seconds)
timestr = "20051213141205+0500"
time = ipautil.parse_generalized_time(timestr)
self.assertEqual(5, time.tzinfo.houroffset)
self.assertEqual(0, time.tzinfo.minoffset)
offset = time.tzinfo.utcoffset(None)
self.assertEqual(5 * 60 * 60, offset.seconds)
timestr = "20051213141205-0500"
time = ipautil.parse_generalized_time(timestr)
self.assertEqual(-5, time.tzinfo.houroffset)
self.assertEqual(0, time.tzinfo.minoffset)
# NOTE - the offset is always positive - it's minutes
# _east_ of UTC
offset = time.tzinfo.utcoffset(None)
self.assertEqual((24 - 5) * 60 * 60, offset.seconds)
timestr = "20051213141205-0930"
time = ipautil.parse_generalized_time(timestr)
self.assertEqual(-9, time.tzinfo.houroffset)
self.assertEqual(-30, time.tzinfo.minoffset)
offset = time.tzinfo.utcoffset(None)
self.assertEqual(((24 - 9) * 60 * 60) - (30 * 60), offset.seconds)
if __name__ == '__main__':
unittest.main()