Skip to content

Commit

Permalink
Support unixtime
Browse files Browse the repository at this point in the history
  • Loading branch information
kalaspuff committed Feb 26, 2021
1 parent 89e6ca7 commit 9c2a86a
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 10 deletions.
1 change: 0 additions & 1 deletion tests/test_rfc3799.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
("2021-02-28 10:10:59.123987 Europe/Stockholm", "", True),
("2021/02/28", "", True),
("21-02-28 10:10:59.123987+00:00", "", True),
("2021", "", True),
("2021-02", "", True),
("2021-02-30", "", True),
("1900-01-01 20:30.123", "", True),
Expand Down
102 changes: 102 additions & 0 deletions tests/test_unixtime.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import datetime
from decimal import Decimal
from typing import Union

import pytest


@pytest.mark.parametrize(
"value, expected_output, expect_error",
[
(0, "1970-01-01T00:00:00.000000Z", False),
(0.0, "1970-01-01T00:00:00.000000Z", False),
(0.000000, "1970-01-01T00:00:00.000000Z", False),
(0.000001, "1970-01-01T00:00:00.000001Z", False),
(0.000001, "1970-01-01T00:00:00.000001Z", False),
(0.01, "1970-01-01T00:00:00.010000Z", False),
(1, "1970-01-01T00:00:01.000000Z", False),
(1.0, "1970-01-01T00:00:01.000000Z", False),
(1338, "1970-01-01T00:22:18.000000Z", False),
(4711 * 3.14, "1970-01-01T04:06:32.540000Z", False),
("0", "1970-01-01T00:00:00.000000Z", False),
("0.00", "1970-01-01T00:00:00.000000Z", False),
("0.000109", "1970-01-01T00:00:00.000109Z", False),
(".000109", "1970-01-01T00:00:00.000109Z", False),
("1614300199.462145", "2021-02-26T00:43:19.462145Z", False),
("1614300199.", "2021-02-26T00:43:19.000000Z", False),
(".9919", "1970-01-01T00:00:00.991900Z", False),
("-0", "1970-01-01T00:00:00.000000Z", False),
("-1", "1969-12-31T23:59:59.000000Z", False),
("-0.000109", "1969-12-31T23:59:59.999891Z", False),
("-.000109", "1969-12-31T23:59:59.999891Z", False),
("-1614300199.462145", "1918-11-05T23:16:40.537855Z", False),
("-1614300199.", "1918-11-05T23:16:41.000000Z", False),
("-.9919", "1969-12-31T23:59:59.008100Z", False),
(0, "1970-01-01T00:00:00.000000Z", False),
(0.00, "1970-01-01T00:00:00.000000Z", False),
(0.000109, "1970-01-01T00:00:00.000109Z", False),
(0.000109, "1970-01-01T00:00:00.000109Z", False),
(1614300199.462145, "2021-02-26T00:43:19.462145Z", False),
(1614300199.0, "2021-02-26T00:43:19.000000Z", False),
(0.9919, "1970-01-01T00:00:00.991900Z", False),
(-0, "1970-01-01T00:00:00.000000Z", False),
(-1, "1969-12-31T23:59:59.000000Z", False),
(-0.000109, "1969-12-31T23:59:59.999891Z", False),
(-0.000109, "1969-12-31T23:59:59.999891Z", False),
(-1614300199.462145, "1918-11-05T23:16:40.537855Z", False),
(-1614300199.0, "1918-11-05T23:16:41.000000Z", False),
(-0.9919, "1969-12-31T23:59:59.008100Z", False),
(Decimal("0"), "1970-01-01T00:00:00.000000Z", False),
(Decimal("-0"), "1970-01-01T00:00:00.000000Z", False),
(Decimal("0.00"), "1970-01-01T00:00:00.000000Z", False),
(Decimal("0.000109"), "1970-01-01T00:00:00.000109Z", False),
(Decimal(".000109"), "1970-01-01T00:00:00.000109Z", False),
(Decimal("1614300199.462145"), "2021-02-26T00:43:19.462145Z", False),
(Decimal("1614300199."), "2021-02-26T00:43:19.000000Z", False),
(Decimal(".9919"), "1970-01-01T00:00:00.991900Z", False),
(Decimal("-0"), "1970-01-01T00:00:00.000000Z", False),
(Decimal("-1"), "1969-12-31T23:59:59.000000Z", False),
(Decimal("-0.000109"), "1969-12-31T23:59:59.999891Z", False),
(Decimal("-.000109"), "1969-12-31T23:59:59.999891Z", False),
(Decimal("-1614300199.462145"), "1918-11-05T23:16:40.537855Z", False),
(Decimal("-1614300199."), "1918-11-05T23:16:41.000000Z", False),
(Decimal("-.9919"), "1969-12-31T23:59:59.008100Z", False),
(Decimal("5e5"), "1970-01-06T18:53:20.000000Z", False),
("1.0.0", "", True),
("--1", "", True),
("--", "", True),
("-", "", True),
(".", "", True),
(".-1", "", True),
("1..", "", True),
("..1", "", True),
("..1", "", True),
(float("inf"), "", True),
(Decimal("Infinity"), "", True),
(Decimal("1e100"), "", True),
(Decimal("-1e100"), "", True),
],
)
def test_unixtime_values(value: Union[int, float, str, Decimal], expected_output: str, expect_error: bool) -> None:
import utcnow

try:
assert isinstance(utcnow.as_string(value), str)
assert isinstance(utcnow.as_datetime(value), datetime.datetime)
except Exception:
if not expect_error:
raise
if not expect_error:
# unreachable
assert False

assert True
return

assert utcnow.as_string(value) == expected_output
assert utcnow.as_string(expected_output) == expected_output
assert utcnow.as_string(expected_output) == utcnow.as_string(expected_output)
assert utcnow.as_datetime(value) == utcnow.as_datetime(expected_output)
assert utcnow.utcnow(utcnow.as_datetime(value)) == utcnow.utcnow(utcnow.as_datetime(expected_output))
assert utcnow.utcnow(utcnow.as_datetime(value).replace(tzinfo=None)) == expected_output
assert utcnow.as_string(utcnow.utcnow(utcnow.as_datetime(value))) == expected_output
52 changes: 43 additions & 9 deletions utcnow/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
from __future__ import annotations

import functools
import re
import sys
from datetime import datetime as datetime_
from datetime import timezone as timezone_
from decimal import Decimal
from numbers import Real
from typing import Any, Dict, Tuple, Type, Union, cast

from .__version_data__ import __version__, __version_info__
Expand Down Expand Up @@ -31,17 +35,44 @@
"%Y-%m-%d",
)

NUMERIC_REGEX = re.compile(r"^[-]?([0-9]+|[.][0-9]+|[0-9]+[.]|[0-9]+[.][0-9]+)$")

def _transform_value(value: Union[str_, datetime_, object] = _SENTINEL) -> str_:

@functools.lru_cache(maxsize=128, typed=False)
def _is_numeric(value: str_) -> bool:
if NUMERIC_REGEX.match(value):
return True

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"

if not isinstance(value, str_):
str_value = str_(value)
else:
str_value = value

str_value = str_value.strip()
str_value: str_
try:
if isinstance(value, str_):
str_value = value.strip()
elif isinstance(value, (int, float)):
str_value = datetime_.utcfromtimestamp(value).isoformat() + "Z"
elif isinstance(value, (Decimal, Real)):
str_value = datetime_.utcfromtimestamp(float(value)).isoformat() + "Z"
else:
str_value = str_(value).strip()

if (
str_value
and len(str_value) <= 21
and "T" not in str_value
and ":" not in str_value
and "/" not in str_value
and str_value.count("-") <= 1
and _is_numeric(str_value)
):
str_value = datetime_.utcfromtimestamp(float(str_value)).isoformat() + "Z"
except Exception:
raise ValueError(f"Input value '{value}' (type: {value.__class__}) does not match allowed input format")

ends_with_utc = False
if str_value.endswith(" UTC"):
Expand Down Expand Up @@ -93,7 +124,7 @@ def __new__(cls, *args: Any) -> utcnow_:

return result

def as_string(self, value: Union[str_, datetime_, object] = _SENTINEL) -> str_:
def as_string(self, value: Union[str_, datetime_, object, int, float, Decimal, Real] = _SENTINEL) -> str_:
return _transform_value(value)

as_str = as_string
Expand All @@ -108,7 +139,7 @@ def as_string(self, value: Union[str_, datetime_, object] = _SENTINEL) -> str_:
string = as_string
str = as_string

def as_datetime(self, value: Union[str_, datetime_, object] = _SENTINEL) -> datetime_:
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")

as_date = as_datetime
Expand All @@ -133,6 +164,7 @@ class _module(utcnow_):
__email__: str_ = __email__

utcnow = utcnow_()
now = utcnow_()

def __new__(cls, *args: Any) -> _module:
result = cast(_module, object.__new__(cls, *args))
Expand All @@ -142,6 +174,7 @@ def __new__(cls, *args: Any) -> _module:

_module_value = _module()
utcnow = _module_value.utcnow
now = _module_value.now

as_string = _module_value.as_string
as_str = as_string
Expand Down Expand Up @@ -171,6 +204,7 @@ def __new__(cls, *args: Any) -> _module:
"__author__",
"__email__",
"utcnow",
"now",
"as_string",
"as_str",
"as_timestamp",
Expand Down

0 comments on commit 9c2a86a

Please sign in to comment.