blob: 7a923093d10d80a3dd3852f48bca367cf84608bd [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.
"""Integer calendar and process-local zone helpers for Java temporal casts."""
import time
_GREGORIAN_CYCLE_SECONDS = 146097 * 86400
# Match the finite zone lookup window used by the Native temporal helpers.
_MIN_ZONE_SECONDS = -377705023201
_MAX_ZONE_SECONDS = 253402207200
def _epoch_day_to_date(days):
# March-based Gregorian eras, with floor division also for pre-epoch days.
days += 719468
era, day_of_era = divmod(days, 146097)
year_of_era = (day_of_era - day_of_era // 1460 + day_of_era // 36524 - day_of_era // 146096) // 365
year = year_of_era + era * 400
day_of_year = day_of_era - (365 * year_of_era + year_of_era // 4 - year_of_era // 100)
march_month = (5 * day_of_year + 2) // 153
day = day_of_year - (153 * march_month + 2) // 5 + 1
month = march_month + (3 if march_month < 10 else -9)
return year + (month <= 2), month, day
def _local_offset(seconds):
# libc can lose DST in very large years. Java's final transition rules
# repeat every Gregorian 400-year cycle, including month/day/weekday/time.
# Query an equivalent date in 2370..2770, after recorded transitions.
if seconds > _MAX_ZONE_SECONDS:
seconds = seconds % _GREGORIAN_CYCLE_SECONDS + _GREGORIAN_CYCLE_SECONDS
elif seconds < _MIN_ZONE_SECONDS:
# Before recorded history Java uses its first wall offset.
seconds = _MIN_ZONE_SECONDS
local = time.localtime(seconds)
offset = getattr(local, 'tm_gmtoff', None)
if offset is not None:
return offset
return -(time.altzone if local.tm_isdst > 0 else time.timezone)
def _signed(value, bits):
return (value + (1 << (bits - 1))) % (1 << bits) - (1 << (bits - 1))
def _div(value, divisor):
# Java integer division truncates towards zero.
return -(abs(value) // divisor) if value < 0 else value // divisor
def _rem(value, divisor):
return value - _div(value, divisor) * divisor
def _java_date_to_epoch_day(date):
j = _signed(date + 2440588 + 32044, 32)
g, dg = _div(j, 146097), _rem(j, 146097)
c = _div((_div(dg, 36524) + 1) * 3, 4)
dc = dg - c * 36524
b, db = _div(dc, 1461), _rem(dc, 1461)
a = _div((_div(db, 365) + 1) * 3, 4)
da = db - a * 365
y = g * 400 + c * 100 + b * 4 + a
m = _div(da * 5 + 308, 153) - 2
d = da - _div((m + 4) * 153, 5) + 122
year, month, day = y - 4800 + _div(m + 2, 12), _rem(m + 2, 12) + 1, d + 1
leap = year % 400 == 0 or year % 4 == 0 and year % 100 != 0
days = 29 if month == 2 and leap else 28 if month == 2 else 30 if month in (4, 6, 9, 11) else 31
if not 1 <= month <= 12 or not 1 <= day <= days:
raise ValueError('Invalid Java DATE conversion: {}-{}-{}'.format(year, month, day))
year -= month <= 2
era, yoe = divmod(year, 400)
march = month + (-3 if month > 2 else 9)
return era * 146097 + yoe * 365 + yoe // 4 - yoe // 100 + (153 * march + 2) // 5 + day - 1 - 719468
def _epoch_day_to_java_date(day):
year, month, day = _epoch_day_to_date(day)
a = (14 - month) // 12
y, m = year + 4800 - a, month + 12 * a - 3
return _signed(day + (153 * m + 2) // 5 + 365 * y + _div(y, 4) - _div(y, 100)
+ _div(y, 400) - 32045 - 2440588, 32)
def _local_to_utc_seconds(seconds):
# Inspect both sides of a possible transition. In an overlap both offsets
# are valid: Java chooses the earlier instant. In a gap neither is valid:
# applying the earlier offset shifts the wall clock forward through the gap.
offsets = {_local_offset(seconds + delta) for delta in (-172800, 0, 172800)}
candidates = [seconds - offset for offset in offsets]
valid = [utc for utc in candidates if utc + _local_offset(utc) == seconds]
return min(valid) if valid else max(candidates)