第20章 Time类与Date类
区分wall/monotonic time、UTC/local zone、format/parse、epoch unit与Date calendar arithmetic,建立Time/Date转换政策。
学习目标
- 能解释获取时间与计算时间在“第20章 Time类与Date类”中的责任边界
- 能围绕“怎样区分时间点、日历日期、偏移量、时区规则与字符串格式?”运行正常与故障轨迹并定位首个分岔
- 能用“第20章 Time类与Date类的输入样本、接收者与方法、关键状态前后值、正常与失败输出、异常或退出状态,以及复位后的再次运行记录。”证明“解析和计算明确日历、偏移与边界,格式化结果可以往返或解释损失。”
来源、版次与运行边界
“第20章 Time类与Date类”以作者维护的第 5 版支持页核定 2016 年 3 月 12 日首刷、两位作者、松本行弘监修以及四部分 23 章目录;逐章程序清单、练习答案和勘误只作为公开支持材料,不被冒充为原书全文。
对“第20章 Time类与Date类”而言,中文解释、示例、交互、练习和答案均为独立教学重写;站内中文章名是与官方 23 章顺序对应的课程映射,不宣称是日文小节的逐字翻译,也不从公开程序清单复制整段实现。
“第20章 Time类与Date类”固定在 Ruby 2.3 语境;Ruby 2.3.0 官方文档与稳定版发布说明用于核对当时可用的语言和标准库行为。现代 Ruby 的差异只能另列迁移说明,不能静默改变本页示例的版本结论。
围绕“怎样区分时间点、日历日期、偏移量、时区规则与字符串格式?”,本页验收“第20章 Time类与Date类的输入样本、接收者与方法、关键状态前后值、正常与失败输出、异常或退出状态,以及复位后的再次运行记录。”。先预测正常轨迹,再只注入“把固定 UTC 偏移当成时区规则,跨夏令时计算得到错误本地时间”;若无法定位第一处状态分岔,就拒绝当前解释。
从“时间就是一个Time对象”开始拆分
时间需求至少分instant、elapsed duration、calendar date、local civil time与timezone rules。避免一个Time同时承担所有语义。
先预测:测代码耗时用Time.now是否可靠?系统clock可NTP调整或人工回拨,差值可能跳变;elapsed measurement使用monotonic clock。Wall time保留给timestamp与用户日历。
Time类与Date类
Time表示带offset/zone解释的instant与civil fields;Date标准库表示calendar date而无time-of-day。DateTime也存在,但现代实践常用Time+Date及timezone library;本书语义按Ruby 2.3 API验证。
说明转换不可能完全中性。
require "date"
instant = Time.now
date = Date.today
p instant: instant, date: date
p utc: instant.utcSystem default timezone依部署环境变化,测试不要依赖机器local zone。业务若需IANA zone/DST历史,使用成熟timezone library并固定tzdata version;numeric offset不等于完整zone rules。
获取时间
Time.now获取wall clock,Time.utc/Time.local从civil fields构造,Time.at从epoch seconds构造。让expiration和scheduler可测。
class Expiry
def initialize(clock)
@clock = clock
end
def expired?(deadline)
@clock.call >= deadline
end
end
fixed = -> { Time.utc(2026, 7, 14, 12, 0, 0) }
p Expiry.new(fixed).expired?(Time.utc(2026, 7, 14, 11, 0, 0))Deadline若用于进程内timeout,使用monotonic numeric deadline;业务token expiry跨进程则用UTC instant并接受wall clock skew policy。
计算时间
Time-Time得到seconds数值,Time+seconds得到新Time。Elapsed duration使用Process.clock_gettime(Process::CLOCK_MONOTONIC),不可转calendar。。
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
perform_work
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started
puts format("%.3fs", elapsed)Timeout budget递减同一monotonic deadline,nested calls传remaining time,避免每层重新给完整timeout。Wall timestamps相减可用于历史approximate interval,但DST/clock adjustment语义需说明。
时间的格式与本地时间
strftime按pattern格式化;pattern必须包含业务所需zone offset与precision。不使用locale-dependent默认to_s作为machine protocol。
instant = Time.utc(2026, 7, 14, 12, 34, 56)
p instant.strftime("%Y-%m-%dT%H:%M:%SZ")
p instant.getlocal("+08:00").strftime("%Y-%m-%d %H:%M:%S %:z")Ruby 2.3对%:z等directive支持需验证。UTC用Z/offset;local display需要user timezone而非server timezone。Offset +08:00不包含DST/history,长期schedule使用IANA zone。
从字符串中获取时间
Time.parse灵活但会推断缺失fields,machine input优先Time.strptime(text, pattern)或ISO parser。。
require "time"
text = "2026-07-14T12:34:56+08:00"
parsed = Time.iso8601(text)
p parsed.utcInput若无offset,是local civil time而非唯一instant;API必须要求zone或由明确user context解释。DST overlap同一local time可能对应两个instants,gap可能不存在;timezone library提供disambiguation policy。
获取日期
Date.today依local zone,Date.new(year, month, day)显式构造,Date.parse/strptime/iso8601解析。不能让server zone决定用户“今天”。
require "date"
date = Date.new(2026, 7, 14)
p date
p date.leap?
p date.wday账单/航班日期通常是local calendar date,存Date/ISO date,不先转midnight UTC造成偏移。
计算日期
Date+Integer按calendar days移动,Date-Date得Rational days;>>/<<移动months。不同于加86400 seconds。
date = Date.new(2024, 1, 31)
p next_day: date + 1
p next_month: date >> 1
p previous_month: date << 1Month shift的end-of-month clamping按API验证;“每月最后一天”与“同日号,不存在则某政策”需独立domain object。Business days还要holiday calendar/version,不能只跳周末。
日期的格式与从字符串中获取日期
Date#strftime格式化,Date.strptime按pattern严格解析,Date.iso8601适合YYYY-MM-DD。让API避免混淆。
Flexible Date.parse可能接受不完整/多格式;import先按schema尝试有限formats并记录选择,不随locale。Round-trip测试leap day、month-end、invalid 2023-02-29与leading zeros。
Time与Date的互相转换
Time#to_date丢time-of-day/offset context(先在哪个zone取date很重要);Date#to_time或构造Time必须选择time与zone。禁止隐式server defaults。
require "date"
instant = Time.utc(2026, 7, 14, 23, 30)
shanghai_like = instant.getlocal("+08:00")
p utc_date: instant.to_date
p local_date: shanghai_like.to_date同一instant在不同zone可能不同date。Date转instant用于“当天开始”时,DST使day length非86400秒;timezone library按zone rules计算start/end。
正式节点与章专属证据
- ↡在“第20章 Time类与Date类”中,获取时间必须连接输入、状态变化与可复核结果。 :第 1 个正式节点要能回到“解析和计算明确日历、偏移与边界,格式化结果可以往返或解释损失。”,并说明故障发生前后的第一处差异。
- ↡在“第20章 Time类与Date类”中,计算时间必须连接输入、状态变化与可复核结果。 :第 2 个正式节点要能回到“解析和计算明确日历、偏移与边界,格式化结果可以往返或解释损失。”,并说明故障发生前后的第一处差异。
- ↡在“第20章 Time类与Date类”中,时间的格式必须连接输入、状态变化与可复核结果。 :第 3 个正式节点要能回到“解析和计算明确日历、偏移与边界,格式化结果可以往返或解释损失。”,并说明故障发生前后的第一处差异。
- ↡在“第20章 Time类与Date类”中,本地时间必须连接输入、状态变化与可复核结果。 :第 4 个正式节点要能回到“解析和计算明确日历、偏移与边界,格式化结果可以往返或解释损失。”,并说明故障发生前后的第一处差异。
- ↡在“第20章 Time类与Date类”中,从字符串中获取时间必须连接输入、状态变化与可复核结果。 :第 5 个正式节点要能回到“解析和计算明确日历、偏移与边界,格式化结果可以往返或解释损失。”,并说明故障发生前后的第一处差异。
- ↡在“第20章 Time类与Date类”中,获取日期必须连接输入、状态变化与可复核结果。 :第 6 个正式节点要能回到“解析和计算明确日历、偏移与边界,格式化结果可以往返或解释损失。”,并说明故障发生前后的第一处差异。
- ↡在“第20章 Time类与Date类”中,计算日期必须连接输入、状态变化与可复核结果。 :第 7 个正式节点要能回到“解析和计算明确日历、偏移与边界,格式化结果可以往返或解释损失。”,并说明故障发生前后的第一处差异。
- ↡在“第20章 Time类与Date类”中,日期的格式必须连接输入、状态变化与可复核结果。 :第 8 个正式节点要能回到“解析和计算明确日历、偏移与边界,格式化结果可以往返或解释损失。”,并说明故障发生前后的第一处差异。
- ↡在“第20章 Time类与Date类”中,从字符串中获取日期必须连接输入、状态变化与可复核结果。 :第 9 个正式节点要能回到“解析和计算明确日历、偏移与边界,格式化结果可以往返或解释损失。”,并说明故障发生前后的第一处差异。
- ↡在“第20章 Time类与Date类”中,Time与Date的互相转换必须连接输入、状态变化与可复核结果。 :第 10 个正式节点要能回到“解析和计算明确日历、偏移与边界,格式化结果可以往返或解释损失。”,并说明故障发生前后的第一处差异。
对象与状态模型
从输入、接收者到可观察证据
怎样区分时间点、日历日期、偏移量、时区规则与字符串格式?
输入与接收者
固定获取时间所需的原始值、Ruby 版本和调用入口。
状态变化
在执行前记录接收者身份,并声明计算时间的允许状态。
观察证据
保存第20章 Time类与Date类的初值、参数、编码或资源位置。
正式节点:获取时间、计算时间、时间的格式、本地时间、从字符串中获取时间、获取日期、计算日期、日期的格式、从字符串中获取日期、Time与Date的互相转换
控制与消息轨迹
在相同初值下定位首个分岔
- 01固定获取时间的输入和接收者
- 02执行计算时间并记录状态
- 03观察时间的格式的返回或副作用
- 04用Time与Date的互相转换核对不变量并复位
运行不变量:解析和计算明确日历、偏移与边界,格式化结果可以往返或解释损失。
边界故障探针
一次只破坏一个前提
本章回顾:Instant、Duration与Calendar不可混用
- Time/Date承载不同信息,clock/zone必须显式;测试注入fixed/monotonic source。
- Elapsed measurement用monotonic,UTC instant用于storage,user zone用于display/calendar。
- Format/parse固定pattern、offset、precision与strictness,epoch明确seconds/millis。
- Date arithmetic按calendar day/month并处理leap/month-end/business calendar,不用固定秒数替代。
- Time↔Date转换会丢失或补充time/zone,必须由业务policy决定。
练习与答案
练习
- 问题 1:建立正常轨迹。 回答“怎样区分时间点、日历日期、偏移量、时区规则与字符串格式?”,并写出四步执行记录。
- 问题 2:注入单一故障。 只制造“把固定 UTC 偏移当成时区规则,跨夏令时计算得到错误本地时间”,应从哪里开始定位?
- 问题 3:覆盖正式节点。 用一个证据包串联获取时间、计算时间、时间的格式、本地时间、从字符串中获取时间、获取日期、计算日期、日期的格式、从字符串中获取日期、Time与Date的互相转换,说明为什么结论可由另一位读者独立复核。
名词解释
名词解释
本章出现的专业名词,用大白话再讲一遍。
- 获取时间
“第20章 Time类与Date类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 计算时间
“第20章 Time类与Date类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 时间的格式
“第20章 Time类与Date类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 本地时间
“第20章 Time类与Date类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 从字符串中获取时间
“第20章 Time类与Date类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 获取日期
“第20章 Time类与Date类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 计算日期
“第20章 Time类与Date类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 日期的格式
“第20章 Time类与Date类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 从字符串中获取日期
“第20章 Time类与Date类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- Time与Date的互相转换
“第20章 Time类与Date类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。