From 2ab11bdec06ea9ebb3c3af8f6dfce10105714252 Mon Sep 17 00:00:00 2001 From: Petr Ogarok Date: Tue, 27 Oct 2026 09:52:39 -0400 Subject: [PATCH] Remove pytz from csp Signed-off-by: Petr Ogarok --- conda/dev-environment-unix.yml | 2 +- conda/dev-environment-win.yml | 2 +- csp/adapters/csv.py | 2 +- csp/adapters/db.py | 5 ++-- csp/adapters/parquet.py | 4 +-- csp/baselib.py | 7 ++--- csp/curve.py | 5 ++-- csp/impl/pandas_perspective.py | 7 ++--- csp/impl/perspective_common.py | 7 ++--- csp/impl/wiring/runtime.py | 12 ++++---- csp/tests/adapters/test_db.py | 22 +++++++------- csp/tests/adapters/test_parquet.py | 21 +++++++------ csp/tests/adapters/test_parquet_output.py | 9 +++--- csp/tests/adapters/test_websocket.py | 13 ++++---- csp/tests/impl/test_struct.py | 30 +++++++++---------- csp/tests/test_engine.py | 11 +++---- csp/tests/test_profiler.py | 1 - csp/tests/test_type_checking.py | 5 ++-- .../Input-Output-Adapters-API.md | 2 +- pyproject.toml | 2 +- 20 files changed, 77 insertions(+), 92 deletions(-) diff --git a/conda/dev-environment-unix.yml b/conda/dev-environment-unix.yml index 533b856ac..22e9b8c02 100644 --- a/conda/dev-environment-unix.yml +++ b/conda/dev-environment-unix.yml @@ -43,7 +43,7 @@ dependencies: - python-build - python-graphviz - python-rapidjson - - pytz + - python-tzdata - rapidjson - requests - ruamel.yaml diff --git a/conda/dev-environment-win.yml b/conda/dev-environment-win.yml index 533da6a27..6b0988c25 100644 --- a/conda/dev-environment-win.yml +++ b/conda/dev-environment-win.yml @@ -43,7 +43,7 @@ dependencies: - python-build - python-graphviz - python-rapidjson - - pytz + - python-tzdata - rapidjson - requests - ruamel.yaml diff --git a/csp/adapters/csv.py b/csp/adapters/csv.py index 82dc3d347..02b14fe3a 100644 --- a/csp/adapters/csv.py +++ b/csp/adapters/csv.py @@ -11,7 +11,7 @@ def convert(row): v = row[column] dt = datetime.strptime(v, format_string) if tz is not None: - dt = tz.localize(dt) + dt = tz.localize(dt) if hasattr(tz, "localize") else dt.replace(tzinfo=tz) return dt return convert diff --git a/csp/adapters/db.py b/csp/adapters/db.py index 362d99874..40ad1e519 100644 --- a/csp/adapters/db.py +++ b/csp/adapters/db.py @@ -10,7 +10,6 @@ from importlib.metadata import PackageNotFoundError, version as get_package_version -import pytz from packaging import version from csp import PushMode, ts @@ -74,7 +73,7 @@ def __init__(self, time_column, tz=None): :param tz: timezone if the db timestamp doesn't have timezone """ self._time_column = time_column - if isinstance(tz, pytz.BaseTzInfo): + if getattr(tz, "zone", None): tz = zoneinfo.ZoneInfo(tz.zone) self._tz = tz @@ -122,7 +121,7 @@ def __init__(self, date_column, time_column, tz=None): self._date_column = date_column self._time_column = time_column - if isinstance(tz, pytz.BaseTzInfo): + if getattr(tz, "zone", None): tz = zoneinfo.ZoneInfo(tz.zone) self._tz = tz self._db_has_tz = None diff --git a/csp/adapters/parquet.py b/csp/adapters/parquet.py index 2c8851992..21f65ab69 100644 --- a/csp/adapters/parquet.py +++ b/csp/adapters/parquet.py @@ -132,7 +132,7 @@ def __init__( :param symbol_column: An optional parameter that specifies the name of the symbol column if the file if there is any :param time_column: A mandatory specification of the time column name in the parquet files. This column will be used to inject the row values from parquet at the given timestamps. - :param tz: The pytz timezone of the timestamp column, should only be provided if the time_column in parquet file doesn't have tz info. + :param tz: The timezone (e.g. zoneinfo.ZoneInfo) of the timestamp column, should only be provided if the time_column in parquet file doesn't have tz info. :param start_time: An optional start time of the date, any data prior to this time will be ignored :param end_time: An optional end time of the data, any data after this time will be ignored :param split_columns_to_files: A boolean flag that specifies that each column should be written to a separate file. If true, then @@ -166,7 +166,7 @@ def __init__( if time_column: self._properties["time_column"] = time_column if tz: - self._properties["tz"] = tz.zone + self._properties["tz"] = str(tz) if binary_arrow: self._properties["is_arrow_ipc"] = True if start_time: diff --git a/csp/baselib.py b/csp/baselib.py index d6cf5c551..ec3610cb6 100644 --- a/csp/baselib.py +++ b/csp/baselib.py @@ -4,11 +4,10 @@ import math import queue import threading -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import Callable, Dict, List, Optional, TypeVar, Union import numpy as np -import pytz import csp from csp.impl.__cspimpl import _cspimpl @@ -186,7 +185,7 @@ def _print_ts(tag: str, x: ts["T"]): if csp.ticked(x): t = csp.now() if s_log_tz is not None: - t = pytz.UTC.localize(t).astimezone(s_log_tz) + t = t.replace(tzinfo=timezone.utc).astimezone(s_log_tz) builtins.print("%s %s:%s" % (t, tag, x)) @@ -266,7 +265,7 @@ def _log_ts( if csp.ticked(x): t = csp.now() if s_log_tz is not None: - t = pytz.UTC.localize(t).astimezone(s_log_tz) + t = t.replace(tzinfo=timezone.utc).astimezone(s_log_tz) if use_thread: s_queue.put((s_logger, level, tag, t, x)) diff --git a/csp/curve.py b/csp/curve.py index 9237144fe..532ea505b 100644 --- a/csp/curve.py +++ b/csp/curve.py @@ -1,9 +1,8 @@ import copy -from datetime import timedelta +from datetime import timedelta, timezone from typing import Union import numpy as np -import pytz from csp import null_ts from csp.impl.__cspimpl import _cspimpl @@ -27,7 +26,7 @@ def start(self, start_time, end_time): self._data[idx] = (start_time + data[0], data[1]) elif self._data[0][0].tzinfo is not None: for idx, data in enumerate(self._data): - self._data[idx] = (data[0].astimezone(pytz.UTC).replace(tzinfo=None), data[1]) + self._data[idx] = (data[0].astimezone(timezone.utc).replace(tzinfo=None), data[1]) while self._index < len(self._data) and self._data[self._index][0] < start_time: self._index += 1 diff --git a/csp/impl/pandas_perspective.py b/csp/impl/pandas_perspective.py index 0d65e7479..de96484ef 100644 --- a/csp/impl/pandas_perspective.py +++ b/csp/impl/pandas_perspective.py @@ -1,8 +1,7 @@ -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import Optional import pandas as pd -import pytz from pandas.compat import set_function_name import csp @@ -66,7 +65,7 @@ def _apply_updates( row[index_col] = idx if s_has_time_col: if localize: - row[time_col] = pytz.utc.localize(csp.now()) + row[time_col] = csp.now().replace(tzinfo=timezone.utc) else: row[time_col] = csp.now() if _PERSPECTIVE_3: @@ -75,7 +74,7 @@ def _apply_updates( row = new_rows[idx] if localize and col in s_datetime_cols and value.tzinfo is None: - row[col] = pytz.utc.localize(value) + row[col] = value.replace(tzinfo=timezone.utc) else: row[col] = value diff --git a/csp/impl/perspective_common.py b/csp/impl/perspective_common.py index 99e8898a2..400fc9dd8 100644 --- a/csp/impl/perspective_common.py +++ b/csp/impl/perspective_common.py @@ -1,6 +1,5 @@ -from datetime import date, datetime +from datetime import date, datetime, timezone -import pytz from packaging import version try: @@ -43,10 +42,10 @@ def perspective_type_map(): def datetime_to_perspective(dt: datetime) -> int: """Convert a python datetime to an integer number of milliseconds for perspective >= 3""" if dt.tzinfo is None: - dt = pytz.utc.localize(dt) + dt = dt.replace(tzinfo=timezone.utc) return int(dt.timestamp() * 1000) def date_to_perspective(d: date) -> int: """Convert a python date to an integer number of milliseconds for perspective >= 3""" - return int(datetime(year=d.year, month=d.month, day=d.day, tzinfo=pytz.UTC).timestamp() * 1000) + return int(datetime(year=d.year, month=d.month, day=d.day, tzinfo=timezone.utc).timestamp() * 1000) diff --git a/csp/impl/wiring/runtime.py b/csp/impl/wiring/runtime.py index f18cc0f78..157035d54 100644 --- a/csp/impl/wiring/runtime.py +++ b/csp/impl/wiring/runtime.py @@ -1,9 +1,7 @@ import threading import time from collections import deque -from datetime import datetime, timedelta - -import pytz +from datetime import datetime, timedelta, timezone from csp.impl.__cspimpl import _cspimpl from csp.impl.error_handling import ExceptionContext @@ -19,15 +17,15 @@ def _normalize_run_times(starttime, endtime, realtime): if starttime is None: if realtime: - starttime = datetime.now(pytz.UTC).replace(tzinfo=None) + starttime = datetime.now(timezone.utc).replace(tzinfo=None) else: raise RuntimeError("starttime argument is required") if endtime is None: raise RuntimeError("endtime argument is required") if starttime.tzinfo is not None: - starttime = starttime.astimezone(pytz.UTC).replace(tzinfo=None) + starttime = starttime.astimezone(timezone.utc).replace(tzinfo=None) if isinstance(endtime, datetime) and endtime.tzinfo is not None: - endtime = endtime.astimezone(pytz.UTC).replace(tzinfo=None) + endtime = endtime.astimezone(timezone.utc).replace(tzinfo=None) if isinstance(endtime, timedelta): endtime = starttime + endtime return starttime, endtime @@ -202,7 +200,7 @@ def run( mem_cache.clear(clear_user_objects=False) # Ensure we dont start running realtime engines before starttime if its in the future - now = datetime.now(pytz.UTC).replace(tzinfo=None) + now = datetime.now(timezone.utc).replace(tzinfo=None) if starttime > now and realtime: time.sleep((starttime - now).total_seconds()) diff --git a/csp/tests/adapters/test_db.py b/csp/tests/adapters/test_db.py index dd7fcd295..49c7f71d8 100644 --- a/csp/tests/adapters/test_db.py +++ b/csp/tests/adapters/test_db.py @@ -1,8 +1,8 @@ import os import unittest -from datetime import date, datetime, time +from datetime import date, datetime, time, timezone +from zoneinfo import ZoneInfo -import pytz import sqlalchemy as db import csp @@ -63,7 +63,7 @@ def test_sqlite_basic(self): engine = self._prepopulate_in_mem_engine() for time_accessor in ( EngineStartTimeAccessor(), - TimestampAccessor(time_column="TIME", tz=pytz.timezone("US/Eastern")), + TimestampAccessor(time_column="TIME", tz=ZoneInfo("US/Eastern")), ): def graph(): @@ -225,7 +225,7 @@ def test_sqlite_constraints(self): execute_with_commit(engine, query, values_list) def graph(): - time_accessor = DateTimeAccessor(date_column="DATE", time_column="TIME", tz=pytz.timezone("US/Eastern")) + time_accessor = DateTimeAccessor(date_column="DATE", time_column="TIME", tz=ZoneInfo("US/Eastern")) reader = DBReader.create_from_connection( connection=engine, table_name="test", @@ -256,7 +256,7 @@ def graph(): csp.add_graph_output("all", all) # NYC - result = csp.run(graph, starttime=pytz.timezone("US/Eastern").localize(datetime(2020, 3, 3, 9, 30))) + result = csp.run(graph, starttime=datetime(2020, 3, 3, 9, 30, tzinfo=ZoneInfo("US/Eastern"))) self.assertEqual(len(result["aapl"]), 4) self.assertTrue(all(v[1].SYMBOL == "AAPL" for v in result["aapl"])) @@ -338,7 +338,7 @@ def test_join_query(self): metadata.create_all(engine) def graph(): - time_accessor = TimestampAccessor(time_column="TIME", tz=pytz.timezone("US/Eastern")) + time_accessor = TimestampAccessor(time_column="TIME", tz=ZoneInfo("US/Eastern")) query = "select * from test1 inner join test2 on test2.TIME=test1.TIME" reader = DBReader.create_from_connection( connection=engine, query=query, time_accessor=time_accessor, symbol_column="SYMBOL" @@ -366,7 +366,7 @@ def graph(): csp.add_graph_output("all", all) # NYC - starttime = pytz.timezone("US/Eastern").localize(datetime(2020, 3, 3, 9, 30)) + starttime = datetime(2020, 3, 3, 9, 30, tzinfo=ZoneInfo("US/Eastern")) result = csp.run(graph, starttime=starttime) # result = csp.run(graph, starttime=datetime(2020, 3, 3, 9, 30)) @@ -429,7 +429,7 @@ def test_DateTimeAccessor(self): execute_with_commit(engine, query, values_list) def graph(): - time_accessor = DateTimeAccessor(date_column="DATE", time_column="TIME", tz=pytz.timezone("US/Eastern")) + time_accessor = DateTimeAccessor(date_column="DATE", time_column="TIME", tz=ZoneInfo("US/Eastern")) reader = DBReader.create_from_connection( connection=engine, table_name="test", time_accessor=time_accessor, symbol_column="SYMBOL" ) @@ -439,7 +439,7 @@ def graph(): csp.add_graph_output("aapl_price", aapl_price) # NYC - tz = pytz.timezone("US/Eastern") + tz = ZoneInfo("US/Eastern") periods = [ (datetime(2020, 3, 3), datetime(2020, 3, 4)), (datetime(2020, 3, 3), datetime(2020, 3, 5)), @@ -448,9 +448,9 @@ def graph(): (datetime(2020, 3, 4, 13), datetime(2020, 3, 4, 14)), ] for starttime, endtime in periods: - result = csp.run(graph, starttime=tz.localize(starttime), endtime=tz.localize(endtime)) + result = csp.run(graph, starttime=starttime.replace(tzinfo=tz), endtime=endtime.replace(tzinfo=tz)) target = [ - (tz.localize(v[0]).astimezone(pytz.UTC).replace(tzinfo=None), v[1]) + (v[0].replace(tzinfo=tz).astimezone(timezone.utc).replace(tzinfo=None), v[1]) for v in values if v[0] >= starttime and v[0] <= endtime ] diff --git a/csp/tests/adapters/test_parquet.py b/csp/tests/adapters/test_parquet.py index 5460a307d..b5fdb7685 100644 --- a/csp/tests/adapters/test_parquet.py +++ b/csp/tests/adapters/test_parquet.py @@ -2,14 +2,13 @@ import os import tempfile import unittest -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone import numpy import pandas import polars import pyarrow import pyarrow.parquet -import pytz import csp from csp.adapters.output_adapters.parquet import ParquetOutputConfig @@ -55,14 +54,14 @@ def write_ts_to_file(x: csp.ts["T"]): def graph(): if arrow: reader = ParquetReader( - filename, symbol_column="SYMBOL", time_column="TIME", binary_arrow=arrow, tz=pytz.utc + filename, symbol_column="SYMBOL", time_column="TIME", binary_arrow=arrow, tz=timezone.utc ) reader_all = ParquetReader( - filename, symbol_column="SYMBOL", time_column="TIME", binary_arrow=arrow, tz=pytz.utc + filename, symbol_column="SYMBOL", time_column="TIME", binary_arrow=arrow, tz=timezone.utc ) else: - reader = ParquetReader(filename, symbol_column="SYMBOL", time_column="TIME", tz=pytz.utc) - reader_all = ParquetReader(filename, symbol_column="SYMBOL", time_column="TIME", tz=pytz.utc) + reader = ParquetReader(filename, symbol_column="SYMBOL", time_column="TIME", tz=timezone.utc) + reader_all = ParquetReader(filename, symbol_column="SYMBOL", time_column="TIME", tz=timezone.utc) # Struct aapl = reader.subscribe("AAPL", PriceQuantity) @@ -201,7 +200,7 @@ def graph(): writer.publish("x", x) writer.publish("y", y) - start_time = datetime(2020, 3, 3, 9, 30, tzinfo=pytz.utc) + start_time = datetime(2020, 3, 3, 9, 30, tzinfo=timezone.utc) csp.run(graph, starttime=start_time) df = pandas.read_parquet(filename) expected_columns = {} @@ -235,7 +234,7 @@ def graph(): writer.publish("x", x) writer.publish("y", y) - start_time = datetime(2020, 3, 3, 9, 30, tzinfo=pytz.utc) + start_time = datetime(2020, 3, 3, 9, 30, tzinfo=timezone.utc) csp.run(graph, starttime=start_time) df = pyarrow.RecordBatchStreamReader(pyarrow.memory_map(filename)).read_pandas() @@ -791,7 +790,7 @@ def test_string_array_with_nulls(self): df.to_parquet(temp_file.name) def reader_g(): - reader = ParquetReader(temp_file.name, time_column="t", tz=pytz.utc) + reader = ParquetReader(temp_file.name, time_column="t", tz=timezone.utc) return reader.subscribe_all(csp.typing.Numpy1DArray[str], "v") # Null strings in list arrays are read as empty strings (no NaN equivalent for strings) @@ -808,7 +807,7 @@ def test_float_array_with_nulls(self): df.to_parquet(temp_file.name) def reader_g(): - reader = ParquetReader(temp_file.name, time_column="t", tz=pytz.utc) + reader = ParquetReader(temp_file.name, time_column="t", tz=timezone.utc) return reader.subscribe_all(csp.typing.Numpy1DArray[float], "v") res = csp.run(reader_g, starttime=datetime(2022, 1, 1), endtime=timedelta(seconds=10)) @@ -1170,7 +1169,7 @@ def test_tz(file_name: str, start: datetime): ) writer.publish("int_vals", data) - start = datetime(2020, 1, 1, tzinfo=pytz.utc) + start = datetime(2020, 1, 1, tzinfo=timezone.utc) with tempfile.NamedTemporaryFile(suffix=".parquet") as output: output.file.close() diff --git a/csp/tests/adapters/test_parquet_output.py b/csp/tests/adapters/test_parquet_output.py index 0382f4659..93f7198cb 100644 --- a/csp/tests/adapters/test_parquet_output.py +++ b/csp/tests/adapters/test_parquet_output.py @@ -7,20 +7,19 @@ import os import tempfile import unittest -from datetime import date, datetime, time, timedelta +from datetime import date, datetime, time, timedelta, timezone import numpy import pandas import pyarrow import pyarrow.ipc import pyarrow.parquet -import pytz import csp from csp.adapters.output_adapters.parquet import ParquetOutputConfig from csp.adapters.parquet import ParquetReader, ParquetWriter -START = datetime(2022, 1, 1, tzinfo=pytz.utc) +START = datetime(2022, 1, 1, tzinfo=timezone.utc) def _read_ipc(path): @@ -72,7 +71,7 @@ def test_string(self): self.assertEqual(df["value"].tolist(), ["hello", "world", "", "with spaces", "unicode: 日本語"]) def test_datetime(self): - dts = [datetime(2022, 1, i + 1, tzinfo=pytz.utc) for i in range(5)] + dts = [datetime(2022, 1, i + 1, tzinfo=timezone.utc) for i in range(5)] df = self._write_and_read_scalar(datetime, dts) for i, dt in enumerate(dts): self.assertEqual(df["value"].iloc[i].to_pydatetime(), dt) @@ -1568,7 +1567,7 @@ class FullStruct(csp.Struct): f=3.14, b=True, s="hello", - dt=datetime(2022, 6, 15, 12, 0, tzinfo=pytz.utc), + dt=datetime(2022, 6, 15, 12, 0, tzinfo=timezone.utc), td=timedelta(seconds=123), d=date(2022, 6, 15), t=time(12, 30, 45), diff --git a/csp/tests/adapters/test_websocket.py b/csp/tests/adapters/test_websocket.py index c09e25362..0be46bd72 100644 --- a/csp/tests/adapters/test_websocket.py +++ b/csp/tests/adapters/test_websocket.py @@ -1,10 +1,9 @@ import os import threading import unittest -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import List -import pytz import tornado.ioloop import tornado.web import tornado.websocket @@ -54,7 +53,7 @@ def g(): csp.add_graph_output("recv", recv) csp.stop_engine(recv) - msgs = csp.run(g, starttime=datetime.now(pytz.UTC), realtime=True) + msgs = csp.run(g, starttime=datetime.now(timezone.utc), realtime=True) assert msgs["recv"][0][1] == "Hello, World!" def test_send_recv_json(self): @@ -77,7 +76,7 @@ def g(): csp.add_graph_output("recv", recv) csp.stop_engine(recv) - msgs = csp.run(g, starttime=datetime.now(pytz.UTC), realtime=True) + msgs = csp.run(g, starttime=datetime.now(timezone.utc), realtime=True) obj = msgs["recv"][0][1] assert isinstance(obj, MsgStruct) assert obj.a == 1234 @@ -117,7 +116,7 @@ def g(n: int): csp.stop_engine(stop_on_all_or_timeout(recv, n)) n = 100 - msgs = csp.run(g, n, starttime=datetime.now(pytz.UTC), realtime=True) + msgs = csp.run(g, n, starttime=datetime.now(timezone.utc), realtime=True) assert len(msgs["recv"]) == n assert msgs["recv"][0][1] != msgs["recv"][-1][1] @@ -128,7 +127,7 @@ def g(): assert ws._properties["port"] == "443" csp.stop_engine(ws.status()) - csp.run(g, starttime=datetime.now(pytz.UTC), realtime=True) + csp.run(g, starttime=datetime.now(timezone.utc), realtime=True) def test_send_recv_burst_json(self): class MsgStruct(csp.Struct): @@ -155,7 +154,7 @@ def g(): csp.add_graph_output("recv", recv) csp.stop_engine(recv) - msgs = csp.run(g, starttime=datetime.now(pytz.UTC), realtime=True) + msgs = csp.run(g, starttime=datetime.now(timezone.utc), realtime=True) obj = msgs["recv"][0][1] assert isinstance(obj, list) innerObj = obj[0] diff --git a/csp/tests/impl/test_struct.py b/csp/tests/impl/test_struct.py index ddededf27..38015a6ff 100644 --- a/csp/tests/impl/test_struct.py +++ b/csp/tests/impl/test_struct.py @@ -3,11 +3,11 @@ import pickle import sys import unittest -from datetime import date, datetime, time, timedelta +from datetime import date, datetime, time, timedelta, timezone from typing import Any, Dict, List, Literal, Optional, Set, Tuple, Union +from zoneinfo import ZoneInfo import numpy as np -import pytz from pydantic import TypeAdapter, ValidationError from pydantic_core import PydanticSerializationError from typing_extensions import Annotated @@ -1773,18 +1773,18 @@ class MyStruct(csp.Struct): self.assertEqual(json.loads(test_struct.to_json()), result_dict) dt = datetime(2024, 3, 8) - dt_utc = datetime(2024, 3, 8, tzinfo=pytz.utc) + dt_utc = datetime(2024, 3, 8, tzinfo=timezone.utc) test_struct = MyStruct(i=456, dt=dt) result_dict = json.loads(test_struct.to_json()) self.assertEqual(datetime.fromisoformat(result_dict["dt"]), dt_utc) - dt = datetime.now(tz=pytz.utc) + dt = datetime.now(tz=timezone.utc) test_struct = MyStruct(i=456, dt=dt) result_dict = json.loads(test_struct.to_json()) self.assertEqual(datetime.fromisoformat(result_dict["dt"]), dt) - dt = datetime.now(tz=pytz.timezone("America/New_York")) - dt_utc = dt.astimezone(pytz.utc) + dt = datetime.now(tz=ZoneInfo("America/New_York")) + dt_utc = dt.astimezone(timezone.utc) test_struct = MyStruct(i=456, dt=dt) result_dict = json.loads(test_struct.to_json()) self.assertEqual(datetime.fromisoformat(result_dict["dt"]), dt_utc) @@ -1838,7 +1838,7 @@ class MyStruct(csp.Struct): result_dict = {"i": 456, "l_b": l_b} self.assertEqual(json.loads(test_struct.to_json()), result_dict) - dt = datetime.now(tz=pytz.timezone("Europe/London")) + dt = datetime.now(tz=ZoneInfo("Europe/London")) l_dt = [dt, dt] test_struct = MyStruct(i=456, l_dt=l_dt) result_dict = json.loads(test_struct.to_json()) @@ -1859,7 +1859,7 @@ class MyStruct(csp.Struct): result_dict = {"i": 456, "l_any": l_i} self.assertEqual(json.loads(test_struct.to_json()), result_dict) - dt = datetime.now(tz=pytz.timezone("Europe/London")) + dt = datetime.now(tz=ZoneInfo("Europe/London")) l_dt = [dt, dt] test_struct = MyStruct(i=456, l_any=l_dt) result_dict = json.loads(test_struct.to_json()) @@ -1911,7 +1911,7 @@ class MyStruct(csp.Struct): result_dict = {"i": 456, "d_f": d_f_res} self.assertEqual(json.loads(test_struct.to_json()), result_dict) - dt = datetime.now(tz=pytz.utc) + dt = datetime.now(tz=timezone.utc) d_dt = {"d1": dt, "d2": dt} test_struct = MyStruct(i=456, d_dt=d_dt) result_dict = json.loads(test_struct.to_json()) @@ -1934,7 +1934,7 @@ class MyStruct(csp.Struct): result_dict = {"i": 456, "d_any": d_f_res} self.assertEqual(json.loads(test_struct.to_json()), result_dict) - dt = datetime.now(tz=pytz.utc) + dt = datetime.now(tz=timezone.utc) d_dt = {"d1": dt, "d2": dt} test_struct = MyStruct(i=456, d_any=d_dt) result_dict = json.loads(test_struct.to_json()) @@ -1990,19 +1990,19 @@ class MyStruct(csp.Struct): self.assertEqual(json.loads(test_struct.to_json()), result_dict) # Datetime as key - dt = datetime.now(tz=pytz.utc) + dt = datetime.now(tz=timezone.utc) d_datetime = {dt: "datetime"} test_struct = MyStruct(i=456, d_any=d_datetime) result_dict = json.loads(test_struct.to_json()) self.assertEqual({datetime.fromisoformat(k): v for k, v in result_dict["d_any"].items()}, d_datetime) - dt = datetime.now(tz=pytz.utc) + dt = datetime.now(tz=timezone.utc) d_datetime = {dt.date(): "date"} test_struct = MyStruct(i=456, d_any=d_datetime) result_dict = json.loads(test_struct.to_json()) self.assertEqual({date.fromisoformat(k): v for k, v in result_dict["d_any"].items()}, d_datetime) - dt = datetime.now(tz=pytz.utc) + dt = datetime.now(tz=timezone.utc) d_datetime = {dt.time(): "time"} test_struct = MyStruct(i=456, d_any=d_datetime) result_dict = json.loads(test_struct.to_json()) @@ -2166,7 +2166,7 @@ def custom_jsonifier(obj): mss1 = MySubStruct(d_s_msss={"msss1": msss1}, l_ncsp=[ncsp1, ncsp2], py_l_ncsp=[ncsp2, ncsp3]) mss2 = MySubStruct(d_s_msss={"msss2": msss2}, l_ncsp=[ncsp2], py_l_ncsp=[ncsp3]) - dt = datetime.now(tz=pytz.utc) + dt = datetime.now(tz=timezone.utc) ms = MyStruct( i=123456789, s="NewMyStruct", @@ -3944,7 +3944,7 @@ class SimpleStruct(csp.Struct): f=2.718, s="custom", b=False, - dt=datetime(2024, 1, 1, tzinfo=pytz.UTC), + dt=datetime(2024, 1, 1, tzinfo=timezone.utc), l=[4, 5, 6], d={"x": 9.9, "y": 8.8}, e=MyEnum.OPTION2, diff --git a/csp/tests/test_engine.py b/csp/tests/test_engine.py index 94c71661a..fa9304a90 100644 --- a/csp/tests/test_engine.py +++ b/csp/tests/test_engine.py @@ -9,13 +9,12 @@ import traceback import typing import unittest -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import Callable, Dict, List import numpy as np import psutil import pytest -from pytz import UTC import csp from csp import PushMode, ts @@ -438,7 +437,7 @@ def g() -> ts[bool]: def test_csp_in_realtime__realtime(self): def utc_now() -> datetime: - return datetime.now(UTC) + return datetime.now(timezone.utc) @csp.node def is_in_realtime(x: ts[bool]) -> ts[bool]: @@ -462,7 +461,7 @@ def g() -> ts[bool]: @pytest.mark.skipif(not os.environ.get("CSP_ENGINE_FLAKY_TESTS"), reason="potentially flaky test") def test_csp_in_realtime(self): def utc_now() -> datetime: - return datetime.now(UTC) + return datetime.now(timezone.utc) @csp.node def is_in_realtime(x: ts[bool]) -> ts[bool]: @@ -1544,9 +1543,7 @@ def test_realtime_endtime(self): ) def test_start_realtime_in_future(self): - import pytz - - t = datetime.now(pytz.UTC) + timedelta(seconds=1) + t = datetime.now(timezone.utc) + timedelta(seconds=1) res = csp.run(csp.const(123), starttime=t, endtime=t, realtime=True)[0][0] self.assertEqual(res[1], 123) diff --git a/csp/tests/test_profiler.py b/csp/tests/test_profiler.py index f0a886d0e..e8fd7a457 100644 --- a/csp/tests/test_profiler.py +++ b/csp/tests/test_profiler.py @@ -10,7 +10,6 @@ import numpy as np import pandas as pd -import pytz import csp import csp.stats as stats diff --git a/csp/tests/test_type_checking.py b/csp/tests/test_type_checking.py index 456ad7899..a81c49eef 100644 --- a/csp/tests/test_type_checking.py +++ b/csp/tests/test_type_checking.py @@ -7,6 +7,7 @@ import warnings from datetime import datetime, time, timedelta from typing import Callable, Dict, List, Optional, Union +from zoneinfo import ZoneInfo import numpy as np @@ -675,9 +676,7 @@ def main(): csp.run(main, starttime=utc_now(), endtime=timedelta()) def test_time_tzinfo(self): - import pytz - - timetz = time(1, 2, 3, tzinfo=pytz.timezone("EST")) + timetz = time(1, 2, 3, tzinfo=ZoneInfo("EST")) with self.assertRaisesRegex(TypeError, "csp time type does not support timezones"): # Now that Time is a native type it no longer supports ticking with tzinfo csp.run(csp.const, timetz, starttime=utc_now(), endtime=timedelta()) diff --git a/docs/wiki/api-references/Input-Output-Adapters-API.md b/docs/wiki/api-references/Input-Output-Adapters-API.md index 38e64ada7..a6bc793b1 100644 --- a/docs/wiki/api-references/Input-Output-Adapters-API.md +++ b/docs/wiki/api-references/Input-Output-Adapters-API.md @@ -191,7 +191,7 @@ ParquetReader( :param symbol_column: An optional parameter that specifies the name of the symbol column if the file if there is any :param time_column: A mandatory specification of the time column name in the parquet files. This column will be used to inject the row values from parquet at the given timestamps. - :param tz: The pytz timezone of the timestamp column, should only be provided if the time_column in parquet file doesn't have tz info. + :param tz: The timezone (e.g. zoneinfo.ZoneInfo) of the timestamp column, should only be provided if the time_column in parquet file doesn't have tz info. """ ``` diff --git a/pyproject.toml b/pyproject.toml index 44a82465a..4a1c70892 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,10 +30,10 @@ dependencies = [ "psutil", "pyarrow>=15,<24", "pydantic>=2", - "pytz", "ruamel.yaml", "sqlalchemy", "typing-extensions", + "tzdata", ] classifiers = [