Coverage for tests/test_dateTime.py: 14%

299 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2022-08-18 18:31 +0000

1# 

2# LSST Data Management System 

3# 

4# Copyright 2008-2017 AURA/LSST. 

5# 

6# This product includes software developed by the 

7# LSST Project (http://www.lsst.org/). 

8# 

9# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the LSST License Statement and 

20# the GNU General Public License along with this program. If not, 

21# see <http://www.lsstcorp.org/LegalNotices/>. 

22# 

23 

24import os 

25import pickle 

26import time 

27import unittest 

28 

29import astropy.time 

30 

31from lsst.daf.base import DateTime 

32import lsst.pex.exceptions as pexExcept 

33 

34 

35class DateTimeTestCase(unittest.TestCase): 

36 """A test case for DateTime. 

37 """ 

38 

39 def setUp(self): 

40 self.timeScales = (DateTime.TAI, DateTime.TT, DateTime.UTC) 

41 self.dateSystems = (DateTime.JD, DateTime.MJD, DateTime.EPOCH) 

42 

43 def testMJD(self): 

44 ts = DateTime(45205.125, DateTime.MJD, DateTime.UTC) 

45 self.assertEqual(ts.nsecs(DateTime.UTC), 399006000000000000) 

46 self.assertEqual(ts.nsecs(DateTime.TAI), 399006021000000000) 

47 self.assertAlmostEqual(ts.get(DateTime.MJD, DateTime.UTC), 45205.125) 

48 self.assertAlmostEqual(ts.get(DateTime.MJD, DateTime.TAI), 45205.125 + 21.0/86400.0) 

49 self.assertTrue(ts.isValid()) 

50 

51 def testLeapSecond(self): 

52 trials = ((45205., 21), 

53 (41498.99, 10), 

54 (41499.01, 11), 

55 (57203.99, 35), 

56 (57204.01, 36), 

57 (57000., 35), 

58 (57210., 36)) 

59 for mjd, diff in trials: 

60 ts = DateTime(mjd, DateTime.MJD, DateTime.UTC) 

61 delta = ts.nsecs(DateTime.TAI) - ts.nsecs(DateTime.UTC) 

62 self.assertEqual(delta/1E9, diff) 

63 

64 def testNsecs(self): 

65 ts = DateTime(1192755473000000000, DateTime.UTC) 

66 self.assertEqual(ts.nsecs(DateTime.UTC), 1192755473000000000) 

67 self.assertEqual(ts.nsecs(DateTime.TAI), 1192755506000000000) 

68 self.assertEqual(ts.nsecs(), 1192755506000000000) 

69 self.assertAlmostEqual(ts.get(DateTime.MJD, DateTime.UTC), 54392.040196759262) 

70 ts2 = ts 

71 self.assertEqual(ts, ts2) 

72 ts2 = DateTime(1192755473000000000, DateTime.UTC) 

73 self.assertEqual(ts, ts2) 

74 ts2 = DateTime(1234567890000000000, DateTime.UTC) 

75 self.assertNotEqual(ts, ts2) 

76 

77 def testBoundaryMJD(self): 

78 ts = DateTime(47892.0, DateTime.MJD, DateTime.UTC) 

79 self.assertEqual(ts.nsecs(DateTime.UTC), 631152000000000000) 

80 self.assertEqual(ts.nsecs(DateTime.TAI), 631152025000000000) 

81 self.assertEqual(ts.get(DateTime.MJD, DateTime.UTC), 47892.0) 

82 

83 def testCrossBoundaryNsecs(self): 

84 ts = DateTime(631151998000000000, DateTime.UTC) 

85 self.assertEqual(ts.nsecs(DateTime.UTC), 631151998000000000) 

86 self.assertEqual(ts.nsecs(DateTime.TAI), 631152022000000000) 

87 

88 def testNsecsTAI(self): 

89 ts = DateTime(1192755506000000000, DateTime.TAI) 

90 self.assertEqual(ts.nsecs(DateTime.UTC), 1192755473000000000) 

91 self.assertEqual(ts.nsecs(DateTime.TAI), 1192755506000000000) 

92 self.assertEqual(ts.nsecs(), 1192755506000000000) 

93 self.assertAlmostEqual(ts.get(DateTime.MJD, DateTime.UTC), 54392.040196759262) 

94 self.assertTrue(ts.isValid()) 

95 

96 def testNsecsDefault(self): 

97 ts = DateTime(1192755506000000000) 

98 self.assertEqual(ts.nsecs(DateTime.UTC), 1192755473000000000) 

99 self.assertEqual(ts.nsecs(DateTime.TAI), 1192755506000000000) 

100 self.assertEqual(ts.nsecs(), 1192755506000000000) 

101 self.assertAlmostEqual(ts.get(DateTime.MJD, DateTime.UTC), 54392.040196759262) 

102 self.assertTrue(ts.isValid()) 

103 

104 def testNow(self): 

105 successes = 0 

106 for _ in range(10): 

107 secs = time.time() 

108 ts = DateTime.now() 

109 diff = ts.nsecs(DateTime.UTC)/1.0e9 - secs 

110 if diff > -0.001 and diff < 0.1: 

111 successes += 1 

112 self.assertGreaterEqual(successes, 3) 

113 

114 def testIsoEpoch(self): 

115 ts = DateTime("19700101T000000Z", DateTime.UTC) 

116 self.assertEqual(ts.nsecs(DateTime.UTC), 0) 

117 self.assertEqual(ts.toString(ts.UTC), "1970-01-01T00:00:00.000000000Z") 

118 

119 def testIsoUTCBasic(self): 

120 """Test basic ISO string input and output of UTC dates 

121 """ 

122 for dateSep in ("-", ""): # "-" date separator is optional 

123 for timeSep in (":", ""): # ":" time separator is optional 

124 for decPt in (".", ","): # "." or "," may be used as decimal point 

125 dateStr = "2009{0}04{0}02T07{1}26{1}39{2}314159265Z".format(dateSep, timeSep, decPt) 

126 ts = DateTime(dateStr, DateTime.UTC) 

127 self.assertEqual(ts.nsecs(DateTime.TT), 1238657265498159265) 

128 self.assertEqual(ts.nsecs(DateTime.TAI), 1238657233314159265) 

129 self.assertEqual(ts.nsecs(DateTime.UTC), 1238657199314159265) 

130 self.assertEqual(ts.toString(ts.UTC), "2009-04-02T07:26:39.314159265Z") 

131 

132 def testIsoNonUTCBasics(self): 

133 """Test basic ISO string input and output of TAI and TT dates 

134 """ 

135 for scale in (DateTime.TAI, DateTime.TT): 

136 for dateSep in ("-", ""): # "-" date separator is optional 

137 for timeSep in (":", ""): # ":" time separator is optional 

138 for decPt in (".", ","): # "." or "," may be used as decimal point 

139 dateStr = "2009{0}04{0}02T07{1}26{1}39{2}314159265".format(dateSep, timeSep, decPt) 

140 ts = DateTime(dateStr, scale) 

141 self.assertEqual(ts.toString(scale), "2009-04-02T07:26:39.314159265") 

142 self.assertTrue(ts.isValid()) 

143 

144 def testIsoExpanded(self): 

145 ts = DateTime("2009-04-02T07:26:39.314159265Z", DateTime.UTC) 

146 self.assertEqual(ts.nsecs(DateTime.TAI), 1238657233314159265) 

147 self.assertEqual(ts.nsecs(DateTime.UTC), 1238657199314159265) 

148 self.assertEqual(ts.toString(ts.UTC), "2009-04-02T07:26:39.314159265Z") 

149 self.assertTrue(ts.isValid()) 

150 

151 def testIsoNoNSecs(self): 

152 ts = DateTime("2009-04-02T07:26:39Z", DateTime.UTC) 

153 self.assertEqual(ts.nsecs(DateTime.TAI), 1238657233000000000) 

154 self.assertEqual(ts.nsecs(DateTime.UTC), 1238657199000000000) 

155 self.assertEqual(ts.toString(ts.UTC), "2009-04-02T07:26:39.000000000Z") 

156 self.assertTrue(ts.isValid()) 

157 

158 def testSOFA(self): 

159 """The SOFA documentation includes an example conversion: 

160 https://www.iausofa.org/2017_0420_C/sofa/sofa_ts_c.pdf 

161 (page 8, section 2.4) 

162 

163 The value in those docs is only ~single precision, so I re-computed 

164 it with pyerfa to get a more correct value. 

165 """ 

166 with self.subTest("jd to jyear"): 

167 self.assertEqual(DateTime(2457073.05631, DateTime.JD, DateTime.TAI).get(DateTime.EPOCH), 

168 2015.1349933196439) 

169 with self.subTest("jyear to jd"): 

170 self.assertEqual(DateTime(2015.1349933196, DateTime.EPOCH, DateTime.TAI).get(DateTime.JD), 

171 2457073.056309984) 

172 

173 def testAstropyComparison(self): 

174 """Astropy's Time module is based on ERFA, providing a well verified 

175 comparison point. 

176 """ 

177 def check_times(dateTime, time): 

178 with self.subTest("jyear"): 

179 self.assertAlmostEqual(dateTime.get(DateTime.EPOCH), time.tai.jyear) 

180 with self.subTest("mjd"): 

181 self.assertEqual(dateTime.get(DateTime.MJD), time.tai.mjd) 

182 with self.subTest("jd"): 

183 self.assertEqual(dateTime.get(DateTime.JD), time.tai.jd) 

184 

185 # Unix epoch comparison 

186 dateTime = DateTime("19700101T000000Z", DateTime.UTC) 

187 time = astropy.time.Time("1970-01-01T00:00:00", format="isot", scale="utc") 

188 check_times(dateTime, time) 

189 

190 # J2000 epoch comparison 

191 dateTime = DateTime(2000.0, DateTime.EPOCH, DateTime.UTC) 

192 time = astropy.time.Time(2000.0, format="jyear", scale="utc") 

193 check_times(dateTime, time) 

194 

195 # random future MJD epoch comparison 

196 dateTime = DateTime(65432.1, DateTime.MJD, DateTime.TAI) 

197 time = astropy.time.Time(65432.1, format="mjd", scale="tai") 

198 check_times(dateTime, time) 

199 

200 def testIsoThrow(self): 

201 with self.assertRaises(pexExcept.DomainError): 

202 DateTime("2009-04-01T23:36:05", DateTime.UTC) # Z time zone required for UTC 

203 for scale in (DateTime.TAI, DateTime.TT): 

204 with self.assertRaises(pexExcept.DomainError): 

205 DateTime("2009-04-01T23:36:05Z", scale) # Z time zone forbidden for TAI or TT 

206 

207 for scale in self.timeScales: 

208 with self.assertRaises(pexExcept.DomainError): 

209 DateTime("20090401", scale) # time required 

210 with self.assertRaises(pexExcept.DomainError): 

211 DateTime("20090401T", DateTime.UTC) # time required 

212 with self.assertRaises(pexExcept.DomainError): 

213 DateTime("2009-04-01T", DateTime.UTC) # time required 

214 with self.assertRaises(pexExcept.DomainError): 

215 DateTime("2009-04-01T23:36:05-0700", DateTime.UTC) # time zone offset not supported 

216 with self.assertRaises(pexExcept.DomainError): 

217 DateTime("2009/04/01T23:36:05Z", DateTime.UTC) # "/" not valid 

218 with self.assertRaises(pexExcept.DomainError): 

219 DateTime("2009-04-01T23:36", DateTime.UTC) # partial time 

220 with self.assertRaises(pexExcept.DomainError): 

221 DateTime("2009-04", DateTime.UTC) # partial date without time 

222 with self.assertRaises(pexExcept.DomainError): 

223 DateTime("2009-04T23:36.05", DateTime.UTC) # partial date with time 

224 with self.assertRaises(pexExcept.DomainError): 

225 DateTime("09-04-01T23:36:05", DateTime.UTC) # 2 digit year 

226 

227 # earliest allowed UTC date is the earliest date in the leap second 

228 # table 

229 try: 

230 minLeapSecUTC = "1961-01-01T00:00:00Z" 

231 dt = DateTime(minLeapSecUTC, DateTime.UTC) 

232 dt.toString(DateTime.UTC) 

233 except Exception: 

234 self.fail("minLeapSecUTC={} failed, but should be OK".format(minLeapSecUTC)) 

235 with self.assertRaises(pexExcept.DomainError): 

236 DateTime("1960-01-01T23:59:59Z", DateTime.UTC) # just before leap second table starts 

237 

238 # earliest allowed date for TAI and TT is year = 1902 

239 for timeSys in (DateTime.TAI, DateTime.TT): 

240 try: 

241 earliestDate = "1902-01-01T00:00:00" 

242 dt = DateTime(earliestDate, timeSys) 

243 dt.toString(DateTime.TAI) 

244 dt.toString(DateTime.TT) 

245 except Exception: 

246 self.fail("{} system={} failed, but should be OK".format(earliestDate, timeSys)) 

247 

248 # dates before the leap second table can be created using TAI or TT, 

249 # but not viewed in UTC 

250 earlyDt = DateTime("1960-01-01T00:00:00", DateTime.TAI) 

251 with self.assertRaises(pexExcept.DomainError): 

252 earlyDt.toString(DateTime.UTC) 

253 

254 with self.assertRaises(pexExcept.DomainError): 

255 DateTime("1901-12-12T23:59:59Z", DateTime.TAI) # too early 

256 with self.assertRaises(pexExcept.DomainError): 

257 DateTime("1700-01-01T00:00:00Z", DateTime.TAI) # way too early 

258 with self.assertRaises(pexExcept.DomainError): 

259 DateTime("2262-01-01T00:00:00Z", DateTime.TAI) # too late 

260 with self.assertRaises(pexExcept.DomainError): 

261 DateTime("3200-01-01T00:00:00Z", DateTime.TAI) # way too late 

262 

263 def testWraparound(self): 

264 """Test that a date later than 2038-01-19, 03:14:07 does not wrap 

265 around. 

266 

267 This will fail on old versions of unix, and indicates that DateTime 

268 is not safe. 

269 """ 

270 dateStr = "2040-01-01T00:00:00.000000000" 

271 self.assertEqual(str(DateTime(dateStr, DateTime.TAI)), "DateTime(\"{}\", TAI)".format(dateStr)) 

272 

273 def testDM7622(self): 

274 """Test DM-7622: date with unix time = -1 seconds must be usable 

275 

276 Note that the call in question parses the ISO string without paying 

277 attention to the scale (it applies the scale later), 

278 so the same ISO string is wanted in all cases 

279 (except with a trailing Z for UTC, and without for TAI and TT) 

280 """ 

281 negOneSecIso = "1969-12-31T23:59:59.000000000" 

282 for scale in self.timeScales: 

283 dateStr = negOneSecIso + ("Z" if scale == DateTime.UTC else "") 

284 try: 

285 dt = DateTime(dateStr, scale) 

286 except Exception: 

287 self.fail("Date {} in scale {} unusable".format(dateStr, scale)) 

288 self.assertEqual(dt.nsecs(scale), int(-1e9)) 

289 

290 def testStr(self): 

291 timeStr1 = "2004-03-01T12:39:45.1" 

292 fullTimeStr1 = "2004-03-01T12:39:45.100000000" 

293 dt1 = DateTime(timeStr1, DateTime.TAI) 

294 self.assertEqual(str(dt1), "DateTime(\"{}\", TAI)".format(fullTimeStr1)) 

295 self.assertEqual(repr(dt1), "DateTime(\"{}\", TAI)".format(fullTimeStr1)) 

296 

297 timeStr2 = "2004-03-01T12:39:45.000000001" 

298 dt2 = DateTime(timeStr2, DateTime.TAI) 

299 self.assertEqual(str(dt2), "DateTime(\"{}\", TAI)".format(timeStr2)) 

300 self.assertEqual(repr(dt2), "DateTime(\"{}\", TAI)".format(timeStr2)) 

301 

302 def testNsecsTT(self): 

303 ts = DateTime(1192755538184000000, DateTime.TT) 

304 self.assertEqual(ts.nsecs(DateTime.UTC), 1192755473000000000) 

305 self.assertEqual(ts.nsecs(DateTime.TAI), 1192755506000000000) 

306 self.assertEqual(ts.nsecs(), 1192755506000000000) 

307 self.assertAlmostEqual(ts.get(DateTime.MJD, DateTime.UTC), 54392.040196759262) 

308 self.assertTrue(ts.isValid()) 

309 

310 def testFracSecs(self): 

311 ts = DateTime("2004-03-01T12:39:45.1Z", DateTime.UTC) 

312 self.assertEqual(ts.toString(ts.UTC), '2004-03-01T12:39:45.100000000Z') 

313 ts = DateTime("2004-03-01T12:39:45.01Z", DateTime.UTC) 

314 self.assertEqual(ts.toString(ts.UTC), '2004-03-01T12:39:45.010000000Z') 

315 ts = DateTime("2004-03-01T12:39:45.000000001Z", DateTime.UTC) # nanosecond 

316 self.assertEqual(ts.toString(ts.UTC), '2004-03-01T12:39:45.000000001Z') 

317 ts = DateTime("2004-03-01T12:39:45.0000000001Z", DateTime.UTC) # too small 

318 self.assertEqual(ts.toString(ts.UTC), '2004-03-01T12:39:45.000000000Z') 

319 

320 def testInvalid(self): 

321 ts = DateTime() 

322 self.assertFalse(ts.isValid()) 

323 for scale in self.timeScales: 

324 self.assertEqual(ts.nsecs(scale), DateTime.invalid_nsecs) 

325 for system in self.dateSystems: 

326 with self.assertRaises(RuntimeError): 

327 ts.get(system, scale) 

328 with self.assertRaises(RuntimeError): 

329 ts.gmtime(scale) 

330 with self.assertRaises(RuntimeError): 

331 ts.timespec(scale) 

332 with self.assertRaises(RuntimeError): 

333 ts.timeval(scale) 

334 with self.assertRaises(RuntimeError): 

335 ts.toString(scale) 

336 self.assertEqual(repr(ts), "DateTime()") 

337 

338 def testNegative(self): 

339 ts = DateTime("1969-03-01T00:00:32Z", DateTime.UTC) 

340 self.assertEqual(ts.toString(ts.UTC), '1969-03-01T00:00:32.000000000Z') 

341 ts = DateTime("1969-01-01T00:00:00Z", DateTime.UTC) 

342 self.assertEqual(ts.toString(ts.UTC), '1969-01-01T00:00:00.000000000Z') 

343 ts = DateTime("1969-01-01T00:00:40Z", DateTime.UTC) 

344 self.assertEqual(ts.toString(ts.UTC), '1969-01-01T00:00:40.000000000Z') 

345 ts = DateTime("1969-01-01T00:00:38Z", DateTime.UTC) 

346 self.assertEqual(ts.toString(ts.UTC), '1969-01-01T00:00:38.000000000Z') 

347 ts = DateTime("1969-03-01T12:39:45Z", DateTime.UTC) 

348 self.assertEqual(ts.toString(ts.UTC), '1969-03-01T12:39:45.000000000Z') 

349 ts = DateTime("1969-03-01T12:39:45.000000001Z", DateTime.UTC) 

350 self.assertEqual(ts.toString(ts.UTC), '1969-03-01T12:39:45.000000001Z') 

351 self.assertTrue(ts.isValid()) 

352 

353 # Note slight inaccuracy in UTC-TAI-UTC round-trip 

354 ts = DateTime("1969-03-01T12:39:45.12345Z", DateTime.UTC) 

355 self.assertEqual(ts.toString(ts.UTC), '1969-03-01T12:39:45.123449996Z') 

356 ts = DateTime("1969-03-01T12:39:45.123456Z", DateTime.UTC) 

357 self.assertEqual(ts.toString(ts.UTC), '1969-03-01T12:39:45.123455996Z') 

358 

359 ts = DateTime(-1, DateTime.TAI) 

360 self.assertEqual(ts.toString(ts.UTC), '1969-12-31T23:59:51.999918239Z') 

361 ts = DateTime(0, DateTime.TAI) 

362 self.assertEqual(ts.toString(ts.UTC), '1969-12-31T23:59:51.999918240Z') 

363 ts = DateTime(1, DateTime.TAI) 

364 self.assertEqual(ts.toString(ts.UTC), '1969-12-31T23:59:51.999918241Z') 

365 

366 ts = DateTime(-1, DateTime.UTC) 

367 self.assertEqual(ts.toString(ts.UTC), '1969-12-31T23:59:59.999999999Z') 

368 ts = DateTime(0, DateTime.UTC) 

369 self.assertEqual(ts.toString(ts.UTC), '1970-01-01T00:00:00.000000000Z') 

370 ts = DateTime(1, DateTime.UTC) 

371 self.assertEqual(ts.toString(ts.UTC), '1970-01-01T00:00:00.000000001Z') 

372 

373 def testConvert(self): 

374 year = 2012 

375 month = 7 

376 day = 19 

377 hour = 18 

378 minute = 29 

379 second = 33 

380 

381 ts = DateTime(year, month, day, hour, minute, second, DateTime.UTC) 

382 dt = ts.toPython(DateTime.UTC) 

383 

384 self.assertEqual(dt.year, year) 

385 self.assertEqual(dt.month, month) 

386 self.assertEqual(dt.day, day) 

387 self.assertEqual(dt.hour, hour) 

388 self.assertEqual(dt.minute, minute) 

389 self.assertEqual(dt.second, second) 

390 

391 def testPickle(self): 

392 ts = DateTime(int(1192755473000000000), DateTime.UTC) 

393 nts = pickle.loads(pickle.dumps(ts)) 

394 self.assertEqual(nts.nsecs(DateTime.UTC), int(1192755473000000000)) 

395 

396 

397class TimeZoneBaseTestCase(DateTimeTestCase): 

398 timezone = "" 

399 

400 def setUp(self): 

401 DateTimeTestCase.setUp(self) 

402 self.tz = os.environ.setdefault('TZ', "") 

403 os.environ['TZ'] = self.timezone 

404 

405 def tearDown(self): 

406 if self.tz == "": 

407 del os.environ['TZ'] 

408 else: 

409 os.environ['TZ'] = self.tz 

410 

411 

412class BritishTimeTestCase(TimeZoneBaseTestCase): 

413 timezone = "Europe/London" 

414 

415 

416class BritishTime2TestCase(TimeZoneBaseTestCase): 

417 timezone = "GMT0BST" 

418 

419 

420class PacificTimeTestCase(TimeZoneBaseTestCase): 

421 timezone = "PST8PDT" 

422 

423 

424if __name__ == '__main__': 424 ↛ 425line 424 didn't jump to line 425, because the condition on line 424 was never true

425 unittest.main()