diff --git a/bigframes/core/tools/datetimes.py b/bigframes/core/tools/datetimes.py index 2abb86a2f3..26afdc7910 100644 --- a/bigframes/core/tools/datetimes.py +++ b/bigframes/core/tools/datetimes.py @@ -52,7 +52,7 @@ def to_datetime( f"to datetime is not implemented. {constants.FEEDBACK_LINK}" ) - arg = bigframes.series.Series(arg)._cached() + arg = bigframes.series.Series(arg) if format and unit and arg.dtype in (bigframes.dtypes.INT_DTYPE, bigframes.dtypes.FLOAT_DTYPE): # type: ignore raise ValueError("cannot specify both format and unit") @@ -74,6 +74,11 @@ def to_datetime( ) assert unit is None + + # The following operations evaluate individual values to infer a format, + # so cache if needed. + arg = arg._cached(force=False) + as_datetime = arg._apply_unary_op( # type: ignore ops.ToDatetimeOp( format=format, diff --git a/tests/unit/core/tools/__init__.py b/tests/unit/core/tools/__init__.py new file mode 100644 index 0000000000..0a2669d7a2 --- /dev/null +++ b/tests/unit/core/tools/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/tests/unit/core/tools/test_datetimes.py b/tests/unit/core/tools/test_datetimes.py new file mode 100644 index 0000000000..96a6b14ef8 --- /dev/null +++ b/tests/unit/core/tools/test_datetimes.py @@ -0,0 +1,43 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import cast +from unittest import mock + +import bigframes.core.tools.datetimes +import bigframes.dtypes +import bigframes.pandas +import bigframes.testing.mocks + + +def test_to_datetime_with_series_and_format_doesnt_cache(monkeypatch): + df = bigframes.testing.mocks.create_dataframe(monkeypatch) + series = mock.Mock(spec=bigframes.pandas.Series, wraps=df["col"]) + dt_series = cast( + bigframes.pandas.Series, + bigframes.core.tools.datetimes.to_datetime(series, format="%Y%m%d"), + ) + series._cached.assert_not_called() + assert dt_series.dtype == bigframes.dtypes.DATETIME_DTYPE + + +def test_to_datetime_with_series_and_format_utc_doesnt_cache(monkeypatch): + df = bigframes.testing.mocks.create_dataframe(monkeypatch) + series = mock.Mock(spec=bigframes.pandas.Series, wraps=df["col"]) + dt_series = cast( + bigframes.pandas.Series, + bigframes.core.tools.datetimes.to_datetime(series, format="%Y%m%d", utc=True), + ) + series._cached.assert_not_called() + assert dt_series.dtype == bigframes.dtypes.TIMESTAMP_DTYPE