diff --git a/pyintegration/deephaven2/column.py b/pyintegration/deephaven2/column.py index e7299ba43a7..52211b495fb 100644 --- a/pyintegration/deephaven2/column.py +++ b/pyintegration/deephaven2/column.py @@ -3,13 +3,13 @@ # from dataclasses import dataclass, field from enum import Enum -from typing import Sequence +from typing import Sequence, Any import jpy -from deephaven2 import DHError +from deephaven2.dtypes import DType import deephaven2.dtypes as dtypes -from deephaven2.dtypes import DType +from deephaven2 import DHError _JColumnHeader = jpy.get_type("io.deephaven.qst.column.header.ColumnHeader") _JColumn = jpy.get_type("io.deephaven.qst.column.Column") diff --git a/pyintegration/deephaven2/constants.py b/pyintegration/deephaven2/constants.py index e83e14f1852..e17972fd4a2 100644 --- a/pyintegration/deephaven2/constants.py +++ b/pyintegration/deephaven2/constants.py @@ -5,6 +5,8 @@ from enum import Enum, auto import jpy +_JQueryConstants = jpy.get_type("io.deephaven.util.QueryConstants") + class SortDirection(Enum): """An enum defining the sorting orders.""" @@ -15,8 +17,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 +92,4 @@ class SortDirection(Enum): MIN_POS_DOUBLE = _JQueryConstants.MIN_POS_DOUBLE """ Minimum positive value of type double. """ + diff --git a/pyintegration/deephaven2/dtypes.py b/pyintegration/deephaven2/dtypes.py index b8c12f98f6e..e61e5d00819 100644 --- a/pyintegration/deephaven2/dtypes.py +++ b/pyintegration/deephaven2/dtypes.py @@ -7,14 +7,11 @@ """ from __future__ import annotations -from typing import Iterable, Any, Tuple, Sequence, Callable +from typing import Any, Sequence, Callable, Iterable -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") @@ -26,7 +23,8 @@ def _qst_custom_type(cls_name: str): class DType: """ A class representing a data type in Deephaven.""" - _j_name_map = {} + + _j_name_type_map = {} @classmethod def from_jtype(cls, j_class: Any) -> DType: @@ -34,7 +32,7 @@ def from_jtype(cls, j_class: Any) -> DType: return None j_name = j_class.getName() - dtype = DType._j_name_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: @@ -46,13 +44,16 @@ 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 + DType._j_name_type_map[j_name] = self 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 +112,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,14 +130,12 @@ 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") +DateTime = DType(j_name="io.deephaven.time.DateTime") +Period = DType(j_name="io.deephaven.time.Period") -# endregion - def j_array_list(values: Iterable): j_list = jpy.get_type("java.util.ArrayList")(len(values)) try: diff --git a/pyintegration/deephaven2/html.py b/pyintegration/deephaven2/html.py new file mode 100644 index 00000000000..87af75f5ad0 --- /dev/null +++ b/pyintegration/deephaven2/html.py @@ -0,0 +1,26 @@ +# +# 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 +from deephaven2.table import Table + +_JTableTools = jpy.get_type("io.deephaven.engine.util.TableTools") + + +def to_html(table: Table) -> str: + """ Returns a table formatted as an HTML string. Limit use to small tables to avoid running out of memory. + + Returns: + a HTML string + + 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 e2164243d1b..3924422e37b 100644 --- a/pyintegration/deephaven2/table.py +++ b/pyintegration/deephaven2/table.py @@ -98,16 +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. - - :param source: (io.deephaven.db.tables.Table) - a Deephaven table object - :return: (java.lang.String) a String of the table printout formatted as HTML - """ - - return _JTableTools.html(self.j_table) - def coalesce(self) -> Table: """ Returns a coalesced child table. """ return Table(j_table=self.j_table.coalesce()) 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/deephaven2/time.py b/pyintegration/deephaven2/time.py new file mode 100644 index 00000000000..3a24d70648e --- /dev/null +++ b/pyintegration/deephaven2/time.py @@ -0,0 +1,830 @@ +# +# 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.dtypes import DateTime, Period + +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") +_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 + + Raises: + DHError + """ + if quiet: + return _JDateTimeUtils.convertDateTimeQuiet(s) + + try: + return _JDateTimeUtils.convertDateTime(s) + except Exception as e: + raise DHError(e) from e + + +def to_period(s: str, quiet: bool = False) -> Period: + """ Converts a period string into a Period object. + + 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): when True, if the period string can't be parsed, this function returns None, otherwise + it raises an exception. The default is False + + Returns: + a Period + + Raises: + DHError + """ + if quiet: + return _JDateTimeUtils.convertPeriodQuiet(s) + + try: + return _JDateTimeUtils.convertPeriod(s) + except Exception as e: + raise DHError(e) from e + + +def to_nanos(s, quiet: bool = False) -> int: + """ Converts a time string to nanoseconds. + + Args: + 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: + int + + Raises: + DHError + """ + if quiet: + return _JDateTimeUtils.convertTimeQuiet(s) + + try: + return _JDateTimeUtils.convertTime(s) + except Exception as e: + raise DHError(e) from e + + +def now() -> DateTime: + """ Provides the current datetime. + + Returns: + DateTime + + Raises: + DHError + """ + try: + return _JDateTimeUtils.currentTime() + except Exception as e: + raise DHError(e) from e + + +def datetime_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: + + 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 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 DateTime + + Returns: + int: NULL_INT if dt is None + + Raises: + DHError + """ + try: + 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 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 DateTime. + + Returns: + int: NULL_INT if dt is None + + Raises: + DHError + """ + try: + 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 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 DateTime + + Returns: + int: NULL_INT if dt is None + + Raises: + DHError + """ + try: + return _JDateTimeUtils.dayOfYear(dt, 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: NULL_LONG if either dt1 or dt2 is None + + Raises: + DHError + """ + try: + return _JDateTimeUtils.diffNanos(dt1, dt2) + except Exception as e: + raise DHError(e) from e + + +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 DateTime + + Returns: + str + + Raises: + DHError + """ + try: + return _JDateTimeUtils.format(dt, tz.value) + except Exception as e: + raise DHError(e) from e + + +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 + + Returns: + str + + Raises: + DHError + """ + try: + return _JDateTimeUtils.format(ns) + except Exception as e: + raise DHError(e) from e + + +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 DateTime + + Returns: + str + + Raises: + DHError + """ + try: + return _JDateTimeUtils.formatDate(dt, tz.value) + except Exception as e: + raise DHError(e) from e + + +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 DateTime + + Returns: + int: NULL_INT if dt is None + + Raises: + DHError + """ + try: + return _JDateTimeUtils.hourOfDay(dt, 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: + 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 before a second DateTime value. + + Args: + dt1 (DateTime): the 1st DateTime + dt2 (DateTime): the 2nd DateTime + + Returns: + bool + + Raises: + DHError + """ + try: + 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: + """ 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 DateTime 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: + return _JDateTimeUtils.lowerBin(dt, 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: NULL_LONG if dt is None + + Raises: + DHError + """ + try: + return _JDateTimeUtils.millis(dt) + except Exception as e: + raise DHError(e) from e + + +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 DateTime + + Returns: + int: NULL_INT if dt is None + + 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 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 DateTime + + Returns: + int: NULL_INT if dt is None + + Raises: + DHError + """ + try: + return _JDateTimeUtils.millisOfSecond(dt, 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: NULL_LONG if ms is NULL_LONG + + Raises: + DHError + """ + try: + return _JDateTimeUtils.millisToNanos(ms) + except Exception as e: + raise DHError(e) from e + + +def millis_to_datetime(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, returns the difference in nanos. + + Args: + dt1 (DateTime): the 1st DateTime + dt2 (DateTiem): the 2nd DateTime + + Returns: + int: NULL_LONG if either dt1 or dt2 is None + + Raises: + DHError + """ + try: + return _JDateTimeUtils.minus(dt1, dt2) + 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 number of nanoseconds to subtract from dateTime + + Returns: + DateTime + + Raises: + DHError + """ + try: + return _JDateTimeUtils.minus(dt, ns) + except Exception as e: + raise DHError(e) from e + + +def minus_period(dt: DateTime, 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: + return _JDateTimeUtils.minus(dt, period) + except Exception as e: + raise DHError(e) from e + + +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 DateTime + + Returns: + int: NULL_INT if dt is None + + Raises: + DHError + """ + try: + return _JDateTimeUtils.minuteOfDay(dt, tz.value) + except Exception as e: + raise DHError(e) from e + + +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 DateTime + + Returns: + int: NULL_INT if dt is None + + Raises: + DHError + """ + try: + + 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 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 DateTime + + Returns: + int: NULL_INT if dt is None + + Raises: + DHError + """ + try: + return _JDateTimeUtils.monthOfYear(dt, 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: NULL_LONG if dt is None + + Raises: + DHError + """ + try: + + return _JDateTimeUtils.nanos(dt) + except Exception as e: + raise DHError(e) from e + + +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 DateTime + + Returns: + int: NULL_LONG if dt is None + + Raises: + DHError + """ + try: + return _JDateTimeUtils.nanosOfDay(dt, tz.value) + except Exception as e: + raise DHError(e) from e + + +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 DateTime + + Returns: + int: NULL_LONG if dt is None + + Raises: + DHError + """ + try: + + return _JDateTimeUtils.nanosOfSecond(dt, 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: NULL_LONG if ns is NULL_LONG + + Raises: + DHError + """ + try: + return _JDateTimeUtils.nanosToMillis(ns) + except Exception as e: + raise DHError(e) from e + + +def nanos_to_datetime(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 _JDateTimeUtils.nanosToTime(ns) + except Exception as e: + raise DHError(e) from e + + +def plus_period(dt: DateTime, period: Period) -> DateTime: + """ Adds a period to a DateTime. + + Args: + dt (DateTime): the starting DateTime value + period (Period): the Period to add to the DateTime + + Returns: + DateTime: None if either dt or period is None + + Raises: + DHError + """ + try: + return _JDateTimeUtils.plus(dt, 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 number of nanoseconds to add to DateTime + + Returns: + DateTime: None if dt is None or ns is NULL_LONG + + Raises: + DHError + """ + try: + return _JDateTimeUtils.plus(dt, ns) + except Exception as e: + raise DHError(e) from e + + +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 DateTime + + Returns: + int: NULL_INT if dt is None + + Raises: + DHError + """ + try: + return _JDateTimeUtils.secondOfDay(dt, tz.value) + except Exception as e: + raise DHError(e) from e + + +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 DateTime + + Returns: + int: NULL_INT if dt is None + + Raises: + DHError + """ + try: + return _JDateTimeUtils.secondOfMinute(dt, tz.value) + except Exception as e: + raise DHError(e) from e + + +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 DateTime 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: + return _JDateTimeUtils.upperBin(dt, 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 DateTime + + Returns: + int: NULL_INT if dt is None + + Raises: + DHError + """ + try: + return _JDateTimeUtils.year(dt, tz.value) + except Exception as e: + raise DHError(e) from e + + +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 DateTime + + Returns: + int + + Raises: + DHError + """ + try: + 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 502d11d5b84..3bc7809379f 100644 --- a/pyintegration/tests/test_dtypes.py +++ b/pyintegration/tests/test_dtypes.py @@ -6,13 +6,14 @@ 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 deephaven2.time import now from tests.testbase import BaseTestCase @@ -171,11 +172,11 @@ 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 = 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) + self.assertTrue(all(x == y for x, y in zip(j_array, values))) if __name__ == '__main__': diff --git a/pyintegration/tests/test_table.py b/pyintegration/tests/test_table.py index bfa8b243be8..4e908ad96ed 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)) # @@ -39,7 +38,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..9ea97557217 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, \ @@ -31,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) diff --git a/pyintegration/tests/test_time.py b/pyintegration/tests/test_time.py new file mode 100644 index 00000000000..2fc114f08e8 --- /dev/null +++ b/pyintegration/tests/test_time.py @@ -0,0 +1,263 @@ +# +# Copyright (c) 2016-2021 Deephaven Data Labs and Patent Pending +# + +import unittest +from time import sleep + +from deephaven2.constants import NULL_LONG, NULL_INT +from deephaven2.time import * + + +class DateTimeUtilsTestCase(unittest.TestCase): + def test_to_datetime(self): + datetime_str = "2021-12-10T23:59:59" + timezone_str = "NY" + 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 = to_datetime(f"{datetime_str} {timezone_str}") + self.assertIn("RuntimeException", str(cm.exception)) + + def test_to_period(self): + period_str = "1W" + period = to_period(period_str) + self.assertEqual(str(period).upper(), period_str) + + period_str = "T1M" + period = to_period(period_str) + self.assertEqual(str(period).upper(), period_str) + + with self.assertRaises(DHError) as cm: + period_str = "T1Y" + period = to_period(period_str) + self.assertIn("RuntimeException", str(cm.exception)) + + def test_to_nanos(self): + time_str = "530000:59:39.123456789" + 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 = to_nanos(time_str) + self.assertIn("RuntimeException", str(cm.exception)) + + time_str = "00:59:39.X" + 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) + dt1 = now() + self.assertGreaterEqual(diff_nanos(dt, dt1), 100000000) + + def test_datetime_at_midnight(self): + dt = now() + 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): + 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) + 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() + self.assertIn(TimeZone.SYD.name, format_datetime(dt, TimeZone.SYD)) + + def test_format_nanos(self): + 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_date(self): + dt = now() + self.assertEqual(3, len(format_date(dt, TimeZone.MOS).split("-"))) + + 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() + self.assertGreaterEqual(diff_nanos(lower_bin(dt, 1000000, MINUTE), dt), 0) + + 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() + dt1 = minus_nanos(dt, 1) + self.assertEqual(1, diff_nanos(dt1, dt)) + + def test_minus_period(self): + period_str = "T1H" + period = to_period(period_str) + + 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 = 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" + period = to_period(period_str) + + 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 = 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() + 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 = 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_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__': + unittest.main()