Coverage for tests / test_dateTime.py: 14%
332 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:35 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-14 23:35 +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#
24import os
25import pickle
26import time
27import unittest
29import astropy.time
31from lsst.daf.base import DateTime
32import lsst.pex.exceptions as pexExcept
33import lsst.utils.tests
36class DateTimeTestCase(lsst.utils.tests.TestCase):
37 """Base class for tests of DateTime objects, without any timezone.
38 """
40 def setUp(self):
41 self.timeScales = (DateTime.TAI, DateTime.TT, DateTime.UTC)
42 self.dateSystems = (DateTime.JD, DateTime.MJD, DateTime.EPOCH)
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())
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)
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)
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)
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)
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())
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())
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)
115 def testSorting(self):
116 invalid = DateTime()
117 old = DateTime(1192755506000000000, DateTime.TAI)
118 now = DateTime.now()
119 ordered = [invalid, old, now]
121 self.assertEqual(sorted([now, invalid, old]), ordered)
122 self.assertEqual(sorted([old, now, invalid]), ordered)
124 def testCompare(self):
125 old = DateTime(1192755506000000000, DateTime.TAI)
126 now = DateTime.now()
127 self.assertEqual(now, now)
128 self.assertGreater(now, old)
129 self.assertGreaterEqual(now, old)
130 self.assertGreaterEqual(now, now)
131 self.assertLess(old, now)
132 self.assertLessEqual(old, now)
133 self.assertLessEqual(old, old)
135 def testIsoEpoch(self):
136 ts = DateTime("19700101T000000Z", DateTime.UTC)
137 self.assertEqual(ts.nsecs(DateTime.UTC), 0)
138 self.assertEqual(ts.toString(ts.UTC), "1970-01-01T00:00:00.000000000Z")
140 def testIsoUTCBasic(self):
141 """Test basic ISO string input and output of UTC dates
142 """
143 for dateSep in ("-", ""): # "-" date separator is optional
144 for timeSep in (":", ""): # ":" time separator is optional
145 for decPt in (".", ","): # "." or "," may be used as decimal point
146 dateStr = "2009{0}04{0}02T07{1}26{1}39{2}314159265Z".format(dateSep, timeSep, decPt)
147 ts = DateTime(dateStr, DateTime.UTC)
148 self.assertEqual(ts.nsecs(DateTime.TT), 1238657265498159265)
149 self.assertEqual(ts.nsecs(DateTime.TAI), 1238657233314159265)
150 self.assertEqual(ts.nsecs(DateTime.UTC), 1238657199314159265)
151 self.assertEqual(ts.toString(ts.UTC), "2009-04-02T07:26:39.314159265Z")
153 def testIsoNonUTCBasics(self):
154 """Test basic ISO string input and output of TAI and TT dates
155 """
156 for scale in (DateTime.TAI, DateTime.TT):
157 for dateSep in ("-", ""): # "-" date separator is optional
158 for timeSep in (":", ""): # ":" time separator is optional
159 for decPt in (".", ","): # "." or "," may be used as decimal point
160 dateStr = "2009{0}04{0}02T07{1}26{1}39{2}314159265".format(dateSep, timeSep, decPt)
161 ts = DateTime(dateStr, scale)
162 self.assertEqual(ts.toString(scale), "2009-04-02T07:26:39.314159265")
163 self.assertTrue(ts.isValid())
165 def testIsoExpanded(self):
166 ts = DateTime("2009-04-02T07:26:39.314159265Z", DateTime.UTC)
167 self.assertEqual(ts.nsecs(DateTime.TAI), 1238657233314159265)
168 self.assertEqual(ts.nsecs(DateTime.UTC), 1238657199314159265)
169 self.assertEqual(ts.toString(ts.UTC), "2009-04-02T07:26:39.314159265Z")
170 self.assertTrue(ts.isValid())
172 def testIsoNoNSecs(self):
173 ts = DateTime("2009-04-02T07:26:39Z", DateTime.UTC)
174 self.assertEqual(ts.nsecs(DateTime.TAI), 1238657233000000000)
175 self.assertEqual(ts.nsecs(DateTime.UTC), 1238657199000000000)
176 self.assertEqual(ts.toString(ts.UTC), "2009-04-02T07:26:39.000000000Z")
177 self.assertTrue(ts.isValid())
179 def testSOFA(self):
180 """The SOFA documentation includes an example conversion:
181 https://www.iausofa.org/2017_0420_C/sofa/sofa_ts_c.pdf
182 (page 8, section 2.4)
184 The value in those docs is only ~single precision, so I re-computed
185 it with pyerfa to get a more correct value.
186 """
187 with self.subTest("jd to jyear"):
188 self.assertEqual(DateTime(2457073.05631, DateTime.JD, DateTime.TAI).get(DateTime.EPOCH),
189 2015.1349933196439)
190 with self.subTest("jyear to jd"):
191 self.assertEqual(DateTime(2015.1349933196, DateTime.EPOCH, DateTime.TAI).get(DateTime.JD),
192 2457073.056309984)
194 def testAstropyComparison(self):
195 """Astropy's Time module is based on ERFA, providing a well verified
196 comparison point.
197 """
198 def check_times(dateTime, time):
199 with self.subTest("jyear"):
200 self.assertAlmostEqual(dateTime.get(DateTime.EPOCH), time.tai.jyear)
201 with self.subTest("mjd"):
202 self.assertEqual(dateTime.get(DateTime.MJD), time.tai.mjd)
203 with self.subTest("jd"):
204 self.assertEqual(dateTime.get(DateTime.JD), time.tai.jd)
206 # Unix epoch comparison
207 dateTime = DateTime("19700101T000000Z", DateTime.UTC)
208 time = astropy.time.Time("1970-01-01T00:00:00", format="isot", scale="utc")
209 check_times(dateTime, time)
211 # J2000 epoch comparison
212 dateTime = DateTime(2000.0, DateTime.EPOCH, DateTime.UTC)
213 time = astropy.time.Time(2000.0, format="jyear", scale="utc")
214 check_times(dateTime, time)
216 # random future MJD epoch comparison
217 dateTime = DateTime(65432.1, DateTime.MJD, DateTime.TAI)
218 time = astropy.time.Time(65432.1, format="mjd", scale="tai")
219 check_times(dateTime, time)
221 def testIsoThrow(self):
222 with self.assertRaises(pexExcept.DomainError):
223 DateTime("2009-04-01T23:36:05", DateTime.UTC) # Z time zone required for UTC
224 for scale in (DateTime.TAI, DateTime.TT):
225 with self.assertRaises(pexExcept.DomainError):
226 DateTime("2009-04-01T23:36:05Z", scale) # Z time zone forbidden for TAI or TT
228 for scale in self.timeScales:
229 with self.assertRaises(pexExcept.DomainError):
230 DateTime("20090401", scale) # time required
231 with self.assertRaises(pexExcept.DomainError):
232 DateTime("20090401T", DateTime.UTC) # time required
233 with self.assertRaises(pexExcept.DomainError):
234 DateTime("2009-04-01T", DateTime.UTC) # time required
235 with self.assertRaises(pexExcept.DomainError):
236 DateTime("2009-04-01T23:36:05-0700", DateTime.UTC) # time zone offset not supported
237 with self.assertRaises(pexExcept.DomainError):
238 DateTime("2009/04/01T23:36:05Z", DateTime.UTC) # "/" not valid
239 with self.assertRaises(pexExcept.DomainError):
240 DateTime("2009-04-01T23:36", DateTime.UTC) # partial time
241 with self.assertRaises(pexExcept.DomainError):
242 DateTime("2009-04", DateTime.UTC) # partial date without time
243 with self.assertRaises(pexExcept.DomainError):
244 DateTime("2009-04T23:36.05", DateTime.UTC) # partial date with time
245 with self.assertRaises(pexExcept.DomainError):
246 DateTime("09-04-01T23:36:05", DateTime.UTC) # 2 digit year
248 # earliest allowed UTC date is the earliest date in the leap second
249 # table
250 try:
251 minLeapSecUTC = "1961-01-01T00:00:00Z"
252 dt = DateTime(minLeapSecUTC, DateTime.UTC)
253 dt.toString(DateTime.UTC)
254 except Exception:
255 self.fail("minLeapSecUTC={} failed, but should be OK".format(minLeapSecUTC))
256 with self.assertRaises(pexExcept.DomainError):
257 DateTime("1960-01-01T23:59:59Z", DateTime.UTC) # just before leap second table starts
259 # earliest allowed date for TAI and TT is year = 1902
260 for timeSys in (DateTime.TAI, DateTime.TT):
261 try:
262 earliestDate = "1902-01-01T00:00:00"
263 dt = DateTime(earliestDate, timeSys)
264 dt.toString(DateTime.TAI)
265 dt.toString(DateTime.TT)
266 except Exception:
267 self.fail("{} system={} failed, but should be OK".format(earliestDate, timeSys))
269 # dates before the leap second table can be created using TAI or TT,
270 # but not viewed in UTC
271 earlyDt = DateTime("1960-01-01T00:00:00", DateTime.TAI)
272 with self.assertRaises(pexExcept.DomainError):
273 earlyDt.toString(DateTime.UTC)
275 with self.assertRaises(pexExcept.DomainError):
276 DateTime("1901-12-12T23:59:59Z", DateTime.TAI) # too early
277 with self.assertRaises(pexExcept.DomainError):
278 DateTime("1700-01-01T00:00:00Z", DateTime.TAI) # way too early
279 with self.assertRaises(pexExcept.DomainError):
280 DateTime("2262-01-01T00:00:00Z", DateTime.TAI) # too late
281 with self.assertRaises(pexExcept.DomainError):
282 DateTime("3200-01-01T00:00:00Z", DateTime.TAI) # way too late
284 def testWraparound(self):
285 """Test that a date later than 2038-01-19, 03:14:07 does not wrap
286 around.
288 This will fail on old versions of unix, and indicates that DateTime
289 is not safe.
290 """
291 dateStr = "2040-01-01T00:00:00.000000000"
292 self.assertEqual(str(DateTime(dateStr, DateTime.TAI)), "DateTime(\"{}\", TAI)".format(dateStr))
294 def testDM7622(self):
295 """Test DM-7622: date with unix time = -1 seconds must be usable
297 Note that the call in question parses the ISO string without paying
298 attention to the scale (it applies the scale later),
299 so the same ISO string is wanted in all cases
300 (except with a trailing Z for UTC, and without for TAI and TT)
301 """
302 negOneSecIso = "1969-12-31T23:59:59.000000000"
303 for scale in self.timeScales:
304 dateStr = negOneSecIso + ("Z" if scale == DateTime.UTC else "")
305 try:
306 dt = DateTime(dateStr, scale)
307 except Exception:
308 self.fail("Date {} in scale {} unusable".format(dateStr, scale))
309 self.assertEqual(dt.nsecs(scale), int(-1e9))
311 def testStr(self):
312 timeStr1 = "2004-03-01T12:39:45.1"
313 fullTimeStr1 = "2004-03-01T12:39:45.100000000"
314 dt1 = DateTime(timeStr1, DateTime.TAI)
315 self.assertEqual(str(dt1), "DateTime(\"{}\", TAI)".format(fullTimeStr1))
316 self.assertEqual(repr(dt1), "DateTime(\"{}\", TAI)".format(fullTimeStr1))
318 timeStr2 = "2004-03-01T12:39:45.000000001"
319 dt2 = DateTime(timeStr2, DateTime.TAI)
320 self.assertEqual(str(dt2), "DateTime(\"{}\", TAI)".format(timeStr2))
321 self.assertEqual(repr(dt2), "DateTime(\"{}\", TAI)".format(timeStr2))
323 def testNsecsTT(self):
324 ts = DateTime(1192755538184000000, DateTime.TT)
325 self.assertEqual(ts.nsecs(DateTime.UTC), 1192755473000000000)
326 self.assertEqual(ts.nsecs(DateTime.TAI), 1192755506000000000)
327 self.assertEqual(ts.nsecs(), 1192755506000000000)
328 self.assertAlmostEqual(ts.get(DateTime.MJD, DateTime.UTC), 54392.040196759262)
329 self.assertTrue(ts.isValid())
331 def testFracSecs(self):
332 ts = DateTime("2004-03-01T12:39:45.1Z", DateTime.UTC)
333 self.assertEqual(ts.toString(ts.UTC), '2004-03-01T12:39:45.100000000Z')
334 ts = DateTime("2004-03-01T12:39:45.01Z", DateTime.UTC)
335 self.assertEqual(ts.toString(ts.UTC), '2004-03-01T12:39:45.010000000Z')
336 ts = DateTime("2004-03-01T12:39:45.000000001Z", DateTime.UTC) # nanosecond
337 self.assertEqual(ts.toString(ts.UTC), '2004-03-01T12:39:45.000000001Z')
338 ts = DateTime("2004-03-01T12:39:45.0000000001Z", DateTime.UTC) # too small
339 self.assertEqual(ts.toString(ts.UTC), '2004-03-01T12:39:45.000000000Z')
341 def testInvalid(self):
342 ts = DateTime()
343 self.assertFalse(ts.isValid())
344 for scale in self.timeScales:
345 self.assertEqual(ts.nsecs(scale), DateTime.invalid_nsecs)
346 for system in self.dateSystems:
347 with self.assertRaises(RuntimeError):
348 ts.get(system, scale)
349 with self.assertRaises(RuntimeError):
350 ts.gmtime(scale)
351 with self.assertRaises(RuntimeError):
352 ts.timespec(scale)
353 with self.assertRaises(RuntimeError):
354 ts.timeval(scale)
355 with self.assertRaises(RuntimeError):
356 ts.toString(scale)
357 with self.assertRaises(RuntimeError):
358 ts.toPython()
359 self.assertEqual(repr(ts), "DateTime()")
361 def testNegative(self):
362 ts = DateTime("1969-03-01T00:00:32Z", DateTime.UTC)
363 self.assertEqual(ts.toString(ts.UTC), '1969-03-01T00:00:32.000000000Z')
364 ts = DateTime("1969-01-01T00:00:00Z", DateTime.UTC)
365 self.assertEqual(ts.toString(ts.UTC), '1969-01-01T00:00:00.000000000Z')
366 ts = DateTime("1969-01-01T00:00:40Z", DateTime.UTC)
367 self.assertEqual(ts.toString(ts.UTC), '1969-01-01T00:00:40.000000000Z')
368 ts = DateTime("1969-01-01T00:00:38Z", DateTime.UTC)
369 self.assertEqual(ts.toString(ts.UTC), '1969-01-01T00:00:38.000000000Z')
370 ts = DateTime("1969-03-01T12:39:45Z", DateTime.UTC)
371 self.assertEqual(ts.toString(ts.UTC), '1969-03-01T12:39:45.000000000Z')
372 ts = DateTime("1969-03-01T12:39:45.000000001Z", DateTime.UTC)
373 self.assertEqual(ts.toString(ts.UTC), '1969-03-01T12:39:45.000000001Z')
374 self.assertTrue(ts.isValid())
376 # Note slight inaccuracy in UTC-TAI-UTC round-trip
377 ts = DateTime("1969-03-01T12:39:45.12345Z", DateTime.UTC)
378 self.assertEqual(ts.toString(ts.UTC), '1969-03-01T12:39:45.123449996Z')
379 ts = DateTime("1969-03-01T12:39:45.123456Z", DateTime.UTC)
380 self.assertEqual(ts.toString(ts.UTC), '1969-03-01T12:39:45.123455996Z')
382 ts = DateTime(-1, DateTime.TAI)
383 self.assertEqual(ts.toString(ts.UTC), '1969-12-31T23:59:51.999918239Z')
384 ts = DateTime(0, DateTime.TAI)
385 self.assertEqual(ts.toString(ts.UTC), '1969-12-31T23:59:51.999918240Z')
386 ts = DateTime(1, DateTime.TAI)
387 self.assertEqual(ts.toString(ts.UTC), '1969-12-31T23:59:51.999918241Z')
389 ts = DateTime(-1, DateTime.UTC)
390 self.assertEqual(ts.toString(ts.UTC), '1969-12-31T23:59:59.999999999Z')
391 ts = DateTime(0, DateTime.UTC)
392 self.assertEqual(ts.toString(ts.UTC), '1970-01-01T00:00:00.000000000Z')
393 ts = DateTime(1, DateTime.UTC)
394 self.assertEqual(ts.toString(ts.UTC), '1970-01-01T00:00:00.000000001Z')
396 def testConvert(self):
397 year = 2012
398 month = 7
399 day = 19
400 hour = 18
401 minute = 29
402 second = 33
404 ts = DateTime(year, month, day, hour, minute, second, DateTime.UTC)
405 dt = ts.toPython(DateTime.UTC)
407 self.assertEqual(dt.year, year)
408 self.assertEqual(dt.month, month)
409 self.assertEqual(dt.day, day)
410 self.assertEqual(dt.hour, hour)
411 self.assertEqual(dt.minute, minute)
412 self.assertEqual(dt.second, second)
414 def testPickle(self):
415 ts = DateTime(int(1192755473000000000), DateTime.UTC)
416 nts = pickle.loads(pickle.dumps(ts))
417 self.assertEqual(nts.nsecs(DateTime.UTC), int(1192755473000000000))
419 def testToAstropy(self):
420 """Test that DateTime converstion to astropy is exact to within machine
421 precision.
422 """
423 date = DateTime(0)
424 astropyTime = date.toAstropy()
425 self.assertFloatsAlmostEqual(date.get(), astropyTime.to_value(format="mjd"))
427 date = DateTime(45205.125, DateTime.MJD, DateTime.UTC)
428 astropyTime = date.toAstropy()
429 self.assertFloatsAlmostEqual(date.get(), astropyTime.to_value(format="mjd"))
431 date = DateTime(2023.123456789012, DateTime.EPOCH)
432 astropyTime = date.toAstropy()
433 self.assertFloatsAlmostEqual(date.get(), astropyTime.to_value(format="mjd"))
435 date = DateTime()
436 with self.assertRaisesRegex(RuntimeError, "DateTime not valid"):
437 date.toAstropy()
440class TimeZoneBaseTestCase(DateTimeTestCase):
441 timezone = ""
443 def setUp(self):
444 DateTimeTestCase.setUp(self)
445 self.tz = os.environ.setdefault('TZ', "")
446 os.environ['TZ'] = self.timezone
448 def tearDown(self):
449 if self.tz == "":
450 del os.environ['TZ']
451 else:
452 os.environ['TZ'] = self.tz
455class BritishTimeTestCase(TimeZoneBaseTestCase):
456 timezone = "Europe/London"
459class BritishTime2TestCase(TimeZoneBaseTestCase):
460 timezone = "GMT0BST"
463class PacificTimeTestCase(TimeZoneBaseTestCase):
464 timezone = "PST8PDT"
467if __name__ == '__main__': 467 ↛ 468line 467 didn't jump to line 468 because the condition on line 467 was never true
468 unittest.main()