[Freeipa-devel] [PATCH] Generalized Time parser and tests

Kevin McCarthy kmccarth at redhat.com
Tue Sep 4 20:40:48 UTC 2007


Attached is a Generalized Time parser, for use in the
krbPaswordExpiration field.  Pete mentioned that this may be better done
on the DS, but I was almost done with the code, so here it is anyway.

This patch depends on Simo's patch just sent to the list:
freeipa-150-ipautil.patch

-Kevin

-------------- next part --------------
# HG changeset patch
# User Kevin McCarthy <kmccarth at redhat.com>
# Date 1188938699 25200
# Node ID 354f0a246e344b7404e00c0ed4402329c0bb4edc
# Parent  046b581f32f76c3888da4910bb04a7f6f9475e6d
Generalized Time parser and tests, for use in krbPasswordExpiration

diff -r 046b581f32f7 -r 354f0a246e34 ipa-python/ipautil.py
--- a/ipa-python/ipautil.py	Tue Sep 04 16:13:15 2007 -0400
+++ b/ipa-python/ipautil.py	Tue Sep 04 13:44:59 2007 -0700
@@ -30,6 +30,7 @@ from string import lower
 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 -r 046b581f32f7 -r 354f0a246e34 ipa-python/test/test_ipautil.py
--- a/ipa-python/test/test_ipautil.py	Tue Sep 04 16:13:15 2007 -0400
+++ b/ipa-python/test/test_ipautil.py	Tue Sep 04 13:44:59 2007 -0700
@@ -21,6 +21,7 @@ sys.path.insert(0, ".")
 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()
-------------- next part --------------
A non-text attachment was scrubbed...
Name: smime.p7s
Type: application/x-pkcs7-signature
Size: 2228 bytes
Desc: not available
URL: <http://listman.redhat.com/archives/freeipa-devel/attachments/20070904/ad5e4391/attachment.bin>


More information about the Freeipa-devel mailing list