一个 #2024-01-05# 字面量背后是一条 67 个函数的完整时间线工具链——构造、解析、分量、运算、比较、时区、格式化、相对时间。本章把它们按功能分组全部实测一遍,卷四就此收官。
第 3 章见过 time 的基本面貌,第 19 章用过 time.format/formatPattern。本章补齐整条时间线:#...# 字面量三形态、time 命名空间 67 个函数按功能组逐一实测——当前时间、构造解析、分量、add/diff 运算、比较区间、时间戳与 UTC、月周年周边界、格式化与相对时间,以计时和日志排序两个实战收尾。实测发现三个 RC0 缺陷(月末钳制、var 转换、字典数组字面量),如实记录并给出规避方案。
掌握 #...# 三种字面量形态;分清 now/today/clock/nowUTC/timer 五个"当前时间";会用 fromDate 构造、tryParse 安全解析;背下 weekday 周日为 0 等约定;掌握 add/diff 八族的方向与截断;会用 inRange/rangeOverlap/approx 做区间判断;理解 toUnix 的 UTC 口径与时区转换链;会用 relative 输出中文相对时间、timer 测耗时、运算符给时间排序。
第 3 章介绍过:time 是一等基础类型,字面量用 #...# 包裹,三种形态的运行时表现:
auto d = #2024-01-05# // 仅日期 auto t = #12:30:59# // 仅时间 auto dt = #2024-01-05 12:30:59# // 日期时间 println(type(d)) // time println(d) // 2024-01-05 00:00:00:日期形态时分秒补零 println(t) // 0-01-01 12:30:59:时间形态日期部分是"第 0 天" println(dt) // 2024-01-05 12:30:59 println(int(dt)) // 63871677059:time 本质是秒数序列 println(int(t)) // 45059 = 12×3600 + 30×60 + 59
三形态共享一个内部表示:从第 0 天起算的秒数(int() 可见)。日期形态 = 整天秒数,时间形态 = 当天内秒数,日期时间 = 两者相加——int(dt) == int(d) + int(t) 精确成立。0-01-01 的怪异显示也由此而来:纯时间的日期部分是 0,被格式化器翻译成"第 0 年第 1 天"。规则:# 到下一处 #,中间不能换行。
"现在"有五种口径,语义分明(输出为实测当时的真实值):
println(time.now()) // 2026-07-19 00:54:35:本地当前日期时间 println(time.today()) // 2026-07-19 00:00:00:今天零点(日期部分) println(time.clock()) // 0-01-01 00:54:35:当前时刻(纯时间部分) println(time.nowUTC()) // 2026-07-18 16:54:35:UTC 口径(本机 UTC+8,差 8 小时) println(time.timer()) // 1234.567...:高精度计时器读数(float 秒,不是时钟!)
记忆法:now 管"现在"、today 管"今天"、clock 管"几点"、nowUTC 管"伦敦几点"、timer 管"跑了多久"。timer 的绝对值无意义,两次取差才是耗时(20.10 实战)。today 与 clock 互补:一个把时间清零,一个把日期清零。
字面量写死在源码里,运行期的日期来自两个入口:按字段构造与字符串解析:
println(time.fromDate(2024, 2, 29)) // 2024-02-29 00:00:00 println(time.fromTime(8, 5, 3)) // 0-01-01 08:05:03 println(time.fromDateTime(2024, 1, 5)) // 时分秒有默认值 0 println(time.fromDateTime(2024, 1, 5, 12, 30)) // 2024-01-05 12:30:00 println(time.fromDateTime(2024, 1, 5, 12, 30, 59))
解析宽容——连字符、斜杠、点、甚至中文年月日都认:
println(time.parse("2024-01-05")) // 2024-01-05 00:00:00 println(time.parse("2024/01/05")) // 2024-01-05 00:00:00 println(time.parse("2024年1月5日")) // 2024-01-05 00:00:00 println(time.parse("2024-01-05 12:30:59")) // 2024-01-05 12:30:59 println(time.parse("2024-13-40")) // 0-01-01 00:00:00:失败静默返回零值! auto ok, v = time.tryParse("2024-13-40") println(f"{ok} {v}") // false 0-01-01 00:00:00:元组带回成功标志 auto ok2, v2 = time.tryParse("2024-01-05") println(f"{ok2} {v2}") // true 2024-01-05 00:00:00 println(time.parsePattern("05-01-2024", "dd-mm-yyyy")) // 按自定义模式解析
关键纪律:parse 失败不报错、静默返回 time(0),外部输入必须走 tryParse / tryParsePattern——返回 (bool, time) 元组(第 9 章多返回值),用标志位判成败。parsePattern 的模式字母规则同 formatPattern(第 19 章)。
拿到 time 值后拆字段:
auto dt = #2024-01-05 12:30:59# println(time.year(dt), " ", time.month(dt), " ", time.day(dt)) // 2024 1 5 println(time.hour(dt), " ", time.minute(dt), " ", time.second(dt)) // 12 30 59 println(time.weekday(#2024-01-07#)) // 0:周日是 0!周一 1 … 周六 6 println(time.weekday(dt)) // 5:2024-01-05 是周五 println(time.dayOfYear(dt)) // 5:年内第几天 println(time.quarter(#2024-11-15#)) // 4:季度 1~4 println(time.isLeapYear(2024), " ", time.isLeapYear(1900)) // true false(百年不闰) println(time.daysInMonth(2024, 2)) // 29:闰年二月 println(time.daysInYear(2024)) // 366 println(time.datePart(dt)) // 2024-01-05 00:00:00:时间清零 println(time.timePart(dt)) // 0-01-01 12:30:59:日期清零
三个易错约定:weekday 周日为 0(不是 7,也不是周一为 0);isLeapYear 收年份 int;datePart/timePart 返回 time 类型,对应部分清零。
time 不支持 +/- 运算符(坑 1),运算全部函数化。add 八族:amount 传负数即减:
println(time.addDays(#2024-01-31#, 1)) // 2024-02-01:自动进月 println(time.addDays(#2024-01-31#, -31)) // 2023-12-31:负数倒退跨年 println(time.addWeeks(#2024-01-05#, 2)) // 2024-01-19 println(time.addQuarters(#2024-01-05#, 1)) // 2024-04-05 println(time.addMonths(#2024-01-15#, 1)) // 2024-02-15 println(time.addYears(#2024-01-15#, -4)) // 2020-01-15 println(time.addHours(#2024-01-05 12:00:00#, 13)) // 2024-01-06 01:00:00:跨天进位 println(time.addMinutes(#2024-01-05 12:00:00#, 90)) // 2024-01-05 13:30:00 println(time.addSeconds(#2024-01-05 12:00:00#, 3601)) // 2024-01-05 13:00:01
diff 八族回答"从左到右隔多远",方向敏感:右晚左早为正。两套语义并存——秒到天是流逝量换算,周到年是日历字段差:
println(time.diffDays(#2024-01-05#, #2024-01-15#)) // 10:左→右为正 println(time.diffDays(#2024-01-15#, #2024-01-05#)) // -10:反向为负 println(time.diffHours(#2024-01-05 00:00:00#, #2024-01-06 12:00:00#)) // 36 println(time.diffMinutes(#2024-01-05 00:00:00#, #2024-01-05 02:30:00#)) // 150 println(time.diffWeeks(#2024-01-01#, #2024-01-20#)) // 2:19 天 ÷ 7 截断 println(time.diffMonths(#2024-01-31#, #2024-03-01#)) // 2:月份字段差,不看"日" println(time.diffYears(#2020-06-15#, #2024-03-01#)) // 4:年份字段差(实际只过了 3 年 8 个月)
用 diffYears 算年龄会得到"虚岁"——精确年龄需减一后用月日比较修正。addMonths/addYears 的月末钳制缺陷见坑 3。
比较运算符直接可用,语义是秒数的精确比较——时分秒参与运算:
auto a = #2024-01-05# // 午夜零点 auto b = #2024-01-05 12:00:00# // 同日正午 auto c = #2024-01-10# println(a < c, " ", a == #2024-01-05#, " ", a != c) // true true true println(a == b) // false:同一天不同时刻不相等! println(time.isSameDay(a, b)) // true:只比日期部分 println(time.isSameMonth(a, c)) // true println(time.isSameYear(a, #2024-12-31#)) // true println(time.inRange(b, a, c)) // true:闭区间,两端含 println(time.inRange(#2024-01-11#, a, c)) // false println(time.rangeOverlap(a, b, b, c)) // true:端点相触算重叠 println(time.rangeOverlap(a, a, c, c)) // false:真正不相交 println(time.approx(a, time.addSeconds(a, 10))) // true:容差 10 秒 println(time.approx(a, time.addSeconds(a, 30))) // false
分工:== 精确到秒;isSameDay/Month/Year 按日历粒度;inRange 闭区间点查;rangeOverlap 判区间相交(含端点相触);approx 判"差不多同时"——容差实测 10 秒,适合对时钟抖动宽容的场景。
与外部系统交换时间用 Unix 时间戳。实测口径:toUnix 把 time 当 UTC 墙钟解读,fromUnix 同样返回 UTC 墙钟:
println(time.toUnix(#2024-01-05 00:00:00#)) // 1704412800:恰是该时刻的 UTC 时间戳 println(time.fromUnix(0)) // 1970-01-01 00:00:00:UTC 纪元 println(time.fromUnix(1704412800)) // 2024-01-05 00:00:00:往返一致 auto now = time.now() auto u = time.localToUTC(now) println(int(now) - int(u)) // 28800:本地与 UTC 差 8 小时的秒数 println(time.utcToLocal(u) == now) // true:转换链往返还原 println(time.timezoneOffset()) // 28800:本机时区偏移(秒)
本地时间转标准时间戳的完整链:toUnix(localToUTC(本地时间));还原:utcToLocal(fromUnix(时间戳))。timezoneOffset 单位是秒(UTC+8 = 28800),读本机设置,跨时区程序别假设恒定。
报表类高频操作:"本月初""本年末""本周一",六个边界函数一次给全(返回值的时分秒清零):
auto d = #2024-01-05 12:30:59# println(time.firstDayOfMonth(d)) // 2024-01-01 00:00:00 println(time.lastDayOfMonth(d)) // 2024-01-31 00:00:00 println(time.lastDayOfMonth(#2024-02-15#)) // 2024-02-29:闰年认得 println(time.firstDayOfYear(d)) // 2024-01-01 00:00:00 println(time.lastDayOfYear(d)) // 2024-12-31 00:00:00 println(time.firstDayOfWeek(d)) // 2024-01-01:默认周一(startDay=1)起算 println(time.lastDayOfWeek(d)) // 2024-01-07:周日收尾 println(time.firstDayOfWeek(d, 0)) // 2023-12-31:startDay=0 改为周日起算 println(time.weekOfYear(#2024-12-31#)) // 53:年内第几周 println(time.weekOfMonth(#2024-01-29#)) // 5:月内第几周
firstDayOfWeek/lastDayOfWeek 的 startDay 沿用 weekday 编号(周日 0、周一 1),默认 1 即周一起算——周报直接用,美式周日历传 0。
格式化第 19 章已详解(format 三形态、formatPattern 模式字母小写、mm 语境二义),此处补两个便捷别名与新函数 relative:
auto dt = #2024-01-05 12:30:59# println(time.dateString(dt)) // 2024-01-05:等价 format(dt, 1) println(time.timeString(dt)) // 12:30:59:等价 format(dt, 2) auto base = #2024-01-05 12:00:00# println(time.relative(time.addSeconds(base, 30), base)) // 30秒后 println(time.relative(time.addMinutes(base, -5), base)) // 5分钟前 println(time.relative(time.addHours(base, 3), base)) // 3小时后 println(time.relative(time.addDays(base, 2), base)) // 2天后 println(time.relative(time.addDays(base, 30), base)) // 1个月后 println(time.relative(time.addDays(base, 400), base)) // 1年后 println(time.relative(time.addMinutes(base, 90), base)) // 1小时后:截断,不四舍五入
relative(value, base) 输出中文相对时间:单位按秒→分→时→天→月→年自动升档,数值向零截断,方向用"前/后"表达。社交 Feed 的"3 分钟前"一行搞定。
计时:timer 取差测耗时,sleep 暂停毫秒:
auto t0 = time.timer() time.sleep(120) // 暂停 120 毫秒 println(time.timer() - t0) // 0.1202...:实测耗时(float 秒,略高于 0.12 是调度开销)
排序:给带时间戳的日志按时间重排。数组没有内建 sort(第 16 章),手写冒泡;time 直接用 > 比较:
auto ts = [#2024-01-05 09:00:12#, #2024-01-05 08:59:47#, #2024-01-05 09:00:03#] auto msgs = ["服务启动", "磁盘告警", "用户登录"] for i in range(ts.length) for j in range(ts.length - 1 - i) if ts[j] > ts[j + 1] then auto tt = ts[j] ts[j] = ts[j + 1] ts[j + 1] = tt auto tm = msgs[j] msgs[j] = msgs[j + 1] msgs[j + 1] = tm end end end for i in range(ts.length) println(time.format(ts[i], 0), " ", msgs[i]) end // 2024-01-05 08:59:47 磁盘告警 // 2024-01-05 09:00:03 用户登录 // 2024-01-05 09:00:12 服务启动
这里用平行数组(ts 与 msgs 同步交换)而非字典数组,是实测后的选择——字典字面量数组与 var 装 time 各有一个 RC0 缺陷(坑 4、坑 5)。
用 xl cgen 观察 auto d = #2024-01-05# 与 time.addDays(d, 30),生成的核心代码是:
字面量在模块初始化时被 xrtStrToTime 解析一次,之后就是 long long 秒数;time 在 C 层不带盒子和引用计数,比较、加减都是原生整数运算——这是 time 比 var 快得多的根本原因(对照第 15 章拾遗)。20.1 的 int() 实测与此互为印证。
纯时间字面量显示为 0-01-01 12:30:59,不是 bug 是表示法:内部只有一个秒数,日期部分为 0 时被忠实翻译成"第 0 年第 1 天"。推论:判断纯时间看 int(t) < 86400;把纯时间贴到某日期上,用 fromDateTime 按字段重组(time 没有 + 运算符)。
diff 八族看似整齐,实则两套逻辑:diffSeconds~diffDays 是流逝量换算(相差秒数逐级除,截断取整);diffWeeks~diffYears 是日历字段差(2020→2024 恒为 4,不管是否真过了四个整年)。混用会踩坑:算工龄用 diffYears 会虚高,正确姿势是减一后按"今年纪念日是否已过"修正。add 族同理:addMonths 是"月份字段 +1"而非"加 30 天",这正是月末钳制问题(坑 3)的来源。
以下输出全部来自真实编译器与运行时(文件路径已缩短)。
原因:time 不是数值类型,运算一律走函数。正确做法:加用 add 八族(addSeconds(t, 3600)),减用 diff 八族(diffDays(t1, t2))。
原因:parse 失败静默返回 time(0),脏数据会带着"第 0 年"流入后续逻辑。正确做法:外部输入一律 auto ok, t = time.tryParse(s),判 ok 再用 t。
原因:1 月 31 日 +1 月 = 2 月 31 日(不存在),闰日 +1 年同理;RC0 不钳制也不报错。正确做法:锚日固定在 1~28 号;或先 firstDayOfMonth 取月初运算;用前自查 month 分量。
原因:var→静态 time 转换路径有缺陷,静默产出零值;var 间的比较、println 直打却是好的。正确做法:time 待在静态世界——静态数组、dict<time>(取值直接是 time);必须装箱时存 int(v) 秒数,用时 time(...) 还原。
原因:字典字面量直接组成数组字面量时类型描述符未生成,tcc 链接失败。正确做法:先 auto logs = [] 再逐个 push(实测可用);或用 20.10 的平行数组。
| 主题 | 规则 | 要点 |
|---|---|---|
| 字面量 | #yyyy-mm-dd# / #hh:mm:ss# / 合体 | 内部是秒数;纯时间显示 0-01-01 开头 |
| 当前时间 | now / today / clock / nowUTC / timer | timer 是秒表取差用,其余是时钟 |
| 构造解析 | fromDate/Time/DateTime;parse/tryParse/Pattern | parse 失败静默零值;外部输入走 tryParse 元组 |
| 分量 | year…second / weekday / dayOfYear / quarter | weekday 周日=0;isLeapYear 收 int 年份 |
| 运算 | add/diff 各八族,负数即减 | diff 左→右为正;秒~天流逝量、周~年字段差;月末钳制有缺陷 |
| 比较区间 | 运算符精确到秒;isSame* / inRange / rangeOverlap / approx | inRange 闭区间;端点相触算重叠;approx 容差 10 秒 |
| 时间戳 | toUnix/fromUnix 按 UTC 解读 | localToUTC/utcToLocal 搭桥;timezoneOffset 单位是秒 |
| 边界 | firstDay/lastDay × 月/周/年;weekOf* | 周默认周一起算,startDay=0 改周日 |
| 格式化 | format/formatPattern(19 章);dateString/timeString;relative | relative 输出中文"N 单位前/后",数值截断 |
| 工具 | sleep(毫秒);timer() 取差 | time 无 +/- 运算符;字典数组字面量、var as time 有缺陷 |
编写 birthday.xl:给定生日月日(如 8 月 15 日),计算今天到下一个生日还有多少天;今年已过则算到明年,输出"距你的生日还有 N 天"。
参考思路:time.year(time.today()) 取今年,fromDate(y, m, d) 造今年生日;与 today 用运算符比较;已过则 fromDate(y + 1, m, d)——避开 addYears 的钳制缺陷;最后 diffDays 取天数。
编写 bench.xl:用 timer 测量"一百万次空循环"的耗时并打印;再 sleep(200) 后用 timer 验证暂停精度,打印两次测量与理论值的偏差。
参考思路:auto t0 = time.timer() … time.timer() - t0;sleep 参数是毫秒;实测略高于理论值是正常的调度开销(本章实测 120ms 得 0.1202s)。
编写 week.xl:打印本周日期范围与已过天数,形如"本周:01 月 01 日 ~ 01 月 07 日,已过 5 天"。再用 startDay=0 与默认值各打印一次,体会两种周历差别。
参考思路:firstDayOfWeek/lastDayOfWeek 取边界;formatPattern(t, "mm 月 dd 日") 格式化(mm 在日期语境为月,第 19 章规则);已过天数 diffDays(周一, today) + 1;周日历版本传 startDay=0。