Yaoxuan Wu created SPARK-60081:
----------------------------------

             Summary: Python UDFs get the wrong value for TIMESTAMPs in the 
repeated DST hour
                 Key: SPARK-60081
                 URL: https://issues.apache.org/jira/browse/SPARK-60081
             Project: Spark
          Issue Type: Bug
          Components: PySpark
    Affects Versions: 4.1.1, 4.2.0, 5.0.0
            Reporter: Yaoxuan Wu


When clocks go back for DST, one local hour happens twice. For a TIMESTAMP in 
the second occurrence of that hour, a Python UDF gets a value that is one hour 
too early. Even an identity UDF {{lambda x: x}} returns a different value.

 
{code:java}
import os, time
os.environ["TZ"] = "America/Los_Angeles"
time.tzset()
from pyspark.sql import SparkSession, functions as F
from pyspark.sql.types import LongType, TimestampType
spark = SparkSession.builder.config("spark.sql.session.timeZone", 
"America/Los_Angeles").getOrCreate()

# 2021-11-07 09:30 UTC = 01:30 PST, the second 01:30 of the night the clocks go 
back.
# Its epoch second is 1636277400.
df = spark.sql("SELECT timestamp'2021-11-07 09:30:00Z' AS t")# 1) The value a 
Python UDF receives

udf_input = F.udf(lambda x: int(x.timestamp()), LongType())
df.select(udf_input("t")).first()[0]
# expected: 1636277400
# Spark 4.2.0: 1636273800 (one hour early)# 2) An identity UDF should return 
its input unchanged

identity = F.udf(lambda x: x, TimestampType())
df.select(F.unix_timestamp(identity("t"))).first()[0]
# expected: 1636277400
# Spark 4.2.0: 1636273800 (one hour early)
{code}
 

The results above use 4.2.0's default, an Arrow-optimized UDF. Both paths are 
wrong on 4.1.1, 4.2.0 and master:
|| ||What the UDF receives||What an identity UDF returns||
|Arrow UDF ({{{}useArrow=True{}}}, default in 4.2)|1 hour early|1 hour early|
|Pickled UDF ({{{}useArrow=False{}}}, default in 4.1)|correct|1 hour early|

 

The first 01:30 (PDT, {{{}08:30Z{}}}) and all other times are correct.

*Cause*

Python marks the second occurrence with {{{}datetime.fold = 1{}}}, and PySpark 
drops {{fold}} in two places:
 # Arrow input conversion ({{{}pyspark/sql/conversion.py{}}}): 
{{value.astimezone().replace(tzinfo=None)}} always gives {{{}fold=0{}}}. A fix: 
{{{}datetime.datetime.fromtimestamp(value.timestamp()){}}}.
 # {{TimestampType.toInternal}} ({{{}pyspark/sql/types.py{}}}, used for UDF 
results on the pickled path and by {{{}createDataFrame{}}}): 
{{time.mktime(dt.timetuple())}} ignores {{{}fold{}}}. A fix: 
{{{}dt.timestamp(){}}}.

Since pyspark-4.2 makes Arrow UDFs the default, every Python UDF now reads 
these timestamps wrongly by default, while the pickled path reads them 
correctly.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

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

Reply via email to