这些函数将时间值(see 时刻)转换为 Lisp 时间戳,或转换为日历信息,反之亦然。
许多操作系统使用 64 位有符号整数来计数秒数,可以表示遥远的过去或未来的时间。然而,有些系统限制更多。例如,使用 32 位有符号整数的老式操作系统通常仅能处理协调世界时 1901-12-13 20:45:52 至 2038-01-19 03:14:07 之间的时间。
日历转换函数即使对于公历启用之前的日期,以及对于极其遥远的过去或未来的日期(公历在这些日期极不准确,且与天文学、古生物学等科学领域的常用惯例不符,这些领域使用儒略历年长),也一律使用公历。年份从公元前 1 年开始计数,不会像传统公历那样跳过 0 年;例如,年份 −3 表示公历公元前 4 年。
该函数将一个时间值转换为 Lisp 时间戳。
参数 form 指定要返回的时间戳格式。
如果 form 是符号 integer,该函数返回以秒为单位的整数值。
如果 form 是正整数,它指定时钟频率,该函数返回整数对时间戳 (ticks . form)。
如果 form 是 t,该函数将其视为适合表示该时间戳的正整数;例如,如果 time 为 nil 且平台时间戳具有纳秒精度,则它会被视为 1000000000。
如果 form 是 list,该函数返回整数列表 (high low micro pico)。
尽管 nil 形式的 form 目前作用与 list 相同,但这一行为计划在未来的 Emacs 版本中更改,因此需要列表时间戳的调用者应显式传入 list。
如果 time 不是合法的时间值,该函数会抛出错误。
否则,如果 time 无法被精确表示,转换会向负无穷方向截断。
当 form 为 t 时,转换总是精确的,因此不会发生截断,且返回的时钟精度不低于 time。
相比之下,尽管 float-time 也可以转换任意时间值而不抛出错误,但其结果可能不精确。see 时刻。
为了效率,该函数可能返回一个与 time 满足 eq 关系的值,或者与 time 共享结构。
尽管 (time-convert nil nil) 等价于 (current-time),但后者可能稍快一些。
(setq a (time-convert nil t)) ⇒ (1564826753904873156 . 1000000000)
(time-convert a 100000) ⇒ (156482675390487 . 100000)
(time-convert a 'integer) ⇒ 1564826753
(time-convert a 'list) ⇒ (23877 23681 904873 156000)
该函数将一个时间值转换为日历信息。 如果你不指定 time,它会解码当前时间;同样,zone 默认为当前时区规则。see 时区规则。 操作系统会限制时间和时区值的范围。
参数 form 控制返回的 seconds 元素的格式,如下所述。 返回值是一个包含九个元素的列表,如下所示:
(seconds minutes hour day month year dow dst utcoff)
以下是各元素的含义:
分钟已过的秒数,格式如下所述。
小时已过的分钟数,0 到 59 之间的整数。
一天中的小时数,0 到 23 之间的整数。
一个月中的日期,1 到 31 之间的整数。
一年中的月份,1 到 12 之间的整数。
年份,通常是大于 1900 的整数。
星期几,0 到 6 之间的整数,0 代表星期日。
如果夏令时生效则为 t,未生效则为 nil,无法获取该信息则为 −1。
一个整数,表示与协调世界时的偏移秒数,即格林威治以东的秒数。
seconds 元素是一个非负且小于 61 的 Lisp 时间戳;除了正闰秒期间(假设操作系统支持闰秒),它始终小于 60。
如果可选参数 form 为 t,seconds 使用与 time 相同的精度;
如果 form 为 integer,seconds 会被截断为整数。
例如,如果 time 是时间戳 (1566009571321 . 1000),在缺乏闰秒的典型系统上表示 2019-08-17 02:39:31.321 UTC,
那么 (decode-time time t t) 返回 ((31321 . 1000) 39 2 17 8 2019 6 nil 0),
而 (decode-time time t 'integer) 返回 (31 39 2 17 8 2019 6 nil 0)。
如果 form 被省略或为 nil,当前默认值为 integer,但该默认值可能在未来的 Emacs 版本中更改,因此需要特定格式的调用者应显式指定 form。
Common Lisp 注意: Common Lisp 对 dow、dst 和 utcoff 的定义不同,且其二秒元素是 0 到 59 之间的整数。
要访问(或修改)日历信息中的元素,可以使用 decoded-time-second、decoded-time-minute、
decoded-time-hour、decoded-time-day、
decoded-time-month、decoded-time-year、
decoded-time-weekday、decoded-time-dst 和
decoded-time-zone 这些访问器。
该函数将 time 转换为 Lisp 时间戳。
它可以作为 decode-time 的逆函数。
通常,第一个参数是一个列表
(second minute hour day month
year ignored dst zone),
它以 decode-time 的格式指定解码后的时间。
这些列表元素的含义参见 decode-time 下的表格。
特别地,dst 说明在夏令时回退导致时间戳重复时如何解释。
如果 dst 为 −1,则自动猜测 DST 值;
如果为 t 或 nil,则返回对应 DST 值的时间戳,若不存在则抛出错误。
遗憾的是,当基于 TZDB 的时区向格林威治以西偏移时,dst 为 t 或 nil 无法区分重复的时间戳;
例如,当 zone 为 ‘"Europe/Volgograd"’ 时,2020-12-27 01:30 的两个标准时间戳无法区分,
因为当天 02:00 该时区标准时间从格林威治以东 4 小时改为 3 小时。
如果需要处理此类情况,可以使用数值型 zone 来区分。
第一个参数也可以是列表 (second minute
hour day month year),
它等价于列表 (second minute hour day
month year nil -1 nil)。
作为一种过时的调用约定,该函数可以接受六个或更多参数。
前六个参数 second、minute、hour、day、month 和 year
指定了解码时间的大部分组件。
如果参数超过六个,最后一个 参数会被用作 zone,其他额外参数会被忽略,
因此 (apply #'encode-time (decode-time ...)) 可以正常工作。
在这种过时约定中,dst 为 −1,zone 默认为当前时区规则(see 时区规则)。
将过时调用代码现代化时,请确保现代等效列表包含 9 个元素,且 dst 元素为 −1 而非 nil。
小于 100 的年份不会被特殊处理。
如果你希望它们代表 1900 年以上或 2000 年以上的年份,必须在调用 encode-time 之前自行修改。
操作系统会限制时间和时区值的范围。
但是,从纪元到不久的将来的时间戳始终受支持。
encode-time 函数大致作为 decode-time 的逆函数。
例如,你可以将后者的输出传递给前者:
(encode-time (decode-time ...))
你可以对 seconds、minutes、hour、day 和 month 使用超出范围的值来执行简单的日期运算; 例如,日期 0 表示指定月份的前一天。 这样做时要小心,因为在某些情况下这通常会失败。 例如:
;; 尝试计算一个月后的时间。
;; 注意;这可能不会按预期工作。
(let ((time (decode-time)))
(setf (decoded-time-month time)
(+ (decoded-time-month time) 1))
time)
遗憾的是,如果结果时间因月份长度差异、夏令时切换、时区更改、
缺少闰日或闰秒而无效,这段代码可能不会按预期工作。
例如,如果在 1 月 30 日执行,这段代码会生成不存在的日期 2 月 30 日,
encode-time 会将其调整为 3 月初。
同样,在 2096 年 2 月 29 日上加四年会得到不存在的 2100 年 2 月 29 日;
在纽约 2022 年 3 月 13 日 01:30 加一小时会得到不存在的 02:30 时间戳,
因为当天时钟从 02:00 直接跳至 03:00。
为避免部分(并非全部)问题,你可以基于受影响单位的中间进行计算,
例如,增加月份时从每月 15 日开始。
或者,你可以使用 calendar 和 time-date 库。