Package gavo :: Package stc :: Module times
[frames] | no frames]

Source Code for Module gavo.stc.times

  1  """ 
  2  Helpers for time parsing and conversion. 
  3  """ 
  4   
  5  #c Copyright 2008-2019, the GAVO project 
  6  #c 
  7  #c This program is free software, covered by the GNU GPL.  See the 
  8  #c COPYING file in the source distribution. 
  9   
 10   
 11  import bisect 
 12  import datetime 
 13  import math 
 14   
 15  from gavo import utils 
 16  from gavo.stc import common 
 17   
 18   
 19  JD_MJD = 2400000.5 
20 21 -def parseISODT(value):
22 try: 23 return utils.parseISODT(value) 24 except ValueError as ex: 25 raise common.STCLiteralError(unicode(ex), value)
26
27 28 @utils.document 29 -def jdnToDateTime(jd):
30 """returns a ``datetime.datetime`` instance for a julian day number. 31 """ 32 return jYearToDateTime((jd-2451545.0)/365.25+2000.0)
33
34 35 @utils.document 36 -def mjdToDateTime(mjd):
37 """returns a ``datetime.datetime`` instance for a modified julian day number. 38 39 Beware: This loses a couple of significant digits due to transformation 40 to jd. 41 """ 42 return jdnToDateTime(mjd+JD_MJD)
43
44 45 @utils.document 46 -def bYearToDateTime(bYear):
47 """returns a datetime.datetime instance for a fractional Besselian year. 48 49 This uses the formula given by Lieske, J.H., A&A 73, 282 (1979). 50 """ 51 jdn = (bYear-1900.0)*common.tropicalYear+2415020.31352 52 return jdnToDateTime(jdn)
53
54 55 @utils.document 56 -def jYearToDateTime(jYear):
57 """returns a datetime.datetime instance for a fractional (julian) year. 58 59 This refers to time specifications like J2001.32. 60 """ 61 return datetime.datetime(2000, 1, 1, 12)+datetime.timedelta( 62 days=(jYear-2000.0)*365.25)
63 64 65 dtJ2000 = jYearToDateTime(2000.0) 66 dtB1950 = bYearToDateTime(1950.0)
67 68 @utils.document 69 -def dateTimeToJdn(dt):
70 """returns a julian day number (including fractionals) from a datetime 71 instance. 72 """ 73 a = (14-dt.month)//12 74 y = dt.year+4800-a 75 m = dt.month+12*a-3 76 jdn = dt.day+(153*m+2)//5+365*y+y//4-y//100+y//400-32045 77 try: 78 secsOnDay = dt.hour*3600+dt.minute*60+dt.second+dt.microsecond/1e6 79 except AttributeError: 80 secsOnDay = 0 81 return jdn+(secsOnDay-43200)/86400.
82
83 @utils.document 84 -def dateTimeToMJD(dt):
85 """returns a modified julian date for a datetime instance. 86 """ 87 return dateTimeToJdn(dt)-JD_MJD
88
89 -def dateTimeToBYear(dt):
90 return (dateTimeToJdn(dt)-2415020.31352)/common.tropicalYear+1900.0
91
92 93 @utils.document 94 -def dateTimeToJYear(dt):
95 """returns a fractional (julian) year for a datetime.datetime instance. 96 """ 97 return (dateTimeToJdn(dt)-2451545)/365.25+2000
98
99 100 -def getSeconds(td):
101 """returns the number of seconds corresponding to a timedelta object. 102 """ 103 return td.days*86400+td.seconds+td.microseconds*1e-6
104 105 106 ############ Time scale conversions 107 # All these convert to/from TT. 108 109 _TDTminusTAI = datetime.timedelta(seconds=32.184)
110 111 @utils.document 112 -def TTtoTAI(tdt):
113 """returns TAI for a (datetime.datetime) TDT. 114 """ 115 return tdt+_TDTminusTAI
116
117 @utils.document 118 -def TAItoTT(tai):
119 """returns TDT for a (datetime.datetime) TAI. 120 """ 121 return tai-_TDTminusTAI
122
123 124 -def _getTDBOffset(tdb):
125 """returns the TDB-TDT according to [EXS] 2.222-1. 126 """ 127 g = (357.53+0.9856003*(dateTimeToJdn(tdb)-2451545.0))/180*math.pi 128 return datetime.timedelta(0.001658*math.sin(g)+0.000014*math.sin(2*g))
129
130 -def TDBtoTT(tdb):
131 """returns an approximate TT from a TDB. 132 133 The simplified formula 2.222-1 from [EXS] is used. 134 """ 135 return tdb-_getTDBOffset(tdb)
136
137 -def TTtoTDB(tt):
138 """returns approximate TDB from TT. 139 140 The simplified formula 2.222-1 from [EXS] is used. 141 """ 142 return tt+_getTDBOffset(tt)
143 144 145 _L_G = 6.969291e-10 # [EXS], p. 47
146 147 -def _getTCGminusTT(dt): # [EXS], 2.223-5
148 return datetime.timedelta(seconds= 149 _L_G*(dateTimeToJdn(dt)-2443144.5)*86400) 150
151 -def TTtoTCG(tt):
152 """returns TT from TCG. 153 154 This uses 2.223-5 from [EXS]. 155 """ 156 return tt+_getTCGminusTT(tt)
157
158 -def TCGtoTT(tcg):
159 """returns TT from TCG. 160 161 This uses 2.223-5 from [EXS]. 162 """ 163 return tcg+_getTCGminusTT(tcg)
164 165 166 _L_B = 1.550505e-8
167 168 -def _getTCBminusTDB(dt): # [EXS], 2.223-2
169 return datetime.timedelta( 170 seconds=_L_B*(dateTimeToJdn(dt)-2443144.5)*86400) 171
172 -def TCBtoTT(tcb):
173 """returns an approximate TCB from a TT. 174 175 This uses [EXS] 2.223-2 and the approximate conversion from TDB to TT. 176 """ 177 return TDBtoTT(tcb+_getTCBminusTDB(tcb))
178
179 -def TTtoTCB(tt):
180 """returns an approximate TT from a TCB. 181 182 This uses [EXS] 2.223-2 and the approximate conversion from TT to TDB. 183 """ 184 return TTtoTDB(tt)-_getTCBminusTDB(tt)
185
186 187 -def _makeLeapSecondTable():
188 lsTable = [] 189 for lsCount, lsMoment in enumerate([ # from Lenny tzinfo 190 datetime.datetime(1971, 12, 31, 23, 59, 59), 191 datetime.datetime(1972, 06, 30, 23, 59, 59), 192 datetime.datetime(1972, 12, 31, 23, 59, 59), 193 datetime.datetime(1973, 12, 31, 23, 59, 59), 194 datetime.datetime(1974, 12, 31, 23, 59, 59), 195 datetime.datetime(1975, 12, 31, 23, 59, 59), 196 datetime.datetime(1976, 12, 31, 23, 59, 59), 197 datetime.datetime(1977, 12, 31, 23, 59, 59), 198 datetime.datetime(1978, 12, 31, 23, 59, 59), 199 datetime.datetime(1979, 12, 31, 23, 59, 59), 200 datetime.datetime(1981, 06, 30, 23, 59, 59), 201 datetime.datetime(1982, 06, 30, 23, 59, 59), 202 datetime.datetime(1983, 06, 30, 23, 59, 59), 203 datetime.datetime(1985, 06, 30, 23, 59, 59), 204 datetime.datetime(1987, 12, 31, 23, 59, 59), 205 datetime.datetime(1989, 12, 31, 23, 59, 59), 206 datetime.datetime(1990, 12, 31, 23, 59, 59), 207 datetime.datetime(1992, 06, 30, 23, 59, 59), 208 datetime.datetime(1993, 06, 30, 23, 59, 59), 209 datetime.datetime(1994, 06, 30, 23, 59, 59), 210 datetime.datetime(1995, 12, 31, 23, 59, 59), 211 datetime.datetime(1997, 06, 30, 23, 59, 59), 212 datetime.datetime(1998, 12, 31, 23, 59, 59), 213 datetime.datetime(2005, 12, 31, 23, 59, 59), 214 datetime.datetime(2008, 12, 31, 23, 59, 59), 215 ]): 216 lsTable.append((lsMoment, datetime.timedelta(seconds=lsCount+10))) 217 return lsTable
218 219 # A table of TAI-UTC, indexed by UTC 220 leapSecondTable = _makeLeapSecondTable() 221 del _makeLeapSecondTable 222 _sentinelTD = datetime.timedelta(seconds=0)
223 224 -def getLeapSeconds(dt, table=leapSecondTable):
225 """returns TAI-UTC for the datetime dt. 226 """ 227 ind = bisect.bisect_left(leapSecondTable, (dt, _sentinelTD)) 228 if ind==0: 229 return datetime.timedelta(seconds=9.) 230 return table[ind-1][1]
231
232 233 -def UTCtoTT(utc):
234 """returns TT from UTC. 235 236 The leap second table is complete through 2009-5. 237 238 >>> getLeapSeconds(datetime.datetime(1998,12,31,23,59,58)) 239 datetime.timedelta(0, 31) 240 >>> TTtoTAI(UTCtoTT(datetime.datetime(1998,12,31,23,59,59))) 241 datetime.datetime(1999, 1, 1, 0, 0, 30) 242 >>> TTtoTAI(UTCtoTT(datetime.datetime(1999,1,1,0,0,0))) 243 datetime.datetime(1999, 1, 1, 0, 0, 32) 244 """ 245 return TAItoTT(utc+getLeapSeconds(utc))
246 247 248 # A table of TAI-UTC, indexed by TT 249 ttLeapSecondTable = [(UTCtoTT(t), dt) 250 for t, dt in leapSecondTable]
251 252 253 -def TTtoUTC(tt):
254 """returns UTC from TT. 255 256 The leap second table is complete through 2009-5. 257 258 >>> TTtoUTC(UTCtoTT(datetime.datetime(1998,12,31,23,59,59))) 259 datetime.datetime(1998, 12, 31, 23, 59, 59) 260 >>> TTtoUTC(UTCtoTT(datetime.datetime(1999,1,1,0,0,0))) 261 datetime.datetime(1999, 1, 1, 0, 0) 262 """ 263 # XXX TODO: leap seconds need to be computed from UTC, so this will 264 # be one second off in the immediate vicinity of a leap second. 265 return TTtoTAI(tt)-getLeapSeconds(tt, ttLeapSecondTable)
266 267 268 # A dict mapping timescales to conversions to/from TT. 269 timeConversions = { 270 "UTC": (UTCtoTT, TTtoUTC), 271 "TCB": (TCBtoTT, TTtoTCB), 272 "TCG": (TCGtoTT, TTtoTCG), 273 "TDB": (TDBtoTT, TTtoTDB), 274 "TAI": (TAItoTT, TTtoTAI), 275 "TT": (utils.identity, utils.identity), 276 }
277 278 279 -def getTransformFromScales(fromScale, toScale):
280 if not fromScale or not toScale: 281 return utils.identity 282 283 try: 284 toTT = timeConversions[fromScale][0] 285 toTarget = timeConversions[toScale][1] 286 except KeyError as key: 287 raise common.STCValueError("Unknown timescale for transform: %s"%key) 288 289 def transform(val): 290 return toTarget(toTT(val))
291 292 return transform 293
294 295 -def getTransformFromSTC(fromSTC, toSTC):
296 fromScale, toScale = fromSTC.time.frame.timeScale, toSTC.time.frame.timeScale 297 if fromScale!=toSTC and toSTC is not None: 298 return getTransformFromScales(fromScale, toScale)
299
300 301 -def datetimeMapperFactory(colDesc):
302 import time 303 # This is too gruesome. We want some other way of handling this... 304 # Simplify this, and kick out all the mess we don't want. 305 # CAUTION: these heuristics reflected in user.info._getTimeTransformer 306 if (colDesc["dbtype"]=="timestamp" 307 or colDesc["dbtype"]=="date" 308 # must look in original, as the one in colDesc comes from typesystems 309 or colDesc.original.xtype=="adql:TIMESTAMP" # legacy, delete ~2020 310 or colDesc.original.xtype=="timestamp"): 311 unit = colDesc["unit"] 312 if (colDesc["displayHint"].get("format")=="humanDate" 313 or colDesc.original.xtype=="adql:TIMESTAMP" # legacy, delete ~2020 314 or colDesc.original.xtype=="timestamp"): 315 fun = lambda val: (val and val.isoformat()) or None 316 destType = ("char", "*") 317 colDesc["nullvalue"] = "" 318 319 elif (colDesc["ucd"] and "MJD" in colDesc["ucd"].upper() 320 or colDesc["xtype"]=="mjd" 321 or "mjd" in colDesc["name"]): 322 colDesc["unit"] = "d" 323 fun = lambda val: (val and dateTimeToMJD(val)) 324 destType = ("double", '1') 325 colDesc["nullvalue"] = "NaN" 326 colDesc["xtype"] = None 327 328 elif unit=="yr" or unit=="a": 329 fun = lambda val: (val and dateTimeToJYear(val)) 330 def fun(val): 331 return (val and dateTimeToJYear(val)) 332 return str(val)
333 destType = ("double", '1') 334 colDesc["nullvalue"] = "NaN" 335 colDesc["xtype"] = None 336 337 elif unit=="d": 338 fun = lambda val: (val and dateTimeToJdn(val)) 339 destType = ("double", '1') 340 colDesc["nullvalue"] = "NaN" 341 colDesc["xtype"] = None 342 343 elif unit=="s": 344 fun = lambda val: (val and time.mktime(val.timetuple())) 345 destType = ("double", '1') 346 colDesc["nullvalue"] = "NaN" 347 colDesc["xtype"] = None 348 349 else: 350 # default for datetime column: serialise to timestamp 351 fun = lambda val: (val and val.isoformat()) or None 352 destType = ("char", "*") 353 colDesc["nullvalue"] = "" 354 colDesc["xtype"] = "timestamp" 355 356 colDesc["datatype"], colDesc["arraysize"] = destType 357 return fun 358 utils.registerDefaultMF(datetimeMapperFactory)
359 360 361 -def _test():
362 import doctest 363 from gavo.stc import times 364 doctest.testmod(times)
365 366 if __name__=="__main__": 367 _test() 368