Skip to content

Ruby 日期 & 时间

在编程中,正确处理日期和时间是非常重要的。Ruby提供了强大的日期和时间处理功能,包括内置的[Date](file:///D:/Workspace/Coding/VueProjects/tutorials-web/docs/ruby/../../../../../../Ruby30-x64/lib/ruby/3.0.0/date.rb#L75-L2470)、[Time](file:///D:/Workspace/Coding/VueProjects/tutorials-web/docs/ruby/../../../../../../Ruby30-x64/lib/ruby/3.0.0/time.rb#L1-L466)和[DateTime](file:///D:/Workspace/Coding/VueProjects/tutorials-web/docs/ruby/../../../../../../Ruby30-x64/lib/ruby/3.0.0/date.rb#L2472-L3114)类,以及更现代的[Time](file:///D:/Workspace/Coding/VueProjects/tutorials-web/docs/ruby/../../../../../../Ruby30-x64/lib/ruby/3.0.0/time.rb#L1-L466)类扩展。本章将详细介绍Ruby中日期和时间的创建、操作和处理方法。

🎯 日期和时间基础

引入必要的库

ruby
# Date和DateTime类需要显式引入
require 'date'

# Time类是内置的,但可以引入扩展功能
require 'time'

当前日期和时间

ruby
# 获取当前时间
current_time = Time.now
puts current_time  # 2023-12-25 14:30:45 +0800

# 获取当前日期
current_date = Date.today
puts current_date  # 2023-12-25

# 获取当前日期时间
current_datetime = DateTime.now
puts current_datetime  # 2023-12-25T14:30:45+08:00

# 使用Time获取日期部分
time_date = Time.now.to_date
puts time_date  # 2023-12-25

📅 Date类

创建Date对象

ruby
require 'date'

# 创建特定日期
date1 = Date.new(2023, 12, 25)
puts date1  # 2023-12-25

# 解析字符串创建日期
date2 = Date.parse("2023-12-25")
puts date2  # 2023-12-25

date3 = Date.parse("December 25, 2023")
puts date3  # 2023-12-25

# 从纪元日创建(从公元1年1月1日开始的天数)
date4 = Date.jd(2451545)  # 2000-01-01
puts date4  # 2000-01-01

# 从商业日期创建(年,周,星期)
date5 = Date.commercial(2023, 52, 1)  # 2023年第52周的星期一
puts date5  # 2023-12-25

# 从天文儒略日创建
date6 = Date.ordinal(2023, 359)  # 2023年第359天
puts date6  # 2023-12-25

Date属性和方法

ruby
date = Date.new(2023, 12, 25)

# 基本属性
puts date.year    # 2023
puts date.month   # 12
puts date.day     # 25
puts date.wday    # 1 (星期一,0表示星期日)
puts date.yday    # 359 (一年中的第几天)

# 星期信息
puts date.monday?    # true
puts date.sunday?    # false
puts date.strftime("%A")  # Monday

# 月份和季节
puts date.strftime("%B")  # December
puts date.mday           # 25 (月份中的天数)

# 闰年判断
puts date.leap?  # false (2023年不是闰年)

# 日期比较
date1 = Date.new(2023, 12, 25)
date2 = Date.new(2023, 12, 26)

puts date1 < date2   # true
puts date1 == date2  # false
puts date1 > date2   # false

# 日期运算
future_date = date + 10
puts future_date  # 2024-01-04

past_date = date - 5
puts past_date  # 2023-12-20

# 计算两个日期之间的天数
days_difference = (date2 - date1).to_i
puts days_difference  # 1

🕐 Time类

创建Time对象

ruby
# 当前时间
current_time = Time.now
puts current_time  # 2023-12-25 14:30:45 +0800

# 创建特定时间
time1 = Time.new(2023, 12, 25, 14, 30, 45)
puts time1  # 2023-12-25 14:30:45 +0800

# 指定时区
time2 = Time.new(2023, 12, 25, 14, 30, 45, "+09:00")
puts time2  # 2023-12-25 14:30:45 +0900

# 解析字符串创建时间
time3 = Time.parse("2023-12-25 14:30:45")
puts time3  # 2023-12-25 14:30:45 +0800

time4 = Time.parse("December 25, 2023 2:30 PM")
puts time4  # 2023-12-25 14:30:00 +0800

# 从纪元时间创建
epoch_time = Time.at(1703485845)
puts epoch_time  # 2023-12-25 14:30:45 +0800

# 本地时间和UTC时间
local_time = Time.local(2023, 12, 25, 14, 30, 45)
puts local_time  # 2023-12-25 14:30:45 +0800

utc_time = Time.utc(2023, 12, 25, 14, 30, 45)
puts utc_time  # 2023-12-25 14:30:45 UTC

Time属性和方法

ruby
time = Time.new(2023, 12, 25, 14, 30, 45, "+08:00")

# 基本属性
puts time.year    # 2023
puts time.month   # 12
puts time.day     # 25
puts time.hour    # 14
puts time.min     # 30
puts time.sec     # 45

# 星期信息
puts time.wday    # 1 (星期一)
puts time.yday    # 359 (一年中的第几天)

# 时间戳
puts time.to_i    # 1703485845 (秒级时间戳)
puts time.to_f    # 1703485845.0 (浮点数时间戳)

# 时区信息
puts time.zone    # +08:00
puts time.utc?    # false
puts time.utc_offset  # 28800 (秒数,+08:00 = 8 * 3600)

# 时间比较
time1 = Time.new(2023, 12, 25, 14, 30, 45)
time2 = Time.new(2023, 12, 25, 15, 0, 0)

puts time1 < time2   # true
puts time1 == time2  # false

# 时间运算
future_time = time + 3600  # 加1小时
puts future_time  # 2023-12-25 15:30:45 +0800

past_time = time - 1800  # 减30分钟
puts past_time  # 2023-12-25 14:00:45 +0800

# 计算时间差
seconds_difference = time2 - time1
puts seconds_difference  # 1755.0 (秒数)
puts seconds_difference / 60  # 29.25 (分钟数)

📆 DateTime类

创建DateTime对象

ruby
require 'date'

# 创建特定日期时间
datetime1 = DateTime.new(2023, 12, 25, 14, 30, 45)
puts datetime1  # 2023-12-25T14:30:45+00:00

# 指定时区
datetime2 = DateTime.new(2023, 12, 25, 14, 30, 45, "+08:00")
puts datetime2  # 2023-12-25T14:30:45+08:00

# 解析字符串创建日期时间
datetime3 = DateTime.parse("2023-12-25 14:30:45")
puts datetime3  # 2023-12-25T14:30:45+00:00

# 从纪元日创建
datetime4 = DateTime.jd(2451545.5)
puts datetime4  # 2000-01-01T12:00:00+00:00

# ISO 8601格式
datetime5 = DateTime.iso8601("2023-12-25T14:30:45+08:00")
puts datetime5  # 2023-12-25T14:30:45+08:00

DateTime属性和方法

ruby
datetime = DateTime.new(2023, 12, 25, 14, 30, 45, "+08:00")

# 基本属性
puts datetime.year    # 2023
puts datetime.month   # 12
puts datetime.day     # 25
puts datetime.hour    # 14
puts datetime.min     # 30
puts datetime.sec     # 45

# 精确的小数秒
puts datetime.sec_fraction  # (0/1) (秒的小数部分)

# 时区信息
puts datetime.zone    # +08:00
puts datetime.offset  # (1/3) (时区偏移量,+08:00 = 8/24 = 1/3)

# 星期信息
puts datetime.wday    # 1 (星期一)
puts datetime.yday    # 359 (一年中的第几天)

# 日期时间比较
dt1 = DateTime.new(2023, 12, 25, 14, 30, 45)
dt2 = DateTime.new(2023, 12, 25, 15, 0, 0)

puts dt1 < dt2   # true
puts dt1 == dt2  # false

# 日期时间运算
future_dt = datetime + Rational(1, 24)  # 加1小时 (1/24天)
puts future_dt  # 2023-12-25T15:30:45+08:00

past_dt = datetime - Rational(30, 1440)  # 减30分钟 (30/1440天)
puts past_dt  # 2023-12-25T14:00:45+08:00

🔄 日期时间格式化

strftime方法

ruby
time = Time.new(2023, 12, 25, 14, 30, 45, "+08:00")

# 常用格式
puts time.strftime("%Y-%m-%d")           # 2023-12-25
puts time.strftime("%H:%M:%S")           # 14:30:45
puts time.strftime("%Y-%m-%d %H:%M:%S")  # 2023-12-25 14:30:45

# 详细格式
puts time.strftime("%A, %B %d, %Y")     # Monday, December 25, 2023
puts time.strftime("%a, %b %d, %Y")     # Mon, Dec 25, 2023

# 中文格式
puts time.strftime("%Y年%m月%d日 %H时%M分%S秒")  # 2023年12月25日 14时30分45秒

# 12小时制
puts time.strftime("%I:%M:%S %p")       # 02:30:45 PM

# 带时区信息
puts time.strftime("%Y-%m-%d %H:%M:%S %z")  # 2023-12-25 14:30:45 +0800
puts time.strftime("%Y-%m-%d %H:%M:%S %Z")  # 2023-12-25 14:30:45 +08:00

# 星期和周信息
puts time.strftime("%A is day %w of the week")  # Monday is day 1 of the week
puts time.strftime("Day %j of the year")        # Day 359 of the year

# ISO 8601格式
puts time.strftime("%Y-%m-%dT%H:%M:%S%:z")      # 2023-12-25T14:30:45+08:00

解析日期时间字符串

ruby
require 'time'

# 解析各种格式的日期时间字符串
time_strings = [
  "2023-12-25 14:30:45",
  "December 25, 2023 2:30 PM",
  "2023/12/25 14:30:45",
  "Mon, 25 Dec 2023 14:30:45 +0800",
  "2023-12-25T14:30:45+08:00"
]

time_strings.each do |str|
  begin
    parsed_time = Time.parse(str)
    puts "#{str} => #{parsed_time}"
  rescue ArgumentError => e
    puts "无法解析: #{str} (#{e.message})"
  end
end

# ISO 8601格式解析
iso_string = "2023-12-25T14:30:45+08:00"
iso_time = Time.iso8601(iso_string)
puts iso_time  # 2023-12-25 14:30:45 +0800

# RFC 2822格式解析
rfc_string = "Mon, 25 Dec 2023 14:30:45 +0800"
rfc_time = Time.rfc2822(rfc_string)
puts rfc_time  # 2023-12-25 14:30:45 +0800

⏱️ 时间计算和操作

时间运算

ruby
# 时间加减运算
time = Time.now

# 加减秒数
future_time = time + 60  # 1分钟后
past_time = time - 300   # 5分钟前

# 加减天数
next_day = time + 86400  # 1天后 (24 * 60 * 60)
last_week = time - 604800  # 1周前 (7 * 24 * 60 * 60)

# 使用Rational进行精确计算
exact_time = time + Rational(1, 2)  # 0.5天 = 12小时后

# 计算时间差
time1 = Time.new(2023, 12, 25, 14, 30, 0)
time2 = Time.new(2023, 12, 25, 16, 45, 30)

difference = time2 - time1
puts "相差 #{difference} 秒"  # 相差 8130.0 秒

# 转换为更友好的格式
minutes = difference / 60
hours = minutes / 60
puts "相差 #{hours} 小时"  # 相差 2.2583333333333333 小时

时间范围和迭代

ruby
# 创建时间范围
start_time = Time.new(2023, 12, 25, 9, 0, 0)
end_time = Time.new(2023, 12, 25, 17, 0, 0)
time_range = start_time..end_time

# 检查时间是否在范围内
check_time = Time.new(2023, 12, 25, 12, 0, 0)
puts time_range.include?(check_time)  # true

# 迭代时间范围(按小时)
current = start_time
while current <= end_time
  puts current.strftime("%H:%M")
  current += 3600  # 加1小时
end

# 使用step方法
start_time.step(end_time, 3600) do |t|
  puts t.strftime("%H:%M")
end

工作日计算

ruby
class BusinessDays
  def self.add_business_days(start_date, days)
    current_date = start_date
    days_added = 0
    
    while days_added < days
      current_date += 1
      next if current_date.saturday? || current_date.sunday?
      days_added += 1
    end
    
    current_date
  end
  
  def self.business_days_between(start_date, end_date)
    return 0 if start_date > end_date
    
    business_days = 0
    current_date = start_date
    
    while current_date <= end_date
      unless current_date.saturday? || current_date.sunday?
        business_days += 1
      end
      current_date += 1
    end
    
    business_days
  end
end

# 使用工作日计算
start_date = Date.new(2023, 12, 25)  # 星期一
end_date = Date.new(2023, 12, 31)    # 星期日

# 计算5个工作日后的日期
future_date = BusinessDays.add_business_days(start_date, 5)
puts "5个工作日后: #{future_date}"  # 2023-12-39 => 2024-01-02

# 计算两个日期之间的工作日数
business_days = BusinessDays.business_days_between(start_date, end_date)
puts "工作日数: #{business_days}"  # 工作日数: 5

🌍 时区处理

时区转换

ruby
# 创建不同时区的时间
utc_time = Time.utc(2023, 12, 25, 14, 30, 45)
puts "UTC时间: #{utc_time}"  # UTC时间: 2023-12-25 14:30:45 UTC

# 转换为本地时间
local_time = utc_time.getlocal
puts "本地时间: #{local_time}"  # 本地时间: 2023-12-25 22:30:45 +0800

# 转换为指定时区
# 注意:Ruby标准库不直接支持时区转换,需要使用tzinfo等gem
# 这里演示概念:

class TimeZoneConverter
  # 简单的时区偏移转换
  def self.convert_timezone(time, offset_hours)
    # offset_hours: 正数表示东时区,负数表示西时区
    time + (offset_hours * 3600)
  end
end

# 从UTC转换到不同时区
beijing_time = TimeZoneConverter.convert_timezone(utc_time, 8)
puts "北京时间: #{beijing_time}"  # 北京时间: 2023-12-25 22:30:45 +0800

tokyo_time = TimeZoneConverter.convert_timezone(utc_time, 9)
puts "东京时间: #{tokyo_time}"  # 东京时间: 2023-12-25 23:30:45 +0800

new_york_time = TimeZoneConverter.convert_timezone(utc_time, -5)
puts "纽约时间: #{new_york_time}"  # 纽约时间: 2023-12-25 09:30:45 +0800

夏令时处理

ruby
# 夏令时相关的计算需要使用专门的库如tzinfo
# 这里展示基本概念:

class DSTHandler
  # 简化的夏令时判断(美国规则)
  def self.dst?(time)
    month = time.month
    return false if month < 3 || month > 11
    return true if month > 3 && month < 11
    
    # 3月的第二个星期日和11月的第一个星期日
    # 这里简化处理
    if month == 3
      time.day >= 8 && time.wday == 0  # 3月第二个星期日之后
    elsif month == 11
      time.day < 8 && time.wday == 0   # 11月第一个星期日之前
    end
  end
  
  # 应用夏令时偏移
  def self.apply_dst_offset(time, is_dst)
    if is_dst
      time + 3600  # 加1小时
    else
      time
    end
  end
end

# 使用夏令时处理
time = Time.new(2023, 7, 15, 14, 30, 45, "-05:00")  # 美国东部时间
is_dst = DSTHandler.dst?(time)
adjusted_time = DSTHandler.apply_dst_offset(time, is_dst)
puts "夏令时调整后: #{adjusted_time}"

🎯 实用示例

日志时间戳生成器

ruby
class TimestampGenerator
  # 生成标准日志时间戳
  def self.log_timestamp
    Time.now.strftime("%Y-%m-%d %H:%M:%S.%3N")
  end
  
  # 生成ISO 8601时间戳
  def self.iso_timestamp
    Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%3NZ")
  end
  
  # 生成文件友好时间戳
  def self.file_timestamp
    Time.now.strftime("%Y%m%d_%H%M%S")
  end
  
  # 生成人类可读时间戳
  def self.human_timestamp
    Time.now.strftime("%B %d, %Y at %I:%M %p")
  end
end

# 使用时间戳生成器
puts "日志时间戳: #{TimestampGenerator.log_timestamp}"      # 日志时间戳: 2023-12-25 14:30:45.123
puts "ISO时间戳: #{TimestampGenerator.iso_timestamp}"       # ISO时间戳: 2023-12-25T06:30:45.123Z
puts "文件时间戳: #{TimestampGenerator.file_timestamp}"     # 文件时间戳: 20231225_143045
puts "人类时间戳: #{TimestampGenerator.human_timestamp}"    # 人类时间戳: December 25, 2023 at 02:30 PM

时间计算器

ruby
class TimeCalculator
  # 计算年龄
  def self.age(birth_date)
    today = Date.today
    age = today.year - birth_date.year
    age -= 1 if today < birth_date.next_year(age)
    age
  end
  
  # 计算两个日期之间的完整年数、月数、天数
  def self.date_difference(start_date, end_date)
    years = end_date.year - start_date.year
    months = end_date.month - start_date.month
    days = end_date.day - start_date.day
    
    if days < 0
      months -= 1
      days += Date.new(start_date.year, start_date.month + 1, 0).day
    end
    
    if months < 0
      years -= 1
      months += 12
    end
    
    { years: years, months: months, days: days }
  end
  
  # 计算距离未来某个日期的天数
  def self.days_until(target_date)
    (target_date - Date.today).to_i
  end
  
  # 格式化持续时间
  def self.format_duration(seconds)
    days = (seconds / 86400).to_i
    hours = (seconds % 86400 / 3600).to_i
    minutes = (seconds % 3600 / 60).to_i
    seconds = (seconds % 60).to_i
    
    parts = []
    parts << "#{days}天" if days > 0
    parts << "#{hours}小时" if hours > 0
    parts << "#{minutes}分钟" if minutes > 0
    parts << "#{seconds}秒" if seconds > 0
    
    parts.join(" ")
  end
end

# 使用时间计算器
birth_date = Date.new(1990, 5, 15)
age = TimeCalculator.age(birth_date)
puts "年龄: #{age}岁"  # 年龄: 33岁

start_date = Date.new(2020, 1, 1)
end_date = Date.new(2023, 8, 15)
diff = TimeCalculator.date_difference(start_date, end_date)
puts "时间差: #{diff[:years]}#{diff[:months]}#{diff[:days]}天"  # 时间差: 3年7月14天

future_date = Date.new(2024, 1, 1)
days_until = TimeCalculator.days_until(future_date)
puts "距离#{future_date}还有#{days_until}天"

duration = 7890  # 秒数
formatted_duration = TimeCalculator.format_duration(duration)
puts "持续时间: #{formatted_duration}"  # 持续时间: 2小时11分钟30秒

时间验证器

ruby
class TimeValidator
  # 验证日期格式
  def self.valid_date?(date_string)
    Date.parse(date_string)
    true
  rescue ArgumentError
    false
  end
  
  # 验证时间格式
  def self.valid_time?(time_string)
    Time.parse(time_string)
    true
  rescue ArgumentError
    false
  end
  
  # 验证日期是否在合理范围内
  def self.reasonable_date?(date, min_year = 1900, max_year = 2100)
    return false unless date.is_a?(Date)
    date.year >= min_year && date.year <= max_year
  end
  
  # 验证时间是否在营业时间内
  def self.business_hours?(time, start_hour = 9, end_hour = 18)
    return false unless time.is_a?(Time)
    hour = time.hour
    hour >= start_hour && hour < end_hour
  end
  
  # 验证日期是否为工作日
  def self.weekday?(date)
    return false unless date.is_a?(Date)
    !(date.saturday? || date.sunday?)
  end
end

# 使用时间验证器
puts TimeValidator.valid_date?("2023-12-25")     # true
puts TimeValidator.valid_date?("invalid-date")   # false
puts TimeValidator.valid_time?("14:30:45")       # true
puts TimeValidator.valid_time?("25:00:00")       # false

date = Date.new(2023, 12, 25)
puts TimeValidator.reasonable_date?(date)        # true
puts TimeValidator.weekday?(date)                # true (星期一)

time = Time.new(2023, 12, 25, 14, 30, 45)
puts TimeValidator.business_hours?(time)         # true

📊 性能优化

高效的时间处理

ruby
# 避免重复创建时间对象
# 低效方式
def inefficient_time_check
  1000.times do
    if Time.now.hour > 12
      # 处理逻辑
    end
  end
end

# 高效方式
def efficient_time_check
  current_time = Time.now  # 只创建一次
  1000.times do
    if current_time.hour > 12
      # 处理逻辑
    end
  end
end

# 缓存时间计算结果
class TimeCache
  def initialize
    @cache = {}
    @last_update = Time.now
  end
  
  def current_date
    now = Time.now
    if now - @last_update > 1  # 超过1秒才更新
      @cache[:current_date] = now.to_date
      @last_update = now
    end
    @cache[:current_date]
  end
end

# 使用时间缓存
cache = TimeCache.new
puts cache.current_date  # 2023-12-25

批量时间处理

ruby
# 批量处理时间数据
def process_time_data(time_array)
  # 预先计算常用值
  base_time = Time.now
  one_day = 86400
  
  results = []
  time_array.each do |time_str|
    begin
      time = Time.parse(time_str)
      # 执行计算
      diff = (base_time - time).abs
      days = (diff / one_day).to_i
      results << { time: time, days_ago: days }
    rescue ArgumentError
      results << { time: time_str, error: "Invalid time format" }
    end
  end
  results
end

# 使用批量处理
time_strings = [
  "2023-12-20 10:30:00",
  "2023-12-22 15:45:30",
  "invalid-time",
  "2023-12-24 09:15:20"
]

results = process_time_data(time_strings)
results.each { |result| puts result }

🎯 最佳实践

1. 选择合适的时间类

ruby
# 只需要日期时使用Date
def calculate_age(birth_date_string)
  birth_date = Date.parse(birth_date_string)
  today = Date.today
  today.year - birth_date.year - ((today.month > birth_date.month || 
    (today.month == birth_date.month && today.day >= birth_date.day)) ? 0 : 1)
end

# 需要时间精度时使用Time
def log_user_activity(user_id, action)
  timestamp = Time.now
  puts "[#{timestamp.strftime('%Y-%m-%d %H:%M:%S')}] User #{user_id}: #{action}"
end

# 需要高精度和时区支持时使用DateTime
def schedule_event(event_name, datetime_string)
  event_time = DateTime.parse(datetime_string)
  puts "事件 '#{event_name}' 安排在 #{event_time.strftime('%Y-%m-%d %H:%M:%S %z')}"
end

2. 时区处理最佳实践

ruby
class TimeZoneBestPractice
  # 始终以UTC存储时间
  def self.store_as_utc(local_time)
    local_time.utc
  end
  
  # 显示时转换为本地时区
  def self.display_in_timezone(utc_time, timezone_offset = 8)
    utc_time.getlocal(timezone_offset * 3600)
  end
  
  # 处理用户输入的时间
  def self.parse_user_input(time_string, user_timezone = "+08:00")
    # 假设用户输入的时间是其本地时间
    time = Time.parse(time_string)
    # 转换为UTC存储
    time.utc
  end
end

# 使用时区最佳实践
user_input = "2023-12-25 14:30:00"
utc_time = TimeZoneBestPractice.parse_user_input(user_input)
puts "存储的UTC时间: #{utc_time}"

display_time = TimeZoneBestPractice.display_in_timezone(utc_time)
puts "显示的本地时间: #{display_time}"

3. 时间格式化最佳实践

ruby
class TimeFormattingBestPractice
  # 定义标准格式常量
  LOG_FORMAT = "%Y-%m-%d %H:%M:%S.%3N"
  ISO_FORMAT = "%Y-%m-%dT%H:%M:%S.%3NZ"
  HUMAN_FORMAT = "%B %d, %Y at %I:%M %p"
  
  # 标准化日志时间戳
  def self.log_timestamp(time = Time.now)
    time.strftime(LOG_FORMAT)
  end
  
  # 标准化ISO时间戳
  def self.iso_timestamp(time = Time.now)
    time.utc.strftime(ISO_FORMAT)
  end
  
  # 标准化人类可读时间戳
  def self.human_timestamp(time = Time.now)
    time.strftime(HUMAN_FORMAT)
  end
  
  # 解析标准时间格式
  def self.parse_standard(time_string)
    Time.parse(time_string)
  rescue ArgumentError
    nil
  end
end

# 使用格式化最佳实践
current_time = Time.now
puts "日志格式: #{TimeFormattingBestPractice.log_timestamp(current_time)}"
puts "ISO格式: #{TimeFormattingBestPractice.iso_timestamp(current_time)}"
puts "人类格式: #{TimeFormattingBestPractice.human_timestamp(current_time)}"

📚 下一步学习

掌握了Ruby日期和时间处理后,建议继续学习:

继续您的Ruby学习之旅吧!

本站内容仅供学习和研究使用。