diff --git a/python/cudf/cudf/core/column/datetime.py b/python/cudf/cudf/core/column/datetime.py index 936beb289ec..84d283d3f22 100644 --- a/python/cudf/cudf/core/column/datetime.py +++ b/python/cudf/cudf/core/column/datetime.py @@ -242,14 +242,22 @@ def normalize_binop_value(self, other: DatetimeLikeScalar) -> ScalarLike: if isinstance(other, (cudf.Scalar, ColumnBase, cudf.DateOffset)): return other - if isinstance(other, datetime.datetime): - other = np.datetime64(other) - elif isinstance(other, datetime.timedelta): - other = np.timedelta64(other) - elif isinstance(other, pd.Timestamp): + tz_error_msg = ( + "Cannot perform binary operation on timezone-naive columns" + " and timezone-aware timestamps." + ) + if isinstance(other, pd.Timestamp): + if other.tz is not None: + raise NotImplementedError(tz_error_msg) other = other.to_datetime64() elif isinstance(other, pd.Timedelta): other = other.to_timedelta64() + elif isinstance(other, datetime.datetime): + if other.tzinfo is not None: + raise NotImplementedError(tz_error_msg) + other = np.datetime64(other) + elif isinstance(other, datetime.timedelta): + other = np.timedelta64(other) if isinstance(other, np.datetime64): if np.isnat(other): diff --git a/python/cudf/cudf/core/column/timedelta.py b/python/cudf/cudf/core/column/timedelta.py index f2c4c0fe481..272f6e20985 100644 --- a/python/cudf/cudf/core/column/timedelta.py +++ b/python/cudf/cudf/core/column/timedelta.py @@ -213,12 +213,22 @@ def _binaryop(self, other: ColumnBinaryOperand, op: str) -> ColumnBase: def normalize_binop_value(self, other) -> ColumnBinaryOperand: if isinstance(other, (ColumnBase, cudf.Scalar)): return other - if isinstance(other, datetime.timedelta): - other = np.timedelta64(other) - elif isinstance(other, pd.Timestamp): + + tz_error_msg = ( + "Cannot perform binary operation on timezone-naive columns" + " and timezone-aware timestamps." + ) + if isinstance(other, pd.Timestamp): + if other.tz is not None: + raise NotImplementedError(tz_error_msg) other = other.to_datetime64() elif isinstance(other, pd.Timedelta): other = other.to_timedelta64() + elif isinstance(other, datetime.timedelta): + other = np.timedelta64(other) + elif isinstance(other, datetime.datetime) and other.tzinfo is not None: + raise NotImplementedError(tz_error_msg) + if isinstance(other, np.timedelta64): other_time_unit = cudf.utils.dtypes.get_time_unit(other) if np.isnat(other): diff --git a/python/cudf/cudf/tests/test_datetime.py b/python/cudf/cudf/tests/test_datetime.py index b0ef79b44e9..417df53c9c9 100644 --- a/python/cudf/cudf/tests/test_datetime.py +++ b/python/cudf/cudf/tests/test_datetime.py @@ -2093,3 +2093,15 @@ def test_construction_from_tz_timestamps(data): _ = cudf.Series(data) with pytest.raises(NotImplementedError): _ = cudf.Index(data) + + +@pytest.mark.parametrize("op", _cmpops) +def test_datetime_binop_tz_timestamp(op): + s = cudf.Series([1, 2, 3], dtype="datetime64[ns]") + pd_tz_timestamp = pd.Timestamp("1970-01-01 00:00:00.000000001", tz="utc") + with pytest.raises(NotImplementedError): + op(s, pd_tz_timestamp) + + date_scalar = datetime.datetime.now(datetime.timezone.utc) + with pytest.raises(NotImplementedError): + op(s, date_scalar) diff --git a/python/cudf/cudf/tests/test_scalar.py b/python/cudf/cudf/tests/test_scalar.py index 5e1e58f9e68..c1aeb987eff 100644 --- a/python/cudf/cudf/tests/test_scalar.py +++ b/python/cudf/cudf/tests/test_scalar.py @@ -1,4 +1,4 @@ -# Copyright (c) 2021-2022, NVIDIA CORPORATION. +# Copyright (c) 2021-2023, NVIDIA CORPORATION. import datetime import re @@ -450,3 +450,13 @@ def test_scalar_numpy_casting(): s1 = cudf.Scalar(1, dtype=np.int32) s2 = np.int64(2) assert s1 < s2 + + +def test_construct_timezone_scalar_error(): + pd_scalar = pd.Timestamp("1970-01-01 00:00:00.000000001", tz="utc") + with pytest.raises(NotImplementedError): + cudf.utils.dtypes.to_cudf_compatible_scalar(pd_scalar) + + date_scalar = datetime.datetime.now(datetime.timezone.utc) + with pytest.raises(NotImplementedError): + cudf.utils.dtypes.to_cudf_compatible_scalar(date_scalar) diff --git a/python/cudf/cudf/tests/test_timedelta.py b/python/cudf/cudf/tests/test_timedelta.py index 4b1e8cf1027..ab45374c119 100644 --- a/python/cudf/cudf/tests/test_timedelta.py +++ b/python/cudf/cudf/tests/test_timedelta.py @@ -1426,3 +1426,14 @@ def test_timedelta_constructor(data, dtype): actual = cudf.TimedeltaIndex(data=cudf.Series(data), dtype=dtype) assert_eq(expected, actual) + + +@pytest.mark.parametrize("op", [operator.add, operator.sub]) +def test_timdelta_binop_tz_timestamp(op): + s = cudf.Series([1, 2, 3], dtype="timedelta64[ns]") + pd_tz_timestamp = pd.Timestamp("1970-01-01 00:00:00.000000001", tz="utc") + with pytest.raises(NotImplementedError): + op(s, pd_tz_timestamp) + date_tz_scalar = datetime.datetime.now(datetime.timezone.utc) + with pytest.raises(NotImplementedError): + op(s, date_tz_scalar) diff --git a/python/cudf/cudf/utils/dtypes.py b/python/cudf/cudf/utils/dtypes.py index b8dc33345b1..d5e9e5854df 100644 --- a/python/cudf/cudf/utils/dtypes.py +++ b/python/cudf/cudf/utils/dtypes.py @@ -270,14 +270,21 @@ def to_cudf_compatible_scalar(val, dtype=None): # the string value directly (cudf.DeviceScalar will DTRT) return val - if isinstance(val, datetime.datetime): - val = np.datetime64(val) - elif isinstance(val, datetime.timedelta): - val = np.timedelta64(val) - elif isinstance(val, pd.Timestamp): + tz_error_msg = ( + "Cannot covert a timezone-aware timestamp to timezone-naive scalar." + ) + if isinstance(val, pd.Timestamp): + if val.tz is not None: + raise NotImplementedError(tz_error_msg) val = val.to_datetime64() elif isinstance(val, pd.Timedelta): val = val.to_timedelta64() + elif isinstance(val, datetime.datetime): + if val.tzinfo is not None: + raise NotImplementedError(tz_error_msg) + val = np.datetime64(val) + elif isinstance(val, datetime.timedelta): + val = np.timedelta64(val) val = _maybe_convert_to_default_type( cudf.api.types.pandas_dtype(type(val))