From 6137a25ac6a47e444d382f7ec9ebac54153a9a5d Mon Sep 17 00:00:00 2001 From: idoraban Date: Wed, 2 Aug 2023 09:09:34 +0000 Subject: [PATCH] Implemented date.parse and date.format procedures Also added e2e tests for them. --- e2e/date_test/test_format1/input.cyp | 0 e2e/date_test/test_format1/test.yml | 6 ++ e2e/date_test/test_format2/input.cyp | 0 e2e/date_test/test_format2/test.yml | 6 ++ e2e/date_test/test_format3/input.cyp | 0 e2e/date_test/test_format3/test.yml | 6 ++ e2e/date_test/test_parse1/input.cyp | 0 e2e/date_test/test_parse1/test.yml | 6 ++ e2e/date_test/test_parse2/input.cyp | 0 e2e/date_test/test_parse2/test.yml | 6 ++ e2e/date_test/test_parse3/input.cyp | 0 e2e/date_test/test_parse3/test.yml | 6 ++ python/date.py | 124 +++++++++++++++++++++++++++ python/mage/date/constants.py | 12 +++ 14 files changed, 172 insertions(+) create mode 100644 e2e/date_test/test_format1/input.cyp create mode 100644 e2e/date_test/test_format1/test.yml create mode 100644 e2e/date_test/test_format2/input.cyp create mode 100644 e2e/date_test/test_format2/test.yml create mode 100644 e2e/date_test/test_format3/input.cyp create mode 100644 e2e/date_test/test_format3/test.yml create mode 100644 e2e/date_test/test_parse1/input.cyp create mode 100644 e2e/date_test/test_parse1/test.yml create mode 100644 e2e/date_test/test_parse2/input.cyp create mode 100644 e2e/date_test/test_parse2/test.yml create mode 100644 e2e/date_test/test_parse3/input.cyp create mode 100644 e2e/date_test/test_parse3/test.yml create mode 100644 python/date.py create mode 100644 python/mage/date/constants.py diff --git a/e2e/date_test/test_format1/input.cyp b/e2e/date_test/test_format1/input.cyp new file mode 100644 index 000000000..e69de29bb diff --git a/e2e/date_test/test_format1/test.yml b/e2e/date_test/test_format1/test.yml new file mode 100644 index 000000000..a0acdc3a3 --- /dev/null +++ b/e2e/date_test/test_format1/test.yml @@ -0,0 +1,6 @@ +query: > + CALL date.format(74976, "h", "%Y/%m/%d %H:%M:%S %Z", "Mexico/BajaNorte") YIELD formatted + RETURN formatted + +output: + - formatted: "1978/07/21 17:00:00 PDT" diff --git a/e2e/date_test/test_format2/input.cyp b/e2e/date_test/test_format2/input.cyp new file mode 100644 index 000000000..e69de29bb diff --git a/e2e/date_test/test_format2/test.yml b/e2e/date_test/test_format2/test.yml new file mode 100644 index 000000000..aefd7e249 --- /dev/null +++ b/e2e/date_test/test_format2/test.yml @@ -0,0 +1,6 @@ +query: > + CALL date.format(74976002900, "ms", "%Y/%m/%d %H:%M:%S %Z", "Australia/Broken_Hill") YIELD formatted + RETURN formatted + +output: + - formatted: "1972/05/18 04:10:02 ACST" diff --git a/e2e/date_test/test_format3/input.cyp b/e2e/date_test/test_format3/input.cyp new file mode 100644 index 000000000..e69de29bb diff --git a/e2e/date_test/test_format3/test.yml b/e2e/date_test/test_format3/test.yml new file mode 100644 index 000000000..d7bd7cc24 --- /dev/null +++ b/e2e/date_test/test_format3/test.yml @@ -0,0 +1,6 @@ +query: > + CALL date.format(7491, "d", "%Y/%m/%d %H:%M:%S %z", "Brazil/DeNoronha") YIELD formatted + RETURN formatted + +output: + - formatted: "1990/07/05 22:00:00 -0200" diff --git a/e2e/date_test/test_parse1/input.cyp b/e2e/date_test/test_parse1/input.cyp new file mode 100644 index 000000000..e69de29bb diff --git a/e2e/date_test/test_parse1/test.yml b/e2e/date_test/test_parse1/test.yml new file mode 100644 index 000000000..5585e1b47 --- /dev/null +++ b/e2e/date_test/test_parse1/test.yml @@ -0,0 +1,6 @@ +query: > + CALL date.parse("2023/08/03 14:30:00", "h", "%Y/%m/%d %H:%M:%S", "Europe/Zagreb") YIELD parsed + RETURN parsed + +output: + - parsed: 469740 diff --git a/e2e/date_test/test_parse2/input.cyp b/e2e/date_test/test_parse2/input.cyp new file mode 100644 index 000000000..e69de29bb diff --git a/e2e/date_test/test_parse2/test.yml b/e2e/date_test/test_parse2/test.yml new file mode 100644 index 000000000..efa02c90d --- /dev/null +++ b/e2e/date_test/test_parse2/test.yml @@ -0,0 +1,6 @@ +query: > + CALL date.parse("21 June, 2018", "d", "%d %B, %Y", "US/Samoa") YIELD parsed + RETURN parsed + +output: + - parsed: 17703 diff --git a/e2e/date_test/test_parse3/input.cyp b/e2e/date_test/test_parse3/input.cyp new file mode 100644 index 000000000..e69de29bb diff --git a/e2e/date_test/test_parse3/test.yml b/e2e/date_test/test_parse3/test.yml new file mode 100644 index 000000000..042ce2085 --- /dev/null +++ b/e2e/date_test/test_parse3/test.yml @@ -0,0 +1,6 @@ +query: > + CALL date.parse("14-Feb-1985 01:02:03", "s", "%d-%b-%Y %H:%M:%S", "Pacific/Chatham") YIELD parsed + RETURN parsed + +output: + - parsed: 477141423 diff --git a/python/date.py b/python/date.py new file mode 100644 index 000000000..43783eefa --- /dev/null +++ b/python/date.py @@ -0,0 +1,124 @@ +import mgp +import pytz +import datetime + +from mage.date.constants import Conversion, Epoch + + +def getOffset(timezone, date): + offset = pytz.timezone(timezone).utcoffset(date) + if offset.days == 1: + return ( + datetime.timedelta( + minutes=offset.seconds // Conversion.SECONDS_IN_MINUTE + + Conversion.HOURS_IN_DAY * Conversion.MINUTES_IN_HOUR + ), + False, + ) + elif offset.days == -1: + return ( + datetime.timedelta( + minutes=Conversion.HOURS_IN_DAY * Conversion.MINUTES_IN_HOUR + - offset.seconds // Conversion.SECONDS_IN_MINUTE + ), + True, + ) + return ( + datetime.timedelta(minutes=offset.seconds // Conversion.SECONDS_IN_MINUTE), + False, + ) + + +@mgp.read_proc +def parse( + time: str, + unit: str = "ms", + format: str = "%Y-%m-%d %H:%M:%S", + timezone: str = "UTC", +) -> mgp.Record(parsed=int): + first_date = Epoch.UNIX_EPOCH + input_date = datetime.datetime.strptime(time, format) + + if timezone not in pytz.all_timezones: + raise Exception( + "Timezone doesn't exist. Check documentation to see available timezones." + ) + + offset, add = getOffset(timezone, input_date) + tz_input = input_date + offset if add else input_date - offset + + time_since = tz_input - first_date + + if unit == "ms": + parsed = ( + time_since.days + * Conversion.HOURS_IN_DAY + * Conversion.MINUTES_IN_HOUR + * Conversion.SECONDS_IN_MINUTE + * Conversion.MILLISECONDS_IN_SECOND + + time_since.seconds * Conversion.MILLISECONDS_IN_SECOND + ) + elif unit == "s": + parsed = ( + time_since.days + * Conversion.HOURS_IN_DAY + * Conversion.MINUTES_IN_HOUR + * Conversion.SECONDS_IN_MINUTE + + time_since.seconds + ) + elif unit == "m": + parsed = ( + time_since.days * Conversion.HOURS_IN_DAY * Conversion.MINUTES_IN_HOUR + + time_since.seconds // Conversion.SECONDS_IN_MINUTE + ) + elif unit == "h": + parsed = ( + time_since.days * Conversion.HOURS_IN_DAY + + time_since.seconds + // Conversion.SECONDS_IN_MINUTE + // Conversion.MINUTES_IN_HOUR + ) + elif unit == "d": + parsed = time_since.days + else: + raise Exception( + "Unit doesn't exist. Check documentation to see available units." + ) + + return mgp.Record(parsed=parsed) + + +@mgp.read_proc +def format( + time: int, + unit: str = "ms", + format: str = "%Y-%m-%d %H:%M:%S %Z", + timezone: str = "UTC", +) -> mgp.Record(formatted=str): + first_date = Epoch.UNIX_EPOCH + + if unit == "ms": + new_date = first_date + datetime.timedelta(milliseconds=time) + elif unit == "s": + new_date = first_date + datetime.timedelta(seconds=time) + elif unit == "m": + new_date = first_date + datetime.timedelta(minutes=time) + elif unit == "h": + new_date = first_date + datetime.timedelta(hours=time) + elif unit == "d": + new_date = first_date + datetime.timedelta(days=time) + else: + raise Exception( + "Unit doesn't exist. Check documentation to see available units." + ) + + if timezone not in pytz.all_timezones: + raise Exception( + "Timezone doesn't exist. Check documentation to see available timezones." + ) + offset, subtract = getOffset(timezone, new_date) + tz_new = new_date - offset if subtract else new_date + offset + + return mgp.Record( + formatted=pytz.timezone(timezone).localize(tz_new).strftime(format) + ) diff --git a/python/mage/date/constants.py b/python/mage/date/constants.py new file mode 100644 index 000000000..77faade77 --- /dev/null +++ b/python/mage/date/constants.py @@ -0,0 +1,12 @@ +import datetime + + +class Conversion(int): + MINUTES_IN_HOUR = 60 + SECONDS_IN_MINUTE = 60 + MILLISECONDS_IN_SECOND = 1000 + HOURS_IN_DAY = 24 + + +class Epoch(datetime.datetime): + UNIX_EPOCH = datetime.datetime(1970, 1, 1, 0, 0, 0)