Ruby Date & Time
Proper handling of dates and times is crucial in programming. Ruby provides powerful date and time processing capabilities, including built-in Date, Time, and DateTime classes, as well as modern Time class extensions. This chapter will provide a detailed introduction to creating, manipulating, and processing dates and times in Ruby.
🎯 Date and Time Basics
Required Libraries
ruby
# Date and DateTime classes need explicit require
require 'date'
# Time class is built-in, but can include extended functionality
require 'time'Current Date and Time
ruby
# Get current time
current_time = Time.now
puts current_time # 2023-12-25 14:30:45 +0800
# Get current date
current_date = Date.today
puts current_date # 2023-12-25
# Get current datetime
current_datetime = DateTime.now
puts current_datetime # 2023-12-25T14:30:45+08:00
# Use Time to get date portion
time_date = Time.now.to_date
puts time_date # 2023-12-25📅 Date Class
Creating Date Objects
ruby
require 'date'
# Create specific date
date1 = Date.new(2023, 12, 25)
puts date1 # 2023-12-25
# Parse string to create date
date2 = Date.parse("2023-12-25")
puts date2 # 2023-12-25
date3 = Date.parse("December 25, 2023")
puts date3 # 2023-12-25
# Create from epoch day (days since January 1, year 1)
date4 = Date.jd(2451545) # 2000-01-01
puts date4 # 2000-01-01
# Create from commercial date (year, week, day of week)
date5 = Date.commercial(2023, 52, 1) # Monday of week 52, 2023
puts date5 # 2023-12-25
# Create from astronomical ordinal day
date6 = Date.ordinal(2023, 359) # Day 359 of 2023
puts date6 # 2023-12-25Date Properties and Methods
ruby
date = Date.new(2023, 12, 25)
# Basic properties
puts date.year # 2023
puts date.month # 12
puts date.day # 25
puts date.wday # 1 (Monday, 0 = Sunday)
puts date.yday # 359 (day of year)
# Weekday information
puts date.monday? # true
puts date.sunday? # false
puts date.strftime("%A") # Monday
# Month and season
puts date.strftime("%B") # December
puts date.mday # 25 (day of month)
# Leap year check
puts date.leap? # false (2023 is not a leap year)
# Date comparison
date1 = Date.new(2023, 12, 25)
date2 = Date.new(2023, 12, 26)
puts date1 < date2 # true
puts date1 == date2 # false
puts date1 > date2 # false
# Date arithmetic
future_date = date + 10
puts future_date # 2024-01-04
past_date = date - 5
puts past_date # 2023-12-20
# Calculate days between two dates
days_difference = (date2 - date1).to_i
puts days_difference # 1🕐 Time Class
Creating Time Objects
ruby
# Current time
current_time = Time.now
puts current_time # 2023-12-25 14:30:45 +0800
# Create specific time
time1 = Time.new(2023, 12, 25, 14, 30, 45)
puts time1 # 2023-12-25 14:30:45 +0800
# Specify timezone
time2 = Time.new(2023, 12, 25, 14, 30, 45, "+09:00")
puts time2 # 2023-12-25 14:30:45 +0900
# Parse string to create time
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
# Create from epoch time
epoch_time = Time.at(1703485845)
puts epoch_time # 2023-12-25 14:30:45 +0800
# Local time and UTC time
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 UTCTime Properties and Methods
ruby
time = Time.new(2023, 12, 25, 14, 30, 45, "+08:00")
# Basic properties
puts time.year # 2023
puts time.month # 12
puts time.day # 25
puts time.hour # 14
puts time.min # 30
puts time.sec # 45
# Weekday information
puts time.wday # 1 (Monday)
puts time.yday # 359 (day of year)
# Timestamp
puts time.to_i # 1703485845 (second timestamp)
puts time.to_f # 1703485845.0 (floating point timestamp)
# Timezone information
puts time.zone # +08:00
puts time.utc? # false
puts time.utc_offset # 28800 (seconds, +08:00 = 8 * 3600)
# Time comparison
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
# Time arithmetic
future_time = time + 3600 # Add 1 hour
puts future_time # 2023-12-25 15:30:45 +0800
past_time = time - 1800 # Subtract 30 minutes
puts past_time # 2023-12-25 14:00:45 +0800
# Calculate time difference
seconds_difference = time2 - time1
puts seconds_difference # 1755.0 (seconds)
puts seconds_difference / 60 # 29.25 (minutes)📆 DateTime Class
Creating DateTime Objects
ruby
require 'date'
# Create specific datetime
datetime1 = DateTime.new(2023, 12, 25, 14, 30, 45)
puts datetime1 # 2023-12-25T14:30:45+00:00
# Specify timezone
datetime2 = DateTime.new(2023, 12, 25, 14, 30, 45, "+08:00")
puts datetime2 # 2023-12-25T14:30:45+08:00
# Parse string to create datetime
datetime3 = DateTime.parse("2023-12-25 14:30:45")
puts datetime3 # 2023-12-25T14:30:45+00:00
# Create from Julian day
datetime4 = DateTime.jd(2451545.5)
puts datetime4 # 2000-01-01T12:00:00+00:00
# ISO 8601 format
datetime5 = DateTime.iso8601("2023-12-25T14:30:45+08:00")
puts datetime5 # 2023-12-25T14:30:45+08:00DateTime Properties and Methods
ruby
datetime = DateTime.new(2023, 12, 25, 14, 30, 45, "+08:00")
# Basic properties
puts datetime.year # 2023
puts datetime.month # 12
puts datetime.day # 25
puts datetime.hour # 14
puts datetime.min # 30
puts datetime.sec # 45
# Precise fractional seconds
puts datetime.sec_fraction # (0/1) (fractional part of seconds)
# Timezone information
puts datetime.zone # +08:00
puts datetime.offset # (1/3) (timezone offset, +08:00 = 8/24 = 1/3)
# Weekday information
puts datetime.wday # 1 (Monday)
puts datetime.yday # 359 (day of year)
# Datetime comparison
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
# Datetime arithmetic
future_dt = datetime + Rational(1, 24) # Add 1 hour (1/24 day)
puts future_dt # 2023-12-25T15:30:45+08:00
past_dt = datetime - Rational(30, 1440) # Subtract 30 minutes (30/1440 day)
puts past_dt # 2023-12-25T14:00:45+08:00🔄 DateTime Formatting
strftime Method
ruby
time = Time.new(2023, 12, 25, 14, 30, 45, "+08:00")
# Common formats
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
# Detailed formats
puts time.strftime("%A, %B %d, %Y") # Monday, December 25, 2023
puts time.strftime("%a, %b %d, %Y") # Mon, Dec 25, 2023
# Chinese format
puts time.strftime("%Y年%m月%d日 %H时%M分%S秒") # 2023年12月25日 14时30分45秒
# 12-hour format
puts time.strftime("%I:%M:%S %p") # 02:30:45 PM
# With timezone information
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
# Week and week information
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 format
puts time.strftime("%Y-%m-%dT%H:%M:%S%:z") # 2023-12-25T14:30:45+08:00Parsing DateTime Strings
ruby
require 'time'
# Parse various datetime string formats
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 "Cannot parse: #{str} (#{e.message})"
end
end
# ISO 8601 format parsing
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 format parsing
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⏱️ Time Calculations and Operations
Time Arithmetic
ruby
# Time addition and subtraction
time = Time.now
# Add/subtract seconds
future_time = time + 60 # 1 minute later
past_time = time - 300 # 5 minutes ago
# Add/subtract days
next_day = time + 86400 # 1 day later (24 * 60 * 60)
last_week = time - 604800 # 1 week ago (7 * 24 * 60 * 60)
# Use Rational for precise calculation
exact_time = time + Rational(1, 2) # 0.5 days = 12 hours later
# Calculate time difference
time1 = Time.new(2023, 12, 25, 14, 30, 0)
time2 = Time.new(2023, 12, 25, 16, 45, 30)
difference = time2 - time1
puts "Difference: #{difference} seconds" # Difference: 8130.0 seconds
# Convert to more friendly format
minutes = difference / 60
hours = minutes / 60
puts "Difference: #{hours} hours" # Difference: 2.2583333333333333 hoursTime Range and Iteration
ruby
# Create time range
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 if time is in range
check_time = Time.new(2023, 12, 25, 12, 0, 0)
puts time_range.include?(check_time) # true
# Iterate time range (by hour)
current = start_time
while current <= end_time
puts current.strftime("%H:%M")
current += 3600 # Add 1 hour
end
# Use step method
start_time.step(end_time, 3600) do |t|
puts t.strftime("%H:%M")
endBusiness Days Calculation
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
# Using business days calculation
start_date = Date.new(2023, 12, 25) # Monday
end_date = Date.new(2023, 12, 31) # Sunday
# Calculate date 5 business days later
future_date = BusinessDays.add_business_days(start_date, 5)
puts "5 business days later: #{future_date}" # 2023-12-39 => 2024-01-02
# Calculate business days between two dates
business_days = BusinessDays.business_days_between(start_date, end_date)
puts "Business days: #{business_days}" # Business days: 5🌍 Timezone Handling
Timezone Conversion
ruby
# Create time in different timezones
utc_time = Time.utc(2023, 12, 25, 14, 30, 45)
puts "UTC time: #{utc_time}" # UTC time: 2023-12-25 14:30:45 UTC
# Convert to local time
local_time = utc_time.getlocal
puts "Local time: #{local_time}" # Local time: 2023-12-25 22:30:45 +0800
# Convert to specified timezone
# Note: Ruby standard library doesn't directly support timezone conversion, need to use gems like tzinfo
# Here's demonstrating the concept:
class TimeZoneConverter
# Simple timezone offset conversion
def self.convert_timezone(time, offset_hours)
# offset_hours: positive for eastern timezones, negative for western timezones
time + (offset_hours * 3600)
end
end
# Convert from UTC to different timezones
beijing_time = TimeZoneConverter.convert_timezone(utc_time, 8)
puts "Beijing time: #{beijing_time}" # Beijing time: 2023-12-25 22:30:45 +0800
tokyo_time = TimeZoneConverter.convert_timezone(utc_time, 9)
puts "Tokyo time: #{tokyo_time}" # Tokyo time: 2023-12-25 23:30:45 +0900
new_york_time = TimeZoneConverter.convert_timezone(utc_time, -5)
puts "New York time: #{new_york_time}" # New York time: 2023-12-25 09:30:45 +0800Daylight Saving Time Handling
ruby
# Daylight saving time calculations require specialized libraries like tzinfo
# Here showing basic concepts:
class DSTHandler
# Simplified DST check (US rules)
def self.dst?(time)
month = time.month
return false if month < 3 || month > 11
return true if month > 3 && month < 11
# Second Sunday of March and first Sunday of November
# Simplified handling here
if month == 3
time.day >= 8 && time.wday == 0 # After second Sunday of March
elsif month == 11
time.day < 8 && time.wday == 0 # Before first Sunday of November
end
end
# Apply DST offset
def self.apply_dst_offset(time, is_dst)
if is_dst
time + 3600 # Add 1 hour
else
time
end
end
end
# Using DST handling
time = Time.new(2023, 7, 15, 14, 30, 45, "-05:00") # US Eastern Time
is_dst = DSTHandler.dst?(time)
adjusted_time = DSTHandler.apply_dst_offset(time, is_dst)
puts "After DST adjustment: #{adjusted_time}"🎯 Practical Examples
Log Timestamp Generator
ruby
class TimestampGenerator
# Generate standard log timestamp
def self.log_timestamp
Time.now.strftime("%Y-%m-%d %H:%M:%S.%3N")
end
# Generate ISO 8601 timestamp
def self.iso_timestamp
Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%3NZ")
end
# Generate file-friendly timestamp
def self.file_timestamp
Time.now.strftime("%Y%m%d_%H%M%S")
end
# Generate human-readable timestamp
def self.human_timestamp
Time.now.strftime("%B %d, %Y at %I:%M %p")
end
end
# Using timestamp generator
puts "Log timestamp: #{TimestampGenerator.log_timestamp}" # Log timestamp: 2023-12-25 14:30:45.123
puts "ISO timestamp: #{TimestampGenerator.iso_timestamp}" # ISO timestamp: 2023-12-25T06:30:45.123Z
puts "File timestamp: #{TimestampGenerator.file_timestamp}" # File timestamp: 20231225_143045
puts "Human timestamp: #{TimestampGenerator.human_timestamp}" # Human timestamp: December 25, 2023 at 02:30 PMTime Calculator
ruby
class TimeCalculator
# Calculate age
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
# Calculate full years, months, days between two dates
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
# Calculate days until a future date
def self.days_until(target_date)
(target_date - Date.today).to_i
end
# Format duration
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} days" if days > 0
parts << "#{hours} hours" if hours > 0
parts << "#{minutes} minutes" if minutes > 0
parts << "#{seconds} seconds" if seconds > 0
parts.join(" ")
end
end
# Using time calculator
birth_date = Date.new(1990, 5, 15)
age = TimeCalculator.age(birth_date)
puts "Age: #{age} years old" # Age: 33 years old
start_date = Date.new(2020, 1, 1)
end_date = Date.new(2023, 8, 15)
diff = TimeCalculator.date_difference(start_date, end_date)
puts "Date difference: #{diff[:years]} years #{diff[:months]} months #{diff[:days]} days" # Date difference: 3 years 7 months 14 days
future_date = Date.new(2024, 1, 1)
days_until = TimeCalculator.days_until(future_date)
puts "Days until #{future_date}: #{days_until}"
duration = 7890 # seconds
formatted_duration = TimeCalculator.format_duration(duration)
puts "Duration: #{formatted_duration}" # Duration: 2 hours 11 minutes 30 secondsTime Validator
ruby
class TimeValidator
# Validate date format
def self.valid_date?(date_string)
Date.parse(date_string)
true
rescue ArgumentError
false
end
# Validate time format
def self.valid_time?(time_string)
Time.parse(time_string)
true
rescue ArgumentError
false
end
# Validate if date is within reasonable range
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
# Validate if time is within business hours
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
# Validate if date is a weekday
def self.weekday?(date)
return false unless date.is_a?(Date)
!(date.saturday? || date.sunday?)
end
end
# Using time validator
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 (Monday)
time = Time.new(2023, 12, 25, 14, 30, 45)
puts TimeValidator.business_hours?(time) # true📊 Performance Optimization
Efficient Time Processing
ruby
# Avoid creating time objects repeatedly
# Inefficient way
def inefficient_time_check
1000.times do
if Time.now.hour > 12
# Processing logic
end
end
end
# Efficient way
def efficient_time_check
current_time = Time.now # Create only once
1000.times do
if current_time.hour > 12
# Processing logic
end
end
end
# Cache time calculation results
class TimeCache
def initialize
@cache = {}
@last_update = Time.now
end
def current_date
now = Time.now
if now - @last_update > 1 # Update after more than 1 second
@cache[:current_date] = now.to_date
@last_update = now
end
@cache[:current_date]
end
end
# Using time cache
cache = TimeCache.new
puts cache.current_date # 2023-12-25Batch Time Processing
ruby
# Batch process time data
def process_time_data(time_array)
# Pre-calculate commonly used values
base_time = Time.now
one_day = 86400
results = []
time_array.each do |time_str|
begin
time = Time.parse(time_str)
# Perform calculation
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
# Using batch processing
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 }🎯 Best Practices
1. Choose the Right Time Class
ruby
# Use Date when only date is needed
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
# Use Time when time precision is needed
def log_user_activity(user_id, action)
timestamp = Time.now
puts "[#{timestamp.strftime('%Y-%m-%d %H:%M:%S')}] User #{user_id}: #{action}"
end
# Use DateTime when high precision and timezone support are needed
def schedule_event(event_name, datetime_string)
event_time = DateTime.parse(datetime_string)
puts "Event '#{event_name}' scheduled at #{event_time.strftime('%Y-%m-%d %H:%M:%S %z')}"
end2. Timezone Handling Best Practices
ruby
class TimeZoneBestPractice
# Always store time in UTC
def self.store_as_utc(local_time)
local_time.utc
end
# Convert to local timezone for display
def self.display_in_timezone(utc_time, timezone_offset = 8)
utc_time.getlocal(timezone_offset * 3600)
end
# Process user input time
def self.parse_user_input(time_string, user_timezone = "+08:00")
# Assume user input is their local time
time = Time.parse(time_string)
# Convert to UTC for storage
time.utc
end
end
# Using timezone best practices
user_input = "2023-12-25 14:30:00"
utc_time = TimeZoneBestPractice.parse_user_input(user_input)
puts "Stored UTC time: #{utc_time}"
display_time = TimeZoneBestPractice.display_in_timezone(utc_time)
puts "Displayed local time: #{display_time}"3. Time Formatting Best Practices
ruby
class TimeFormattingBestPractice
# Define standard format constants
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"
# Standardize log timestamp
def self.log_timestamp(time = Time.now)
time.strftime(LOG_FORMAT)
end
# Standardize ISO timestamp
def self.iso_timestamp(time = Time.now)
time.utc.strftime(ISO_FORMAT)
end
# Standardize human-readable timestamp
def self.human_timestamp(time = Time.now)
time.strftime(HUMAN_FORMAT)
end
# Parse standard time format
def self.parse_standard(time_string)
Time.parse(time_string)
rescue ArgumentError
nil
end
end
# Using formatting best practices
current_time = Time.now
puts "Log format: #{TimeFormattingBestPractice.log_timestamp(current_time)}"
puts "ISO format: #{TimeFormattingBestPractice.iso_timestamp(current_time)}"
puts "Human format: #{TimeFormattingBestPractice.human_timestamp(current_time)}"📚 Next Steps
After mastering Ruby date and time processing, continue learning:
- Ruby Ranges - Learn about range objects
- Ruby Iterators - Deep dive into iteration patterns
- Ruby Exception Handling - Master error handling mechanisms
- Ruby File I/O - Learn file read/write operations
Continue your Ruby learning journey!