diff --git a/singer_sdk/_singerlib/utils.py b/singer_sdk/_singerlib/utils.py index c1055f75a..e8f513aa7 100644 --- a/singer_sdk/_singerlib/utils.py +++ b/singer_sdk/_singerlib/utils.py @@ -7,6 +7,14 @@ DATETIME_FMT_SAFE = "%Y-%m-%dT%H:%M:%S.%fZ" +class NonUTCDatetimeError(Exception): + """Raised when a non-UTC datetime is passed to a function expecting UTC.""" + + def __init__(self) -> None: + """Initialize the exception.""" + super().__init__("datetime must be pegged at UTC tzoneinfo") + + def strptime_to_utc(dtimestr: str) -> datetime: """Parses a provide datetime string into a UTC datetime object. @@ -34,10 +42,11 @@ def strftime(dtime: datetime, format_str: str = DATETIME_FMT) -> str: A string in the specified format Raises: - Exception: if the datetime is not UTC (if it has a nonzero time zone offset) + NonUTCDatetimeError: if the datetime is not UTC (if it has a nonzero time zone + offset) """ if dtime.utcoffset() != timedelta(0): - raise Exception("datetime must be pegged at UTC tzoneinfo") + raise NonUTCDatetimeError() dt_str = None try: diff --git a/tests/_singerlib/test_utils.py b/tests/_singerlib/test_utils.py index e1035b63c..d26ee4688 100644 --- a/tests/_singerlib/test_utils.py +++ b/tests/_singerlib/test_utils.py @@ -4,6 +4,7 @@ import pytz from singer_sdk._singerlib import strftime, strptime_to_utc +from singer_sdk._singerlib.utils import NonUTCDatetimeError def test_small_years(): @@ -18,3 +19,23 @@ def test_round_trip(): parsed_datetime = strptime_to_utc(dtime) formatted_datetime = strftime(parsed_datetime) assert dtime == formatted_datetime + + +@pytest.mark.parametrize( + "dtimestr", + [ + "2021-01-01T00:00:00.000000Z", + "2021-01-01T00:00:00.000000+00:00", + "2021-01-01T00:00:00.000000+06:00", + "2021-01-01T00:00:00.000000-04:00", + ], + ids=["Z", "offset+0", "offset+6", "offset-4"], +) +def test_strptime_to_utc(dtimestr): + assert strptime_to_utc(dtimestr).tzinfo == pytz.UTC + + +def test_stftime_non_utc(): + now = datetime.utcnow().replace(tzinfo=pytz.timezone("America/New_York")) + with pytest.raises(NonUTCDatetimeError): + strftime(now)