
Hi,大家好!
我的 Access 日期选择器开源项目,这次又更新了。最近,我热衷于做一些开源工具。
虽然现在Access的日期选择器已经很好用了(好像日期选择器好像微软也要更新了,就是不知道具体的时间),但是在实际业务中,单一的日期选择往往无法满足各种不同的需求。比如订单要选下单日期,报表只想选一个月份,统计要按周看,排班只需要时分秒,查询还经常需要一段起止时间。如果每一种都让用户自己输入,格式和操作又得分别处理。
所以,这次我把项目里的选择方式补齐了:日期、日期时间、年份、年月、周、独立时间、日期范围、日期时间范围,一共八种入口。
这里说的补齐,是把这八类常用需求做进同一个项目,不是说它能代替所有业务里的日历规则。比如周从哪天开始、查询结束日期算到哪里,仍然需要与业务约定一致。
项目地址先放这里:
https://github.com/miaowei2/access-datepicker
如果只是给订单选一个日期,Access 自带的日期选择器就很合适。不需要导入模块,也不用生成额外的选择器窗体,设置好日期字段和文本框属性就能用。
这里对比的是桌面 Access 中常见的文本框内置日期选择器,具体外观以你使用的版本为准,不把将来可能更新的功能算进来。
下面先看 Access 自带的日期选择界面:

它主要解决的是「为这个输入框选一个日期」。日期/时间字段本身可以存时间,但能存时间,不等于弹出的日历里就有时、分、秒列表。这两件事要分开看。
我的项目补充的是另一类需求:有时候只要一个年份,有时候要一个月份、一整周,或者一段带时分秒的范围。把这些需求都塞进单日期输入,后面还得加自己的代码和操作规则。
| 对比内容 | Access 自带日期选择器 | 我的日期选择器项目 |
|---|
| 选择某一天 | 内置支持,普通日期输入更省事 | 支持,也可以同时选择时分 |
| 只选年份或年月 | 日历最终选的是具体日期,年份或年月需另外处理 | 有独立年份、年月入口,不必先选某一天 |
| 选择一整周 | 先选日期,再由业务代码换算所属周 | 整周预览、单击选择,按项目约定的周规则回写 |
| 只选时分秒 | 不提供专门的时分秒列表,需要另做输入界面 | 独立时间面板,支持到秒 |
| 选择起止范围 | 一般用两个日期输入框分别选择,再处理起止关系 | 双月范围选择,可统一回写两个端点 |
| 界面调整 | 使用 Access 提供的内置日历界面 | 源码和生成窗体可按项目需要调整 |
| 接入工作量 | 配置文本框即可,不用额外导入选择器模块 | 需要导入对应模块、生成窗体并绑定调用 |
下面是我的项目,把不同选择方式放在同一个 Demo 中,方便直接试用:
比如月度销售报表,用户要选的是「2026 年 10 月」,并不是 10 月的某一天。用年月面板,输入含义就直接对应业务;用自带日期选择器也能通过取年份、月份实现,但这部分规则要自己补。
再比如查询 10 月 1 日到 10 月 19 日的订单。两个自带日期输入框当然也能做,选择器负责分别选日,业务代码负责检查开始不能晚于结束。项目里的双月范围面板,则把区间预览和两端选择放在一起处理。
因此,我不会建议把系统里所有自带日历都换掉。普通单日期字段继续用内置的;遇到年月、周、时间或范围选择,再按需接入项目里的对应模块。

当前示例数据库里有一个 Demo 窗体,八个入口按日期、日期时间、年份、月份、周、时间、日期范围、时间范围的顺序排列。下载后直接打开这个窗体,就能逐个试。
这几个入口各自解决什么问题,可以先看这张表。
| 选择方式 | 主要内容 | 适合的场景 |
|---|
| 日期 | 选年月日,不带时间 | 生日、业务日期 |
| 日期时间 | 选日期和时分 | 需要记录到分钟的时间 |
| 年份 | 独立年份网格 | 年度报表、年度计划 |
| 年月 | 切换年份后选择月份 | 月度结算、按月查询 |
| 周 | 一次选择完整的一周 | 周统计、周计划 |
| 独立时间 | 时、分、秒 | 上班时间、预约时刻 |
| 日期范围 | 选择起止日期 | 按日期区间查询 |
| 日期时间范围 | 两端日期加时分秒 | 操作日志、精确时段查询 |
其中,年份和年月共用一个模块、一个选择器窗体;日期和日期时间也共用一个模块,通过参数决定是否显示时间。八种入口不需要八套模块。
项目继续使用纯 VBA 和 Access 原生窗体,不需要额外注册日历 OCX,也不需要 WebBrowser、网页资源或外部控件库。
对要部署到多台电脑上的 Access 项目来说,这种做法比较省事:需要哪种就导入哪种,生成对应窗体,不必为了一个日期输入框再带上一套额外组件。
有些输入框的业务含义,本来就不是一个完整日期。
比如年度报表,用户只想选 2026 年,让他先选到某个月某一天,最后再从日期里取年份,操作上就多绕了一步。
这次年份入口可以直接显示年份网格,翻页选年,也可以选择今年。默认写回四位年份文本,例如 2026。
年月入口则是四列三行的月份面板。先确定年份,再点月份,默认可以写成 2026-10。对于月报筛选,比先选一个具体日期再解释「只取这个月」更直接。
独立时间也单独做了。它是时、分、秒三列,可以选择到秒;调整列表时先更新预览,点「确定」或按 Enter 才写回。「现在」用于更新预览,不会直接替你提交。
不过,只选时间和修改完整日期时间不是一回事。
独立时间选择器默认回写只含时间部分的 Date 值。原值即使带有日期,确认后也不会替你保留那一天。所以它适合上班时刻之类的字段,不要直接拿来修改必须保留原日期的业务记录。





日期范围使用双月日历。第一次点击选起点,移动鼠标可以预览区间,第二次点击选终点并自动确认。
可以选同一天,也可以反向选,结果会按先后整理;切换月份或年份后,还能继续选择跨月、跨年的范围,不局限于眼前显示的两个月。
日期时间范围也是双月日历,但多了两端完整的时间选择,而且精确到秒。
它不会在第二次选日后立刻提交。日期选好之后,还可以继续调整开始、结束的时分秒,最后点「确定」或按 Enter 才回写。否则刚选完两天,窗体就关了,时间反而没机会改。
下面可以对照看日期范围和日期时间范围:前者主要选起止日期,后者还需要调整两端的时分秒。
反向选择时,处理的是完整的日期时间端点,不是只把日期交换一下、把时间留在原处。同一天也可以选择不同的起止时间,确认时会再按完整日期时间排序。
范围有两种回写方式:一个文本框显示整段范围,或者两个文本框分别保存起点和终点。
如果只是让用户看,可以用一个未绑定文本框显示;如果后面要查询、比较或保存到业务表里,我更建议使用两个日期/时间字段,分别存开始值和结束值。
整段 开始 ~ 结束 是一段文本,不能塞进单个日期/时间字段里。


周选择器不是选中某一天以后只保存那一天。鼠标移入时会预览整周,单击这周中的任意一天,就选择这一整周。
这里采用的规则是:周日到周六,包含 1 月 1 日的一周算新年第 1 周,不采用 ISO 周编号。
例如,2026-40周 对应 2026 年 9 月 27 日到 10 月 3 日。2020 年 12 月 27 日到 2021 年 1 月 2 日,则统一属于 2021-01周。
所以,不能把这个周标签直接当成其他系统里的 ISO 周。要做周报对接,先确认双方规则相同,不能只看都是「第几周」就直接拼起来。
默认回写的是 2026-40周 这样的文本。需要用日期字段保存时,也可以选择回写该周周日的 Date 值,再由它计算周六或生成显示标签。
我一般会把周起始日期作为查询依据,周标签用于展示。这样筛选跨月、跨年的记录时,不用再靠拆文本猜日期。

第一步:导入需要的模块
先备份自己的数据库,按 Alt + F11 打开 VBA 编辑器,通过「文件 → 导入文件」导入需要的标准模块。
| 需要的功能 | 对应模块 |
|---|
| 日期、日期时间 | Module_DatePicker.bas |
| 年份、年月 | Module_YearMonthPicker.bas |
| 日期范围 | Module_DateRangePicker.bas |
| 周 | Module_WeekPicker.bas |
| 独立时间 | Module_TimePicker.bas |
| 日期时间范围 | Module_DateTimeRangePicker.bas |
只需要某一种,就按需安装,不必把六个全部导入。导入后执行「调试 → 编译」,确认没有编译错误,再继续生成窗体。
项目中的中文模块要注意编码,尤其是使用 GBK/CP936 的模块,不要随手另存成 UTF-8 再导入 VBE。也不要把普通 .bas 文件当成窗体序列化文件,用 LoadFromText 加载。
第二步:生成对应窗体
在立即窗口执行相应命令。下面每行对应一种窗体,只执行自己已导入模块对应的行;如果六个模块都装了,可以依次执行这六行:
CreateDatePickerForm
CreateYearMonthPickerForm
CreateDateRangePickerForm
CreateWeekPickerForm
CreateTimePickerForm
CreateDateTimeRangePickerForm
生成后的窗体会保存到数据库中,不需要每次启动系统都重新生成。重新执行创建命令会重建对应窗体,自己调整过布局的,要先另存备份。
选择器内部的事件通过表达式绑定到标准模块的公共函数,不需要给生成的选择器窗体再手工粘贴一份窗体代码。
第三步:绑定业务输入框
比如订单窗体上有一个 txtOrderDate 文本框,控件来源为订单日期字段,另有按钮 cmdPickOrderDate。在这个业务窗体的按钮单击事件里写:
Private Sub cmdPickOrderDate_Click()
Dim pickedDate As Variant
pickedDate = ShowDatePicker(Me.txtOrderDate.Value, False) If Not IsNull(pickedDate) Then
Me.txtOrderDate.Value = pickedDate
End If
End Sub
第二个参数 False 表示只选日期。返回值使用 Variant,是因为用户取消或清除时可能返回 Null。上面这段代码只在取得有效日期时赋值,所以取消或清除都不会改掉原来的文本框值。
如果业务需要「清除按钮就清空原输入」,应采用支持绑定回写的接口,不能把这个返回值判断直接理解成所有调用方式都有同样效果。
范围输入可以用绑定接口处理。假设业务窗体上有 txtStart、txtEnd 两个文本框,分别绑定开始、结束日期/时间字段,再放两个演示按钮:
Private Sub cmdPickDateRange_Click()
PickDateRangeFor Me.txtStart, Me.txtEnd
End Sub
Private Sub cmdPickDateTimeRange_Click()
PickDateTimeRangeFor Me.txtStart, Me.txtEnd
End Sub
前一个只写日期,后一个写完整日期时间。不传格式字符串时,双框写入的是真正的 Date 值,适合绑定日期/时间字段。显示样式用文本框的「格式」属性设置,例如 yyyy-mm-dd hh:nn:ss,其中 nn 表示分钟。
一些入口还可以直接填属性表达式。例如,在名为 frmOrder 的窗体上放置未绑定文本框 txtYear,在它的「单击」事件属性里填写:
=PickYearForCtl("frmOrder","txtYear")不必再写 txtYear_Click。如果窗体或控件改了名字,表达式里的名字也要一起改。
回写值的类型,和文本框看起来是什么样,是两回事。
年份、年月、周标签,以及单框显示的范围,适合未绑定文本框或文本字段。需要保存日期时间的数据,优先使用接口默认的 Date 回写方式,不要为了显示好看,先格式化成字符串再让数据库猜。
取消和清除也有不同含义。在支持绑定回写的这些入口中,「取消」、Esc 或下拉模式点击外部,保留原值;「清除」用于写入 Null。对于必填字段,是否允许保存空值,还要由业务窗体和表规则来约束。
如果采用 Show... 模态返回值接口,取消和清除可能都返回 Null。调用者能否清空原字段,取决于自己的赋值代码,不能混同为绑定接口的行为。
最后还有查询范围。
日期范围返回的是两端日期,不含时间。如果订单表里的字段还带时分秒,结束日期是 10 月 19 日,直接用「小于等于 10 月 19 日」往往只包含当天零点,会漏掉当天后面的记录。
这种按整天筛选的情况,可以使用「字段大于等于开始日期,并且小于结束日期加一天」的半开区间。周查询同样如此,要包含周六全天,就比较到下周日之前。
但日期时间范围已经选了具体时刻,就不能照搬加一天。应按实际起止时间比较;如果目标字段存在小数秒,还要约定结束秒的包含规则。
日历把值选出来之后,这一层查询条件仍然是业务代码的责任。
项目按 Access 2010、2013、2016、2019、365,32 位与 64 位,以及 .accdb、.mdb 提供兼容说明。具体部署前,仍建议在自己的 Access 版本和数据库副本里试用,不把兼容说明当成每个环境都已经逐一验证过。
想直接看效果,就打开项目里的 DatePicker.accdb,进入 Demo 窗体。想接入已有系统,就按需导入模块、创建窗体,再把入口绑定到自己的文本框或按钮上。
建议试的时候别只点一个正常日期,也顺手试试跨月范围、跨年周、午夜 00:00:00、取消后保留原值,以及清除后的空值处理。这些操作能不能符合业务预期,比只看一张日历截图更重要。
这次更新,我想做的是让常见的日期时间输入有一套现成的选择方式:选一天、选一个月、选一周,或者选一段精确到秒的时间,都能按需要接到 Access 窗体中。
代码已经放在项目里,欢迎下载试用。遇到问题可以提交 Issue,写清使用的 Access 版本、位数、调用方式和复现步骤。
https://github.com/miaowei2/access-datepicker
技术培训
定制开发
技术支持