Source code for adapya.base.dtconv

"""
dtconv - Date and Time conversion functions
===========================================

The module adapya.base.dtconv contains various routines to convert
datetime values between various formats.

For Gregorian to Julian date conversions and vice versa see

    http://en.wikipedia.org/wiki/Julian_day#Calculation

"""
from __future__ import print_function          # PY3

import time                     # for clock_time()
from datetime import datetime

# 0001-01-01 in Julian days is actually 1721425.5
# because the Julian calendar started at noon. For convenience of
# calculation we start 12 hours earlier at midnight:
RATADIE = 1721426
DAYSECS = 86400                 # seconds in a day
SECS1970 = 719162 * DAYSECS     # seconds since epoch 0001-01-01 to 1970-01-01

UTC1900=(1900,1,1,0,0,0,0)
UTC1970=(1970,1,1,0,0,0,0)
UTC1582=(1582,1,1,0,0,0,0)
UTCMAX=(9999,12,31,23,59,59,999999)
UTCMIN=(1,1,1,0,0,0,0)
UTC2008SAMPLE=(2008,12,31,13,20,59,123456)
MIC1970=62135596800000000       # microseconds since 0001-01-01 00:00:00.000000

[docs]class InvalidDateException(Exception): pass
[docs]def checkdate(year,month,day): """ Check Gregorian date given as year, month, day :raises InvalidDateException: if invalid input values are given otherwise returns silently Invalid date raises exception: >>> checkdate(2008,12,32) # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): InvalidDateException: day 32 not in range 1..31 Valid dates go trough quiet: >>> checkdate(2008,12,31) """ monthdays=(31,28,31,30,31,30,31,31,30,31,30,31) if not (1 <= year <= 9999): raise InvalidDateException('year %d not in range 1..9999' % year ) if not (1 <= month <= 12): raise InvalidDateException('month %d not in range 1..12' % month ) maxday = monthdays[month-1] if month == 2 and ( # leapyear divisible by 4 but not by 100 unless by 400 (year%400 == 0) or (year%4 == 0) and (year%100 != 0)): maxday+=1 if not (1 <= day <= maxday): raise InvalidDateException('day %d not in range 1..%d' % (day,maxday) )
[docs]def jul2jdn(year,month,day): """Calculate Julian day number from julian date - Earliest supported date is March 1, -4800 - 1 BC is year 0, 2 BC is -1 etc. (astronomical year numbering) - Julian year starts at 1. March """ if year < -4800: raise InvalidDateException('year %d outside range (-4800,9999)' % (year,) ) if year == -4800 and month < 3: raise InvalidDateException('month %d outside range (3,12) for year %d' % (month,year) ) a = (14-month)//12 # Jan/Feb: a=1 else a=0 y = year + 4800 - a # astronomical year numbering m = month + 12*a - 3 # Mar=0, ... Feb=11 # 153 days in each 5 month cycle starting May jdn = day + (153*m+2)//5 + 365*y + y//4 - 32083 return jdn
[docs]def greg2jdn(year,month,day): """Calculate the Julian day number for a proleptic gregorian date: November 24, 4714 BC is Julian day 0 at noon """ a = (14-month)//12 y = year + 4800 - a # astronomical year numbering m = month + 12*a - 3 jdn = day + (153*m+2)//5 + 365*y + y//4 - y//100 + y//400 - 32045 return jdn
[docs]def gx2utc(halfhours, microsecs): """Convert intermediate timestamp of two 32 bit integers to utc form: year,month,day,hour,minute,second,microsecond :param halfhours: number of halfhours since the epoch 0000-01-02 :param microsecs: number of microseconds within half hour >>> gx2utc(0,0) # 0000-01-02 00:00:00.000000 (0, 1, 2, 0, 0, 0, 0) >>> gx2utc(175316351,1799999999) # 9999-12-31 23:59:59.999999 (9999, 12, 31, 23, 59, 59, 999999) >>> gx2utc(34537296,0) # 1970-01-01 00:00:00.000000 (1970, 1, 1, 0, 0, 0, 0) >>> gx2utc(35221034,1259123456) # UTC2008SAMPLE (2008, 12, 31, 13, 20, 59, 123456) """ y,m,d,h,i,s = sec2utc(halfhours*30*60 + microsecs//10**6 - 365*DAYSECS) return y,m,d,h,i,s, microsecs%10**6
[docs]def jdn2greg( jdn ): """Calculate gregorian year, month, day from julian day number. astronomical year numbering year 0 is 1 B.C., -1 is 2 B.C. etc. Even though the first day in the Julian calendar correctly starts at noon rather than on midnight here we assume it to start at midnight (12 hours earlier) """ j = jdn + 32044 b = (4*j+3)//146097 c = j - (b*146097)//4 d = (4*c+3)//1461 e = c - (1461*d)//4 m = (5*e+2)//153 day = e - (153*m+2)//5 + 1 month = m + 3 - 12*(m//10) year = b*100 + d - 4800 + m//10 return year,month,day
[docs]def jdn2jul( jdn ): """Calculate julian year, month, day from julian day number """ b = 0 c = jdn + 32082 d = (4*c+3)//1461 e = c - (1461*d)//4 m = (5*e+2)//153 day = e - (153*m+2)//5 + 1 month = m + 3 - 12*(m//10) year = b*100 + d - 4800 + m//10 return year,month,day
[docs]def micro2nattime(mic): """Convert microseconds since 0001-01-01 to NATTIME Value in 10th of second precision """ return mic//10**5 + 365*DAYSECS*10
[docs]def micro2utc(microsecs): """Convert seconds since epoch value to list composed of:: year,month,day,hour,minute,second,microsecond :param microsec: number of microseconds since the epoch 0001-01-01 >>> micro2utc(0) # 0001-01-01 00:00:00.000000 (1, 1, 1, 0, 0, 0, 0) >>> micro2utc(315537897599999999) # 9999-12-31 23:59:59.999999 (9999, 12, 31, 23, 59, 59, 999999) >>> micro2utc(62135596800000000) # 1970-01-01 00:00:00.000000 (1970, 1, 1, 0, 0, 0, 0) >>> micro2utc(63366326459123456) # UTC2008SAMPLE (2008, 12, 31, 13, 20, 59, 123456) """ microsec = int(microsecs) % 10**6 i = int(microsecs)//10**6 second = i % 60 i //= 60 minute = i % 60 i //= 60 hour = i % 24 i //= 24 year, month, day = jdn2greg(i + RATADIE) return int(year),int(month),int(day),int(hour), \ int(minute),int(second),int(microsec)
[docs]def nattime2utc(nt): """ convert NATTIME Value in 10th of second precision to DATETIME value """ y,m,d,h,i,s = sec2utc(nt//10 - 365*DAYSECS) return y,m,d,h,i,s, nt%10 * 10**5
[docs]def nattime2micro(nt): """ convert NATTIME Value in 10th of second precision to microseconds since 1.1.1 """ return nt*10**5 - 365*DAYSECS*10**6
[docs]def sec2utc(seconds): """Convert seconds since epoch value to tuple composed of year,month,day,hour,minute,second :param seconds: number of seconds since the epoch 0001-01-01 if a float value is given: fraction of seconds yield microseconds """ microsecs = int(seconds*10**6) year,month,day,hour,minute,second,microsec = micro2utc(microsecs) if microsec !=0: second += float(microsec)/10**6 return year,month,day,hour,minute,second
[docs]def sec2interval(seconds): """Convert seconds to tuple of day, hour, minute and second :param seconds: integer :returns: tuple day, hour, minute, second """ i = int(seconds) second = i % 60 i //= 60 minute = i % 60 i //= 60 hour = i % 24 i //= 24 return i, hour, minute, second
def intervalstr(i): days, hours, minutes, seconds = sec2interval(i) days = '%dd' % (days) if days else '' hours = '%dh' % (hours) if hours else '' minutes = '%dm' % (minutes) if minutes else '' seconds = '%ds' % (seconds) if seconds else '' if days or hours or minutes or seconds: # compose nonempty strings with blanks in between return ' '.join([x for x in (days,hours,minutes,seconds) if x]) else: return '0s' def usecintervalstr(u): s = int(u//10**6) usec = u - s*10**6 return intervalstr(s) + ' %010.3fus' % usec stckintervalstr = lambda i: intervalstr(int(i* 1.048576)) stckdintervalstr = lambda i: usecintervalstr(i/4096.)
[docs]def unix2utc(seconds): """ same as sec2utc() only with epoch 1970-01-01""" return sec2utc(seconds+SECS1970)
[docs]def utc2gx(*secmsec): """Convert list composed of year,month,day,hour,minute,second,microsecond to gx format list of two 32 bit integers (halfhours, microsecs) since epoch 0000-01-02 :param secmsec: list of year,month,day,hour,minute,second,microsecond :returns: (halfhours, microsecs) since epoch 0000-01-02 """ mics = utc2micro(*secmsec) return (mics+365*DAYSECS*10**6)//(1800*10**6), \ (mics+365*DAYSECS*10**6)%(1800*10**6)
[docs]def utc2micro(*secmsec): """Convert list composed of year,month,day,hour,minute,second,microsecond :param secmsec: list of year,month,day,hour,minute,second,microsecond :returns: number of microseconds since the epoch 0001-01-01 00:00:00.000000 XTIMESTAMP(epoch 0001) >>> utc2micro(*UTCMIN) # 0001-01-01 00:00:00.000000 0 >>> '%d' % utc2micro(*UTCMAX) # 9999-12-31 23:59:59.999999 '315537897599999999' >>> '%d' % utc2micro(*UTC1970) # 1970-01-01 00:00:00.000000 '62135596800000000' >>> '%d' % utc2micro(*UTC2008SAMPLE) # 2008-12-31 13:20:59.123456 '63366326459123456' XTIMESTAMP(epoch 1970) >>> '%d' % (utc2micro(*UTCMIN)-utc2micro(*UTC1970)) '-62135596800000000' >>> '%d' % (utc2micro(*UTCMAX)-utc2micro(*UTC1970)) '253402300799999999' >>> '%d' % (utc2micro(*UTC1970)-utc2micro(*UTC1970)) '0' >>> '%d' % (utc2micro(*UTC2008SAMPLE)-utc2micro(*UTC1970)) '1230729659123456' """ if len(secmsec) < 2: raise InvalidDateException( 'Only provided %d parameters for utc2sec() with %s' % (len(secmsec), repr(secmsec)) ) microsec = int(secmsec[-1]) second = int(secmsec[-2]) minute=hour=day=month=year=days=0 if len(secmsec) > 2: if not( 0 <= second < 60): raise InvalidDateException( 'Invalid Value for second: %d utc2micro(%s)' % ( second, repr(secmsec)) ) minute = int(secmsec[-3]) if len(secmsec) > 3: hour = int(secmsec[-4]) if not( 0 <= minute < 60): raise InvalidDateException( 'Invalid Value for minute: %d utc2micro(%s)' % (minute, repr(secmsec)) ) if len(secmsec) > 4: days = day = int(secmsec[-5]) if not( 0 <= hour < 60): raise InvalidDateException( 'Invalid Value for hour: %d utc2micro(%s)' % (hour, repr(secmsec)) ) if len(secmsec) > 5: month = int(secmsec[-6]) if len(secmsec) > 6: year = int(secmsec[-7]) if not( 1 <= day <= 31): raise InvalidDateException( 'Invalid Value for day: %d utc2micro(%s)' % (day, repr(secmsec)) ) elif not( 1 <= month <= 12): raise InvalidDateException( 'Invalid Value for month: %d utc2micro(%s)' % (month, repr(secmsec)) ) elif not( 1 <= year <= 9999): raise InvalidDateException( 'Invalid Value for year: %d utc2micro(%s)' % (month, repr(secmsec)) ) days = greg2jdn(year,month,day) - RATADIE return microsec + 10**6 * (second + 60 * (minute + 60*hour) + days * DAYSECS)
[docs]def utc2nattime(*secmsec): """ convert utc timestamp since 0001-01-01 to NATTIME Value in 10th of second precision """ return micro2nattime(utc2micro(*secmsec))
[docs]def utc2sec(*utc): """Convert list composed of year,month,day,hour,minute,second [,microsec] :param utc: list of year,month,day,hour,minute,second :returns: number of seconds since the epoch 0001-01-01 """ utc2=list(utc) if len(utc)<7: utc2.append(0) return int(utc2micro(*utc2)/10**6)
[docs]def utc2unix(*utc): """ same as sec2utc() only with epoch 1970-01-01""" return utc2sec(*utc)-SECS1970
[docs]def utc2xts(*secmsec): """Convert list composed of year,month,day,hour,minute,second,microsecond to XTIMESTAMP (UNIXTIME in microsecond precision) :param secmsec: list of year,month,day,hour,minute,second,microsecond :returns: number of microseconds since the epoch 1970-01-01 00:00:00.000000 """ return utc2micro(*secmsec)-MIC1970
[docs]def xts2utc(microseconds): """ same as micro2utc() only with epoch 1970-01-01 """ return micro2utc(microseconds+MIC1970)
# # --- Adabas datetime specific conversions --- # # from DATE # def date2datetime(year,month,day): return (year,month,day,0,0,0) def date2timestamp(year,month,day): return (year,month,day,0,0,0,0)
[docs]def date2natdate(year,month,day): """Convert a gregorian date to a natdate integer No checking is done for valid dates. Natural does not allow dates earlier than 1582-01-01 >>> date2natdate(1,1,1) 365 >>> date2natdate(0,1,1) -1 >>> date2natdate(0,1,2) 0 >>> date2natdate(1900,1,1) 693960 >>> date2natdate(1582,1,1) 577813 >>> date2natdate(1970,1,1) 719527 >>> date2natdate(2000,1,1) 730484 >>> date2natdate(2008,12,31) 733771 >>> date2natdate(9999,12,31) 3652423 """ return greg2jdn(year,month,day)-RATADIE+365
def date2nattime(year,month,day): return date2natdate(year,month,day) * DAYSECS * 10 def date2unixtime(year,month,day): return utc2unix(date2timestamp(year,month,day)) def date2xtimestamp(year,month,day): return utc2xts(date2timestamp(year,month,day)) # # from datetime # def datetime2date(year,month,day,hour,minute,second): return year,month,day def datetime2time(year,month,day,hour,minute,second): return hour,minute,second def datetime2timestamp(year,month,day,hour,minute,second): return year,month,day,hour,minute,second,0 def datetime2natdate(year,month,day,hour,minute,second): return date2natdate(year,month,day) def datetime2nattime(year,month,day,hour,minute,second): return utc2nattime(year,month,day,hour,minute,second) def datetime2unixtime(year,month,day,hour,minute,second): return utc2unix(year,month,day,hour,minute,second,0) def datetime2xtimestamp(year,month,day,hour,minute,second): return datetime2xtimestamp(year,month,day,hour,minute,second) * 10**6 # # from timestamp # def timestamp2date(y,m,d,h,i,s,x): return y,m,d def timestamp2datetime(y,m,d,h,i,s,x): return y,m,d,h,i,s def timestamp2natdate(y,m,d,h,i,s,x): return date2natdate(y,m,d) def timestamp2nattime(y,m,d,h,i,s,x): return utc2nattime(y,m,d,h,i,s,x) def timestamp2unixtime(y,m,d,h,i,s,x): return utc2unix(y,m,d,h,i,s,x) def timestamp2xtimestamp(y,m,d,h,i,s,x): return utc2xts(y,m,d,h,i,s,x) # # from natdate # def natdate2date(nd): return jdn2greg(nd+RATADIE-365)
[docs]def natdate2nattime(nd): """ convert NATDATE counting days since Jan. 2, 0000 to NATTIME Value in 10th of second precision """ return nd*DAYSECS*10
# # from nattime # def nattime2date(nt): return natdate2date(nt // (DAYSECS*10)) def nattime2datetime(nt): y,m,d,h,i,s,_ = nattime2utc(nt) return y,m,d,h,i,s def nattime2timestamp(nt): return nattime2utc(nt)
[docs]def nattime2natdate(nt): """Convert NATTIME Value in 10th of second precision to NATDATE couning days since Jan. 2, 0000 """ return nt//(DAYSECS*10)
# # from xtimestamp #
[docs]def xtimestamp2timestamp(mics): """ >>> xtimestamp2timestamp(0) (1970, 1, 1, 0, 0, 0, 0) """ return xts2utc(mics)
# # simple datetime tuple to string conversions # DTF="%Y-%m-%d %H:%M:%S" DTN="%Y%m%d%H%M%S"
[docs]def dt2strf(*dt): """ >>> dt2strf( 2010,4,30, 11, 55, 13) '2010-04-30 11:55:13' >>> d1=(2010,1,2,11,44,55) >>> dt2strf( *d1) '2010-01-02 11:44:55' """ d = datetime(*dt) return d.strftime(DTF)
[docs]def ts2strf(*ts): """ >>> ts2strf( 2010,4,30, 11, 55, 13, 999999) '2010-04-30 11:55:13.999999' """ d = datetime(*ts) return d.strftime(DTF)+'.%06d'%d.microsecond
[docs]def dt2str(*dt): """ make a datetime string from a datetime tuple >>> dt2str( 2010,4,30, 11,55,13) '20100430115513' """ d = datetime(*dt) return d.strftime(DTN)
[docs]def str2dt(ds): """ make a datetime tuple from a datetime string or number >>> str2dt( '20160229115513' ) (2016, 2, 29, 11, 55, 13) >>> str2dt( b'20160301235513' ) (2016, 3, 1, 23, 55, 13) """ if not isinstance(ds, (bytes, bytearray, str)): ds=str(ds) # make it a string if number zero = b'0' if isinstance(ds, (bytes,bytearray)) else '0' if len(ds) < 14: ds = zero*(14-len(ds))+ds if len(ds) == 14: # Datetime ds = zero*(14-len(ds))+ds return int(ds[0:4]), int(ds[4:6]), int(ds[6:8]), \ int(ds[8:10]), int(ds[10:12]), int(ds[12:14]) elif len(ds) == 20: # Timestamp return int(ds[0:4]), int(ds[4:6]), int(ds[6:8]), \ int(ds[8:10]), int(ds[10:12]), int(ds[12:14]), \ int(ds[14:20])
# # test some functions #
[docs]def testgreg2jdn(): """Conversion of seconds to gregorian date""" samples = ( # year, month, day, secs # ( 1, 1, 1, 0000000000), ( 100, 1, 1, 3124137600), ( 100, 3, 1, 3129235200), ( 200, 1, 1, 6279811200), ( 200, 3, 1, 6284908800), ( 300, 1, 1, 9435484800), ( 300, 3, 1, 9440582400), ( 400, 1, 1, 12591158400), ( 400, 3, 1, 12596342400), ( 500, 1, 1, 15746918400), ( 500, 3, 1, 15752016000), ( 600, 1, 1, 18902592000), ( 600, 3, 1, 18907689600), ( 700, 1, 1, 22058265600), ( 700, 3, 1, 22063363200), ( 800, 1, 1, 25213939200), ( 800, 3, 1, 25219123200), ( 900, 1, 1, 28369699200), ( 900, 3, 1, 28374796800), ( 1000, 1, 1, 31525372800), ( 1000, 3, 1, 31530470400), ( 1100, 1, 1, 34681046400), ( 1100, 3, 1, 34686144000), ( 1200, 1, 1, 37836720000), ( 1200, 3, 1, 37841904000), ( 1300, 1, 1, 40992480000), ( 1300, 3, 1, 40997577600), ( 1400, 1, 1, 44148153600), ( 1400, 3, 1, 44153251200), ( 1500, 1, 1, 47303827200), ( 1500, 3, 1, 47308924800), ( 1582, 1, 1, 49891507200), # 577448*DAYTICS), # ( 1600, 1, 1, 50459500800), ( 1600, 3, 1, 50464684800), ( 1700, 1, 1, 53615260800), ( 1700, 3, 1, 53620358400), ( 1800, 1, 1, 56770934400), ( 1800, 3, 1, 56776032000), ( 1900, 1, 1, 59926608000), ( 1900, 3, 1, 59931705600), ( 1970, 1, 1, 62135596800), # 719162*DAYTICS ( 2000, 1, 1, 63082281600), ( 2000, 3, 1, 63087465600), ( 2100, 1, 1, 66238041600), ( 2100, 3, 1, 66243139200), ( 2200, 1, 1, 69393715200), ( 2200, 3, 1, 69398812800), ( 2300, 1, 1, 72549388800), ( 2300, 3, 1, 72554486400), ( 2400, 1, 1, 75705062400), ( 2400, 3, 1, 75710246400), ( 2500, 1, 1, 78860822400), ( 2500, 3, 1, 78865920000), ( 2600, 1, 1, 82016496000), ( 2600, 3, 1, 82021593600), ( 2700, 1, 1, 85172169600), ( 2700, 3, 1, 85177267200), ( 2800, 1, 1, 88327843200), ( 2800, 3, 1, 88333027200), ( 2900, 1, 1, 91483603200), ( 2900, 3, 1, 91488700800), ( 3000, 1, 1, 94639276800), ( 3000, 3, 1, 94644374400), ( 1 , 1, 1, 0), ( 1601, 1, 1, 50491123200), ( 1899, 12, 31, 59926521600), ( 1904, 1, 1, 60052752000), ( 1970, 1, 1, 62135596800), ( 2001, 1, 1, 63113904000), ( 9900, 3, 1, 312387321600), ( 9999, 12, 31, 315537811200) ) for y,m,d,secs in samples: gy, gm, gd = jdn2greg(secs//DAYSECS + RATADIE) gsec = (greg2jdn(y,m,d)-RATADIE) * DAYSECS # print Y m d secs days natdate nattime # print( y, m, d, secs, secs//DAYSECS, secs//DAYSECS+365, (secs+DAYSECS*365)*10 ) try: assert secs == gsec assert y == gy assert m == gm assert d == gd except: print( "datetime assertion error:", y,m,d,secs, 'vs.', gy,gm,gd,gsec)
# time0 = 0 clockbase0 = 0
[docs]def clock_time(resync=300): """:param resync: interval for resyncing with time() :returns: high resolution time """ global time0, clockbase0 clock1 = time.clock() if not time0 or (resync and resync < (clock1+clockbase0 - time0)): time0 = time.time() while 1: # until t=time() changes clock1 = time.clock() t = time.time() if t != time0: time0, clockbase0 = t, t-clock1 # print( 'clock_time()', datetime.fromtimestamp(t)) break return clock1+clockbase0
def demo_clock_time(): # show differences in clock_time to time() when time() changes a = [] t1 = time.time() for i in range(100000): c,t = clock_time(), time.time() if t != t1: t1=t a.append( (c, t) ) for c,t in a: print( datetime.fromtimestamp(c), datetime.fromtimestamp(t)) if __name__ == "__main__": import doctest doctest.testmod() # $Date: 2017-05-17 20:51:16 +0200 (Mi, 17 Mai 2017) $ # $Rev: 768 $ # # Copyright 2004-ThisYear Software AG # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.