First Push
This commit is contained in:
@@ -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))
|
||||
Reference in New Issue
Block a user