简介tkcalendar是一款面向Python开发者的开源日历小部件模块基于Tkinter界面库提供日历与日期输入两个组件。日期输入组件类似组合框下拉列表显示日历用户点选即可完成日期录入省去手工输入格式的麻烦该模块兼容Python 2和3可跟随系统语言环境显示并支持颜色自定义。本次分享的压缩包共29个文件体积约62KB其中12个Python源码文件实现组件核心功能10个RST文档涵盖安装、示例与接口说明另有多个YML、Makefile等构建配置辅助文件目录结构清晰便于直接阅读或二次开发。资源已有1902人浏览学习开发者可据此获取完整源码与文档研究Tkinter的扩展机制也能快速将日期选择能力集成到自己的桌面项目中。1. tkcalendarTkinter 生态里的开源日历组件一条 pip 命令替换手写控件tkcalendar 是 Tkinter 生态里少有的开源日历组件一条 pip install 命令就能把 Calendar 和 DateEntry 两个现成部件装进项目。我曾在某企业内部管理系统里画排期日历起初打算手写月历控件结果周末配色、跨月补位、禁用区间越写越失控换成 tkcalendar 后三天工作量缩到半天。它适合所有用 Tkinter 做桌面程序的开发者——考勤、预约、数据录入只要界面里需要日期选择这个开源软件能把几百行手写代码压成两个组件加几行绑定。接下来的章节按选型、参数、事件、业务改造、样式和踩坑的顺序把这份资源完整拆一遍。2. 选型与上手Calendar 和 DateEntry 的区别、参数与事件绑定2.1 Calendar月历视图和 selectmode 的用法先把环境装好。python -m pip install tkcalendar 会从 PyPI 拉取当前发布版本装完用 import 验证一遍import tkcalendar print(tkcalendar.__version__) # 输出当前安装版本形如 x.y.z版本号在这类组件里很关键——不同版本在 date_pattern 和 locale 行为上有细微差别锁定大版本再去查资料能少踩不少坑。Calendar 组件本身展示的是单个月份的网格顶部是星期表头和年份月份导航左右箭头翻月点击月份名还能下拉跳年。它适合用户需要「看着月份分布去点某一天」的场景比如排班、会议日程、打卡补录DateEntry 则适合纯粹录入日期的表单场景两者不冲突同一个界面完全可以同时用。初始化一个最简日历只需要几行from tkinter import Tk from tkcalendar import Calendar root Tk() cal Calendar(root, selectmodeday) cal.pack(fillboth, expandTrue) root.mainloop()不传 year/month/day 时日历默认定位到今天所在月份并高亮今天。selectmode 只有两个取值day 表示允许点选日期none 表示做成只读月历只能翻月不能选。需要强调一点tkcalendar 没有多选模式想做区间选择得靠两个组件或事件标记不要在 selectmode 上找这是绕不过去的边界。常用参数整理如下参数取值示例作用year / month / day2025 / 4 / 10初始定位日期selectmodeday / none是否允许点选firstweekdaysunday / monday一周从哪列开始showweeknumbersTrue / False是否显示 ISO 周序号showothermonthdaysTrue / False是否显示相邻月补位日期mindate / maxdatedatetime.date可选日期范围firstweekday 传的是字符串不是 0、6 这种数字写错类型会直接抛异常这是新手最常见的一个报错。showothermonthdays 设为 True 之后网格首尾会出现上个月和下个月的补位日期它们默认用浅色样式区分但选中逻辑和当月日期完全一致别按视觉颜色判断日期是否可选。2.2 DateEntry带下拉日历的日期输入框DateEntry 在界面上是一个文本框加一个小按钮点按钮弹出一个临时日历选完自动把格式化日期写回文本框。常见做法是把它放在表单里替代手输避免用户把 2025-04-10 写成 2025/4/10 这类格式混战。from tkinter import Tk from tkcalendar import DateEntry root Tk() de DateEntry( root, date_patternyyyy-mm-dd, year2025, month4, day10, width12, ) de.pack(padx10, pady10) def read_selected(event): print(de.get_date(), type(de.get_date())) de.bind(DateEntrySelected, read_selected) root.mainloop()date_pattern 控制文本框和下拉日历里日期的显示格式支持 yyyy、mm、dd 组合也能写 yyyy/mm/dd、dd/mm/yyyy甚至 yyyy年mm月dd日 这种带中文的写法。width 的单位是字符不是像素。state 设为 readonly 可以禁止手输让用户只能从下拉日历里选在数据录入类界面里配合 mindate 使用效果最好。一个容易误解的地方DateEntry 弹出日历默认显示的月份不是「今天」所在的月份而是组件当前值所在的月份。初始化时 month4点开弹出的就是 4 月。想让它默认弹今天直接不传 year/month/day或者每次用 set_date 把值刷新成今天。2.3 事件绑定CalendarSelected 与 DateEntrySelected 别绑错两个组件的事件名不一样Calendar 触发 DateEntry 触发 绑定错了回调永远不会执行而且不会报错。我常用的写法def on_calendar_pick(event): d cal.get_date() print(已选:, d.strftime(%Y-%m-%d)) cal.bind(CalendarSelected, on_calendar_pick)事件回调里用 get_date() 拿当前选中日期返回的是 datetime.date 对象可以直接参与运算比如算两个日期的差值或者加 30 天自动算出截止日。Calendar 还支持 和 翻月或跳年时触发适合做右侧列表联动——月份一变把当月待办事项重新加载一遍。提示事件名区分大小写Calendar 和 DateEntry 的事件不要互相混绑。混绑的表现是静默不触发调试时先打印事件名确认绑定生效再往下查。DateEntry 的下拉日历选完日期后会自动关闭并触发 。这里我习惯把读取操作写在事件回调里而不是依赖文本框内容因为文本框里可能残留用户手输的半截字符串。get_date() 读的是组件内部状态不是 Entry 显示文本这个概念后面几章会反复用到。3. 业务化改造日期事件标记、选择范围限制与视图跳转3.1 calevent 标记机制在日历上画业务事件Calendar 自带的 calevent 机制可以在具体日期下挂事件文本和颜色块业务上常用来标「有评审」「已排班」「截止日」。用法是建事件、绑标签、配样式三件事import datetime from tkcalendar import Calendar cal Calendar(root, selectmodeday) cal.pack() cal.calevent_create( datetime.date(2025, 4, 10), text项目评审, tags[meeting], foregroundwhite, background#e74c3c, ) cal.tag_config(meeting, foregroundwhite, background#e74c3c)calevent_create 在指定日期插入一条事件并返回事件 idtext 是显示文本tags 是标签可以传一个列表同一个日期能挂多个不同标签的事件。tag_config 用于统一配置某类标签的样式先 create 还是先 tag_config 都行但同一个标签的配置后调用的会覆盖先调用的。这里有一个隐藏得很深的接口坑calevent_create 返回的是事件 id但 calevent_remove 接收的却是标签而不是 id。想删单条事件不能拿返回值去删得给每条事件分配唯一标签再按标签清理。我第一次做动态生成事件清理时在这个接口语义上卡了半小时。cal.calevent_remove(meeting) # 删除所有带 meeting 标签的事件配合 calevent_get(datedatetime.date(2025, 4, 10)) 能拿到某一天的全部事件明细返回的是字典列表包含 text 和 tags 字段适合做日期面板联动点某一天右侧列表显示该天的所有事件。3.2 mindate / maxdate限制可选区间日期范围限制是业务刚需比如报销单只能选本月日期。Calendar 和 DateEntry 都支持 mindate、maxdate 参数传 datetime.date 对象from datetime import date from tkcalendar import Calendar cal Calendar( root, mindatedate(2025, 4, 1), maxdatedate(2025, 4, 30), )设置后范围外的日期在界面上变灰点击没反应。注意两个参数必须是 datetime.date传 2025-04-01 这种字符串会在内部做大小比较时报错。另一个容易忽略的点showothermonthdays 开启后网格里的补位日期只要落在范围内仍然可以正常选中它们视觉上显示的是相邻月份选择逻辑按真实日期走别按月份格子判断。DateEntry 同样支持 mindate / maxdate下拉日历里会自动置灰。但动态修改不能用 config——DateEntry 是 ttk.Entry 的扩展运行期配置和构造期参数是两套东西mindate 只认构造时传入。想动态收紧范围只能重建组件这个取舍放到第 6 章细说。3.3 视图跳转show_date、show_month 与 get_displayed_month业务经常需要把日历翻到指定月份。比如用户选了开始日期下一步希望日历自动切到结束日期所在月份。Calendar 提供了一组视图方法cal.show_date(datetime.date(2025, 7, 1)) # 跳到 7 月不改选中日期 cal.show_month(7) # 只改月份年份不动 cal.show_year(2026) # 只改年份月份不动show_date 接收一个 date 对象把视图切到目标日期所在月份。它不会改变当前选中日期也不会触发 只是「看过去」。show_month 和 show_year 适合做年月联动下拉框界面上放两个 Combobox一个选年一个选月回调里分别调用这两个方法比每次重建 Calendar 省心也不会丢选中状态。配合 get_displayed_month() 可以反查当前视图是几月。它返回 (year, month) 元组月份从 1 开始不是 0 开始这和 Python 标准库 calendar 模块的语义不一样写换算的时候别顺手减 1。视图控制与选中状态是两套逻辑先搞清楚这一点后面做联动才不容易乱。4. 样式定制与中文本地化配色参数、locale 与日期格式4.1 配色参数一套参数管住日历外观tkcalendar 把颜色全部暴露成构造参数不需要去改 ttk 主题。一套完整的配色大概长这样cal Calendar( root, background#ffffff, # 网格主体背景 foreground#2c3e50, # 普通日期文字 headersbackground#34495e, # 星期表头背景 headersforeground#ecf0f1, # 星期表头文字 selectbackground#3498db, # 选中日期背景 selectforeground#ffffff, # 选中日期文字 weekendbackground#fdf0f0, # 周末背景 weekendforeground#c0392b, # 周末文字 othermonthbackground#f5f5f5, # 补位日期背景 othermonthforeground#bdc3c7, # 补位日期文字 )这些参数成对出现foreground 管文字background 管背景维度分普通日期、选中态、表头、周末、补位日期五组。参数最终会转成内部样式写入 Calendar 的 ttk 风格里所以如果项目里套了其它 ttk 主题注意别让外层样式把实例参数盖掉。想全局统一可以在创建前配置 ttk.Style再让各实例的显式参数覆盖默认值。颜色参数还有一个习惯问题很多人只调 background然后发现「没生效」——实际上主体背景是变了但表头、选中块和周末区域颜色还占着多数视觉面积看起来像没动过。看效果要把反差最大的 selectbackground 一起调一眼就能确认参数到底吃没吃进去。4.2 locale 配置中文表头与月份名默认状态下日历表头是英文缩写月份名也是英文。加一个 locale 参数就能整体切换语言习惯cal Calendar(root, localezh_CN) de DateEntry(root, localezh_CN, date_patternyyyy年mm月dd日)locale 传系统地区码zh_CN 对应简体中文。设置后星期表头变成「一 二 三 四 五 六 日」的对应形式月份名变成「四月」这类中文表达。Windows 上一般直接可用Linux 上如果系统没生成 zh_CN 的 locale 数据会抛 LookupError: unknown locale这属于操作系统环境问题不是库的 bug。解决路径是补系统 locale 数据或者退回 localeC 用英文表头不要在代码里反复设 locale 变量期待能绕过系统。跨平台分发的程序我一般会在组件创建前包一个 try/exceptlocale 初始化失败就降级到英文宁可表头是英文也不能让程序起不来。4.3 date_pattern 与 textvariable显示格式和绑定变量的两侧date_pattern 是 DateEntry 的核心参数控制文本框里日期的最终形态。常见格式对照date_pattern显示效果yyyy-mm-dd2025-04-10yyyy/mm/dd2025/04/10dd/mm/yyyy10/04/2025yyyy年mm月dd日2025年04月10日格式标记是 tkcalendar 自定义的 yyyy、mm、dd 体系不是 Python strftime 的 %Y-%m-%d两者极易混用。写错大小写不会报错只会原样输出比如 date_patternYYYY-MM-DD文本框里就真的显示 YYYY-04-10排查时需要先怀疑格式串本身。DateEntry 还支持 textvariable 绑定 StringVar选中日期后变量自动更新为格式化字符串from tkinter import StringVar v StringVar() de DateEntry(root, textvariablev, date_patternyyyy-mm-dd)这里有个认知偏差textvariable 里存的是给人看的字符串get_date() 返回的是给代码用的 date 对象两条读取路径各自独立。用户手动改了文本框内容StringVar 会更新但组件内部记录的选择日期不会变此时 get_date() 返回的还是上一次从日历里选的值。想完全统一就把 DateEntry 设为 readonly 强制走日历选择否则就要做一次手工回填校验方向在第 5 章展开。5. 避坑排查tkcalendar 五个高频翻车现场5.1 get_date() 返回的是 date 对象不是字符串现象把 get_date() 的结果直接拼进 SQL 或 f-string 里控制台打印看着是 2025-04-10但某些地方报 unsupported format character 或类型错误。 原因get_date() 返回 datetime.date 对象print 显示和字符串很像但它不是 str拿去做字符串拼接、格式化、写入 JSON 时行为完全不同。 解决需要字符串时显式格式化f{d:%Y-%m-%d} 或 d.strftime(%Y-%m-%d)需要运算时保留 date 对象。我给自己定的规矩是——凡是和外部接口、数据库交互的日期一律先转字符串凡是内部计算的日期一律保留 date 对象两者用函数名区分不混用。5.2 date_pattern 写成 strftime 风格日期格式纹丝不动现象date_pattern%Y-%m-%d文本框里显示 %Y-%m-%d日期没替换进去。 原因date_pattern 走的是 tkcalendar 自己的 yyyy、mm、dd 标记不是 Python 的 %Y %m %d库没有做兼容转换。 解决改成 yyyy-mm-dd大小写也要严格YYYY、DD 不是合法标记会被当普通文本原样显示。升级库版本后先打印一次默认的 date_pattern 确认行为再定制不要靠记忆写。5.3 Linux 上 locale 抛 LookupError程序启动即崩现象Calendar(root, localezh_CN) 在部分机器上直接抛 LookupError: unknown locale: zh_CN窗口都起不来。 原因Python locale 模块依赖操作系统 locale 数据Linux 发行版默认可能没生成 zh_CN系统不认这个地区码库只是把异常透传出来。 解决先在本机执行 locale -a 看已生成的地区没有就补系统包或生成数据不想动系统的就切换 localeC 用英文表头或者包一层 try/except 降级。Windows 一般没这个问题跨平台项目必须做兜底不能假设所有机器都准备好了中文地区数据。5.4 改了 background 参数视觉上像没生效现象构造时传了 background#333333日历网格看起来还是亮底色。 原因background 管的是网格主体但选中块、表头、周末区域各有一套独立配色只改背景时这些区域占掉大部分视觉面积变化被淹没了。另一个常见误用是写 bg 简写这个库只认 background 全称参数名。 解决按「主体、表头、选中、周末」四组成套传参。验证参数是否生效时把 selectbackground 和 background 设成反差大的颜色重开后一秒就能分辨比盯着代码猜快得多。5.5 DateEntry 手输文本后 get_date() 返回旧值现象用户把文本框里的日期手改为 2025-08-01get_date() 返回的却是初始化或上次下拉选的日期。 原因DateEntry 内部维护独立的选择日期Entry 显示文本只是展示层的一部分。手输只更新了文本框字符串没有触发日历选中逻辑。 解决严格模式下把 state 设为 readonly只允许下拉选择必须兼容手输时加一个读取按钮用 datetime.strptime 解析文本框内容解析成功再 set_date 回填组件两端状态就一致了。这个解析函数我后来在多个工具里复用成了固定套路。6. 进阶技巧开始/结束日期联动的公共函数与校验兜底排期界面最常见的联动是「结束日期不能早于开始日期」。我一般用两个 DateEntry 加两个回调解决from tkinter import Tk, messagebox from tkcalendar import DateEntry root Tk() start_de DateEntry(root, date_patternyyyy-mm-dd, width12) end_de DateEntry(root, date_patternyyyy-mm-dd, width12) start_de.pack(sideleft, padx10) end_de.pack(sideleft, padx10) def on_start_selected(event): start start_de.get_date() if end_de.get_date() start: end_de.set_date(start) # 旧值早于开始日强制回填 def on_end_selected(event): start start_de.get_date() end end_de.get_date() if end start: end_de.set_date(start) messagebox.showwarning(日期范围, 结束日期不能早于开始日期) start_de.bind(DateEntrySelected, on_start_selected) end_de.bind(DateEntrySelected, on_end_selected) root.mainloop()回调逻辑分两步开始日期变化时如果已有结束日期早于新开始日期用 set_date 强制回填结束日期变化时再做一次同样的校验并弹提示。set_date 只更新组件值和文本框不触发 所以两个回调不会互相循环这一条我实测过。关于能不能让下拉日历里直接把非法日期置灰DateEntry 的 mindate 只支持构造时传入运行期没有公开的 setter。要实时置灰就得销毁重建整个 DateEntry焦点和当前显示月份都会丢性价比很低。我的取舍是放弃 UI 置灰把校验集中在回调与保存函数里逻辑单一也更好测试。保存前统一读取解析失败返回 None保证脏数据不会进库def read_range(): 返回 (开始, 结束)解析失败返回 None try: return start_de.get_date(), end_de.get_date() except ValueError: return None, None从那以后我每次接日期组件都会先花十分钟把读取、格式化、范围校验三个小函数写掉再谈界面联动顺序反了后面九成要返工重写。希望帮到你。本文还有配套的精品资源点击获取 SEO 优化官网定制响应式建站教育培训建站