Yaoxuan Wu created FLINK-41307:
----------------------------------

             Summary: PyFlink TableResult.collect() changes TIMESTAMP values in 
a DST gap or before 1902 (converted through the JVM and Python local time zones)
                 Key: FLINK-41307
                 URL: https://issues.apache.org/jira/browse/FLINK-41307
             Project: Flink
          Issue Type: Bug
          Components: API / Python
    Affects Versions: 2.2.1, 2.3.0, 2.0.1
            Reporter: Yaoxuan Wu


{code:python}
from pyflink.table import EnvironmentSettings, TableEnvironment
t_env = TableEnvironment.create(EnvironmentSettings.in_batch_mode())
vals = ['2020-03-08 02:30:00', '1900-01-01 00:00:00', '1800-01-01 00:00:00', 
'2020-07-01 12:00:00']
q = " UNION ALL ".join(f"SELECT {i} AS i, TIMESTAMP '{v}' AS ts" for i, v in 
enumerate(vals))
with t_env.execute_sql(q).collect() as it:
    for r in sorted(it, key=lambda r: r[0]):
        print(vals[r[0]], '->', r[1])
# expected: each value unchanged
# actual (TZ=America/Los_Angeles):
#   2020-03-08 02:30:00 -> 2020-03-08 03:30:00
#   1900-01-01 00:00:00 -> 1899-12-31 23:53:00
#   1800-01-01 00:00:00 -> 1800-01-01 00:00:02
#   2020-07-01 12:00:00 -> 2020-07-01 12:00:00
{code}
{{TableResult.collect()}} changes TIMESTAMP (without time zone) values that are 
not a plain instant in the client's default time zone: a time in a DST gap 
moves by an hour, and times before 1901-12-13 move by the difference between 
two LMT offsets. {{TableResult.print()}} shows the original values; with 
{{TZ=UTC}} nothing changes.

Cause: the value goes through two different local-zone conversions.
# {{PythonBridgeUtils.getPickledBytesFromJavaObject}}, TimestampType: 
{{pickler.dumps(Timestamp.valueOf((LocalDateTime) obj))}}. 
{{Timestamp.valueOf}} interprets the wall clock in the JVM default zone (02:30 
in the gap becomes 03:30 PDT).
# The shaded Pyrolite {{Pickler}} turns the {{java.util.Date}} into a 
{{GregorianCalendar}} and pickles {{pytz.timezone(<zone 
id>).localize(datetime(<calendar fields>))}}. pytz only has the 32-bit 
transitions (the first one for America/Los_Angeles is 1901-12-13 20:45:52 UTC), 
so earlier times are localized as LMT, which pytz rounds to whole minutes 
(-7:53).
# {{pickled_bytes_to_python_converter}} (pyflink/table/utils.py) does 
{{field_type.from_sql_type(int(data.timestamp() * 10**6))}}, and 
{{TimestampType.from_sql_type}} is {{datetime.fromtimestamp(...)}}, i.e. back 
to a naive time in the Python local zone, which uses the full tzdata (PST -8:00 
in 1900, LMT -7:52:58 in 1800).

A TIMESTAMP has no zone, so the LocalDateTime fields could be passed without 
going through an instant.

Reproduces on 2.0.1, 2.2.1, 2.3.0 and master.




--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to