123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363 |
- """
- utility functions for converting between
- datetime, iso date and 14-digit timestamp
- """
- import re
- import time
- import datetime
- import calendar
- from email.utils import parsedate, formatdate
- #=================================================================
- # str <-> datetime conversion
- #=================================================================
- DATE_TIMESPLIT = re.compile(r'[^\d]')
- TIMESTAMP_14 = '%Y%m%d%H%M%S'
- ISO_DT = '%Y-%m-%dT%H:%M:%SZ'
- PAD_14_DOWN = '10000101000000'
- PAD_14_UP = '29991231235959'
- PAD_6_UP = '299912'
- PAD_MICRO = '000000'
- def iso_date_to_datetime(string):
- """
- >>> iso_date_to_datetime('2013-12-26T10:11:12Z')
- datetime.datetime(2013, 12, 26, 10, 11, 12)
- >>> iso_date_to_datetime('2013-12-26T10:11:12.456789Z')
- datetime.datetime(2013, 12, 26, 10, 11, 12, 456789)
- >>> iso_date_to_datetime('2013-12-26T10:11:12.30Z')
- datetime.datetime(2013, 12, 26, 10, 11, 12, 300000)
- >>> iso_date_to_datetime('2013-12-26T10:11:12.00001Z')
- datetime.datetime(2013, 12, 26, 10, 11, 12, 10)
- >>> iso_date_to_datetime('2013-12-26T10:11:12.000001Z')
- datetime.datetime(2013, 12, 26, 10, 11, 12, 1)
- >>> iso_date_to_datetime('2013-12-26T10:11:12.0000001Z')
- datetime.datetime(2013, 12, 26, 10, 11, 12)
- >>> iso_date_to_datetime('2013-12-26T10:11:12.000000Z')
- datetime.datetime(2013, 12, 26, 10, 11, 12)
- """
- nums = DATE_TIMESPLIT.split(string)
- if nums[-1] == '':
- nums = nums[:-1]
- if len(nums) == 7:
- nums[6] = nums[6][:6]
- nums[6] += PAD_MICRO[len(nums[6]):]
- the_datetime = datetime.datetime(*(int(num) for num in nums))
- return the_datetime
- def http_date_to_datetime(string):
- """
- >>> http_date_to_datetime('Thu, 26 Dec 2013 09:50:10 GMT')
- datetime.datetime(2013, 12, 26, 9, 50, 10)
- """
- return datetime.datetime(*parsedate(string)[:6])
- def datetime_to_http_date(the_datetime):
- """
- >>> datetime_to_http_date(datetime.datetime(2013, 12, 26, 9, 50, 10))
- 'Thu, 26 Dec 2013 09:50:10 GMT'
- # Verify inverses
- >>> x = 'Thu, 26 Dec 2013 09:50:10 GMT'
- >>> datetime_to_http_date(http_date_to_datetime(x)) == x
- True
- """
- timeval = calendar.timegm(the_datetime.utctimetuple())
- return formatdate(timeval=timeval,
- localtime=False,
- usegmt=True)
- def datetime_to_iso_date(the_datetime, use_micros=False):
- """
- >>> datetime_to_iso_date(datetime.datetime(2013, 12, 26, 10, 11, 12))
- '2013-12-26T10:11:12Z'
- >>> datetime_to_iso_date(datetime.datetime(2013, 12, 26, 10, 11, 12, 456789))
- '2013-12-26T10:11:12Z'
- >>> datetime_to_iso_date(datetime.datetime(2013, 12, 26, 10, 11, 12), use_micros=True)
- '2013-12-26T10:11:12Z'
- >>> datetime_to_iso_date(datetime.datetime(2013, 12, 26, 10, 11, 12, 456789), use_micros=True)
- '2013-12-26T10:11:12.456789Z'
- >>> datetime_to_iso_date(datetime.datetime(2013, 12, 26, 10, 11, 12, 1), use_micros=True)
- '2013-12-26T10:11:12.000001Z'
- """
- if not use_micros:
- return the_datetime.strftime(ISO_DT)
- else:
- return the_datetime.isoformat() + 'Z'
- def datetime_to_timestamp(the_datetime):
- """
- >>> datetime_to_timestamp(datetime.datetime(2013, 12, 26, 10, 11, 12))
- '20131226101112'
- """
- return the_datetime.strftime(TIMESTAMP_14)
- def timestamp_now():
- """
- >>> len(timestamp_now())
- 14
- """
- return datetime_to_timestamp(datetime.datetime.utcnow())
- def timestamp20_now():
- """
- Create 20-digit timestamp, useful to timestamping temp files
- >>> n = timestamp20_now()
- >>> timestamp20_now() >= n
- True
- >>> len(n)
- 20
- """
- now = datetime.datetime.utcnow()
- return now.strftime('%Y%m%d%H%M%S%f')
- def iso_date_to_timestamp(string):
- """
- >>> iso_date_to_timestamp('2013-12-26T10:11:12Z')
- '20131226101112'
- >>> iso_date_to_timestamp('2013-12-26T10:11:12')
- '20131226101112'
- """
- return datetime_to_timestamp(iso_date_to_datetime(string))
- def timestamp_to_iso_date(string):
- """
- >>> timestamp_to_iso_date('20131226101112')
- '2013-12-26T10:11:12Z'
- >>> timestamp_to_iso_date('20131226101112')
- '2013-12-26T10:11:12Z'
- """
- return datetime_to_iso_date(timestamp_to_datetime(string))
- def http_date_to_timestamp(string):
- """
- >>> http_date_to_timestamp('Thu, 26 Dec 2013 09:50:00 GMT')
- '20131226095000'
- >>> http_date_to_timestamp('Sun, 26 Jan 2014 20:08:04 GMT')
- '20140126200804'
- """
- return datetime_to_timestamp(http_date_to_datetime(string))
- # pad to certain length (default 6)
- def pad_timestamp(string, pad_str=PAD_6_UP):
- """
- >>> pad_timestamp('20')
- '209912'
- >>> pad_timestamp('2014')
- '201412'
- >>> pad_timestamp('20141011')
- '20141011'
- >>> pad_timestamp('201410110010')
- '201410110010'
- """
- str_len = len(string)
- pad_len = len(pad_str)
- if str_len < pad_len:
- string = string + pad_str[str_len:]
- return string
- def timestamp_to_datetime(string):
- """
- # >14-digit -- rest ignored
- >>> timestamp_to_datetime('2014122609501011')
- datetime.datetime(2014, 12, 26, 9, 50, 10)
- # 14-digit
- >>> timestamp_to_datetime('20141226095010')
- datetime.datetime(2014, 12, 26, 9, 50, 10)
- # 13-digit padding
- >>> timestamp_to_datetime('2014122609501')
- datetime.datetime(2014, 12, 26, 9, 50, 59)
- # 12-digit padding
- >>> timestamp_to_datetime('201412260950')
- datetime.datetime(2014, 12, 26, 9, 50, 59)
- # 11-digit padding
- >>> timestamp_to_datetime('20141226095')
- datetime.datetime(2014, 12, 26, 9, 59, 59)
- # 10-digit padding
- >>> timestamp_to_datetime('2014122609')
- datetime.datetime(2014, 12, 26, 9, 59, 59)
- # 9-digit padding
- >>> timestamp_to_datetime('201412260')
- datetime.datetime(2014, 12, 26, 23, 59, 59)
- # 8-digit padding
- >>> timestamp_to_datetime('20141226')
- datetime.datetime(2014, 12, 26, 23, 59, 59)
- # 7-digit padding
- >>> timestamp_to_datetime('2014122')
- datetime.datetime(2014, 12, 31, 23, 59, 59)
- # 6-digit padding
- >>> timestamp_to_datetime('201410')
- datetime.datetime(2014, 10, 31, 23, 59, 59)
- # 5-digit padding
- >>> timestamp_to_datetime('20141')
- datetime.datetime(2014, 12, 31, 23, 59, 59)
- # 4-digit padding
- >>> timestamp_to_datetime('2014')
- datetime.datetime(2014, 12, 31, 23, 59, 59)
- # 3-digit padding
- >>> timestamp_to_datetime('201')
- datetime.datetime(2019, 12, 31, 23, 59, 59)
- # 2-digit padding
- >>> timestamp_to_datetime('20')
- datetime.datetime(2099, 12, 31, 23, 59, 59)
- # 1-digit padding
- >>> timestamp_to_datetime('2')
- datetime.datetime(2999, 12, 31, 23, 59, 59)
- # 1-digit out-of-range padding
- >>> timestamp_to_datetime('3')
- datetime.datetime(2999, 12, 31, 23, 59, 59)
- # 0-digit padding
- >>> timestamp_to_datetime('')
- datetime.datetime(2999, 12, 31, 23, 59, 59)
- # bad month
- >>> timestamp_to_datetime('20131709005601')
- datetime.datetime(2013, 12, 9, 0, 56, 1)
- # all out of range except minutes
- >>> timestamp_to_datetime('40001965252477')
- datetime.datetime(2999, 12, 31, 23, 24, 59)
- # not a number!
- >>> timestamp_to_datetime('2010abc')
- datetime.datetime(2010, 12, 31, 23, 59, 59)
- """
- # pad to 6 digits
- string = pad_timestamp(string, PAD_6_UP)
- def clamp(val, min_, max_):
- try:
- val = int(val)
- val = max(min_, min(val, max_))
- return val
- except:
- return max_
- def extract(string, start, end, min_, max_):
- if len(string) >= end:
- return clamp(string[start:end], min_, max_)
- else:
- return max_
- # now parse, clamp to boundary
- year = extract(string, 0, 4, 1900, 2999)
- month = extract(string, 4, 6, 1, 12)
- day = extract(string, 6, 8, 1, calendar.monthrange(year, month)[1])
- hour = extract(string, 8, 10, 0, 23)
- minute = extract(string, 10, 12, 0, 59)
- second = extract(string, 12, 14, 0, 59)
- return datetime.datetime(year=year,
- month=month,
- day=day,
- hour=hour,
- minute=minute,
- second=second)
- #return time.strptime(pad_timestamp(string), TIMESTAMP_14)
- def timestamp_to_sec(string):
- """
- >>> timestamp_to_sec('20131226095010')
- 1388051410
- # rounds to end of 2014
- >>> timestamp_to_sec('2014')
- 1420070399
- """
- return calendar.timegm(timestamp_to_datetime(string).utctimetuple())
- def sec_to_timestamp(secs):
- """
- >>> sec_to_timestamp(1388051410)
- '20131226095010'
- >>> sec_to_timestamp(1420070399)
- '20141231235959'
- """
- return datetime_to_timestamp(datetime.datetime.utcfromtimestamp(secs))
- def timestamp_to_http_date(string):
- """
- >>> timestamp_to_http_date('20131226095000')
- 'Thu, 26 Dec 2013 09:50:00 GMT'
- >>> timestamp_to_http_date('20140126200804')
- 'Sun, 26 Jan 2014 20:08:04 GMT'
- """
- return datetime_to_http_date(timestamp_to_datetime(string))
- if __name__ == "__main__": #pragma: no cover
- import doctest
- doctest.testmod()
|