Coverage for tests/test_dateTime.py: 12%
315 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-22 03:07 -0800
« prev ^ index » next coverage.py v6.5.0, created at 2023-02-22 03:07 -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#
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 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")
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")
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())
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())
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())
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)
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)
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)
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)
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)
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)
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
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
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
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))
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)
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
264 def testWraparound(self):
265 """Test that a date later than 2038-01-19, 03:14:07 does not wrap
266 around.
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))
274 def testDM7622(self):
275 """Test DM-7622: date with unix time = -1 seconds must be usable
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))
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))
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))
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())
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')
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()")
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())
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')
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')
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')
376 def testConvert(self):
377 year = 2012
378 month = 7
379 day = 19
380 hour = 18
381 minute = 29
382 second = 33
384 ts = DateTime(year, month, day, hour, minute, second, DateTime.UTC)
385 dt = ts.toPython(DateTime.UTC)
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)
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))
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"))
407 date = DateTime(45205.125, DateTime.MJD, DateTime.UTC)
408 astropyTime = date.toAstropy()
409 self.assertFloatsAlmostEqual(date.get(), astropyTime.to_value(format="mjd"))
411 date = DateTime(2023.123456789012, DateTime.EPOCH)
412 astropyTime = date.toAstropy()
413 self.assertFloatsAlmostEqual(date.get(), astropyTime.to_value(format="mjd"))
415 date = DateTime()
416 with self.assertRaisesRegex(RuntimeError, "DateTime not valid"):
417 date.toAstropy()
420class TimeZoneBaseTestCase(DateTimeTestCase):
421 timezone = ""
423 def setUp(self):
424 DateTimeTestCase.setUp(self)
425 self.tz = os.environ.setdefault('TZ', "")
426 os.environ['TZ'] = self.timezone
428 def tearDown(self):
429 if self.tz == "":
430 del os.environ['TZ']
431 else:
432 os.environ['TZ'] = self.tz
435class BritishTimeTestCase(TimeZoneBaseTestCase):
436 timezone = "Europe/London"
439class BritishTime2TestCase(TimeZoneBaseTestCase):
440 timezone = "GMT0BST"
443class PacificTimeTestCase(TimeZoneBaseTestCase):
444 timezone = "PST8PDT"
447if __name__ == '__main__': 447 ↛ 448line 447 didn't jump to line 448, because the condition on line 447 was never true
448 unittest.main()