Coverage for tests/test_exprParserYacc.py: 9%
380 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-10-02 08:00 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-10-02 08:00 +0000
1# This file is part of daf_butler.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This software is dual licensed under the GNU General Public License and also
10# under a 3-clause BSD license. Recipients may choose which of these licenses
11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
12# respectively. If you choose the GPL option then the following text applies
13# (but note that there is still no warranty even if you opt for BSD instead):
14#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU General Public License as published by
17# the Free Software Foundation, either version 3 of the License, or
18# (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU General Public License for more details.
24#
25# You should have received a copy of the GNU General Public License
26# along with this program. If not, see <https://www.gnu.org/licenses/>.
28"""Simple unit test for exprParser subpackage module.
29"""
31import unittest
33import astropy.time
34from lsst.daf.butler.registry.queries.expressions.parser import ParseError, ParserYacc, TreeVisitor, exprTree
35from lsst.daf.butler.registry.queries.expressions.parser.parserYacc import _parseTimeString
38class _Visitor(TreeVisitor):
39 """Trivial implementation of TreeVisitor."""
41 def visitNumericLiteral(self, value, node):
42 return f"N({value})"
44 def visitStringLiteral(self, value, node):
45 return f"S({value})"
47 def visitTimeLiteral(self, value, node):
48 return f"T({value})"
50 def visitRangeLiteral(self, start, stop, stride, node):
51 if stride is None:
52 return f"R({start}..{stop})"
53 else:
54 return f"R({start}..{stop}:{stride})"
56 def visitIdentifier(self, name, node):
57 return f"ID({name})"
59 def visitUnaryOp(self, operator, operand, node):
60 return f"U({operator} {operand})"
62 def visitBinaryOp(self, operator, lhs, rhs, node):
63 return f"B({lhs} {operator} {rhs})"
65 def visitIsIn(self, lhs, values, not_in, node):
66 values = ", ".join([str(val) for val in values])
67 if not_in:
68 return f"!IN({lhs} ({values}))"
69 else:
70 return f"IN({lhs} ({values}))"
72 def visitParens(self, expression, node):
73 return f"P({expression})"
76class ParserLexTestCase(unittest.TestCase):
77 """A test case for ParserYacc"""
79 def setUp(self):
80 pass
82 def tearDown(self):
83 pass
85 def testInstantiate(self):
86 """Tests for making ParserLex instances"""
87 parser = ParserYacc() # noqa: F841
89 def testEmpty(self):
90 """Tests for empty expression"""
91 parser = ParserYacc()
93 # empty expression is allowed, returns None
94 tree = parser.parse("")
95 self.assertIsNone(tree)
97 def testParseLiteral(self):
98 """Tests for literals (strings/numbers)"""
99 parser = ParserYacc()
101 tree = parser.parse("1")
102 self.assertIsInstance(tree, exprTree.NumericLiteral)
103 self.assertEqual(tree.value, "1")
105 tree = parser.parse(".5e-2")
106 self.assertIsInstance(tree, exprTree.NumericLiteral)
107 self.assertEqual(tree.value, ".5e-2")
109 tree = parser.parse("'string'")
110 self.assertIsInstance(tree, exprTree.StringLiteral)
111 self.assertEqual(tree.value, "string")
113 tree = parser.parse("10..20")
114 self.assertIsInstance(tree, exprTree.RangeLiteral)
115 self.assertEqual(tree.start, 10)
116 self.assertEqual(tree.stop, 20)
117 self.assertEqual(tree.stride, None)
119 tree = parser.parse("-10 .. 10:5")
120 self.assertIsInstance(tree, exprTree.RangeLiteral)
121 self.assertEqual(tree.start, -10)
122 self.assertEqual(tree.stop, 10)
123 self.assertEqual(tree.stride, 5)
125 # more extensive tests of time parsing is below
126 tree = parser.parse("T'51544.0'")
127 self.assertIsInstance(tree, exprTree.TimeLiteral)
128 self.assertEqual(tree.value, astropy.time.Time(51544.0, format="mjd", scale="tai"))
130 tree = parser.parse("T'2020-03-30T12:20:33'")
131 self.assertIsInstance(tree, exprTree.TimeLiteral)
132 self.assertEqual(tree.value, astropy.time.Time("2020-03-30T12:20:33", format="isot", scale="utc"))
134 def testParseIdentifiers(self):
135 """Tests for identifiers"""
136 parser = ParserYacc()
138 tree = parser.parse("a")
139 self.assertIsInstance(tree, exprTree.Identifier)
140 self.assertEqual(tree.name, "a")
142 tree = parser.parse("a.b")
143 self.assertIsInstance(tree, exprTree.Identifier)
144 self.assertEqual(tree.name, "a.b")
146 def testParseParens(self):
147 """Tests for identifiers"""
148 parser = ParserYacc()
150 tree = parser.parse("(a)")
151 self.assertIsInstance(tree, exprTree.Parens)
152 self.assertIsInstance(tree.expr, exprTree.Identifier)
153 self.assertEqual(tree.expr.name, "a")
155 def testUnaryOps(self):
156 """Tests for unary plus and minus"""
157 parser = ParserYacc()
159 tree = parser.parse("+a")
160 self.assertIsInstance(tree, exprTree.UnaryOp)
161 self.assertEqual(tree.op, "+")
162 self.assertIsInstance(tree.operand, exprTree.Identifier)
163 self.assertEqual(tree.operand.name, "a")
165 tree = parser.parse("- x.y")
166 self.assertIsInstance(tree, exprTree.UnaryOp)
167 self.assertEqual(tree.op, "-")
168 self.assertIsInstance(tree.operand, exprTree.Identifier)
169 self.assertEqual(tree.operand.name, "x.y")
171 def testBinaryOps(self):
172 """Tests for binary operators"""
173 parser = ParserYacc()
175 tree = parser.parse("a + b")
176 self.assertIsInstance(tree, exprTree.BinaryOp)
177 self.assertEqual(tree.op, "+")
178 self.assertIsInstance(tree.lhs, exprTree.Identifier)
179 self.assertIsInstance(tree.rhs, exprTree.Identifier)
180 self.assertEqual(tree.lhs.name, "a")
181 self.assertEqual(tree.rhs.name, "b")
183 tree = parser.parse("a - 2")
184 self.assertIsInstance(tree, exprTree.BinaryOp)
185 self.assertEqual(tree.op, "-")
186 self.assertIsInstance(tree.lhs, exprTree.Identifier)
187 self.assertIsInstance(tree.rhs, exprTree.NumericLiteral)
188 self.assertEqual(tree.lhs.name, "a")
189 self.assertEqual(tree.rhs.value, "2")
191 tree = parser.parse("2 * 2")
192 self.assertIsInstance(tree, exprTree.BinaryOp)
193 self.assertEqual(tree.op, "*")
194 self.assertIsInstance(tree.lhs, exprTree.NumericLiteral)
195 self.assertIsInstance(tree.rhs, exprTree.NumericLiteral)
196 self.assertEqual(tree.lhs.value, "2")
197 self.assertEqual(tree.rhs.value, "2")
199 tree = parser.parse("1.e5/2")
200 self.assertIsInstance(tree, exprTree.BinaryOp)
201 self.assertEqual(tree.op, "/")
202 self.assertIsInstance(tree.lhs, exprTree.NumericLiteral)
203 self.assertIsInstance(tree.rhs, exprTree.NumericLiteral)
204 self.assertEqual(tree.lhs.value, "1.e5")
205 self.assertEqual(tree.rhs.value, "2")
207 tree = parser.parse("333%76")
208 self.assertIsInstance(tree, exprTree.BinaryOp)
209 self.assertEqual(tree.op, "%")
210 self.assertIsInstance(tree.lhs, exprTree.NumericLiteral)
211 self.assertIsInstance(tree.rhs, exprTree.NumericLiteral)
212 self.assertEqual(tree.lhs.value, "333")
213 self.assertEqual(tree.rhs.value, "76")
215 # tests for overlaps operator
216 tree = parser.parse("region1 OVERLAPS region2")
217 self.assertIsInstance(tree, exprTree.BinaryOp)
218 self.assertEqual(tree.op, "OVERLAPS")
219 self.assertIsInstance(tree.lhs, exprTree.Identifier)
220 self.assertIsInstance(tree.rhs, exprTree.Identifier)
222 # time ranges with literals
223 tree = parser.parse("(T'2020-01-01', T'2020-01-02') overlaps (T'2020-01-01', T'2020-01-02')")
224 self.assertIsInstance(tree, exprTree.BinaryOp)
225 self.assertEqual(tree.op, "OVERLAPS")
226 self.assertIsInstance(tree.lhs, exprTree.TupleNode)
227 self.assertIsInstance(tree.rhs, exprTree.TupleNode)
229 # but syntax allows anything, it's visitor responsibility to decide
230 # what are the right operands
231 tree = parser.parse("x+y Overlaps function(x-y)")
232 self.assertIsInstance(tree, exprTree.BinaryOp)
233 self.assertEqual(tree.op, "OVERLAPS")
234 self.assertIsInstance(tree.lhs, exprTree.BinaryOp)
235 self.assertIsInstance(tree.rhs, exprTree.FunctionCall)
237 def testIsIn(self):
238 """Tests for IN"""
239 parser = ParserYacc()
241 tree = parser.parse("a in (1,2,'X')")
242 self.assertIsInstance(tree, exprTree.IsIn)
243 self.assertFalse(tree.not_in)
244 self.assertIsInstance(tree.lhs, exprTree.Identifier)
245 self.assertEqual(tree.lhs.name, "a")
246 self.assertIsInstance(tree.values, list)
247 self.assertEqual(len(tree.values), 3)
248 self.assertIsInstance(tree.values[0], exprTree.NumericLiteral)
249 self.assertEqual(tree.values[0].value, "1")
250 self.assertIsInstance(tree.values[1], exprTree.NumericLiteral)
251 self.assertEqual(tree.values[1].value, "2")
252 self.assertIsInstance(tree.values[2], exprTree.StringLiteral)
253 self.assertEqual(tree.values[2].value, "X")
255 tree = parser.parse("10 not in (1000, 2000..3000:100)")
256 self.assertIsInstance(tree, exprTree.IsIn)
257 self.assertTrue(tree.not_in)
258 self.assertIsInstance(tree.lhs, exprTree.NumericLiteral)
259 self.assertEqual(tree.lhs.value, "10")
260 self.assertIsInstance(tree.values, list)
261 self.assertEqual(len(tree.values), 2)
262 self.assertIsInstance(tree.values[0], exprTree.NumericLiteral)
263 self.assertEqual(tree.values[0].value, "1000")
264 self.assertIsInstance(tree.values[1], exprTree.RangeLiteral)
265 self.assertEqual(tree.values[1].start, 2000)
266 self.assertEqual(tree.values[1].stop, 3000)
267 self.assertEqual(tree.values[1].stride, 100)
269 tree = parser.parse("10 in (-1000, -2000)")
270 self.assertIsInstance(tree, exprTree.IsIn)
271 self.assertFalse(tree.not_in)
272 self.assertIsInstance(tree.lhs, exprTree.NumericLiteral)
273 self.assertEqual(tree.lhs.value, "10")
274 self.assertIsInstance(tree.values, list)
275 self.assertEqual(len(tree.values), 2)
276 self.assertIsInstance(tree.values[0], exprTree.NumericLiteral)
277 self.assertEqual(tree.values[0].value, "-1000")
278 self.assertIsInstance(tree.values[1], exprTree.NumericLiteral)
279 self.assertEqual(tree.values[1].value, "-2000")
281 # test for time contained in time range, all literals
282 tree = parser.parse("T'2020-01-01' in (T'2020-01-01', T'2020-01-02')")
283 self.assertIsInstance(tree, exprTree.IsIn)
284 self.assertFalse(tree.not_in)
285 self.assertIsInstance(tree.lhs, exprTree.TimeLiteral)
286 self.assertEqual(len(tree.values), 2)
287 self.assertIsInstance(tree.values[0], exprTree.TimeLiteral)
288 self.assertIsInstance(tree.values[1], exprTree.TimeLiteral)
290 # test for time range contained in time range
291 tree = parser.parse("(T'2020-01-01', t1) in (T'2020-01-01', t2)")
292 self.assertIsInstance(tree, exprTree.IsIn)
293 self.assertFalse(tree.not_in)
294 self.assertIsInstance(tree.lhs, exprTree.TupleNode)
295 self.assertEqual(len(tree.values), 2)
296 self.assertIsInstance(tree.values[0], exprTree.TimeLiteral)
297 self.assertIsInstance(tree.values[1], exprTree.Identifier)
299 # test for point in region (we don't have region syntax yet, use
300 # identifier)
301 tree = parser.parse("point(1, 2) in (region1)")
302 self.assertIsInstance(tree, exprTree.IsIn)
303 self.assertFalse(tree.not_in)
304 self.assertIsInstance(tree.lhs, exprTree.PointNode)
305 self.assertEqual(len(tree.values), 1)
306 self.assertIsInstance(tree.values[0], exprTree.Identifier)
308 # parens on right hand side are required
309 with self.assertRaises(ParseError):
310 parser.parse("point(1, 2) in region1")
312 # and we don't support full expressions in RHS list
313 with self.assertRaises(ParseError):
314 parser.parse("point(1, 2) in (x + y)")
316 def testCompareOps(self):
317 """Tests for comparison operators"""
318 parser = ParserYacc()
320 for op in ("=", "!=", "<", "<=", ">", ">="):
321 tree = parser.parse(f"a {op} 10")
322 self.assertIsInstance(tree, exprTree.BinaryOp)
323 self.assertEqual(tree.op, op)
324 self.assertIsInstance(tree.lhs, exprTree.Identifier)
325 self.assertIsInstance(tree.rhs, exprTree.NumericLiteral)
326 self.assertEqual(tree.lhs.name, "a")
327 self.assertEqual(tree.rhs.value, "10")
329 def testBoolOps(self):
330 """Tests for boolean operators"""
331 parser = ParserYacc()
333 for op in ("OR", "AND"):
334 tree = parser.parse(f"a {op} b")
335 self.assertIsInstance(tree, exprTree.BinaryOp)
336 self.assertEqual(tree.op, op)
337 self.assertIsInstance(tree.lhs, exprTree.Identifier)
338 self.assertIsInstance(tree.rhs, exprTree.Identifier)
339 self.assertEqual(tree.lhs.name, "a")
340 self.assertEqual(tree.rhs.name, "b")
342 tree = parser.parse("NOT b")
343 self.assertIsInstance(tree, exprTree.UnaryOp)
344 self.assertEqual(tree.op, "NOT")
345 self.assertIsInstance(tree.operand, exprTree.Identifier)
346 self.assertEqual(tree.operand.name, "b")
348 def testFunctionCall(self):
349 """Tests for function calls"""
350 parser = ParserYacc()
352 tree = parser.parse("f()")
353 self.assertIsInstance(tree, exprTree.FunctionCall)
354 self.assertEqual(tree.name, "f")
355 self.assertEqual(tree.args, [])
357 tree = parser.parse("f1(a)")
358 self.assertIsInstance(tree, exprTree.FunctionCall)
359 self.assertEqual(tree.name, "f1")
360 self.assertEqual(len(tree.args), 1)
361 self.assertIsInstance(tree.args[0], exprTree.Identifier)
362 self.assertEqual(tree.args[0].name, "a")
364 tree = parser.parse("anything_goes('a', x+y, ((a AND b) or (C = D)), NOT T < 42., Z IN (1,2,3,4))")
365 self.assertIsInstance(tree, exprTree.FunctionCall)
366 self.assertEqual(tree.name, "anything_goes")
367 self.assertEqual(len(tree.args), 5)
368 self.assertIsInstance(tree.args[0], exprTree.StringLiteral)
369 self.assertIsInstance(tree.args[1], exprTree.BinaryOp)
370 self.assertIsInstance(tree.args[2], exprTree.Parens)
371 self.assertIsInstance(tree.args[3], exprTree.UnaryOp)
372 self.assertIsInstance(tree.args[4], exprTree.IsIn)
374 with self.assertRaises(ParseError):
375 parser.parse("f.ff()")
377 def testPointNode(self):
378 """Tests for POINT() function"""
379 parser = ParserYacc()
381 # POINT function makes special node type
382 tree = parser.parse("POINT(Object.ra, 0.0)")
383 self.assertIsInstance(tree, exprTree.PointNode)
384 self.assertIsInstance(tree.ra, exprTree.Identifier)
385 self.assertEqual(tree.ra.name, "Object.ra")
386 self.assertIsInstance(tree.dec, exprTree.NumericLiteral)
387 self.assertEqual(tree.dec.value, "0.0")
389 # it is not case sensitive
390 tree = parser.parse("Point(1, 1)")
391 self.assertIsInstance(tree, exprTree.PointNode)
393 def testTupleNode(self):
394 """Tests for tuple"""
395 parser = ParserYacc()
397 # test with simple identifier and literal
398 tree = parser.parse("(Object.ra, 0.0)")
399 self.assertIsInstance(tree, exprTree.TupleNode)
400 self.assertEqual(len(tree.items), 2)
401 self.assertIsInstance(tree.items[0], exprTree.Identifier)
402 self.assertEqual(tree.items[0].name, "Object.ra")
403 self.assertIsInstance(tree.items[1], exprTree.NumericLiteral)
404 self.assertEqual(tree.items[1].value, "0.0")
406 # any expression can appear in tuple
407 tree = parser.parse("(x+y, ((a AND b) or (C = D)))")
408 self.assertIsInstance(tree, exprTree.TupleNode)
409 self.assertEqual(len(tree.items), 2)
410 self.assertIsInstance(tree.items[0], exprTree.BinaryOp)
411 self.assertIsInstance(tree.items[1], exprTree.Parens)
413 # only two items can appear in a tuple
414 with self.assertRaises(ParseError):
415 parser.parse("(1, 2, 3)")
417 def testExpression(self):
418 """Test for more or less complete expression"""
419 parser = ParserYacc()
421 expression = (
422 "((instrument='HSC' AND detector != 9) OR instrument='CFHT') "
423 "AND tract=8766 AND patch.cell_x > 5 AND "
424 "patch.cell_y < 4 AND band='i'"
425 )
427 tree = parser.parse(expression)
428 self.assertIsInstance(tree, exprTree.BinaryOp)
429 self.assertEqual(tree.op, "AND")
430 self.assertIsInstance(tree.lhs, exprTree.BinaryOp)
431 # AND is left-associative, so rhs operand will be the
432 # last sub-expressions
433 self.assertIsInstance(tree.rhs, exprTree.BinaryOp)
434 self.assertEqual(tree.rhs.op, "=")
435 self.assertIsInstance(tree.rhs.lhs, exprTree.Identifier)
436 self.assertEqual(tree.rhs.lhs.name, "band")
437 self.assertIsInstance(tree.rhs.rhs, exprTree.StringLiteral)
438 self.assertEqual(tree.rhs.rhs.value, "i")
440 def testSubstitution(self):
441 """Test for identifier substitution"""
442 # substitution is not recursive, so we can swap id2/id3
443 idMap = {
444 "id1": exprTree.StringLiteral("id1 value"),
445 "id2": exprTree.Identifier("id3"),
446 "id3": exprTree.Identifier("id2"),
447 "POINT": exprTree.StringLiteral("not used"),
448 "OR": exprTree.StringLiteral("not used"),
449 }
450 parser = ParserYacc(idMap=idMap)
452 expression = "id1 = 'v'"
453 tree = parser.parse(expression)
454 self.assertIsInstance(tree, exprTree.BinaryOp)
455 self.assertEqual(tree.op, "=")
456 self.assertIsInstance(tree.lhs, exprTree.StringLiteral)
457 self.assertEqual(tree.lhs.value, "id1 value")
459 expression = "id2 - id3"
460 tree = parser.parse(expression)
461 self.assertIsInstance(tree, exprTree.BinaryOp)
462 self.assertEqual(tree.op, "-")
463 self.assertIsInstance(tree.lhs, exprTree.Identifier)
464 self.assertEqual(tree.lhs.name, "id3")
465 self.assertIsInstance(tree.rhs, exprTree.Identifier)
466 self.assertEqual(tree.rhs.name, "id2")
468 # reserved words are not substituted
469 expression = "id2 OR id3"
470 tree = parser.parse(expression)
471 self.assertIsInstance(tree, exprTree.BinaryOp)
472 self.assertEqual(tree.op, "OR")
474 # function names are not substituted
475 expression = "POINT(1, 2)"
476 tree = parser.parse(expression)
477 self.assertIsInstance(tree, exprTree.PointNode)
479 def testException(self):
480 """Test for exceptional cases"""
482 def _assertExc(exc, expr, token, pos, lineno, posInLine):
483 """Check exception attribute values"""
484 self.assertEqual(exc.expression, expr)
485 self.assertEqual(exc.token, token)
486 self.assertEqual(exc.pos, pos)
487 self.assertEqual(exc.lineno, lineno)
488 self.assertEqual(exc.posInLine, posInLine)
490 parser = ParserYacc()
492 expression = "(1, 2, 3)"
493 with self.assertRaises(ParseError) as catcher:
494 parser.parse(expression)
495 _assertExc(catcher.exception, expression, ",", 5, 1, 5)
497 expression = "\n(1\n,\n 2, 3)"
498 with self.assertRaises(ParseError) as catcher:
499 parser.parse(expression)
500 _assertExc(catcher.exception, expression, ",", 8, 4, 2)
502 expression = "T'not-a-time'"
503 with self.assertRaises(ParseError) as catcher:
504 parser.parse(expression)
505 _assertExc(catcher.exception, expression, "not-a-time", 0, 1, 0)
507 def testStr(self):
508 """Test for formatting"""
509 parser = ParserYacc()
511 tree = parser.parse("(a+b)")
512 self.assertEqual(str(tree), "(a + b)")
514 tree = parser.parse("1 in (1,'x',3)")
515 self.assertEqual(str(tree), "1 IN (1, 'x', 3)")
517 tree = parser.parse("a not in (1,'x',3)")
518 self.assertEqual(str(tree), "a NOT IN (1, 'x', 3)")
520 tree = parser.parse("(A or B) And NoT (x+3 > y)")
521 self.assertEqual(str(tree), "(A OR B) AND NOT (x + 3 > y)")
523 tree = parser.parse("A in (100, 200..300:50)")
524 self.assertEqual(str(tree), "A IN (100, 200..300:50)")
526 def testVisit(self):
527 """Test for visitor methods"""
528 # test should cover all visit* methods
529 parser = ParserYacc()
530 visitor = _Visitor()
532 tree = parser.parse("(a+b)")
533 result = tree.visit(visitor)
534 self.assertEqual(result, "P(B(ID(a) + ID(b)))")
536 tree = parser.parse("(A or B) and not (x + 3 > y)")
537 result = tree.visit(visitor)
538 self.assertEqual(result, "B(P(B(ID(A) OR ID(B))) AND U(NOT P(B(B(ID(x) + N(3)) > ID(y)))))")
540 tree = parser.parse("x in (1,2) AND y NOT IN (1.1, .25, 1e2) OR z in ('a', 'b')")
541 result = tree.visit(visitor)
542 self.assertEqual(
543 result,
544 "B(B(IN(ID(x) (N(1), N(2))) AND !IN(ID(y) (N(1.1), N(.25), N(1e2)))) OR IN(ID(z) (S(a), S(b))))",
545 )
547 tree = parser.parse("x in (1,2,5..15) AND y NOT IN (-100..100:10)")
548 result = tree.visit(visitor)
549 self.assertEqual(result, "B(IN(ID(x) (N(1), N(2), R(5..15))) AND !IN(ID(y) (R(-100..100:10))))")
551 tree = parser.parse("time > T'2020-03-30'")
552 result = tree.visit(visitor)
553 self.assertEqual(result, "B(ID(time) > T(2020-03-30 00:00:00.000))")
555 def testParseTimeStr(self):
556 """Test for _parseTimeString method"""
557 # few expected failures
558 bad_times = [
559 "",
560 " ",
561 "123.456e10", # no exponents
562 "mjd-dj/123.456", # format can only have word chars
563 "123.456/mai-tai", # scale can only have word chars
564 "2020-03-01 00", # iso needs minutes if hour is given
565 "2020-03-01T", # isot needs hour:minute
566 "2020:100:12", # yday needs minutes if hour is given
567 "format/123456.00", # unknown format
568 "123456.00/unscale", # unknown scale
569 ]
570 for bad_time in bad_times:
571 with self.assertRaises(ValueError):
572 _parseTimeString(bad_time)
574 # each tuple is (string, value, format, scale)
575 tests = [
576 ("51544.0", 51544.0, "mjd", "tai"),
577 ("mjd/51544.0", 51544.0, "mjd", "tai"),
578 ("51544.0/tai", 51544.0, "mjd", "tai"),
579 ("mjd/51544.0/tai", 51544.0, "mjd", "tai"),
580 ("MJd/51544.0/TAi", 51544.0, "mjd", "tai"),
581 ("jd/2451544.5", 2451544.5, "jd", "tai"),
582 ("jd/2451544.5", 2451544.5, "jd", "tai"),
583 ("51544.0/utc", 51544.0, "mjd", "utc"),
584 ("unix/946684800.0", 946684800.0, "unix", "utc"),
585 ("cxcsec/63072064.184", 63072064.184, "cxcsec", "tt"),
586 ("2020-03-30", "2020-03-30 00:00:00.000", "iso", "utc"),
587 ("2020-03-30 12:20", "2020-03-30 12:20:00.000", "iso", "utc"),
588 ("2020-03-30 12:20:33.456789", "2020-03-30 12:20:33.457", "iso", "utc"),
589 ("2020-03-30T12:20", "2020-03-30T12:20:00.000", "isot", "utc"),
590 ("2020-03-30T12:20:33.456789", "2020-03-30T12:20:33.457", "isot", "utc"),
591 ("isot/2020-03-30", "2020-03-30T00:00:00.000", "isot", "utc"),
592 ("2020-03-30/tai", "2020-03-30 00:00:00.000", "iso", "tai"),
593 ("+02020-03-30", "2020-03-30T00:00:00.000", "fits", "utc"),
594 ("+02020-03-30T12:20:33", "2020-03-30T12:20:33.000", "fits", "utc"),
595 ("+02020-03-30T12:20:33.456789", "2020-03-30T12:20:33.457", "fits", "utc"),
596 ("fits/2020-03-30", "2020-03-30T00:00:00.000", "fits", "utc"),
597 ("2020:123", "2020:123:00:00:00.000", "yday", "utc"),
598 ("2020:123:12:20", "2020:123:12:20:00.000", "yday", "utc"),
599 ("2020:123:12:20:33.456789", "2020:123:12:20:33.457", "yday", "utc"),
600 ("yday/2020:123:12:20/tai", "2020:123:12:20:00.000", "yday", "tai"),
601 ]
602 for time_str, value, fmt, scale in tests:
603 time = _parseTimeString(time_str)
604 self.assertEqual(time.value, value)
605 self.assertEqual(time.format, fmt)
606 self.assertEqual(time.scale, scale)
609if __name__ == "__main__":
610 unittest.main()