RPKI Engine 1.0
|
00001 """ 00002 Unified RPKI date/time handling, based on the standard Python datetime module. 00003 00004 Module name chosen to sidestep a nightmare of import-related errors 00005 that occur with the more obvious module names. 00006 00007 List of arithmetic methods that require result casting was derived by 00008 inspection of the datetime module, to wit: 00009 00010 >>> import datetime 00011 >>> for t in (datetime.datetime, datetime.timedelta): 00012 ... for k in t.__dict__.keys(): 00013 ... if k.startswith("__"): 00014 ... print "%s.%s()" % (t.__name__, k) 00015 00016 $Id: sundial.py 3783 2011-04-21 22:01:02Z sra $ 00017 00018 Copyright (C) 2009--2011 Internet Systems Consortium ("ISC") 00019 00020 Permission to use, copy, modify, and distribute this software for any 00021 purpose with or without fee is hereby granted, provided that the above 00022 copyright notice and this permission notice appear in all copies. 00023 00024 THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH 00025 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 00026 AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, 00027 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 00028 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 00029 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 00030 PERFORMANCE OF THIS SOFTWARE. 00031 00032 Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN") 00033 00034 Permission to use, copy, modify, and distribute this software for any 00035 purpose with or without fee is hereby granted, provided that the above 00036 copyright notice and this permission notice appear in all copies. 00037 00038 THE SOFTWARE IS PROVIDED "AS IS" AND ARIN DISCLAIMS ALL WARRANTIES WITH 00039 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 00040 AND FITNESS. IN NO EVENT SHALL ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, 00041 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 00042 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 00043 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 00044 PERFORMANCE OF THIS SOFTWARE. 00045 """ 00046 00047 import datetime as pydatetime 00048 import re 00049 00050 def now(): 00051 """ 00052 Get current timestamp. 00053 """ 00054 return datetime.utcnow() 00055 00056 class datetime(pydatetime.datetime): 00057 """ 00058 RPKI extensions to standard datetime.datetime class. All work here 00059 is in UTC, so we use naive datetime objects. 00060 """ 00061 00062 def totimestamp(self): 00063 """ 00064 Convert to seconds from epoch (like time.time()). Conversion 00065 method is a bit silly, but avoids time module timezone whackiness. 00066 """ 00067 return int(self.strftime("%s")) 00068 00069 @classmethod 00070 def fromUTCTime(cls, x): 00071 """ 00072 Convert from ASN.1 UTCTime. 00073 """ 00074 x = str(x) 00075 return cls.fromGeneralizedTime(("19" if x[0] >= "5" else "20") + x) 00076 00077 def toUTCTime(self): 00078 """ 00079 Convert to ASN.1 UTCTime. 00080 """ 00081 return self.strftime("%y%m%d%H%M%SZ") 00082 00083 @classmethod 00084 def fromGeneralizedTime(cls, x): 00085 """ 00086 Convert from ASN.1 GeneralizedTime. 00087 """ 00088 return cls.strptime(x, "%Y%m%d%H%M%SZ") 00089 00090 def toGeneralizedTime(self): 00091 """ 00092 Convert to ASN.1 GeneralizedTime. 00093 """ 00094 return self.strftime("%Y%m%d%H%M%SZ") 00095 00096 @classmethod 00097 def fromASN1tuple(cls, x): 00098 """ 00099 Convert from ASN.1 tuple representation. 00100 """ 00101 assert isinstance(x, tuple) and len(x) == 2 and x[0] in ("utcTime", "generalTime") 00102 if x[0] == "utcTime": 00103 return cls.fromUTCTime(x[1]) 00104 else: 00105 return cls.fromGeneralizedTime(x[1]) 00106 00107 ## @var PKIX_threshhold 00108 # Threshold specified in RFC 3280 for switchover from UTCTime to GeneralizedTime. 00109 00110 PKIX_threshhold = pydatetime.datetime(2050, 1, 1) 00111 00112 def toASN1tuple(self): 00113 """ 00114 Convert to ASN.1 tuple representation. 00115 """ 00116 if self < self.PKIX_threshhold: 00117 return "utcTime", self.toUTCTime() 00118 else: 00119 return "generalTime", self.toGeneralizedTime() 00120 00121 @classmethod 00122 def fromXMLtime(cls, x): 00123 """ 00124 Convert from XML time representation. 00125 """ 00126 if x is None: 00127 return None 00128 else: 00129 return cls.strptime(x, "%Y-%m-%dT%H:%M:%SZ") 00130 00131 def toXMLtime(self): 00132 """ 00133 Convert to XML time representation. 00134 """ 00135 return self.strftime("%Y-%m-%dT%H:%M:%SZ") 00136 00137 def __str__(self): 00138 return self.toXMLtime() 00139 00140 @classmethod 00141 def fromdatetime(cls, x): 00142 """ 00143 Convert a datetime.datetime object into this subclass. This is 00144 whacky due to the weird constructors for datetime. 00145 """ 00146 return cls.combine(x.date(), x.time()) 00147 00148 @classmethod 00149 def fromOpenSSL(cls, x): 00150 """ 00151 Convert from the format OpenSSL's command line tool uses into this 00152 subclass. May require rewriting if we run into locale problems. 00153 """ 00154 if x.startswith("notBefore=") or x.startswith("notAfter="): 00155 x = x.partition("=")[2] 00156 return cls.strptime(x, "%b %d %H:%M:%S %Y GMT") 00157 00158 @classmethod 00159 def from_sql(cls, x): 00160 """ 00161 Convert from SQL storage format. 00162 """ 00163 return cls.fromdatetime(x) 00164 00165 def to_sql(self): 00166 """ 00167 Convert to SQL storage format. 00168 00169 There's something whacky going on in the MySQLdb module, it throws 00170 range errors when storing a derived type into a DATETIME column. 00171 Investigate some day, but for now brute force this by copying the 00172 relevant fields into a datetime.datetime for MySQLdb's 00173 consumption. 00174 00175 """ 00176 return pydatetime.datetime(year = self.year, month = self.month, day = self.day, 00177 hour = self.hour, minute = self.minute, second = self.second, 00178 microsecond = 0, tzinfo = None) 00179 00180 def later(self, other): 00181 """ 00182 Return the later of two timestamps. 00183 """ 00184 return other if other > self else self 00185 00186 def earlier(self, other): 00187 """ 00188 Return the earlier of two timestamps. 00189 """ 00190 return other if other < self else self 00191 00192 def __add__(self, y): return _cast(pydatetime.datetime.__add__(self, y)) 00193 def __radd__(self, y): return _cast(pydatetime.datetime.__radd__(self, y)) 00194 def __rsub__(self, y): return _cast(pydatetime.datetime.__rsub__(self, y)) 00195 def __sub__(self, y): return _cast(pydatetime.datetime.__sub__(self, y)) 00196 00197 class timedelta(pydatetime.timedelta): 00198 """ 00199 Timedelta with text parsing. This accepts two input formats: 00200 00201 - A simple integer, indicating a number of seconds. 00202 00203 - A string of the form "uY vW wD xH yM zS" where u, v, w, x, y, and z 00204 are integers and Y, W, D, H, M, and S indicate years, weeks, days, 00205 hours, minutes, and seconds. All of the fields are optional, but 00206 at least one must be specified. Eg,"3D4H" means "three days plus 00207 four hours". 00208 00209 There is no "months" format, because the definition of a month is too 00210 fuzzy to be useful (what day is six months from August 30th?) 00211 00212 Similarly, the "years" conversion may produce surprising results, as 00213 "one year" in conventional English does not refer to a fixed interval 00214 but rather a fixed (and in some cases undefined) offset within the 00215 Gregorian calendar (what day is one year from February 29th?) 1Y as 00216 implemented by this code refers to a specific number of seconds. 00217 If you mean 365 days or 52 weeks, say that instead. 00218 """ 00219 00220 ## @var regexp 00221 # Hideously ugly regular expression to parse the complex text form. 00222 # Tags are intended for use with re.MatchObject.groupdict() and map 00223 # directly to the keywords expected by the timedelta constructor. 00224 00225 regexp = re.compile("\\s*".join(("^", 00226 "(?:(?P<years>\\d+)Y)?", 00227 "(?:(?P<weeks>\\d+)W)?", 00228 "(?:(?P<days>\\d+)D)?", 00229 "(?:(?P<hours>\\d+)H)?", 00230 "(?:(?P<minutes>\\d+)M)?", 00231 "(?:(?P<seconds>\\d+)S)?", 00232 "$")), 00233 re.I) 00234 00235 ## @var years_to_seconds 00236 # Conversion factor from years to seconds (value furnished by the 00237 # "units" program). 00238 00239 years_to_seconds = 31556926 00240 00241 @classmethod 00242 def parse(cls, arg): 00243 """ 00244 Parse text into a timedelta object. 00245 """ 00246 if not isinstance(arg, str): 00247 return cls(seconds = arg) 00248 elif arg.isdigit(): 00249 return cls(seconds = int(arg)) 00250 else: 00251 match = cls.regexp.match(arg) 00252 if match: 00253 #return cls(**dict((k, int(v)) for (k, v) in match.groupdict().items() if v is not None)) 00254 d = match.groupdict("0") 00255 for k, v in d.iteritems(): 00256 d[k] = int(v) 00257 d["days"] += d.pop("weeks") * 7 00258 d["seconds"] += d.pop("years") * cls.years_to_seconds 00259 return cls(**d) 00260 else: 00261 raise RuntimeError, "Couldn't parse timedelta %r" % (arg,) 00262 00263 def convert_to_seconds(self): 00264 """ 00265 Convert a timedelta interval to seconds. 00266 """ 00267 return self.days * 24 * 60 * 60 + self.seconds 00268 00269 @classmethod 00270 def fromtimedelta(cls, x): 00271 """ 00272 Convert a datetime.timedelta object into this subclass. 00273 """ 00274 return cls(days = x.days, seconds = x.seconds, microseconds = x.microseconds) 00275 00276 def __abs__(self): return _cast(pydatetime.timedelta.__abs__(self)) 00277 def __add__(self, x): return _cast(pydatetime.timedelta.__add__(self, x)) 00278 def __div__(self, x): return _cast(pydatetime.timedelta.__div__(self, x)) 00279 def __floordiv__(self, x): return _cast(pydatetime.timedelta.__floordiv__(self, x)) 00280 def __mul__(self, x): return _cast(pydatetime.timedelta.__mul__(self, x)) 00281 def __neg__(self): return _cast(pydatetime.timedelta.__neg__(self)) 00282 def __pos__(self): return _cast(pydatetime.timedelta.__pos__(self)) 00283 def __radd__(self, x): return _cast(pydatetime.timedelta.__radd__(self, x)) 00284 def __rdiv__(self, x): return _cast(pydatetime.timedelta.__rdiv__(self, x)) 00285 def __rfloordiv__(self, x): return _cast(pydatetime.timedelta.__rfloordiv__(self, x)) 00286 def __rmul__(self, x): return _cast(pydatetime.timedelta.__rmul__(self, x)) 00287 def __rsub__(self, x): return _cast(pydatetime.timedelta.__rsub__(self, x)) 00288 def __sub__(self, x): return _cast(pydatetime.timedelta.__sub__(self, x)) 00289 00290 def _cast(x): 00291 """ 00292 Cast result of arithmetic operations back into correct subtype. 00293 """ 00294 if isinstance(x, pydatetime.datetime): 00295 return datetime.fromdatetime(x) 00296 if isinstance(x, pydatetime.timedelta): 00297 return timedelta.fromtimedelta(x) 00298 return x 00299 00300 if __name__ == "__main__": 00301 00302 def test(t): 00303 print 00304 print "str: ", t 00305 print "repr: ", repr(t) 00306 print "seconds since epoch:", t.strftime("%s") 00307 print "UTCTime: ", t.toUTCTime() 00308 print "GeneralizedTime: ", t.toGeneralizedTime() 00309 print "ASN1tuple: ", t.toASN1tuple() 00310 print "XMLtime: ", t.toXMLtime() 00311 print 00312 00313 print 00314 print "Testing time conversion routines" 00315 test(now()) 00316 test(now() + timedelta(days = 30)) 00317 test(now() + timedelta.parse("3d5s")) 00318 test(now() + timedelta.parse(" 3d 5s ")) 00319 test(now() + timedelta.parse("1y3d5h"))