diff --git a/README.md b/README.md index 3aa722d..c20f0c7 100644 --- a/README.md +++ b/README.md @@ -126,17 +126,17 @@ Some additional examples of timestamps and to what they whould be converted. Thr ```python import utcnow -# This represents 20 minutes and 50.52 seconds after the 23rd hour of April 12th, 1985 in UTC. +# This represents 20 minutes and 50.52 seconds after the 23rd hour of April 12th, 1985 UTC. utcnow.get("1985-04-12T23:20:50.52Z") # "1985-04-12T23:20:50.520000Z" -# This represents 39 minutes and 57 seconds after the 16th hour of December 19th, 1996 with an -# offset of -08:00 from UTC (Pacific Standard Time). Note that this is equivalent to +# This represents 39 minutes and 57 seconds after the 16th hour of December 19th, 1996 with +# an offset of -08:00 from UTC (Pacific Standard Time). Note that this is equivalent to # 1996-12-20T00:39:57Z in UTC. utcnow.get("1996-12-19T16:39:57-08:00") # "1996-12-20T00:39:57.000000Z" -# This represents the same instant of time as noon, January 1, 1937, Netherlands time. Standard -# time in the Netherlands was exactly 19 minutes and 32.13 seconds ahead of UTC by law from -# 1909-05-01 through 1937-06-30. +# This represents the same instant of time as noon, January 1, 1937, Netherlands time. +# Standard time in the Netherlands was exactly 19 minutes and 32.13 seconds ahead of UTC by +# law from 1909-05-01 through 1937-06-30. utcnow.get("1937-01-01T12:00:27.87+00:20") # "1937-01-01T11:40:27.870000Z" # Examples of other formats of accepted inputs: @@ -177,7 +177,7 @@ result = utcnow.get("1984-08-01 13:38") ``` ```python -# RFC 3339 timestamps as input – dates and datetimes – UTC will be assumed if tz is left out +# RFC 3339 timestamp as input – dates and datetimes – UTC assumed if tz is left out from utcnow import utcnow result = utcnow.get("2077-10-27") @@ -199,7 +199,7 @@ result = utcnow.get(dt) ``` ```python -# It's also possible to transform datetime values with timezone offsets into timestamp strings +# It's also possible to convert datetime values with tz offsets to timestamp strings import datetime from utcnow import utcnow @@ -214,7 +214,7 @@ result = utcnow.get(dt) ``` ```python -# Or vice versa, transforming a timestamp string into a datetime object (with tzinfo set to UTC) +# Vice versa, transforming a timestamp string to a datetime object (with tzinfo set to UTC) from utcnow import utcnow result = utcnow.as_datetime("1984-08-01T13:38:00.123450Z") @@ -222,7 +222,7 @@ result = utcnow.as_datetime("1984-08-01T13:38:00.123450Z") ``` ```python -# Example of using a value from "arrow" – a popular date-time Python lib with a large featureset +# Example using a value from "arrow" – a popular date-time Python lib with large featureset import arrow from utcnow import utcnow @@ -235,7 +235,7 @@ str(value) result = utcnow.get(value) # "2021-04-30T05:58:30.047110Z" -# the same output as via utcnow can be returned in the following ways, including direct via arrow: +# the same output as via utcnow can be returned in following ways, also directly arrow: # 1. utcnow.get(value) # 2. value.to("UTC").strftime("%Y-%m-%dT%H:%M:%S.%fZ") ``` @@ -247,7 +247,7 @@ import utcnow utcnow.utcnow() # "2021-02-18T08:24:48.382262Z" -# same thing can be accomplished using datetime and all of these calls returns the same str value: +# Similar can be accomplished with datetime – these lines returns the same string value: # 1. utcnow.utcnow() # 2. str(utcnow) # 3. str(utcnow.utcnow) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..feccf7f --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,10 @@ +import pytest + + +@pytest.fixture(autouse=True) +def clear_lru_cache() -> None: + from utcnow import _is_numeric, _timestamp_to_datetime, _transform_value + + _is_numeric.cache_clear() + _transform_value.cache_clear() + _timestamp_to_datetime.cache_clear() diff --git a/tests/test_dates.py b/tests/test_dates.py new file mode 100644 index 0000000..2f365a7 --- /dev/null +++ b/tests/test_dates.py @@ -0,0 +1,127 @@ +import datetime + +import pytest + + +@pytest.mark.parametrize( + "value, expect_error", + [ + ("1970-01-01", False), + ("2020-01-01", False), + ("2020-01-29", False), + ("2020-01-30", False), + ("2020-01-31", False), + ("2020-01-32", True), + ("2020-01-40", True), + ("2020-01-50", True), + ("2020-00-00", True), + ("2020-01-00", True), + ("2020-00-01", True), + ("2020-12-01", False), + ("2020-12-31", False), + ("2020-12-32", True), + ("2020-13-01", True), + ("2020-02-01", False), + ("2020-02-28", False), + ("2020-02-29", False), + ("2020-02-30", True), + ("2020-02-31", True), + ("2021-02-28", False), + ("2021-02-29", True), + ], +) +def test_dates(value: str, expect_error: bool) -> None: + import utcnow + + try: + assert isinstance(utcnow.as_string(value), str) + assert isinstance(utcnow.as_datetime(value), datetime.datetime) + if expect_error: + assert False + except Exception: + if not expect_error: + raise + if not expect_error: + # unreachable + assert False + + assert True + + try: + assert isinstance(utcnow.as_string(f"{value}T00:00:00.000000Z"), str) + assert isinstance(utcnow.as_datetime(f"{value}T00:00:00.000000Z"), datetime.datetime) + if expect_error: + assert False + except Exception: + if not expect_error: + raise + if not expect_error: + # unreachable + assert False + + assert True + + try: + assert isinstance(utcnow.as_string(value), str) + assert isinstance(utcnow.as_datetime(value), datetime.datetime) + + assert isinstance(utcnow.as_string(f"{value}T00:00:00.000000Z"), str) + assert isinstance(utcnow.as_datetime(f"{value}T00:00:00.000000Z"), datetime.datetime) + + assert utcnow.as_string(value) == utcnow.as_string(f"{value}T00:00:00.000000Z") + + assert isinstance(utcnow.as_string(f"{value} 00:00:00.000000Z"), str) + assert isinstance(utcnow.as_datetime(f"{value} 00:00:00.000000Z"), datetime.datetime) + + assert isinstance(utcnow.as_string(f"{value}T00:00:00.000000"), str) + assert isinstance(utcnow.as_datetime(f"{value}T00:00:00.000000"), datetime.datetime) + + assert isinstance(utcnow.as_string(f"{value} 00:00:00.000000"), str) + assert isinstance(utcnow.as_datetime(f"{value} 00:00:00.000000"), datetime.datetime) + + assert isinstance(utcnow.as_string(f"{value}T00:00:00.000000+00:00"), str) + assert isinstance(utcnow.as_datetime(f"{value}T00:00:00.000000+00:00"), datetime.datetime) + + assert isinstance(utcnow.as_string(f"{value} 00:00:00.000000+00:00"), str) + assert isinstance(utcnow.as_datetime(f"{value} 00:00:00.000000+00:00"), datetime.datetime) + + assert isinstance(utcnow.as_string(f"{value}T00:00:00.000000-00:00"), str) + assert isinstance(utcnow.as_datetime(f"{value}T00:00:00.000000-00:00"), datetime.datetime) + + assert isinstance(utcnow.as_string(f"{value}T00:00:00.000000 UTC"), str) + assert isinstance(utcnow.as_datetime(f"{value}T00:00:00.000000 UTC"), datetime.datetime) + + assert isinstance(utcnow.as_string(f"{value}T00:00:00Z"), str) + assert isinstance(utcnow.as_datetime(f"{value}T00:00:00Z"), datetime.datetime) + + assert isinstance(utcnow.as_string(f"{value} 00:00:00Z"), str) + assert isinstance(utcnow.as_datetime(f"{value} 00:00:00Z"), datetime.datetime) + + assert isinstance(utcnow.as_string(f"{value}T00:00:00"), str) + assert isinstance(utcnow.as_datetime(f"{value}T00:00:00"), datetime.datetime) + + assert isinstance(utcnow.as_string(f"{value} 00:00:00"), str) + assert isinstance(utcnow.as_datetime(f"{value} 00:00:00"), datetime.datetime) + + assert isinstance(utcnow.as_string(f"{value}T00:00:00+00:00"), str) + assert isinstance(utcnow.as_datetime(f"{value}T00:00:00+00:00"), datetime.datetime) + + assert isinstance(utcnow.as_string(f"{value} 00:00:00+00:00"), str) + assert isinstance(utcnow.as_datetime(f"{value} 00:00:00+00:00"), datetime.datetime) + + assert isinstance(utcnow.as_string(f"{value}T00:00:00-00:00"), str) + assert isinstance(utcnow.as_datetime(f"{value}T00:00:00-00:00"), datetime.datetime) + + assert isinstance(utcnow.as_string(f"{value}T00:00:00 UTC"), str) + assert isinstance(utcnow.as_datetime(f"{value}T00:00:00 UTC"), datetime.datetime) + + if expect_error: + assert False + except Exception: + if not expect_error: + raise + if not expect_error: + # unreachable + assert False + + assert True diff --git a/tests/test_lrucache.py b/tests/test_lrucache.py new file mode 100644 index 0000000..2b9bafd --- /dev/null +++ b/tests/test_lrucache.py @@ -0,0 +1,726 @@ +import datetime +import time +from typing import Callable, Tuple + + +def hits_miss_currsize(func: Callable) -> Tuple[int, int, int]: + hits: int = 0 + misses: int = 0 + currsize: int = 0 + + hits, misses, _, currsize = func.cache_info() # type: ignore + return (hits, misses, currsize) + + +def test_functional_cache_hits() -> None: + import utcnow + from utcnow import _is_numeric, _timestamp_to_datetime, _transform_value + + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 0, 0) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + _is_numeric("1") + assert hits_miss_currsize(_is_numeric) == (0, 1, 1) + + _is_numeric("1") + assert hits_miss_currsize(_is_numeric) == (1, 1, 1) + + _is_numeric("2") + assert hits_miss_currsize(_is_numeric) == (1, 2, 2) + + _is_numeric("4711") + assert hits_miss_currsize(_is_numeric) == (1, 3, 3) + + _is_numeric("4711.0") + assert hits_miss_currsize(_is_numeric) == (1, 4, 4) + + _is_numeric("4711.00") + assert hits_miss_currsize(_is_numeric) == (1, 5, 5) + + _is_numeric("4711.00") + assert hits_miss_currsize(_is_numeric) == (2, 5, 5) + + _is_numeric("4711.") + assert hits_miss_currsize(_is_numeric) == (2, 6, 6) + + assert hits_miss_currsize(_transform_value) == (0, 0, 0) + + utcnow.get(1) + assert hits_miss_currsize(_is_numeric) == (2, 6, 6) + + utcnow.get("1") + assert hits_miss_currsize(_is_numeric) == (3, 6, 6) + + utcnow.get("2") + assert hits_miss_currsize(_is_numeric) == (4, 6, 6) + + utcnow.get("3") + assert hits_miss_currsize(_is_numeric) == (4, 7, 7) + + utcnow.get("3") + assert hits_miss_currsize(_is_numeric) == (4, 7, 7) + + utcnow.get(3) + assert hits_miss_currsize(_is_numeric) == (4, 7, 7) + + utcnow.get(3.0) + assert hits_miss_currsize(_is_numeric) == (4, 7, 7) + + utcnow.get(3.0) + assert hits_miss_currsize(_is_numeric) == (4, 7, 7) + + utcnow.get(3.000) + assert hits_miss_currsize(_is_numeric) == (4, 7, 7) + + utcnow.get(3.001) + assert hits_miss_currsize(_is_numeric) == (4, 7, 7) + + utcnow.get("3.") + assert hits_miss_currsize(_is_numeric) == (4, 8, 8) + + utcnow.get("3.0") + assert hits_miss_currsize(_is_numeric) == (4, 9, 9) + + assert hits_miss_currsize(_is_numeric) == (4, 9, 9) + assert hits_miss_currsize(_transform_value) == (3, 9, 9) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + utcnow.as_datetime(0) + utcnow.as_datetime(0) + utcnow.as_datetime("0") + utcnow.as_datetime(0.0) + utcnow.as_datetime(-0) + utcnow.as_datetime(-0.0) + utcnow.as_datetime("1") + utcnow.as_datetime("2") + utcnow.as_datetime("3") + utcnow.as_datetime("1") + utcnow.as_datetime("2") + utcnow.as_datetime("3") + utcnow.as_datetime("3") + utcnow.as_datetime("3.") + utcnow.as_datetime("3") + utcnow.as_datetime("1.0") + utcnow.as_datetime("2.0") + utcnow.as_datetime("3.0") + utcnow.as_datetime("1.00") + utcnow.as_datetime("2.00") + utcnow.as_datetime("3.00") + utcnow.as_datetime("1.00") + utcnow.as_datetime("2.00") + utcnow.as_datetime("3.00") + utcnow.as_datetime(1.00) + utcnow.as_datetime(2.00) + utcnow.as_datetime(3.00) + utcnow.as_datetime(1.0) + utcnow.as_datetime(2.0) + utcnow.as_datetime(3.0) + utcnow.as_datetime(1.0) + utcnow.as_datetime(2.0) + utcnow.as_datetime(3.0) + utcnow.as_datetime("-0") + utcnow.as_datetime("-0.") + utcnow.as_datetime("-0.0") + + assert hits_miss_currsize(_is_numeric) == (4, 18, 18) + assert hits_miss_currsize(_transform_value) == (26, 22, 22) + assert hits_miss_currsize(_timestamp_to_datetime) == (32, 4, 4) + + utcnow.get("1970-01-01") + assert hits_miss_currsize(_is_numeric) == (4, 18, 18) + assert hits_miss_currsize(_transform_value) == (26, 23, 23) + assert hits_miss_currsize(_timestamp_to_datetime) == (32, 4, 4) + + utcnow.get("1970-01-01") + assert hits_miss_currsize(_is_numeric) == (4, 18, 18) + assert hits_miss_currsize(_transform_value) == (27, 23, 23) + assert hits_miss_currsize(_timestamp_to_datetime) == (32, 4, 4) + + utcnow.get("1970-01-01T00:00:00.000000Z") + assert hits_miss_currsize(_is_numeric) == (4, 18, 18) + assert hits_miss_currsize(_transform_value) == (27, 24, 24) + assert hits_miss_currsize(_timestamp_to_datetime) == (32, 4, 4) + + utcnow.as_datetime("1970-01-01") + assert hits_miss_currsize(_is_numeric) == (4, 18, 18) + assert hits_miss_currsize(_transform_value) == (28, 24, 24) + assert hits_miss_currsize(_timestamp_to_datetime) == (33, 4, 4) + + utcnow.as_datetime("1970-01-02") + assert hits_miss_currsize(_is_numeric) == (4, 18, 18) + assert hits_miss_currsize(_transform_value) == (28, 25, 25) + assert hits_miss_currsize(_timestamp_to_datetime) == (33, 5, 5) + + utcnow.as_datetime("1970-01-01T00:00:00.000000Z") + assert hits_miss_currsize(_is_numeric) == (4, 18, 18) + assert hits_miss_currsize(_transform_value) == (29, 25, 25) + assert hits_miss_currsize(_timestamp_to_datetime) == (34, 5, 5) + + utcnow.as_datetime("1970-01-01T00:00:00.000000") + assert hits_miss_currsize(_is_numeric) == (4, 18, 18) + assert hits_miss_currsize(_transform_value) == (29, 26, 26) + assert hits_miss_currsize(_timestamp_to_datetime) == (35, 5, 5) + + utcnow.as_datetime("1970-01-01 00:00:00") + assert hits_miss_currsize(_is_numeric) == (4, 18, 18) + assert hits_miss_currsize(_transform_value) == (29, 27, 27) + assert hits_miss_currsize(_timestamp_to_datetime) == (36, 5, 5) + + utcnow.as_datetime("1970-01-01 00:00:00+00:00") + assert hits_miss_currsize(_is_numeric) == (4, 18, 18) + assert hits_miss_currsize(_transform_value) == (29, 28, 28) + assert hits_miss_currsize(_timestamp_to_datetime) == (37, 5, 5) + + utcnow.as_datetime("1970-01-01 00:00:00.000000") + assert hits_miss_currsize(_is_numeric) == (4, 18, 18) + assert hits_miss_currsize(_transform_value) == (29, 29, 29) + assert hits_miss_currsize(_timestamp_to_datetime) == (38, 5, 5) + + utcnow.as_datetime("1970-01-01T00:00:00.000000") + assert hits_miss_currsize(_is_numeric) == (4, 18, 18) + assert hits_miss_currsize(_transform_value) == (30, 29, 29) + assert hits_miss_currsize(_timestamp_to_datetime) == (39, 5, 5) + + utcnow.get("1970-01-01 00:00") + assert hits_miss_currsize(_is_numeric) == (4, 18, 18) + assert hits_miss_currsize(_transform_value) == (30, 30, 30) + assert hits_miss_currsize(_timestamp_to_datetime) == (39, 5, 5) + + utcnow.get("1970-01-01 00:00:00") + assert hits_miss_currsize(_is_numeric) == (4, 18, 18) + assert hits_miss_currsize(_transform_value) == (31, 30, 30) + assert hits_miss_currsize(_timestamp_to_datetime) == (39, 5, 5) + + +def test_cache_hits_similar() -> None: + import utcnow + from utcnow import _is_numeric, _timestamp_to_datetime, _transform_value + + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 0, 0) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + values = list( + map( + utcnow.get, + [ + 1, + 1, + 1.0, + 1.0, + 1.0, + 1.00, + 1.000, + "1", + "1.", + "1.0", + "1.00", + "1.000", + "1.000", + "1970-01-01T00:00:01.000000Z", + "1970-01-01T00:00:01.000000+00:00", + "1970-01-01T00:00:01.000000", + "1970-01-01T00:00:01.000000", + "1970-01-01T00:00:01Z", + "1970-01-01T00:00:01+00:00", + "1970-01-01T00:00:01", + "1970-01-01T00:00:01.0Z", + "1970-01-01T00:00:01.0+00:00", + "1970-01-01T00:00:01.000+00:00", + "1970-01-01T00:00:01.000", + ], + ) + ) + + assert len(list(filter(lambda value: value == "1970-01-01T00:00:01.000000Z", values))) == 24 + + assert hits_miss_currsize(_is_numeric) == (0, 5, 5) + assert hits_miss_currsize(_transform_value) == (7, 17, 17) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + values2 = list( + map( + utcnow.get, + [ + 1, + 1, + 1.0, + 1.0, + 1.0, + 1.00, + 1.000, + "1", + "1.", + "1.0", + "1.00", + "1.000", + "1.000", + "1970-01-01T00:00:01.000000Z", + "1970-01-01T00:00:01.000000+00:00", + "1970-01-01T00:00:01.000000", + "1970-01-01T00:00:01.000000", + "1970-01-01T00:00:01Z", + "1970-01-01T00:00:01+00:00", + "1970-01-01T00:00:01", + "1970-01-01T00:00:01.0Z", + "1970-01-01T00:00:01.0+00:00", + "1970-01-01T00:00:01.000+00:00", + "1970-01-01T00:00:01.000", + ], + ) + ) + + assert len(list(filter(lambda value: value == "1970-01-01T00:00:01.000000Z", values2))) == 24 + + assert hits_miss_currsize(_is_numeric) == (0, 5, 5) + assert hits_miss_currsize(_transform_value) == (7 + 24, 17, 17) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + values3 = list( + map( + utcnow.as_datetime, + [ + 1, + 1, + 1.0, + 1.0, + 1.0, + 1.00, + 1.000, + "1", + "1.", + "1.0", + "1.00", + "1.000", + "1.000", + "1970-01-01T00:00:01.000000Z", + "1970-01-01T00:00:01.000000+00:00", + "1970-01-01T00:00:01.000000", + "1970-01-01T00:00:01.000000", + "1970-01-01T00:00:01Z", + "1970-01-01T00:00:01+00:00", + "1970-01-01T00:00:01", + "1970-01-01T00:00:01.0Z", + "1970-01-01T00:00:01.0+00:00", + "1970-01-01T00:00:01.000+00:00", + "1970-01-01T00:00:01.000", + ], + ) + ) + + assert ( + len( + list( + filter( + lambda value: value == datetime.datetime(1970, 1, 1, 0, 0, 1, tzinfo=datetime.timezone.utc), values3 + ) + ) + ) + == 24 + ) + + assert hits_miss_currsize(_is_numeric) == (0, 5, 5) + assert hits_miss_currsize(_transform_value) == (7 + 24 + 24, 17, 17) + assert hits_miss_currsize(_timestamp_to_datetime) == (23, 1, 1) + + values4 = list(map(utcnow.get, values3)) + + assert len(list(filter(lambda value: value == "1970-01-01T00:00:01.000000Z", values4))) == 24 + + assert hits_miss_currsize(_is_numeric) == (0, 5, 5) + assert hits_miss_currsize(_transform_value) == (7 + 24 + 24 + 23, 18, 18) + assert hits_miss_currsize(_timestamp_to_datetime) == (23, 1, 1) + + +def test_cache_hits_with_sentinel() -> None: + import utcnow + from utcnow import _is_numeric, _timestamp_to_datetime, _transform_value + + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 0, 0) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + utcnow.get() + + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 0, 0) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + utcnow.as_datetime() + + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 0, 0) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + utcnow.utcnow() + + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 0, 0) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + utcnow() # type: ignore + + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 0, 0) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + +def test_cache_hits_with_sentinel_loop() -> None: + import utcnow + from utcnow import _is_numeric, _timestamp_to_datetime, _transform_value + + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 0, 0) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + call_count = 100 + values = set() + + for _ in range(call_count): + values.add(utcnow.get()) + + assert len(values) == call_count + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 0, 0) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + for _ in range(call_count): + values.add(utcnow()) # type: ignore + + assert len(values) == call_count * 2 + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 0, 0) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + for _ in range(call_count): + values.add(f"{utcnow}") + + assert len(values) == call_count * 3 + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 0, 0) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + for _ in range(call_count): + values.add(f"{utcnow.utcnow}") + + assert len(values) == call_count * 4 + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 0, 0) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + for _ in range(call_count): + values.add(str(utcnow())) # type: ignore + + assert len(values) == call_count * 5 + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 0, 0) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + for _ in range(call_count): + values.add(str(utcnow)) + + assert len(values) == call_count * 6 + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 0, 0) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + for _ in range(call_count): + values.add(str(utcnow.utcnow)) + + assert len(values) == call_count * 7 + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 0, 0) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + for _ in range(call_count): + values.add(str(utcnow.utcnow())) + + assert len(values) == call_count * 8 + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 0, 0) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + for _ in range(call_count): + values.add(str(utcnow.as_string())) + + assert len(values) == call_count * 9 + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 0, 0) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + for _ in range(call_count): + values.add(str(utcnow.as_datetime())) + + assert len(values) == call_count * 10 + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 0, 0) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + d = {"timestamp": str(utcnow)} + for _ in range(call_count): + values.add(str(d)) + + assert len(values) == call_count * 10 + 1 + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 0, 0) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + d = {"timestamp": str(utcnow)} + for _ in range(call_count): + values.add(str(d)) + + assert len(values) == call_count * 10 + 2 + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 0, 0) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + d = {"timestamp": utcnow} # type: ignore + for _ in range(call_count): + values.add(str(d)) + + assert len(values) == call_count * 11 + 2 + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 0, 0) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + d = {"timestamp": utcnow.utcnow} # type: ignore + for _ in range(call_count): + values.add(str(d)) + + assert len(values) == call_count * 12 + 2 + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 0, 0) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + +def test_cache_hits_with_uniques() -> None: + import utcnow + from utcnow import _is_numeric, _timestamp_to_datetime, _transform_value + + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 0, 0) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + utcnow.get(time.time()) + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 1, 1) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + utcnow.as_datetime(time.time()) + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 2, 2) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 1, 1) + + utcnow.utcnow(time.time()) + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 3, 3) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 1, 1) + + utcnow(time.time()) # type: ignore + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 4, 4) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 1, 1) + + utcnow.get(0) + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 5, 5) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 1, 1) + + utcnow.as_datetime(0) + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (1, 5, 5) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 2, 2) + + utcnow.get("0") + assert hits_miss_currsize(_is_numeric) == (0, 1, 1) + assert hits_miss_currsize(_transform_value) == (1, 6, 6) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 2, 2) + + utcnow.as_datetime("0") + assert hits_miss_currsize(_is_numeric) == (0, 1, 1) + assert hits_miss_currsize(_transform_value) == (2, 6, 6) + assert hits_miss_currsize(_timestamp_to_datetime) == (1, 2, 2) + + utcnow.get(str(time.time())) + assert hits_miss_currsize(_is_numeric) == (0, 2, 2) + assert hits_miss_currsize(_transform_value) == (2, 7, 7) + assert hits_miss_currsize(_timestamp_to_datetime) == (1, 2, 2) + + utcnow.as_datetime(str(time.time())) + assert hits_miss_currsize(_is_numeric) == (0, 3, 3) + assert hits_miss_currsize(_transform_value) == (2, 8, 8) + assert hits_miss_currsize(_timestamp_to_datetime) == (1, 3, 3) + + t = time.time() + + utcnow.get(t) + assert hits_miss_currsize(_is_numeric) == (0, 3, 3) + assert hits_miss_currsize(_transform_value) == (2, 9, 9) + assert hits_miss_currsize(_timestamp_to_datetime) == (1, 3, 3) + + utcnow.as_datetime(t) + assert hits_miss_currsize(_is_numeric) == (0, 3, 3) + assert hits_miss_currsize(_transform_value) == (3, 9, 9) + assert hits_miss_currsize(_timestamp_to_datetime) == (1, 4, 4) + + utcnow.get(str(t)) + assert hits_miss_currsize(_is_numeric) == (0, 4, 4) + assert hits_miss_currsize(_transform_value) == (3, 10, 10) + assert hits_miss_currsize(_timestamp_to_datetime) == (1, 4, 4) + + utcnow.as_datetime(str(t)) + assert hits_miss_currsize(_is_numeric) == (0, 4, 4) + assert hits_miss_currsize(_transform_value) == (4, 10, 10) + assert hits_miss_currsize(_timestamp_to_datetime) == (2, 4, 4) + + utcnow.as_datetime(str(t)) + assert hits_miss_currsize(_is_numeric) == (0, 4, 4) + assert hits_miss_currsize(_transform_value) == (5, 10, 10) + assert hits_miss_currsize(_timestamp_to_datetime) == (3, 4, 4) + + utcnow.as_datetime(t) + assert hits_miss_currsize(_is_numeric) == (0, 4, 4) + assert hits_miss_currsize(_transform_value) == (6, 10, 10) + assert hits_miss_currsize(_timestamp_to_datetime) == (4, 4, 4) + + utcnow.get("2020-02-29T03:01:13.000020-00:00") + assert hits_miss_currsize(_is_numeric) == (0, 4, 4) + assert hits_miss_currsize(_transform_value) == (6, 11, 11) + assert hits_miss_currsize(_timestamp_to_datetime) == (4, 4, 4) + + utcnow.get("2020-02-29T03:01:13.000020+00:00") + assert hits_miss_currsize(_is_numeric) == (0, 4, 4) + assert hits_miss_currsize(_transform_value) == (6, 12, 12) + assert hits_miss_currsize(_timestamp_to_datetime) == (4, 4, 4) + + utcnow.get("2020-02-29T04:01:13.000020+01:00") + assert hits_miss_currsize(_is_numeric) == (0, 4, 4) + assert hits_miss_currsize(_transform_value) == (6, 13, 13) + assert hits_miss_currsize(_timestamp_to_datetime) == (4, 4, 4) + + utcnow.as_datetime("2020-02-29T05:01:13.00002+02:00") + assert hits_miss_currsize(_is_numeric) == (0, 4, 4) + assert hits_miss_currsize(_transform_value) == (6, 14, 14) + assert hits_miss_currsize(_timestamp_to_datetime) == (4, 5, 5) + + utcnow.as_datetime("2020-02-29T06:01:13.000020+03:00") + assert hits_miss_currsize(_is_numeric) == (0, 4, 4) + assert hits_miss_currsize(_transform_value) == (6, 15, 15) + assert hits_miss_currsize(_timestamp_to_datetime) == (5, 5, 5) + + utcnow.as_datetime("2020-02-29 03:01:13.000020Z") + assert hits_miss_currsize(_is_numeric) == (0, 4, 4) + assert hits_miss_currsize(_transform_value) == (6, 16, 16) + assert hits_miss_currsize(_timestamp_to_datetime) == (6, 5, 5) + + utcnow.as_datetime(datetime.datetime(2020, 2, 29, 3, 1, 13, 20)) + assert hits_miss_currsize(_is_numeric) == (0, 4, 4) + assert hits_miss_currsize(_transform_value) == (6, 17, 17) + assert hits_miss_currsize(_timestamp_to_datetime) == (7, 5, 5) + + utcnow.as_datetime(datetime.datetime(2020, 2, 29, 3, 1, 13, 20, tzinfo=datetime.timezone.utc)) + assert hits_miss_currsize(_is_numeric) == (0, 4, 4) + assert hits_miss_currsize(_transform_value) == (6, 18, 18) + assert hits_miss_currsize(_timestamp_to_datetime) == (8, 5, 5) + + utcnow.as_datetime(datetime.datetime(2020, 2, 29, 3, 1, 13, 20, tzinfo=datetime.timezone.utc)) + assert hits_miss_currsize(_is_numeric) == (0, 4, 4) + assert hits_miss_currsize(_transform_value) == (7, 18, 18) + assert hits_miss_currsize(_timestamp_to_datetime) == (9, 5, 5) + + utcnow.as_datetime(datetime.datetime(2020, 2, 29, 3, 1, 13, 21, tzinfo=datetime.timezone.utc)) + assert hits_miss_currsize(_is_numeric) == (0, 4, 4) + assert hits_miss_currsize(_transform_value) == (7, 19, 19) + assert hits_miss_currsize(_timestamp_to_datetime) == (9, 6, 6) + + tz = datetime.timezone(offset=datetime.timedelta(hours=-4)) + utcnow.as_datetime(datetime.datetime(2020, 2, 28, 23, 1, 13, 21, tzinfo=tz)) + assert hits_miss_currsize(_is_numeric) == (0, 4, 4) + assert hits_miss_currsize(_transform_value) == (8, 19, 19) + assert hits_miss_currsize(_timestamp_to_datetime) == (10, 6, 6) + + utcnow.as_datetime("2020-02-29 00:00") + assert hits_miss_currsize(_is_numeric) == (0, 4, 4) + assert hits_miss_currsize(_transform_value) == (8, 20, 20) + assert hits_miss_currsize(_timestamp_to_datetime) == (10, 7, 7) + + utcnow.as_datetime(utcnow.get("2020-02-29 00:00")) + assert hits_miss_currsize(_is_numeric) == (0, 4, 4) + assert hits_miss_currsize(_transform_value) == (9, 21, 21) + assert hits_miss_currsize(_timestamp_to_datetime) == (11, 7, 7) + + tz = datetime.timezone(offset=datetime.timedelta(hours=-1)) + utcnow.as_datetime(datetime.datetime(2020, 2, 28, 23, 0, tzinfo=tz)) + assert hits_miss_currsize(_is_numeric) == (0, 4, 4) + assert hits_miss_currsize(_transform_value) == (9, 22, 22) + assert hits_miss_currsize(_timestamp_to_datetime) == (12, 7, 7) + + +def test_cache_hits_with_uniques_loop() -> None: + import utcnow + from utcnow import _is_numeric, _timestamp_to_datetime, _transform_value + + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, 0, 0) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + call_count = 100 + values = set() + + for _ in range(call_count): + values.add(utcnow.get(time.time())) + + assert len(values) == call_count + + assert hits_miss_currsize(_is_numeric) == (0, 0, 0) + assert hits_miss_currsize(_transform_value) == (0, call_count, call_count) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + for _ in range(call_count): + values.add(utcnow.get(str(time.time()))) + + assert len(values) == call_count * 2 + + assert hits_miss_currsize(_is_numeric) == (0, call_count, call_count) + assert hits_miss_currsize(_transform_value) == (0, call_count * 2, 128) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + t = time.time() + for _ in range(call_count): + values.add(utcnow.get(t)) + + assert len(values) == call_count * 2 + 1 + + assert hits_miss_currsize(_is_numeric) == (0, call_count, call_count) + assert hits_miss_currsize(_transform_value) == (call_count - 1, call_count * 2 + 1, 128) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + t_str = str(time.time()) + for _ in range(call_count): + values.add(utcnow.get(t_str)) + + assert len(values) == call_count * 2 + 2 + + assert hits_miss_currsize(_is_numeric) == (0, call_count + 1, call_count + 1) + assert hits_miss_currsize(_transform_value) == ((call_count - 1) * 2, call_count * 2 + 2, 128) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + t_dt = datetime.datetime.utcnow() + for _ in range(call_count): + values.add(utcnow.get(t_dt)) + + assert len(values) == call_count * 2 + 3 + + assert hits_miss_currsize(_is_numeric) == (0, call_count + 1, call_count + 1) + assert hits_miss_currsize(_transform_value) == ((call_count - 1) * 3, call_count * 2 + 3, 128) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) + + for _ in range(call_count): + values.add(utcnow.get(datetime.datetime.utcnow())) + + assert len(values) == call_count * 3 + 3 + + assert hits_miss_currsize(_is_numeric) == (0, call_count + 1, call_count + 1) + assert hits_miss_currsize(_transform_value) == ((call_count - 1) * 3, call_count * 3 + 3, 128) + assert hits_miss_currsize(_timestamp_to_datetime) == (0, 0, 0) diff --git a/tests/test_now.py b/tests/test_now.py new file mode 100644 index 0000000..3376009 --- /dev/null +++ b/tests/test_now.py @@ -0,0 +1,95 @@ +import random + + +def test_now_string() -> None: + import utcnow + + a = utcnow.get() + b = utcnow.get() + c = utcnow.get() + d = utcnow.get() + e = utcnow.get() + f = utcnow.get() + assert a <= b <= c <= d <= e <= f + + +def test_now_datetime() -> None: + import utcnow + + a = utcnow.as_datetime() + b = utcnow.as_datetime() + c = utcnow.as_datetime() + d = utcnow.as_datetime() + e = utcnow.as_datetime() + f = utcnow.as_datetime() + assert a <= b <= c <= d <= e <= f + + +def test_now_list_string() -> None: + import utcnow + + list_a = [utcnow.get() for _ in range(10000)] + list_b = list_a + list_c = list_a[:] + + assert list_a == list_b + assert list_a is list_b + assert list_a == list_c + assert list_b == list_c + assert list_a is not list_c + assert list_b is not list_c + + random.shuffle(list_c) + + assert list_a == list_b + assert list_a is list_b + assert list_a != list_c + assert list_b != list_c + assert list_a is not list_c + assert list_b is not list_c + + list_d = sorted(list_c) + + assert list_a == list_b + assert list_a is list_b + assert list_a == list_d + assert list_b == list_d + assert list_c != list_d + assert list_a is not list_d + assert list_b is not list_d + assert list_c is not list_d + + +def test_now_list_datetime() -> None: + import utcnow + + list_a = [utcnow.as_datetime() for _ in range(10000)] + list_b = list_a + list_c = list_a[:] + + assert list_a == list_b + assert list_a is list_b + assert list_a == list_c + assert list_b == list_c + assert list_a is not list_c + assert list_b is not list_c + + random.shuffle(list_c) + + assert list_a == list_b + assert list_a is list_b + assert list_a != list_c + assert list_b != list_c + assert list_a is not list_c + assert list_b is not list_c + + list_d = sorted(list_c) + + assert list_a == list_b + assert list_a is list_b + assert list_a == list_d + assert list_b == list_d + assert list_c != list_d + assert list_a is not list_d + assert list_b is not list_d + assert list_c is not list_d diff --git a/tests/test_rfc3799.py b/tests/test_rfc3799.py index 52c7060..e788c1a 100644 --- a/tests/test_rfc3799.py +++ b/tests/test_rfc3799.py @@ -50,6 +50,8 @@ def test_to_string_values(value: str, expected_output: str, expect_error: bool) try: assert isinstance(utcnow.as_string(value), str) assert isinstance(utcnow.as_datetime(value), datetime.datetime) + if expect_error: + assert False except Exception: if not expect_error: raise diff --git a/tests/test_unixtime.py b/tests/test_unixtime.py index 3282724..e221b3f 100644 --- a/tests/test_unixtime.py +++ b/tests/test_unixtime.py @@ -83,6 +83,8 @@ def test_unixtime_values(value: Union[int, float, str, Decimal], expected_output try: assert isinstance(utcnow.as_string(value), str) assert isinstance(utcnow.as_datetime(value), datetime.datetime) + if expect_error: + assert False except Exception: if not expect_error: raise diff --git a/utcnow/__init__.py b/utcnow/__init__.py index 1f44035..75413ba 100644 --- a/utcnow/__init__.py +++ b/utcnow/__init__.py @@ -21,21 +21,27 @@ _ACCEPTED_INPUT_FORMAT_VALUES = ( "%Y-%m-%dT%H:%M:%S.%f%z", "%Y-%m-%d %H:%M:%S.%f%z", + "%Y-%m-%dT%H:%M:%S.%f", + "%Y-%m-%d %H:%M:%S.%f", "%Y-%m-%dT%H:%M:%S%z", "%Y-%m-%d %H:%M:%S%z", + "%Y-%m-%dT%H:%M:%S", + "%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M%z", "%Y-%m-%d %H:%M%z", "%Y-%m-%d%z", - "%Y-%m-%dT%H:%M:%S.%f", - "%Y-%m-%d %H:%M:%S.%f", - "%Y-%m-%dT%H:%M:%S", - "%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M", "%Y-%m-%d %H:%M", "%Y-%m-%d", ) NUMERIC_REGEX = re.compile(r"^[-]?([0-9]+|[.][0-9]+|[0-9]+[.]|[0-9]+[.][0-9]+)$") +PREFERRED_FORMAT_REGEX = re.compile( + r"^[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt ]([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9].[0-9]{6}([Zz]|[+-]00:00|)$" +) + + +utc = UTC = timezone_.utc @functools.lru_cache(maxsize=128, typed=False) @@ -46,18 +52,16 @@ def _is_numeric(value: str_) -> bool: return False -def _transform_value(value: Union[str_, datetime_, object, int, float, Decimal, Real] = _SENTINEL) -> str_: - if value is _SENTINEL: - return datetime_.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%f") + "Z" - +@functools.lru_cache(maxsize=128, typed=True) +def _transform_value(value: Union[str_, datetime_, object, int, float, Decimal, Real]) -> str_: str_value: str_ try: if isinstance(value, str_): str_value = value.strip() elif isinstance(value, (int, float)): - str_value = datetime_.utcfromtimestamp(value).isoformat() + "Z" + return datetime_.utcfromtimestamp(value).isoformat(timespec="microseconds") + "Z" elif isinstance(value, (Decimal, Real)): - str_value = datetime_.utcfromtimestamp(float(value)).isoformat() + "Z" + str_value = datetime_.utcfromtimestamp(float(value)).isoformat(timespec="microseconds") + "Z" else: str_value = str_(value).strip() @@ -70,16 +74,23 @@ def _transform_value(value: Union[str_, datetime_, object, int, float, Decimal, and str_value.count("-") <= 1 and _is_numeric(str_value) ): - str_value = datetime_.utcfromtimestamp(float(str_value)).isoformat() + "Z" + str_value = datetime_.utcfromtimestamp(float(str_value)).isoformat(timespec="microseconds") + "Z" except Exception: raise ValueError(f"Input value '{value}' (type: {value.__class__}) does not match allowed input format") + if PREFERRED_FORMAT_REGEX.match(str_value): + if int(str_value[8:10]) >= 30 or (int(str_value[5:7]) == 2 and int(str_value[8:10]) >= 28): + try: + dt_value = datetime_.strptime(str_value[0:10], "%Y-%m-%d") + except ValueError: + raise ValueError(f"Input value '{value}' (type: {value.__class__}) does not match allowed input format") + return (str_value[:10] + "T" + str_value[11:]).upper().rstrip("Z").rsplit("+00:00")[0].rsplit("-00:00")[0] + "Z" + ends_with_utc = False if str_value.endswith(" UTC"): str_value = str_value[0:-4] ends_with_utc = True - dt_value = None for format_ in _ACCEPTED_INPUT_FORMAT_VALUES: try: dt_value = datetime_.strptime(str_value, format_) @@ -92,15 +103,19 @@ def _transform_value(value: Union[str_, datetime_, object, int, float, Decimal, ) break - - if not dt_value: + else: raise ValueError(f"Input value '{value}' (type: {value.__class__}) does not match allowed input format") if not dt_value.tzinfo: # Timezone declaration missing, skipping tz application and blindly assuming UTC - return dt_value.strftime("%Y-%m-%dT%H:%M:%S.%f") + "Z" + return dt_value.isoformat(timespec="microseconds") + "Z" + + return dt_value.astimezone(UTC).isoformat(timespec="microseconds").replace("+00:00", "Z") + - return dt_value.astimezone(timezone_.utc).strftime("%Y-%m-%dT%H:%M:%S.%f") + "Z" +@functools.lru_cache(maxsize=128) +def _timestamp_to_datetime(value: str_) -> datetime_: + return datetime_.strptime(value, "%Y-%m-%dT%H:%M:%S.%f%z") class _metaclass(type): @@ -114,7 +129,9 @@ class _baseclass(metaclass=_metaclass): def __init__(self) -> None: pass - def __call__(self, value: Union[str_, datetime_, object] = _SENTINEL) -> str_: + def __call__(self, value: Union[str_, datetime_, object, int, float, Decimal, Real] = _SENTINEL) -> str_: + if value is _SENTINEL: + return datetime_.utcnow().isoformat(timespec="microseconds") + "Z" return _transform_value(value) @@ -125,6 +142,8 @@ def __new__(cls, *args: Any) -> utcnow_: return result def as_string(self, value: Union[str_, datetime_, object, int, float, Decimal, Real] = _SENTINEL) -> str_: + if value is _SENTINEL: + return datetime_.utcnow().isoformat(timespec="microseconds") + "Z" return _transform_value(value) as_str = as_string @@ -140,15 +159,22 @@ def as_string(self, value: Union[str_, datetime_, object, int, float, Decimal, R str = as_string def as_datetime(self, value: Union[str_, datetime_, object, int, float, Decimal, Real] = _SENTINEL) -> datetime_: - return datetime_.strptime(_transform_value(value), "%Y-%m-%dT%H:%M:%S.%f%z") + if value is _SENTINEL: + # return datetime_.utcnow().replace(tzinfo=UTC) + return datetime_.now(UTC) + return _timestamp_to_datetime(_transform_value(value)) as_date = as_datetime + as_dt = as_datetime to_datetime = as_datetime to_date = as_datetime + to_dt = as_datetime get_datetime = as_datetime get_date = as_datetime + get_dt = as_datetime datetime = as_datetime date = as_datetime + dt = as_datetime def __str__(self) -> str_: return self.as_string() @@ -191,12 +217,16 @@ def __new__(cls, *args: Any) -> _module: as_datetime = _module_value.as_datetime as_date = as_datetime +as_dt = as_datetime to_datetime = as_datetime to_date = as_datetime +to_dt = as_datetime get_datetime = as_datetime get_date = as_datetime +get_dt = as_datetime datetime = as_datetime date = as_datetime +dt = as_datetime __all__ = [ "__version__", diff --git a/utcnow/__version_data__.py b/utcnow/__version_data__.py index 6b62c99..d3cbf7f 100644 --- a/utcnow/__version_data__.py +++ b/utcnow/__version_data__.py @@ -1,6 +1,6 @@ from typing import Tuple, Union -__version_info__: Tuple[Union[int, str], ...] = (0, 2, 1) +__version_info__: Tuple[Union[int, str], ...] = (0, 2, 2) __version__: str = "".join([".{}".format(str(n)) if type(n) is int else str(n) for n in __version_info__]).replace( ".", "", 1 if type(__version_info__[0]) is int else 0 )