HyukjinKwon commented on code in PR #59326:
URL: https://github.com/apache/spark/pull/59326#discussion_r4238550089


##########
python/pyspark/sql/pandas/conversion.py:
##########
@@ -58,14 +58,34 @@
     from pyspark.sql.pandas._typing import DataFrameLike as PandasDataFrameLike
 
 
+def _create_array_from_pandas(
+    series: "pd.Series",
+    arrow_type: Optional["pa.DataType"],
+    *,
+    mask: Optional["pd.Series"],
+    safecheck: bool,
+) -> Union["pa.Array", "pa.ChunkedArray"]:
+    import pandas as pd
+    import pyarrow as pa
+
+    if isinstance(series.array, pd.arrays.ArrowExtensionArray):
+        # pyarrow-backed arrays can ignore the requested type or force a safe 
cast.
+        # Extract first, then cast explicitly. Other dtypes already honor the 
type.
+        result = pa.Array.from_pandas(series, safe=safecheck)
+        if arrow_type is not None and result.type != arrow_type:
+            result = result.cast(arrow_type, safe=safecheck)

Review Comment:
   **Non-blocking (P2):** Take a tz-naive `pd.ArrowDtype` timestamp series and 
a `TimestampType` field. This line casts `timestamp[ns]` to `timestamp('us', 
tz=<session tz>)` by changing only the metadata, so Arrow reads the wall-clock 
values as UTC. NumPy `datetime64` series get localized to the session timezone 
first, in `_check_series_convert_timestamps_internal`. That function only 
checks `is_datetime64_dtype` and `DatetimeTZDtype`, and both are False for 
`ArrowDtype`.
   
   Example with session timezone `America/Los_Angeles`: 
`pd.Series(pd.to_datetime(['2020-01-01 10:00']), 
dtype=pd.ArrowDtype(pa.timestamp('ns')))` becomes 1577872800000000 (10:00 UTC). 
The same NumPy series becomes 1577901600000000 (18:00 UTC). I reproduced this 
with the helper's calls on pyarrow 25.0.1 / pandas 3.0.6.
   
   PyArrow >= 19 already gave the same result through its protocol cast before 
this PR. On PyArrow 18, the supported minimum, the requested type used to be 
ignored and the `timestamp[ns]` column failed downstream. Classic 
`createDataFrame` and pandas UDF/UDTF results now succeed with the shifted 
value instead. None of the new tests uses a timestamp target. tz-aware Arrow 
timestamps and `TimestampNTZType` targets are not affected.
   
   **Recommended change:** In the shared pandas timestamp converter, localize 
tz-naive Arrow-backed timestamp series to the session (or local) timezone and 
normalize them to UTC, using the same DST-ambiguity choice (standard time) and 
nonexistent-time handling as the NumPy path. Leave tz-aware Arrow-backed 
timestamps unchanged. Add PySpark conversion tests for Arrow-backed 
TimestampType targets, and extend the PySpark migration note to cover this 
behavior.
   
   **Why this works:** Once the converter emits tz-aware UTC instants for 
Arrow-backed naive input, the cast in `_create_array_from_pandas` from that 
tz-aware source to `timestamp('us', tz=<session>)` only changes the timezone 
label and preserves the instant. Arrow-backed and NumPy-backed inputs then 
produce the same values on every PyArrow version and entry point.
   
   **Scope:** Localize tz-naive Arrow-backed timestamp columns in the shared 
pandas timestamp converter, so the shared Arrow constructor's cast preserves 
instants for every pandas backing and PyArrow version.
   
   **Compatibility:** NumPy-backed datetime64 and DatetimeTZDtype localization, 
including the standard-time DST choice, stays unchanged.
   
   **Risks:** On PyArrow >= 19 the merge target already returned shifted values 
for this input, so the fix changes observable results there too. The migration 
note must say so. pandas' ArrowDtype `.dt.tz_localize` does not accept 
`ambiguous=False`, so the implementation must reproduce the standard-time 
choice for ambiguous DST times another way.
   
   **Constraints:** Do not change NumPy-backed `datetime64` or 
`DatetimeTZDtype` handling. Keep the timestamp resolution, and do not overflow 
dates outside the nanosecond range for microsecond-unit Arrow inputs.
   
   **Success:** With a non-UTC session timezone, a tz-naive `pd.ArrowDtype` 
timestamp series converted for TimestampType through 
`create_arrow_array_from_pandas` and through 
`PandasToArrowConversion.from_pandas` (both error-handling modes) yields the 
same UTC instants as the equivalent NumPy `datetime64` series, on every 
supported PyArrow version. tz-aware Arrow-backed timestamps keep their 
instants, TimestampNTZType targets keep their wall-clock values, and nulls are 
preserved.
   
   **Verification:**
   - **Regression:** A tz-naive Arrow-backed timestamp series converted for 
TimestampType under a non-UTC session timezone produces the same epoch values 
as the equivalent NumPy-backed series, through both the createDataFrame helper 
and the pandas UDF output conversion.
   - **Behavior:** tz-aware Arrow-backed timestamps keep their instants, and 
TimestampNTZType targets keep their wall-clock values, including nulls.
   - **Compatibility:** The existing NumPy-backed timestamp conversion and 
SPARK-60009 requested-type, safety, offset-width, nullable-dtype, and 
legacy-path conversion checks still pass.



##########
python/pyspark/sql/conversion.py:
##########
@@ -543,7 +544,9 @@ def _convert_column_legacy(
             # trigger the cast fallback when arrow_cast is enabled.
             try:
                 try:
-                    return pa.Array.from_pandas(series, mask=mask, 
type=arrow_type, safe=safecheck)
+                    return _create_array_from_pandas(

Review Comment:
   Thanks, confirmed. 
`test_from_pandas_arrow_dtype_requested_type_legacy_error_handling` runs the 
`use_legacy_error_handling=True, arrow_cast=True` branch with a 
`pd.ArrowDtype(pa.int32())` series and asserts the `int64` result type. So if 
that call site went back to the raw `pa.Array.from_pandas(..., type=...)` call, 
the test would fail on the PyArrow 18 job.
   
   <!-- SPARK_DEV_REVIEW_REPLY 
{"feedback_id":"inline:4238252067","thread_id":"inline:4238252067","verdict_sha256":"b5a8c6db037983c56343f6fda30b5b312ccbc91ff9dc23933c51b7143516b533"}
 -->



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to