第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.utc

System 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.utc

Input若无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 << 1

Month 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。

正式节点与章专属证据

  • :第 1 个正式节点要能回到“解析和计算明确日历、偏移与边界,格式化结果可以往返或解释损失。”,并说明故障发生前后的第一处差异。
  • :第 2 个正式节点要能回到“解析和计算明确日历、偏移与边界,格式化结果可以往返或解释损失。”,并说明故障发生前后的第一处差异。
  • :第 3 个正式节点要能回到“解析和计算明确日历、偏移与边界,格式化结果可以往返或解释损失。”,并说明故障发生前后的第一处差异。
  • :第 4 个正式节点要能回到“解析和计算明确日历、偏移与边界,格式化结果可以往返或解释损失。”,并说明故障发生前后的第一处差异。
  • :第 5 个正式节点要能回到“解析和计算明确日历、偏移与边界,格式化结果可以往返或解释损失。”,并说明故障发生前后的第一处差异。
  • :第 6 个正式节点要能回到“解析和计算明确日历、偏移与边界,格式化结果可以往返或解释损失。”,并说明故障发生前后的第一处差异。
  • :第 7 个正式节点要能回到“解析和计算明确日历、偏移与边界,格式化结果可以往返或解释损失。”,并说明故障发生前后的第一处差异。
  • :第 8 个正式节点要能回到“解析和计算明确日历、偏移与边界,格式化结果可以往返或解释损失。”,并说明故障发生前后的第一处差异。
  • :第 9 个正式节点要能回到“解析和计算明确日历、偏移与边界,格式化结果可以往返或解释损失。”,并说明故障发生前后的第一处差异。
  • :第 10 个正式节点要能回到“解析和计算明确日历、偏移与边界,格式化结果可以往返或解释损失。”,并说明故障发生前后的第一处差异。

对象与状态模型

从输入、接收者到可观察证据

怎样区分时间点、日历日期、偏移量、时区规则与字符串格式?

输入与接收者

固定获取时间所需的原始值、Ruby 版本和调用入口。

状态变化

在执行前记录接收者身份,并声明计算时间的允许状态。

观察证据

保存第20章 Time类与Date类的初值、参数、编码或资源位置。

正式节点:获取时间、计算时间、时间的格式、本地时间、从字符串中获取时间、获取日期、计算日期、日期的格式、从字符串中获取日期、Time与Date的互相转换

控制与消息轨迹

在相同初值下定位首个分岔

  1. 01固定获取时间的输入和接收者
  2. 02执行计算时间并记录状态
  3. 03观察时间的格式的返回或副作用
  4. 04用Time与Date的互相转换核对不变量并复位

运行不变量:解析和计算明确日历、偏移与边界,格式化结果可以往返或解释损失。

边界故障探针

一次只破坏一个前提

基线满足:解析和计算明确日历、偏移与边界,格式化结果可以往返或解释损失。

本章回顾:Instant、Duration与Calendar不可混用

  1. Time/Date承载不同信息,clock/zone必须显式;测试注入fixed/monotonic source。
  2. Elapsed measurement用monotonic,UTC instant用于storage,user zone用于display/calendar。
  3. Format/parse固定pattern、offset、precision与strictness,epoch明确seconds/millis。
  4. Date arithmetic按calendar day/month并处理leap/month-end/business calendar,不用固定秒数替代。
  5. Time↔Date转换会丢失或补充time/zone,必须由业务policy决定。

练习与答案

练习

  1. 问题 1:建立正常轨迹。 回答“怎样区分时间点、日历日期、偏移量、时区规则与字符串格式?”,并写出四步执行记录。
  1. 问题 2:注入单一故障。 只制造“把固定 UTC 偏移当成时区规则,跨夏令时计算得到错误本地时间”,应从哪里开始定位?
  1. 问题 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类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

资料与写作方式声明

本章以Ruby 基础教程第 5 版作者支持资料合法公开试读核定可见范围,并以目录限定未公开部分,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…