From dea45233b3654dec9a3b47d320e3c743625c9d3c Mon Sep 17 00:00:00 2001 From: jianfengmao Date: Sun, 12 Dec 2021 16:21:06 -0700 Subject: [PATCH 01/12] hand-wrapped datetimeutils module, fixes #1640 --- pyintegration/deephaven2/column.py | 2 +- pyintegration/deephaven2/constants.py | 58 +- pyintegration/deephaven2/datetimeutils.py | 803 ++++++++++++++++++ pyintegration/deephaven2/dtypes/__init__.py | 23 + pyintegration/deephaven2/dtypes/_datetime.py | 108 +++ .../{dtypes.py => dtypes/_dtype.py} | 32 +- pyintegration/deephaven2/table.py | 13 +- pyintegration/deephaven2/table_factory.py | 21 +- pyintegration/tests/test_datetimeutils.py | 228 +++++ pyintegration/tests/test_dtypes.py | 11 +- pyintegration/tests/test_table.py | 2 +- pyintegration/tests/test_table_factory.py | 1 - 12 files changed, 1255 insertions(+), 47 deletions(-) create mode 100644 pyintegration/deephaven2/datetimeutils.py create mode 100644 pyintegration/deephaven2/dtypes/__init__.py create mode 100644 pyintegration/deephaven2/dtypes/_datetime.py rename pyintegration/deephaven2/{dtypes.py => dtypes/_dtype.py} (82%) create mode 100644 pyintegration/tests/test_datetimeutils.py diff --git a/pyintegration/deephaven2/column.py b/pyintegration/deephaven2/column.py index e7299ba43a7..5a8bd7eb914 100644 --- a/pyintegration/deephaven2/column.py +++ b/pyintegration/deephaven2/column.py @@ -6,9 +6,9 @@ from typing import Sequence import jpy -from deephaven2 import DHError import deephaven2.dtypes as dtypes +from deephaven2 import DHError from deephaven2.dtypes import DType _JColumnHeader = jpy.get_type("io.deephaven.qst.column.header.ColumnHeader") diff --git a/pyintegration/deephaven2/constants.py b/pyintegration/deephaven2/constants.py index e83e14f1852..758b30388a5 100644 --- a/pyintegration/deephaven2/constants.py +++ b/pyintegration/deephaven2/constants.py @@ -5,6 +5,9 @@ from enum import Enum, auto import jpy +_JQueryConstants = jpy.get_type("io.deephaven.util.QueryConstants") +_JTimeZone = jpy.get_type("io.deephaven.time.TimeZone") + class SortDirection(Enum): """An enum defining the sorting orders.""" @@ -15,8 +18,6 @@ class SortDirection(Enum): # Deephaven Special Null values for primitive types -_JQueryConstants = jpy.get_type("io.deephaven.util.QueryConstants") - NULL_BOOLEAN = _JQueryConstants.NULL_BOOLEAN """ Null boolean value. """ NULL_CHAR = _JQueryConstants.NULL_CHAR @@ -92,3 +93,56 @@ class SortDirection(Enum): MIN_POS_DOUBLE = _JQueryConstants.MIN_POS_DOUBLE """ Minimum positive value of type double. """ + +class TimeZone(Enum): + """ A Enum for known time zones. """ + NY = _JTimeZone.TZ_NY + """ America/New_York """ + ET = _JTimeZone.TZ_ET + """ America/New_York """ + MN = _JTimeZone.TZ_MN + """ America/Chicago """ + CT = _JTimeZone.TZ_CT + """ America/Chicago """ + MT = _JTimeZone.TZ_MT + """ America/Denver """ + PT = _JTimeZone.TZ_PT + """ America/Los_Angeles """ + HI = _JTimeZone.TZ_HI + """ Pacific/Honolulu """ + BT = _JTimeZone.TZ_BT + """ America/Sao_Paulo """ + KR = _JTimeZone.TZ_KR + """ Asia/Seoul """ + HK = _JTimeZone.TZ_HK + """ Asia/Hong_Kong """ + JP = _JTimeZone.TZ_JP + """ Asia/Tokyo """ + AT = _JTimeZone.TZ_AT + """ Canada/Atlantic """ + NF = _JTimeZone.TZ_NF + """ Canada/Newfoundland """ + AL = _JTimeZone.TZ_AL + """ America/Anchorage """ + IN = _JTimeZone.TZ_IN + """ Asia/Kolkata """ + CE = _JTimeZone.TZ_CE + """ Europe/Berlin """ + SG = _JTimeZone.TZ_SG + """ Asia/Singapore """ + LON = _JTimeZone.TZ_LON + """ Europe/London """ + MOS = _JTimeZone.TZ_MOS + """ Europe/Moscow """ + SHG = _JTimeZone.TZ_SHG + """ Asia/Shanghai """ + CH = _JTimeZone.TZ_CH + """ Europe/Zurich """ + NL = _JTimeZone.TZ_NL + """ Europe/Amsterdam """ + TW = _JTimeZone.TZ_TW + """ Asia/Taipei """ + SYD = _JTimeZone.TZ_SYD + """ Australia/Sydney """ + UTC = _JTimeZone.TZ_UTC + """ UTC """ diff --git a/pyintegration/deephaven2/datetimeutils.py b/pyintegration/deephaven2/datetimeutils.py new file mode 100644 index 00000000000..abc7c7117fe --- /dev/null +++ b/pyintegration/deephaven2/datetimeutils.py @@ -0,0 +1,803 @@ +# +# Copyright (c) 2016-2021 Deephaven Data Labs and Patent Pending +# +""" Utilities for Deephaven date/time storage and manipulation.""" + +import jpy + +from deephaven2 import DHError +from deephaven2.dtypes import DType +from deephaven2.dtypes import DateTime, Period +from deephaven2.constants import TimeZone + +SECOND = 1000000000 #: One second in nanoseconds. +MINUTE = 60 * SECOND #: One minute in nanoseconds. +HOUR = 60 * MINUTE #: One hour in nanoseconds. +DAY = 24 * HOUR #: One day in nanoseconds. +WEEK = 7 * DAY #: One week in nanoseconds. +YEAR = 52 * WEEK #: One year in nanoseconds. + +_JDateTimeUtils = jpy.get_type("io.deephaven.time.DateTimeUtils") + + +def convert_datetime(s: str, quiet: bool = False) -> DateTime: + """ Converts a datetime string from a few specific zoned formats to a DateTime object + + Args: + s (str): usually in the form yyyy-MM-ddThh:mm:ss and with optional sub-seconds after an + optional decimal point, followed by a mandatory time zone character code + quiet (bool): to return None or raise an exception if the string can't be parsed, default is False + + Returns: + a DateTime + + Raises: + DHError + """ + if quiet: + return DateTime(_JDateTimeUtils.convertDateTimeQuiet(s)) + + try: + return DateTime(_JDateTimeUtils.convertDateTime(s)) + except Exception as e: + raise DHError(e) from e + + +def convert_period(s: str, quiet: bool = False) -> Period: + """ Converts a period string into a Period object. + + Args: + s (str): in the form of number+type, e.g. 1W for one week, or T+number+type, e.g. T1M for one minute + quiet (bool): to return None or raise an exception if the string can't be parsed, default is False + + Returns: + Period + + Raises: + DHError + """ + if quiet: + return Period(_JDateTimeUtils.convertPeriodQuiet(s)) + + try: + return Period(_JDateTimeUtils.convertPeriod(s)) + except Exception as e: + raise DHError(e) from e + + +def convert_time(s, quiet: bool = False) -> int: + """ Converts a string time to nanoseconds from Epoch. + + Args: + s (str): in the format of: hh:mm:ss[.nnnnnnnnn] + quiet (bool): to return None or raise an exception if the string can't be parsed, default is False + + Returns: + int + + Raises: + DHError + """ + if quiet: + return _JDateTimeUtils.convertTimeQuiet(s) + + try: + return _JDateTimeUtils.convertTime(s) + except Exception as e: + raise DHError(e) from e + + +def current_time() -> DateTime: + """ Provides the current date/time, or, if a custom timeProvider has been configured, provides the current + time according to the custom provider. + + Returns: + DateTime + + Raises: + DHError + """ + try: + return DateTime(_JDateTimeUtils.currentTime()) + except Exception as e: + raise DHError(e) from e + + +def date_at_midnight(dt: DateTime, tz: TimeZone) -> DateTime: + """ Returns a DateTime for the requested DateTime at midnight in the specified time zone. + + Args: + dt (DateTime) - DateTime for which the new value at midnight should be calculated + tz: (TimeZone) - TimeZone for which the new value at midnight should be calculated + + Returns: + DateTime + + Raises: + DHError + """ + try: + j_datetime = dt.j_datetime if dt else None + return DateTime(_JDateTimeUtils.dateAtMidnight(j_datetime, tz.value)) + except Exception as e: + raise DHError(e) from e + + +def day_of_month(dt: DateTime, tz: TimeZone) -> int: + """ Returns an int value of the day of the month for a DateTime and specified time zone. + + Args: + dt (DateTime): the DateTime for which to find the day of the month + tz (TimeZone): the TimeZone to use when interpreting the date/time + + Returns: + int + + Raises: + DHError + """ + try: + j_datetime = dt.j_datetime if dt else None + return _JDateTimeUtils.dayOfMonth(j_datetime, tz.value) + except Exception as e: + raise DHError(e) from e + + +def day_of_week(dt: DateTime, tz: TimeZone) -> int: + """ Returns an int value of the day of the week for a DateTime in the specified time zone, with 1 being + Monday and 7 being Sunday. + + Args: + dt (DateTime): the DateTime for which to find the day of the week. + tz (TimeZone): the TimeZone to use when interpreting the date/time. + + Returns: + int + + Raises: + DHError + """ + try: + j_datetime = dt.j_datetime if dt else None + return _JDateTimeUtils.dayOfWeek(j_datetime, tz.value) + except Exception as e: + raise DHError(e) from e + + +def day_of_year(dt: DateTime, tz: TimeZone) -> int: + """ Returns an int value of the day of the year (Julian date) for a DateTime in the specified time zone. + + Args: + dt (DateTime): the DateTime for which to find the day of the year + tz (TimeZone): the TimeZone to use when interpreting the date/time + + Returns: + int + + Raises: + DHError + """ + try: + j_datetime = dt.j_datetime if dt else None + return _JDateTimeUtils.dayOfYear(j_datetime, tz.value) + except Exception as e: + raise DHError(e) from e + + +def diff_nanos(dt1: DateTime, dt2: DateTime) -> int: + """ Returns the difference in nanoseconds between two DateTime values. + + Args: + dt1 (DateTime): the 1st DateTime + dt2 (DateTime): the 2nd DateTime + + Returns: + int + + Raises: + DHError + """ + try: + j_datetime1 = dt1.j_datetime if dt1 else None + j_datetime2 = dt2.j_datetime if dt2 else None + return _JDateTimeUtils.diffNanos(j_datetime1, j_datetime2) + except Exception as e: + raise DHError(e) from e + + +def format_datetime(dt: DateTime, tz: TimeZone) -> str: + """ Returns a string date/time representation formatted as yyyy-MM-ddThh:mm:ss.nnnnnnnnn TZ. + + Args: + dt (DateTime): the DateTime to format as a string + tz (TimeZone): the TimeZone to use when interpreting the date/time + + Returns: + str + + Raises: + DHError + """ + try: + j_datetime = dt.j_datetime if dt else None + return _JDateTimeUtils.format(j_datetime, tz.value) + except Exception as e: + raise DHError(e) from e + + +def format_nanos(ns: int) -> str: + """ Returns a string date/time representation formatted as yyyy-MM-ddThh:mm:ss.nnnnnnnnn. + + Args: + ns (int): the number of nanoseconds from Epoch + + Returns: + str + + Raises: + DHError + """ + try: + return _JDateTimeUtils.format(ns) + except Exception as e: + raise DHError(e) from e + + +def format_as_date(dt: DateTime, tz: TimeZone) -> str: + """ Returns a string date representation of a DateTime interpreted for a specified time zone formatted as yyy-MM-dd. + + Args: + dt (DateTime): the DateTime to format + tz (TimeZone): the TimeZone to use when interpreting the date/time + + Returns: + str + + Raises: + DHError + """ + try: + j_datetime = dt.j_datetime if dt else None + return _JDateTimeUtils.formatDate(j_datetime, tz.value) + except Exception as e: + raise DHError(e) from e + + +def hour_of_day(dt: DateTime, tz: TimeZone) -> int: + """ Returns an int value of the hour of the day for a DateTime in the specified time zone. The hour is on a + 24 hour clock (0 - 23). + + Args: + dt (DateTime): the DateTime for which to find the hour of the day + tz (TimeZone): the TimeZone to use when interpreting the date/time + + Returns: + int + + Raises: + DHError + """ + try: + j_datetime = dt.j_datetime if dt else None + return _JDateTimeUtils.hourOfDay(j_datetime, tz.value) + except Exception as e: + raise DHError(e) from e + + +def is_after(dt1: DateTime, dt2: DateTime) -> bool: + """ Evaluates whether one DateTime value is later than a second DateTime value. + + Args: + dt1 (DateTime): the 1st DateTime + dt2 (DateTime): the 2nd DateTime + + Returns: + bool + + Raises: + DHError + """ + try: + j_datetime1 = dt1.j_datetime if dt1 else None + j_datetime2 = dt2.j_datetime if dt2 else None + return _JDateTimeUtils.isAfter(j_datetime1, j_datetime2) + except Exception as e: + raise DHError(e) from e + + +def is_before(dt1: DateTime, dt2: DateTime) -> bool: + """ Evaluates whether one DateTime value is later than a second DateTime value. + + Args: + dt1 (DateTime): the 1st DateTime + dt2 (DateTime): the 2nd DateTime + + Returns: + bool + + Raises: + DHError + """ + try: + j_datetime1 = dt1.j_datetime if dt1 else None + j_datetime2 = dt2.j_datetime if dt2 else None + return _JDateTimeUtils.isBefore(j_datetime1, j_datetime2) + except Exception as e: + raise DHError(e) from e + + +def lower_bin(dt: DateTime, interval: int, offset: int = 0) -> DateTime: + """ Returns a DateTime value, which is at the starting (lower) end of a time range defined by the interval + nanoseconds. For example, a 5*MINUTE intervalNanos value would return the date/time value for the start of the + five minute window that contains the input date time. + + Args: + dt (DateTime): the DateTime for which to evaluate the start of the containing window + interval (int): the size of the window in nanoseconds + offset (int): the window start offset in nanoseconds. For example, a value of MINUTE would offset all windows by + one minute. Default is 0 + + Returns: + DateTime + + Raises: + DHError + """ + try: + j_datetime = dt.j_datetime if dt else None + return DateTime(_JDateTimeUtils.lowerBin(j_datetime, interval, offset)) + except Exception as e: + raise DHError(e) from e + + +def millis(dt: DateTime) -> int: + """ Returns milliseconds since Epoch for a DateTime value. + + Args: + dt (DateTime): the DateTime for which the milliseconds offset should be returned + + Returns: + int + + Raises: + DHError + """ + try: + j_datetime = dt.j_datetime if dt else None + return _JDateTimeUtils.millis(j_datetime) + except Exception as e: + raise DHError(e) from e + + +def millis_of_day(dt: DateTime, tz: TimeZone) -> int: + """ Returns an int value of milliseconds since midnight for a DateTime in the specified time zone. + + Args: + dt (DateTime): the DateTime for which to find the milliseconds since midnight + tz (TimeZone): the TimeZone to use when interpreting the date/time + + Returns: + int + + Raises: + DHError + """ + try: + return _JDateTimeUtils.millisOfDay(dt, tz.value) + except Exception as e: + raise DHError(e) from e + + +def millis_of_second(dt: DateTime, tz: TimeZone) -> int: + """ Returns an int value of milliseconds since the top of the second for a DateTime in the specified time zone. + + Args: + dt (DateTime): the DateTime for which to find the milliseconds + tz (TimeZone): the TimeZone to use when interpreting the date/time + + Returns: + int + + Raises: + DHError + """ + try: + j_datetime = dt.j_datetime if dt else None + return _JDateTimeUtils.millisOfSecond(j_datetime, tz.value) + except Exception as e: + raise DHError(e) from e + + +def millis_to_nanos(ms: int) -> int: + """ Converts milliseconds to nanoseconds. + + Args: + ms (int): the milliseconds value to convert + + Returns: + int + + Raises: + DHError + """ + try: + return _JDateTimeUtils.millisToNanos(ms) + except Exception as e: + raise DHError(e) from e + + +def millis_to_time(ms: int) -> DateTime: + """ Converts a value of milliseconds from Epoch in the UTC time zone to a DateTime. + + Args: + ms (int): the milliseconds value to convert + + returns: + DateTime + + Raises: + DHError + """ + try: + return _JDateTimeUtils.millisToTime(ms) + except Exception as e: + raise DHError(e) from e + + +def minus(dt1: DateTime, dt2: DateTime) -> int: + """ Subtracts one time from another. + + Args: + dt1 (DateTime): the 1st DateTime + dt2 (DateTiem): the 2nd DateTime + + Returns: + int + + Raises: + DHError + """ + try: + j_datetime1 = dt1.j_datetime if dt1 else None + j_datetime2 = dt2.j_datetime if dt2 else None + return _JDateTimeUtils.minus(j_datetime1, j_datetime2) + except Exception as e: + raise DHError(e) from e + + +def minus_nanos(dt: DateTime, ns: int) -> DateTime: + """ Subtracts nanoseconds from a DateTime. + + Args: + dt (DateTime): the starting DateTime value + ns (int): the long number of nanoseconds to subtract from dateTime + + Returns: + DateTime + + Raises: + DHError + """ + try: + j_datetime = dt.j_datetime if dt else None + return DateTime(_JDateTimeUtils.minus(j_datetime, ns)) + except Exception as e: + raise DHError(e) from e + + +def minus_period(dt: DateTime, period: Period) -> DateTime: + """ Subtracts a period from a DateTime. + + Args: + dt (DateTime): the starting DateTime value + period (Period): the Period to subtract from dateTime + + Returns: + DateTime + + Raises: + DHError + """ + try: + j_datetime = dt.j_datetime if dt else None + j_period = period.j_period if period else None + return DateTime(_JDateTimeUtils.minus(j_datetime, j_period)) + except Exception as e: + raise DHError(e) from e + + +def minute_of_day(dt: DateTime, tz: TimeZone) -> int: + """ Returns an int value of minutes since midnight for a DateTime in the specified time zone. + + Args: + dt (DateTime): the DateTime for which to find the minutes + tz (TimeZone): the TimeZone to use when interpreting the date/time + + Returns: + int + + Raises: + DHError + """ + try: + j_datetime = dt.j_datetime if dt else None + return _JDateTimeUtils.minuteOfDay(j_datetime, tz.value) + except Exception as e: + raise DHError(e) from e + + +def minute_of_hour(dt: DateTime, tz: TimeZone) -> int: + """ Returns an int value of minutes since the top of the hour for a DateTime in the specified time zone. + + Args: + dt (DateTime): the DateTime for which to find the minutes + tz (TimeZone): the TimeZone to use when interpreting the date/time + + Returns: + int + + Raises: + DHError + """ + try: + j_datetime = dt.j_datetime if dt else None + return _JDateTimeUtils.minuteOfHour(j_datetime, tz.value) + except Exception as e: + raise DHError(e) from e + + +def month_of_year(dt: DateTime, tz: TimeZone) -> int: + """ Returns an int value for the month of a DateTime in the specified time zone. + + Args: + dt (DateTime): the DateTime for which to find the month + tz (TimeZone): the TimeZone to use when interpreting the date/time + + Returns: + int + + Raises: + DHError + """ + try: + j_datetime = dt.j_datetime if dt else None + return _JDateTimeUtils.monthOfYear(j_datetime, tz.value) + except Exception as e: + raise DHError(e) from e + + +def nanos(dt: DateTime) -> int: + """ Returns nanoseconds since Epoch for a DateTime value. + + Args: + dt (DateTime): the DateTime for which the nanoseconds offset should be returned + + Returns: + int + + Raises: + DHError + """ + try: + j_datetime = dt.j_datetime if dt else None + return _JDateTimeUtils.nanos(j_datetime) + except Exception as e: + raise DHError(e) from e + + +def nanos_of_day(dt: DateTime, tz: TimeZone) -> int: + """ Returns a long value of nanoseconds since midnight for a DateTime in the specified time zone. + + Args: + dt (DateTime): the DateTime for which to find the nanoseconds since midnight + tz (TimeZone): the TimeZone to use when interpreting the date/time + + Returns: + int + + Raises: + DHError + """ + try: + j_datetime = dt.j_datetime if dt else None + return _JDateTimeUtils.nanosOfDay(j_datetime, tz.value) + except Exception as e: + raise DHError(e) from e + + +def nanos_of_second(dt: DateTime, tz: TimeZone) -> int: + """ Returns a long value of nanoseconds since the top of the second for a DateTime in the specified time zone. + + Args: + dt (DateTime): the DateTime for which to find the nanoseconds + tz (TimeZone): the TimeZone to use when interpreting the date/time + + Returns: + int + + Raises: + DHError + """ + try: + j_datetime = dt.j_datetime if dt else None + return _JDateTimeUtils.nanosOfSecond(j_datetime, tz.value) + except Exception as e: + raise DHError(e) from e + + +def nanos_to_millis(ns: int) -> int: + """ Converts nanoseconds to milliseconds. + + Args: + ns (int): the value of nanoseconds to convert + + Returns: + int + + Raises: + DHError + """ + try: + return _JDateTimeUtils.nanosToMillis(ns) + except Exception as e: + raise DHError(e) from e + + +def nanos_to_time(ns: int) -> DateTime: + """ Converts a value of nanoseconds from Epoch to a DateTime. + + Args: + ns (long): the long nanoseconds since Epoch value to convert + + Returns: + DateTime + """ + try: + return DateTime(_JDateTimeUtils.nanosToTime(ns)) + except Exception as e: + raise DHError(e) from e + + +def plus_period(dt: DateTime, period: Period) -> DateTime: + """ Adds one time from another. + + Args: + dt (DateTime): the starting DateTime value + period (Period): the Period to add to the DateTime + + Returns: + DateTime + + Raises: + DHError + """ + try: + j_datetime = dt.j_datetime if dt else None + j_period = period.j_period if period else None + return DateTime(_JDateTimeUtils.plus(j_datetime, j_period)) + except Exception as e: + raise DHError(e) from e + + +def plus_nanos(dt: DateTime, ns: int) -> DateTime: + """ Adds nanoseconds to a DateTime. + + Args: + dt (DateTime): the starting DateTime value + ns (int): the long number of nanoseconds to add to DateTime + + Returns: + DateTime + + Raises: + DHError + """ + try: + j_datetime = dt.j_datetime if dt else None + return DateTime(_JDateTimeUtils.plus(j_datetime, ns)) + except Exception as e: + raise DHError(e) from e + + +def second_of_day(dt: DateTime, tz: TimeZone) -> int: + """ Returns an int value of seconds since midnight for a DateTime in the specified time zone. + + Args: + dt (DateTime): the DateTime for which to find the seconds + tz (TimeZone): the TimeZone to use when interpreting the date/time + + Returns: + int + + Raises: + DHError + """ + try: + j_datetime = dt.j_datetime if dt else None + return _JDateTimeUtils.secondOfDay(j_datetime, tz.value) + except Exception as e: + raise DHError(e) from e + + +def second_of_minute(dt: DateTime, tz: TimeZone) -> int: + """ Returns an int value of seconds since the top of the minute for a DateTime in the specified time zone. + + Args: + dt (DateTime): the DateTime for which to find the seconds + tz (TimeZone): the TimeZone to use when interpreting the date/time + + Returns: + int + + Raises: + DHError + """ + try: + j_datetime = dt.j_datetime if dt else None + return _JDateTimeUtils.secondOfMinute(j_datetime, tz.value) + except Exception as e: + raise DHError(e) from e + + +def upper_bin(dt: DateTime, interval: int, offset: int = 0) -> DateTime: + """ Returns a DateTime value, which is at the ending (upper) end of a time range defined by the interval + nanoseconds. For example, a 5*MINUTE intervalNanos value would return the date/time value for the end of the five + minute window that contains the input date time. + + Args: + dt (DateTime): the DateTime for which to evaluate the end of the containing window + interval (int): the size of the window in nanoseconds + offset (int): the window start offset in nanoseconds. For example, a value of MINUTE would offset all windows by + one minute. Default is 0 + + Returns: + DateTime + + Raises: + DHError + """ + try: + j_datetime = dt.j_datetime if dt else None + return DateTime(_JDateTimeUtils.upperBin(j_datetime, interval, offset)) + except Exception as e: + raise DHError(e) from e + + +def year(dt: DateTime, tz: TimeZone) -> int: + """ Returns an int value of the year for a DateTime in the specified time zone. + + Args: + dt (DateTime): the DateTime for which to find the year + tz (TimeZone): the TimeZone to use when interpreting the date/time + + Returns: + int + + Raises: + DHError + """ + try: + j_datetime = dt.j_datetime if dt else None + return _JDateTimeUtils.year(j_datetime, tz.value) + except Exception as e: + raise DHError(e) from e + + +def year_of_century(dt: DateTime, tz: TimeZone) -> int: + """ Returns an int value of the two-digit year for a DateTime in the specified time zone. + + Args: + dt (DateTime): the DateTime for which to find the year + tz (TimeZone): the TimeZone to use when interpreting the date/time + + Returns: + int + + Raises: + DHError + """ + try: + j_datetime = dt.j_datetime if dt else None + return _JDateTimeUtils.yearOfCentury(j_datetime, tz.value) + except Exception as e: + raise DHError(e) from e diff --git a/pyintegration/deephaven2/dtypes/__init__.py b/pyintegration/deephaven2/dtypes/__init__.py new file mode 100644 index 00000000000..42c8394acb7 --- /dev/null +++ b/pyintegration/deephaven2/dtypes/__init__.py @@ -0,0 +1,23 @@ +# +# Copyright (c) 2016-2021 Deephaven Data Labs and Patent Pending +# +""" This module defines the data types supported by the Deephaven engine. + +Each data type is represented by a DType class which supports creating arrays of the same type and more. +""" +from __future__ import annotations + +from typing import Iterable + +from ._dtype import * +from ._datetime import DateTime, Period + + +def j_array_list(values: Iterable): + j_list = jpy.get_type("java.util.ArrayList")(len(values)) + try: + for v in values: + j_list.add(v) + return j_list + except Exception as e: + raise DHError(e, "failed to create a Java ArrayList from the Python collection.") from e diff --git a/pyintegration/deephaven2/dtypes/_datetime.py b/pyintegration/deephaven2/dtypes/_datetime.py new file mode 100644 index 00000000000..2a6b47e4e0d --- /dev/null +++ b/pyintegration/deephaven2/dtypes/_datetime.py @@ -0,0 +1,108 @@ +# +# Copyright (c) 2016-2021 Deephaven Data Labs and Patent Pending +# +from typing import Any, Sequence, Callable + +from deephaven2 import DHError +from deephaven2.dtypes import DType + +_DHDateTime = DType(j_name="io.deephaven.time.DateTime") +_DHPeriod = DType(j_name="io.deephaven.time.Period") + + +class DateTime: + is_primitive = False + j_type = _DHDateTime.j_type + qst_type = _DHDateTime.qst_type + + def __init__(self, v: Any): + if isinstance(v, _DHDateTime.j_type): + self.j_datetime = v + else: + # TODO conversion support from Python built-in datetime, string datetime representation etc. + self.j_datetime = _DHDateTime(v) + + def __eq__(self, other): + if other is None: + if self.j_datetime is None: + return True + else: + return False + + if isinstance(other, DateTime): + if self.j_datetime is not None: + return self.j_datetime.equals(other.j_datetime) + elif other.j_datetime is None: + return True + + return False + + def __repr__(self): + return str(self.j_datetime) + + @staticmethod + def now(): + return DateTime(_DHDateTime.j_type.now()) + + @staticmethod + def array(size: int): + """ Creates a Java array of the Deephaven DateTime data type of the specified size. + + Args: + size (int): the size of the array + + Returns: + a Java array + + Raises: + DHError + """ + return _DHDateTime.array(size) + + @staticmethod + def array_from(seq: Sequence, remap: Callable[[Any], Any] = None): + """ Creates a Java array of Deephaven DateTime instances from a sequence. + + Args: + seq: a sequence of compatible data, e.g. list, tuple, numpy array, Pandas series, etc. + remap (optional): a callable that takes one value and maps it to another, for handling the translation of + special DH values such as NULL_INT, NAN_INT between Python and the DH engine + + Returns: + a Java array + + Raises: + DHError + """ + + new_seq = [] + for v in seq: + if v is None: + new_seq.append(None) + elif isinstance(v, DateTime): + new_seq.append(v.j_datetime) + elif isinstance(v, _DHDateTime.j_type): + new_seq.append(v) + else: + raise DHError(message="Not a valid datetime") + + return _DHDateTime.array_from(seq=new_seq, remap=remap) + + +class Period: + is_primitive = False + j_type = _DHPeriod.j_type + qst_type = _DHPeriod.qst_type + + def __init__(self, v: Any): + if v is None: + self.j_period = None + else: + if isinstance(v, _DHPeriod.j_type): + self.j_period = v + else: + # TODO? conversion support from Python timedelta, etc. + self.j_period = _DHPeriod(v) + + def __repr__(self): + return str(self.j_period) \ No newline at end of file diff --git a/pyintegration/deephaven2/dtypes.py b/pyintegration/deephaven2/dtypes/_dtype.py similarity index 82% rename from pyintegration/deephaven2/dtypes.py rename to pyintegration/deephaven2/dtypes/_dtype.py index b8c12f98f6e..8be620195a9 100644 --- a/pyintegration/deephaven2/dtypes.py +++ b/pyintegration/deephaven2/dtypes/_dtype.py @@ -1,20 +1,12 @@ # # Copyright (c) 2016-2021 Deephaven Data Labs and Patent Pending # -""" This module defines the data types supported by the Deephaven engine. - -Each data type is represented by a DType class which supports creating arrays of the same type and more. -""" from __future__ import annotations +from typing import Any, Sequence, Callable -from typing import Iterable, Any, Tuple, Sequence, Callable - -import numpy as np -import pandas as pd import jpy from deephaven2 import DHError -from deephaven2.constants import * _JQstType = jpy.get_type("io.deephaven.qst.type.Type") _JTableTools = jpy.get_type("io.deephaven.engine.util.TableTools") @@ -52,7 +44,10 @@ def __repr__(self): return self.j_name def __call__(self, *args, **kwargs): - return self.j_type(*args, **kwargs) + try: + return self.j_type(*args, **kwargs) + except Exception as e: + raise DHError(e, f"failed to create an instance of {self.j_name}") from e def array(self, size: int): """ Creates a Java array of the same data type of the specified size. @@ -111,7 +106,6 @@ def array_from(self, seq: Sequence, remap: Callable[[Any], Any] = None): return super().array_from(seq, remap) -# region predefined types and aliases bool_ = DType(j_name="java.lang.Boolean", qst_type=_JQstType.booleanType()) byte = DType(j_name="byte", qst_type=_JQstType.byteType(), is_primitive=True) int8 = byte @@ -130,19 +124,5 @@ def array_from(self, seq: Sequence, remap: Callable[[Any], Any] = None): string = DType(j_name="java.lang.String", qst_type=_JQstType.stringType()) BigDecimal = DType(j_name="java.math.BigDecimal") StringSet = DType(j_name="io.deephaven.stringset.StringSet") -DateTime = DType(j_name="io.deephaven.time.DateTime") -Period = DType(j_name="io.deephaven.time.Period") PyObject = DType(j_name="org.jpy.PyObject") -JObject = DType(j_name="java.lang.Object") - - -# endregion - -def j_array_list(values: Iterable): - j_list = jpy.get_type("java.util.ArrayList")(len(values)) - try: - for v in values: - j_list.add(v) - return j_list - except Exception as e: - raise DHError(e, "failed to create a Java ArrayList from the Python collection.") from e +JObject = DType(j_name="java.lang.Object") \ No newline at end of file diff --git a/pyintegration/deephaven2/table.py b/pyintegration/deephaven2/table.py index e2164243d1b..7a7b79e14ec 100644 --- a/pyintegration/deephaven2/table.py +++ b/pyintegration/deephaven2/table.py @@ -99,14 +99,17 @@ def to_string(self, num_rows: int = 10, cols: List[str] = []) -> str: raise DHError(e, "table to_string failed") from e def to_html(self): - """ - Returns a printout of a table formatted as HTML. Limit use to small tables to avoid running out of memory. + """ Returns a printout of a table formatted as HTML. Limit use to small tables to avoid running out of memory. + - :param source: (io.deephaven.db.tables.Table) - a Deephaven table object - :return: (java.lang.String) a String of the table printout formatted as HTML + Returns: + a String of the table printout formatted as HTML """ + try: + return _JTableTools.html(self.j_table) + except Exception as e: + raise DHError(e, "table to_html failed") from e - return _JTableTools.html(self.j_table) def coalesce(self) -> Table: """ Returns a coalesced child table. """ diff --git a/pyintegration/deephaven2/table_factory.py b/pyintegration/deephaven2/table_factory.py index 3bd8245181a..973e8477ed4 100644 --- a/pyintegration/deephaven2/table_factory.py +++ b/pyintegration/deephaven2/table_factory.py @@ -6,8 +6,8 @@ import jpy -from deephaven2.column import Column from deephaven2 import DHError +from deephaven2.column import InputColumn from deephaven2.table import Table _JTableFactory = jpy.get_type("io.deephaven.engine.table.TableFactory") @@ -55,8 +55,17 @@ def time_table(period: str, start_time: str = None) -> Table: raise DHError(e, "failed to create a time table.") from e -def new_table(cols: List[Column]) -> Table: - """ Creates an in-memory table from a list of columns. Each column must have an equal number of elements. +def new_table(cols: List[InputColumn]) -> Table: + """ Creates an in-memory table from a list of input columns. Each column must have an equal number of elements. + + Args: + cols (List[InputColumn]): a list of InputColumn + + Returns: + a Table + + Raises: + DHError """ try: return Table(j_table=_JTableFactory.newTable(*[col.j_column for col in cols])) @@ -72,7 +81,7 @@ def merge(tables: List[Table]): tables (List[Table]): the source tables Returns: - a new table + a Table Raises: DHError @@ -83,7 +92,7 @@ def merge(tables: List[Table]): raise DHError(e, "merge tables operation failed.") from e -def merge_sorted(tables: List[Table], order_by: str): +def merge_sorted(tables: List[Table], order_by: str) -> Table: """ Combines two or more tables into one sorted, aggregate table. This essentially stacks the tables one on top of the other and sorts the result. Null tables are ignored. mergeSorted is more efficient than using merge followed by sort. @@ -93,7 +102,7 @@ def merge_sorted(tables: List[Table], order_by: str): order_by (str): the name of the key column Returns: - a new table + a Table Raises: DHError diff --git a/pyintegration/tests/test_datetimeutils.py b/pyintegration/tests/test_datetimeutils.py new file mode 100644 index 00000000000..6713583805c --- /dev/null +++ b/pyintegration/tests/test_datetimeutils.py @@ -0,0 +1,228 @@ +# +# Copyright (c) 2016-2021 Deephaven Data Labs and Patent Pending +# + +import unittest +from time import sleep + +from deephaven2.constants import NULL_LONG +from deephaven2.datetimeutils import * + + +class DateTimeUtilsTestCase(unittest.TestCase): + def test_convert_datetime(self): + datetime_str = "2021-12-10T23:59:59" + timezone_str = "NY" + dt = convert_datetime(f"{datetime_str} {timezone_str}") + print(dt) + self.assertTrue(str(dt).startswith(datetime_str)) + + with self.assertRaises(DHError) as cm: + datetime_str = "2021-12-10T23:59:59" + timezone_str = "--" + dt = convert_datetime(f"{datetime_str} {timezone_str}") + self.assertIn("RuntimeException", str(cm.exception)) + + def test_convert_period(self): + period_str = "1W" + period = convert_period(period_str) + self.assertEqual(str(period).upper(), period_str) + + period_str = "T1M" + period = convert_period(period_str) + self.assertEqual(repr(period).upper(), period_str) + + with self.assertRaises(DHError) as cm: + period_str = "T1Y" + period = convert_period(period_str) + self.assertIn("RuntimeException", str(cm.exception)) + + def test_convert_time(self): + time_str = "530000:59:39.123456789" + in_nanos = convert_time(time_str) + self.assertEqual(str(in_nanos), "1908003579123456789") + + with self.assertRaises(DHError) as cm: + time_str = "530000:59:39.X" + in_nanos = convert_time(time_str) + self.assertIn("RuntimeException", str(cm.exception)) + + time_str = "00:59:39.X" + in_nanos = convert_time(time_str, quiet=True) + self.assertEqual(in_nanos, NULL_LONG) + + def test_current_time_and_diff(self): + dt = current_time() + sleep(1) + dt1 = current_time() + self.assertGreaterEqual(diff_nanos(dt, dt1), 100000000) + + def test_date_at_midnight(self): + dt = current_time() + mid_night_time_ny = date_at_midnight(dt, TimeZone.NY) + mid_night_time_pt = date_at_midnight(dt, TimeZone.PT) + self.assertGreaterEqual(diff_nanos(mid_night_time_ny, mid_night_time_pt), 0) + + def test_day_of_month(self): + dt = current_time() + self.assertIn(day_of_month(dt, TimeZone.MT), range(1, 32)) + + def test_day_of_week(self): + dt = current_time() + self.assertIn(day_of_week(dt, TimeZone.MT), range(1, 8)) + + def test_day_of_year(self): + dt = current_time() + self.assertIn(day_of_year(dt, TimeZone.MT), range(1, 366)) + + def test_format_date(self): + dt = current_time() + self.assertIn(TimeZone.SYD.name, format_datetime(dt, TimeZone.SYD)) + + def test_format_nanos(self): + dt = current_time() + ns = nanos(dt) + ns_str1 = format_nanos(ns).split(".")[-1] + ns_str2 = format_datetime(dt, TimeZone.UTC).split(".")[-1] + self.assertTrue(ns_str2.startswith(ns_str1)) + + def test_format_as_date(self): + dt = current_time() + self.assertEqual(3, len(format_as_date(dt, TimeZone.MOS).split("-"))) + + def test_hour_of_day(self): + dt = current_time() + self.assertIn(hour_of_day(dt, TimeZone.AL), range(0, 24)) + + def test_is_after(self): + dt1 = current_time() + sleep(0.001) + dt2 = current_time() + self.assertTrue(is_after(dt2, dt1)) + + def test_is_before(self): + dt1 = current_time() + sleep(0.001) + dt2 = current_time() + self.assertFalse(is_before(dt2, dt1)) + + def test_lower_bin(self): + dt = current_time() + self.assertGreaterEqual(diff_nanos(lower_bin(dt, 1000000, MINUTE), dt), 0) + + def test_millis(self): + dt = current_time() + self.assertGreaterEqual(nanos(dt), millis(dt) * 10 ** 6) + + def test_millis_of_second(self): + dt = current_time() + self.assertGreaterEqual(millis_of_second(dt, TimeZone.AT), 0) + + def test_millis_to_nanos(self): + dt = current_time() + ms = millis(dt) + self.assertEqual(ms * 10 ** 6, millis_to_nanos(ms)) + + def test_minus(self): + dt1 = current_time() + dt2 = current_time() + self.assertGreaterEqual(0, minus(dt1, dt2)) + + def test_minus_nanos(self): + dt = current_time() + dt1 = minus_nanos(dt, 1) + self.assertEqual(1, diff_nanos(dt1, dt)) + + def test_minus_period(self): + period_str = "T1H" + period = convert_period(period_str) + + dt = current_time() + dt1 = minus_period(dt, period) + self.assertEqual(diff_nanos(dt1, dt), 60 * 60 * 10 ** 9) + + def test_minute_of_day(self): + datetime_str = "2021-12-10T00:59:59" + timezone_str = "BT" + dt = convert_datetime(f"{datetime_str} {timezone_str}") + self.assertEqual(59, minute_of_day(dt, TimeZone.BT)) + + def test_minute_of_hour(self): + datetime_str = "2021-12-10T23:59:59" + timezone_str = "CE" + dt = convert_datetime(f"{datetime_str} {timezone_str}") + self.assertEqual(59, minute_of_hour(dt, TimeZone.CE)) + + def test_month_of_year(self): + datetime_str = "2021-08-10T23:59:59" + timezone_str = "CH" + dt = convert_datetime(f"{datetime_str} {timezone_str}") + self.assertEqual(8, month_of_year(dt, TimeZone.CH)) + + def test_nanos_of_day(self): + datetime_str = "2021-12-10T00:00:01" + timezone_str = "CT" + dt = convert_datetime(f"{datetime_str} {timezone_str}") + self.assertEqual(10 ** 9, nanos_of_day(dt, TimeZone.CT)) + + def test_nanos_of_second(self): + datetime_str = "2021-12-10T00:00:01.000000123" + timezone_str = "ET" + dt = convert_datetime(f"{datetime_str} {timezone_str}") + self.assertEqual(123, nanos_of_second(dt, TimeZone.ET)) + + def test_nanos_to_millis(self): + dt = current_time() + ns = nanos(dt) + self.assertEqual(round(ns / 10 ** 6), nanos_to_millis(ns)) + + def test_nanos_to_time(self): + dt = current_time() + ns = nanos(dt) + dt1 = nanos_to_time(ns) + self.assertEqual(dt, dt1) + + def test_plus_period(self): + period_str = "T1H" + period = convert_period(period_str) + + dt = current_time() + dt1 = plus_period(dt, period) + self.assertEqual(diff_nanos(dt, dt1), 60 * 60 * 10 ** 9) + + def test_plus_nanos(self): + dt = current_time() + dt1 = plus_nanos(dt, 1) + self.assertEqual(1, diff_nanos(dt, dt1)) + + def test_second_of_day(self): + datetime_str = "2021-12-10T00:01:05" + timezone_str = "HI" + dt = convert_datetime(f"{datetime_str} {timezone_str}") + self.assertEqual(65, second_of_day(dt, TimeZone.HI)) + + def test_second_of_minute(self): + datetime_str = "2021-12-10T00:01:05" + timezone_str = "HK" + dt = convert_datetime(f"{datetime_str} {timezone_str}") + self.assertEqual(5, second_of_minute(dt, TimeZone.HK)) + + def test_upper_bin(self): + dt = current_time() + self.assertGreaterEqual(diff_nanos(dt, upper_bin(dt, 1000000, MINUTE)), 0) + + def test_year(self): + datetime_str = "2021-12-10T00:01:05" + timezone_str = "IN" + dt = convert_datetime(f"{datetime_str} {timezone_str}") + self.assertEqual(2021, year(dt, TimeZone.IN)) + + def test_year(self): + datetime_str = "2021-12-10T00:01:05" + timezone_str = "JP" + dt = convert_datetime(f"{datetime_str} {timezone_str}") + self.assertEqual(21, year_of_century(dt, TimeZone.JP)) + + +if __name__ == '__main__': + unittest.main() diff --git a/pyintegration/tests/test_dtypes.py b/pyintegration/tests/test_dtypes.py index 502d11d5b84..e534e152855 100644 --- a/pyintegration/tests/test_dtypes.py +++ b/pyintegration/tests/test_dtypes.py @@ -6,13 +6,13 @@ import time import unittest -import jpy import numpy import numpy as np import pandas as pd from deephaven2 import dtypes from deephaven2.constants import * +from deephaven2.dtypes import DateTime from tests.testbase import BaseTestCase @@ -171,11 +171,12 @@ def remap_char(v): self.assertEqual(expected, py_array) def test_datetime(self): - dt1 = dtypes.DateTime(round(time.time())) - dt2 = dtypes.DateTime.j_type.now() + dt1 = DateTime(round(time.time())) + dt2 = DateTime.now() values = [dt1, dt2, None] - j_array = dtypes.DateTime.array_from(values) - self.assertEqual(values, [dt for dt in j_array]) + j_array = DateTime.array_from(values) + p_list = [DateTime(dt) for dt in j_array] + self.assertTrue(all(x == y for x, y in zip(p_list, values))) if __name__ == '__main__': diff --git a/pyintegration/tests/test_table.py b/pyintegration/tests/test_table.py index eb34bb2c10c..d415c22e71b 100644 --- a/pyintegration/tests/test_table.py +++ b/pyintegration/tests/test_table.py @@ -39,7 +39,7 @@ def test_coalesce(self): def test_drop_columns(self): column_names = [f.name for f in self.test_table.columns] result_table = self.test_table.drop_columns(cols=column_names[:-1]) - self.assertEquals(1, len(result_table.columns)) + self.assertEqual(1, len(result_table.columns)) def test_move_columns(self): column_names = [f.name for f in self.test_table.columns] diff --git a/pyintegration/tests/test_table_factory.py b/pyintegration/tests/test_table_factory.py index 985113814e1..ddf3b2b157c 100644 --- a/pyintegration/tests/test_table_factory.py +++ b/pyintegration/tests/test_table_factory.py @@ -6,7 +6,6 @@ import jpy import numpy as np -import pandas as pd from deephaven2 import DHError, read_csv, time_table, empty_table, merge, merge_sorted, dtypes, new_table from deephaven2.column import byte_col, char_col, short_col, bool_col, int_col, long_col, float_col, double_col, \ From dd66862eaa48115978b20f291601feee9f969f9a Mon Sep 17 00:00:00 2001 From: jianfengmao Date: Fri, 7 Jan 2022 19:21:38 -0700 Subject: [PATCH 02/12] Addressed Devin's review comments Fixed the test case problem that showed up in CI checks --- pyintegration/deephaven2/dtypes/_datetime.py | 11 +---------- pyintegration/tests/test_datetimeutils.py | 2 +- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/pyintegration/deephaven2/dtypes/_datetime.py b/pyintegration/deephaven2/dtypes/_datetime.py index 2a6b47e4e0d..26199608d2e 100644 --- a/pyintegration/deephaven2/dtypes/_datetime.py +++ b/pyintegration/deephaven2/dtypes/_datetime.py @@ -23,17 +23,8 @@ def __init__(self, v: Any): self.j_datetime = _DHDateTime(v) def __eq__(self, other): - if other is None: - if self.j_datetime is None: - return True - else: - return False - if isinstance(other, DateTime): - if self.j_datetime is not None: - return self.j_datetime.equals(other.j_datetime) - elif other.j_datetime is None: - return True + return self.j_datetime.equals(other.j_datetime) return False diff --git a/pyintegration/tests/test_datetimeutils.py b/pyintegration/tests/test_datetimeutils.py index 6713583805c..a1f1977c9cd 100644 --- a/pyintegration/tests/test_datetimeutils.py +++ b/pyintegration/tests/test_datetimeutils.py @@ -174,7 +174,7 @@ def test_nanos_of_second(self): def test_nanos_to_millis(self): dt = current_time() ns = nanos(dt) - self.assertEqual(round(ns / 10 ** 6), nanos_to_millis(ns)) + self.assertEqual(ns // 10 ** 6, nanos_to_millis(ns)) def test_nanos_to_time(self): dt = current_time() From a02b8f43e8c87854934b0375e288971509ad6820 Mon Sep 17 00:00:00 2001 From: jianfengmao Date: Fri, 7 Jan 2022 20:40:42 -0700 Subject: [PATCH 03/12] Fixed a test case error --- pyintegration/tests/test_dtypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyintegration/tests/test_dtypes.py b/pyintegration/tests/test_dtypes.py index e534e152855..167cbc66f82 100644 --- a/pyintegration/tests/test_dtypes.py +++ b/pyintegration/tests/test_dtypes.py @@ -175,7 +175,7 @@ def test_datetime(self): dt2 = DateTime.now() values = [dt1, dt2, None] j_array = DateTime.array_from(values) - p_list = [DateTime(dt) for dt in j_array] + p_list = [DateTime(dt) if dt else dt for dt in j_array] self.assertTrue(all(x == y for x, y in zip(p_list, values))) From 4a1025eb21726d747523075efd37770bcb7abe72 Mon Sep 17 00:00:00 2001 From: jianfengmao Date: Mon, 10 Jan 2022 09:37:33 -0700 Subject: [PATCH 04/12] Removed print() calls left in the code --- pyintegration/tests/test_datetimeutils.py | 1 - pyintegration/tests/test_table.py | 1 - pyintegration/tests/test_table_factory.py | 1 - 3 files changed, 3 deletions(-) diff --git a/pyintegration/tests/test_datetimeutils.py b/pyintegration/tests/test_datetimeutils.py index a1f1977c9cd..ac77f401781 100644 --- a/pyintegration/tests/test_datetimeutils.py +++ b/pyintegration/tests/test_datetimeutils.py @@ -14,7 +14,6 @@ def test_convert_datetime(self): datetime_str = "2021-12-10T23:59:59" timezone_str = "NY" dt = convert_datetime(f"{datetime_str} {timezone_str}") - print(dt) self.assertTrue(str(dt).startswith(datetime_str)) with self.assertRaises(DHError) as cm: diff --git a/pyintegration/tests/test_table.py b/pyintegration/tests/test_table.py index d415c22e71b..19a5e2bbef4 100644 --- a/pyintegration/tests/test_table.py +++ b/pyintegration/tests/test_table.py @@ -18,7 +18,6 @@ def tearDown(self) -> None: self.test_table = None def test_repr(self): - print(self.test_table) self.assertIn(self.test_table.__class__.__name__, repr(self.test_table)) # diff --git a/pyintegration/tests/test_table_factory.py b/pyintegration/tests/test_table_factory.py index ddf3b2b157c..9ea97557217 100644 --- a/pyintegration/tests/test_table_factory.py +++ b/pyintegration/tests/test_table_factory.py @@ -30,7 +30,6 @@ def test_empty_table_error(self): with self.assertRaises(DHError) as cm: t = empty_table("abc") - print(cm.exception.root_cause) self.assertIn("RuntimeError", cm.exception.root_cause) self.assertIn("no matching Java method overloads found", cm.exception.compact_traceback) From 1c2afb4660ec7aae07a43978d7480b182f9a77c0 Mon Sep 17 00:00:00 2001 From: jianfengmao Date: Mon, 10 Jan 2022 09:39:55 -0700 Subject: [PATCH 05/12] Fixed more docstring issues --- pyintegration/deephaven2/table.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyintegration/deephaven2/table.py b/pyintegration/deephaven2/table.py index 7a7b79e14ec..cc414516b43 100644 --- a/pyintegration/deephaven2/table.py +++ b/pyintegration/deephaven2/table.py @@ -101,16 +101,17 @@ def to_string(self, num_rows: int = 10, cols: List[str] = []) -> str: def to_html(self): """ Returns a printout of a table formatted as HTML. Limit use to small tables to avoid running out of memory. - Returns: a String of the table printout formatted as HTML + + Raises: + DHError """ try: return _JTableTools.html(self.j_table) except Exception as e: raise DHError(e, "table to_html failed") from e - def coalesce(self) -> Table: """ Returns a coalesced child table. """ return Table(j_table=self.j_table.coalesce()) From f87ae31ae44188b8f51a1d9b595751d23aa1ccb9 Mon Sep 17 00:00:00 2001 From: jianfengmao Date: Mon, 10 Jan 2022 14:58:45 -0700 Subject: [PATCH 06/12] Corrected method types in _datetime.py Also made DateTime/Period lookup-able from DType --- pyintegration/deephaven2/column.py | 7 +++---- pyintegration/deephaven2/dtypes/_datetime.py | 12 ++++++------ pyintegration/deephaven2/dtypes/_dtype.py | 10 +++++++--- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/pyintegration/deephaven2/column.py b/pyintegration/deephaven2/column.py index 5a8bd7eb914..1d9339faf08 100644 --- a/pyintegration/deephaven2/column.py +++ b/pyintegration/deephaven2/column.py @@ -3,13 +3,12 @@ # from dataclasses import dataclass, field from enum import Enum -from typing import Sequence +from typing import Sequence, Any import jpy import deephaven2.dtypes as dtypes from deephaven2 import DHError -from deephaven2.dtypes import DType _JColumnHeader = jpy.get_type("io.deephaven.qst.column.header.ColumnHeader") _JColumn = jpy.get_type("io.deephaven.qst.column.Column") @@ -33,8 +32,8 @@ def __repr__(self): class Column: """ A Column object represents a column definition in a Deephaven Table. """ name: str - data_type: DType - component_type: DType = None + data_type: Any + component_type: Any = None column_type: ColumnType = ColumnType.NORMAL @property diff --git a/pyintegration/deephaven2/dtypes/_datetime.py b/pyintegration/deephaven2/dtypes/_datetime.py index 26199608d2e..9e6a56225d4 100644 --- a/pyintegration/deephaven2/dtypes/_datetime.py +++ b/pyintegration/deephaven2/dtypes/_datetime.py @@ -31,12 +31,12 @@ def __eq__(self, other): def __repr__(self): return str(self.j_datetime) - @staticmethod - def now(): + @classmethod + def now(cls): return DateTime(_DHDateTime.j_type.now()) - @staticmethod - def array(size: int): + @classmethod + def array(cls, size: int): """ Creates a Java array of the Deephaven DateTime data type of the specified size. Args: @@ -50,8 +50,8 @@ def array(size: int): """ return _DHDateTime.array(size) - @staticmethod - def array_from(seq: Sequence, remap: Callable[[Any], Any] = None): + @classmethod + def array_from(cls, seq: Sequence, remap: Callable[[Any], Any] = None): """ Creates a Java array of Deephaven DateTime instances from a sequence. Args: diff --git a/pyintegration/deephaven2/dtypes/_dtype.py b/pyintegration/deephaven2/dtypes/_dtype.py index 8be620195a9..698556fa1a8 100644 --- a/pyintegration/deephaven2/dtypes/_dtype.py +++ b/pyintegration/deephaven2/dtypes/_dtype.py @@ -7,6 +7,7 @@ import jpy from deephaven2 import DHError +from deephaven2.dtypes import DateTime, Period _JQstType = jpy.get_type("io.deephaven.qst.type.Type") _JTableTools = jpy.get_type("io.deephaven.engine.util.TableTools") @@ -18,10 +19,13 @@ def _qst_custom_type(cls_name: str): class DType: """ A class representing a data type in Deephaven.""" - _j_name_map = {} + _j_name_map = { + "io.deephaven.time.DateTime": DateTime, + "io.deephaven.time.Period": Period + } @classmethod - def from_jtype(cls, j_class: Any) -> DType: + def from_jtype(cls, j_class: Any) -> Any: if not j_class: return None @@ -125,4 +129,4 @@ def array_from(self, seq: Sequence, remap: Callable[[Any], Any] = None): BigDecimal = DType(j_name="java.math.BigDecimal") StringSet = DType(j_name="io.deephaven.stringset.StringSet") PyObject = DType(j_name="org.jpy.PyObject") -JObject = DType(j_name="java.lang.Object") \ No newline at end of file +JObject = DType(j_name="java.lang.Object") From aa74758529181240c57ed893c67133f007348a9a Mon Sep 17 00:00:00 2001 From: jianfengmao Date: Tue, 11 Jan 2022 08:53:36 -0700 Subject: [PATCH 07/12] Added more docstrings --- pyintegration/deephaven2/dtypes/__init__.py | 3 --- pyintegration/deephaven2/dtypes/_datetime.py | 5 +++++ pyintegration/deephaven2/dtypes/_dtype.py | 4 ++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/pyintegration/deephaven2/dtypes/__init__.py b/pyintegration/deephaven2/dtypes/__init__.py index 42c8394acb7..57f3ad86535 100644 --- a/pyintegration/deephaven2/dtypes/__init__.py +++ b/pyintegration/deephaven2/dtypes/__init__.py @@ -1,10 +1,7 @@ # # Copyright (c) 2016-2021 Deephaven Data Labs and Patent Pending # -""" This module defines the data types supported by the Deephaven engine. -Each data type is represented by a DType class which supports creating arrays of the same type and more. -""" from __future__ import annotations from typing import Iterable diff --git a/pyintegration/deephaven2/dtypes/_datetime.py b/pyintegration/deephaven2/dtypes/_datetime.py index 9e6a56225d4..0709616c4fa 100644 --- a/pyintegration/deephaven2/dtypes/_datetime.py +++ b/pyintegration/deephaven2/dtypes/_datetime.py @@ -1,6 +1,8 @@ # # Copyright (c) 2016-2021 Deephaven Data Labs and Patent Pending # +""" This module defines the classes for Deephaven DateTime and Period. """ + from typing import Any, Sequence, Callable from deephaven2 import DHError @@ -11,6 +13,7 @@ class DateTime: + """ This class defines a Deephaven DateTime object. """ is_primitive = False j_type = _DHDateTime.j_type qst_type = _DHDateTime.qst_type @@ -33,6 +36,7 @@ def __repr__(self): @classmethod def now(cls): + """ Returns a DataTime object initialized to the current time. """ return DateTime(_DHDateTime.j_type.now()) @classmethod @@ -81,6 +85,7 @@ def array_from(cls, seq: Sequence, remap: Callable[[Any], Any] = None): class Period: + """ This class defines a Deephaven Period object. """ is_primitive = False j_type = _DHPeriod.j_type qst_type = _DHPeriod.qst_type diff --git a/pyintegration/deephaven2/dtypes/_dtype.py b/pyintegration/deephaven2/dtypes/_dtype.py index 698556fa1a8..e94c3ee1edf 100644 --- a/pyintegration/deephaven2/dtypes/_dtype.py +++ b/pyintegration/deephaven2/dtypes/_dtype.py @@ -1,6 +1,10 @@ # # Copyright (c) 2016-2021 Deephaven Data Labs and Patent Pending # +""" This module defines the data types supported by the Deephaven engine. + +Each data type is represented by a DType class which supports creating arrays of the same type and more. +""" from __future__ import annotations from typing import Any, Sequence, Callable From ce8615150cbb5fdad4af032eb0acbcdc97c04a7b Mon Sep 17 00:00:00 2001 From: jianfengmao Date: Tue, 11 Jan 2022 10:11:25 -0700 Subject: [PATCH 08/12] Fixed an import cycle issue --- pyintegration/deephaven2/dtypes/_datetime.py | 21 +++++++++++--------- pyintegration/deephaven2/dtypes/_dtype.py | 13 +++++------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/pyintegration/deephaven2/dtypes/_datetime.py b/pyintegration/deephaven2/dtypes/_datetime.py index 0709616c4fa..e6ee36fb121 100644 --- a/pyintegration/deephaven2/dtypes/_datetime.py +++ b/pyintegration/deephaven2/dtypes/_datetime.py @@ -5,8 +5,9 @@ from typing import Any, Sequence, Callable +from deephaven2.dtypes import DType, j_name_type_map + from deephaven2 import DHError -from deephaven2.dtypes import DType _DHDateTime = DType(j_name="io.deephaven.time.DateTime") _DHPeriod = DType(j_name="io.deephaven.time.Period") @@ -91,14 +92,16 @@ class Period: qst_type = _DHPeriod.qst_type def __init__(self, v: Any): - if v is None: - self.j_period = None + if isinstance(v, _DHPeriod.j_type): + self.j_period = v else: - if isinstance(v, _DHPeriod.j_type): - self.j_period = v - else: - # TODO? conversion support from Python timedelta, etc. - self.j_period = _DHPeriod(v) + # TODO? conversion support from Python timedelta, etc. + self.j_period = _DHPeriod(v) def __repr__(self): - return str(self.j_period) \ No newline at end of file + return str(self.j_period) + + +# to Support type lookup +j_name_type_map["io.deephaven.time.DateTime"] = DateTime +j_name_type_map["io.deephaven.time.Period"] = Period diff --git a/pyintegration/deephaven2/dtypes/_dtype.py b/pyintegration/deephaven2/dtypes/_dtype.py index e94c3ee1edf..4000f194926 100644 --- a/pyintegration/deephaven2/dtypes/_dtype.py +++ b/pyintegration/deephaven2/dtypes/_dtype.py @@ -6,16 +6,18 @@ Each data type is represented by a DType class which supports creating arrays of the same type and more. """ from __future__ import annotations + from typing import Any, Sequence, Callable import jpy from deephaven2 import DHError -from deephaven2.dtypes import DateTime, Period _JQstType = jpy.get_type("io.deephaven.qst.type.Type") _JTableTools = jpy.get_type("io.deephaven.engine.util.TableTools") +j_name_type_map = {} + def _qst_custom_type(cls_name: str): return _JQstType.find(_JTableTools.typeFromName(cls_name)) @@ -23,18 +25,13 @@ def _qst_custom_type(cls_name: str): class DType: """ A class representing a data type in Deephaven.""" - _j_name_map = { - "io.deephaven.time.DateTime": DateTime, - "io.deephaven.time.Period": Period - } - @classmethod def from_jtype(cls, j_class: Any) -> Any: if not j_class: return None j_name = j_class.getName() - dtype = DType._j_name_map.get(j_name) + dtype = j_name_type_map.get(j_name) if not dtype: return cls(j_name=j_name, j_type=j_class) else: @@ -46,7 +43,7 @@ def __init__(self, j_name: str, j_type: Any = None, qst_type: Any = None, is_pri self.qst_type = qst_type if qst_type else _qst_custom_type(j_name) self.is_primitive = is_primitive - DType._j_name_map[j_name] = self + j_name_type_map[j_name] = self def __repr__(self): return self.j_name From aa5cf6ac5ba839d57797af2bb8f1197acd05c009 Mon Sep 17 00:00:00 2001 From: jianfengmao Date: Wed, 19 Jan 2022 16:08:17 -0700 Subject: [PATCH 09/12] Addressed most of the concerns and suggestions 1. revert a lot of the changes in the PR to be consistent in using types, based on the advice given by Ryan 2. followed the suggestions given by Chip to have a time module to be the focal point for time related functions 3. there are still some comments that require further discussion and clarification --- pyintegration/deephaven2/column.py | 5 +- .../{dtypes/_dtype.py => dtypes.py} | 25 +- pyintegration/deephaven2/dtypes/__init__.py | 20 -- pyintegration/deephaven2/dtypes/_datetime.py | 107 --------- pyintegration/deephaven2/hmtl.py | 24 ++ pyintegration/deephaven2/table.py | 14 -- .../deephaven2/{datetimeutils.py => time.py} | 218 ++++++++---------- pyintegration/tests/test_dtypes.py | 6 +- .../{test_datetimeutils.py => test_time.py} | 119 +++++----- 9 files changed, 208 insertions(+), 330 deletions(-) rename pyintegration/deephaven2/{dtypes/_dtype.py => dtypes.py} (87%) delete mode 100644 pyintegration/deephaven2/dtypes/__init__.py delete mode 100644 pyintegration/deephaven2/dtypes/_datetime.py create mode 100644 pyintegration/deephaven2/hmtl.py rename pyintegration/deephaven2/{datetimeutils.py => time.py} (66%) rename pyintegration/tests/{test_datetimeutils.py => test_time.py} (70%) diff --git a/pyintegration/deephaven2/column.py b/pyintegration/deephaven2/column.py index 1d9339faf08..52211b495fb 100644 --- a/pyintegration/deephaven2/column.py +++ b/pyintegration/deephaven2/column.py @@ -6,6 +6,7 @@ from typing import Sequence, Any import jpy +from deephaven2.dtypes import DType import deephaven2.dtypes as dtypes from deephaven2 import DHError @@ -32,8 +33,8 @@ def __repr__(self): class Column: """ A Column object represents a column definition in a Deephaven Table. """ name: str - data_type: Any - component_type: Any = None + data_type: DType + component_type: DType = None column_type: ColumnType = ColumnType.NORMAL @property diff --git a/pyintegration/deephaven2/dtypes/_dtype.py b/pyintegration/deephaven2/dtypes.py similarity index 87% rename from pyintegration/deephaven2/dtypes/_dtype.py rename to pyintegration/deephaven2/dtypes.py index 4000f194926..e61e5d00819 100644 --- a/pyintegration/deephaven2/dtypes/_dtype.py +++ b/pyintegration/deephaven2/dtypes.py @@ -7,7 +7,7 @@ """ from __future__ import annotations -from typing import Any, Sequence, Callable +from typing import Any, Sequence, Callable, Iterable import jpy @@ -16,8 +16,6 @@ _JQstType = jpy.get_type("io.deephaven.qst.type.Type") _JTableTools = jpy.get_type("io.deephaven.engine.util.TableTools") -j_name_type_map = {} - def _qst_custom_type(cls_name: str): return _JQstType.find(_JTableTools.typeFromName(cls_name)) @@ -25,13 +23,16 @@ def _qst_custom_type(cls_name: str): class DType: """ A class representing a data type in Deephaven.""" + + _j_name_type_map = {} + @classmethod - def from_jtype(cls, j_class: Any) -> Any: + def from_jtype(cls, j_class: Any) -> DType: if not j_class: return None j_name = j_class.getName() - dtype = j_name_type_map.get(j_name) + dtype = DType._j_name_type_map.get(j_name) if not dtype: return cls(j_name=j_name, j_type=j_class) else: @@ -43,7 +44,7 @@ def __init__(self, j_name: str, j_type: Any = None, qst_type: Any = None, is_pri self.qst_type = qst_type if qst_type else _qst_custom_type(j_name) self.is_primitive = is_primitive - j_name_type_map[j_name] = self + DType._j_name_type_map[j_name] = self def __repr__(self): return self.j_name @@ -131,3 +132,15 @@ def array_from(self, seq: Sequence, remap: Callable[[Any], Any] = None): StringSet = DType(j_name="io.deephaven.stringset.StringSet") PyObject = DType(j_name="org.jpy.PyObject") JObject = DType(j_name="java.lang.Object") +DateTime = DType(j_name="io.deephaven.time.DateTime") +Period = DType(j_name="io.deephaven.time.Period") + + +def j_array_list(values: Iterable): + j_list = jpy.get_type("java.util.ArrayList")(len(values)) + try: + for v in values: + j_list.add(v) + return j_list + except Exception as e: + raise DHError(e, "failed to create a Java ArrayList from the Python collection.") from e diff --git a/pyintegration/deephaven2/dtypes/__init__.py b/pyintegration/deephaven2/dtypes/__init__.py deleted file mode 100644 index 57f3ad86535..00000000000 --- a/pyintegration/deephaven2/dtypes/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# -# Copyright (c) 2016-2021 Deephaven Data Labs and Patent Pending -# - -from __future__ import annotations - -from typing import Iterable - -from ._dtype import * -from ._datetime import DateTime, Period - - -def j_array_list(values: Iterable): - j_list = jpy.get_type("java.util.ArrayList")(len(values)) - try: - for v in values: - j_list.add(v) - return j_list - except Exception as e: - raise DHError(e, "failed to create a Java ArrayList from the Python collection.") from e diff --git a/pyintegration/deephaven2/dtypes/_datetime.py b/pyintegration/deephaven2/dtypes/_datetime.py deleted file mode 100644 index e6ee36fb121..00000000000 --- a/pyintegration/deephaven2/dtypes/_datetime.py +++ /dev/null @@ -1,107 +0,0 @@ -# -# Copyright (c) 2016-2021 Deephaven Data Labs and Patent Pending -# -""" This module defines the classes for Deephaven DateTime and Period. """ - -from typing import Any, Sequence, Callable - -from deephaven2.dtypes import DType, j_name_type_map - -from deephaven2 import DHError - -_DHDateTime = DType(j_name="io.deephaven.time.DateTime") -_DHPeriod = DType(j_name="io.deephaven.time.Period") - - -class DateTime: - """ This class defines a Deephaven DateTime object. """ - is_primitive = False - j_type = _DHDateTime.j_type - qst_type = _DHDateTime.qst_type - - def __init__(self, v: Any): - if isinstance(v, _DHDateTime.j_type): - self.j_datetime = v - else: - # TODO conversion support from Python built-in datetime, string datetime representation etc. - self.j_datetime = _DHDateTime(v) - - def __eq__(self, other): - if isinstance(other, DateTime): - return self.j_datetime.equals(other.j_datetime) - - return False - - def __repr__(self): - return str(self.j_datetime) - - @classmethod - def now(cls): - """ Returns a DataTime object initialized to the current time. """ - return DateTime(_DHDateTime.j_type.now()) - - @classmethod - def array(cls, size: int): - """ Creates a Java array of the Deephaven DateTime data type of the specified size. - - Args: - size (int): the size of the array - - Returns: - a Java array - - Raises: - DHError - """ - return _DHDateTime.array(size) - - @classmethod - def array_from(cls, seq: Sequence, remap: Callable[[Any], Any] = None): - """ Creates a Java array of Deephaven DateTime instances from a sequence. - - Args: - seq: a sequence of compatible data, e.g. list, tuple, numpy array, Pandas series, etc. - remap (optional): a callable that takes one value and maps it to another, for handling the translation of - special DH values such as NULL_INT, NAN_INT between Python and the DH engine - - Returns: - a Java array - - Raises: - DHError - """ - - new_seq = [] - for v in seq: - if v is None: - new_seq.append(None) - elif isinstance(v, DateTime): - new_seq.append(v.j_datetime) - elif isinstance(v, _DHDateTime.j_type): - new_seq.append(v) - else: - raise DHError(message="Not a valid datetime") - - return _DHDateTime.array_from(seq=new_seq, remap=remap) - - -class Period: - """ This class defines a Deephaven Period object. """ - is_primitive = False - j_type = _DHPeriod.j_type - qst_type = _DHPeriod.qst_type - - def __init__(self, v: Any): - if isinstance(v, _DHPeriod.j_type): - self.j_period = v - else: - # TODO? conversion support from Python timedelta, etc. - self.j_period = _DHPeriod(v) - - def __repr__(self): - return str(self.j_period) - - -# to Support type lookup -j_name_type_map["io.deephaven.time.DateTime"] = DateTime -j_name_type_map["io.deephaven.time.Period"] = Period diff --git a/pyintegration/deephaven2/hmtl.py b/pyintegration/deephaven2/hmtl.py new file mode 100644 index 00000000000..57f42759e9e --- /dev/null +++ b/pyintegration/deephaven2/hmtl.py @@ -0,0 +1,24 @@ +# +# Copyright (c) 2016-2021 Deephaven Data Labs and Patent Pending +# +import jpy + +from deephaven2 import DHError +from deephaven2.table import Table + +_JTableTools = jpy.get_type("io.deephaven.engine.util.TableTools") + + +def to_html(table: Table) -> str: + """ Returns a printout of a table formatted as HTML. Limit use to small tables to avoid running out of memory. + + Returns: + a String of the table printout formatted as HTML + + Raises: + DHError + """ + try: + return _JTableTools.html(table.j_table) + except Exception as e: + raise DHError(e, "table to_html failed") from e diff --git a/pyintegration/deephaven2/table.py b/pyintegration/deephaven2/table.py index cc414516b43..3924422e37b 100644 --- a/pyintegration/deephaven2/table.py +++ b/pyintegration/deephaven2/table.py @@ -98,20 +98,6 @@ def to_string(self, num_rows: int = 10, cols: List[str] = []) -> str: except Exception as e: raise DHError(e, "table to_string failed") from e - def to_html(self): - """ Returns a printout of a table formatted as HTML. Limit use to small tables to avoid running out of memory. - - Returns: - a String of the table printout formatted as HTML - - Raises: - DHError - """ - try: - return _JTableTools.html(self.j_table) - except Exception as e: - raise DHError(e, "table to_html failed") from e - def coalesce(self) -> Table: """ Returns a coalesced child table. """ return Table(j_table=self.j_table.coalesce()) diff --git a/pyintegration/deephaven2/datetimeutils.py b/pyintegration/deephaven2/time.py similarity index 66% rename from pyintegration/deephaven2/datetimeutils.py rename to pyintegration/deephaven2/time.py index abc7c7117fe..3cf439b06e2 100644 --- a/pyintegration/deephaven2/datetimeutils.py +++ b/pyintegration/deephaven2/time.py @@ -1,14 +1,13 @@ # # Copyright (c) 2016-2021 Deephaven Data Labs and Patent Pending # -""" Utilities for Deephaven date/time storage and manipulation.""" +""" This module defines functions for handling Deephaven date/time data. """ import jpy from deephaven2 import DHError -from deephaven2.dtypes import DType -from deephaven2.dtypes import DateTime, Period from deephaven2.constants import TimeZone +from deephaven2.dtypes import DateTime, Period SECOND = 1000000000 #: One second in nanoseconds. MINUTE = 60 * SECOND #: One minute in nanoseconds. @@ -20,13 +19,14 @@ _JDateTimeUtils = jpy.get_type("io.deephaven.time.DateTimeUtils") -def convert_datetime(s: str, quiet: bool = False) -> DateTime: - """ Converts a datetime string from a few specific zoned formats to a DateTime object +def to_datetime(s: str, quiet: bool = False): + """ Converts a datetime string to a DateTime object Args: - s (str): usually in the form yyyy-MM-ddThh:mm:ss and with optional sub-seconds after an + s (str): in the form yyyy-MM-ddThh:mm:ss and with optional sub-seconds after an optional decimal point, followed by a mandatory time zone character code - quiet (bool): to return None or raise an exception if the string can't be parsed, default is False + quiet (bool, optional): when True, if the datetime string can't be parsed, this function returns None, otherwise + it raises an exception; default is False Returns: a DateTime @@ -35,20 +35,22 @@ def convert_datetime(s: str, quiet: bool = False) -> DateTime: DHError """ if quiet: - return DateTime(_JDateTimeUtils.convertDateTimeQuiet(s)) + return _JDateTimeUtils.convertDateTimeQuiet(s) try: - return DateTime(_JDateTimeUtils.convertDateTime(s)) + return _JDateTimeUtils.convertDateTime(s) except Exception as e: raise DHError(e) from e -def convert_period(s: str, quiet: bool = False) -> Period: +def to_period(s: str, quiet: bool = False) -> Period: """ Converts a period string into a Period object. Args: - s (str): in the form of number+type, e.g. 1W for one week, or T+number+type, e.g. T1M for one minute - quiet (bool): to return None or raise an exception if the string can't be parsed, default is False + s (str): in the form of number+type, e.g. 1W for one week, or T+number+type, e.g. T1M for one minute, or a + combination, e.g. 1WT1H + quiet (bool, optional): when True, if the period string can't be parsed, this function returns None, otherwise + it raises an exception; default is False Returns: Period @@ -57,16 +59,16 @@ def convert_period(s: str, quiet: bool = False) -> Period: DHError """ if quiet: - return Period(_JDateTimeUtils.convertPeriodQuiet(s)) + return _JDateTimeUtils.convertPeriodQuiet(s) try: - return Period(_JDateTimeUtils.convertPeriod(s)) + return _JDateTimeUtils.convertPeriod(s) except Exception as e: raise DHError(e) from e -def convert_time(s, quiet: bool = False) -> int: - """ Converts a string time to nanoseconds from Epoch. +def to_nanos(s, quiet: bool = False) -> int: + """ Converts a time string to nanoseconds from the Epoch. Args: s (str): in the format of: hh:mm:ss[.nnnnnnnnn] @@ -87,9 +89,8 @@ def convert_time(s, quiet: bool = False) -> int: raise DHError(e) from e -def current_time() -> DateTime: - """ Provides the current date/time, or, if a custom timeProvider has been configured, provides the current - time according to the custom provider. +def now(): + """ Provides the current date/time. Returns: DateTime @@ -98,12 +99,12 @@ def current_time() -> DateTime: DHError """ try: - return DateTime(_JDateTimeUtils.currentTime()) + return _JDateTimeUtils.currentTime() except Exception as e: raise DHError(e) from e -def date_at_midnight(dt: DateTime, tz: TimeZone) -> DateTime: +def date_at_midnight(dt, tz: TimeZone): """ Returns a DateTime for the requested DateTime at midnight in the specified time zone. Args: @@ -117,14 +118,14 @@ def date_at_midnight(dt: DateTime, tz: TimeZone) -> DateTime: DHError """ try: - j_datetime = dt.j_datetime if dt else None - return DateTime(_JDateTimeUtils.dateAtMidnight(j_datetime, tz.value)) + + return _JDateTimeUtils.dateAtMidnight(dt, tz.value) except Exception as e: raise DHError(e) from e -def day_of_month(dt: DateTime, tz: TimeZone) -> int: - """ Returns an int value of the day of the month for a DateTime and specified time zone. +def day_of_month(dt, tz: TimeZone) -> int: + """ Returns an 1-based int value of the day of the month for a DateTime and specified time zone. Args: dt (DateTime): the DateTime for which to find the day of the month @@ -137,14 +138,13 @@ def day_of_month(dt: DateTime, tz: TimeZone) -> int: DHError """ try: - j_datetime = dt.j_datetime if dt else None - return _JDateTimeUtils.dayOfMonth(j_datetime, tz.value) + return _JDateTimeUtils.dayOfMonth(dt, tz.value) except Exception as e: raise DHError(e) from e -def day_of_week(dt: DateTime, tz: TimeZone) -> int: - """ Returns an int value of the day of the week for a DateTime in the specified time zone, with 1 being +def day_of_week(dt, tz: TimeZone) -> int: + """ Returns an 1-based int value of the day of the week for a DateTime in the specified time zone, with 1 being Monday and 7 being Sunday. Args: @@ -158,14 +158,13 @@ def day_of_week(dt: DateTime, tz: TimeZone) -> int: DHError """ try: - j_datetime = dt.j_datetime if dt else None - return _JDateTimeUtils.dayOfWeek(j_datetime, tz.value) + return _JDateTimeUtils.dayOfWeek(dt, tz.value) except Exception as e: raise DHError(e) from e -def day_of_year(dt: DateTime, tz: TimeZone) -> int: - """ Returns an int value of the day of the year (Julian date) for a DateTime in the specified time zone. +def day_of_year(dt, tz: TimeZone) -> int: + """ Returns an 1-based int value of the day of the year (Julian date) for a DateTime in the specified time zone. Args: dt (DateTime): the DateTime for which to find the day of the year @@ -178,8 +177,7 @@ def day_of_year(dt: DateTime, tz: TimeZone) -> int: DHError """ try: - j_datetime = dt.j_datetime if dt else None - return _JDateTimeUtils.dayOfYear(j_datetime, tz.value) + return _JDateTimeUtils.dayOfYear(dt, tz.value) except Exception as e: raise DHError(e) from e @@ -198,14 +196,12 @@ def diff_nanos(dt1: DateTime, dt2: DateTime) -> int: DHError """ try: - j_datetime1 = dt1.j_datetime if dt1 else None - j_datetime2 = dt2.j_datetime if dt2 else None - return _JDateTimeUtils.diffNanos(j_datetime1, j_datetime2) + return _JDateTimeUtils.diffNanos(dt1, dt2) except Exception as e: raise DHError(e) from e -def format_datetime(dt: DateTime, tz: TimeZone) -> str: +def format_datetime(dt, tz: TimeZone) -> str: """ Returns a string date/time representation formatted as yyyy-MM-ddThh:mm:ss.nnnnnnnnn TZ. Args: @@ -219,8 +215,7 @@ def format_datetime(dt: DateTime, tz: TimeZone) -> str: DHError """ try: - j_datetime = dt.j_datetime if dt else None - return _JDateTimeUtils.format(j_datetime, tz.value) + return _JDateTimeUtils.format(dt, tz.value) except Exception as e: raise DHError(e) from e @@ -229,7 +224,7 @@ def format_nanos(ns: int) -> str: """ Returns a string date/time representation formatted as yyyy-MM-ddThh:mm:ss.nnnnnnnnn. Args: - ns (int): the number of nanoseconds from Epoch + ns (int): the number of nanoseconds from the Epoch Returns: str @@ -243,7 +238,7 @@ def format_nanos(ns: int) -> str: raise DHError(e) from e -def format_as_date(dt: DateTime, tz: TimeZone) -> str: +def format_date(dt, tz: TimeZone) -> str: """ Returns a string date representation of a DateTime interpreted for a specified time zone formatted as yyy-MM-dd. Args: @@ -257,13 +252,12 @@ def format_as_date(dt: DateTime, tz: TimeZone) -> str: DHError """ try: - j_datetime = dt.j_datetime if dt else None - return _JDateTimeUtils.formatDate(j_datetime, tz.value) + return _JDateTimeUtils.formatDate(dt, tz.value) except Exception as e: raise DHError(e) from e -def hour_of_day(dt: DateTime, tz: TimeZone) -> int: +def hour_of_day(dt, tz: TimeZone) -> int: """ Returns an int value of the hour of the day for a DateTime in the specified time zone. The hour is on a 24 hour clock (0 - 23). @@ -278,8 +272,7 @@ def hour_of_day(dt: DateTime, tz: TimeZone) -> int: DHError """ try: - j_datetime = dt.j_datetime if dt else None - return _JDateTimeUtils.hourOfDay(j_datetime, tz.value) + return _JDateTimeUtils.hourOfDay(dt, tz.value) except Exception as e: raise DHError(e) from e @@ -298,15 +291,15 @@ def is_after(dt1: DateTime, dt2: DateTime) -> bool: DHError """ try: - j_datetime1 = dt1.j_datetime if dt1 else None - j_datetime2 = dt2.j_datetime if dt2 else None - return _JDateTimeUtils.isAfter(j_datetime1, j_datetime2) + dt1 = dt1 if dt1 else None + dt2 = dt2 if dt2 else None + return _JDateTimeUtils.isAfter(dt1, dt2) except Exception as e: raise DHError(e) from e -def is_before(dt1: DateTime, dt2: DateTime) -> bool: - """ Evaluates whether one DateTime value is later than a second DateTime value. +def is_before(dt1, dt2) -> bool: + """ Evaluates whether one DateTime value is before a second DateTime value. Args: dt1 (DateTime): the 1st DateTime @@ -319,14 +312,12 @@ def is_before(dt1: DateTime, dt2: DateTime) -> bool: DHError """ try: - j_datetime1 = dt1.j_datetime if dt1 else None - j_datetime2 = dt2.j_datetime if dt2 else None - return _JDateTimeUtils.isBefore(j_datetime1, j_datetime2) + return _JDateTimeUtils.isBefore(dt1, dt2) except Exception as e: raise DHError(e) from e -def lower_bin(dt: DateTime, interval: int, offset: int = 0) -> DateTime: +def lower_bin(dt, interval: int, offset: int = 0): """ Returns a DateTime value, which is at the starting (lower) end of a time range defined by the interval nanoseconds. For example, a 5*MINUTE intervalNanos value would return the date/time value for the start of the five minute window that contains the input date time. @@ -344,13 +335,12 @@ def lower_bin(dt: DateTime, interval: int, offset: int = 0) -> DateTime: DHError """ try: - j_datetime = dt.j_datetime if dt else None - return DateTime(_JDateTimeUtils.lowerBin(j_datetime, interval, offset)) + return _JDateTimeUtils.lowerBin(dt, interval, offset) except Exception as e: raise DHError(e) from e -def millis(dt: DateTime) -> int: +def millis(dt) -> int: """ Returns milliseconds since Epoch for a DateTime value. Args: @@ -363,13 +353,12 @@ def millis(dt: DateTime) -> int: DHError """ try: - j_datetime = dt.j_datetime if dt else None - return _JDateTimeUtils.millis(j_datetime) + return _JDateTimeUtils.millis(dt) except Exception as e: raise DHError(e) from e -def millis_of_day(dt: DateTime, tz: TimeZone) -> int: +def millis_of_day(dt, tz: TimeZone) -> int: """ Returns an int value of milliseconds since midnight for a DateTime in the specified time zone. Args: @@ -388,7 +377,7 @@ def millis_of_day(dt: DateTime, tz: TimeZone) -> int: raise DHError(e) from e -def millis_of_second(dt: DateTime, tz: TimeZone) -> int: +def millis_of_second(dt, tz: TimeZone) -> int: """ Returns an int value of milliseconds since the top of the second for a DateTime in the specified time zone. Args: @@ -402,8 +391,7 @@ def millis_of_second(dt: DateTime, tz: TimeZone) -> int: DHError """ try: - j_datetime = dt.j_datetime if dt else None - return _JDateTimeUtils.millisOfSecond(j_datetime, tz.value) + return _JDateTimeUtils.millisOfSecond(dt, tz.value) except Exception as e: raise DHError(e) from e @@ -426,7 +414,7 @@ def millis_to_nanos(ms: int) -> int: raise DHError(e) from e -def millis_to_time(ms: int) -> DateTime: +def millis_to_datetime(ms: int): """ Converts a value of milliseconds from Epoch in the UTC time zone to a DateTime. Args: @@ -444,8 +432,8 @@ def millis_to_time(ms: int) -> DateTime: raise DHError(e) from e -def minus(dt1: DateTime, dt2: DateTime) -> int: - """ Subtracts one time from another. +def minus(dt1, dt2) -> int: + """ Subtracts one time from another, returns the difference in nanos. Args: dt1 (DateTime): the 1st DateTime @@ -458,14 +446,12 @@ def minus(dt1: DateTime, dt2: DateTime) -> int: DHError """ try: - j_datetime1 = dt1.j_datetime if dt1 else None - j_datetime2 = dt2.j_datetime if dt2 else None - return _JDateTimeUtils.minus(j_datetime1, j_datetime2) + return _JDateTimeUtils.minus(dt1, dt2) except Exception as e: raise DHError(e) from e -def minus_nanos(dt: DateTime, ns: int) -> DateTime: +def minus_nanos(dt, ns: int): """ Subtracts nanoseconds from a DateTime. Args: @@ -479,13 +465,12 @@ def minus_nanos(dt: DateTime, ns: int) -> DateTime: DHError """ try: - j_datetime = dt.j_datetime if dt else None - return DateTime(_JDateTimeUtils.minus(j_datetime, ns)) + return _JDateTimeUtils.minus(dt, ns) except Exception as e: raise DHError(e) from e -def minus_period(dt: DateTime, period: Period) -> DateTime: +def minus_period(dt, period): """ Subtracts a period from a DateTime. Args: @@ -499,14 +484,12 @@ def minus_period(dt: DateTime, period: Period) -> DateTime: DHError """ try: - j_datetime = dt.j_datetime if dt else None - j_period = period.j_period if period else None - return DateTime(_JDateTimeUtils.minus(j_datetime, j_period)) + return _JDateTimeUtils.minus(dt, period) except Exception as e: raise DHError(e) from e -def minute_of_day(dt: DateTime, tz: TimeZone) -> int: +def minute_of_day(dt, tz: TimeZone) -> int: """ Returns an int value of minutes since midnight for a DateTime in the specified time zone. Args: @@ -520,13 +503,12 @@ def minute_of_day(dt: DateTime, tz: TimeZone) -> int: DHError """ try: - j_datetime = dt.j_datetime if dt else None - return _JDateTimeUtils.minuteOfDay(j_datetime, tz.value) + return _JDateTimeUtils.minuteOfDay(dt, tz.value) except Exception as e: raise DHError(e) from e -def minute_of_hour(dt: DateTime, tz: TimeZone) -> int: +def minute_of_hour(dt, tz: TimeZone) -> int: """ Returns an int value of minutes since the top of the hour for a DateTime in the specified time zone. Args: @@ -540,14 +522,14 @@ def minute_of_hour(dt: DateTime, tz: TimeZone) -> int: DHError """ try: - j_datetime = dt.j_datetime if dt else None - return _JDateTimeUtils.minuteOfHour(j_datetime, tz.value) + + return _JDateTimeUtils.minuteOfHour(dt, tz.value) except Exception as e: raise DHError(e) from e -def month_of_year(dt: DateTime, tz: TimeZone) -> int: - """ Returns an int value for the month of a DateTime in the specified time zone. +def month_of_year(dt, tz: TimeZone) -> int: + """ Returns an 1-based int value for the month of a DateTime in the specified time zone. Args: dt (DateTime): the DateTime for which to find the month @@ -560,13 +542,12 @@ def month_of_year(dt: DateTime, tz: TimeZone) -> int: DHError """ try: - j_datetime = dt.j_datetime if dt else None - return _JDateTimeUtils.monthOfYear(j_datetime, tz.value) + return _JDateTimeUtils.monthOfYear(dt, tz.value) except Exception as e: raise DHError(e) from e -def nanos(dt: DateTime) -> int: +def nanos(dt) -> int: """ Returns nanoseconds since Epoch for a DateTime value. Args: @@ -579,13 +560,13 @@ def nanos(dt: DateTime) -> int: DHError """ try: - j_datetime = dt.j_datetime if dt else None - return _JDateTimeUtils.nanos(j_datetime) + + return _JDateTimeUtils.nanos(dt) except Exception as e: raise DHError(e) from e -def nanos_of_day(dt: DateTime, tz: TimeZone) -> int: +def nanos_of_day(dt, tz: TimeZone) -> int: """ Returns a long value of nanoseconds since midnight for a DateTime in the specified time zone. Args: @@ -599,13 +580,12 @@ def nanos_of_day(dt: DateTime, tz: TimeZone) -> int: DHError """ try: - j_datetime = dt.j_datetime if dt else None - return _JDateTimeUtils.nanosOfDay(j_datetime, tz.value) + return _JDateTimeUtils.nanosOfDay(dt, tz.value) except Exception as e: raise DHError(e) from e -def nanos_of_second(dt: DateTime, tz: TimeZone) -> int: +def nanos_of_second(dt, tz: TimeZone) -> int: """ Returns a long value of nanoseconds since the top of the second for a DateTime in the specified time zone. Args: @@ -619,8 +599,8 @@ def nanos_of_second(dt: DateTime, tz: TimeZone) -> int: DHError """ try: - j_datetime = dt.j_datetime if dt else None - return _JDateTimeUtils.nanosOfSecond(j_datetime, tz.value) + + return _JDateTimeUtils.nanosOfSecond(dt, tz.value) except Exception as e: raise DHError(e) from e @@ -643,7 +623,7 @@ def nanos_to_millis(ns: int) -> int: raise DHError(e) from e -def nanos_to_time(ns: int) -> DateTime: +def nanos_to_datetime(ns: int): """ Converts a value of nanoseconds from Epoch to a DateTime. Args: @@ -653,13 +633,13 @@ def nanos_to_time(ns: int) -> DateTime: DateTime """ try: - return DateTime(_JDateTimeUtils.nanosToTime(ns)) + return _JDateTimeUtils.nanosToTime(ns) except Exception as e: raise DHError(e) from e -def plus_period(dt: DateTime, period: Period) -> DateTime: - """ Adds one time from another. +def plus_period(dt, period): + """ Adds a period to a DateTime. Args: dt (DateTime): the starting DateTime value @@ -672,14 +652,12 @@ def plus_period(dt: DateTime, period: Period) -> DateTime: DHError """ try: - j_datetime = dt.j_datetime if dt else None - j_period = period.j_period if period else None - return DateTime(_JDateTimeUtils.plus(j_datetime, j_period)) + return _JDateTimeUtils.plus(dt, period) except Exception as e: raise DHError(e) from e -def plus_nanos(dt: DateTime, ns: int) -> DateTime: +def plus_nanos(dt, ns: int): """ Adds nanoseconds to a DateTime. Args: @@ -693,13 +671,12 @@ def plus_nanos(dt: DateTime, ns: int) -> DateTime: DHError """ try: - j_datetime = dt.j_datetime if dt else None - return DateTime(_JDateTimeUtils.plus(j_datetime, ns)) + return _JDateTimeUtils.plus(dt, ns) except Exception as e: raise DHError(e) from e -def second_of_day(dt: DateTime, tz: TimeZone) -> int: +def second_of_day(dt, tz: TimeZone) -> int: """ Returns an int value of seconds since midnight for a DateTime in the specified time zone. Args: @@ -713,13 +690,12 @@ def second_of_day(dt: DateTime, tz: TimeZone) -> int: DHError """ try: - j_datetime = dt.j_datetime if dt else None - return _JDateTimeUtils.secondOfDay(j_datetime, tz.value) + return _JDateTimeUtils.secondOfDay(dt, tz.value) except Exception as e: raise DHError(e) from e -def second_of_minute(dt: DateTime, tz: TimeZone) -> int: +def second_of_minute(dt, tz: TimeZone) -> int: """ Returns an int value of seconds since the top of the minute for a DateTime in the specified time zone. Args: @@ -733,13 +709,12 @@ def second_of_minute(dt: DateTime, tz: TimeZone) -> int: DHError """ try: - j_datetime = dt.j_datetime if dt else None - return _JDateTimeUtils.secondOfMinute(j_datetime, tz.value) + return _JDateTimeUtils.secondOfMinute(dt, tz.value) except Exception as e: raise DHError(e) from e -def upper_bin(dt: DateTime, interval: int, offset: int = 0) -> DateTime: +def upper_bin(dt, interval: int, offset: int = 0): """ Returns a DateTime value, which is at the ending (upper) end of a time range defined by the interval nanoseconds. For example, a 5*MINUTE intervalNanos value would return the date/time value for the end of the five minute window that contains the input date time. @@ -757,13 +732,12 @@ def upper_bin(dt: DateTime, interval: int, offset: int = 0) -> DateTime: DHError """ try: - j_datetime = dt.j_datetime if dt else None - return DateTime(_JDateTimeUtils.upperBin(j_datetime, interval, offset)) + return _JDateTimeUtils.upperBin(dt, interval, offset) except Exception as e: raise DHError(e) from e -def year(dt: DateTime, tz: TimeZone) -> int: +def year(dt, tz: TimeZone) -> int: """ Returns an int value of the year for a DateTime in the specified time zone. Args: @@ -777,13 +751,12 @@ def year(dt: DateTime, tz: TimeZone) -> int: DHError """ try: - j_datetime = dt.j_datetime if dt else None - return _JDateTimeUtils.year(j_datetime, tz.value) + return _JDateTimeUtils.year(dt, tz.value) except Exception as e: raise DHError(e) from e -def year_of_century(dt: DateTime, tz: TimeZone) -> int: +def year_of_century(dt, tz: TimeZone) -> int: """ Returns an int value of the two-digit year for a DateTime in the specified time zone. Args: @@ -797,7 +770,6 @@ def year_of_century(dt: DateTime, tz: TimeZone) -> int: DHError """ try: - j_datetime = dt.j_datetime if dt else None - return _JDateTimeUtils.yearOfCentury(j_datetime, tz.value) + return _JDateTimeUtils.yearOfCentury(dt, tz.value) except Exception as e: raise DHError(e) from e diff --git a/pyintegration/tests/test_dtypes.py b/pyintegration/tests/test_dtypes.py index 167cbc66f82..3bc7809379f 100644 --- a/pyintegration/tests/test_dtypes.py +++ b/pyintegration/tests/test_dtypes.py @@ -13,6 +13,7 @@ from deephaven2 import dtypes from deephaven2.constants import * from deephaven2.dtypes import DateTime +from deephaven2.time import now from tests.testbase import BaseTestCase @@ -172,11 +173,10 @@ def remap_char(v): def test_datetime(self): dt1 = DateTime(round(time.time())) - dt2 = DateTime.now() + dt2 = now() values = [dt1, dt2, None] j_array = DateTime.array_from(values) - p_list = [DateTime(dt) if dt else dt for dt in j_array] - self.assertTrue(all(x == y for x, y in zip(p_list, values))) + self.assertTrue(all(x == y for x, y in zip(j_array, values))) if __name__ == '__main__': diff --git a/pyintegration/tests/test_datetimeutils.py b/pyintegration/tests/test_time.py similarity index 70% rename from pyintegration/tests/test_datetimeutils.py rename to pyintegration/tests/test_time.py index ac77f401781..189d2ab73ef 100644 --- a/pyintegration/tests/test_datetimeutils.py +++ b/pyintegration/tests/test_time.py @@ -6,220 +6,229 @@ from time import sleep from deephaven2.constants import NULL_LONG -from deephaven2.datetimeutils import * +from deephaven2.time import * class DateTimeUtilsTestCase(unittest.TestCase): - def test_convert_datetime(self): + def test_to_datetime(self): datetime_str = "2021-12-10T23:59:59" timezone_str = "NY" - dt = convert_datetime(f"{datetime_str} {timezone_str}") + dt = to_datetime(f"{datetime_str} {timezone_str}") self.assertTrue(str(dt).startswith(datetime_str)) with self.assertRaises(DHError) as cm: datetime_str = "2021-12-10T23:59:59" timezone_str = "--" - dt = convert_datetime(f"{datetime_str} {timezone_str}") + dt = to_datetime(f"{datetime_str} {timezone_str}") self.assertIn("RuntimeException", str(cm.exception)) - def test_convert_period(self): + def test_to_period(self): period_str = "1W" - period = convert_period(period_str) + period = to_period(period_str) self.assertEqual(str(period).upper(), period_str) period_str = "T1M" - period = convert_period(period_str) - self.assertEqual(repr(period).upper(), period_str) + period = to_period(period_str) + self.assertEqual(str(period).upper(), period_str) with self.assertRaises(DHError) as cm: period_str = "T1Y" - period = convert_period(period_str) + period = to_period(period_str) self.assertIn("RuntimeException", str(cm.exception)) - def test_convert_time(self): + def test_to_nanos(self): time_str = "530000:59:39.123456789" - in_nanos = convert_time(time_str) + in_nanos = to_nanos(time_str) self.assertEqual(str(in_nanos), "1908003579123456789") with self.assertRaises(DHError) as cm: time_str = "530000:59:39.X" - in_nanos = convert_time(time_str) + in_nanos = to_nanos(time_str) self.assertIn("RuntimeException", str(cm.exception)) time_str = "00:59:39.X" - in_nanos = convert_time(time_str, quiet=True) + in_nanos = to_nanos(time_str, quiet=True) self.assertEqual(in_nanos, NULL_LONG) def test_current_time_and_diff(self): - dt = current_time() + dt = now() sleep(1) - dt1 = current_time() + dt1 = now() self.assertGreaterEqual(diff_nanos(dt, dt1), 100000000) def test_date_at_midnight(self): - dt = current_time() + dt = now() mid_night_time_ny = date_at_midnight(dt, TimeZone.NY) mid_night_time_pt = date_at_midnight(dt, TimeZone.PT) self.assertGreaterEqual(diff_nanos(mid_night_time_ny, mid_night_time_pt), 0) def test_day_of_month(self): - dt = current_time() + dt = now() self.assertIn(day_of_month(dt, TimeZone.MT), range(1, 32)) + datetime_str = "2021-12-01T00:01:05" + timezone_str = "HI" + dt = to_datetime(f"{datetime_str} {timezone_str}") + self.assertEqual(day_of_month(dt, TimeZone.HI), 1) def test_day_of_week(self): - dt = current_time() + dt = now() self.assertIn(day_of_week(dt, TimeZone.MT), range(1, 8)) def test_day_of_year(self): - dt = current_time() + dt = now() self.assertIn(day_of_year(dt, TimeZone.MT), range(1, 366)) - def test_format_date(self): - dt = current_time() + def test_format_datetime(self): + dt = now() self.assertIn(TimeZone.SYD.name, format_datetime(dt, TimeZone.SYD)) def test_format_nanos(self): - dt = current_time() + dt = now() ns = nanos(dt) ns_str1 = format_nanos(ns).split(".")[-1] ns_str2 = format_datetime(dt, TimeZone.UTC).split(".")[-1] self.assertTrue(ns_str2.startswith(ns_str1)) - def test_format_as_date(self): - dt = current_time() - self.assertEqual(3, len(format_as_date(dt, TimeZone.MOS).split("-"))) + def test_format_date(self): + dt = now() + self.assertEqual(3, len(format_date(dt, TimeZone.MOS).split("-"))) def test_hour_of_day(self): - dt = current_time() + dt = now() self.assertIn(hour_of_day(dt, TimeZone.AL), range(0, 24)) def test_is_after(self): - dt1 = current_time() + dt1 = now() sleep(0.001) - dt2 = current_time() + dt2 = now() self.assertTrue(is_after(dt2, dt1)) def test_is_before(self): - dt1 = current_time() + dt1 = now() sleep(0.001) - dt2 = current_time() + dt2 = now() self.assertFalse(is_before(dt2, dt1)) def test_lower_bin(self): - dt = current_time() + dt = now() self.assertGreaterEqual(diff_nanos(lower_bin(dt, 1000000, MINUTE), dt), 0) def test_millis(self): - dt = current_time() + dt = now() self.assertGreaterEqual(nanos(dt), millis(dt) * 10 ** 6) def test_millis_of_second(self): - dt = current_time() + dt = now() self.assertGreaterEqual(millis_of_second(dt, TimeZone.AT), 0) def test_millis_to_nanos(self): - dt = current_time() + dt = now() ms = millis(dt) self.assertEqual(ms * 10 ** 6, millis_to_nanos(ms)) def test_minus(self): - dt1 = current_time() - dt2 = current_time() + dt1 = now() + dt2 = now() self.assertGreaterEqual(0, minus(dt1, dt2)) def test_minus_nanos(self): - dt = current_time() + dt = now() dt1 = minus_nanos(dt, 1) self.assertEqual(1, diff_nanos(dt1, dt)) def test_minus_period(self): period_str = "T1H" - period = convert_period(period_str) + period = to_period(period_str) - dt = current_time() + dt = now() dt1 = minus_period(dt, period) self.assertEqual(diff_nanos(dt1, dt), 60 * 60 * 10 ** 9) def test_minute_of_day(self): datetime_str = "2021-12-10T00:59:59" timezone_str = "BT" - dt = convert_datetime(f"{datetime_str} {timezone_str}") + dt = to_datetime(f"{datetime_str} {timezone_str}") self.assertEqual(59, minute_of_day(dt, TimeZone.BT)) def test_minute_of_hour(self): datetime_str = "2021-12-10T23:59:59" timezone_str = "CE" - dt = convert_datetime(f"{datetime_str} {timezone_str}") + dt = to_datetime(f"{datetime_str} {timezone_str}") self.assertEqual(59, minute_of_hour(dt, TimeZone.CE)) def test_month_of_year(self): datetime_str = "2021-08-10T23:59:59" timezone_str = "CH" - dt = convert_datetime(f"{datetime_str} {timezone_str}") + dt = to_datetime(f"{datetime_str} {timezone_str}") self.assertEqual(8, month_of_year(dt, TimeZone.CH)) def test_nanos_of_day(self): datetime_str = "2021-12-10T00:00:01" timezone_str = "CT" - dt = convert_datetime(f"{datetime_str} {timezone_str}") + dt = to_datetime(f"{datetime_str} {timezone_str}") self.assertEqual(10 ** 9, nanos_of_day(dt, TimeZone.CT)) def test_nanos_of_second(self): datetime_str = "2021-12-10T00:00:01.000000123" timezone_str = "ET" - dt = convert_datetime(f"{datetime_str} {timezone_str}") + dt = to_datetime(f"{datetime_str} {timezone_str}") self.assertEqual(123, nanos_of_second(dt, TimeZone.ET)) def test_nanos_to_millis(self): - dt = current_time() + dt = now() ns = nanos(dt) self.assertEqual(ns // 10 ** 6, nanos_to_millis(ns)) def test_nanos_to_time(self): - dt = current_time() + dt = now() ns = nanos(dt) - dt1 = nanos_to_time(ns) + dt1 = nanos_to_datetime(ns) self.assertEqual(dt, dt1) def test_plus_period(self): period_str = "T1H" - period = convert_period(period_str) + period = to_period(period_str) - dt = current_time() + dt = now() dt1 = plus_period(dt, period) self.assertEqual(diff_nanos(dt, dt1), 60 * 60 * 10 ** 9) + period_str = "1WT1H" + period = to_period(period_str) + dt2 = plus_period(dt, period) + self.assertEqual(diff_nanos(dt, dt2), (7 * 24 + 1) * 60 * 60 * 10 ** 9) + def test_plus_nanos(self): - dt = current_time() + dt = now() dt1 = plus_nanos(dt, 1) self.assertEqual(1, diff_nanos(dt, dt1)) def test_second_of_day(self): datetime_str = "2021-12-10T00:01:05" timezone_str = "HI" - dt = convert_datetime(f"{datetime_str} {timezone_str}") + dt = to_datetime(f"{datetime_str} {timezone_str}") self.assertEqual(65, second_of_day(dt, TimeZone.HI)) def test_second_of_minute(self): datetime_str = "2021-12-10T00:01:05" timezone_str = "HK" - dt = convert_datetime(f"{datetime_str} {timezone_str}") + dt = to_datetime(f"{datetime_str} {timezone_str}") self.assertEqual(5, second_of_minute(dt, TimeZone.HK)) def test_upper_bin(self): - dt = current_time() + dt = now() self.assertGreaterEqual(diff_nanos(dt, upper_bin(dt, 1000000, MINUTE)), 0) def test_year(self): datetime_str = "2021-12-10T00:01:05" timezone_str = "IN" - dt = convert_datetime(f"{datetime_str} {timezone_str}") + dt = to_datetime(f"{datetime_str} {timezone_str}") self.assertEqual(2021, year(dt, TimeZone.IN)) def test_year(self): datetime_str = "2021-12-10T00:01:05" timezone_str = "JP" - dt = convert_datetime(f"{datetime_str} {timezone_str}") + dt = to_datetime(f"{datetime_str} {timezone_str}") self.assertEqual(21, year_of_century(dt, TimeZone.JP)) From 8cf1c5438a008a742d73b2a0fc43927581382ada Mon Sep 17 00:00:00 2001 From: jianfengmao Date: Wed, 19 Jan 2022 18:18:01 -0700 Subject: [PATCH 10/12] Added docstring on Period string format --- pyintegration/deephaven2/time.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyintegration/deephaven2/time.py b/pyintegration/deephaven2/time.py index 3cf439b06e2..a411b3fbe59 100644 --- a/pyintegration/deephaven2/time.py +++ b/pyintegration/deephaven2/time.py @@ -47,8 +47,8 @@ def to_period(s: str, quiet: bool = False) -> Period: """ Converts a period string into a Period object. Args: - s (str): in the form of number+type, e.g. 1W for one week, or T+number+type, e.g. T1M for one minute, or a - combination, e.g. 1WT1H + s (str): a string in the form of nYnMnWnDTnHnMnS, with n being numeric values, e.g. 1W for one week, T1M for + one minute, 1WT1H for one week plus one hour quiet (bool, optional): when True, if the period string can't be parsed, this function returns None, otherwise it raises an exception; default is False From 2bfbe1268b71d8c4cb1b7840cf3fc6dab59bd0ee Mon Sep 17 00:00:00 2001 From: jianfengmao Date: Mon, 24 Jan 2022 15:02:42 -0700 Subject: [PATCH 11/12] Improved docstrings/typehints/test coverage --- pyintegration/deephaven2/constants.py | 53 ---- pyintegration/deephaven2/{hmtl.py => html.py} | 6 +- pyintegration/deephaven2/time.py | 263 +++++++++++------- pyintegration/tests/test_time.py | 32 ++- 4 files changed, 190 insertions(+), 164 deletions(-) rename pyintegration/deephaven2/{hmtl.py => html.py} (67%) diff --git a/pyintegration/deephaven2/constants.py b/pyintegration/deephaven2/constants.py index 758b30388a5..e17972fd4a2 100644 --- a/pyintegration/deephaven2/constants.py +++ b/pyintegration/deephaven2/constants.py @@ -6,7 +6,6 @@ import jpy _JQueryConstants = jpy.get_type("io.deephaven.util.QueryConstants") -_JTimeZone = jpy.get_type("io.deephaven.time.TimeZone") class SortDirection(Enum): @@ -94,55 +93,3 @@ class SortDirection(Enum): """ Minimum positive value of type double. """ -class TimeZone(Enum): - """ A Enum for known time zones. """ - NY = _JTimeZone.TZ_NY - """ America/New_York """ - ET = _JTimeZone.TZ_ET - """ America/New_York """ - MN = _JTimeZone.TZ_MN - """ America/Chicago """ - CT = _JTimeZone.TZ_CT - """ America/Chicago """ - MT = _JTimeZone.TZ_MT - """ America/Denver """ - PT = _JTimeZone.TZ_PT - """ America/Los_Angeles """ - HI = _JTimeZone.TZ_HI - """ Pacific/Honolulu """ - BT = _JTimeZone.TZ_BT - """ America/Sao_Paulo """ - KR = _JTimeZone.TZ_KR - """ Asia/Seoul """ - HK = _JTimeZone.TZ_HK - """ Asia/Hong_Kong """ - JP = _JTimeZone.TZ_JP - """ Asia/Tokyo """ - AT = _JTimeZone.TZ_AT - """ Canada/Atlantic """ - NF = _JTimeZone.TZ_NF - """ Canada/Newfoundland """ - AL = _JTimeZone.TZ_AL - """ America/Anchorage """ - IN = _JTimeZone.TZ_IN - """ Asia/Kolkata """ - CE = _JTimeZone.TZ_CE - """ Europe/Berlin """ - SG = _JTimeZone.TZ_SG - """ Asia/Singapore """ - LON = _JTimeZone.TZ_LON - """ Europe/London """ - MOS = _JTimeZone.TZ_MOS - """ Europe/Moscow """ - SHG = _JTimeZone.TZ_SHG - """ Asia/Shanghai """ - CH = _JTimeZone.TZ_CH - """ Europe/Zurich """ - NL = _JTimeZone.TZ_NL - """ Europe/Amsterdam """ - TW = _JTimeZone.TZ_TW - """ Asia/Taipei """ - SYD = _JTimeZone.TZ_SYD - """ Australia/Sydney """ - UTC = _JTimeZone.TZ_UTC - """ UTC """ diff --git a/pyintegration/deephaven2/hmtl.py b/pyintegration/deephaven2/html.py similarity index 67% rename from pyintegration/deephaven2/hmtl.py rename to pyintegration/deephaven2/html.py index 57f42759e9e..87af75f5ad0 100644 --- a/pyintegration/deephaven2/hmtl.py +++ b/pyintegration/deephaven2/html.py @@ -1,6 +1,8 @@ # # Copyright (c) 2016-2021 Deephaven Data Labs and Patent Pending # +""" This module supports exporting Deephaven data in the HTML format. """ + import jpy from deephaven2 import DHError @@ -10,10 +12,10 @@ def to_html(table: Table) -> str: - """ Returns a printout of a table formatted as HTML. Limit use to small tables to avoid running out of memory. + """ Returns a table formatted as an HTML string. Limit use to small tables to avoid running out of memory. Returns: - a String of the table printout formatted as HTML + a HTML string Raises: DHError diff --git a/pyintegration/deephaven2/time.py b/pyintegration/deephaven2/time.py index a411b3fbe59..715671381db 100644 --- a/pyintegration/deephaven2/time.py +++ b/pyintegration/deephaven2/time.py @@ -2,11 +2,11 @@ # Copyright (c) 2016-2021 Deephaven Data Labs and Patent Pending # """ This module defines functions for handling Deephaven date/time data. """ +from enum import Enum import jpy from deephaven2 import DHError -from deephaven2.constants import TimeZone from deephaven2.dtypes import DateTime, Period SECOND = 1000000000 #: One second in nanoseconds. @@ -17,16 +17,70 @@ YEAR = 52 * WEEK #: One year in nanoseconds. _JDateTimeUtils = jpy.get_type("io.deephaven.time.DateTimeUtils") - - -def to_datetime(s: str, quiet: bool = False): - """ Converts a datetime string to a DateTime object - - Args: - s (str): in the form yyyy-MM-ddThh:mm:ss and with optional sub-seconds after an - optional decimal point, followed by a mandatory time zone character code - quiet (bool, optional): when True, if the datetime string can't be parsed, this function returns None, otherwise - it raises an exception; default is False +_JTimeZone = jpy.get_type("io.deephaven.time.TimeZone") + + +class TimeZone(Enum): + """ A Enum for known time zones. """ + NY = _JTimeZone.TZ_NY + """ America/New_York """ + ET = _JTimeZone.TZ_ET + """ America/New_York """ + MN = _JTimeZone.TZ_MN + """ America/Chicago """ + CT = _JTimeZone.TZ_CT + """ America/Chicago """ + MT = _JTimeZone.TZ_MT + """ America/Denver """ + PT = _JTimeZone.TZ_PT + """ America/Los_Angeles """ + HI = _JTimeZone.TZ_HI + """ Pacific/Honolulu """ + BT = _JTimeZone.TZ_BT + """ America/Sao_Paulo """ + KR = _JTimeZone.TZ_KR + """ Asia/Seoul """ + HK = _JTimeZone.TZ_HK + """ Asia/Hong_Kong """ + JP = _JTimeZone.TZ_JP + """ Asia/Tokyo """ + AT = _JTimeZone.TZ_AT + """ Canada/Atlantic """ + NF = _JTimeZone.TZ_NF + """ Canada/Newfoundland """ + AL = _JTimeZone.TZ_AL + """ America/Anchorage """ + IN = _JTimeZone.TZ_IN + """ Asia/Kolkata """ + CE = _JTimeZone.TZ_CE + """ Europe/Berlin """ + SG = _JTimeZone.TZ_SG + """ Asia/Singapore """ + LON = _JTimeZone.TZ_LON + """ Europe/London """ + MOS = _JTimeZone.TZ_MOS + """ Europe/Moscow """ + SHG = _JTimeZone.TZ_SHG + """ Asia/Shanghai """ + CH = _JTimeZone.TZ_CH + """ Europe/Zurich """ + NL = _JTimeZone.TZ_NL + """ Europe/Amsterdam """ + TW = _JTimeZone.TZ_TW + """ Asia/Taipei """ + SYD = _JTimeZone.TZ_SYD + """ Australia/Sydney """ + UTC = _JTimeZone.TZ_UTC + """ UTC """ + + +def to_datetime(s: str, quiet: bool = False) -> DateTime: + """ Converts a datetime string to a DateTime object. + + Args: + s (str): in the form of "yyyy-MM-ddThh:mm:ss[.SSSSSSSSS] TZ" + quiet (bool): when True, if the datetime string can't be parsed, this function returns None, otherwise + it raises an exception. The default is False Returns: a DateTime @@ -49,11 +103,11 @@ def to_period(s: str, quiet: bool = False) -> Period: Args: s (str): a string in the form of nYnMnWnDTnHnMnS, with n being numeric values, e.g. 1W for one week, T1M for one minute, 1WT1H for one week plus one hour - quiet (bool, optional): when True, if the period string can't be parsed, this function returns None, otherwise - it raises an exception; default is False + quiet (bool): when True, if the period string can't be parsed, this function returns None, otherwise + it raises an exception. The default is False Returns: - Period + a Period Raises: DHError @@ -71,7 +125,7 @@ def to_nanos(s, quiet: bool = False) -> int: """ Converts a time string to nanoseconds from the Epoch. Args: - s (str): in the format of: hh:mm:ss[.nnnnnnnnn] + s (str): in the format of: hh:mm:ss[.SSSSSSSSS] quiet (bool): to return None or raise an exception if the string can't be parsed, default is False Returns: @@ -89,8 +143,8 @@ def to_nanos(s, quiet: bool = False) -> int: raise DHError(e) from e -def now(): - """ Provides the current date/time. +def now() -> DateTime: + """ Provides the current datetime. Returns: DateTime @@ -104,7 +158,7 @@ def now(): raise DHError(e) from e -def date_at_midnight(dt, tz: TimeZone): +def datetime_at_midnight(dt: DateTime, tz: TimeZone) -> DateTime: """ Returns a DateTime for the requested DateTime at midnight in the specified time zone. Args: @@ -124,15 +178,15 @@ def date_at_midnight(dt, tz: TimeZone): raise DHError(e) from e -def day_of_month(dt, tz: TimeZone) -> int: +def day_of_month(dt: DateTime, tz: TimeZone) -> int: """ Returns an 1-based int value of the day of the month for a DateTime and specified time zone. Args: dt (DateTime): the DateTime for which to find the day of the month - tz (TimeZone): the TimeZone to use when interpreting the date/time + tz (TimeZone): the TimeZone to use when interpreting the DateTime Returns: - int + int: NULL_INT if dt is None Raises: DHError @@ -143,16 +197,16 @@ def day_of_month(dt, tz: TimeZone) -> int: raise DHError(e) from e -def day_of_week(dt, tz: TimeZone) -> int: +def day_of_week(dt: DateTime, tz: TimeZone) -> int: """ Returns an 1-based int value of the day of the week for a DateTime in the specified time zone, with 1 being Monday and 7 being Sunday. Args: dt (DateTime): the DateTime for which to find the day of the week. - tz (TimeZone): the TimeZone to use when interpreting the date/time. + tz (TimeZone): the TimeZone to use when interpreting the Datetime. Returns: - int + int: NULL_INT if dt is None Raises: DHError @@ -163,15 +217,15 @@ def day_of_week(dt, tz: TimeZone) -> int: raise DHError(e) from e -def day_of_year(dt, tz: TimeZone) -> int: +def day_of_year(dt: DateTime, tz: TimeZone) -> int: """ Returns an 1-based int value of the day of the year (Julian date) for a DateTime in the specified time zone. Args: dt (DateTime): the DateTime for which to find the day of the year - tz (TimeZone): the TimeZone to use when interpreting the date/time + tz (TimeZone): the TimeZone to use when interpreting the DateTime Returns: - int + int: NULL_INT if dt is None Raises: DHError @@ -190,7 +244,7 @@ def diff_nanos(dt1: DateTime, dt2: DateTime) -> int: dt2 (DateTime): the 2nd DateTime Returns: - int + int: NULL_LONG if either dt1 or dt2 is None Raises: DHError @@ -201,12 +255,12 @@ def diff_nanos(dt1: DateTime, dt2: DateTime) -> int: raise DHError(e) from e -def format_datetime(dt, tz: TimeZone) -> str: - """ Returns a string date/time representation formatted as yyyy-MM-ddThh:mm:ss.nnnnnnnnn TZ. +def format_datetime(dt: DateTime, tz: TimeZone) -> str: + """ Returns a string DateTime representation formatted as "yyyy-MM-ddThh:mm:ss.SSSSSSSSS TZ". Args: dt (DateTime): the DateTime to format as a string - tz (TimeZone): the TimeZone to use when interpreting the date/time + tz (TimeZone): the TimeZone to use when interpreting the DateTime Returns: str @@ -221,7 +275,7 @@ def format_datetime(dt, tz: TimeZone) -> str: def format_nanos(ns: int) -> str: - """ Returns a string date/time representation formatted as yyyy-MM-ddThh:mm:ss.nnnnnnnnn. + """ Returns a string DateTime representation formatted as "yyyy-MM-ddThh:mm:ss.SSSSSSSSS". Args: ns (int): the number of nanoseconds from the Epoch @@ -238,12 +292,13 @@ def format_nanos(ns: int) -> str: raise DHError(e) from e -def format_date(dt, tz: TimeZone) -> str: - """ Returns a string date representation of a DateTime interpreted for a specified time zone formatted as yyy-MM-dd. +def format_date(dt: DateTime, tz: TimeZone) -> str: + """ Returns a string date representation of a DateTime interpreted for a specified time zone formatted as + "yyy-MM-dd". Args: dt (DateTime): the DateTime to format - tz (TimeZone): the TimeZone to use when interpreting the date/time + tz (TimeZone): the TimeZone to use when interpreting the DateTime Returns: str @@ -257,16 +312,15 @@ def format_date(dt, tz: TimeZone) -> str: raise DHError(e) from e -def hour_of_day(dt, tz: TimeZone) -> int: - """ Returns an int value of the hour of the day for a DateTime in the specified time zone. The hour is on a - 24 hour clock (0 - 23). +def hour_of_day(dt: DateTime, tz: TimeZone) -> int: + """ Returns the hour of the day for a DateTime in the specified time zone. The hour is on a 24 hour clock (0 - 23). Args: dt (DateTime): the DateTime for which to find the hour of the day - tz (TimeZone): the TimeZone to use when interpreting the date/time + tz (TimeZone): the TimeZone to use when interpreting the DateTime Returns: - int + int: NULL_INT if dt is None Raises: DHError @@ -298,7 +352,7 @@ def is_after(dt1: DateTime, dt2: DateTime) -> bool: raise DHError(e) from e -def is_before(dt1, dt2) -> bool: +def is_before(dt1: DateTime, dt2: DateTime) -> bool: """ Evaluates whether one DateTime value is before a second DateTime value. Args: @@ -317,9 +371,9 @@ def is_before(dt1, dt2) -> bool: raise DHError(e) from e -def lower_bin(dt, interval: int, offset: int = 0): +def lower_bin(dt: DateTime, interval: int, offset: int = 0) -> DateTime: """ Returns a DateTime value, which is at the starting (lower) end of a time range defined by the interval - nanoseconds. For example, a 5*MINUTE intervalNanos value would return the date/time value for the start of the + nanoseconds. For example, a 5*MINUTE intervalNanos value would return the DateTime value for the start of the five minute window that contains the input date time. Args: @@ -340,14 +394,14 @@ def lower_bin(dt, interval: int, offset: int = 0): raise DHError(e) from e -def millis(dt) -> int: +def millis(dt: DateTime) -> int: """ Returns milliseconds since Epoch for a DateTime value. Args: dt (DateTime): the DateTime for which the milliseconds offset should be returned Returns: - int + int: NULL_LONG if dt is None Raises: DHError @@ -358,15 +412,15 @@ def millis(dt) -> int: raise DHError(e) from e -def millis_of_day(dt, tz: TimeZone) -> int: - """ Returns an int value of milliseconds since midnight for a DateTime in the specified time zone. +def millis_of_day(dt: DateTime, tz: TimeZone) -> int: + """ Returns the number of milliseconds since midnight for a DateTime in the specified time zone. Args: dt (DateTime): the DateTime for which to find the milliseconds since midnight - tz (TimeZone): the TimeZone to use when interpreting the date/time + tz (TimeZone): the TimeZone to use when interpreting the DateTime Returns: - int + int: NULL_INT if dt is None Raises: DHError @@ -377,15 +431,15 @@ def millis_of_day(dt, tz: TimeZone) -> int: raise DHError(e) from e -def millis_of_second(dt, tz: TimeZone) -> int: - """ Returns an int value of milliseconds since the top of the second for a DateTime in the specified time zone. +def millis_of_second(dt: DateTime, tz: TimeZone) -> int: + """ Returns the number of milliseconds since the top of the second for a DateTime in the specified time zone. Args: dt (DateTime): the DateTime for which to find the milliseconds - tz (TimeZone): the TimeZone to use when interpreting the date/time + tz (TimeZone): the TimeZone to use when interpreting the DateTime Returns: - int + int: NULL_INT if dt is None Raises: DHError @@ -403,7 +457,7 @@ def millis_to_nanos(ms: int) -> int: ms (int): the milliseconds value to convert Returns: - int + int: NULL_LONG if ms is NULL_LONG Raises: DHError @@ -414,7 +468,7 @@ def millis_to_nanos(ms: int) -> int: raise DHError(e) from e -def millis_to_datetime(ms: int): +def millis_to_datetime(ms: int) -> DateTime: """ Converts a value of milliseconds from Epoch in the UTC time zone to a DateTime. Args: @@ -432,7 +486,7 @@ def millis_to_datetime(ms: int): raise DHError(e) from e -def minus(dt1, dt2) -> int: +def minus(dt1: DateTime, dt2: DateTime) -> int: """ Subtracts one time from another, returns the difference in nanos. Args: @@ -440,7 +494,7 @@ def minus(dt1, dt2) -> int: dt2 (DateTiem): the 2nd DateTime Returns: - int + int: NULL_LONG if either dt1 or dt2 is None Raises: DHError @@ -451,12 +505,12 @@ def minus(dt1, dt2) -> int: raise DHError(e) from e -def minus_nanos(dt, ns: int): +def minus_nanos(dt: DateTime, ns: int) -> DateTime: """ Subtracts nanoseconds from a DateTime. Args: dt (DateTime): the starting DateTime value - ns (int): the long number of nanoseconds to subtract from dateTime + ns (int): the number of nanoseconds to subtract from dateTime Returns: DateTime @@ -470,7 +524,7 @@ def minus_nanos(dt, ns: int): raise DHError(e) from e -def minus_period(dt, period): +def minus_period(dt: DateTime, period) -> DateTime: """ Subtracts a period from a DateTime. Args: @@ -489,15 +543,15 @@ def minus_period(dt, period): raise DHError(e) from e -def minute_of_day(dt, tz: TimeZone) -> int: - """ Returns an int value of minutes since midnight for a DateTime in the specified time zone. +def minute_of_day(dt: DateTime, tz: TimeZone) -> int: + """ Returns the number of minutes since midnight for a DateTime in the specified time zone. Args: dt (DateTime): the DateTime for which to find the minutes - tz (TimeZone): the TimeZone to use when interpreting the date/time + tz (TimeZone): the TimeZone to use when interpreting the DateTime Returns: - int + int: NULL_INT if dt is None Raises: DHError @@ -508,15 +562,15 @@ def minute_of_day(dt, tz: TimeZone) -> int: raise DHError(e) from e -def minute_of_hour(dt, tz: TimeZone) -> int: - """ Returns an int value of minutes since the top of the hour for a DateTime in the specified time zone. +def minute_of_hour(dt: DateTime, tz: TimeZone) -> int: + """ Returns the number of minutes since the top of the hour for a DateTime in the specified time zone. Args: dt (DateTime): the DateTime for which to find the minutes - tz (TimeZone): the TimeZone to use when interpreting the date/time + tz (TimeZone): the TimeZone to use when interpreting the DateTime Returns: - int + int: NULL_INT if dt is None Raises: DHError @@ -528,15 +582,16 @@ def minute_of_hour(dt, tz: TimeZone) -> int: raise DHError(e) from e -def month_of_year(dt, tz: TimeZone) -> int: - """ Returns an 1-based int value for the month of a DateTime in the specified time zone. +def month_of_year(dt: DateTime, tz: TimeZone) -> int: + """ Returns an 1-based int value for the month of a DateTime in the specified time zone. January is 1, + and December is 12. Args: dt (DateTime): the DateTime for which to find the month - tz (TimeZone): the TimeZone to use when interpreting the date/time + tz (TimeZone): the TimeZone to use when interpreting the DateTime Returns: - int + int: NULL_INT if dt is None Raises: DHError @@ -547,14 +602,14 @@ def month_of_year(dt, tz: TimeZone) -> int: raise DHError(e) from e -def nanos(dt) -> int: +def nanos(dt: DateTime) -> int: """ Returns nanoseconds since Epoch for a DateTime value. Args: dt (DateTime): the DateTime for which the nanoseconds offset should be returned Returns: - int + int: NULL_LONG if dt is None Raises: DHError @@ -566,15 +621,15 @@ def nanos(dt) -> int: raise DHError(e) from e -def nanos_of_day(dt, tz: TimeZone) -> int: - """ Returns a long value of nanoseconds since midnight for a DateTime in the specified time zone. +def nanos_of_day(dt: DateTime, tz: TimeZone) -> int: + """ Returns the number of nanoseconds since midnight for a DateTime in the specified time zone. Args: dt (DateTime): the DateTime for which to find the nanoseconds since midnight - tz (TimeZone): the TimeZone to use when interpreting the date/time + tz (TimeZone): the TimeZone to use when interpreting the DateTime Returns: - int + int: NULL_LONG if dt is None Raises: DHError @@ -585,15 +640,15 @@ def nanos_of_day(dt, tz: TimeZone) -> int: raise DHError(e) from e -def nanos_of_second(dt, tz: TimeZone) -> int: - """ Returns a long value of nanoseconds since the top of the second for a DateTime in the specified time zone. +def nanos_of_second(dt: DateTime, tz: TimeZone) -> int: + """ Returns the number of nanoseconds since the top of the second for a DateTime in the specified time zone. Args: dt (DateTime): the DateTime for which to find the nanoseconds - tz (TimeZone): the TimeZone to use when interpreting the date/time + tz (TimeZone): the TimeZone to use when interpreting the DateTime Returns: - int + int: NULL_LONG if dt is None Raises: DHError @@ -612,7 +667,7 @@ def nanos_to_millis(ns: int) -> int: ns (int): the value of nanoseconds to convert Returns: - int + int: NULL_LONG if ns is NULL_LONG Raises: DHError @@ -623,7 +678,7 @@ def nanos_to_millis(ns: int) -> int: raise DHError(e) from e -def nanos_to_datetime(ns: int): +def nanos_to_datetime(ns: int) -> DateTime: """ Converts a value of nanoseconds from Epoch to a DateTime. Args: @@ -638,7 +693,7 @@ def nanos_to_datetime(ns: int): raise DHError(e) from e -def plus_period(dt, period): +def plus_period(dt: DateTime, period: Period) -> DateTime: """ Adds a period to a DateTime. Args: @@ -646,7 +701,7 @@ def plus_period(dt, period): period (Period): the Period to add to the DateTime Returns: - DateTime + DateTime: None if either dt or period is None Raises: DHError @@ -657,15 +712,15 @@ def plus_period(dt, period): raise DHError(e) from e -def plus_nanos(dt, ns: int): +def plus_nanos(dt: DateTime, ns: int) -> DateTime: """ Adds nanoseconds to a DateTime. Args: dt (DateTime): the starting DateTime value - ns (int): the long number of nanoseconds to add to DateTime + ns (int): the number of nanoseconds to add to DateTime Returns: - DateTime + DateTime: None if dt is None or ns is NULL_LONG Raises: DHError @@ -676,15 +731,15 @@ def plus_nanos(dt, ns: int): raise DHError(e) from e -def second_of_day(dt, tz: TimeZone) -> int: - """ Returns an int value of seconds since midnight for a DateTime in the specified time zone. +def second_of_day(dt: DateTime, tz: TimeZone) -> int: + """ Returns the number of seconds since midnight for a DateTime in the specified time zone. Args: dt (DateTime): the DateTime for which to find the seconds - tz (TimeZone): the TimeZone to use when interpreting the date/time + tz (TimeZone): the TimeZone to use when interpreting the DateTime Returns: - int + int: NULL_INT if dt is None Raises: DHError @@ -695,15 +750,15 @@ def second_of_day(dt, tz: TimeZone) -> int: raise DHError(e) from e -def second_of_minute(dt, tz: TimeZone) -> int: - """ Returns an int value of seconds since the top of the minute for a DateTime in the specified time zone. +def second_of_minute(dt: DateTime, tz: TimeZone) -> int: + """ Returns the number of seconds since the top of the minute for a DateTime in the specified time zone. Args: dt (DateTime): the DateTime for which to find the seconds - tz (TimeZone): the TimeZone to use when interpreting the date/time + tz (TimeZone): the TimeZone to use when interpreting the DateTime Returns: - int + int: NULL_INT if dt is None Raises: DHError @@ -716,7 +771,7 @@ def second_of_minute(dt, tz: TimeZone) -> int: def upper_bin(dt, interval: int, offset: int = 0): """ Returns a DateTime value, which is at the ending (upper) end of a time range defined by the interval - nanoseconds. For example, a 5*MINUTE intervalNanos value would return the date/time value for the end of the five + nanoseconds. For example, a 5*MINUTE intervalNanos value would return the DateTime value for the end of the five minute window that contains the input date time. Args: @@ -737,15 +792,15 @@ def upper_bin(dt, interval: int, offset: int = 0): raise DHError(e) from e -def year(dt, tz: TimeZone) -> int: +def year(dt: DateTime, tz: TimeZone) -> int: """ Returns an int value of the year for a DateTime in the specified time zone. Args: dt (DateTime): the DateTime for which to find the year - tz (TimeZone): the TimeZone to use when interpreting the date/time + tz (TimeZone): the TimeZone to use when interpreting the DateTime Returns: - int + int: NULL_INT if dt is None Raises: DHError @@ -756,12 +811,12 @@ def year(dt, tz: TimeZone) -> int: raise DHError(e) from e -def year_of_century(dt, tz: TimeZone) -> int: - """ Returns an int value of the two-digit year for a DateTime in the specified time zone. +def year_of_century(dt: DateTime, tz: TimeZone) -> int: + """ Returns the two-digit year for a DateTime in the specified time zone. Args: dt (DateTime): the DateTime for which to find the year - tz (TimeZone): the TimeZone to use when interpreting the date/time + tz (TimeZone): the TimeZone to use when interpreting the DateTime Returns: int diff --git a/pyintegration/tests/test_time.py b/pyintegration/tests/test_time.py index 189d2ab73ef..9caaed77171 100644 --- a/pyintegration/tests/test_time.py +++ b/pyintegration/tests/test_time.py @@ -5,7 +5,7 @@ import unittest from time import sleep -from deephaven2.constants import NULL_LONG +from deephaven2.constants import NULL_LONG, NULL_INT from deephaven2.time import * @@ -56,10 +56,10 @@ def test_current_time_and_diff(self): dt1 = now() self.assertGreaterEqual(diff_nanos(dt, dt1), 100000000) - def test_date_at_midnight(self): + def test_datetime_at_midnight(self): dt = now() - mid_night_time_ny = date_at_midnight(dt, TimeZone.NY) - mid_night_time_pt = date_at_midnight(dt, TimeZone.PT) + mid_night_time_ny = datetime_at_midnight(dt, TimeZone.NY) + mid_night_time_pt = datetime_at_midnight(dt, TimeZone.PT) self.assertGreaterEqual(diff_nanos(mid_night_time_ny, mid_night_time_pt), 0) def test_day_of_month(self): @@ -69,14 +69,17 @@ def test_day_of_month(self): timezone_str = "HI" dt = to_datetime(f"{datetime_str} {timezone_str}") self.assertEqual(day_of_month(dt, TimeZone.HI), 1) + self.assertEqual(day_of_month(None, TimeZone.HI), NULL_INT) def test_day_of_week(self): dt = now() self.assertIn(day_of_week(dt, TimeZone.MT), range(1, 8)) + self.assertEqual(day_of_week(None, TimeZone.MT), NULL_INT) def test_day_of_year(self): dt = now() self.assertIn(day_of_year(dt, TimeZone.MT), range(1, 366)) + self.assertEqual(day_of_year(None, TimeZone.MT), NULL_INT) def test_format_datetime(self): dt = now() @@ -96,18 +99,21 @@ def test_format_date(self): def test_hour_of_day(self): dt = now() self.assertIn(hour_of_day(dt, TimeZone.AL), range(0, 24)) + self.assertEqual(hour_of_day(None, TimeZone.AL), NULL_INT) def test_is_after(self): dt1 = now() sleep(0.001) dt2 = now() self.assertTrue(is_after(dt2, dt1)) + self.assertFalse(is_after(None, dt1)) def test_is_before(self): dt1 = now() sleep(0.001) dt2 = now() self.assertFalse(is_before(dt2, dt1)) + self.assertFalse(is_after(None, dt1)) def test_lower_bin(self): dt = now() @@ -116,20 +122,24 @@ def test_lower_bin(self): def test_millis(self): dt = now() self.assertGreaterEqual(nanos(dt), millis(dt) * 10 ** 6) + self.assertEqual(millis(None), NULL_LONG) def test_millis_of_second(self): dt = now() self.assertGreaterEqual(millis_of_second(dt, TimeZone.AT), 0) + self.assertEqual(millis_of_second(None, TimeZone.AT), NULL_INT) def test_millis_to_nanos(self): dt = now() ms = millis(dt) self.assertEqual(ms * 10 ** 6, millis_to_nanos(ms)) + self.assertEqual(NULL_LONG, millis_to_nanos(NULL_LONG)) def test_minus(self): dt1 = now() dt2 = now() self.assertGreaterEqual(0, minus(dt1, dt2)) + self.assertEqual(NULL_LONG, minus(None, dt2)) def test_minus_nanos(self): dt = now() @@ -149,41 +159,48 @@ def test_minute_of_day(self): timezone_str = "BT" dt = to_datetime(f"{datetime_str} {timezone_str}") self.assertEqual(59, minute_of_day(dt, TimeZone.BT)) + self.assertEqual(NULL_INT, minute_of_day(None, TimeZone.BT)) def test_minute_of_hour(self): datetime_str = "2021-12-10T23:59:59" timezone_str = "CE" dt = to_datetime(f"{datetime_str} {timezone_str}") self.assertEqual(59, minute_of_hour(dt, TimeZone.CE)) + self.assertEqual(NULL_INT, minute_of_hour(None, TimeZone.CE)) def test_month_of_year(self): datetime_str = "2021-08-10T23:59:59" timezone_str = "CH" dt = to_datetime(f"{datetime_str} {timezone_str}") self.assertEqual(8, month_of_year(dt, TimeZone.CH)) + self.assertEqual(NULL_INT, month_of_year(None, TimeZone.CH)) def test_nanos_of_day(self): datetime_str = "2021-12-10T00:00:01" timezone_str = "CT" dt = to_datetime(f"{datetime_str} {timezone_str}") self.assertEqual(10 ** 9, nanos_of_day(dt, TimeZone.CT)) + self.assertEqual(NULL_LONG, nanos_of_day(None, TimeZone.CT)) def test_nanos_of_second(self): datetime_str = "2021-12-10T00:00:01.000000123" timezone_str = "ET" dt = to_datetime(f"{datetime_str} {timezone_str}") self.assertEqual(123, nanos_of_second(dt, TimeZone.ET)) + self.assertEqual(NULL_LONG, nanos_of_second(None, TimeZone.ET)) def test_nanos_to_millis(self): dt = now() ns = nanos(dt) self.assertEqual(ns // 10 ** 6, nanos_to_millis(ns)) + self.assertEqual(NULL_LONG, nanos_to_millis(NULL_LONG)) def test_nanos_to_time(self): dt = now() ns = nanos(dt) dt1 = nanos_to_datetime(ns) self.assertEqual(dt, dt1) + self.assertEqual(None, nanos_to_datetime(NULL_LONG)) def test_plus_period(self): period_str = "T1H" @@ -202,18 +219,21 @@ def test_plus_nanos(self): dt = now() dt1 = plus_nanos(dt, 1) self.assertEqual(1, diff_nanos(dt, dt1)) + self.assertEqual(None, plus_nanos(None, 1)) def test_second_of_day(self): datetime_str = "2021-12-10T00:01:05" timezone_str = "HI" dt = to_datetime(f"{datetime_str} {timezone_str}") self.assertEqual(65, second_of_day(dt, TimeZone.HI)) + self.assertEqual(NULL_INT, second_of_day(None, TimeZone.HI)) def test_second_of_minute(self): datetime_str = "2021-12-10T00:01:05" timezone_str = "HK" dt = to_datetime(f"{datetime_str} {timezone_str}") self.assertEqual(5, second_of_minute(dt, TimeZone.HK)) + self.assertEqual(NULL_INT, second_of_minute(None, TimeZone.HK)) def test_upper_bin(self): dt = now() @@ -224,12 +244,14 @@ def test_year(self): timezone_str = "IN" dt = to_datetime(f"{datetime_str} {timezone_str}") self.assertEqual(2021, year(dt, TimeZone.IN)) + self.assertEqual(NULL_INT, year(None, TimeZone.IN)) - def test_year(self): + def test_year_of_century(self): datetime_str = "2021-12-10T00:01:05" timezone_str = "JP" dt = to_datetime(f"{datetime_str} {timezone_str}") self.assertEqual(21, year_of_century(dt, TimeZone.JP)) + self.assertEqual(NULL_INT, year_of_century(None, TimeZone.JP)) if __name__ == '__main__': From bdff19d1b09aba5dc4302a80afbd016a76e672d9 Mon Sep 17 00:00:00 2001 From: jianfengmao Date: Fri, 28 Jan 2022 14:42:41 -0700 Subject: [PATCH 12/12] Fixed the wrong docs on to/format_nano() Also added a test case to confirm the doc fix --- pyintegration/deephaven2/time.py | 6 +++--- pyintegration/tests/test_time.py | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pyintegration/deephaven2/time.py b/pyintegration/deephaven2/time.py index 715671381db..3a24d70648e 100644 --- a/pyintegration/deephaven2/time.py +++ b/pyintegration/deephaven2/time.py @@ -122,7 +122,7 @@ def to_period(s: str, quiet: bool = False) -> Period: def to_nanos(s, quiet: bool = False) -> int: - """ Converts a time string to nanoseconds from the Epoch. + """ Converts a time string to nanoseconds. Args: s (str): in the format of: hh:mm:ss[.SSSSSSSSS] @@ -203,7 +203,7 @@ def day_of_week(dt: DateTime, tz: TimeZone) -> int: Args: dt (DateTime): the DateTime for which to find the day of the week. - tz (TimeZone): the TimeZone to use when interpreting the Datetime. + tz (TimeZone): the TimeZone to use when interpreting the DateTime. Returns: int: NULL_INT if dt is None @@ -278,7 +278,7 @@ def format_nanos(ns: int) -> str: """ Returns a string DateTime representation formatted as "yyyy-MM-ddThh:mm:ss.SSSSSSSSS". Args: - ns (int): the number of nanoseconds from the Epoch + ns (int): the number of nanoseconds Returns: str diff --git a/pyintegration/tests/test_time.py b/pyintegration/tests/test_time.py index 9caaed77171..2fc114f08e8 100644 --- a/pyintegration/tests/test_time.py +++ b/pyintegration/tests/test_time.py @@ -50,6 +50,11 @@ def test_to_nanos(self): in_nanos = to_nanos(time_str, quiet=True) self.assertEqual(in_nanos, NULL_LONG) + time_str = "1:02:03" + in_nanos = to_nanos(time_str) + time_str2 = format_nanos(in_nanos) + self.assertEqual(time_str2, time_str) + def test_current_time_and_diff(self): dt = now() sleep(1)