python语言基于lunar-python库的节气计算器GUI软件代码QZQ
·
# -*- coding: utf-8 -*-
"""
节气计算器 GUI v1.0
- 所有文本框支持全选、复制、右键菜单
"""
import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext
from datetime import datetime
import math
# 尝试导入 lunar-python
try:
from lunar_python import Solar, Lunar
LUNAR_AVAILABLE = True
except ImportError:
LUNAR_AVAILABLE = False
# ============ 文本框右键菜单功能 ============
def create_context_menu(widget):
"""为文本控件创建右键菜单"""
menu = tk.Menu(widget, tearoff=0)
menu.add_command(label="全选", command=lambda: widget.event_generate("<<SelectAll>>"))
menu.add_command(label="复制", command=lambda: widget.event_generate("<<Copy>>"))
menu.add_command(label="全选并复制", command=lambda: select_all_and_copy(widget))
menu.add_separator()
menu.add_command(label="剪切", command=lambda: widget.event_generate("<<Cut>>"))
menu.add_command(label="粘贴", command=lambda: widget.event_generate("<<Paste>>"))
def show_menu(event):
menu.post(event.x_root, event.y_root)
widget.bind("<Button-3>", show_menu) # 右键
widget.bind("<Control-a>", lambda e: widget.event_generate("<<SelectAll>>"))
widget.bind("<Control-A>", lambda e: widget.event_generate("<<SelectAll>>"))
return menu
def select_all_and_copy(widget):
"""全选并复制文本"""
try:
# 先全选
widget.event_generate("<<SelectAll>>")
# 再复制
widget.event_generate("<<Copy>>")
except tk.TclError:
pass # 忽略可能的错误
return "break"
class CopyableEntry(ttk.Entry):
"""支持全选复制的 Entry"""
def __init__(self, master=None, **kwargs):
super().__init__(master, **kwargs)
create_context_menu(self)
# 额外绑定,确保 Entry 的 select all 有效
self.bind("<Control-a>", self.select_all)
self.bind("<Control-A>", self.select_all)
# 绑定全选并复制快捷键
self.bind("<Control-Shift-C>", self.select_all_and_copy)
self.bind("<Control-Shift-c>", self.select_all_and_copy)
def select_all(self, event=None):
self.select_range(0, tk.END)
self.icursor(tk.END)
return "break" # 阻止默认行为
def select_all_and_copy(self, event=None):
self.select_range(0, tk.END)
self.icursor(tk.END)
self.event_generate("<<Copy>>")
return "break"
class CopyableSpinbox(ttk.Spinbox):
"""支持全选复制的 Spinbox"""
def __init__(self, master=None, **kwargs):
super().__init__(master, **kwargs)
create_context_menu(self)
self.bind("<Control-a>", self.select_all)
self.bind("<Control-A>", self.select_all)
# 绑定全选并复制快捷键
self.bind("<Control-Shift-C>", self.select_all_and_copy)
self.bind("<Control-Shift-c>", self.select_all_and_copy)
def select_all(self, event=None):
self.select_range(0, tk.END)
self.icursor(tk.END)
return "break"
def select_all_and_copy(self, event=None):
self.select_range(0, tk.END)
self.icursor(tk.END)
self.event_generate("<<Copy>>")
return "break"
class CopyableScrolledText(scrolledtext.ScrolledText):
"""支持全选复制的 ScrolledText"""
def __init__(self, master=None, **kwargs):
super().__init__(master, **kwargs)
create_context_menu(self)
# ScrolledText 默认已有 Ctrl+C,补充 Ctrl+A
self.bind("<Control-a>", self.select_all)
self.bind("<Control-A>", self.select_all)
# 绑定全选并复制快捷键
self.bind("<Control-Shift-C>", self.select_all_and_copy)
self.bind("<Control-Shift-c>", self.select_all_and_copy)
def select_all(self, event=None):
self.tag_add(tk.SEL, "1.0", tk.END)
self.mark_set(tk.INSERT, tk.END)
self.see(tk.END)
return "break"
def select_all_and_copy(self, event=None):
self.tag_add(tk.SEL, "1.0", tk.END)
self.mark_set(tk.INSERT, tk.END)
self.see(tk.END)
self.event_generate("<<Copy>>")
return "break"
# ============ 数据类 ============
class JieQiResult:
def __init__(self, year, month, day, hour, minute, second, name, is_jie):
self.year = year
self.month = month
self.day = day
self.hour = hour
self.minute = minute
self.second = second
self.name = name
self.is_jie = is_jie
def __str__(self):
return f"{self.year}年{self.month:02d}月{self.day:02d}日 {self.hour:02d}:{self.minute:02d}:{self.second:02d} {self.name}"
def to_datetime(self):
return datetime(self.year, self.month, self.day, self.hour, self.minute, self.second)
# ============ 节气名称表 ============
JIEQI_NAMES = [
'冬至', '小寒', '大寒',
'立春', '雨水', '惊蛰',
'春分', '清明', '谷雨',
'立夏', '小满', '芒种',
'夏至', '小暑', '大暑',
'立秋', '处暑', '白露',
'秋分', '寒露', '霜降',
'立冬', '小雪', '大雪',
]
JIE_QI_TYPE = [
False, True, True,
True, False, True,
False, True, False,
True, False, True,
False, True, True,
True, False, True,
False, True, False,
True, False, True,
]
# 节气名称映射表(处理 lunar-python 可能返回的英文/拼音)
JIEQI_NAME_MAP = {
# 大写英文/拼音
'DONGZHI': '冬至', 'XIAOHAN': '小寒', 'DAHAN': '大寒',
'LICHUN': '立春', 'YUSHUI': '雨水', 'JINGZHE': '惊蛰',
'CHUNFEN': '春分', 'QINGMING': '清明', 'GUYU': '谷雨',
'LIXIA': '立夏', 'XIAOMAN': '小满', 'MANGZHONG': '芒种',
'XIAZHI': '夏至', 'XIAOSHU': '小暑', 'DASHU': '大暑',
'LIQIU': '立秋', 'CHUSHU': '处暑', 'BAILU': '白露',
'QIUFEN': '秋分', 'HANLU': '寒露', 'SHUANGJIANG': '霜降',
'LIDONG': '立冬', 'XIAOXUE': '小雪', 'DAXUE': '大雪',
# 小写英文/拼音
'dongzhi': '冬至', 'xiaohan': '小寒', 'dahan': '大寒',
'lichun': '立春', 'yushui': '雨水', 'jingzhe': '惊蛰',
'chunfen': '春分', 'qingming': '清明', 'guyu': '谷雨',
'lixia': '立夏', 'xiaoman': '小满', 'mangzhong': '芒种',
'xiazhi': '夏至', 'xiaoshu': '小暑', 'dashu': '大暑',
'liqiu': '立秋', 'chushu': '处暑', 'bailu': '白露',
'qiufen': '秋分', 'hanlu': '寒露', 'shuangjiang': '霜降',
'lidong': '立冬', 'xiaoxue': '小雪', 'daxue': '大雪',
# 其他可能变体
'DongZhi': '冬至', 'XiaoHan': '小寒', 'DaHan': '大寒',
'LiChun': '立春', 'YuShui': '雨水', 'JingZhe': '惊蛰',
'ChunFen': '春分', 'QingMing': '清明', 'GuYu': '谷雨',
'LiXia': '立夏', 'XiaoMan': '小满', 'MangZhong': '芒种',
'XiaZhi': '夏至', 'XiaoShu': '小暑', 'DaShu': '大暑',
'LiQiu': '立秋', 'ChuShu': '处暑', 'BaiLu': '白露',
'QiuFen': '秋分', 'HanLu': '寒露', 'ShuangJiang': '霜降',
'LiDong': '立冬', 'XiaoXue': '小雪', 'DaXue': '大雪',
}
def normalize_jieqi_name(name):
"""统一转换为中文节气名"""
if not name:
return None
if name in JIEQI_NAMES:
return name
mapped = JIEQI_NAME_MAP.get(name)
if mapped:
return mapped
lower_name = name.lower() if isinstance(name, str) else name
for key, val in JIEQI_NAME_MAP.items():
if key.lower() == lower_name:
return val
clean_name = name.replace('_', '').replace('-', '').replace(' ', '')
mapped = JIEQI_NAME_MAP.get(clean_name)
if mapped:
return mapped
print(f"警告:未识别的节气名称: {repr(name)}")
return name
def get_jieqi_index(name):
"""安全获取节气索引"""
normalized = normalize_jieqi_name(name)
if normalized in JIEQI_NAMES:
return JIEQI_NAMES.index(normalized)
return 0
# ============ 天文算法核心 ============
def gregorian_to_jd(year, month, day, hour=0, minute=0, second=0):
if month <= 2:
year -= 1
month += 12
a = int(year / 100)
b = 2 - a + int(a / 4)
if year < 1582 or (year == 1582 and month < 10) or (year == 1582 and month == 10 and day <= 4):
b = 0
jd = int(365.25 * (year + 4716)) + int(30.6001 * (month + 1)) + day + b - 1524.5
jd += (hour + minute / 60 + second / 3600) / 24
return jd
def jd_to_gregorian(jd):
jd += 0.5
z = int(jd)
f = jd - z
if z < 2299161:
a = z
else:
alpha = int((z - 1867216.25) / 36524.25)
a = z + 1 + alpha - int(alpha / 4)
b = a + 1524
c = int((b - 122.1) / 365.25)
d = int(365.25 * c)
e = int((b - d) / 30.6001)
day = b - d - int(30.6001 * e)
month = e - 1 if e < 14 else e - 13
year = c - 4716 if month > 2 else c - 4715
f *= 24
hour = int(f)
f = (f - hour) * 60
minute = int(f)
second = int((f - minute) * 60)
return year, month, day, hour, minute, second
def sun_longitude(jd):
t = (jd - 2451545.0) / 36525
l0 = 280.46646 + 36000.76983 * t + 0.0003032 * t * t
m = 357.52911 + 35999.05029 * t - 0.0001537 * t * t
e = 0.016708634 - 0.000042037 * t - 0.0000001267 * t * t
c = (1.914602 - 0.004817 * t - 0.000014 * t * t) * math.sin(math.radians(m))
c += (0.019993 - 0.000101 * t) * math.sin(math.radians(2 * m))
c += 0.000289 * math.sin(math.radians(3 * m))
true_l = l0 + c
omega = 125.04452 - 1934.136261 * t
nutation = -0.00478 * math.sin(math.radians(omega))
apparent_l = true_l + nutation - 0.00569 - 0.00479 * math.sin(math.radians(omega))
apparent_l = apparent_l % 360
if apparent_l < 0:
apparent_l += 360
return apparent_l
def find_solar_term(target_lon, start_jd, tolerance=0.00001):
jd = start_jd
for _ in range(50):
lon = sun_longitude(jd)
diff = (target_lon - lon) % 360
if diff > 180:
diff -= 360
if abs(diff) < tolerance:
return jd
step = diff / 0.9856
step = max(-5, min(5, step))
jd += step
return jd
# ============ lunar-python 精确计算 ============
def get_jieqi_by_lunar_python(year, month, day, hour=0, minute=0, second=0):
if not LUNAR_AVAILABLE:
return None
try:
solar = Solar.fromYmd(year, month, day)
lunar = solar.getLunar()
jie_qi = lunar.getJieQi()
if jie_qi:
jie_qi = normalize_jieqi_name(jie_qi)
jq_table = lunar.getJieQiTable()
if jie_qi in jq_table:
dt = jq_table[jie_qi]
idx = get_jieqi_index(jie_qi)
is_jie = JIE_QI_TYPE[idx]
return JieQiResult(
dt.getYear(), dt.getMonth(), dt.getDay(),
dt.getHour(), dt.getMinute(), dt.getSecond(),
jie_qi, is_jie
)
jq_table = lunar.getJieQiTable()
jq_list = lunar.getJieQiList()
target_dt = datetime(year, month, day, hour, minute, second)
closest = None
min_diff = float('inf')
for name in jq_list:
cn_name = normalize_jieqi_name(name)
if cn_name in jq_table:
dt = jq_table[cn_name]
jq_dt = datetime(dt.getYear(), dt.getMonth(), dt.getDay(),
dt.getHour(), dt.getMinute(), dt.getSecond())
diff = abs((jq_dt - target_dt).total_seconds())
if diff < min_diff and jq_dt <= target_dt:
min_diff = diff
closest = (dt, cn_name)
if closest:
dt, name = closest
idx = get_jieqi_index(name)
is_jie = JIE_QI_TYPE[idx]
return JieQiResult(dt.getYear(), dt.getMonth(), dt.getDay(),
dt.getHour(), dt.getMinute(), dt.getSecond(),
name, is_jie)
return None
except Exception as e:
print(f"lunar-python 计算错误: {e}")
return None
def get_all_jieqi_year_lunar_python(year):
if not LUNAR_AVAILABLE:
return None
results = []
seen_names = set() # 新增:用于去重
try:
solar = Solar.fromYmd(year, 1, 1)
lunar = solar.getLunar()
jq_table = lunar.getJieQiTable()
jq_list = lunar.getJieQiList()
for name in jq_list:
cn_name = normalize_jieqi_name(name)
# 跳过已处理的节气
if cn_name in seen_names:
continue
seen_names.add(cn_name)
if cn_name in jq_table:
dt = jq_table[cn_name]
idx = get_jieqi_index(cn_name)
is_jie = JIE_QI_TYPE[idx]
results.append(JieQiResult(
dt.getYear(), dt.getMonth(), dt.getDay(),
dt.getHour(), dt.getMinute(), dt.getSecond(),
cn_name, is_jie
))
return sorted(results, key=lambda x: x.to_datetime())
except Exception as e:
print(f"lunar-python 获取全年节气错误: {e}")
return None
# ============ 天文算法 ============
def get_jieqi_by_astronomy(year, month, day, hour=0, minute=0, second=0):
jd = gregorian_to_jd(year, month, day, hour, minute, second)
current_lon = sun_longitude(jd)
term_lon = 270
for i in range(24):
next_term_lon = term_lon + 15
if next_term_lon >= 360:
next_term_lon -= 360
if term_lon < next_term_lon:
in_range = term_lon <= current_lon < next_term_lon
else:
in_range = current_lon >= term_lon or current_lon < next_term_lon
if in_range:
search_jd = jd - 30
exact_jd = find_solar_term(term_lon, search_jd)
y, m, d, h, mi, s = jd_to_gregorian(exact_jd)
return JieQiResult(y, m, d, h, mi, s, JIEQI_NAMES[i], JIE_QI_TYPE[i])
term_lon = next_term_lon
return get_jieqi_by_astronomy_approx(year, month, day, hour, minute, second)
def get_jieqi_by_astronomy_approx(year, month, day, hour=0, minute=0, second=0):
jd = gregorian_to_jd(year, month, day, hour, minute, second)
dongzhi_jd = gregorian_to_jd(year, 12, 21, 0, 0, 0)
exact_dongzhi = find_solar_term(270, dongzhi_jd - 5)
results = []
for i in range(24):
target_lon = 270 + i * 15
target_lon = target_lon % 360
search_jd = exact_dongzhi + i * 15.2 - 2
exact_jd = find_solar_term(target_lon, search_jd)
y, m, d, h, mi, s = jd_to_gregorian(exact_jd)
results.append(JieQiResult(y, m, d, h, mi, s, JIEQI_NAMES[i], JIE_QI_TYPE[i]))
target_dt = datetime(year, month, day, hour, minute, second)
closest = None
min_diff = float('inf')
for r in results:
r_dt = r.to_datetime()
if r_dt <= target_dt:
diff = (target_dt - r_dt).total_seconds()
if diff < min_diff:
min_diff = diff
closest = r
return closest if closest else results[0]
def get_all_jieqi_year_astronomy(year):
results = []
search_jd = gregorian_to_jd(year, 1, 1) - 10
dongzhi_jd = find_solar_term(270, search_jd)
y, m, d, h, mi, s = jd_to_gregorian(dongzhi_jd)
if y < year:
start_jd = dongzhi_jd
start_idx = 0
else:
start_jd = find_solar_term(255, search_jd - 15)
start_idx = 23
for i in range(24):
idx = (start_idx + i) % 24
target_lon = 270 + idx * 15
target_lon = target_lon % 360
if i > 0:
search_start = current_jd + 13
else:
search_start = start_jd
exact_jd = find_solar_term(target_lon, search_start)
y, m, d, h, mi, s = jd_to_gregorian(exact_jd)
results.append(JieQiResult(y, m, d, h, mi, s, JIEQI_NAMES[idx], JIE_QI_TYPE[idx]))
current_jd = exact_jd
year_results = [r for r in results if r.year == year]
return year_results
# ============ 统一接口 ============
def get_current_jieqi(year, month, day, hour=0, minute=0, second=0):
if year < 0 or year > 9999:
raise ValueError("年份必须在 0-9999 范围内")
if LUNAR_AVAILABLE and 1900 <= year <= 2100:
result = get_jieqi_by_lunar_python(year, month, day, hour, minute, second)
if result:
dt_input = datetime(year, month, day, hour, minute, second)
dt_jieqi = result.to_datetime()
if dt_jieqi <= dt_input:
return result
return get_jieqi_by_astronomy(year, month, day, hour, minute, second)
def get_next_jieqi(year, month, day, hour=0, minute=0, second=0):
current = get_current_jieqi(year, month, day, hour, minute, second)
idx = JIEQI_NAMES.index(current.name)
next_idx = (idx + 1) % 24
jd = gregorian_to_jd(year, month, day, hour, minute, second)
target_lon = 270 + next_idx * 15
target_lon = target_lon % 360
search_jd = jd + 5
next_jd = find_solar_term(target_lon, search_jd)
y, m, d, h, mi, s = jd_to_gregorian(next_jd)
return JieQiResult(y, m, d, h, mi, s, JIEQI_NAMES[next_idx], JIE_QI_TYPE[next_idx])
def get_prev_jieqi(year, month, day, hour=0, minute=0, second=0):
current = get_current_jieqi(year, month, day, hour, minute, second)
idx = JIEQI_NAMES.index(current.name)
prev_idx = (idx - 1) % 24
jd = gregorian_to_jd(year, month, day, hour, minute, second)
target_lon = 270 + prev_idx * 15
target_lon = target_lon % 360
search_jd = jd - 20
prev_jd = find_solar_term(target_lon, search_jd)
y, m, d, h, mi, s = jd_to_gregorian(prev_jd)
return JieQiResult(y, m, d, h, mi, s, JIEQI_NAMES[prev_idx], JIE_QI_TYPE[prev_idx])
def get_all_jieqi_in_year(year):
if year < 0 or year > 9999:
raise ValueError("年份必须在 0-9999 范围内")
if LUNAR_AVAILABLE and 1900 <= year <= 2100:
results = get_all_jieqi_year_lunar_python(year)
if results and len(results) >= 24:
return results
return get_all_jieqi_year_astronomy(year)
def get_jieqi_in_range(start_year, start_month, start_day, end_year, end_month, end_day):
results = []
current_year = start_year
while current_year <= end_year:
year_jieqi = get_all_jieqi_in_year(current_year)
for jq in year_jieqi:
dt = jq.to_datetime()
start_dt = datetime(start_year, start_month, start_day)
end_dt = datetime(end_year, end_month, end_day, 23, 59, 59)
if start_dt <= dt <= end_dt:
results.append(jq)
current_year += 1
return results
def is_jieqi_day(year, month, day):
if LUNAR_AVAILABLE and 1900 <= year <= 2100:
try:
solar = Solar.fromYmd(year, month, day)
lunar = solar.getLunar()
jieqi = lunar.getJieQi()
return jieqi is not None and normalize_jieqi_name(jieqi) is not None
except:
pass
jieqi_list = get_all_jieqi_in_year(year)
for jq in jieqi_list:
if jq.year == year and jq.month == month and jq.day == day:
return True
return False
# ============ GUI 界面 ============
class JieQiApp:
def __init__(self, root):
self.root = root
self.root.title("节气计算器 v4.0 - 基于 lunar-python")
self.root.geometry("950x750")
self.status_var = tk.StringVar()
if LUNAR_AVAILABLE:
self.status_var.set("lunar-python 已加载 | 1900-2100年精确 | 其他年份天文算法")
else:
self.status_var.set("lunar-python 未安装 | 使用纯天文算法 | pip install lunar-python")
self.create_widgets()
def create_widgets(self):
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
main_frame.columnconfigure(1, weight=1)
main_frame.rowconfigure(2, weight=1)
# === 输入区域 ===
input_frame = ttk.LabelFrame(main_frame, text="输入时间", padding="10")
input_frame.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=5)
# 日期
ttk.Label(input_frame, text="年:").grid(row=0, column=0, padx=5)
self.year_var = tk.StringVar(value=str(datetime.now().year))
CopyableSpinbox(input_frame, from_=0, to=9999, textvariable=self.year_var, width=6).grid(row=0, column=1, padx=5)
ttk.Label(input_frame, text="月:").grid(row=0, column=2, padx=5)
self.month_var = tk.StringVar(value=str(datetime.now().month))
CopyableSpinbox(input_frame, from_=1, to=12, textvariable=self.month_var, width=4).grid(row=0, column=3, padx=5)
ttk.Label(input_frame, text="日:").grid(row=0, column=4, padx=5)
self.day_var = tk.StringVar(value=str(datetime.now().day))
CopyableSpinbox(input_frame, from_=1, to=31, textvariable=self.day_var, width=4).grid(row=0, column=5, padx=5)
# 时间
ttk.Label(input_frame, text="时:").grid(row=0, column=6, padx=5)
self.hour_var = tk.StringVar(value="12")
CopyableSpinbox(input_frame, from_=0, to=23, textvariable=self.hour_var, width=4, format="%02.0f").grid(row=0, column=7, padx=5)
ttk.Label(input_frame, text="分:").grid(row=0, column=8, padx=5)
self.minute_var = tk.StringVar(value="00")
CopyableSpinbox(input_frame, from_=0, to=59, textvariable=self.minute_var, width=4, format="%02.0f").grid(row=0, column=9, padx=5)
ttk.Label(input_frame, text="秒:").grid(row=0, column=10, padx=5)
self.second_var = tk.StringVar(value="00")
CopyableSpinbox(input_frame, from_=0, to=59, textvariable=self.second_var, width=4, format="%02.0f").grid(row=0, column=11, padx=5)
# === 按钮区域 ===
btn_frame = ttk.Frame(main_frame)
btn_frame.grid(row=1, column=0, columnspan=2, pady=10)
ttk.Button(btn_frame, text="查询当前节气", command=self.query_current).grid(row=0, column=0, padx=5)
ttk.Button(btn_frame, text="查询全年节气", command=self.query_year).grid(row=0, column=1, padx=5)
ttk.Button(btn_frame, text="查询范围节气", command=self.query_range).grid(row=0, column=2, padx=5)
ttk.Button(btn_frame, text="使用当前时间", command=self.use_now).grid(row=0, column=3, padx=5)
ttk.Button(btn_frame, text="清空结果", command=self.clear).grid(row=0, column=4, padx=5)
# === 结果显示区域 ===
result_frame = ttk.LabelFrame(main_frame, text="计算结果", padding="10")
result_frame.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
result_frame.columnconfigure(0, weight=1)
result_frame.rowconfigure(0, weight=1)
self.result_text = CopyableScrolledText(result_frame, wrap=tk.WORD, width=85, height=35, font=('微软雅黑', 10))
self.result_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# === 状态栏 ===
status_bar = ttk.Label(main_frame, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
status_bar.grid(row=3, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=5)
def get_input_datetime(self):
try:
year = int(self.year_var.get())
month = int(self.month_var.get())
day = int(self.day_var.get())
hour = int(self.hour_var.get())
minute = int(self.minute_var.get())
second = int(self.second_var.get())
if not (0 <= year <= 9999 and 1 <= month <= 12 and 1 <= day <= 31):
raise ValueError("日期范围错误")
if not (0 <= hour <= 23 and 0 <= minute <= 59 and 0 <= second <= 59):
raise ValueError("时间范围错误")
return year, month, day, hour, minute, second
except ValueError as e:
messagebox.showerror("输入错误", f"请输入有效的日期时间: {e}")
return None
def query_current(self):
dt = self.get_input_datetime()
if not dt:
return
year, month, day, hour, minute, second = dt
try:
self.result_text.delete(1.0, tk.END)
self.result_text.insert(tk.END, f"{'='*60}\n")
self.result_text.insert(tk.END, f"查询时间: {year}年{month:02d}月{day:02d}日 {hour:02d}:{minute:02d}:{second:02d}\n")
self.result_text.insert(tk.END, f"{'='*60}\n\n")
# 当前节气
current = get_current_jieqi(year, month, day, hour, minute, second)
self.result_text.insert(tk.END, f"【当前所在节气】\n")
self.result_text.insert(tk.END, f" {current}\n")
self.result_text.insert(tk.END, f" 类型: {'节' if current.is_jie else '气'}\n\n")
# 上一个
prev = get_prev_jieqi(year, month, day, hour, minute, second)
self.result_text.insert(tk.END, f"【上一个节气】\n {prev}\n\n")
# 下一个
next_jq = get_next_jieqi(year, month, day, hour, minute, second)
self.result_text.insert(tk.END, f"【下一个节气】\n {next_jq}\n\n")
# 当天是否有节气
if is_jieqi_day(year, month, day):
self.result_text.insert(tk.END, f"★ 当天就是节气日!\n\n")
# 对比
if LUNAR_AVAILABLE and 1900 <= year <= 2100:
lunar_result = get_jieqi_by_lunar_python(year, month, day, hour, minute, second)
if lunar_result:
self.result_text.insert(tk.END, f"【lunar-python 精确值】\n {lunar_result}\n")
diff = abs((current.to_datetime() - lunar_result.to_datetime()).total_seconds() / 60)
self.result_text.insert(tk.END, f" 天文算法误差: {diff:.1f}分钟\n")
else:
self.result_text.insert(tk.END, f"【计算方式】\n 使用天文算法(年份超出精确库范围)\n")
self.result_text.insert(tk.END, f"\n{'='*60}\n")
except Exception as e:
messagebox.showerror("计算错误", str(e))
def query_year(self):
try:
year = int(self.year_var.get())
if not (0 <= year <= 9999):
raise ValueError("年份必须在 0-9999 范围内")
except ValueError as e:
messagebox.showerror("输入错误", str(e))
return
try:
self.result_text.delete(1.0, tk.END)
self.result_text.insert(tk.END, f"{'='*60}\n")
self.result_text.insert(tk.END, f"{year}年 二十四节气表\n")
self.result_text.insert(tk.END, f"{'='*60}\n\n")
results = get_all_jieqi_in_year(year)
for i, r in enumerate(results, 1):
jie_qi = "节" if r.is_jie else "气"
self.result_text.insert(tk.END, f"{i:2d}. {r.name:4s} ({jie_qi}) {r}\n")
self.result_text.insert(tk.END, f"\n{'='*60}\n")
self.result_text.insert(tk.END, f"共 {len(results)} 个节气\n")
if LUNAR_AVAILABLE and 1900 <= year <= 2100:
self.result_text.insert(tk.END, f"数据来源: lunar-python 精确数据\n")
else:
self.result_text.insert(tk.END, f"数据来源: 天文算法计算\n")
self.result_text.insert(tk.END, f"{'='*60}\n")
except Exception as e:
messagebox.showerror("计算错误", str(e))
def query_range(self):
"""查询日期范围内的节气"""
# 弹出对话框输入范围
dialog = tk.Toplevel(self.root)
dialog.title("查询日期范围")
dialog.geometry("300x200")
dialog.transient(self.root)
dialog.grab_set()
ttk.Label(dialog, text="开始日期 (年-月-日):").grid(row=0, column=0, padx=5, pady=5)
start_entry = CopyableEntry(dialog, width=15)
start_entry.grid(row=0, column=1, padx=5, pady=5)
start_entry.insert(0, f"{self.year_var.get()}-{self.month_var.get()}-{self.day_var.get()}")
ttk.Label(dialog, text="结束日期 (年-月-日):").grid(row=1, column=0, padx=5, pady=5)
end_entry = CopyableEntry(dialog, width=15)
end_entry.grid(row=1, column=1, padx=5, pady=5)
end_entry.insert(0, f"{self.year_var.get()}-12-31")
def do_query():
try:
s = start_entry.get().split('-')
e = end_entry.get().split('-')
sy, sm, sd = int(s[0]), int(s[1]), int(s[2])
ey, em, ed = int(e[0]), int(e[1]), int(e[2])
dialog.destroy()
self.result_text.delete(1.0, tk.END)
self.result_text.insert(tk.END, f"{'='*60}\n")
self.result_text.insert(tk.END, f"日期范围: {sy}-{sm:02d}-{sd:02d} 至 {ey}-{em:02d}-{ed:02d}\n")
self.result_text.insert(tk.END, f"{'='*60}\n\n")
results = get_jieqi_in_range(sy, sm, sd, ey, em, ed)
for r in results:
jie_qi = "节" if r.is_jie else "气"
self.result_text.insert(tk.END, f"{r.name:4s} ({jie_qi}) {r}\n")
self.result_text.insert(tk.END, f"\n{'='*60}\n")
self.result_text.insert(tk.END, f"共 {len(results)} 个节气\n")
self.result_text.insert(tk.END, f"{'='*60}\n")
except Exception as e:
messagebox.showerror("错误", str(e), parent=dialog)
ttk.Button(dialog, text="查询", command=do_query).grid(row=2, column=0, columnspan=2, pady=20)
def use_now(self):
now = datetime.now()
self.year_var.set(str(now.year))
self.month_var.set(str(now.month))
self.day_var.set(str(now.day))
self.hour_var.set(f"{now.hour:02d}")
self.minute_var.set(f"{now.minute:02d}")
self.second_var.set(f"{now.second:02d}")
def clear(self):
self.result_text.delete(1.0, tk.END)
def main():
root = tk.Tk()
app = JieQiApp(root)
root.mainloop()
if __name__ == "__main__":
main()
更多推荐
所有评论(0)