blob: fe5a519d61039d02501c6289f01f92f6ade1f18e [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.
"""Java local-zone conversions used by existing schema-evolution casts."""
import pyarrow as pa
from pypaimon.utils.temporal_utils import (
_epoch_day_to_java_date, _java_date_to_epoch_day,
_local_offset, _local_to_utc_seconds, _signed,
)
from pypaimon.table.row.generic_row import _parse_type_precision_scale
def cast_temporal(array, target, source_type=None, target_type=None):
timestamp = pa.types.is_timestamp(array.type)
source_name = str(source_type).upper() if source_type is not None else ''
ltz = timestamp and (array.type.tz is not None if source_type is None else
source_name.startswith('TIMESTAMP_LTZ') or 'WITH LOCAL TIME ZONE' in source_name)
target_ts = pa.types.is_timestamp(target)
cross = timestamp and target_ts and ltz != (target.tz is not None)
date = pa.types.is_date32(array.type) and target_ts and target.tz is not None
numeric = array.type == pa.int64() and target_ts and target.tz is not None
if not (cross or date or numeric or timestamp and (pa.types.is_date32(target) or pa.types.is_time(target))):
return None
raw = array.cast(pa.int32() if date else pa.int64()).to_pylist()
unit = array.type.unit if timestamp else 's'
source_precision = (_parse_type_precision_scale(source_type)[0] if source_type is not None else
{'s': 0, 'ms': 3, 'us': 6, 'ns': 9}[unit] if timestamp else 0)
target_precision = (_parse_type_precision_scale(target_type)[0] if target_type is not None else
{'s': 0, 'ms': 3, 'us': 6, 'ns': 9}[target.unit] if target_ts else 0)
values = []
for value in raw:
if value is None:
values.append(None)
continue
if date:
millis, sub_milli = _java_date_to_epoch_day(value) * 86400000, 0
elif unit == 's':
millis, sub_milli = _signed(value * 1000, 64), 0
elif unit == 'ms':
millis, sub_milli = value, 0
elif unit == 'us':
millis, sub_milli = value // 1000, value % 1000 * 1000
else:
millis, sub_milli = value // 1000000, value % 1000000
original = millis
if cross and not ltz:
millis = _signed(millis // 86400000, 32) * 86400000 + millis % 86400000
if date or cross and not ltz:
millis = _local_to_utc_seconds(millis // 1000) * 1000 + millis % 1000
elif ltz or numeric:
millis = _signed(millis + _local_offset(millis // 1000) * 1000, 64)
if pa.types.is_date32(target):
value = (_epoch_day_to_java_date((original + _local_offset(original // 1000) * 1000) // 86400000)
if ltz else _signed(millis // 86400000, 32))
elif pa.types.is_time(target):
value = ((original + _local_offset(original // 1000) * 1000) % 86400000
if ltz else millis % 86400000)
else:
if source_precision > target_precision:
if target_precision <= 3:
quantum = 10 ** (3 - target_precision)
millis, sub_milli = _signed(millis // quantum * quantum, 64), 0
else:
quantum = 10 ** (9 - target_precision)
sub_milli = sub_milli // quantum * quantum
if target.unit == 's':
value = millis // 1000
elif target.unit == 'ms':
value = millis
elif target.unit == 'us':
value = millis * 1000 + sub_milli // 1000
else:
value = millis * 1000000 + sub_milli
if not -(1 << 63) <= value < (1 << 63):
value = None
values.append(value)
if pa.types.is_time(target):
return pa.array(values, type=pa.int32()).cast(pa.time32('ms')).cast(target, safe=False)
return pa.array(values, type=pa.int32() if pa.types.is_date32(target) else pa.int64()).cast(target)