summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKevin McCarthy <kmccarth@redhat.com>2007-09-04 13:44:59 -0700
committerKevin McCarthy <kmccarth@redhat.com>2007-09-04 13:44:59 -0700
commit3afd023c3aad0ade7ce391ba18dcd2c9c8e59426 (patch)
tree2f1b6a646e33f496d81aa80131d6b5ed44814055
parent584baa7ee21f22db6978efc89de1f1491768fab5 (diff)
downloadfreeipa-3afd023c3aad0ade7ce391ba18dcd2c9c8e59426.tar.gz
freeipa-3afd023c3aad0ade7ce391ba18dcd2c9c8e59426.tar.xz
freeipa-3afd023c3aad0ade7ce391ba18dcd2c9c8e59426.zip
Generalized Time parser and tests, for use in krbPasswordExpiration
-rw-r--r--ipa-python/ipautil.py98
-rw-r--r--ipa-python/test/test_ipautil.py97
2 files changed, 195 insertions, 0 deletions
diff --git a/ipa-python/ipautil.py b/ipa-python/ipautil.py
index f6d62f7a3..2989b4211 100644
--- a/ipa-python/ipautil.py
+++ b/ipa-python/ipautil.py
@@ -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
diff --git a/ipa-python/test/test_ipautil.py b/ipa-python/test/test_ipautil.py
index 54ff1dc26..2755f71ea 100644
--- a/ipa-python/test/test_ipautil.py
+++ b/ipa-python/test/test_ipautil.py
@@ -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()