
一个基于 Python + Tkinter 的桌面时间工具,集成实时时钟、月历、时间戳转换、倒计时、日期差计算和世界时钟,浅色主题设计,零外部依赖开箱即用。
datetime_viewer.pypython datetime_viewer.pypip install lunarcalendar安装后程序自动在时钟区域显示农历日期。
每秒自动刷新,窗口置顶按钮可让工具常驻桌面。
输入格式:
1785542425(秒)或 1785542425000(毫秒)2026-07-29 14:30:25输入格式:2026-12-31
输入格式:2026-07-29
同时显示 6 个城市的当前时间:
每秒自动更新。
┌──────────────────────────────────────────────────┐│ 14:30:25 2026年07月29日 星期三 📌置顶 ││ 农历 六月十五 ││ 第210天 | 第30周 | 距年末155天 ││ 秒: 1785542425 | 毫秒: 1785542425000 │├──────────────────────────────────────────────────┤│ │ ││ ◀ 2026年7月 ▶ [今天]│ [⏱时间戳] [⏳倒计时] [📅日期差]││ │ ││ 一 二 三 四 五 六 日│ 当前时间戳 ││ .. .. 1 2 3 4 5│ 1785542425 [复制] ││ 6 7 8 9 10 11 12│ ││ 13 14 15 16 17 18 19│ 时间戳 → 日期 ││ 20 21 22 23 24 25 26│ [输入框] [转换] ││ 27 28 [29] 30 31 │ ││ │ 日期 → 时间戳 ││ │ [输入框] [转换] │├──────────────────────────────────────────────────┤│ 🌍 世界时钟 ││ 北京 东京 伦敦 纽约 洛杉矶 悉尼 ││ 14:30 15:30 06:30 02:30 23:30 16:30 ││ 本地 UTC+9 UTC+0 UTC-4 UTC-7 UTC+10 │└──────────────────────────────────────────────────┘采用清新浅色主题:
#f8fafc | ||
#ffffff | ||
#e2e8f0 | ||
#1e293b | ||
#64748b | ||
#94a3b8 | ||
#3b82f6 | ||
#10b981 | ||
#ef4444 | ||
#e11d48 | ||
#3b82f6 | ||
#eff6ff | ||
#f1f5f9 |
世界时钟使用固定 UTC 偏移量,未处理夏令时。实际使用中:
pytz 扩展lunarcalendar 时会显示提示文字,不影响其他功能pip install lunarcalendarself.world_zones = [ ("北京", 8), ("东京", 9), ("伦敦", 0), ("纽约", -4), ("洛杉矶", -7), ("悉尼", 10),# 添加更多城市 ("柏林", 2), ("迪拜", 4), ("新加坡", 8),]在 _refresh_common_countdowns 方法中添加:
targets = [ ("今天下班 (18:00)", datetime(now.year, now.month, now.day, 18, 0, 0)), ("本周末", now + timedelta(days=(5 - now.weekday()) % 7)), ("元旦", datetime(now.year + 1, 1, 1)),# 添加自定义 ("春节", datetime(2027, 2, 6)), ("国庆", datetime(now.year, 10, 1)),]F_CLOCK = ("Segoe UI", 42, "bold") # 改为你喜欢的大小pip install pyinstallerpyinstaller --onefile --windowed --name "DateTimeViewer" datetime_viewer.py# -*- coding: utf-8 -*-"""时刻日期查看器 - 浅色主题 GUI功能:实时时钟、月历、时间戳工具、倒计时、世界时钟、日期差计算依赖:tkinter(内置)可选:pip install lunarcalendar"""import tkinter as tkfrom tkinter import ttk, messageboximport calendarimport timefrom datetime import datetime, timedelta, timezone# ══════ 浅色主题配色 ══════BG = "#f8fafc"CARD = "#ffffff"BORDER = "#e2e8f0"TEXT = "#1e293b"TEXT_SEC = "#64748b"TEXT_WEAK = "#94a3b8"ACCENT = "#3b82f6"GREEN = "#10b981"YELLOW = "#f59e0b"RED = "#ef4444"PINK = "#e11d48"BTN_BG = "#eff6ff"BTN_FG = "#1d4ed8"INPUT_BG = "#f1f5f9"TODAY_BG = "#3b82f6"TODAY_FG = "#ffffff"SELECT_BG = "#dbeafe"F_CLOCK = ("Segoe UI", 42, "bold")F_DATE = ("Microsoft YaHei", 13)F_TITLE = ("Microsoft YaHei", 11, "bold")F_BODY = ("Microsoft YaHei", 10)F_SMALL = ("Microsoft YaHei", 9)F_MONO = ("Consolas", 11)F_CAL = ("Microsoft YaHei", 10)F_CAL_HEAD = ("Microsoft YaHei", 9, "bold")classDateTimeViewer:def__init__(self): self.root = tk.Tk() self.root.title("时刻日期查看器") sw = self.root.winfo_screenwidth() sh = self.root.winfo_screenheight() w, h = min(sw - 80, 960), min(sh - 80, 680) self.root.geometry(f"{w}x{h}+{(sw-w)//2}+{(sh-h)//2}") self.root.configure(bg=BG) self.root.resizable(True, True) self.cal_year = datetime.now().year self.cal_month = datetime.now().month self.topmost = False self._build_ui() self._update_clock()def_card(self, parent, **kwargs):"""创建卡片容器""" f = tk.Frame(parent, bg=CARD, bd=1, relief="solid", highlightbackground=BORDER, highlightthickness=1, **kwargs)return fdef_build_ui(self):# 顶部时钟区 self._build_clock()# 中部主区域 mid = tk.Frame(self.root, bg=BG) mid.pack(fill="both", expand=True, padx=16, pady=(0, 8))# 左侧日历 self._build_calendar(mid)# 右侧工具区 self._build_tools(mid)# 底部世界时钟 self._build_world_clock()# ══════ 顶部时钟 ══════def_build_clock(self): clock_card = self._card(self.root) clock_card.pack(fill="x", padx=16, pady=(12, 8)) top_row = tk.Frame(clock_card, bg=CARD) top_row.pack(fill="x", padx=16, pady=(12, 4))# 时钟大字 self.lbl_time = tk.Label(top_row, text="00:00:00", font=F_CLOCK, bg=CARD, fg=TEXT) self.lbl_time.pack(side="left")# 右侧信息 info_frame = tk.Frame(top_row, bg=CARD) info_frame.pack(side="right", anchor="e") self.lbl_date = tk.Label(info_frame, text="", font=F_DATE, bg=CARD, fg=TEXT) self.lbl_date.pack(anchor="e") self.lbl_lunar = tk.Label(info_frame, text="", font=F_BODY, bg=CARD, fg=TEXT_SEC) self.lbl_lunar.pack(anchor="e") self.lbl_year_info = tk.Label(info_frame, text="", font=F_SMALL, bg=CARD, fg=TEXT_WEAK) self.lbl_year_info.pack(anchor="e")# 置顶按钮 self.pin_btn = tk.Button(top_row, text="📌 置顶", font=F_SMALL, bg=BTN_BG, fg=BTN_FG, relief="flat", padx=8, command=self._toggle_topmost) self.pin_btn.pack(side="right", padx=(0, 12))# ══════ 月历面板 ══════def_build_calendar(self, parent): left = self._card(parent) left.pack(side="left", fill="both", padx=(0, 6), pady=0, expand=False)# 月份导航 nav = tk.Frame(left, bg=CARD) nav.pack(fill="x", padx=12, pady=(10, 4)) tk.Button(nav, text="◀", font=F_BODY, bg=BTN_BG, fg=BTN_FG, relief="flat", width=3, command=self._prev_month).pack(side="left") self.lbl_month = tk.Label(nav, text="", font=F_TITLE, bg=CARD, fg=TEXT) self.lbl_month.pack(side="left", expand=True) tk.Button(nav, text="▶", font=F_BODY, bg=BTN_BG, fg=BTN_FG, relief="flat", width=3, command=self._next_month).pack(side="left") tk.Button(nav, text="今天", font=F_SMALL, bg=ACCENT, fg="#ffffff", relief="flat", padx=8, command=self._goto_today).pack(side="left", padx=(8, 0))# 星期头 week_frame = tk.Frame(left, bg=CARD) week_frame.pack(fill="x", padx=12, pady=(4, 0)) weekdays = ["一", "二", "三", "四", "五", "六", "日"]for i, d in enumerate(weekdays): color = PINK if i >= 5else TEXT_SEC tk.Label(week_frame, text=d, font=F_CAL_HEAD, bg=CARD, fg=color, width=4).grid(row=0, column=i, pady=2)# 日期格子 self.cal_frame = tk.Frame(left, bg=CARD) self.cal_frame.pack(fill="both", expand=True, padx=12, pady=(0, 10)) self.cal_labels = []for r in range(6): row = []for c in range(7): lbl = tk.Label(self.cal_frame, text="", font=F_CAL, bg=CARD, fg=TEXT, width=4, height=1) lbl.grid(row=r, column=c, pady=1, padx=1) row.append(lbl) self.cal_labels.append(row) self._refresh_calendar()def_refresh_calendar(self): self.lbl_month.configure(text=f"{self.cal_year}年 {self.cal_month}月") today = datetime.now() cal = calendar.Calendar(firstweekday=0) days = cal.monthdayscalendar(self.cal_year, self.cal_month)for r in range(6):for c in range(7): lbl = self.cal_labels[r][c]if r < len(days) and days[r][c] != 0: day = days[r][c] lbl.configure(text=str(day))# 今天高亮if (self.cal_year == today.year and self.cal_month == today.monthand day == today.day): lbl.configure(bg=TODAY_BG, fg=TODAY_FG)elif c >= 5: # 周末 lbl.configure(bg=CARD, fg=PINK)else: lbl.configure(bg=CARD, fg=TEXT)else: lbl.configure(text="", bg=CARD, fg=CARD)def_prev_month(self): self.cal_month -= 1if self.cal_month < 1: self.cal_month = 12 self.cal_year -= 1 self._refresh_calendar()def_next_month(self): self.cal_month += 1if self.cal_month > 12: self.cal_month = 1 self.cal_year += 1 self._refresh_calendar()def_goto_today(self): now = datetime.now() self.cal_year = now.year self.cal_month = now.month self._refresh_calendar()# ══════ 右侧工具区 ══════def_build_tools(self, parent): right = tk.Frame(parent, bg=BG) right.pack(side="right", fill="both", expand=True, padx=(6, 0)) notebook = ttk.Notebook(right) notebook.pack(fill="both", expand=True)# 配置 ttk 样式 style = ttk.Style() style.configure("TNotebook", background=BG) style.configure("TNotebook.Tab", background=BTN_BG, foreground=TEXT, padding=[12, 4], font=F_BODY) style.map("TNotebook.Tab", background=[("selected", CARD)], foreground=[("selected", ACCENT)])# Tab1: 时间戳工具 ts_frame = tk.Frame(notebook, bg=CARD) notebook.add(ts_frame, text=" ⏱ 时间戳 ") self._build_timestamp_tab(ts_frame)# Tab2: 倒计时 cd_frame = tk.Frame(notebook, bg=CARD) notebook.add(cd_frame, text=" ⏳ 倒计时 ") self._build_countdown_tab(cd_frame)# Tab3: 日期计算 dc_frame = tk.Frame(notebook, bg=CARD) notebook.add(dc_frame, text=" 📅 日期差 ") self._build_date_calc_tab(dc_frame)def_build_timestamp_tab(self, parent): px = 12 tk.Label(parent, text="当前时间戳", font=F_TITLE, bg=CARD, fg=TEXT).pack(anchor="w", padx=px, pady=(12, 4)) ts_row = tk.Frame(parent, bg=CARD) ts_row.pack(fill="x", padx=px, pady=4) self.lbl_ts = tk.Label(ts_row, text="", font=F_MONO, bg=INPUT_BG, fg=TEXT, padx=10, pady=4, relief="flat") self.lbl_ts.pack(side="left", fill="x", expand=True) tk.Button(ts_row, text="复制", font=F_SMALL, bg=BTN_BG, fg=BTN_FG, relief="flat", padx=8, command=self._copy_ts).pack(side="left", padx=(6, 0))# 时间戳转日期 tk.Label(parent, text="时间戳 → 日期", font=F_TITLE, bg=CARD, fg=TEXT).pack(anchor="w", padx=px, pady=(12, 4)) row1 = tk.Frame(parent, bg=CARD) row1.pack(fill="x", padx=px, pady=4) self.ts_input = tk.Entry(row1, font=F_MONO, bg=INPUT_BG, fg=TEXT, relief="flat", bd=1) self.ts_input.pack(side="left", fill="x", expand=True, ipady=3) self.ts_input.insert(0, str(int(time.time()))) tk.Button(row1, text="转换", font=F_SMALL, bg=ACCENT, fg="#ffffff", relief="flat", padx=10, command=self._ts_to_date).pack(side="left", padx=(6, 0)) self.lbl_ts_result = tk.Label(parent, text="", font=F_BODY, bg=CARD, fg=GREEN) self.lbl_ts_result.pack(anchor="w", padx=px, pady=4)# 日期转时间戳 tk.Label(parent, text="日期 → 时间戳", font=F_TITLE, bg=CARD, fg=TEXT).pack(anchor="w", padx=px, pady=(12, 4)) row2 = tk.Frame(parent, bg=CARD) row2.pack(fill="x", padx=px, pady=4) self.date_input = tk.Entry(row2, font=F_MONO, bg=INPUT_BG, fg=TEXT, relief="flat", bd=1) self.date_input.pack(side="left", fill="x", expand=True, ipady=3) self.date_input.insert(0, datetime.now().strftime("%Y-%m-%d %H:%M:%S")) tk.Button(row2, text="转换", font=F_SMALL, bg=ACCENT, fg="#ffffff", relief="flat", padx=10, command=self._date_to_ts).pack(side="left", padx=(6, 0)) self.lbl_date_result = tk.Label(parent, text="", font=F_BODY, bg=CARD, fg=GREEN) self.lbl_date_result.pack(anchor="w", padx=px, pady=4)def_build_countdown_tab(self, parent): px = 12 tk.Label(parent, text="自定义倒计时", font=F_TITLE, bg=CARD, fg=TEXT).pack(anchor="w", padx=px, pady=(12, 4)) row = tk.Frame(parent, bg=CARD) row.pack(fill="x", padx=px, pady=4) tk.Label(row, text="目标日期:", font=F_BODY, bg=CARD, fg=TEXT).pack(side="left") self.cd_input = tk.Entry(row, font=F_MONO, bg=INPUT_BG, fg=TEXT, relief="flat", bd=1, width=16) self.cd_input.pack(side="left", padx=6, ipady=3) self.cd_input.insert(0, f"{datetime.now().year}-12-31") tk.Button(row, text="计算", font=F_SMALL, bg=ACCENT, fg="#ffffff", relief="flat", padx=10, command=self._calc_countdown).pack(side="left") self.lbl_countdown = tk.Label(parent, text="", font=("Microsoft YaHei", 14, "bold"), bg=CARD, fg=ACCENT) self.lbl_countdown.pack(anchor="w", padx=px, pady=(8, 4))# 常用倒计时 tk.Label(parent, text="常用倒计时", font=F_TITLE, bg=CARD, fg=TEXT).pack(anchor="w", padx=px, pady=(12, 4)) self.countdown_list = tk.Frame(parent, bg=CARD) self.countdown_list.pack(fill="x", padx=px, pady=4) self._refresh_common_countdowns()def_build_date_calc_tab(self, parent): px = 12 tk.Label(parent, text="日期差计算", font=F_TITLE, bg=CARD, fg=TEXT).pack(anchor="w", padx=px, pady=(12, 4)) row1 = tk.Frame(parent, bg=CARD) row1.pack(fill="x", padx=px, pady=4) tk.Label(row1, text="日期A:", font=F_BODY, bg=CARD, fg=TEXT).pack(side="left") self.dateA_input = tk.Entry(row1, font=F_MONO, bg=INPUT_BG, fg=TEXT, relief="flat", bd=1, width=14) self.dateA_input.pack(side="left", padx=6, ipady=3) self.dateA_input.insert(0, datetime.now().strftime("%Y-%m-%d")) row2 = tk.Frame(parent, bg=CARD) row2.pack(fill="x", padx=px, pady=4) tk.Label(row2, text="日期B:", font=F_BODY, bg=CARD, fg=TEXT).pack(side="left") self.dateB_input = tk.Entry(row2, font=F_MONO, bg=INPUT_BG, fg=TEXT, relief="flat", bd=1, width=14) self.dateB_input.pack(side="left", padx=6, ipady=3) self.dateB_input.insert(0, (datetime.now() + timedelta(days=100)).strftime("%Y-%m-%d")) tk.Button(parent, text="计算日期差", font=F_BODY, bg=ACCENT, fg="#ffffff", relief="flat", padx=16, command=self._calc_date_diff).pack(anchor="w", padx=px, pady=(8, 4)) self.lbl_diff = tk.Label(parent, text="", font=("Microsoft YaHei", 12, "bold"), bg=CARD, fg=ACCENT) self.lbl_diff.pack(anchor="w", padx=px, pady=4)# 快捷操作 tk.Label(parent, text="快捷查询", font=F_TITLE, bg=CARD, fg=TEXT).pack(anchor="w", padx=px, pady=(16, 4)) quick_frame = tk.Frame(parent, bg=CARD) quick_frame.pack(fill="x", padx=px, pady=4)for text, days in [("30天后", 30), ("90天后", 90), ("180天后", 180), ("365天后", 365)]: target = (datetime.now() + timedelta(days=days)).strftime("%Y-%m-%d") tk.Label(quick_frame, text=f"{text}: {target}", font=F_SMALL, bg=INPUT_BG, fg=TEXT_SEC, padx=8, pady=3).pack(anchor="w", pady=2)# ══════ 底部世界时钟 ══════def_build_world_clock(self): world_card = self._card(self.root) world_card.pack(fill="x", padx=16, pady=(0, 12)) tk.Label(world_card, text="🌍 世界时钟", font=F_TITLE, bg=CARD, fg=TEXT).pack( anchor="w", padx=12, pady=(8, 4)) self.world_frame = tk.Frame(world_card, bg=CARD) self.world_frame.pack(fill="x", padx=12, pady=(0, 10)) self.world_zones = [ ("北京", 8), ("东京", 9), ("伦敦", 0), ("纽约", -4), ("洛杉矶", -7), ("悉尼", 10), ] self.world_labels = []for i, (city, offset) in enumerate(self.world_zones): cf = tk.Frame(self.world_frame, bg=INPUT_BG, padx=10, pady=6) cf.pack(side="left", padx=4, pady=2, fill="x", expand=True) tk.Label(cf, text=city, font=F_SMALL, bg=INPUT_BG, fg=TEXT_SEC).pack() lbl = tk.Label(cf, text="--:--", font=F_MONO, bg=INPUT_BG, fg=TEXT) lbl.pack() diff = offset - 8# 与北京时差 diff_str = f"UTC{offset:+d}"if offset != 8else"本地" tk.Label(cf, text=diff_str, font=("Microsoft YaHei", 8), bg=INPUT_BG, fg=TEXT_WEAK).pack() self.world_labels.append((lbl, offset))# ══════ 时钟更新 ══════def_update_clock(self): now = datetime.now()# 时间 self.lbl_time.configure(text=now.strftime("%H:%M:%S"))# 日期 weekdays = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"] self.lbl_date.configure(text=f"{now.strftime('%Y年%m月%d日')}{weekdays[now.weekday()]}")# 农历(尝试加载) lunar_text = self._get_lunar(now) self.lbl_lunar.configure(text=lunar_text)# 年度信息 day_of_year = now.timetuple().tm_yday week_of_year = int(now.strftime("%W")) days_left = (datetime(now.year, 12, 31) - now).days self.lbl_year_info.configure( text=f"第{day_of_year}天 | 第{week_of_year}周 | 距年末{days_left}天")# 时间戳 ts = int(now.timestamp()) ms_ts = int(now.timestamp() * 1000) self.lbl_ts.configure(text=f"秒: {ts} | 毫秒: {ms_ts}")# 世界时钟 utc_now = datetime.now(timezone.utc)for lbl, offset in self.world_labels: tz_time = utc_now + timedelta(hours=offset) lbl.configure(text=tz_time.strftime("%H:%M:%S")) self.root.after(1000, self._update_clock)def_get_lunar(self, dt):"""获取农历日期"""try:from lunarcalendar import Converter, Solar solar = Solar(dt.year, dt.month, dt.day) lunar = Converter.Solar2Lunar(solar) months = ["正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "冬", "腊"] days = ["初一", "初二", "初三", "初四", "初五", "初六", "初七", "初八", "初九", "初十","十一", "十二", "十三", "十四", "十五", "十六", "十七", "十八", "十九", "二十","廿一", "廿二", "廿三", "廿四", "廿五", "廿六", "廿七", "廿八", "廿九", "三十"] m = months[lunar.month - 1] d = days[lunar.day - 1] if lunar.day <= 30else str(lunar.day)returnf"农历 {m}月{d}"except ImportError:return"(安装 lunarcalendar 可显示农历)"except Exception:return""# ══════ 工具方法 ══════def_copy_ts(self): ts = str(int(time.time())) self.root.clipboard_clear() self.root.clipboard_append(ts)def_ts_to_date(self):try: ts = int(self.ts_input.get().strip())if ts > 9999999999: # 毫秒级 ts = ts // 1000 dt = datetime.fromtimestamp(ts) self.lbl_ts_result.configure(text=f"→ {dt.strftime('%Y-%m-%d %H:%M:%S')}", fg=GREEN)except Exception as e: self.lbl_ts_result.configure(text=f"错误: {e}", fg=RED)def_date_to_ts(self):try: text = self.date_input.get().strip() dt = datetime.strptime(text, "%Y-%m-%d %H:%M:%S") ts = int(dt.timestamp()) self.lbl_date_result.configure(text=f"→ {ts} (秒) | {ts*1000} (毫秒)", fg=GREEN)except Exception as e: self.lbl_date_result.configure(text=f"错误: {e}", fg=RED)def_calc_countdown(self):try: text = self.cd_input.get().strip() target = datetime.strptime(text, "%Y-%m-%d") now = datetime.now() delta = target - nowif delta.total_seconds() > 0: days = delta.days hours = delta.seconds // 3600 mins = (delta.seconds % 3600) // 60 self.lbl_countdown.configure( text=f"距 {text} 还有 {days}天 {hours}时 {mins}分", fg=ACCENT)else: days = abs(delta.days) self.lbl_countdown.configure(text=f"{text} 已过去 {days} 天", fg=RED)except Exception as e: self.lbl_countdown.configure(text=f"格式错误(请用 YYYY-MM-DD)", fg=RED)def_refresh_common_countdowns(self):for w in self.countdown_list.winfo_children(): w.destroy() now = datetime.now() targets = [ ("今天下班 (18:00)", datetime(now.year, now.month, now.day, 18, 0, 0)), ("本周末", now + timedelta(days=(5 - now.weekday()) % 7)), ("元旦", datetime(now.year + 1, 1, 1)), ]for name, target in targets: delta = target - nowif delta.total_seconds() > 0:if delta.days > 0: text = f"{name}:{delta.days}天"else: h = delta.seconds // 3600 m = (delta.seconds % 3600) // 60 text = f"{name}:{h}时{m}分" color = GREENelse: text = f"{name}:已过" color = TEXT_WEAK tk.Label(self.countdown_list, text=text, font=F_BODY, bg=CARD, fg=color).pack(anchor="w", pady=1)def_calc_date_diff(self):try: a = datetime.strptime(self.dateA_input.get().strip(), "%Y-%m-%d") b = datetime.strptime(self.dateB_input.get().strip(), "%Y-%m-%d") diff = abs((b - a).days) weeks = diff // 7 months_approx = diff / 30.44 self.lbl_diff.configure( text=f"相差 {diff} 天 (约 {weeks} 周 / {months_approx:.1f} 个月)", fg=ACCENT)except Exception: self.lbl_diff.configure(text="格式错误(请用 YYYY-MM-DD)", fg=RED)def_toggle_topmost(self): self.topmost = not self.topmost self.root.attributes("-topmost", self.topmost) self.pin_btn.configure( text="📌 已置顶"if self.topmost else"📌 置顶", bg=ACCENT if self.topmost else BTN_BG, fg="#ffffff"if self.topmost else BTN_FG)defrun(self): self.root.mainloop()if __name__ == "__main__": app = DateTimeViewer() app.run()MIT License