""" 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)