# -*- coding: utf-8 -*- # Copyright (c) 2021, Brandon Nielsen # All rights reserved. # # This software may be modified and distributed under the terms # of the BSD license. See the LICENSE file for details. import datetime import unittest from aniso8601 import compat from aniso8601.builders import ( DatetimeTuple, DateTuple, DurationTuple, IntervalTuple, Limit, TimeTuple, TimezoneTuple, ) from aniso8601.builders.python import ( FractionalComponent, PythonTimeBuilder, _cast_to_fractional_component, fractional_range_check, year_range_check, ) from aniso8601.exceptions import ( DayOutOfBoundsError, HoursOutOfBoundsError, ISOFormatError, LeapSecondError, MidnightBoundsError, MinutesOutOfBoundsError, MonthOutOfBoundsError, SecondsOutOfBoundsError, WeekOutOfBoundsError, YearOutOfBoundsError, ) from aniso8601.utcoffset import UTCOffset class TestPythonTimeBuilder_UtiltyFunctions(unittest.TestCase): def test_year_range_check(self): yearlimit = Limit( "Invalid year string.", 0000, 9999, YearOutOfBoundsError, "Year must be between 1..9999.", None, ) self.assertEqual(year_range_check("1", yearlimit), 1000) def test_fractional_range_check(self): limit = Limit( "Invalid string.", -1, 1, ValueError, "Value must be between -1..1.", None ) self.assertEqual(fractional_range_check(10, "1", limit), 1) self.assertEqual(fractional_range_check(10, "-1", limit), -1) self.assertEqual( fractional_range_check(10, "0.1", limit), FractionalComponent(0, 1) ) self.assertEqual( fractional_range_check(10, "-0.1", limit), FractionalComponent(-0, 1) ) with self.assertRaises(ValueError): fractional_range_check(10, "1.1", limit) with self.assertRaises(ValueError): fractional_range_check(10, "-1.1", limit) def test_cast_to_fractional_component(self): self.assertEqual( _cast_to_fractional_component(10, "1.1"), FractionalComponent(1, 1) ) self.assertEqual( _cast_to_fractional_component(10, "-1.1"), FractionalComponent(-1, 1) ) self.assertEqual( _cast_to_fractional_component(100, "1.1"), FractionalComponent(1, 10) ) self.assertEqual( _cast_to_fractional_component(100, "-1.1"), FractionalComponent(-1, 10) ) class TestPythonTimeBuilder(unittest.TestCase): def test_build_date(self): testtuples = ( ( { "YYYY": "2013", "MM": None, "DD": None, "Www": None, "D": None, "DDD": None, }, datetime.date(2013, 1, 1), ), ( { "YYYY": "0001", "MM": None, "DD": None, "Www": None, "D": None, "DDD": None, }, datetime.date(1, 1, 1), ), ( { "YYYY": "1900", "MM": None, "DD": None, "Www": None, "D": None, "DDD": None, }, datetime.date(1900, 1, 1), ), ( { "YYYY": "1981", "MM": "04", "DD": "05", "Www": None, "D": None, "DDD": None, }, datetime.date(1981, 4, 5), ), ( { "YYYY": "1981", "MM": "04", "DD": None, "Www": None, "D": None, "DDD": None, }, datetime.date(1981, 4, 1), ), ( { "YYYY": "1981", "MM": None, "DD": None, "Www": None, "D": None, "DDD": "095", }, datetime.date(1981, 4, 5), ), ( { "YYYY": "1981", "MM": None, "DD": None, "Www": None, "D": None, "DDD": "365", }, datetime.date(1981, 12, 31), ), ( { "YYYY": "1980", "MM": None, "DD": None, "Www": None, "D": None, "DDD": "366", }, datetime.date(1980, 12, 31), ), # Make sure we shift in zeros ( { "YYYY": "1", "MM": None, "DD": None, "Www": None, "D": None, "DDD": None, }, datetime.date(1000, 1, 1), ), ( { "YYYY": "12", "MM": None, "DD": None, "Www": None, "D": None, "DDD": None, }, datetime.date(1200, 1, 1), ), ( { "YYYY": "123", "MM": None, "DD": None, "Www": None, "D": None, "DDD": None, }, datetime.date(1230, 1, 1), ), ) for testtuple in testtuples: result = PythonTimeBuilder.build_date(**testtuple[0]) self.assertEqual(result, testtuple[1]) # Test weekday testtuples = ( ( { "YYYY": "2004", "MM": None, "DD": None, "Www": "53", "D": None, "DDD": None, }, datetime.date(2004, 12, 27), 0, ), ( { "YYYY": "2009", "MM": None, "DD": None, "Www": "01", "D": None, "DDD": None, }, datetime.date(2008, 12, 29), 0, ), ( { "YYYY": "2010", "MM": None, "DD": None, "Www": "01", "D": None, "DDD": None, }, datetime.date(2010, 1, 4), 0, ), ( { "YYYY": "2009", "MM": None, "DD": None, "Www": "53", "D": None, "DDD": None, }, datetime.date(2009, 12, 28), 0, ), ( { "YYYY": "2009", "MM": None, "DD": None, "Www": "01", "D": "1", "DDD": None, }, datetime.date(2008, 12, 29), 0, ), ( { "YYYY": "2009", "MM": None, "DD": None, "Www": "53", "D": "7", "DDD": None, }, datetime.date(2010, 1, 3), 6, ), ( { "YYYY": "2010", "MM": None, "DD": None, "Www": "01", "D": "1", "DDD": None, }, datetime.date(2010, 1, 4), 0, ), ( { "YYYY": "2004", "MM": None, "DD": None, "Www": "53", "D": "6", "DDD": None, }, datetime.date(2005, 1, 1), 5, ), ) for testtuple in testtuples: result = PythonTimeBuilder.build_date(**testtuple[0]) self.assertEqual(result, testtuple[1]) self.assertEqual(result.weekday(), testtuple[2]) def test_build_time(self): testtuples = ( ({}, datetime.time()), ({"hh": "12.5"}, datetime.time(hour=12, minute=30)), ( {"hh": "23.99999999997"}, datetime.time(hour=23, minute=59, second=59, microsecond=999999), ), ({"hh": "1", "mm": "23"}, datetime.time(hour=1, minute=23)), ( {"hh": "1", "mm": "23.4567"}, datetime.time(hour=1, minute=23, second=27, microsecond=402000), ), ( {"hh": "14", "mm": "43.999999997"}, datetime.time(hour=14, minute=43, second=59, microsecond=999999), ), ( {"hh": "1", "mm": "23", "ss": "45"}, datetime.time(hour=1, minute=23, second=45), ), ( {"hh": "23", "mm": "21", "ss": "28.512400"}, datetime.time(hour=23, minute=21, second=28, microsecond=512400), ), ( {"hh": "01", "mm": "03", "ss": "11.858714"}, datetime.time(hour=1, minute=3, second=11, microsecond=858714), ), ( {"hh": "14", "mm": "43", "ss": "59.9999997"}, datetime.time(hour=14, minute=43, second=59, microsecond=999999), ), ({"hh": "24"}, datetime.time(hour=0)), ({"hh": "24", "mm": "00"}, datetime.time(hour=0)), ({"hh": "24", "mm": "00", "ss": "00"}, datetime.time(hour=0)), ( {"tz": TimezoneTuple(False, None, "00", "00", "UTC")}, datetime.time(tzinfo=UTCOffset(name="UTC", minutes=0)), ), ( { "hh": "23", "mm": "21", "ss": "28.512400", "tz": TimezoneTuple(False, None, "00", "00", "+00:00"), }, datetime.time( hour=23, minute=21, second=28, microsecond=512400, tzinfo=UTCOffset(name="+00:00", minutes=0), ), ), ( { "hh": "1", "mm": "23", "tz": TimezoneTuple(False, None, "01", "00", "+1"), }, datetime.time( hour=1, minute=23, tzinfo=UTCOffset(name="+1", minutes=60) ), ), ( { "hh": "1", "mm": "23.4567", "tz": TimezoneTuple(True, None, "01", "00", "-1"), }, datetime.time( hour=1, minute=23, second=27, microsecond=402000, tzinfo=UTCOffset(name="-1", minutes=-60), ), ), ( { "hh": "23", "mm": "21", "ss": "28.512400", "tz": TimezoneTuple(False, None, "01", "30", "+1:30"), }, datetime.time( hour=23, minute=21, second=28, microsecond=512400, tzinfo=UTCOffset(name="+1:30", minutes=90), ), ), ( { "hh": "23", "mm": "21", "ss": "28.512400", "tz": TimezoneTuple(False, None, "11", "15", "+11:15"), }, datetime.time( hour=23, minute=21, second=28, microsecond=512400, tzinfo=UTCOffset(name="+11:15", minutes=675), ), ), ( { "hh": "23", "mm": "21", "ss": "28.512400", "tz": TimezoneTuple(False, None, "12", "34", "+12:34"), }, datetime.time( hour=23, minute=21, second=28, microsecond=512400, tzinfo=UTCOffset(name="+12:34", minutes=754), ), ), ( { "hh": "23", "mm": "21", "ss": "28.512400", "tz": TimezoneTuple(False, None, "00", "00", "UTC"), }, datetime.time( hour=23, minute=21, second=28, microsecond=512400, tzinfo=UTCOffset(name="UTC", minutes=0), ), ), # Make sure we truncate, not round # https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is # https://bitbucket.org/nielsenb/aniso8601/issues/21/sub-microsecond-precision-is-lost-when ( {"hh": "14.9999999999999999"}, datetime.time(hour=14, minute=59, second=59, microsecond=999999), ), ({"mm": "0.00000000999"}, datetime.time()), ({"mm": "0.0000000999"}, datetime.time(microsecond=5)), ({"ss": "0.0000001"}, datetime.time()), ({"ss": "2.0000048"}, datetime.time(second=2, microsecond=4)), ) for testtuple in testtuples: result = PythonTimeBuilder.build_time(**testtuple[0]) self.assertEqual(result, testtuple[1]) def test_build_datetime(self): testtuples = ( ( ( DateTuple("2019", "06", "05", None, None, None), TimeTuple("01", "03", "11.858714", None), ), datetime.datetime( 2019, 6, 5, hour=1, minute=3, second=11, microsecond=858714 ), ), ( ( DateTuple("1234", "02", "03", None, None, None), TimeTuple("23", "21", "28.512400", None), ), datetime.datetime( 1234, 2, 3, hour=23, minute=21, second=28, microsecond=512400 ), ), ( ( DateTuple("1981", "04", "05", None, None, None), TimeTuple( "23", "21", "28.512400", TimezoneTuple(False, None, "11", "15", "+11:15"), ), ), datetime.datetime( 1981, 4, 5, hour=23, minute=21, second=28, microsecond=512400, tzinfo=UTCOffset(name="+11:15", minutes=675), ), ), ) for testtuple in testtuples: result = PythonTimeBuilder.build_datetime(*testtuple[0]) self.assertEqual(result, testtuple[1]) def test_build_duration(self): testtuples = ( ( { "PnY": "1", "PnM": "2", "PnD": "3", "TnH": "4", "TnM": "54", "TnS": "6", }, datetime.timedelta(days=428, hours=4, minutes=54, seconds=6), ), ( { "PnY": "1", "PnM": "2", "PnD": "3", "TnH": "4", "TnM": "54", "TnS": "6.5", }, datetime.timedelta(days=428, hours=4, minutes=54, seconds=6.5), ), ({"PnY": "1", "PnM": "2", "PnD": "3"}, datetime.timedelta(days=428)), ({"PnY": "1", "PnM": "2", "PnD": "3.5"}, datetime.timedelta(days=428.5)), ( {"TnH": "4", "TnM": "54", "TnS": "6.5"}, datetime.timedelta(hours=4, minutes=54, seconds=6.5), ), ( {"TnH": "1", "TnM": "3", "TnS": "11.858714"}, datetime.timedelta(hours=1, minutes=3, seconds=11, microseconds=858714), ), ( {"TnH": "4", "TnM": "54", "TnS": "28.512400"}, datetime.timedelta( hours=4, minutes=54, seconds=28, microseconds=512400 ), ), # Make sure we truncate, not round # https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is # https://bitbucket.org/nielsenb/aniso8601/issues/21/sub-microsecond-precision-is-lost-when ( {"PnY": "1999.9999999999999999"}, datetime.timedelta(days=729999, seconds=86399, microseconds=999999), ), ( {"PnM": "1.9999999999999999"}, datetime.timedelta( days=59, hours=23, minutes=59, seconds=59, microseconds=999999 ), ), ( {"PnW": "1.9999999999999999"}, datetime.timedelta( days=13, hours=23, minutes=59, seconds=59, microseconds=999999 ), ), ( {"PnD": "1.9999999999999999"}, datetime.timedelta( days=1, hours=23, minutes=59, seconds=59, microseconds=999999 ), ), ( {"TnH": "14.9999999999999999"}, datetime.timedelta( hours=14, minutes=59, seconds=59, microseconds=999999 ), ), ({"TnM": "0.00000000999"}, datetime.timedelta(0)), ({"TnM": "0.0000000999"}, datetime.timedelta(microseconds=5)), ({"TnS": "0.0000001"}, datetime.timedelta(0)), ({"TnS": "2.0000048"}, datetime.timedelta(seconds=2, microseconds=4)), ({"PnY": "1"}, datetime.timedelta(days=365)), ({"PnY": "1.5"}, datetime.timedelta(days=547.5)), ({"PnM": "1"}, datetime.timedelta(days=30)), ({"PnM": "1.5"}, datetime.timedelta(days=45)), ({"PnW": "1"}, datetime.timedelta(days=7)), ({"PnW": "1.5"}, datetime.timedelta(days=10.5)), ({"PnD": "1"}, datetime.timedelta(days=1)), ({"PnD": "1.5"}, datetime.timedelta(days=1.5)), ( { "PnY": "0003", "PnM": "06", "PnD": "04", "TnH": "12", "TnM": "30", "TnS": "05", }, datetime.timedelta(days=1279, hours=12, minutes=30, seconds=5), ), ( { "PnY": "0003", "PnM": "06", "PnD": "04", "TnH": "12", "TnM": "30", "TnS": "05.5", }, datetime.timedelta(days=1279, hours=12, minutes=30, seconds=5.5), ), # Test timedelta limit ( {"PnD": "999999999", "TnH": "23", "TnM": "59", "TnS": "59.999999"}, datetime.timedelta.max, ), # Make sure we truncate, not round # https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is ( { "PnY": "0001", "PnM": "02", "PnD": "03", "TnH": "14", "TnM": "43", "TnS": "59.9999997", }, datetime.timedelta( days=428, hours=14, minutes=43, seconds=59, microseconds=999999 ), ), # Verify overflows ({"TnH": "36"}, datetime.timedelta(days=1, hours=12)), ) for testtuple in testtuples: result = PythonTimeBuilder.build_duration(**testtuple[0]) self.assertEqual(result, testtuple[1]) def test_build_interval(self): testtuples = ( ( { "end": DatetimeTuple( DateTuple("1981", "04", "05", None, None, None), TimeTuple("01", "01", "00", None), ), "duration": DurationTuple(None, "1", None, None, None, None, None), }, datetime.datetime(year=1981, month=4, day=5, hour=1, minute=1), datetime.datetime(year=1981, month=3, day=6, hour=1, minute=1), ), ( { "end": DateTuple("1981", "04", "05", None, None, None), "duration": DurationTuple(None, "1", None, None, None, None, None), }, datetime.date(year=1981, month=4, day=5), datetime.date(year=1981, month=3, day=6), ), ( { "end": DateTuple("2018", "03", "06", None, None, None), "duration": DurationTuple( "1.5", None, None, None, None, None, None ), }, datetime.date(year=2018, month=3, day=6), datetime.datetime(year=2016, month=9, day=4, hour=12), ), ( { "end": DateTuple("2014", "11", "12", None, None, None), "duration": DurationTuple(None, None, None, None, "1", None, None), }, datetime.date(year=2014, month=11, day=12), datetime.datetime(year=2014, month=11, day=11, hour=23), ), ( { "end": DateTuple("2014", "11", "12", None, None, None), "duration": DurationTuple(None, None, None, None, "4", "54", "6.5"), }, datetime.date(year=2014, month=11, day=12), datetime.datetime( year=2014, month=11, day=11, hour=19, minute=5, second=53, microsecond=500000, ), ), ( { "end": DatetimeTuple( DateTuple("2050", "03", "01", None, None, None), TimeTuple( "13", "00", "00", TimezoneTuple(False, True, None, None, "Z"), ), ), "duration": DurationTuple(None, None, None, None, "10", None, None), }, datetime.datetime( year=2050, month=3, day=1, hour=13, tzinfo=UTCOffset(name="UTC", minutes=0), ), datetime.datetime( year=2050, month=3, day=1, hour=3, tzinfo=UTCOffset(name="UTC", minutes=0), ), ), # Make sure we truncate, not round # https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is # https://bitbucket.org/nielsenb/aniso8601/issues/21/sub-microsecond-precision-is-lost-when ( { "end": DateTuple("2000", "01", "01", None, None, None), "duration": DurationTuple( "1999.9999999999999999", None, None, None, None, None, None ), }, datetime.date(year=2000, month=1, day=1), datetime.datetime( year=1, month=4, day=30, hour=0, minute=0, second=0, microsecond=1 ), ), ( { "end": DateTuple("1989", "03", "01", None, None, None), "duration": DurationTuple( None, "1.9999999999999999", None, None, None, None, None ), }, datetime.date(year=1989, month=3, day=1), datetime.datetime( year=1988, month=12, day=31, hour=0, minute=0, second=0, microsecond=1, ), ), ( { "end": DateTuple("1989", "03", "01", None, None, None), "duration": DurationTuple( None, None, "1.9999999999999999", None, None, None, None ), }, datetime.date(year=1989, month=3, day=1), datetime.datetime( year=1989, month=2, day=15, hour=0, minute=0, second=0, microsecond=1, ), ), ( { "end": DateTuple("1989", "03", "01", None, None, None), "duration": DurationTuple( None, None, None, "1.9999999999999999", None, None, None ), }, datetime.date(year=1989, month=3, day=1), datetime.datetime( year=1989, month=2, day=27, hour=0, minute=0, second=0, microsecond=1, ), ), ( { "end": DateTuple("2001", "01", "01", None, None, None), "duration": DurationTuple( None, None, None, None, "14.9999999999999999", None, None ), }, datetime.date(year=2001, month=1, day=1), datetime.datetime( year=2000, month=12, day=31, hour=9, minute=0, second=0, microsecond=1, ), ), ( { "end": DateTuple("2001", "01", "01", None, None, None), "duration": DurationTuple( None, None, None, None, None, "0.00000000999", None ), }, datetime.date(year=2001, month=1, day=1), datetime.datetime(year=2001, month=1, day=1), ), ( { "end": DateTuple("2001", "01", "01", None, None, None), "duration": DurationTuple( None, None, None, None, None, "0.0000000999", None ), }, datetime.date(year=2001, month=1, day=1), datetime.datetime( year=2000, month=12, day=31, hour=23, minute=59, second=59, microsecond=999995, ), ), ( { "end": DateTuple("2018", "03", "06", None, None, None), "duration": DurationTuple( None, None, None, None, None, None, "0.0000001" ), }, datetime.date(year=2018, month=3, day=6), datetime.datetime(year=2018, month=3, day=6), ), ( { "end": DateTuple("2018", "03", "06", None, None, None), "duration": DurationTuple( None, None, None, None, None, None, "2.0000048" ), }, datetime.date(year=2018, month=3, day=6), datetime.datetime( year=2018, month=3, day=5, hour=23, minute=59, second=57, microsecond=999996, ), ), ( { "start": DatetimeTuple( DateTuple("1981", "04", "05", None, None, None), TimeTuple("01", "01", "00", None), ), "duration": DurationTuple(None, "1", None, "1", None, "1", None), }, datetime.datetime(year=1981, month=4, day=5, hour=1, minute=1), datetime.datetime(year=1981, month=5, day=6, hour=1, minute=2), ), ( { "start": DateTuple("1981", "04", "05", None, None, None), "duration": DurationTuple(None, "1", None, "1", None, None, None), }, datetime.date(year=1981, month=4, day=5), datetime.date(year=1981, month=5, day=6), ), ( { "start": DateTuple("2018", "03", "06", None, None, None), "duration": DurationTuple( None, "2.5", None, None, None, None, None ), }, datetime.date(year=2018, month=3, day=6), datetime.date(year=2018, month=5, day=20), ), ( { "start": DateTuple("2014", "11", "12", None, None, None), "duration": DurationTuple(None, None, None, None, "1", None, None), }, datetime.date(year=2014, month=11, day=12), datetime.datetime(year=2014, month=11, day=12, hour=1, minute=0), ), ( { "start": DateTuple("2014", "11", "12", None, None, None), "duration": DurationTuple(None, None, None, None, "4", "54", "6.5"), }, datetime.date(year=2014, month=11, day=12), datetime.datetime( year=2014, month=11, day=12, hour=4, minute=54, second=6, microsecond=500000, ), ), ( { "start": DatetimeTuple( DateTuple("2050", "03", "01", None, None, None), TimeTuple( "13", "00", "00", TimezoneTuple(False, True, None, None, "Z"), ), ), "duration": DurationTuple(None, None, None, None, "10", None, None), }, datetime.datetime( year=2050, month=3, day=1, hour=13, tzinfo=UTCOffset(name="UTC", minutes=0), ), datetime.datetime( year=2050, month=3, day=1, hour=23, tzinfo=UTCOffset(name="UTC", minutes=0), ), ), # Make sure we truncate, not round # https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is ( { "start": DateTuple("0001", "01", "01", None, None, None), "duration": DurationTuple( "1999.9999999999999999", None, None, None, None, None, None ), }, datetime.date(year=1, month=1, day=1), datetime.datetime( year=1999, month=9, day=3, hour=23, minute=59, second=59, microsecond=999999, ), ), ( { "start": DateTuple("1989", "03", "01", None, None, None), "duration": DurationTuple( None, "1.9999999999999999", None, None, None, None, None ), }, datetime.date(year=1989, month=3, day=1), datetime.datetime( year=1989, month=4, day=29, hour=23, minute=59, second=59, microsecond=999999, ), ), ( { "start": DateTuple("1989", "03", "01", None, None, None), "duration": DurationTuple( None, None, "1.9999999999999999", None, None, None, None ), }, datetime.date(year=1989, month=3, day=1), datetime.datetime( year=1989, month=3, day=14, hour=23, minute=59, second=59, microsecond=999999, ), ), ( { "start": DateTuple("1989", "03", "01", None, None, None), "duration": DurationTuple( None, None, None, "1.9999999999999999", None, None, None ), }, datetime.date(year=1989, month=3, day=1), datetime.datetime( year=1989, month=3, day=2, hour=23, minute=59, second=59, microsecond=999999, ), ), ( { "start": DateTuple("2001", "01", "01", None, None, None), "duration": DurationTuple( None, None, None, None, "14.9999999999999999", None, None ), }, datetime.date(year=2001, month=1, day=1), datetime.datetime( year=2001, month=1, day=1, hour=14, minute=59, second=59, microsecond=999999, ), ), ( { "start": DateTuple("2001", "01", "01", None, None, None), "duration": DurationTuple( None, None, None, None, None, "0.00000000999", None ), }, datetime.date(year=2001, month=1, day=1), datetime.datetime(year=2001, month=1, day=1), ), ( { "start": DateTuple("2001", "01", "01", None, None, None), "duration": DurationTuple( None, None, None, None, None, "0.0000000999", None ), }, datetime.date(year=2001, month=1, day=1), datetime.datetime( year=2001, month=1, day=1, hour=0, minute=0, second=0, microsecond=5 ), ), ( { "start": DateTuple("2018", "03", "06", None, None, None), "duration": DurationTuple( None, None, None, None, None, None, "0.0000001" ), }, datetime.date(year=2018, month=3, day=6), datetime.datetime(year=2018, month=3, day=6), ), ( { "start": DateTuple("2018", "03", "06", None, None, None), "duration": DurationTuple( None, None, None, None, None, None, "2.0000048" ), }, datetime.date(year=2018, month=3, day=6), datetime.datetime( year=2018, month=3, day=6, hour=0, minute=0, second=2, microsecond=4 ), ), ( { "start": DatetimeTuple( DateTuple("1980", "03", "05", None, None, None), TimeTuple("01", "01", "00", None), ), "end": DatetimeTuple( DateTuple("1981", "04", "05", None, None, None), TimeTuple("01", "01", "00", None), ), }, datetime.datetime(year=1980, month=3, day=5, hour=1, minute=1), datetime.datetime(year=1981, month=4, day=5, hour=1, minute=1), ), ( { "start": DatetimeTuple( DateTuple("1980", "03", "05", None, None, None), TimeTuple("01", "01", "00", None), ), "end": DateTuple("1981", "04", "05", None, None, None), }, datetime.datetime(year=1980, month=3, day=5, hour=1, minute=1), datetime.date(year=1981, month=4, day=5), ), ( { "start": DateTuple("1980", "03", "05", None, None, None), "end": DatetimeTuple( DateTuple("1981", "04", "05", None, None, None), TimeTuple("01", "01", "00", None), ), }, datetime.date(year=1980, month=3, day=5), datetime.datetime(year=1981, month=4, day=5, hour=1, minute=1), ), ( { "start": DateTuple("1980", "03", "05", None, None, None), "end": DateTuple("1981", "04", "05", None, None, None), }, datetime.date(year=1980, month=3, day=5), datetime.date(year=1981, month=4, day=5), ), ( { "start": DateTuple("1981", "04", "05", None, None, None), "end": DateTuple("1980", "03", "05", None, None, None), }, datetime.date(year=1981, month=4, day=5), datetime.date(year=1980, month=3, day=5), ), ( { "start": DatetimeTuple( DateTuple("2050", "03", "01", None, None, None), TimeTuple( "13", "00", "00", TimezoneTuple(False, True, None, None, "Z"), ), ), "end": DatetimeTuple( DateTuple("2050", "05", "11", None, None, None), TimeTuple( "15", "30", "00", TimezoneTuple(False, True, None, None, "Z"), ), ), }, datetime.datetime( year=2050, month=3, day=1, hour=13, tzinfo=UTCOffset(name="UTC", minutes=0), ), datetime.datetime( year=2050, month=5, day=11, hour=15, minute=30, tzinfo=UTCOffset(name="UTC", minutes=0), ), ), # Test concise representation ( { "start": DateTuple("2020", "01", "01", None, None, None), "end": DateTuple(None, None, "02", None, None, None), }, datetime.date(year=2020, month=1, day=1), datetime.date(year=2020, month=1, day=2), ), ( { "start": DateTuple("2008", "02", "15", None, None, None), "end": DateTuple(None, "03", "14", None, None, None), }, datetime.date(year=2008, month=2, day=15), datetime.date(year=2008, month=3, day=14), ), ( { "start": DatetimeTuple( DateTuple("2007", "12", "14", None, None, None), TimeTuple("13", "30", None, None), ), "end": TimeTuple("15", "30", None, None), }, datetime.datetime(year=2007, month=12, day=14, hour=13, minute=30), datetime.datetime(year=2007, month=12, day=14, hour=15, minute=30), ), ( { "start": DatetimeTuple( DateTuple("2007", "11", "13", None, None, None), TimeTuple("09", "00", None, None), ), "end": DatetimeTuple( DateTuple(None, None, "15", None, None, None), TimeTuple("17", "00", None, None), ), }, datetime.datetime(year=2007, month=11, day=13, hour=9), datetime.datetime(year=2007, month=11, day=15, hour=17), ), ( { "start": DatetimeTuple( DateTuple("2007", "11", "13", None, None, None), TimeTuple("00", "00", None, None), ), "end": DatetimeTuple( DateTuple(None, None, "16", None, None, None), TimeTuple("00", "00", None, None), ), }, datetime.datetime(year=2007, month=11, day=13), datetime.datetime(year=2007, month=11, day=16), ), ( { "start": DatetimeTuple( DateTuple("2007", "11", "13", None, None, None), TimeTuple( "09", "00", None, TimezoneTuple(False, True, None, None, "Z"), ), ), "end": DatetimeTuple( DateTuple(None, None, "15", None, None, None), TimeTuple("17", "00", None, None), ), }, datetime.datetime( year=2007, month=11, day=13, hour=9, tzinfo=UTCOffset(name="UTC", minutes=0), ), datetime.datetime( year=2007, month=11, day=15, hour=17, tzinfo=UTCOffset(name="UTC", minutes=0), ), ), ( { "start": DatetimeTuple( DateTuple("2007", "11", "13", None, None, None), TimeTuple("09", "00", None, None), ), "end": TimeTuple("12", "34.567", None, None), }, datetime.datetime(year=2007, month=11, day=13, hour=9), datetime.datetime( year=2007, month=11, day=13, hour=12, minute=34, second=34, microsecond=20000, ), ), ( { "start": DateTuple("2007", "11", "13", None, None, None), "end": TimeTuple("12", "34", None, None), }, datetime.date(year=2007, month=11, day=13), datetime.datetime(year=2007, month=11, day=13, hour=12, minute=34), ), # Make sure we truncate, not round # https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is ( { "start": DatetimeTuple( DateTuple("1980", "03", "05", None, None, None), TimeTuple("01", "01", "00.0000001", None), ), "end": DatetimeTuple( DateTuple("1981", "04", "05", None, None, None), TimeTuple("14", "43", "59.9999997", None), ), }, datetime.datetime(year=1980, month=3, day=5, hour=1, minute=1), datetime.datetime( year=1981, month=4, day=5, hour=14, minute=43, second=59, microsecond=999999, ), ), ) for testtuple in testtuples: result = PythonTimeBuilder.build_interval(**testtuple[0]) self.assertEqual(result[0], testtuple[1]) self.assertEqual(result[1], testtuple[2]) def test_build_repeating_interval(self): args = { "Rnn": "3", "interval": IntervalTuple( DateTuple("1981", "04", "05", None, None, None), None, DurationTuple(None, None, None, "1", None, None, None), ), } results = list(PythonTimeBuilder.build_repeating_interval(**args)) self.assertEqual(results[0], datetime.date(year=1981, month=4, day=5)) self.assertEqual(results[1], datetime.date(year=1981, month=4, day=6)) self.assertEqual(results[2], datetime.date(year=1981, month=4, day=7)) args = { "Rnn": "11", "interval": IntervalTuple( None, DatetimeTuple( DateTuple("1980", "03", "05", None, None, None), TimeTuple("01", "01", "00", None), ), DurationTuple(None, None, None, None, "1", "2", None), ), } results = list(PythonTimeBuilder.build_repeating_interval(**args)) for dateindex in compat.range(0, 11): self.assertEqual( results[dateindex], datetime.datetime(year=1980, month=3, day=5, hour=1, minute=1) - dateindex * datetime.timedelta(hours=1, minutes=2), ) args = { "Rnn": "2", "interval": IntervalTuple( DatetimeTuple( DateTuple("1980", "03", "05", None, None, None), TimeTuple("01", "01", "00", None), ), DatetimeTuple( DateTuple("1981", "04", "05", None, None, None), TimeTuple("01", "01", "00", None), ), None, ), } results = list(PythonTimeBuilder.build_repeating_interval(**args)) self.assertEqual( results[0], datetime.datetime(year=1980, month=3, day=5, hour=1, minute=1) ) self.assertEqual( results[1], datetime.datetime(year=1981, month=4, day=5, hour=1, minute=1) ) args = { "Rnn": "2", "interval": IntervalTuple( DatetimeTuple( DateTuple("1980", "03", "05", None, None, None), TimeTuple("01", "01", "00", None), ), DatetimeTuple( DateTuple("1981", "04", "05", None, None, None), TimeTuple("01", "01", "00", None), ), None, ), } results = list(PythonTimeBuilder.build_repeating_interval(**args)) self.assertEqual( results[0], datetime.datetime(year=1980, month=3, day=5, hour=1, minute=1) ) self.assertEqual( results[1], datetime.datetime(year=1981, month=4, day=5, hour=1, minute=1) ) args = { "R": True, "interval": IntervalTuple( None, DatetimeTuple( DateTuple("1980", "03", "05", None, None, None), TimeTuple("01", "01", "00", None), ), DurationTuple(None, None, None, None, "1", "2", None), ), } resultgenerator = PythonTimeBuilder.build_repeating_interval(**args) # Test the first 11 generated for dateindex in compat.range(0, 11): self.assertEqual( next(resultgenerator), datetime.datetime(year=1980, month=3, day=5, hour=1, minute=1) - dateindex * datetime.timedelta(hours=1, minutes=2), ) args = { "R": True, "interval": IntervalTuple( DateTuple("1981", "04", "05", None, None, None), None, DurationTuple(None, None, None, "1", None, None, None), ), } resultgenerator = PythonTimeBuilder.build_repeating_interval(**args) # Test the first 11 generated for dateindex in compat.range(0, 11): self.assertEqual( next(resultgenerator), ( datetime.datetime(year=1981, month=4, day=5, hour=0, minute=0) + dateindex * datetime.timedelta(days=1) ).date(), ) def test_build_timezone(self): testtuples = ( ({"Z": True, "name": "Z"}, datetime.timedelta(hours=0), "UTC"), ( {"negative": False, "hh": "00", "mm": "00", "name": "+00:00"}, datetime.timedelta(hours=0), "+00:00", ), ( {"negative": False, "hh": "01", "mm": "00", "name": "+01:00"}, datetime.timedelta(hours=1), "+01:00", ), ( {"negative": True, "hh": "01", "mm": "00", "name": "-01:00"}, -datetime.timedelta(hours=1), "-01:00", ), ( {"negative": False, "hh": "00", "mm": "12", "name": "+00:12"}, datetime.timedelta(minutes=12), "+00:12", ), ( {"negative": False, "hh": "01", "mm": "23", "name": "+01:23"}, datetime.timedelta(hours=1, minutes=23), "+01:23", ), ( {"negative": True, "hh": "01", "mm": "23", "name": "-01:23"}, -datetime.timedelta(hours=1, minutes=23), "-01:23", ), ( {"negative": False, "hh": "00", "name": "+00"}, datetime.timedelta(hours=0), "+00", ), ( {"negative": False, "hh": "01", "name": "+01"}, datetime.timedelta(hours=1), "+01", ), ( {"negative": True, "hh": "01", "name": "-01"}, -datetime.timedelta(hours=1), "-01", ), ( {"negative": False, "hh": "12", "name": "+12"}, datetime.timedelta(hours=12), "+12", ), ( {"negative": True, "hh": "12", "name": "-12"}, -datetime.timedelta(hours=12), "-12", ), ) for testtuple in testtuples: result = PythonTimeBuilder.build_timezone(**testtuple[0]) self.assertEqual(result.utcoffset(None), testtuple[1]) self.assertEqual(result.tzname(None), testtuple[2]) def test_range_check_date(self): # 0 isn't a valid year for a Python builder with self.assertRaises(YearOutOfBoundsError): PythonTimeBuilder.build_date(YYYY="0000") # Leap year # https://bitbucket.org/nielsenb/aniso8601/issues/14/parsing-ordinal-dates-should-only-allow with self.assertRaises(DayOutOfBoundsError): PythonTimeBuilder.build_date(YYYY="1981", DDD="366") def test_range_check_time(self): # Hour 24 can only represent midnight with self.assertRaises(MidnightBoundsError): PythonTimeBuilder.build_time(hh="24", mm="00", ss="01") with self.assertRaises(MidnightBoundsError): PythonTimeBuilder.build_time(hh="24", mm="00.1") with self.assertRaises(MidnightBoundsError): PythonTimeBuilder.build_time(hh="24", mm="01") with self.assertRaises(MidnightBoundsError): PythonTimeBuilder.build_time(hh="24.1") def test_range_check_duration(self): with self.assertRaises(YearOutOfBoundsError): PythonTimeBuilder.build_duration( PnY=str((datetime.timedelta.max.days // 365) + 1) ) with self.assertRaises(MonthOutOfBoundsError): PythonTimeBuilder.build_duration( PnM=str((datetime.timedelta.max.days // 30) + 1) ) with self.assertRaises(DayOutOfBoundsError): PythonTimeBuilder.build_duration(PnD=str(datetime.timedelta.max.days + 1)) with self.assertRaises(WeekOutOfBoundsError): PythonTimeBuilder.build_duration( PnW=str((datetime.timedelta.max.days // 7) + 1) ) with self.assertRaises(HoursOutOfBoundsError): PythonTimeBuilder.build_duration( TnH=str((datetime.timedelta.max.days * 24) + 24) ) with self.assertRaises(MinutesOutOfBoundsError): PythonTimeBuilder.build_duration( TnM=str((datetime.timedelta.max.days * 24 * 60) + 24 * 60) ) with self.assertRaises(SecondsOutOfBoundsError): PythonTimeBuilder.build_duration( TnS=str((datetime.timedelta.max.days * 24 * 60 * 60) + 24 * 60 * 60) ) # Split max range across all parts maxpart = datetime.timedelta.max.days // 7 with self.assertRaises(DayOutOfBoundsError): PythonTimeBuilder.build_duration( PnY=str((maxpart // 365) + 1), PnM=str((maxpart // 30) + 1), PnD=str((maxpart + 1)), PnW=str((maxpart // 7) + 1), TnH=str((maxpart * 24) + 1), TnM=str((maxpart * 24 * 60) + 1), TnS=str((maxpart * 24 * 60 * 60) + 1), ) def test_range_check_interval(self): with self.assertRaises(YearOutOfBoundsError): PythonTimeBuilder.build_interval( start=DateTuple("0007", None, None, None, None, None), duration=DurationTuple( None, None, None, str(datetime.timedelta.max.days), None, None, None ), ) with self.assertRaises(YearOutOfBoundsError): PythonTimeBuilder.build_interval( start=DatetimeTuple( DateTuple("0007", None, None, None, None, None), TimeTuple("1", None, None, None), ), duration=DurationTuple( str(datetime.timedelta.max.days // 365), None, None, None, None, None, None, ), ) with self.assertRaises(YearOutOfBoundsError): PythonTimeBuilder.build_interval( end=DateTuple("0001", None, None, None, None, None), duration=DurationTuple("3", None, None, None, None, None, None), ) with self.assertRaises(YearOutOfBoundsError): PythonTimeBuilder.build_interval( end=DatetimeTuple( DateTuple("0001", None, None, None, None, None), TimeTuple("1", None, None, None), ), duration=DurationTuple("2", None, None, None, None, None, None), ) def test_build_week_date(self): weekdate = PythonTimeBuilder._build_week_date(2009, 1) self.assertEqual(weekdate, datetime.date(year=2008, month=12, day=29)) weekdate = PythonTimeBuilder._build_week_date(2009, 53, isoday=7) self.assertEqual(weekdate, datetime.date(year=2010, month=1, day=3)) def test_build_ordinal_date(self): ordinaldate = PythonTimeBuilder._build_ordinal_date(1981, 95) self.assertEqual(ordinaldate, datetime.date(year=1981, month=4, day=5)) def test_iso_year_start(self): yearstart = PythonTimeBuilder._iso_year_start(2004) self.assertEqual(yearstart, datetime.date(year=2003, month=12, day=29)) yearstart = PythonTimeBuilder._iso_year_start(2010) self.assertEqual(yearstart, datetime.date(year=2010, month=1, day=4)) yearstart = PythonTimeBuilder._iso_year_start(2009) self.assertEqual(yearstart, datetime.date(year=2008, month=12, day=29)) def test_date_generator(self): startdate = datetime.date(year=2018, month=8, day=29) timedelta = datetime.timedelta(days=1) iterations = 10 generator = PythonTimeBuilder._date_generator(startdate, timedelta, iterations) results = list(generator) for dateindex in compat.range(0, 10): self.assertEqual( results[dateindex], datetime.date(year=2018, month=8, day=29) + dateindex * datetime.timedelta(days=1), ) def test_date_generator_unbounded(self): startdate = datetime.date(year=2018, month=8, day=29) timedelta = datetime.timedelta(days=5) generator = PythonTimeBuilder._date_generator_unbounded(startdate, timedelta) # Check the first 10 results for dateindex in compat.range(0, 10): self.assertEqual( next(generator), datetime.date(year=2018, month=8, day=29) + dateindex * datetime.timedelta(days=5), ) def test_distribute_microseconds(self): self.assertEqual(PythonTimeBuilder._distribute_microseconds(1, (), ()), (1,)) self.assertEqual( PythonTimeBuilder._distribute_microseconds(11, (0,), (10,)), (1, 1) ) self.assertEqual( PythonTimeBuilder._distribute_microseconds(211, (0, 0), (100, 10)), (2, 1, 1), ) self.assertEqual(PythonTimeBuilder._distribute_microseconds(1, (), ()), (1,)) self.assertEqual( PythonTimeBuilder._distribute_microseconds(11, (5,), (10,)), (6, 1) ) self.assertEqual( PythonTimeBuilder._distribute_microseconds(211, (10, 5), (100, 10)), (12, 6, 1), )