""" Stolen from https://github.com/django/django/blob/master/tests/utils_tests/test_dateparse.py at 9718fa2e8abe430c3526a9278dd976443d4ae3c6 Changed to: * use standard pytest layout * parametrize tests """ from datetime import date, datetime, time, timedelta, timezone import pytest from pydantic import BaseModel, ValidationError, errors from pydantic.datetime_parse import parse_date, parse_datetime, parse_duration, parse_time def create_tz(minutes): return timezone(timedelta(minutes=minutes)) @pytest.mark.parametrize( 'value,result', [ # Valid inputs ('1494012444.883309', date(2017, 5, 5)), (b'1494012444.883309', date(2017, 5, 5)), (1_494_012_444.883_309, date(2017, 5, 5)), ('1494012444', date(2017, 5, 5)), (1_494_012_444, date(2017, 5, 5)), (0, date(1970, 1, 1)), ('2012-04-23', date(2012, 4, 23)), (b'2012-04-23', date(2012, 4, 23)), ('2012-4-9', date(2012, 4, 9)), (date(2012, 4, 9), date(2012, 4, 9)), (datetime(2012, 4, 9, 12, 15), date(2012, 4, 9)), # Invalid inputs ('x20120423', errors.DateError), ('2012-04-56', errors.DateError), (19_999_999_999, date(2603, 10, 11)), # just before watershed (20_000_000_001, date(1970, 8, 20)), # just after watershed (1_549_316_052, date(2019, 2, 4)), # nowish in s (1_549_316_052_104, date(2019, 2, 4)), # nowish in ms (1_549_316_052_104_324, date(2019, 2, 4)), # nowish in μs (1_549_316_052_104_324_096, date(2019, 2, 4)), # nowish in ns ], ) def test_date_parsing(value, result): if result == errors.DateError: with pytest.raises(errors.DateError): parse_date(value) else: assert parse_date(value) == result @pytest.mark.parametrize( 'value,result', [ # Valid inputs ('09:15:00', time(9, 15)), ('10:10', time(10, 10)), ('10:20:30.400', time(10, 20, 30, 400_000)), (b'10:20:30.400', time(10, 20, 30, 400_000)), ('4:8:16', time(4, 8, 16)), (time(4, 8, 16), time(4, 8, 16)), (3610, time(1, 0, 10)), (3600.5, time(1, 0, 0, 500000)), (86400 - 1, time(23, 59, 59)), ('11:05:00-05:30', time(11, 5, 0, tzinfo=create_tz(-330))), ('11:05:00-0530', time(11, 5, 0, tzinfo=create_tz(-330))), ('11:05:00Z', time(11, 5, 0, tzinfo=timezone.utc)), ('11:05:00+00', time(11, 5, 0, tzinfo=timezone.utc)), ('11:05-06', time(11, 5, 0, tzinfo=create_tz(-360))), ('11:05+06', time(11, 5, 0, tzinfo=create_tz(360))), # Invalid inputs (86400, errors.TimeError), ('xxx', errors.TimeError), ('091500', errors.TimeError), (b'091500', errors.TimeError), ('09:15:90', errors.TimeError), ('11:05:00Y', errors.TimeError), ('11:05:00-25:00', errors.TimeError), ], ) def test_time_parsing(value, result): if result == errors.TimeError: with pytest.raises(errors.TimeError): parse_time(value) else: assert parse_time(value) == result @pytest.mark.parametrize( 'value,result', [ # Valid inputs # values in seconds ('1494012444.883309', datetime(2017, 5, 5, 19, 27, 24, 883_309, tzinfo=timezone.utc)), (1_494_012_444.883_309, datetime(2017, 5, 5, 19, 27, 24, 883_309, tzinfo=timezone.utc)), ('1494012444', datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), (b'1494012444', datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), (1_494_012_444, datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), # values in ms ('1494012444000.883309', datetime(2017, 5, 5, 19, 27, 24, 883, tzinfo=timezone.utc)), ('-1494012444000.883309', datetime(1922, 8, 29, 4, 32, 35, 999117, tzinfo=timezone.utc)), (1_494_012_444_000, datetime(2017, 5, 5, 19, 27, 24, tzinfo=timezone.utc)), ('2012-04-23T09:15:00', datetime(2012, 4, 23, 9, 15)), ('2012-4-9 4:8:16', datetime(2012, 4, 9, 4, 8, 16)), ('2012-04-23T09:15:00Z', datetime(2012, 4, 23, 9, 15, 0, 0, timezone.utc)), ('2012-4-9 4:8:16-0320', datetime(2012, 4, 9, 4, 8, 16, 0, create_tz(-200))), ('2012-04-23T10:20:30.400+02:30', datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(150))), ('2012-04-23T10:20:30.400+02', datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(120))), ('2012-04-23T10:20:30.400-02', datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(-120))), (b'2012-04-23T10:20:30.400-02', datetime(2012, 4, 23, 10, 20, 30, 400_000, create_tz(-120))), (datetime(2017, 5, 5), datetime(2017, 5, 5)), (0, datetime(1970, 1, 1, 0, 0, 0, tzinfo=timezone.utc)), # Invalid inputs ('x20120423091500', errors.DateTimeError), ('2012-04-56T09:15:90', errors.DateTimeError), ('2012-04-23T11:05:00-25:00', errors.DateTimeError), (19_999_999_999, datetime(2603, 10, 11, 11, 33, 19, tzinfo=timezone.utc)), # just before watershed (20_000_000_001, datetime(1970, 8, 20, 11, 33, 20, 1000, tzinfo=timezone.utc)), # just after watershed (1_549_316_052, datetime(2019, 2, 4, 21, 34, 12, 0, tzinfo=timezone.utc)), # nowish in s (1_549_316_052_104, datetime(2019, 2, 4, 21, 34, 12, 104_000, tzinfo=timezone.utc)), # nowish in ms (1_549_316_052_104_324, datetime(2019, 2, 4, 21, 34, 12, 104_324, tzinfo=timezone.utc)), # nowish in μs (1_549_316_052_104_324_096, datetime(2019, 2, 4, 21, 34, 12, 104_324, tzinfo=timezone.utc)), # nowish in ns ], ) def test_datetime_parsing(value, result): if result == errors.DateTimeError: with pytest.raises(errors.DateTimeError): parse_datetime(value) else: assert parse_datetime(value) == result @pytest.mark.parametrize( 'delta', [ timedelta(days=4, minutes=15, seconds=30, milliseconds=100), # fractions of seconds timedelta(hours=10, minutes=15, seconds=30), # hours, minutes, seconds timedelta(days=4, minutes=15, seconds=30), # multiple days timedelta(days=1, minutes=00, seconds=00), # single day timedelta(days=-4, minutes=15, seconds=30), # negative durations timedelta(minutes=15, seconds=30), # minute & seconds timedelta(seconds=30), # seconds ], ) def test_parse_python_format(delta): assert parse_duration(delta) == delta assert parse_duration(str(delta)) == delta @pytest.mark.parametrize( 'value,result', [ # seconds (timedelta(seconds=30), timedelta(seconds=30)), ('30', timedelta(seconds=30)), (30, timedelta(seconds=30)), (30.1, timedelta(seconds=30, milliseconds=100)), # minutes seconds ('15:30', timedelta(minutes=15, seconds=30)), ('5:30', timedelta(minutes=5, seconds=30)), # hours minutes seconds ('10:15:30', timedelta(hours=10, minutes=15, seconds=30)), ('1:15:30', timedelta(hours=1, minutes=15, seconds=30)), ('100:200:300', timedelta(hours=100, minutes=200, seconds=300)), # days ('4 15:30', timedelta(days=4, minutes=15, seconds=30)), ('4 10:15:30', timedelta(days=4, hours=10, minutes=15, seconds=30)), # fractions of seconds ('15:30.1', timedelta(minutes=15, seconds=30, milliseconds=100)), ('15:30.01', timedelta(minutes=15, seconds=30, milliseconds=10)), ('15:30.001', timedelta(minutes=15, seconds=30, milliseconds=1)), ('15:30.0001', timedelta(minutes=15, seconds=30, microseconds=100)), ('15:30.00001', timedelta(minutes=15, seconds=30, microseconds=10)), ('15:30.000001', timedelta(minutes=15, seconds=30, microseconds=1)), (b'15:30.000001', timedelta(minutes=15, seconds=30, microseconds=1)), # negative ('-4 15:30', timedelta(days=-4, minutes=15, seconds=30)), ('-172800', timedelta(days=-2)), ('-15:30', timedelta(minutes=-15, seconds=30)), ('-1:15:30', timedelta(hours=-1, minutes=15, seconds=30)), ('-30.1', timedelta(seconds=-30, milliseconds=-100)), # iso_8601 ('P4Y', errors.DurationError), ('P4M', errors.DurationError), ('P4W', errors.DurationError), ('P4D', timedelta(days=4)), ('P0.5D', timedelta(hours=12)), ('PT5H', timedelta(hours=5)), ('PT5M', timedelta(minutes=5)), ('PT5S', timedelta(seconds=5)), ('PT0.000005S', timedelta(microseconds=5)), (b'PT0.000005S', timedelta(microseconds=5)), ], ) def test_parse_durations(value, result): if result == errors.DurationError: with pytest.raises(errors.DurationError): parse_duration(value) else: assert parse_duration(value) == result @pytest.mark.parametrize( 'field, value, error_message', [ ('dt', [], 'invalid type; expected datetime, string, bytes, int or float'), ('dt', {}, 'invalid type; expected datetime, string, bytes, int or float'), ('dt', object, 'invalid type; expected datetime, string, bytes, int or float'), ('d', [], 'invalid type; expected date, string, bytes, int or float'), ('d', {}, 'invalid type; expected date, string, bytes, int or float'), ('d', object, 'invalid type; expected date, string, bytes, int or float'), ('t', [], 'invalid type; expected time, string, bytes, int or float'), ('t', {}, 'invalid type; expected time, string, bytes, int or float'), ('t', object, 'invalid type; expected time, string, bytes, int or float'), ('td', [], 'invalid type; expected timedelta, string, bytes, int or float'), ('td', {}, 'invalid type; expected timedelta, string, bytes, int or float'), ('td', object, 'invalid type; expected timedelta, string, bytes, int or float'), ], ) def test_model_type_errors(field, value, error_message): class Model(BaseModel): dt: datetime = None d: date = None t: time = None td: timedelta = None with pytest.raises(ValidationError) as exc_info: Model(**{field: value}) assert len(exc_info.value.errors()) == 1 error = exc_info.value.errors()[0] assert error == {'loc': (field,), 'type': 'type_error', 'msg': error_message} @pytest.mark.parametrize('field', ['dt', 'd', 't', 'dt']) def test_unicode_decode_error(field): class Model(BaseModel): dt: datetime = None d: date = None t: time = None td: timedelta = None with pytest.raises(ValidationError) as exc_info: Model(**{field: b'\x81'}) assert len(exc_info.value.errors()) == 1 error = exc_info.value.errors()[0] assert error == { 'loc': (field,), 'type': 'value_error.unicodedecode', 'msg': "'utf-8' codec can't decode byte 0x81 in position 0: invalid start byte", }