summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarko Kreen2009-12-11 14:57:17 +0000
committerMarko Kreen2009-12-11 14:57:17 +0000
commit1ff746f96d97f581a0bf6e27887cb086476223aa (patch)
tree1b567234632822c44ad9958f4dd6981cf7fec3c2
parentedf760ecbc39cb445e52b9266657eb63ff6b9319 (diff)
New python module: skytools.timeutil
ATM contains FixedOffsetTimezone class and parse_iso_timestamp() function, which are sadly missing from core Python.
-rw-r--r--python/skytools/__init__.py3
-rw-r--r--python/skytools/timeutil.py114
2 files changed, 117 insertions, 0 deletions
diff --git a/python/skytools/__init__.py b/python/skytools/__init__.py
index 03b18a04..1dd3f348 100644
--- a/python/skytools/__init__.py
+++ b/python/skytools/__init__.py
@@ -14,6 +14,7 @@ import skytools.scripting
import skytools.parsing
import skytools.dbstruct
import skytools.adminscript
+import skytools.timeutil
from skytools.psycopgwrapper import *
from skytools.config import *
@@ -24,6 +25,7 @@ from skytools.sqltools import *
from skytools.quoting import *
from skytools.parsing import *
from skytools.adminscript import *
+from skytools.timeutil import *
__all__ = (skytools.psycopgwrapper.__all__
+ skytools.config.__all__
@@ -33,6 +35,7 @@ __all__ = (skytools.psycopgwrapper.__all__
+ skytools.sqltools.__all__
+ skytools.quoting.__all__
+ skytools.adminscript.__all__
+ + skytools.timeutil.__all__
+ skytools.parsing.__all__)
import skytools.installer_config
diff --git a/python/skytools/timeutil.py b/python/skytools/timeutil.py
new file mode 100644
index 00000000..b6728bf2
--- /dev/null
+++ b/python/skytools/timeutil.py
@@ -0,0 +1,114 @@
+
+"""Module for parsing ISO 8601 format timestamps.
+
+Only fixed offset timezones are supported.
+
+http://en.wikipedia.org/wiki/ISO_8601
+"""
+
+import re
+
+from datetime import datetime, timedelta, tzinfo
+
+"""
+TODO:
+- support more combinations from ISO 8601 (only reasonable ones)
+- cache TZ objects
+- make it faster?
+"""
+
+__all__ = ['parse_iso_timestamp', 'FixedOffsetTimezone']
+
+class FixedOffsetTimezone(tzinfo):
+ """Fixed offset in minutes east from UTC."""
+ __slots__ = ('__offset', '__name')
+
+ def __init__(self, offset):
+ self.__offset = timedelta(minutes = offset)
+
+ # numeric tz name
+ h, m = divmod(abs(offset), 60)
+ if offset < 0:
+ h = -h
+ if m:
+ self.__name = "%+03d:%02d" % (h,m)
+ else:
+ self.__name = "%+03d" % h
+
+ def utcoffset(self, dt):
+ return self.__offset
+
+ def tzname(self, dt):
+ return self.__name
+
+ def dst(self, dt):
+ return ZERO
+
+ZERO = timedelta(0)
+
+_iso_regex = r"""
+ \s*
+ (?P<year> \d\d\d\d) [-] (?P<month> \d\d) [-] (?P<day> \d\d) [ T]
+ (?P<hour> \d\d) [:] (?P<min> \d\d)
+ (?: [:] (?P<sec> \d\d ) (?: [.,] (?P<ss> \d+))? )?
+ (?: \s* (?P<tzsign> [-+]) (?P<tzhr> \d\d) (?: [:]? (?P<tzmin> \d\d))? )?
+ \s* $
+ """
+_iso_rc = None
+
+def parse_iso_timestamp(s, default_tz = None):
+ """Parse ISO timestamp to datetime object.
+
+ YYYY-MM-DD[ T]HH:MM[:SS[.ss]][-+HH[:MM]]
+
+ Assumes that second fractions are zero-trimmed from the end,
+ so '.15' means 150000 microseconds.
+
+ If the timezone offset is not present, use default_tz as tzinfo.
+ By default its None, meaning the datetime object will be without tz.
+
+ >>> str(parse_iso_timestamp('2005-06-01 15:00'))
+ '2005-06-01 15:00:00'
+ >>> str(parse_iso_timestamp(' 2005-06-01T15:00 +02 '))
+ '2005-06-01 15:00:00+02:00'
+ >>> str(parse_iso_timestamp('2005-06-01 15:00:33+02:00'))
+ '2005-06-01 15:00:33+02:00'
+ >>> d = parse_iso_timestamp('2005-06-01 15:00:59.33 +02')
+ >>> d.strftime("%z %Z")
+ '+0200 +02'
+ >>> str(parse_iso_timestamp(str(d)))
+ '2005-06-01 15:00:59.330000+02:00'
+ >>> parse_iso_timestamp('2005-06-01 15:00-0530').strftime('%Y-%m-%d %H:%M %z %Z')
+ '2005-06-01 15:00 -0530 -05:30'
+ """
+
+ global _iso_rc
+ if _iso_rc is None:
+ _iso_rc = re.compile(_iso_regex, re.X)
+
+ m = _iso_rc.match(s)
+ if not m:
+ raise ValueError('Date not in ISO format: %s' % repr(s))
+
+ tz = default_tz
+ if m.group('tzsign'):
+ tzofs = int(m.group('tzhr')) * 60
+ if m.group('tzmin'):
+ tzofs += int(m.group('tzmin'))
+ if m.group('tzsign') == '-':
+ tzofs = -tzofs
+ tz = FixedOffsetTimezone(tzofs)
+
+ return datetime(int(m.group('year')),
+ int(m.group('month')),
+ int(m.group('day')),
+ int(m.group('hour')),
+ int(m.group('min')),
+ m.group('sec') and int(m.group('sec')) or 0,
+ m.group('ss') and int(m.group('ss').ljust(6, '0')) or 0,
+ tz)
+
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod()
+