#!/usr/bin/env python3
"""
真太阳时计算工具
真太阳时是根据出生地的经度修正后的地方时间。
中国统一使用北京时间(东八区,UTC+8,标准经度120°E),
但由于中国地域辽阔,东西跨度大,实际各地的地方时与北京时间存在差异。
计算公式:
真太阳时 = 北京时间 + (出生地经度 - 120°) × 4分钟/度
示例:
- 北京(116.4°E):比北京时间慢约14分钟
- 上海(121.5°E):比北京时间快约6分钟
- 乌鲁木齐(87.6°E):比北京时间慢约129分钟
"""
from datetime import datetime, timedelta
from typing import Tuple
# 东八区(北京时间)的标准经度
BEIJING_LONGITUDE = 120.0
# 每度经度对应的时间差(分钟)
MINUTES_PER_DEGREE = 4.0
def calculate_solar_time_offset(longitude: float) -> int:
"""
计算真太阳时偏移量(分钟)
Args:
longitude: 出生地经度(东经为正,西经为负)
Returns:
偏移分钟数(正数表示快于北京时间,负数表示慢于北京时间)
Examples:
>>> calculate_solar_time_offset(116.4) # 北京
-14
>>> calculate_solar_time_offset(121.5) # 上海
6
>>> calculate_solar_time_offset(87.6) # 乌鲁木齐
-129
"""
offset_degrees = longitude - BEIJING_LONGITUDE
offset_minutes = offset_degrees * MINUTES_PER_DEGREE
return round(offset_minutes)
def beijing_to_solar_time(beijing_time: datetime, longitude: float) -> datetime:
"""
将北京时间转换为真太阳时
Args:
beijing_time: 北京时间
longitude: 出生地经度
Returns:
真太阳时
Examples:
>>> bt = datetime(2000, 8, 16, 12, 0)
>>> beijing_to_solar_time(bt, 116.4) # 北京
datetime(2000, 8, 16, 11, 46)
>>> beijing_to_solar_time(bt, 121.5) # 上海
datetime(2000, 8, 16, 12, 6)
"""
offset_minutes = calculate_solar_time_offset(longitude)
solar_time = beijing_time + timedelta(minutes=offset_minutes)
return solar_time
def adjust_time_index_for_solar_time(
beijing_hour: int, beijing_minute: int, longitude: float
) -> Tuple[int, int, int]:
"""
根据真太阳时调整时辰序号
Args:
beijing_hour: 北京时间小时(0-23)
beijing_minute: 北京时间分钟(0-59)
longitude: 出生地经度
Returns:
(调整后的时辰序号, 真太阳时小时, 真太阳时分钟)
注意:
时辰序号范围:0-12
- 0: 早子时 (23:00-01:00)
- 1: 丑时 (01:00-03:00)
- 2: 寅时 (03:00-05:00)
- ...
- 11: 亥时 (21:00-23:00)
- 12: 晚子时 (23:00-01:00)
Examples:
>>> adjust_time_index_for_solar_time(12, 0, 116.4) # 北京中午12点
(6, 11, 46) # 午时,真太阳时11:46
"""
# 创建临时datetime对象进行计算
temp_datetime = datetime(2000, 1, 1, beijing_hour, beijing_minute)
solar_datetime = beijing_to_solar_time(temp_datetime, longitude)
solar_hour = solar_datetime.hour
solar_minute = solar_datetime.minute
# 计算时辰序号
time_index = calculate_time_index(solar_hour, solar_minute)
return time_index, solar_hour, solar_minute
def calculate_time_index(hour: int, minute: int) -> int:
"""
根据小时和分钟计算时辰序号
时辰划分:
- 子时:23:00-01:00(分早子0和晚子12)
- 丑时:01:00-03:00 (1)
- 寅时:03:00-05:00 (2)
- 卯时:05:00-07:00 (3)
- 辰时:07:00-09:00 (4)
- 巳时:09:00-11:00 (5)
- 午时:11:00-13:00 (6)
- 未时:13:00-15:00 (7)
- 申时:15:00-17:00 (8)
- 酉时:17:00-19:00 (9)
- 戌时:19:00-21:00 (10)
- 亥时:21:00-23:00 (11)
Args:
hour: 小时 (0-23)
minute: 分钟 (0-59)
Returns:
时辰序号 (0-12)
"""
# 23:00-23:59 属于晚子时
if hour == 23:
return 12
# 00:00-00:59 属于早子时
if hour == 0:
return 0
# 01:00-22:59 按照每两小时一个时辰计算
# 丑时(1:00-2:59) -> 1, 寅时(3:00-4:59) -> 2, ...
return (hour + 1) // 2
def get_major_cities_longitude() -> dict:
"""
获取全球主要城市的经度数据
Returns:
城市名到经度的映射字典
数据来源:
- 基于WGS84坐标系
- 保留一位小数精度
- 涵盖全球200+个主要城市
"""
return {
# ========== 中国 ==========
# 直辖市
"北京": 116.4,
"上海": 121.5,
"天津": 117.2,
"重庆": 106.5,
# 省会城市(按拼音排序)
"长春": 125.3,
"长沙": 112.9,
"成都": 104.1,
"福州": 119.3,
"广州": 113.3,
"贵阳": 106.7,
"哈尔滨": 126.6,
"杭州": 120.2,
"合肥": 117.3,
"呼和浩特": 111.7,
"济南": 117.0,
"昆明": 102.7,
"拉萨": 91.1,
"兰州": 103.8,
"南昌": 115.9,
"南京": 118.8,
"南宁": 108.3,
"石家庄": 114.5,
"沈阳": 123.4,
"太原": 112.5,
"乌鲁木齐": 87.6,
"武汉": 114.3,
"西安": 108.9,
"西宁": 101.8,
"银川": 106.3,
"郑州": 113.6,
# 其他重要城市
"深圳": 114.1,
"厦门": 118.1,
"青岛": 120.4,
"大连": 121.6,
"宁波": 121.6,
"苏州": 120.6,
"无锡": 120.3,
"东莞": 113.8,
"珠海": 113.6,
"佛山": 113.1,
# 港澳台
"香港": 114.2,
"澳门": 113.5,
"台北": 121.5,
# ========== 亚洲其他国家 ==========
# 日本
"东京": 139.7,
"大阪": 135.5,
"京都": 135.8,
"横滨": 139.6,
"名古屋": 136.9,
"札幌": 141.4,
"福冈": 130.4,
# 韩国
"首尔": 127.0,
"釜山": 129.0,
"仁川": 126.7,
"大邱": 128.6,
# 新加坡
"新加坡": 103.8,
# 泰国
"曼谷": 100.5,
"清迈": 98.9,
"普吉": 98.4,
# 越南
"河内": 105.8,
"胡志明市": 106.7,
"岘港": 108.2,
# 马来西亚
"吉隆坡": 101.7,
"槟城": 100.3,
# 印度尼西亚
"雅加达": 106.8,
"巴厘岛": 115.2,
"泗水": 112.7,
# 菲律宾
"马尼拉": 121.0,
# 印度
"新德里": 77.2,
"孟买": 72.8,
"班加罗尔": 77.6,
"加尔各答": 88.4,
"清奈": 80.3,
# 巴基斯坦
"伊斯兰堡": 73.1,
"卡拉奇": 67.0,
# 阿联酋
"迪拜": 55.3,
"阿布扎比": 54.4,
# 以色列
"耶路撒冷": 35.2,
"特拉维夫": 34.8,
# 土耳其
"伊斯坦布尔": 28.9,
"安卡拉": 32.9,
# ========== 欧洲 ==========
# 英国
"伦敦": -0.1,
"曼彻斯特": -2.2,
"爱丁堡": -3.2,
"利物浦": -3.0,
# 法国
"巴黎": 2.3,
"马赛": 5.4,
"里昂": 4.8,
"尼斯": 7.3,
# 德国
"柏林": 13.4,
"慕尼黑": 11.6,
"法兰克福": 8.7,
"汉堡": 10.0,
"科隆": 7.0,
# 意大利
"罗马": 12.5,
"米兰": 9.2,
"威尼斯": 12.3,
"佛罗伦萨": 11.3,
"那不勒斯": 14.3,
# 西班牙
"马德里": -3.7,
"巴塞罗那": 2.2,
"瓦伦西亚": -0.4,
"塞维利亚": -5.9,
# 葡萄牙
"里斯本": -9.1,
"波尔图": -8.6,
# 荷兰
"阿姆斯特丹": 4.9,
"鹿特丹": 4.5,
# 比利时
"布鲁塞尔": 4.4,
# 瑞士
"苏黎世": 8.5,
"日内瓦": 6.1,
"伯尔尼": 7.4,
# 奥地利
"维也纳": 16.4,
# 希腊
"雅典": 23.7,
# 俄罗斯
"莫斯科": 37.6,
"圣彼得堡": 30.3,
"符拉迪沃斯托克": 131.9,
# 波兰
"华沙": 21.0,
# 捷克
"布拉格": 14.4,
# 瑞典
"斯德哥尔摩": 18.1,
# 挪威
"奥斯陆": 10.8,
# 丹麦
"哥本哈根": 12.6,
# 芬兰
"赫尔辛基": 24.9,
# ========== 北美洲 ==========
# 美国东部
"纽约": -74.0,
"波士顿": -71.1,
"华盛顿": -77.0,
"费城": -75.2,
"迈阿密": -80.2,
"亚特兰大": -84.4,
# 美国中部
"芝加哥": -87.6,
"休斯顿": -95.4,
"达拉斯": -96.8,
"奥斯汀": -97.7,
"丹佛": -104.9,
# 美国西部
"洛杉矶": -118.2,
"旧金山": -122.4,
"西雅图": -122.3,
"波特兰": -122.7,
"拉斯维加斯": -115.1,
"凤凰城": -112.1,
"圣迭戈": -117.2, # 美国加州圣迭戈(San Diego)
# 加拿大
"多伦多": -79.4,
"温哥华": -123.1,
"蒙特利尔": -73.6,
"渥太华": -75.7,
"卡尔加里": -114.1,
# 墨西哥
"墨西哥城": -99.1,
"坎昆": -86.9,
"瓜达拉哈拉": -103.3,
# ========== 南美洲 ==========
# 巴西
"圣保罗": -46.6,
"里约热内卢": -43.2,
"巴西利亚": -47.9,
# 阿根廷
"布宜诺斯艾利斯": -58.4,
# 智利
"圣地亚哥": -70.7,
# 秘鲁
"利马": -77.0,
# 哥伦比亚
"波哥大": -74.1,
# ========== 大洋洲 ==========
# 澳大利亚
"悉尼": 151.2,
"墨尔本": 144.9,
"布里斯班": 153.0,
"珀斯": 115.9,
"阿德莱德": 138.6,
"堪培拉": 149.1,
# 新西兰
"奥克兰": 174.8,
"惠灵顿": 174.8,
"基督城": 172.6,
# ========== 非洲 ==========
# 埃及
"开罗": 31.2,
# 南非
"约翰内斯堡": 28.0,
"开普敦": 18.4,
# 肯尼亚
"内罗毕": 36.8,
# 摩洛哥
"卡萨布兰卡": -7.6,
# 尼日利亚
"拉各斯": 3.4,
}
def get_longitude_by_city(city_name: str) -> float:
"""
根据城市名获取经度
Args:
city_name: 城市名称
Returns:
经度值
Raises:
ValueError: 如果城市不在数据库中
Examples:
>>> get_longitude_by_city("北京")
116.4
>>> get_longitude_by_city("上海")
121.5
"""
cities = get_major_cities_longitude()
if city_name not in cities:
raise ValueError(
f"城市 '{city_name}' 不在数据库中。" f"支持的城市:{', '.join(sorted(cities.keys()))}"
)
return cities[city_name]
def format_solar_time_info(beijing_time: datetime, longitude: float, city_name: str = None) -> str:
"""
格式化真太阳时信息(用于日志和调试)
Args:
beijing_time: 北京时间
longitude: 经度
city_name: 城市名(可选)
Returns:
格式化的信息字符串
Examples:
>>> bt = datetime(2000, 8, 16, 12, 0)
>>> print(format_solar_time_info(bt, 116.4, "北京"))
北京时间: 2000-08-16 12:00
出生地: 北京 (经度: 116.4°E)
时差: -14分钟
真太阳时: 2000-08-16 11:46
时辰: 午时 (序号: 6)
"""
solar_time = beijing_to_solar_time(beijing_time, longitude)
offset = calculate_solar_time_offset(longitude)
time_index, _, _ = adjust_time_index_for_solar_time(
beijing_time.hour, beijing_time.minute, longitude
)
# 时辰名称映射
time_names = {
0: "早子时",
1: "丑时",
2: "寅时",
3: "卯时",
4: "辰时",
5: "巳时",
6: "午时",
7: "未时",
8: "申时",
9: "酉时",
10: "戌时",
11: "亥时",
12: "晚子时",
}
location = f"{city_name} (经度: {longitude}°E)" if city_name else f"经度: {longitude}°E"
offset_str = f"+{offset}分钟" if offset > 0 else f"{offset}分钟"
return f"""北京时间: {beijing_time.strftime('%Y-%m-%d %H:%M')}
出生地: {location}
时差: {offset_str}
真太阳时: {solar_time.strftime('%Y-%m-%d %H:%M')}
时辰: {time_names[time_index]} (序号: {time_index})"""
if __name__ == "__main__":
# 测试示例
print("=" * 50)
print("真太阳时计算示例")
print("=" * 50)
test_time = datetime(2000, 8, 16, 12, 0)
test_cities = ["北京", "上海", "乌鲁木齐", "广州"]
for city in test_cities:
longitude = get_longitude_by_city(city)
print(f"\n{format_solar_time_info(test_time, longitude, city)}")
print("-" * 50)