From c8793d50e8274a38e0ba7a8297b7455b561b4dd6 Mon Sep 17 00:00:00 2001 From: chaseastewart <153762074+chaseastewart@users.noreply.github.com> Date: Tue, 30 Jan 2024 04:00:27 -0600 Subject: [PATCH] Additional date hardening / test cases. --- fhir_converter/filters.py | 45 ++++++++++----- tests/test_filters.py | 114 +++++++++++++++++++++++++++++++++++--- 2 files changed, 139 insertions(+), 20 deletions(-) diff --git a/fhir_converter/filters.py b/fhir_converter/filters.py index 946ba4b..adaf048 100644 --- a/fhir_converter/filters.py +++ b/fhir_converter/filters.py @@ -1,7 +1,8 @@ from base64 import b64encode from datetime import datetime, timezone -from functools import wraps +from functools import partial, wraps from hashlib import sha1, sha256 +from re import Match from re import findall as re_findall from re import sub as re_sub from typing import Any, Callable, Iterable, List, Mapping, Sequence, Tuple @@ -34,7 +35,7 @@ FilterT = Callable[..., Any] """Callable[..., Any]: A liquid filter function""" -FORMAT_MAP = frozendict( +FORMAT_MAP: frozendict[str, str] = frozendict( { "yyyy": "%Y", "MM": "%m", @@ -43,12 +44,36 @@ "mm": "%M", "ss": "%S", "%K": "%Z", + "zzz": "%Z", + "zz": "%h", } ) -"""Dict[str, str]: C# format mappings""" +"""frozendict[str, str]: C# format mappings""" -ISO_FORMATING_MAP = frozendict({"fff": lambda dt: "%03d" % (dt.microsecond // 1000)}) -"""Dict[str, Callable[[datetime], str]]: ISO formatting map""" +FORMAT_PATTERN = "|".join(["y+", "M+", "d+", "H+", "m+", "s+", "%K", "z+"]) +"""C# Date Format Regex Pattern""" + +ISO_FORMATING_MAP: frozendict[str, Callable[[datetime], str]] = frozendict( + { + "ffffff": lambda dt: "%06d" % dt.microsecond, + "fff": lambda dt: "%03d" % (dt.microsecond // 1000), + } +) +"""frozendict[str, Callable[[datetime], str]]: ISO formatting map""" + +ISO_FORMAT_PATTERN = "f+" +"""ISO format Regex Pattern""" + + +def _repl_format(m: Match[str]) -> str: + return FORMAT_MAP.get(m.group(0), m.group(0)) + + +def _repl_iso_format(dt: datetime, m: Match[str]) -> str: + format_f = ISO_FORMATING_MAP.get(m.group(0), None) + if format_f is not None: + return format_f(dt) + return m.group(0) def str_arg(val: Any, default: str = "") -> str: @@ -146,16 +171,10 @@ def date(input: str, format: Any = None) -> str: if not format: return datetime_isoformat(parse_datetime(input)) - format = re_sub( - "|".join(FORMAT_MAP.keys()), - lambda m: FORMAT_MAP[m.group(0)], - format, - ) + format = re_sub(FORMAT_PATTERN, _repl_format, format) dt = parse_datetime(input) return re_sub( - "|".join(ISO_FORMATING_MAP.keys()), - lambda m: ISO_FORMATING_MAP[m.group(0)](dt), - datetime_isoformat(dt, format), + ISO_FORMAT_PATTERN, partial(_repl_iso_format, dt), datetime_isoformat(dt, format) ) diff --git a/tests/test_filters.py b/tests/test_filters.py index b2f557a..b7c5670 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -219,50 +219,150 @@ def test_year(self) -> None: result = self.bound_template.render(dt=self.datetime_with_us, format="yyyy") self.assertEqual(result, "2014") + def test_year_python(self) -> None: + result = self.bound_template.render(dt=self.datetime_with_us, format="%Y") + self.assertEqual(result, "2014") + def test_month(self) -> None: + result = self.bound_template.render(dt=self.datetime_with_us, format="MM") + self.assertEqual(result, "10") + + def test_month_python(self) -> None: + result = self.bound_template.render(dt=self.datetime_with_us, format="%m") + self.assertEqual(result, "10") + + def test_day(self) -> None: + result = self.bound_template.render(dt=self.datetime_with_us, format="dd") + self.assertEqual(result, "09") + + def test_day_python(self) -> None: + result = self.bound_template.render(dt=self.datetime_with_us, format="%d") + self.assertEqual(result, "09") + + def test_hour(self) -> None: + result = self.bound_template.render(dt=self.datetime_with_us, format="HH") + self.assertEqual(result, "11") + + def test_hour_python(self) -> None: + result = self.bound_template.render(dt=self.datetime_with_us, format="%H") + self.assertEqual(result, "11") + + def test_minute(self) -> None: + result = self.bound_template.render(dt=self.datetime_with_us, format="mm") + self.assertEqual(result, "58") + + def test_minute_python(self) -> None: + result = self.bound_template.render(dt=self.datetime_with_us, format="%M") + self.assertEqual(result, "58") + + def test_second(self) -> None: + result = self.bound_template.render(dt=self.datetime_with_us, format="ss") + self.assertEqual(result, "10") + + def test_second_python(self) -> None: + result = self.bound_template.render(dt=self.datetime_with_us, format="%S") + self.assertEqual(result, "10") + + def test_milli(self) -> None: + result = self.bound_template.render(dt=self.datetime_with_us, format="fff") + self.assertEqual(result, "001") + + def test_micro(self) -> None: + result = self.bound_template.render(dt=self.datetime_with_us, format="ffffff") + self.assertEqual(result, "001981") + + def test_micro_python(self) -> None: + result = self.bound_template.render(dt=self.datetime_with_us, format="%f") + self.assertEqual(result, "001981") + + def test_k_specifier_tz_undefined(self) -> None: + result = self.bound_template.render(dt="2014-10-09T11:58:10", format="%K") + self.assertEqual(result, "") + + def test_k_specifier_tz_offset(self) -> None: + result = self.bound_template.render(dt=self.datetime_with_us, format="%K") + self.assertEqual(result, "+08:00") + + def test_k_specifier_tz_offset_zero(self) -> None: + result = self.bound_template.render(dt="2014-10-09T11:58:10+00:00", format="%K") + self.assertEqual(result, "Z") + + def test_k_specifier_tz_utc(self) -> None: + result = self.bound_template.render(dt="2014-10-09T11:58:10Z", format="%K") + self.assertEqual(result, "Z") + + def test_tz_hour_min_offset(self) -> None: + result = self.bound_template.render(dt=self.datetime_with_us, format="zzz") + self.assertEqual(result, "+08:00") + + def test_tz_hour_min_offset_python(self) -> None: + result = self.bound_template.render(dt=self.datetime_with_us, format="%Z") + self.assertEqual(result, "+08:00") + + def test_tz_hour_offset(self) -> None: + result = self.bound_template.render(dt=self.datetime_with_us, format="zz") + self.assertEqual(result, "+08") + + def test_tz_hour_offset_python(self) -> None: + result = self.bound_template.render(dt=self.datetime_with_us, format="%h") + self.assertEqual(result, "+08") + + def test_year_month(self) -> None: result = self.bound_template.render(dt=self.datetime_with_us, format="yyyy-MM") self.assertEqual(result, "2014-10") - def test_day(self) -> None: + def test_year_month_day(self) -> None: result = self.bound_template.render(dt=self.datetime_with_us, format="yyyy-MM-dd") self.assertEqual(result, "2014-10-09") - def test_hour(self) -> None: + def test_year_month_day_hour(self) -> None: result = self.bound_template.render( dt=self.datetime_with_us, format="yyyy-MM-ddTHH" ) self.assertEqual(result, "2014-10-09T11") - def test_minute(self) -> None: + def test_year_month_day_hour_minute(self) -> None: result = self.bound_template.render( dt=self.datetime_with_us, format="yyyy-MM-ddTHH:mm" ) self.assertEqual(result, "2014-10-09T11:58") - def test_second(self) -> None: + def test_year_month_day_hour_minute_second(self) -> None: result = self.bound_template.render( dt=self.datetime_with_us, format="yyyy-MM-ddTHH:mm:ss" ) self.assertEqual(result, "2014-10-09T11:58:10") - def test_second_tz(self) -> None: + def test_year_month_day_hour_minute_second_tz(self) -> None: result = self.bound_template.render( dt=self.datetime_with_us, format="yyyy-MM-ddTHH:mm:ss%K" ) self.assertEqual(result, "2014-10-09T11:58:10+08:00") - def test_milli(self) -> None: + def test_year_month_day_hour_minute_second_milli(self) -> None: result = self.bound_template.render( dt=self.datetime_with_us, format="yyyy-MM-ddTHH:mm:ss.fff" ) self.assertEqual(result, "2014-10-09T11:58:10.001") - def test_milli_tz(self) -> None: + def test_year_month_day_hour_minute_second_milli_tz(self) -> None: result = self.bound_template.render( dt=self.datetime_with_us, format="yyyy-MM-ddTHH:mm:ss.fff%K" ) self.assertEqual(result, "2014-10-09T11:58:10.001+08:00") + def test_year_month_day_hour_minute_second_milli_micro(self) -> None: + result = self.bound_template.render( + dt=self.datetime_with_us, format="yyyy-MM-ddTHH:mm:ss.ffffff" + ) + self.assertEqual(result, "2014-10-09T11:58:10.001981") + + def test_year_month_day_hour_minute_second_milli_micro_tz(self) -> None: + result = self.bound_template.render( + dt=self.datetime_with_us, format="yyyy-MM-ddTHH:mm:ss.ffffff%K" + ) + self.assertEqual(result, "2014-10-09T11:58:10.001981+08:00") + class NowTest(TestCase, FilterTest): template = """{{ "" | now }}"""