""" Android text.format 包 — DateFormat, Time, Formatter, DateUtils 等 方法签名完全对齐 Android SDK;使用 Python datetime 模块实现真实逻辑 """ from __future__ import annotations import datetime import math import re import time as _time from typing import Any, Dict, List, Optional, Tuple from .._core import Stub, AndroidOnlyError from ..java.lang import RuntimeException IllegalArgumentException = type("IllegalArgumentException", (RuntimeException,), {}) __all__ = [ "DateFormat", "Time", "Formatter", "DateUtils", ] # ═══════════════════════════════════════════════════════════════ # DateFormat # ═══════════════════════════════════════════════════════════════ class DateFormat(Stub): """DateFormat — 日期格式化工具 (使用 Python datetime 实现)""" DAY_OF_WEEK = 1 DAY_OF_MONTH = 2 DAY_OF_YEAR = 3 HOUR_OF_DAY = 4 MINUTE = 5 SECOND = 6 MONTH = 7 YEAR = 8 _PATTERNS = { "short": "%m/%d/%y", "medium": "%b %d, %Y", "long": "%B %d, %Y", "full": "%A, %B %d, %Y", "time_short": "%I:%M %p", "time_medium": "%I:%M:%S %p", "time_long": "%I:%M:%S %p %Z", } def __init__(self): super().__init__() @staticmethod def getDateFormat(context: Any) -> "DateFormat": """获取日期格式""" return DateFormat() @staticmethod def getTimeFormat(context: Any) -> "DateFormat": """获取时间格式""" return DateFormat() @staticmethod def getDateTimeFormat(context: Any) -> "DateFormat": """获取日期时间格式""" return DateFormat() @staticmethod def is24HourFormat(context: Any) -> bool: """检查是否使用 24 小时制""" return False @staticmethod def format(date_str: str, in_format: str = "%Y-%m-%d", out_format: str = "%b %d, %Y") -> str: """格式化日期字符串""" try: dt = datetime.datetime.strptime(date_str, in_format) return dt.strftime(out_format) except (ValueError, TypeError): return date_str @staticmethod def formatDateRange(context: Any, start_millis: int, end_millis: int, flags: int = 0) -> str: """格式化日期范围""" start_dt = datetime.datetime.fromtimestamp(start_millis / 1000.0) end_dt = datetime.datetime.fromtimestamp(end_millis / 1000.0) if start_dt.date() == end_dt.date(): return start_dt.strftime("%b %d, %Y") return f"{start_dt.strftime('%b %d')} - {end_dt.strftime('%b %d, %Y')}" @staticmethod def getBestDateTimePattern(pattern: str, locale: Any) -> str: """获取最佳日期时间模式""" return pattern @staticmethod def getLongDateFormat(context: Any) -> "DateFormat": return DateFormat() @staticmethod def getMediumDateFormat(context: Any) -> "DateFormat": return DateFormat() @staticmethod def getShortDateFormat(context: Any) -> "DateFormat": return DateFormat() @staticmethod def getTimePeriodString(time_period: int) -> str: """获取时间段字符串""" periods = { 0: "morning", 1: "afternoon", 2: "evening", 3: "night", } return periods.get(time_period, "unknown") @staticmethod def formatElapsedTime(elapsed_millis: int) -> str: """格式化经过的时间""" seconds = elapsed_millis // 1000 minutes = seconds // 60 hours = minutes // 60 seconds = seconds % 60 minutes = minutes % 60 if hours > 0: return f"{hours}:{minutes:02d}:{seconds:02d}" return f"{minutes}:{seconds:02d}" # ═══════════════════════════════════════════════════════════════ # Time # ═══════════════════════════════════════════════════════════════ class Time(Stub): """Time — 时间类 (使用 Python datetime 实现)""" TIMEZONE_UTC = "UTC" EPOCH_JULIAN_DAY = 2440588 MONDAY = 2 SUNDAY = 1 JANUARY = 0 DECEMBER = 11 def __init__(self, timezone: str = "UTC"): super().__init__() self._dt = datetime.datetime.utcnow() self.timezone = timezone self.allDay = False self.julianDay = self._to_julian(self._dt) self.weekNumber = 0 self.gmtoff = 0 self.hour = self._dt.hour self.minute = self._dt.minute self.second = self._dt.second self.weekDay = self._dt.isoweekday() self.yearDay = self._dt.timetuple().tm_yday self.month = self._dt.month - 1 # 0-based self.monthDay = self._dt.day self.year = self._dt.year self.isDst = 0 @staticmethod def _to_julian(dt: datetime.datetime) -> int: """将 datetime 转换为儒略日""" timestamp = dt.timestamp() return int(Time.EPOCH_JULIAN_DAY + timestamp / 86400.0) @staticmethod def _from_julian(jd: int) -> datetime.datetime: """将儒略日转换为 datetime""" timestamp = (jd - Time.EPOCH_JULIAN_DAY) * 86400.0 return datetime.datetime.fromtimestamp(timestamp) def setToNow(self) -> None: """设置当前时间""" self._dt = datetime.datetime.now() self._update_fields() def set(self, second: int, minute: int, hour: int, monthDay: int, month: int, year: int) -> None: """设置时间""" self._dt = datetime.datetime(year, month + 1, monthDay, hour, minute, second) self._update_fields() def setTimezone(self, timezone: str) -> None: """设置时区""" self.timezone = timezone def normalize(self, ignoreDst: bool = False) -> int: """标准化时间,返回毫秒""" self._update_fields() return int(self._dt.timestamp() * 1000) def format(self, pattern: str = "%Y-%m-%d %H:%M:%S") -> str: """格式化输出""" return self._dt.strftime(pattern) def toString(self) -> str: return self._dt.isoformat() def parse(self, s: str) -> bool: """解析时间字符串""" formats = [ "%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d", "%m/%d/%Y %H:%M:%S", "%m/%d/%Y", ] for fmt in formats: try: self._dt = datetime.datetime.strptime(s, fmt) self._update_fields() return True except ValueError: continue return False def parse3339(self, s: str) -> bool: """解析 RFC 3339 时间字符串""" try: self._dt = datetime.datetime.fromisoformat(s.replace("Z", "+00:00")) self._update_fields() return True except (ValueError, TypeError): return False def switchTimezone(self, timezone: str) -> None: """切换时区""" self.timezone = timezone def toMillis(self) -> int: """转换为毫秒""" return int(self._dt.timestamp() * 1000) def setJulianDay(self, julianDay: int) -> None: """设置儒略日""" self.julianDay = julianDay self._dt = self._from_julian(julianDay) self._update_fields() @staticmethod def getJulianDay(millis: int, gmtoff: int) -> int: """从毫秒获取儒略日""" dt = datetime.datetime.fromtimestamp(millis / 1000.0) return Time._to_julian(dt) def getWeekNumber(self) -> int: """获取周数""" return self._dt.isocalendar()[1] def _update_fields(self) -> None: """更新所有字段""" self.hour = self._dt.hour self.minute = self._dt.minute self.second = self._dt.second self.weekDay = self._dt.isoweekday() self.yearDay = self._dt.timetuple().tm_yday self.month = self._dt.month - 1 self.monthDay = self._dt.day self.year = self._dt.year self.julianDay = self._to_julian(self._dt) self.weekNumber = self.getWeekNumber() # ═══════════════════════════════════════════════════════════════ # Formatter # ═══════════════════════════════════════════════════════════════ class Formatter(Stub): """Formatter — 格式化工具 (使用真实实现)""" def __init__(self): super().__init__() @staticmethod def formatFileSize(context: Any, size_bytes: int) -> str: """格式化文件大小""" if size_bytes < 0: return "0 B" units = ["B", "KB", "MB", "GB", "TB"] unit_idx = 0 size_f = float(size_bytes) while size_f >= 1024 and unit_idx < len(units) - 1: size_f /= 1024.0 unit_idx += 1 if unit_idx == 0: return f"{int(size_f)} {units[unit_idx]}" return f"{size_f:.1f} {units[unit_idx]}" @staticmethod def formatShortFileSize(context: Any, size_bytes: int) -> str: """格式化短文件大小""" return Formatter.formatFileSize(context, size_bytes) @staticmethod def formatIpAddress(addr: int) -> str: """格式化 IP 地址""" return f"{(addr >> 24) & 0xFF}.{(addr >> 16) & 0xFF}.{(addr >> 8) & 0xFF}.{addr & 0xFF}" @staticmethod def formatDateFromDB(context: Any, date_str: str) -> str: """格式化数据库日期""" return DateFormat.format(date_str, "%Y-%m-%d", "%b %d, %Y") @staticmethod def formatShortDateFromDB(context: Any, date_str: str) -> str: """格式化短数据库日期""" return DateFormat.format(date_str, "%Y-%m-%d", "%m/%d/%y") # ═══════════════════════════════════════════════════════════════ # DateUtils # ═══════════════════════════════════════════════════════════════ class DateUtils(Stub): """DateUtils — 日期工具类 (使用 Python datetime 实现)""" DAY_IN_MILLIS = 86400000 HOUR_IN_MILLIS = 3600000 MINUTE_IN_MILLIS = 60000 SECOND_IN_MILLIS = 1000 WEEK_IN_MILLIS = 604800000 YEAR_IN_MILLIS = 31536000000 FORMAT_SHOW_TIME = 0x00001 FORMAT_SHOW_DATE = 0x00002 FORMAT_SHOW_YEAR = 0x00004 FORMAT_NO_MIDNIGHT = 0x00008 FORMAT_SHOW_WEEKDAY = 0x00010 FORMAT_UTC = 0x00020 FORMAT_ABBREV_MONTH = 0x00040 FORMAT_ABBREV_WEEKDAY = 0x00080 FORMAT_NO_NOON = 0x00100 FORMAT_CAP_AMPM = 0x00200 FORMAT_NO_YEAR = 0x00400 FORMAT_NUMERIC_DATE = 0x00800 FORMAT_ABBREV_ALL = 0x01000 FORMAT_12HOUR = 0x02000 FORMAT_24HOUR = 0x04000 _WEEKDAYS = ["", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] _WEEKDAYS_ABBREV = ["", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] _MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] _MONTHS_ABBREV = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] def __init__(self): super().__init__() @staticmethod def formatDateTime(context: Any, millis: int, flags: int) -> str: """格式化日期时间""" dt = datetime.datetime.fromtimestamp(millis / 1000.0) parts = [] if flags & DateUtils.FORMAT_SHOW_WEEKDAY: abbrev = bool(flags & DateUtils.FORMAT_ABBREV_WEEKDAY) wd = DateUtils._WEEKDAYS_ABBREV[dt.isoweekday()] if abbrev else DateUtils._WEEKDAYS[dt.isoweekday()] parts.append(wd) if flags & DateUtils.FORMAT_SHOW_DATE: abbrev = bool(flags & DateUtils.FORMAT_ABBREV_MONTH) if flags & DateUtils.FORMAT_NUMERIC_DATE: parts.append(f"{dt.month}/{dt.day}") else: mn = DateUtils._MONTHS_ABBREV[dt.month - 1] if abbrev else DateUtils._MONTHS[dt.month - 1] parts.append(f"{mn} {dt.day}") if flags & DateUtils.FORMAT_SHOW_YEAR: parts.append(str(dt.year)) if flags & DateUtils.FORMAT_SHOW_TIME: if flags & DateUtils.FORMAT_24HOUR: parts.append(dt.strftime("%H:%M")) else: parts.append(dt.strftime("%I:%M %p")) return " ".join(parts) @staticmethod def formatDateRange(context: Any, start_millis: int, end_millis: int, flags: int = 0) -> str: """格式化日期范围""" return DateFormat.formatDateRange(context, start_millis, end_millis, flags) @staticmethod def formatElapsedTime(now: int, past: int) -> str: """格式化经过的时间""" diff = now - past return DateFormat.formatElapsedTime(diff) @staticmethod def getRelativeTimeSpanString(time_millis: int, now_millis: int, min_resolution: int = MINUTE_IN_MILLIS, flags: int = 0) -> str: """获取相对时间字符串""" diff = now_millis - time_millis if diff < 0: return "just now" if diff < DateUtils.MINUTE_IN_MILLIS: return "just now" if diff < DateUtils.HOUR_IN_MILLIS: minutes = int(diff / DateUtils.MINUTE_IN_MILLIS) return f"{minutes} minute{'s' if minutes > 1 else ''} ago" if diff < DateUtils.DAY_IN_MILLIS: hours = int(diff / DateUtils.HOUR_IN_MILLIS) return f"{hours} hour{'s' if hours > 1 else ''} ago" if diff < DateUtils.WEEK_IN_MILLIS: days = int(diff / DateUtils.DAY_IN_MILLIS) return f"{days} day{'s' if days > 1 else ''} ago" if diff < DateUtils.YEAR_IN_MILLIS: weeks = int(diff / DateUtils.WEEK_IN_MILLIS) return f"{weeks} week{'s' if weeks > 1 else ''} ago" years = int(diff / DateUtils.YEAR_IN_MILLIS) return f"{years} year{'s' if years > 1 else ''} ago" @staticmethod def getRelativeDateTimeString(time_millis: int, now_millis: int, min_resolution: int, transition_resolution: int, flags: int) -> str: """获取相对日期时间字符串""" return DateUtils.getRelativeTimeSpanString(time_millis, now_millis, min_resolution, flags) @staticmethod def getDayOfWeekString(day_of_week: int, flags: int) -> str: """获取星期字符串""" abbrev = bool(flags & DateUtils.FORMAT_ABBREV_WEEKDAY) if 1 <= day_of_week <= 7: return DateUtils._WEEKDAYS_ABBREV[day_of_week] if abbrev else DateUtils._WEEKDAYS[day_of_week] return "" @staticmethod def getMonthString(month: int, flags: int) -> str: """获取月份字符串""" abbrev = bool(flags & DateUtils.FORMAT_ABBREV_MONTH) if 0 <= month <= 11: return DateUtils._MONTHS_ABBREV[month] if abbrev else DateUtils._MONTHS[month] return "" @staticmethod def isToday(millis: int) -> bool: """检查是否是今天""" today = datetime.date.today() dt = datetime.datetime.fromtimestamp(millis / 1000.0) return dt.date() == today @staticmethod def sameDay(millis1: int, millis2: int) -> bool: """检查是否是同一天""" dt1 = datetime.datetime.fromtimestamp(millis1 / 1000.0) dt2 = datetime.datetime.fromtimestamp(millis2 / 1000.0) return dt1.date() == dt2.date()