Coverage for tests/test_dateTime.py: 12%

315 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-02-04 02:35 -0800

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 

33import lsst.utils.tests 

34 

35 

36class DateTimeTestCase(lsst.utils.tests.TestCase): 

37 """Base class for tests of DateTime objects, without any timezone. 

38 """ 

39 

40 def setUp(self): 

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

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

43 

44 def testMJD(self): 

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

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

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

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

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

50 self.assertTrue(ts.isValid()) 

51 

52 def testLeapSecond(self): 

53 trials = ((45205., 21), 

54 (41498.99, 10), 

55 (41499.01, 11), 

56 (57203.99, 35), 

57 (57204.01, 36), 

58 (57000., 35), 

59 (57210., 36)) 

60 for mjd, diff in trials: 

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

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

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

64 

65 def testNsecs(self): 

66 ts = DateTime(1192755473000000000, DateTime.UTC) 

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

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

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

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

71 ts2 = ts 

72 self.assertEqual(ts, ts2) 

73 ts2 = DateTime(1192755473000000000, DateTime.UTC) 

74 self.assertEqual(ts, ts2) 

75 ts2 = DateTime(1234567890000000000, DateTime.UTC) 

76 self.assertNotEqual(ts, ts2) 

77 

78 def testBoundaryMJD(self): 

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

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

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

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

83 

84 def testCrossBoundaryNsecs(self): 

85 ts = DateTime(631151998000000000, DateTime.UTC) 

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

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

88 

89 def testNsecsTAI(self): 

90 ts = DateTime(1192755506000000000, DateTime.TAI) 

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

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

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

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

95 self.assertTrue(ts.isValid()) 

96 

97 def testNsecsDefault(self): 

98 ts = DateTime(1192755506000000000) 

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

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

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

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

103 self.assertTrue(ts.isValid()) 

104 

105 def testNow(self): 

106 successes = 0 

107 for _ in range(10): 

108 secs = time.time() 

109 ts = DateTime.now() 

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

111 if diff > -0.001 and diff < 0.1: 

112 successes += 1 

113 self.assertGreaterEqual(successes, 3) 

114 

115 def testIsoEpoch(self): 

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

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

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

119 

120 def testIsoUTCBasic(self): 

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

122 """ 

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

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

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

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

127 ts = DateTime(dateStr, DateTime.UTC) 

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

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

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

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

132 

133 def testIsoNonUTCBasics(self): 

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

135 """ 

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

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

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

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

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

141 ts = DateTime(dateStr, scale) 

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

143 self.assertTrue(ts.isValid()) 

144 

145 def testIsoExpanded(self): 

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

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

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

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

150 self.assertTrue(ts.isValid()) 

151 

152 def testIsoNoNSecs(self): 

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

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

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

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

157 self.assertTrue(ts.isValid()) 

158 

159 def testSOFA(self): 

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

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

162 (page 8, section 2.4) 

163 

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

165 it with pyerfa to get a more correct value. 

166 """ 

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

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

169 2015.1349933196439) 

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

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

172 2457073.056309984) 

173 

174 def testAstropyComparison(self): 

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

176 comparison point. 

177 """ 

178 def check_times(dateTime, time): 

179 with self.subTest("jyear"): 

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

181 with self.subTest("mjd"): 

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

183 with self.subTest("jd"): 

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

185 

186 # Unix epoch comparison 

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

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

189 check_times(dateTime, time) 

190 

191 # J2000 epoch comparison 

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

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

194 check_times(dateTime, time) 

195 

196 # random future MJD epoch comparison 

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

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

199 check_times(dateTime, time) 

200 

201 def testIsoThrow(self): 

202 with self.assertRaises(pexExcept.DomainError): 

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

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

205 with self.assertRaises(pexExcept.DomainError): 

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

207 

208 for scale in self.timeScales: 

209 with self.assertRaises(pexExcept.DomainError): 

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

211 with self.assertRaises(pexExcept.DomainError): 

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

213 with self.assertRaises(pexExcept.DomainError): 

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

215 with self.assertRaises(pexExcept.DomainError): 

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

217 with self.assertRaises(pexExcept.DomainError): 

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

219 with self.assertRaises(pexExcept.DomainError): 

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

221 with self.assertRaises(pexExcept.DomainError): 

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

223 with self.assertRaises(pexExcept.DomainError): 

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

225 with self.assertRaises(pexExcept.DomainError): 

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

227 

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

229 # table 

230 try: 

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

232 dt = DateTime(minLeapSecUTC, DateTime.UTC) 

233 dt.toString(DateTime.UTC) 

234 except Exception: 

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

236 with self.assertRaises(pexExcept.DomainError): 

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

238 

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

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

241 try: 

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

243 dt = DateTime(earliestDate, timeSys) 

244 dt.toString(DateTime.TAI) 

245 dt.toString(DateTime.TT) 

246 except Exception: 

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

248 

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

250 # but not viewed in UTC 

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

252 with self.assertRaises(pexExcept.DomainError): 

253 earlyDt.toString(DateTime.UTC) 

254 

255 with self.assertRaises(pexExcept.DomainError): 

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

257 with self.assertRaises(pexExcept.DomainError): 

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

259 with self.assertRaises(pexExcept.DomainError): 

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

261 with self.assertRaises(pexExcept.DomainError): 

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

263 

264 def testWraparound(self): 

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

266 around. 

267 

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

269 is not safe. 

270 """ 

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

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

273 

274 def testDM7622(self): 

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

276 

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

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

279 so the same ISO string is wanted in all cases 

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

281 """ 

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

283 for scale in self.timeScales: 

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

285 try: 

286 dt = DateTime(dateStr, scale) 

287 except Exception: 

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

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

290 

291 def testStr(self): 

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

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

294 dt1 = DateTime(timeStr1, DateTime.TAI) 

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

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

297 

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

299 dt2 = DateTime(timeStr2, DateTime.TAI) 

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

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

302 

303 def testNsecsTT(self): 

304 ts = DateTime(1192755538184000000, DateTime.TT) 

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

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

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

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

309 self.assertTrue(ts.isValid()) 

310 

311 def testFracSecs(self): 

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

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

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

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

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

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

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

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

320 

321 def testInvalid(self): 

322 ts = DateTime() 

323 self.assertFalse(ts.isValid()) 

324 for scale in self.timeScales: 

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

326 for system in self.dateSystems: 

327 with self.assertRaises(RuntimeError): 

328 ts.get(system, scale) 

329 with self.assertRaises(RuntimeError): 

330 ts.gmtime(scale) 

331 with self.assertRaises(RuntimeError): 

332 ts.timespec(scale) 

333 with self.assertRaises(RuntimeError): 

334 ts.timeval(scale) 

335 with self.assertRaises(RuntimeError): 

336 ts.toString(scale) 

337 with self.assertRaises(RuntimeError): 

338 ts.toPython() 

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

340 

341 def testNegative(self): 

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

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

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

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

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

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

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

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

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

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

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

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

354 self.assertTrue(ts.isValid()) 

355 

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

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

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

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

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

361 

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

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

364 ts = DateTime(0, DateTime.TAI) 

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

366 ts = DateTime(1, DateTime.TAI) 

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

368 

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

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

371 ts = DateTime(0, DateTime.UTC) 

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

373 ts = DateTime(1, DateTime.UTC) 

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

375 

376 def testConvert(self): 

377 year = 2012 

378 month = 7 

379 day = 19 

380 hour = 18 

381 minute = 29 

382 second = 33 

383 

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

385 dt = ts.toPython(DateTime.UTC) 

386 

387 self.assertEqual(dt.year, year) 

388 self.assertEqual(dt.month, month) 

389 self.assertEqual(dt.day, day) 

390 self.assertEqual(dt.hour, hour) 

391 self.assertEqual(dt.minute, minute) 

392 self.assertEqual(dt.second, second) 

393 

394 def testPickle(self): 

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

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

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

398 

399 def testToAstropy(self): 

400 """Test that DateTime converstion to astropy is exact to within machine 

401 precision. 

402 """ 

403 date = DateTime(0) 

404 astropyTime = date.toAstropy() 

405 self.assertFloatsAlmostEqual(date.get(), astropyTime.to_value(format="mjd")) 

406 

407 date = DateTime(45205.125, DateTime.MJD, DateTime.UTC) 

408 astropyTime = date.toAstropy() 

409 self.assertFloatsAlmostEqual(date.get(), astropyTime.to_value(format="mjd")) 

410 

411 date = DateTime(2023.123456789012, DateTime.EPOCH) 

412 astropyTime = date.toAstropy() 

413 self.assertFloatsAlmostEqual(date.get(), astropyTime.to_value(format="mjd")) 

414 

415 date = DateTime() 

416 with self.assertRaisesRegex(RuntimeError, "DateTime not valid"): 

417 date.toAstropy() 

418 

419 

420class TimeZoneBaseTestCase(DateTimeTestCase): 

421 timezone = "" 

422 

423 def setUp(self): 

424 DateTimeTestCase.setUp(self) 

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

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

427 

428 def tearDown(self): 

429 if self.tz == "": 

430 del os.environ['TZ'] 

431 else: 

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

433 

434 

435class BritishTimeTestCase(TimeZoneBaseTestCase): 

436 timezone = "Europe/London" 

437 

438 

439class BritishTime2TestCase(TimeZoneBaseTestCase): 

440 timezone = "GMT0BST" 

441 

442 

443class PacificTimeTestCase(TimeZoneBaseTestCase): 

444 timezone = "PST8PDT" 

445 

446 

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

448 unittest.main()