Ruby 日期与时间处理:Time、Date、DateTime 怎么选怎么用

Ruby 的 Time、Date、DateTime 三个类各有适用场景。本文讲清创建、解析、格式化、时区处理与时间运算的正确姿势,避开夏令时、月末加减等经典陷阱。

最佳实践
红宝石与应用开发工作台插画

记录"某个时刻"(带时区)用 Time,只关心日历日期用 Date,DateTime 基本可以淘汰(Rails 的 TimeWithZone 接管了它的场景)。生产代码永远显式指定时区,解析外部输入用 strptime 而不是靠猜的 parse。

三个类的分工

  • Time:表示精确到秒(含小数)的时刻,内部带时区偏移,性能最好,绝大多数业务场景用它;
  • Date:纯日历日期,没有时刻和时区概念,适合生日、合同起止日这类"日历语义"数据;
  • DateTime:历史遗留,功能被 Time 和 Rails 的 TimeWithZone 覆盖,新项目不建议使用。

创建时间对象

Time.now                          # 当前时刻(系统时区)
Time.utc(2026, 10, 8, 12, 0, 0)   # UTC 时间
Time.local(2026, 10, 8, 12, 0, 0) # 本地时区
Time.new(2026, 10, 8, 12, 0, 0, '+08:00')  # 显式时区偏移
Date.new(2026, 10, 8)             # 纯日期

Rails 项目里优先用 Time.current、Time.zone.now,它们走应用配置的时区而非服务器时区。

时区:生产事故高发区

时区问题的本质是:存库和比较用 UTC,展示时才转换成本地时间。

t = Time.utc(2026, 10, 8, 4, 0, 0)
t.getlocal('+08:00')   # => 2026-10-08 12:00:00 +0800

Rails 中用 t.in_time_zone('Beijing')。注意夏令时:美国东部时间在 3 月和 11 月会切换 EST/EDT,写死 -05:00 偏移会在夏令时期间错一小时——用 in_time_zone('Eastern Time (US & Canada)') 让系统处理。

解析字符串

require 'time'
Time.parse('2026-10-08 12:00:00')            # 智能猜测格式
Time.strptime('08/10/2026', '%d/%m/%Y')      # 显式指定格式
Date.strptime('2026-10-08', '%Y-%m-%d')

parse 靠猜,遇到 01/02/2026 分不清是 1 月 2 日还是 2 月 1 日;处理用户输入和第三方数据时务必用 strptime 写明格式,并对 ArgumentError 做兜底。

格式化输出

t.strftime('%Y-%m-%d %H:%M:%S')   # => "2026-10-08 12:00:00"
t.iso8601                          # => "2026-10-08T12:00:00+08:00"

常用占位符:%Y 年、%m 月、%d 日、%H 24 小时制、%M 分、%S 秒、%z 时区偏移。接口交互统一用 ISO 8601,日志里也一样,避免格式歧义。

时间运算与差值

t + 3600          # 加 3600 秒
t - (24 * 3600)   # 减一天
(t2 - t1) / 60    # 相差多少分钟

日期运算用 Date 更直观:Date.today + 7 得到一周后。月份加减是陷阱:Date.new(2026,1,31) >> 1(加一个月)结果是 2 月 28 日而非 3 月 3 日——Ruby 会合理回退,但你的业务未必接受这个结果,涉及账单日、订阅周期的逻辑要单独定义规则。

Rails 提供的 1.day.ago、2.weeks.from_now、beginning_of_month 等 ActiveSupport 方法可读性更好,同样遵循上述语义。

常见问题(FAQ)

Q:数据库里该存 UTC 还是本地时间?

一律存 UTC。Rails 默认就是这么做的(config.time_zone 只影响展示层)。混存时区会让跨时区查询和数据迁移变成噩梦。

Q:为什么 Time == DateTime 比较结果是 false?

两者是不同类的实例,即使表示同一时刻也不相等。跨类比较前先统一转换:time.to_i == datetime.to_time.to_i,或用时间戳比较。

Q:如何计算两个日期之间的工作日?

标准库没有现成方法。简单场景用 (start..end).count { |d| (1..5).include?(d.wday) };要考虑节假日就引入 holidays gem 或维护业务日历表。


参考文档

本文依据公开文档整理,示例需按项目依赖版本验证;不将文档中的性能描述视为本项目实测结果。

延伸阅读

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台