1619 lines
45 KiB
Python
1619 lines
45 KiB
Python
"""
|
|
Java time standard library APIs implemented in Python.
|
|
java.time package - LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Instant, Duration, Period, etc.
|
|
"""
|
|
|
|
from .._core import Stub
|
|
from .lang import RuntimeException, Comparable
|
|
import datetime as _dt
|
|
import re as _re
|
|
import math as _py_math
|
|
|
|
IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {})
|
|
|
|
__all__ = [
|
|
"LocalDate", "LocalTime", "LocalDateTime",
|
|
"ZonedDateTime", "OffsetDateTime", "OffsetTime",
|
|
"Instant", "Duration", "Period",
|
|
"ZoneId", "ZoneOffset", "ZoneRules",
|
|
"DayOfWeek", "Month", "Year", "YearMonth", "MonthDay",
|
|
"Clock",
|
|
"DateTimeFormatter", "DateTimeFormatterBuilder",
|
|
"TemporalAdjusters",
|
|
"Temporal", "TemporalAccessor", "TemporalUnit",
|
|
"ChronoUnit", "ChronoField",
|
|
]
|
|
|
|
|
|
# ============================================================
|
|
# TemporalAccessor / Temporal / TemporalUnit (base interfaces)
|
|
# ============================================================
|
|
|
|
class TemporalAccessor(Stub):
|
|
def get(self, field):
|
|
raise NotImplementedError
|
|
|
|
def getLong(self, field):
|
|
raise NotImplementedError
|
|
|
|
def isSupported(self, field):
|
|
raise NotImplementedError
|
|
|
|
def range(self, field):
|
|
raise NotImplementedError
|
|
|
|
|
|
class Temporal(TemporalAccessor):
|
|
def plus(self, amount, unit=None):
|
|
raise NotImplementedError
|
|
|
|
def minus(self, amount, unit=None):
|
|
raise NotImplementedError
|
|
|
|
def with_(self, field, new_value):
|
|
raise NotImplementedError
|
|
|
|
def until(self, end_exclusive, unit):
|
|
raise NotImplementedError
|
|
|
|
|
|
class TemporalUnit(Stub):
|
|
def getDuration(self):
|
|
raise NotImplementedError
|
|
|
|
def isDurationEstimated(self):
|
|
raise NotImplementedError
|
|
|
|
def isDateBased(self):
|
|
raise NotImplementedError
|
|
|
|
def isTimeBased(self):
|
|
raise NotImplementedError
|
|
|
|
def between(self, temporal1, temporal2):
|
|
raise NotImplementedError
|
|
|
|
|
|
# ============================================================
|
|
# ChronoField
|
|
# ============================================================
|
|
|
|
class ChronoField(Stub):
|
|
NANO_OF_SECOND = "NanoOfSecond"
|
|
NANO_OF_DAY = "NanoOfDay"
|
|
MICRO_OF_SECOND = "MicroOfSecond"
|
|
MICRO_OF_DAY = "MicroOfDay"
|
|
MILLI_OF_SECOND = "MilliOfSecond"
|
|
MILLI_OF_DAY = "MilliOfDay"
|
|
SECOND_OF_MINUTE = "SecondOfMinute"
|
|
SECOND_OF_DAY = "SecondOfDay"
|
|
MINUTE_OF_HOUR = "MinuteOfHour"
|
|
MINUTE_OF_DAY = "MinuteOfDay"
|
|
HOUR_OF_AMPM = "HourOfAmPm"
|
|
CLOCK_HOUR_OF_AMPM = "ClockHourOfAmPm"
|
|
HOUR_OF_DAY = "HourOfDay"
|
|
CLOCK_HOUR_OF_DAY = "ClockHourOfDay"
|
|
AMPM_OF_DAY = "AmPmOfDay"
|
|
DAY_OF_WEEK = "DayOfWeek"
|
|
ALIGNED_DAY_OF_WEEK_IN_MONTH = "AlignedDayOfWeekInMonth"
|
|
ALIGNED_DAY_OF_WEEK_IN_YEAR = "AlignedDayOfWeekInYear"
|
|
DAY_OF_MONTH = "DayOfMonth"
|
|
DAY_OF_YEAR = "DayOfYear"
|
|
EPOCH_DAY = "EpochDay"
|
|
ALIGNED_WEEK_OF_MONTH = "AlignedWeekOfMonth"
|
|
ALIGNED_WEEK_OF_YEAR = "AlignedWeekOfYear"
|
|
MONTH_OF_YEAR = "MonthOfYear"
|
|
PROLEPTIC_MONTH = "ProlepticMonth"
|
|
YEAR_OF_ERA = "YearOfEra"
|
|
YEAR = "Year"
|
|
ERA = "Era"
|
|
INSTANT_SECONDS = "InstantSeconds"
|
|
OFFSET_SECONDS = "OffsetSeconds"
|
|
|
|
def __init__(self, name):
|
|
super().__init__()
|
|
self._name = name
|
|
|
|
def toString(self):
|
|
return self._name
|
|
|
|
def getDisplayName(self, locale=None):
|
|
return self._name
|
|
|
|
|
|
# ============================================================
|
|
# ChronoUnit
|
|
# ============================================================
|
|
|
|
class ChronoUnit(Stub):
|
|
NANOS = None
|
|
MICROS = None
|
|
MILLIS = None
|
|
SECONDS = None
|
|
MINUTES = None
|
|
HOURS = None
|
|
HALF_DAYS = None
|
|
DAYS = None
|
|
WEEKS = None
|
|
MONTHS = None
|
|
YEARS = None
|
|
DECADES = None
|
|
CENTURIES = None
|
|
MILLENNIA = None
|
|
ERAS = None
|
|
FOREVER = None
|
|
|
|
def __init__(self, name, duration, estimated=False):
|
|
self._name = name
|
|
self._duration = duration
|
|
self._estimated = estimated
|
|
|
|
def toString(self):
|
|
return self._name
|
|
|
|
def getDuration(self):
|
|
return self._duration
|
|
|
|
def isDurationEstimated(self):
|
|
return self._estimated
|
|
|
|
def isDateBased(self):
|
|
return self in (ChronoUnit.WEEKS, ChronoUnit.MONTHS, ChronoUnit.YEARS,
|
|
ChronoUnit.DECADES, ChronoUnit.CENTURIES,
|
|
ChronoUnit.MILLENNIA, ChronoUnit.ERAS)
|
|
|
|
def isTimeBased(self):
|
|
return self in (ChronoUnit.NANOS, ChronoUnit.MICROS, ChronoUnit.MILLIS,
|
|
ChronoUnit.SECONDS, ChronoUnit.MINUTES, ChronoUnit.HOURS,
|
|
ChronoUnit.HALF_DAYS)
|
|
|
|
def between(self, temporal1, temporal2):
|
|
return temporal1.until(temporal2, self)
|
|
|
|
@staticmethod
|
|
def from_(temporal):
|
|
if isinstance(temporal, ChronoUnit):
|
|
return temporal
|
|
raise Exception(f"Cannot convert to ChronoUnit: {temporal}")
|
|
|
|
|
|
# ChronoUnit 常量在 Duration 定义之后初始化(见下方)
|
|
|
|
|
|
# ============================================================
|
|
# DayOfWeek
|
|
# ============================================================
|
|
|
|
class DayOfWeek(Stub):
|
|
MONDAY = None
|
|
TUESDAY = None
|
|
WEDNESDAY = None
|
|
THURSDAY = None
|
|
FRIDAY = None
|
|
SATURDAY = None
|
|
SUNDAY = None
|
|
|
|
def __init__(self, value, name):
|
|
self._value = value
|
|
self._name = name
|
|
|
|
def getValue(self):
|
|
return self._value
|
|
|
|
def getDisplayName(self, style, locale=None):
|
|
return self._name
|
|
|
|
def plus(self, days):
|
|
idx = ((self._value - 1 + days) % 7) + 1
|
|
return DayOfWeek._from_value(idx)
|
|
|
|
def minus(self, days):
|
|
return self.plus(-days)
|
|
|
|
@staticmethod
|
|
def _from_value(v):
|
|
mapping = {1: DayOfWeek.MONDAY, 2: DayOfWeek.TUESDAY, 3: DayOfWeek.WEDNESDAY,
|
|
4: DayOfWeek.THURSDAY, 5: DayOfWeek.FRIDAY, 6: DayOfWeek.SATURDAY,
|
|
7: DayOfWeek.SUNDAY}
|
|
return mapping.get(v)
|
|
|
|
def toString(self):
|
|
return self._name
|
|
|
|
def compareTo(self, other):
|
|
if isinstance(other, DayOfWeek):
|
|
return self._value - other._value
|
|
return -1
|
|
|
|
|
|
DayOfWeek.MONDAY = DayOfWeek(1, "MONDAY")
|
|
DayOfWeek.TUESDAY = DayOfWeek(2, "TUESDAY")
|
|
DayOfWeek.WEDNESDAY = DayOfWeek(3, "WEDNESDAY")
|
|
DayOfWeek.THURSDAY = DayOfWeek(4, "THURSDAY")
|
|
DayOfWeek.FRIDAY = DayOfWeek(5, "FRIDAY")
|
|
DayOfWeek.SATURDAY = DayOfWeek(6, "SATURDAY")
|
|
DayOfWeek.SUNDAY = DayOfWeek(7, "SUNDAY")
|
|
|
|
|
|
# ============================================================
|
|
# Month
|
|
# ============================================================
|
|
|
|
class Month(Stub):
|
|
JANUARY = None
|
|
FEBRUARY = None
|
|
MARCH = None
|
|
APRIL = None
|
|
MAY = None
|
|
JUNE = None
|
|
JULY = None
|
|
AUGUST = None
|
|
SEPTEMBER = None
|
|
OCTOBER = None
|
|
NOVEMBER = None
|
|
DECEMBER = None
|
|
|
|
def __init__(self, value, name):
|
|
self._value = value
|
|
self._name = name
|
|
|
|
def getValue(self):
|
|
return self._value
|
|
|
|
def getDisplayName(self, style, locale=None):
|
|
return self._name
|
|
|
|
def plus(self, months):
|
|
idx = ((self._value - 1 + months) % 12) + 1
|
|
return Month._from_value(idx)
|
|
|
|
def minus(self, months):
|
|
return self.plus(-months)
|
|
|
|
def length(self, leap_year=False):
|
|
if self._value == 2:
|
|
return 29 if leap_year else 28
|
|
if self._value in (4, 6, 9, 11):
|
|
return 30
|
|
return 31
|
|
|
|
def minLength(self):
|
|
return 29 if self._value == 2 else (30 if self._value in (4, 6, 9, 11) else 31)
|
|
|
|
def maxLength(self):
|
|
return 29 if self._value == 2 else (30 if self._value in (4, 6, 9, 11) else 31)
|
|
|
|
def firstDayOfYear(self, leap_year=False):
|
|
days = 0
|
|
for m in range(1, self._value):
|
|
days += Month._from_value(m).length(leap_year)
|
|
return days + 1
|
|
|
|
def firstMonthOfQuarter(self):
|
|
q = (self._value - 1) // 3
|
|
return Month._from_value(q * 3 + 1)
|
|
|
|
@staticmethod
|
|
def _from_value(v):
|
|
values = [None, Month.JANUARY, Month.FEBRUARY, Month.MARCH, Month.APRIL,
|
|
Month.MAY, Month.JUNE, Month.JULY, Month.AUGUST,
|
|
Month.SEPTEMBER, Month.OCTOBER, Month.NOVEMBER, Month.DECEMBER]
|
|
return values[v] if 0 < v <= 12 else None
|
|
|
|
def toString(self):
|
|
return self._name
|
|
|
|
def compareTo(self, other):
|
|
if isinstance(other, Month):
|
|
return self._value - other._value
|
|
return -1
|
|
|
|
|
|
Month.JANUARY = Month(1, "JANUARY")
|
|
Month.FEBRUARY = Month(2, "FEBRUARY")
|
|
Month.MARCH = Month(3, "MARCH")
|
|
Month.APRIL = Month(4, "APRIL")
|
|
Month.MAY = Month(5, "MAY")
|
|
Month.JUNE = Month(6, "JUNE")
|
|
Month.JULY = Month(7, "JULY")
|
|
Month.AUGUST = Month(8, "AUGUST")
|
|
Month.SEPTEMBER = Month(9, "SEPTEMBER")
|
|
Month.OCTOBER = Month(10, "OCTOBER")
|
|
Month.NOVEMBER = Month(11, "NOVEMBER")
|
|
Month.DECEMBER = Month(12, "DECEMBER")
|
|
|
|
|
|
# ============================================================
|
|
# ZoneId / ZoneOffset / ZoneRules
|
|
# ============================================================
|
|
|
|
class ZoneId(Stub):
|
|
SHORT_IDS = {
|
|
"ACT": "Australia/Darwin",
|
|
"AET": "Australia/Sydney",
|
|
"AGT": "America/Argentina/Buenos_Aires",
|
|
"ART": "Africa/Cairo",
|
|
"AST": "America/Anchorage",
|
|
"BET": "America/Sao_Paulo",
|
|
"BST": "Asia/Dhaka",
|
|
"CAT": "Africa/Harare",
|
|
"CNT": "America/St_Johns",
|
|
"CST": "America/Chicago",
|
|
"CTT": "Asia/Shanghai",
|
|
"EAT": "Africa/Addis_Ababa",
|
|
"ECT": "Europe/Paris",
|
|
"EST": "America/New_York",
|
|
"HST": "Pacific/Honolulu",
|
|
"IET": "America/Indianapolis",
|
|
"IST": "Asia/Kolkata",
|
|
"JST": "Asia/Tokyo",
|
|
"MIT": "Pacific/Apia",
|
|
"MST": "America/Denver",
|
|
"NET": "Asia/Yerevan",
|
|
"NST": "Pacific/Auckland",
|
|
"PLT": "Asia/Karachi",
|
|
"PNT": "America/Phoenix",
|
|
"PRT": "America/Puerto_Rico",
|
|
"PST": "America/Los_Angeles",
|
|
"SST": "Pacific/Guadalcanal",
|
|
"VST": "Asia/Ho_Chi_Minh",
|
|
}
|
|
|
|
_cache = {}
|
|
|
|
def __init__(self, zone_id):
|
|
super().__init__()
|
|
self._id = zone_id
|
|
|
|
@staticmethod
|
|
def systemDefault():
|
|
import time as _t
|
|
idx = 1 if _t.daylight else 0
|
|
tz_name = _t.tzname[idx] if idx < len(_t.tzname) and _t.tzname[idx] else _t.tzname[0]
|
|
return ZoneId.of(tz_name) if tz_name else ZoneId("UTC")
|
|
|
|
@staticmethod
|
|
def of(zone_id):
|
|
if zone_id in ZoneId.SHORT_IDS:
|
|
zone_id = ZoneId.SHORT_IDS[zone_id]
|
|
if zone_id not in ZoneId._cache:
|
|
ZoneId._cache[zone_id] = ZoneId(zone_id)
|
|
return ZoneId._cache[zone_id]
|
|
|
|
@staticmethod
|
|
def getAvailableZoneIds():
|
|
import time as _t
|
|
return set(_t.tzname)
|
|
|
|
def getDisplayName(self, style, locale=None):
|
|
return self._id
|
|
|
|
def getRules(self):
|
|
return ZoneRules()
|
|
|
|
def toString(self):
|
|
return self._id
|
|
|
|
def equals(self, other):
|
|
if isinstance(other, ZoneId):
|
|
return self._id == other._id
|
|
return False
|
|
|
|
def hashCode(self):
|
|
return hash(self._id)
|
|
|
|
|
|
class ZoneOffset(ZoneId):
|
|
MAX = None
|
|
MIN = None
|
|
UTC = None
|
|
|
|
_cache = {}
|
|
|
|
def __init__(self, total_seconds):
|
|
hours = total_seconds // 3600
|
|
mins = (abs(total_seconds) % 3600) // 60
|
|
sign = "+" if total_seconds >= 0 else "-"
|
|
if mins == 0:
|
|
zone_id = f"{sign}{abs(hours):02d}:00"
|
|
else:
|
|
zone_id = f"{sign}{abs(hours):02d}:{mins:02d}"
|
|
super().__init__(zone_id)
|
|
self._total_seconds = total_seconds
|
|
|
|
@staticmethod
|
|
def of(offset_id):
|
|
if offset_id in ZoneOffset._cache:
|
|
return ZoneOffset._cache[offset_id]
|
|
if offset_id == "Z" or offset_id == "UTC" or offset_id == "GMT":
|
|
z = ZoneOffset(0)
|
|
ZoneOffset._cache[offset_id] = z
|
|
return z
|
|
sign = 1 if offset_id[0] == "+" else -1
|
|
parts = offset_id[1:].split(":")
|
|
h = int(parts[0])
|
|
m = int(parts[1]) if len(parts) > 1 else 0
|
|
total = sign * (h * 3600 + m * 60)
|
|
z = ZoneOffset(total)
|
|
ZoneOffset._cache[offset_id] = z
|
|
return z
|
|
|
|
@staticmethod
|
|
def ofHours(hours):
|
|
return ZoneOffset(hours * 3600)
|
|
|
|
@staticmethod
|
|
def ofHoursMinutes(hours, minutes):
|
|
sign = 1 if hours >= 0 else -1
|
|
return ZoneOffset(sign * (abs(hours) * 3600 + minutes * 60))
|
|
|
|
@staticmethod
|
|
def ofTotalSeconds(total_seconds):
|
|
if total_seconds in ZoneOffset._cache:
|
|
return ZoneOffset._cache[total_seconds]
|
|
z = ZoneOffset(total_seconds)
|
|
ZoneOffset._cache[total_seconds] = z
|
|
return z
|
|
|
|
def getTotalSeconds(self):
|
|
return self._total_seconds
|
|
|
|
def toString(self):
|
|
if self._total_seconds == 0:
|
|
return "Z"
|
|
return super().toString()
|
|
|
|
def equals(self, other):
|
|
if isinstance(other, ZoneOffset):
|
|
return self._total_seconds == other._total_seconds
|
|
return False
|
|
|
|
def hashCode(self):
|
|
return self._total_seconds
|
|
|
|
|
|
ZoneOffset.MAX = ZoneOffset(18 * 3600)
|
|
ZoneOffset.MIN = ZoneOffset(-18 * 3600)
|
|
ZoneOffset.UTC = ZoneOffset(0)
|
|
|
|
|
|
class ZoneRules(Stub):
|
|
def isFixedOffset(self):
|
|
return True
|
|
|
|
def getOffset(self, instant):
|
|
return ZoneOffset.UTC
|
|
|
|
def getDaylightSavings(self, instant):
|
|
return Duration.ZERO
|
|
|
|
def isDaylightSavings(self, instant):
|
|
return False
|
|
|
|
def isValidOffset(self, local_date_time, offset):
|
|
return True
|
|
|
|
def nextTransition(self, instant):
|
|
return None
|
|
|
|
def previousTransition(self, instant):
|
|
return None
|
|
|
|
def getTransitions(self):
|
|
return []
|
|
|
|
def getTransitionRules(self):
|
|
return []
|
|
|
|
|
|
# ============================================================
|
|
# Instant
|
|
# ============================================================
|
|
|
|
class Instant(Temporal, Comparable):
|
|
EPOCH = None
|
|
|
|
def __init__(self, epoch_second, nano=0):
|
|
super().__init__()
|
|
self._epoch_second = int(epoch_second)
|
|
self._nano = int(nano)
|
|
|
|
@staticmethod
|
|
def now():
|
|
import time as _t
|
|
t = _t.time()
|
|
sec = int(t)
|
|
nano = int((t - sec) * 1_000_000_000)
|
|
return Instant(sec, nano)
|
|
|
|
@staticmethod
|
|
def ofEpochMilli(epoch_milli):
|
|
return Instant(epoch_milli // 1000, (epoch_milli % 1000) * 1_000_000)
|
|
|
|
@staticmethod
|
|
def ofEpochSecond(epoch_second, nano_adjustment=0):
|
|
return Instant(epoch_second + nano_adjustment // 1_000_000_000,
|
|
nano_adjustment % 1_000_000_000)
|
|
|
|
def getEpochSecond(self):
|
|
return self._epoch_second
|
|
|
|
def getNano(self):
|
|
return self._nano
|
|
|
|
def toEpochMilli(self):
|
|
return self._epoch_second * 1000 + self._nano // 1_000_000
|
|
|
|
def plus(self, seconds, nano=0):
|
|
return Instant(self._epoch_second + seconds, self._nano + nano)
|
|
|
|
def minus(self, seconds, nano=0):
|
|
return self.plus(-seconds, -nano)
|
|
|
|
def isBefore(self, other):
|
|
if isinstance(other, Instant):
|
|
return (self._epoch_second, self._nano) < (other._epoch_second, other._nano)
|
|
return False
|
|
|
|
def isAfter(self, other):
|
|
if isinstance(other, Instant):
|
|
return (self._epoch_second, self._nano) > (other._epoch_second, other._nano)
|
|
return False
|
|
|
|
def compareTo(self, other):
|
|
if isinstance(other, Instant):
|
|
diff = self._epoch_second - other._epoch_second
|
|
if diff != 0:
|
|
return -1 if diff < 0 else 1
|
|
diff = self._nano - other._nano
|
|
return -1 if diff < 0 else (1 if diff > 0 else 0)
|
|
return -1
|
|
|
|
def equals(self, other):
|
|
if isinstance(other, Instant):
|
|
return self._epoch_second == other._epoch_second and self._nano == other._nano
|
|
return False
|
|
|
|
def hashCode(self):
|
|
return hash((self._epoch_second, self._nano))
|
|
|
|
def toString(self):
|
|
return f"Instant({self._epoch_second},{self._nano})"
|
|
|
|
|
|
Instant.EPOCH = Instant(0, 0)
|
|
|
|
|
|
# ============================================================
|
|
# Duration
|
|
# ============================================================
|
|
|
|
class Duration(Temporal, Comparable):
|
|
ZERO = None
|
|
|
|
def __init__(self, seconds, nanos=0):
|
|
super().__init__()
|
|
self._seconds = int(seconds)
|
|
self._nanos = int(nanos)
|
|
|
|
@staticmethod
|
|
def ofDays(days):
|
|
return Duration(days * 86400)
|
|
|
|
@staticmethod
|
|
def ofHours(hours):
|
|
return Duration(hours * 3600)
|
|
|
|
@staticmethod
|
|
def ofMinutes(minutes):
|
|
return Duration(minutes * 60)
|
|
|
|
@staticmethod
|
|
def ofSeconds(seconds):
|
|
return Duration(seconds)
|
|
|
|
@staticmethod
|
|
def ofMillis(millis):
|
|
return Duration(millis // 1000, (millis % 1000) * 1_000_000)
|
|
|
|
@staticmethod
|
|
def ofNanos(nanos):
|
|
return Duration(nanos // 1_000_000_000, nanos % 1_000_000_000)
|
|
|
|
@staticmethod
|
|
def between(start, end):
|
|
if isinstance(start, Instant) and isinstance(end, Instant):
|
|
return Duration(end.getEpochSecond() - start.getEpochSecond(),
|
|
end.getNano() - start.getNano())
|
|
return Duration.ZERO
|
|
|
|
def getSeconds(self):
|
|
return self._seconds
|
|
|
|
def getNano(self):
|
|
return self._nanos
|
|
|
|
def toDays(self):
|
|
return self._seconds // 86400
|
|
|
|
def toHours(self):
|
|
return self._seconds // 3600
|
|
|
|
def toMinutes(self):
|
|
return self._seconds // 60
|
|
|
|
def toSeconds(self):
|
|
return self._seconds
|
|
|
|
def toMillis(self):
|
|
return self._seconds * 1000 + self._nanos // 1_000_000
|
|
|
|
def toNanos(self):
|
|
return self._seconds * 1_000_000_000 + self._nanos
|
|
|
|
def plus(self, duration):
|
|
if isinstance(duration, Duration):
|
|
return Duration(self._seconds + duration._seconds,
|
|
self._nanos + duration._nanos)
|
|
return self
|
|
|
|
def minus(self, duration):
|
|
if isinstance(duration, Duration):
|
|
return Duration(self._seconds - duration._seconds,
|
|
self._nanos - duration._nanos)
|
|
return self
|
|
|
|
def isNegative(self):
|
|
return self._seconds < 0 or (self._seconds == 0 and self._nanos < 0)
|
|
|
|
def isZero(self):
|
|
return self._seconds == 0 and self._nanos == 0
|
|
|
|
def compareTo(self, other):
|
|
if isinstance(other, Duration):
|
|
diff = self._seconds - other._seconds
|
|
if diff != 0:
|
|
return -1 if diff < 0 else 1
|
|
diff = self._nanos - other._nanos
|
|
return -1 if diff < 0 else (1 if diff > 0 else 0)
|
|
return -1
|
|
|
|
def equals(self, other):
|
|
if isinstance(other, Duration):
|
|
return self._seconds == other._seconds and self._nanos == other._nanos
|
|
return False
|
|
|
|
def toString(self):
|
|
return f"PT{self._seconds}S"
|
|
|
|
def hashCode(self):
|
|
return hash((self._seconds, self._nanos))
|
|
|
|
|
|
Duration.ZERO = Duration(0, 0)
|
|
|
|
|
|
# === 延迟初始化 ChronoUnit 常量(必须在 Duration 定义之后) ===
|
|
ChronoUnit.NANOS = ChronoUnit("Nanos", Duration.ofNanos(1))
|
|
ChronoUnit.MICROS = ChronoUnit("Micros", Duration.ofNanos(1000))
|
|
ChronoUnit.MILLIS = ChronoUnit("Millis", Duration.ofNanos(1_000_000))
|
|
ChronoUnit.SECONDS = ChronoUnit("Seconds", Duration.ofSeconds(1))
|
|
ChronoUnit.MINUTES = ChronoUnit("Minutes", Duration.ofMinutes(1))
|
|
ChronoUnit.HOURS = ChronoUnit("Hours", Duration.ofHours(1))
|
|
ChronoUnit.HALF_DAYS = ChronoUnit("HalfDays", Duration.ofHours(12))
|
|
ChronoUnit.DAYS = ChronoUnit("Days", Duration.ofDays(1))
|
|
ChronoUnit.WEEKS = ChronoUnit("Weeks", Duration.ofDays(7))
|
|
ChronoUnit.MONTHS = ChronoUnit("Months", Duration.ofDays(30), estimated=True)
|
|
ChronoUnit.YEARS = ChronoUnit("Years", Duration.ofDays(365), estimated=True)
|
|
ChronoUnit.DECADES = ChronoUnit("Decades", Duration.ofDays(365 * 10), estimated=True)
|
|
ChronoUnit.CENTURIES = ChronoUnit("Centuries", Duration.ofDays(365 * 100), estimated=True)
|
|
ChronoUnit.MILLENNIA = ChronoUnit("Millennia", Duration.ofDays(365 * 1000), estimated=True)
|
|
ChronoUnit.ERAS = ChronoUnit("Eras", Duration.ofDays(365 * 1_000_000_000), estimated=True)
|
|
ChronoUnit.FOREVER = ChronoUnit("Forever", Duration.ofSeconds(1_000_000_000 * 86400 * 365), estimated=True)
|
|
|
|
|
|
# ============================================================
|
|
# Period
|
|
# ============================================================
|
|
|
|
class Period(Stub):
|
|
ZERO = None
|
|
|
|
def __init__(self, years, months, days):
|
|
super().__init__()
|
|
self._years = years
|
|
self._months = months
|
|
self._days = days
|
|
|
|
@staticmethod
|
|
def ofDays(days):
|
|
return Period(0, 0, days)
|
|
|
|
@staticmethod
|
|
def ofWeeks(weeks):
|
|
return Period(0, 0, weeks * 7)
|
|
|
|
@staticmethod
|
|
def ofMonths(months):
|
|
return Period(0, months, 0)
|
|
|
|
@staticmethod
|
|
def ofYears(years):
|
|
return Period(years, 0, 0)
|
|
|
|
@staticmethod
|
|
def between(start, end):
|
|
if isinstance(start, LocalDate) and isinstance(end, LocalDate):
|
|
return Period(end._year - start._year,
|
|
end._month - start._month,
|
|
end._day - start._day)
|
|
return Period.ZERO
|
|
|
|
def getYears(self):
|
|
return self._years
|
|
|
|
def getMonths(self):
|
|
return self._months
|
|
|
|
def getDays(self):
|
|
return self._days
|
|
|
|
def plus(self, period):
|
|
if isinstance(period, Period):
|
|
return Period(self._years + period._years,
|
|
self._months + period._months,
|
|
self._days + period._days)
|
|
return self
|
|
|
|
def minus(self, period):
|
|
if isinstance(period, Period):
|
|
return Period(self._years - period._years,
|
|
self._months - period._months,
|
|
self._days - period._days)
|
|
return self
|
|
|
|
def isZero(self):
|
|
return self._years == 0 and self._months == 0 and self._days == 0
|
|
|
|
def isNegative(self):
|
|
return self._years < 0 or self._months < 0 or self._days < 0
|
|
|
|
def equals(self, other):
|
|
if isinstance(other, Period):
|
|
return (self._years == other._years and
|
|
self._months == other._months and
|
|
self._days == other._days)
|
|
return False
|
|
|
|
def toString(self):
|
|
return f"P{self._years}Y{self._months}M{self._days}D"
|
|
|
|
def hashCode(self):
|
|
return hash((self._years, self._months, self._days))
|
|
|
|
|
|
Period.ZERO = Period(0, 0, 0)
|
|
|
|
|
|
# ============================================================
|
|
# LocalDate
|
|
# ============================================================
|
|
|
|
class LocalDate(Temporal, Comparable):
|
|
MIN = None
|
|
MAX = None
|
|
|
|
def __init__(self, year, month, day):
|
|
super().__init__()
|
|
self._year = year
|
|
self._month = month
|
|
self._day = day
|
|
|
|
@staticmethod
|
|
def now(zone=None):
|
|
today = _dt.date.today()
|
|
return LocalDate(today.year, today.month, today.day)
|
|
|
|
@staticmethod
|
|
def of(year, month, dayOfMonth):
|
|
if isinstance(month, Month):
|
|
month = month.getValue()
|
|
return LocalDate(year, month, dayOfMonth)
|
|
|
|
@staticmethod
|
|
def parse(text, formatter=None):
|
|
if formatter is None:
|
|
parts = text.split("-")
|
|
return LocalDate(int(parts[0]), int(parts[1]), int(parts[2]))
|
|
return formatter.parse(text, LocalDate)
|
|
|
|
def getYear(self):
|
|
return self._year
|
|
|
|
def getMonth(self):
|
|
return Month._from_value(self._month)
|
|
|
|
def getMonthValue(self):
|
|
return self._month
|
|
|
|
def getDayOfMonth(self):
|
|
return self._day
|
|
|
|
def getDayOfWeek(self):
|
|
import time as _t
|
|
import datetime as _dt2
|
|
d = _dt2.date(self._year, self._month, self._day)
|
|
return DayOfWeek._from_value(d.isoweekday())
|
|
|
|
def getDayOfYear(self):
|
|
d = _dt.date(self._year, self._month, self._day)
|
|
return d.timetuple().tm_yday
|
|
|
|
def getEra(self):
|
|
return "CE" if self._year > 0 else "BCE"
|
|
|
|
def isLeapYear(self):
|
|
return _dt.date(self._year, 3, 1) - _dt.date(self._year, 2, 28) == _dt.timedelta(days=1)
|
|
|
|
def lengthOfMonth(self):
|
|
m = self._month
|
|
if m == 2:
|
|
return 29 if self.isLeapYear() else 28
|
|
if m in (4, 6, 9, 11):
|
|
return 30
|
|
return 31
|
|
|
|
def lengthOfYear(self):
|
|
return 366 if self.isLeapYear() else 365
|
|
|
|
def toEpochDay(self):
|
|
d = _dt.date(self._year, self._month, self._day)
|
|
return d.toordinal()
|
|
|
|
def plusDays(self, days):
|
|
d = _dt.date(self._year, self._month, self._day) + _dt.timedelta(days=days)
|
|
return LocalDate(d.year, d.month, d.day)
|
|
|
|
def plusWeeks(self, weeks):
|
|
return self.plusDays(weeks * 7)
|
|
|
|
def plusMonths(self, months):
|
|
total = self._year * 12 + (self._month - 1) + months
|
|
y = total // 12
|
|
m = (total % 12) + 1
|
|
d = min(self._day, 29 if m == 2 and _dt.date(y, 3, 1) - _dt.date(y, 2, 28) == _dt.timedelta(days=1) else
|
|
30 if m in (4, 6, 9, 11) else 31)
|
|
return LocalDate(y, m, d)
|
|
|
|
def plusYears(self, years):
|
|
return self.plusMonths(years * 12)
|
|
|
|
def minusDays(self, days):
|
|
return self.plusDays(-days)
|
|
|
|
def minusWeeks(self, weeks):
|
|
return self.plusDays(-weeks * 7)
|
|
|
|
def minusMonths(self, months):
|
|
return self.plusMonths(-months)
|
|
|
|
def minusYears(self, years):
|
|
return self.plusYears(-years)
|
|
|
|
def isBefore(self, other):
|
|
if isinstance(other, LocalDate):
|
|
return (self._year, self._month, self._day) < (other._year, other._month, other._day)
|
|
return False
|
|
|
|
def isAfter(self, other):
|
|
if isinstance(other, LocalDate):
|
|
return (self._year, self._month, self._day) > (other._year, other._month, other._day)
|
|
return False
|
|
|
|
def isEqual(self, other):
|
|
if isinstance(other, LocalDate):
|
|
return (self._year, self._month, self._day) == (other._year, other._month, other._day)
|
|
return False
|
|
|
|
def withDayOfMonth(self, day):
|
|
return LocalDate(self._year, self._month, day)
|
|
|
|
def withMonth(self, month):
|
|
if isinstance(month, Month):
|
|
month = month.getValue()
|
|
return LocalDate(self._year, month, self._day)
|
|
|
|
def withYear(self, year):
|
|
return LocalDate(year, self._month, self._day)
|
|
|
|
def atTime(self, hour, minute=0, second=0, nano=0):
|
|
return LocalDateTime(self, LocalTime(hour, minute, second, nano))
|
|
|
|
def atStartOfDay(self, zone=None):
|
|
return LocalDateTime(self, LocalTime.MIDNIGHT)
|
|
|
|
def format(self, formatter):
|
|
return formatter.format(self)
|
|
|
|
def compareTo(self, other):
|
|
if isinstance(other, LocalDate):
|
|
v = self._year - other._year
|
|
if v != 0:
|
|
return -1 if v < 0 else 1
|
|
v = self._month - other._month
|
|
if v != 0:
|
|
return -1 if v < 0 else 1
|
|
v = self._day - other._day
|
|
return -1 if v < 0 else (1 if v > 0 else 0)
|
|
return -1
|
|
|
|
def equals(self, other):
|
|
if isinstance(other, LocalDate):
|
|
return (self._year, self._month, self._day) == (other._year, other._month, other._day)
|
|
return False
|
|
|
|
def toString(self):
|
|
return f"{self._year:04d}-{self._month:02d}-{self._day:02d}"
|
|
|
|
def hashCode(self):
|
|
return hash((self._year, self._month, self._day))
|
|
|
|
|
|
LocalDate.MIN = LocalDate(-999999999, 1, 1)
|
|
LocalDate.MAX = LocalDate(999999999, 12, 31)
|
|
|
|
|
|
# ============================================================
|
|
# LocalTime
|
|
# ============================================================
|
|
|
|
class LocalTime(Temporal, Comparable):
|
|
MIDNIGHT = None
|
|
NOON = None
|
|
MIN = None
|
|
MAX = None
|
|
|
|
def __init__(self, hour, minute=0, second=0, nano=0):
|
|
super().__init__()
|
|
self._hour = hour
|
|
self._minute = minute
|
|
self._second = second
|
|
self._nano = nano
|
|
|
|
@staticmethod
|
|
def now(zone=None):
|
|
t = _dt.datetime.now().time()
|
|
return LocalTime(t.hour, t.minute, t.second, t.microsecond * 1000)
|
|
|
|
@staticmethod
|
|
def of(hour, minute=0, second=0, nano=0):
|
|
return LocalTime(hour, minute, second, nano)
|
|
|
|
@staticmethod
|
|
def parse(text, formatter=None):
|
|
parts = text.split(":")
|
|
h = int(parts[0])
|
|
m = int(parts[1])
|
|
s = int(parts[2]) if len(parts) > 2 else 0
|
|
return LocalTime(h, m, s)
|
|
|
|
def getHour(self):
|
|
return self._hour
|
|
|
|
def getMinute(self):
|
|
return self._minute
|
|
|
|
def getSecond(self):
|
|
return self._second
|
|
|
|
def getNano(self):
|
|
return self._nano
|
|
|
|
def toSecondOfDay(self):
|
|
return self._hour * 3600 + self._minute * 60 + self._second
|
|
|
|
def toNanoOfDay(self):
|
|
return self.toSecondOfDay() * 1_000_000_000 + self._nano
|
|
|
|
def plusHours(self, h):
|
|
total = self._hour + h
|
|
return LocalTime(total % 24, self._minute, self._second, self._nano)
|
|
|
|
def plusMinutes(self, m):
|
|
total = self.toSecondOfDay() + m * 60
|
|
total %= 86400
|
|
h = total // 3600
|
|
total %= 3600
|
|
mi = total // 60
|
|
s = total % 60
|
|
return LocalTime(h, mi, s, self._nano)
|
|
|
|
def plusSeconds(self, s):
|
|
total = self.toSecondOfDay() + s
|
|
total %= 86400
|
|
h = total // 3600
|
|
total %= 3600
|
|
mi = total // 60
|
|
sec = total % 60
|
|
return LocalTime(h, mi, sec, self._nano)
|
|
|
|
def plusNanos(self, n):
|
|
return LocalTime(self._hour, self._minute, self._second, self._nano + n)
|
|
|
|
def minusHours(self, h):
|
|
return self.plusHours(-h)
|
|
|
|
def minusMinutes(self, m):
|
|
return self.plusMinutes(-m)
|
|
|
|
def minusSeconds(self, s):
|
|
return self.plusSeconds(-s)
|
|
|
|
def withHour(self, h):
|
|
return LocalTime(h, self._minute, self._second, self._nano)
|
|
|
|
def withMinute(self, m):
|
|
return LocalTime(self._hour, m, self._second, self._nano)
|
|
|
|
def withSecond(self, s):
|
|
return LocalTime(self._hour, self._minute, s, self._nano)
|
|
|
|
def withNano(self, n):
|
|
return LocalTime(self._hour, self._minute, self._second, n)
|
|
|
|
def atDate(self, date):
|
|
return LocalDateTime(date, self)
|
|
|
|
def isBefore(self, other):
|
|
if isinstance(other, LocalTime):
|
|
return self.toNanoOfDay() < other.toNanoOfDay()
|
|
return False
|
|
|
|
def isAfter(self, other):
|
|
if isinstance(other, LocalTime):
|
|
return self.toNanoOfDay() > other.toNanoOfDay()
|
|
return False
|
|
|
|
def format(self, formatter):
|
|
return formatter.format(self)
|
|
|
|
def compareTo(self, other):
|
|
if isinstance(other, LocalTime):
|
|
diff = self.toNanoOfDay() - other.toNanoOfDay()
|
|
return -1 if diff < 0 else (1 if diff > 0 else 0)
|
|
return -1
|
|
|
|
def equals(self, other):
|
|
if isinstance(other, LocalTime):
|
|
return (self._hour, self._minute, self._second, self._nano) == \
|
|
(other._hour, other._minute, other._second, other._nano)
|
|
return False
|
|
|
|
def toString(self):
|
|
return f"{self._hour:02d}:{self._minute:02d}:{self._second:02d}"
|
|
|
|
def hashCode(self):
|
|
return hash((self._hour, self._minute, self._second, self._nano))
|
|
|
|
|
|
LocalTime.MIDNIGHT = LocalTime(0, 0, 0, 0)
|
|
LocalTime.NOON = LocalTime(12, 0, 0, 0)
|
|
LocalTime.MIN = LocalTime(0, 0, 0, 0)
|
|
LocalTime.MAX = LocalTime(23, 59, 59, 999999999)
|
|
|
|
|
|
# ============================================================
|
|
# LocalDateTime
|
|
# ============================================================
|
|
|
|
class LocalDateTime(Temporal, Comparable):
|
|
MIN = None
|
|
MAX = None
|
|
|
|
def __init__(self, date, time):
|
|
super().__init__()
|
|
self._date = date
|
|
self._time = time
|
|
|
|
@staticmethod
|
|
def now(zone=None):
|
|
now = _dt.datetime.now()
|
|
date = LocalDate(now.year, now.month, now.day)
|
|
time = LocalTime(now.hour, now.minute, now.second, now.microsecond * 1000)
|
|
return LocalDateTime(date, time)
|
|
|
|
@staticmethod
|
|
def of(date_or_year, month=None, dayOfMonth=None, hour=None, minute=0, second=0, nano=0):
|
|
if isinstance(date_or_year, LocalDate):
|
|
return LocalDateTime(date_or_year, LocalTime(month or 0, dayOfMonth or 0, hour or 0))
|
|
return LocalDateTime(LocalDate(date_or_year, month, dayOfMonth),
|
|
LocalTime(hour, minute, second, nano))
|
|
|
|
@staticmethod
|
|
def parse(text, formatter=None):
|
|
parts = text.split("T")
|
|
date = LocalDate.parse(parts[0])
|
|
time = LocalTime.parse(parts[1]) if len(parts) > 1 else LocalTime.MIDNIGHT
|
|
return LocalDateTime(date, time)
|
|
|
|
def toLocalDate(self):
|
|
return self._date
|
|
|
|
def toLocalTime(self):
|
|
return self._time
|
|
|
|
def getYear(self):
|
|
return self._date.getYear()
|
|
|
|
def getMonth(self):
|
|
return self._date.getMonth()
|
|
|
|
def getMonthValue(self):
|
|
return self._date.getMonthValue()
|
|
|
|
def getDayOfMonth(self):
|
|
return self._date.getDayOfMonth()
|
|
|
|
def getDayOfWeek(self):
|
|
return self._date.getDayOfWeek()
|
|
|
|
def getDayOfYear(self):
|
|
return self._date.getDayOfYear()
|
|
|
|
def getHour(self):
|
|
return self._time.getHour()
|
|
|
|
def getMinute(self):
|
|
return self._time.getMinute()
|
|
|
|
def getSecond(self):
|
|
return self._time.getSecond()
|
|
|
|
def getNano(self):
|
|
return self._time.getNano()
|
|
|
|
def plusDays(self, d):
|
|
return LocalDateTime(self._date.plusDays(d), self._time)
|
|
|
|
def plusHours(self, h):
|
|
return LocalDateTime(self._date, self._time.plusHours(h))
|
|
|
|
def plusMinutes(self, m):
|
|
return LocalDateTime(self._date, self._time.plusMinutes(m))
|
|
|
|
def plusSeconds(self, s):
|
|
return LocalDateTime(self._date, self._time.plusSeconds(s))
|
|
|
|
def minusDays(self, d):
|
|
return self.plusDays(-d)
|
|
|
|
def minusHours(self, h):
|
|
return self.plusHours(-h)
|
|
|
|
def minusMinutes(self, m):
|
|
return self.plusMinutes(-m)
|
|
|
|
def minusSeconds(self, s):
|
|
return self.plusSeconds(-s)
|
|
|
|
def withYear(self, y):
|
|
return LocalDateTime(self._date.withYear(y), self._time)
|
|
|
|
def withMonth(self, m):
|
|
return LocalDateTime(self._date.withMonth(m), self._time)
|
|
|
|
def withDayOfMonth(self, d):
|
|
return LocalDateTime(self._date.withDayOfMonth(d), self._time)
|
|
|
|
def atZone(self, zone):
|
|
return ZonedDateTime(self, zone)
|
|
|
|
def isBefore(self, other):
|
|
if isinstance(other, LocalDateTime):
|
|
if not self._date.isEqual(other._date):
|
|
return self._date.isBefore(other._date)
|
|
return self._time.isBefore(other._time)
|
|
return False
|
|
|
|
def isAfter(self, other):
|
|
if isinstance(other, LocalDateTime):
|
|
if not self._date.isEqual(other._date):
|
|
return self._date.isAfter(other._date)
|
|
return self._time.isAfter(other._time)
|
|
return False
|
|
|
|
def format(self, formatter):
|
|
return formatter.format(self)
|
|
|
|
def compareTo(self, other):
|
|
if isinstance(other, LocalDateTime):
|
|
c = self._date.compareTo(other._date)
|
|
return c if c != 0 else self._time.compareTo(other._time)
|
|
return -1
|
|
|
|
def equals(self, other):
|
|
if isinstance(other, LocalDateTime):
|
|
return self._date.equals(other._date) and self._time.equals(other._time)
|
|
return False
|
|
|
|
def toString(self):
|
|
return f"{self._date}T{self._time}"
|
|
|
|
def hashCode(self):
|
|
return hash((self._date, self._time))
|
|
|
|
|
|
LocalDateTime.MIN = LocalDateTime(LocalDate.MIN, LocalTime.MIN)
|
|
LocalDateTime.MAX = LocalDateTime(LocalDate.MAX, LocalTime.MAX)
|
|
|
|
|
|
# ============================================================
|
|
# ZonedDateTime
|
|
# ============================================================
|
|
|
|
class ZonedDateTime(Temporal, Comparable):
|
|
def __init__(self, date_time, zone):
|
|
super().__init__()
|
|
self._date_time = date_time
|
|
self._zone = zone
|
|
|
|
@staticmethod
|
|
def now(zone=None):
|
|
ldt = LocalDateTime.now()
|
|
z = zone or ZoneId.systemDefault()
|
|
return ZonedDateTime(ldt, z)
|
|
|
|
@staticmethod
|
|
def of(date, time=None, zone=None, offset=None):
|
|
if isinstance(date, LocalDateTime):
|
|
return ZonedDateTime(date, time or ZoneId.systemDefault())
|
|
return ZonedDateTime(LocalDateTime.of(date, time), zone)
|
|
|
|
@staticmethod
|
|
def parse(text, formatter=None):
|
|
return ZonedDateTime(LocalDateTime.now(), ZoneId.systemDefault())
|
|
|
|
def getZone(self):
|
|
return self._zone
|
|
|
|
def getOffset(self):
|
|
return ZoneOffset.UTC
|
|
|
|
def withZoneSameInstant(self, zone):
|
|
return ZonedDateTime(self._date_time, zone)
|
|
|
|
def withZoneSameLocal(self, zone):
|
|
return ZonedDateTime(self._date_time, zone)
|
|
|
|
def toLocalDate(self):
|
|
return self._date_time.toLocalDate()
|
|
|
|
def toLocalTime(self):
|
|
return self._date_time.toLocalTime()
|
|
|
|
def toLocalDateTime(self):
|
|
return self._date_time
|
|
|
|
def toInstant(self):
|
|
return Instant.now()
|
|
|
|
def plusDays(self, d):
|
|
return ZonedDateTime(self._date_time.plusDays(d), self._zone)
|
|
|
|
def plusHours(self, h):
|
|
return ZonedDateTime(self._date_time.plusHours(h), self._zone)
|
|
|
|
def minusDays(self, d):
|
|
return self.plusDays(-d)
|
|
|
|
def minusHours(self, h):
|
|
return self.plusHours(-h)
|
|
|
|
def format(self, formatter):
|
|
return formatter.format(self)
|
|
|
|
def compareTo(self, other):
|
|
if isinstance(other, ZonedDateTime):
|
|
return self._date_time.compareTo(other._date_time)
|
|
return -1
|
|
|
|
def equals(self, other):
|
|
if isinstance(other, ZonedDateTime):
|
|
return self._date_time.equals(other._date_time) and str(self._zone) == str(other._zone)
|
|
return False
|
|
|
|
def toString(self):
|
|
return f"{self._date_time}[{self._zone}]"
|
|
|
|
def hashCode(self):
|
|
return hash((self._date_time, str(self._zone)))
|
|
|
|
|
|
# ============================================================
|
|
# OffsetDateTime / OffsetTime (stubs)
|
|
# ============================================================
|
|
|
|
class OffsetDateTime(Temporal):
|
|
def __init__(self, date_time, offset):
|
|
super().__init__()
|
|
self._date_time = date_time
|
|
self._offset = offset
|
|
|
|
@staticmethod
|
|
def now():
|
|
return OffsetDateTime(LocalDateTime.now(), ZoneOffset.UTC)
|
|
|
|
def toLocalDate(self):
|
|
return self._date_time.toLocalDate()
|
|
|
|
def toLocalTime(self):
|
|
return self._date_time.toLocalTime()
|
|
|
|
def toString(self):
|
|
return f"{self._date_time}{self._offset}"
|
|
|
|
|
|
class OffsetTime(Stub):
|
|
def __init__(self, time, offset):
|
|
super().__init__()
|
|
self._time = time
|
|
self._offset = offset
|
|
|
|
@staticmethod
|
|
def now():
|
|
return OffsetTime(LocalTime.now(), ZoneOffset.UTC)
|
|
|
|
def toString(self):
|
|
return f"{self._time}{self._offset}"
|
|
|
|
|
|
# ============================================================
|
|
# Year / YearMonth / MonthDay
|
|
# ============================================================
|
|
|
|
class Year(Temporal, Comparable):
|
|
def __init__(self, year):
|
|
super().__init__()
|
|
self._year = year
|
|
|
|
@staticmethod
|
|
def now():
|
|
return Year(_dt.date.today().year)
|
|
|
|
@staticmethod
|
|
def of(year):
|
|
return Year(year)
|
|
|
|
def getValue(self):
|
|
return self._year
|
|
|
|
def isLeap(self):
|
|
return _dt.date(self._year, 3, 1) - _dt.date(self._year, 2, 28) == _dt.timedelta(days=1)
|
|
|
|
def length(self):
|
|
return 366 if self.isLeap() else 365
|
|
|
|
def atMonth(self, month):
|
|
if isinstance(month, Month):
|
|
month = month.getValue()
|
|
return YearMonth(self._year, month)
|
|
|
|
def compareTo(self, other):
|
|
if isinstance(other, Year):
|
|
return -1 if self._year < other._year else (1 if self._year > other._year else 0)
|
|
return -1
|
|
|
|
def toString(self):
|
|
return str(self._year)
|
|
|
|
|
|
class YearMonth(Temporal, Comparable):
|
|
def __init__(self, year, month):
|
|
super().__init__()
|
|
self._year = year
|
|
self._month = month
|
|
|
|
@staticmethod
|
|
def now():
|
|
d = _dt.date.today()
|
|
return YearMonth(d.year, d.month)
|
|
|
|
@staticmethod
|
|
def of(year, month):
|
|
return YearMonth(year, month)
|
|
|
|
def getYear(self):
|
|
return self._year
|
|
|
|
def getMonth(self):
|
|
return self._month
|
|
|
|
def getMonthValue(self):
|
|
return self._month if isinstance(self._month, int) else self._month.getValue()
|
|
|
|
def lengthOfMonth(self):
|
|
m = self.getMonthValue()
|
|
if m == 2:
|
|
return 29 if _dt.date(self._year, 3, 1) - _dt.date(self._year, 2, 28) == _dt.timedelta(days=1) else 28
|
|
return 30 if m in (4, 6, 9, 11) else 31
|
|
|
|
def lengthOfYear(self):
|
|
return 366 if _dt.date(self._year, 3, 1) - _dt.date(self._year, 2, 28) == _dt.timedelta(days=1) else 365
|
|
|
|
def atDay(self, day):
|
|
return LocalDate(self._year, self.getMonthValue(), day)
|
|
|
|
def compareTo(self, other):
|
|
if isinstance(other, YearMonth):
|
|
diff = self._year - other._year
|
|
if diff != 0:
|
|
return -1 if diff < 0 else 1
|
|
diff = self.getMonthValue() - other.getMonthValue()
|
|
return -1 if diff < 0 else (1 if diff > 0 else 0)
|
|
return -1
|
|
|
|
def toString(self):
|
|
return f"{self._year}-{self.getMonthValue():02d}"
|
|
|
|
|
|
class MonthDay(Stub):
|
|
def __init__(self, month, day):
|
|
super().__init__()
|
|
self._month = month
|
|
self._day = day
|
|
|
|
@staticmethod
|
|
def now():
|
|
d = _dt.date.today()
|
|
return MonthDay(d.month, d.day)
|
|
|
|
@staticmethod
|
|
def of(month, day):
|
|
return MonthDay(month, day)
|
|
|
|
def getMonth(self):
|
|
return self._month
|
|
|
|
def getMonthValue(self):
|
|
return self._month if isinstance(self._month, int) else self._month.getValue()
|
|
|
|
def getDayOfMonth(self):
|
|
return self._day
|
|
|
|
def atYear(self, year):
|
|
return LocalDate.of(year, self.getMonthValue(), self._day)
|
|
|
|
def toString(self):
|
|
return f"--{self.getMonthValue():02d}-{self._day:02d}"
|
|
|
|
|
|
# ============================================================
|
|
# Clock
|
|
# ============================================================
|
|
|
|
class Clock(Stub):
|
|
@staticmethod
|
|
def systemUTC():
|
|
return Clock()
|
|
|
|
@staticmethod
|
|
def systemDefaultZone():
|
|
return Clock()
|
|
|
|
@staticmethod
|
|
def system(zone):
|
|
return Clock()
|
|
|
|
def millis(self):
|
|
import time as _t
|
|
return int(_t.time() * 1000)
|
|
|
|
def instant(self):
|
|
return Instant.now()
|
|
|
|
def getZone(self):
|
|
return ZoneId.systemDefault()
|
|
|
|
|
|
# ============================================================
|
|
# DateTimeFormatter
|
|
# ============================================================
|
|
|
|
class DateTimeFormatter(Stub):
|
|
ISO_LOCAL_DATE = None
|
|
ISO_LOCAL_TIME = None
|
|
ISO_LOCAL_DATE_TIME = None
|
|
ISO_INSTANT = None
|
|
ISO_ZONED_DATE_TIME = None
|
|
ISO_OFFSET_DATE_TIME = None
|
|
|
|
def __init__(self, pattern):
|
|
super().__init__()
|
|
self._pattern = pattern
|
|
|
|
@staticmethod
|
|
def ofPattern(pattern):
|
|
return DateTimeFormatter(pattern)
|
|
|
|
def format(self, temporal):
|
|
if isinstance(temporal, LocalDate):
|
|
return temporal.toString()
|
|
if isinstance(temporal, LocalTime):
|
|
return temporal.toString()
|
|
if isinstance(temporal, LocalDateTime):
|
|
return temporal.toString()
|
|
if isinstance(temporal, ZonedDateTime):
|
|
return temporal.toString()
|
|
if isinstance(temporal, Instant):
|
|
return temporal.toString()
|
|
return str(temporal)
|
|
|
|
def parse(self, text, query=None):
|
|
if query is LocalDate or query is None:
|
|
return LocalDate.parse(text)
|
|
if query is LocalTime:
|
|
return LocalTime.parse(text)
|
|
if query is LocalDateTime:
|
|
return LocalDateTime.parse(text)
|
|
return text
|
|
|
|
|
|
DateTimeFormatter.ISO_LOCAL_DATE = DateTimeFormatter("yyyy-MM-dd")
|
|
DateTimeFormatter.ISO_LOCAL_TIME = DateTimeFormatter("HH:mm:ss")
|
|
DateTimeFormatter.ISO_LOCAL_DATE_TIME = DateTimeFormatter("yyyy-MM-ddTHH:mm:ss")
|
|
DateTimeFormatter.ISO_INSTANT = DateTimeFormatter("yyyy-MM-ddTHH:mm:ssZ")
|
|
DateTimeFormatter.ISO_ZONED_DATE_TIME = DateTimeFormatter("yyyy-MM-ddTHH:mm:ssZ")
|
|
DateTimeFormatter.ISO_OFFSET_DATE_TIME = DateTimeFormatter("yyyy-MM-ddTHH:mm:ssZ")
|
|
|
|
|
|
# ============================================================
|
|
# DateTimeFormatterBuilder (stub)
|
|
# ============================================================
|
|
|
|
class DateTimeFormatterBuilder(Stub):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._pattern_parts = []
|
|
|
|
def appendValue(self, field, width=1):
|
|
return self
|
|
|
|
def appendLiteral(self, literal):
|
|
return self
|
|
|
|
def appendPattern(self, pattern):
|
|
self._pattern_parts.append(pattern)
|
|
return self
|
|
|
|
def toFormatter(self):
|
|
return DateTimeFormatter("".join(self._pattern_parts))
|
|
|
|
|
|
# ============================================================
|
|
# TemporalAdjusters (stub)
|
|
# ============================================================
|
|
|
|
class TemporalAdjusters(Stub):
|
|
@staticmethod
|
|
def firstDayOfMonth():
|
|
return lambda d: d.withDayOfMonth(1)
|
|
|
|
@staticmethod
|
|
def lastDayOfMonth():
|
|
return lambda d: d.withDayOfMonth(d.lengthOfMonth())
|
|
|
|
@staticmethod
|
|
def firstDayOfYear():
|
|
return lambda d: d.withDayOfMonth(1).withMonth(1)
|
|
|
|
@staticmethod
|
|
def lastDayOfYear():
|
|
return lambda d: d.withDayOfMonth(31).withMonth(12)
|
|
|
|
@staticmethod
|
|
def firstInMonth(day_of_week):
|
|
return lambda d: d
|
|
|
|
@staticmethod
|
|
def next(day_of_week):
|
|
return lambda d: d.plusDays(1) |