diff --git a/src/chronify/time_configs.py b/src/chronify/time_configs.py index 1babd51..f6d8743 100644 --- a/src/chronify/time_configs.py +++ b/src/chronify/time_configs.py @@ -2,7 +2,7 @@ import logging from datetime import datetime, timedelta, tzinfo from typing import Union, Literal, Optional -from pydantic import Field, field_validator +from pydantic import Field, field_validator, field_serializer from typing_extensions import Annotated from chronify.base_models import ChronifyBaseModel @@ -16,6 +16,7 @@ list_representative_time_columns, ) from chronify.exceptions import InvalidValue, InvalidParameter +from chronify.time_utils import get_tzname logger = logging.getLogger(__name__) @@ -121,11 +122,18 @@ def get_time_zones(self) -> list[tzinfo | None]: @classmethod def check_duplicated_time_zones(cls, time_zones: list[tzinfo | None]) -> list[tzinfo | None]: if len(set(time_zones)) < len(time_zones): - msg = ("DatetimeRangeWithTZColumn.time_zones has duplicates: ", time_zones) + msg = f"DatetimeRangeWithTZColumn.time_zones has duplicates: {time_zones}" raise InvalidValue(msg) return time_zones - # Lixi TODO: ensure table schema has time_zone col? + @field_serializer("time_zones") + def serialize_time_zones(self, time_zones: list[tzinfo | None]) -> list[str]: + """Serialize tzinfo objects to their string names for Pydantic serialization.""" + return [get_tzname(tz) for tz in time_zones] + + # Note: Validation that the table schema contains the time_zone_column + # is deferred to runtime when the table is accessed, as schema validation + # occurs separately in the table schema validation logic. DatetimeRanges = Union[ @@ -243,7 +251,10 @@ def get_time_zone_column(self) -> str: return self.time_zone_column def get_time_zones(self) -> list[tzinfo | None]: - return [] # LIXI TODO + # Time zones are determined by values in the time_zone_column for each row. + # The actual time zones are not available at the configuration level and must + # be retrieved from the data itself. Returns an empty list to indicate this. + return [] IndexTimeRanges = Union[ @@ -286,7 +297,10 @@ def get_time_zone_column(self) -> str: return self.time_zone_column def get_time_zones(self) -> list[tzinfo | None]: - return [] # LIXI TODO + # Time zones are determined by values in the time_zone_column for each row. + # The actual time zones are not available at the configuration level and must + # be retrieved from the data itself. Returns an empty list to indicate this. + return [] class ColumnRepresentativeBase(TimeBaseModel): diff --git a/src/chronify/time_series_checker.py b/src/chronify/time_series_checker.py index 8af4517..ce6df20 100644 --- a/src/chronify/time_series_checker.py +++ b/src/chronify/time_series_checker.py @@ -89,7 +89,7 @@ def _check_expected_timestamps_with_external_time_zone(self) -> int: if sorted(expected_dct.keys()) != sorted(actual_dct.keys()): msg = "Time zone records do not match between expected and actual from table " - msg += f"\nexpected: {sorted(expected_dct.keys())} vs. \neactual: {sorted(actual_dct.keys())}" + msg += f"\nexpected: {sorted(expected_dct.keys())} vs. \nactual: {sorted(actual_dct.keys())}" raise InvalidTable(msg) for tz_name in expected_dct.keys(): diff --git a/src/chronify/time_utils.py b/src/chronify/time_utils.py index b07118e..9dc90d5 100644 --- a/src/chronify/time_utils.py +++ b/src/chronify/time_utils.py @@ -180,4 +180,4 @@ def get_tzname(tz: tzinfo | None) -> str: if isinstance(tz, ZoneInfo): return tz.key ts = datetime(year=2020, month=1, day=1, tzinfo=tz) - return tz.tzname(ts) # type: ignore # LIXI TODO + return tz.tzname(ts) # type: ignore # tz is guaranteed to be a tzinfo instance here, but mypy cannot infer this from the isinstance check above diff --git a/src/chronify/time_zone_converter.py b/src/chronify/time_zone_converter.py index b0ece3b..d0203ed 100644 --- a/src/chronify/time_zone_converter.py +++ b/src/chronify/time_zone_converter.py @@ -86,12 +86,12 @@ def convert_time_zone_by_column( sqlalchemy engine metadata sqlalchemy metadata - srd_schema + src_schema Defines the source table in the database. time_zone_column Column name in the source table that contains the time zone information. wrap_time_allowed - If False, the converted timestamps will aligned with the original timestamps in real time scale + If False, the converted timestamps will be aligned with the original timestamps in real time scale E.g. 2018-01-01 00:00 ~ 2018-12-31 23:00 in US/Eastern becomes 2017-12-31 23:00 ~ 2018-12-31 22:00 in US/Central If True, the converted timestamps will fit into the time range of the src_schema in tz-naive clock time @@ -304,8 +304,8 @@ def generate_to_time_config(self) -> DatetimeRangeBase: def generate_to_schema(self) -> TableSchema: id_cols = self._from_schema.time_array_id_columns - if "time_zone" not in id_cols: - id_cols.append("time_zone") + if self.time_zone_column not in id_cols: + id_cols.append(self.time_zone_column) to_schema: TableSchema = self._from_schema.model_copy( update={ "name": f"{self._from_schema.name}_tz_converted",