First Push

This commit is contained in:
喆OvO
2026-08-06 11:11:11 +08:00
commit 0fef309972
551 changed files with 15793 additions and 0 deletions
+114
View File
@@ -0,0 +1,114 @@
"""日出日落时间计算(NOAA 简化算法)与白天/夜晚判定。
用于"自动"主题模式:根据用户天气配置的经纬度 + 本地时区,判断当前
时刻处于白天(浅色主题)还是夜晚(深色主题)。
算法来源:Ed Williams《Sunrise/Sunset Algorithm》
(https://edwilliams.org/sunrise_sunset_algorithm.htm)
"""
import math
from datetime import datetime, timezone, timedelta
# 太阳天顶角:日出日落定义(含大气折射)
ZENITH = 90.833
def _day_of_year(dt: datetime) -> int:
"""计算一年中的第几天(NOAA 公式)。"""
y, m, d = dt.year, dt.month, dt.day
n1 = math.floor(275 * m / 9)
n2 = math.floor((m + 9) / 12)
n3 = 1 + math.floor((y - 4 * math.floor(y / 4) + 2) / 3)
return n1 - (n2 * n3) + d - 30
def _hour_angle(lat: float, lon: float, day_of_year: int, is_sunrise: bool) -> float | None:
"""返回日出/日落的 UTC 小时(0~24),None 表示极昼/极夜(当天无日出日落)。"""
lng_hour = lon / 15.0
if is_sunrise:
t = day_of_year + ((6 - lng_hour) / 24.0)
else:
t = day_of_year + ((18 - lng_hour) / 24.0)
m = (0.9856 * t) - 3.289
l = (m + 1.916 * math.sin(math.radians(m))
+ 0.020 * math.sin(math.radians(2 * m)) + 282.634) % 360
ra = math.degrees(math.atan(0.91764 * math.tan(math.radians(l)))) % 360
# 校正 RA 象限
l_quad = math.floor(l / 90) * 90
ra_quad = math.floor(ra / 90) * 90
ra = ra + (l_quad - ra_quad)
ra /= 15.0 # 转为小时
sin_dec = 0.39782 * math.sin(math.radians(l))
cos_dec = math.cos(math.asin(sin_dec))
cos_h = ((math.cos(math.radians(ZENITH)) - sin_dec * math.sin(math.radians(lat)))
/ (cos_dec * math.cos(math.radians(lat))))
if cos_h > 1 or cos_h < -1:
return None # 极昼/极夜
if is_sunrise:
h = 360 - math.degrees(math.acos(cos_h))
else:
h = math.degrees(math.acos(cos_h))
h /= 15.0
# NOAA:T = H + RA - 0.06571*t - 6.622(小时),UT = T - lngHour(mod 24)
local_t = h + ra - 0.06571 * t - 6.622
return (local_t - lng_hour) % 24.0
def sun_times_utc(lat: float, lon: float, date: datetime) -> tuple[datetime | None, datetime | None]:
"""返回 (日出UTC, 日落UTC),极昼/极夜时对应项为 None。
date 为任意带时区 datetime(取其日期)。"""
lat = float(lat)
lon = float(lon)
day = _day_of_year(date.astimezone(timezone.utc))
sunrise_h = _hour_angle(lat, lon, day, True)
sunset_h = _hour_angle(lat, lon, day, False)
if sunrise_h is None and sunset_h is None:
return None, None # 极昼或极夜,调用方按本地小时粗判
def to_utc(h: float) -> datetime:
base = date.astimezone(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0)
return base + timedelta(hours=h)
sunrise = to_utc(sunrise_h) if sunrise_h is not None else None
sunset = to_utc(sunset_h) if sunset_h is not None else None
return sunrise, sunset
def is_daytime(lat: float, lon: float, now_local: datetime) -> bool:
"""判断本地时刻是否为白天。
now_local 为带时区的本地 datetime(使用用户配置时区)。
用 UTC 小时比较(忽略日期归属),自动处理日出/日落跨 UTC 午夜的情况。
"""
now_utc = now_local.astimezone(timezone.utc)
sunrise, sunset = sun_times_utc(lat, lon, now_local)
if sunrise is None or sunset is None:
# 极昼/极夜:根据本地小时粗判(6-18 视为白天)
return 6 <= now_local.hour < 18
sr_h = sunrise.hour + sunrise.minute / 60.0
ss_h = sunset.hour + sunset.minute / 60.0
now_h = now_utc.hour + now_utc.minute / 60.0
if ss_h < sr_h:
ss_h += 24.0 # 跨 UTC 午夜(东半球)
if now_h < sr_h:
now_h += 24.0
return sr_h <= now_h <= ss_h
def day_fraction_today(lat: float, lon: float, now_local: datetime) -> float:
"""当天白昼占全天比例(0~1),用于自动模式的平滑切换(预留)。"""
sunrise, sunset = sun_times_utc(lat, lon, now_local)
if sunrise is None or sunset is None:
return 0.5
total = (sunset - sunrise).total_seconds()
return max(0.0, min(1.0, total / 86400.0))