CrackedRuby logo

CrackedRuby

DateTime Class

Technical documentation covering Ruby's DateTime class for precise date and time manipulation with timezone awareness and calendar calculations.

Core Built-in Classes Time and Date Classes
2.8.7

Overview

DateTime extends Ruby's Date class to include time-of-day information with microsecond precision. Ruby implements DateTime as part of the standard library, providing comprehensive date and time operations including parsing, formatting, arithmetic, and timezone handling. The class represents dates and times in the proleptic Gregorian calendar system.

DateTime objects are immutable. Each operation returns a new DateTime instance rather than modifying the existing object. The class stores date and time information internally as a rational number representing days since January 1, 4713 BCE in the Julian calendar system, with fractional portions representing the time-of-day component.

require 'date'

# Current date and time
now = DateTime.now
# => #<DateTime: 2025-08-29T14:30:45-05:00>

# Specific date and time
dt = DateTime.new(2025, 8, 29, 14, 30, 45)
# => #<DateTime: 2025-08-29T14:30:45+00:00>

# Parsing from string
parsed = DateTime.parse("2025-08-29 14:30:45")
# => #<DateTime: 2025-08-29T14:30:45+00:00>

DateTime provides three primary construction methods: new for explicit component specification, parse for string interpretation, and now for current system time. The class supports timezone offsets, daylight saving time calculations, and extensive formatting options through the strftime method.

The class integrates with Ruby's Rational and Integer classes for precise arithmetic operations. Adding or subtracting integers represents day differences, while Rational objects allow fractional day calculations for hours, minutes, and seconds.

# Date arithmetic
tomorrow = DateTime.now + 1
# => Tomorrow's date at same time

# Time arithmetic using rational numbers
one_hour_later = DateTime.now + Rational(1, 24)
# => One hour from now

# Component access
dt = DateTime.now
dt.year    # => 2025
dt.month   # => 8
dt.day     # => 29
dt.hour    # => 14
dt.minute  # => 30
dt.second  # => 45

Basic Usage

DateTime construction accepts year, month, day as required parameters, with optional hour, minute, second, offset, and start values. The offset parameter specifies timezone offset from UTC as a fractional day value. Ruby provides helper methods for common offset formats.

# Basic construction with date only
date_only = DateTime.new(2025, 12, 25)
# => #<DateTime: 2025-12-25T00:00:00+00:00>

# Full specification with timezone
christmas = DateTime.new(2025, 12, 25, 9, 30, 0, '-5')
# => #<DateTime: 2025-12-25T09:30:00-05:00>

# Using rational offset for precise timezone
mountain_time = DateTime.new(2025, 8, 29, 14, 30, 45, Rational(-7, 24))
# => #<DateTime: 2025-08-29T14:30:45-07:00>

# Current time with system timezone
local_now = DateTime.now
# => #<DateTime: 2025-08-29T14:30:45-05:00>

String parsing handles multiple date and time formats automatically. The parse method recognizes ISO 8601, RFC 2822, and common human-readable formats. For ambiguous formats, strptime provides explicit format specification using format codes.

# Automatic parsing of various formats
iso_format = DateTime.parse("2025-08-29T14:30:45-05:00")
rfc_format = DateTime.parse("Thu, 29 Aug 2025 14:30:45 -0500")
readable = DateTime.parse("August 29, 2025 2:30 PM")

# Explicit format parsing
custom_format = DateTime.strptime("29/08/2025 14:30", "%d/%m/%Y %H:%M")
# => #<DateTime: 2025-08-29T14:30:00+00:00>

# Parsing with timezone information
with_zone = DateTime.strptime("2025-08-29 14:30:45 EST", "%Y-%m-%d %H:%M:%S %Z")

Formatting output uses strftime with extensive format code support. Common patterns include ISO 8601 for APIs, localized formats for user interfaces, and custom formats for specific requirements. The method handles timezone display and locale-specific formatting.

dt = DateTime.new(2025, 8, 29, 14, 30, 45, '-5')

# Standard formats
dt.strftime("%Y-%m-%d %H:%M:%S")     # => "2025-08-29 14:30:45"
dt.strftime("%B %d, %Y at %I:%M %p") # => "August 29, 2025 at 02:30 PM"
dt.iso8601                           # => "2025-08-29T14:30:45-05:00"

# Custom business format
dt.strftime("%a %b %e, %Y - %l:%M %p %Z")
# => "Fri Aug 29, 2025 -  2:30 PM -05:00"

Date and time arithmetic operates through addition and subtraction of numeric values. Integers represent whole days, while Rational objects provide precise fractional day calculations. The class maintains timezone information through arithmetic operations.

base_time = DateTime.new(2025, 8, 29, 14, 30, 45)

# Day arithmetic
next_week = base_time + 7
last_month = base_time - 30

# Hour, minute, second arithmetic using rationals
two_hours_later = base_time + Rational(2, 24)
thirty_minutes_ago = base_time - Rational(30, 1440)
ten_seconds_forward = base_time + Rational(10, 86400)

# Mixed calculations
complex_calc = base_time + 5 + Rational(3, 24) + Rational(45, 1440)
# => 5 days, 3 hours, 45 minutes later

Error Handling & Debugging

DateTime parsing and construction raise ArgumentError for invalid parameters and Date::Error for calendar-specific issues. Common errors include invalid dates, malformed strings, and timezone specification problems. Proper error handling prevents application crashes and provides user feedback.

begin
  # Invalid date construction
  invalid_date = DateTime.new(2025, 2, 30)  # February 30th doesn't exist
rescue ArgumentError => e
  puts "Date construction failed: #{e.message}"
  # => "Date construction failed: invalid date"
end

begin
  # Parsing failure with malformed string
  bad_parse = DateTime.parse("not a date string")
rescue ArgumentError => e
  puts "Parsing failed: #{e.message}"
  # => "Parsing failed: invalid date"
end

# Validation helper method
def safe_date_construction(year, month, day, hour = 0, minute = 0, second = 0)
  DateTime.new(year, month, day, hour, minute, second)
rescue ArgumentError => e
  puts "Invalid date parameters: #{e.message}"
  nil
end

Timezone parsing errors occur frequently with ambiguous or invalid timezone specifications. The DateTime class handles numeric offsets reliably but struggles with named timezones without additional libraries. Validation prevents runtime errors in timezone-sensitive applications.

# Safe timezone parsing with validation
def parse_with_timezone_fallback(date_string)
  DateTime.parse(date_string)
rescue ArgumentError
  # Try parsing without timezone, then add UTC
  base_date = DateTime.parse(date_string.gsub(/\s+[A-Z]{3,4}$/, ''))
  puts "Warning: Timezone ignored, using UTC"
  base_date
end

# Example usage with problematic timezone
problematic = "2025-08-29 14:30:45 PST"
safe_result = parse_with_timezone_fallback(problematic)

# Debugging date parsing issues
def debug_date_parsing(input_string)
  puts "Attempting to parse: #{input_string.inspect}"
  
  result = DateTime.parse(input_string)
  puts "Success: #{result}"
  puts "Year: #{result.year}, Month: #{result.month}, Day: #{result.day}"
  puts "Hour: #{result.hour}, Minute: #{result.minute}, Second: #{result.second}"
  puts "Offset: #{result.offset}"
  result
rescue ArgumentError => e
  puts "Parse error: #{e.message}"
  puts "Try alternative parsing strategies"
  nil
end

Range and boundary validation becomes critical when processing user input or external data sources. DateTime handles dates from 4713 BCE forward but may encounter precision limits with extreme values. Validation ensures data integrity and prevents arithmetic errors.

# Date range validation
class DateTimeValidator
  EARLIEST_SUPPORTED = DateTime.new(-4712, 1, 1)
  LATEST_REASONABLE = DateTime.new(9999, 12, 31)
  
  def self.validate_range(datetime)
    unless datetime.between?(EARLIEST_SUPPORTED, LATEST_REASONABLE)
      raise ArgumentError, "Date outside supported range: #{datetime}"
    end
    datetime
  end
  
  def self.safe_parse_and_validate(input)
    parsed = DateTime.parse(input.to_s)
    validate_range(parsed)
  rescue ArgumentError => e
    puts "Validation error: #{e.message}"
    nil
  end
end

# Testing boundary conditions
test_dates = [
  "0001-01-01",
  "9999-12-31", 
  "2025-02-29",  # Not a leap year
  "2024-02-29"   # Valid leap year date
]

test_dates.each do |date_str|
  result = DateTimeValidator.safe_parse_and_validate(date_str)
  puts "#{date_str}: #{result ? 'Valid' : 'Invalid'}"
end

Production Patterns

DateTime objects in production applications require careful consideration of timezone handling, database storage, and user interface display. Applications typically standardize on UTC for internal storage while displaying localized times to users. This pattern prevents timezone confusion and daylight saving time issues.

# Production-ready DateTime service class
class DateTimeService
  UTC_OFFSET = 0
  
  def self.current_utc
    DateTime.now.new_offset(UTC_OFFSET)
  end
  
  def self.parse_user_input(input, user_timezone_offset)
    # Parse user input assuming their local timezone
    local_time = DateTime.parse(input)
    # Convert to UTC for storage
    local_time.new_offset(UTC_OFFSET) - Rational(user_timezone_offset, 24)
  end
  
  def self.display_to_user(utc_datetime, user_timezone_offset)
    # Convert UTC to user's local time for display
    utc_datetime.new_offset(Rational(user_timezone_offset, 24))
  end
  
  def self.format_for_api(datetime)
    datetime.strftime("%Y-%m-%dT%H:%M:%S.%3NZ")
  end
end

# Usage in web application context
user_timezone_offset = -5  # EST
user_input = "2025-08-29 2:30 PM"

stored_time = DateTimeService.parse_user_input(user_input, user_timezone_offset)
display_time = DateTimeService.display_to_user(stored_time, user_timezone_offset)
api_format = DateTimeService.format_for_api(stored_time)

Database integration patterns ensure consistent DateTime storage and retrieval across different database systems. Most databases support timestamp types with timezone information, but application-level timezone handling provides better control and portability.

# Database integration pattern
class EventRecord
  attr_accessor :id, :name, :scheduled_at_utc, :timezone_offset
  
  def initialize(name, local_datetime, timezone_offset)
    @name = name
    @timezone_offset = timezone_offset
    @scheduled_at_utc = convert_to_utc(local_datetime, timezone_offset)
  end
  
  def scheduled_at_local
    @scheduled_at_utc.new_offset(Rational(@timezone_offset, 24))
  end
  
  def scheduled_at_formatted(format = "%B %d, %Y at %I:%M %p")
    scheduled_at_local.strftime(format)
  end
  
  def time_until_event
    return "Event passed" if @scheduled_at_utc < DateTime.now
    
    diff_days = (@scheduled_at_utc - DateTime.now).to_f
    days = diff_days.floor
    hours = ((diff_days - days) * 24).floor
    
    if days > 0
      "#{days} days, #{hours} hours"
    else
      minutes = ((diff_days * 1440) % 60).floor
      "#{hours} hours, #{minutes} minutes"
    end
  end
  
  private
  
  def convert_to_utc(local_datetime, offset)
    local_datetime.new_offset(0) - Rational(offset, 24)
  end
end

# Production usage
conference_call = EventRecord.new(
  "Quarterly Review",
  DateTime.parse("2025-09-15 10:00"),
  -4  # EDT
)

puts conference_call.scheduled_at_formatted
puts "Time remaining: #{conference_call.time_until_event}"

Logging and monitoring patterns capture DateTime-related operations for debugging and performance analysis. Structured logging includes timezone information and operation context to facilitate troubleshooting in distributed systems.

require 'logger'
require 'json'

class DateTimeLogger
  def initialize
    @logger = Logger.new(STDOUT)
    @logger.formatter = proc do |severity, datetime, progname, msg|
      log_entry = {
        timestamp: datetime.strftime("%Y-%m-%dT%H:%M:%S.%3N%z"),
        level: severity,
        message: msg,
        service: progname
      }
      "#{log_entry.to_json}\n"
    end
  end
  
  def log_datetime_operation(operation, input, result, duration = nil)
    log_data = {
      operation: operation,
      input: input.to_s,
      result: result.to_s,
      input_timezone: input.respond_to?(:offset) ? input.offset.to_s : 'unknown',
      result_timezone: result.respond_to?(:offset) ? result.offset.to_s : 'unknown',
      duration_ms: duration
    }
    
    @logger.info("DateTime operation: #{log_data.to_json}")
  rescue => e
    @logger.error("Logging error: #{e.message}")
  end
  
  def timed_operation(operation_name, &block)
    start_time = Time.now
    result = yield
    duration = ((Time.now - start_time) * 1000).round(2)
    
    log_datetime_operation(operation_name, "timed_block", result, duration)
    result
  end
end

# Usage in production monitoring
logger = DateTimeLogger.new

parsed_date = logger.timed_operation("parse_user_date") do
  DateTime.parse("2025-08-29 14:30:45 -0500")
end

converted_date = logger.timed_operation("timezone_conversion") do
  parsed_date.new_offset(0)  # Convert to UTC
end

Reference

Constructor Methods

Method Parameters Returns Description
DateTime.new(year, month=1, day=1, hour=0, minute=0, second=0, offset=0, start=Date::ITALY) Integer year, month, day; optional hour, minute, second, offset, start DateTime Creates DateTime with specified components
DateTime.now(start=Date::ITALY) Optional calendar start date DateTime Current system date and time
DateTime.parse(string='-4712-01-01T00:00:00+00:00', comp=true, start=Date::ITALY) String to parse, completion flag, calendar start DateTime Parses string into DateTime object
DateTime.strptime(string='-4712-01-01T00:00:00+00:00', format='%FT%T%z', start=Date::ITALY) String, format specification, calendar start DateTime Parses with explicit format
DateTime.civil(year, month=1, day=1, hour=0, minute=0, second=0, offset=0, start=Date::ITALY) Same as new DateTime Alias for new method
DateTime.commercial(cwyear, cweek=1, cwday=1, hour=0, minute=0, second=0, offset=0, start=Date::ITALY) Commercial week components DateTime Creates from ISO week date
DateTime.jd(jd=0, hour=0, minute=0, second=0, offset=0, start=Date::ITALY) Julian day number and time components DateTime Creates from Julian day
DateTime.ordinal(year, yday=1, hour=0, minute=0, second=0, offset=0, start=Date::ITALY) Year and day-of-year DateTime Creates from ordinal date

Instance Methods - Component Access

Method Parameters Returns Description
#year None Integer Year component
#month None Integer Month component (1-12)
#day None Integer Day of month (1-31)
#hour None Integer Hour component (0-23)
#minute None Integer Minute component (0-59)
#second None Integer Second component (0-59)
#second_fraction None Rational Fractional seconds
#offset None Rational Timezone offset as fraction of day
#zone None String Timezone abbreviation
#wday None Integer Day of week (0=Sunday, 6=Saturday)
#yday None Integer Day of year (1-366)
#cweek None Integer ISO 8601 week number
#cwyear None Integer ISO 8601 week year
#cwday None Integer ISO 8601 day of week (1=Monday, 7=Sunday)

Instance Methods - Conversion and Formatting

Method Parameters Returns Description
#strftime(format) Format string String Formatted string representation
#iso8601(fraction_digits=0) Optional decimal places String ISO 8601 formatted string
#rfc3339(fraction_digits=0) Optional decimal places String RFC 3339 formatted string
#httpdate None String HTTP date format
#jisx0301 None String JIS X 0301 format
#to_date None Date Date portion only
#to_time None Time Convert to Time object
#to_datetime None DateTime Returns self
#to_s None String String representation
#inspect None String Detailed string representation

Instance Methods - Arithmetic and Comparison

Method Parameters Returns Description
#+(numeric) Numeric days to add DateTime Addition operation
#-(numeric_or_date) Days to subtract or DateTime for difference DateTime or Rational Subtraction operation
#<=>(other) DateTime or compatible object Integer Comparison operator
#==(other) DateTime or compatible object Boolean Equality comparison
#next None DateTime Next day at same time
#prev None DateTime Previous day at same time
#next_day(n=1) Days to advance DateTime Advance by specified days
#prev_day(n=1) Days to retreat DateTime Retreat by specified days
#next_month(n=1) Months to advance DateTime Advance by months
#prev_month(n=1) Months to retreat DateTime Retreat by months
#next_year(n=1) Years to advance DateTime Advance by years
#prev_year(n=1) Years to retreat DateTime Retreat by years

Instance Methods - Timezone and Calendar

Method Parameters Returns Description
#new_offset(offset=0) Rational or string offset DateTime Change timezone offset
#start None Integer Calendar start date
#julian? None Boolean Uses Julian calendar
#gregorian? None Boolean Uses Gregorian calendar
#leap? None Boolean Leap year status
#dst? None Boolean Daylight saving time status

Constants

Constant Value Description
Date::JULIAN 0 Julian calendar start
Date::GREGORIAN -1 Gregorian calendar start
Date::ITALY 2299161 Italian calendar reform date
Date::ENGLAND 2361222 English calendar reform date

Format Codes for strftime

Code Description Example
%Y 4-digit year 2025
%y 2-digit year 25
%m Month number (01-12) 08
%B Full month name August
%b Abbreviated month Aug
%d Day of month (01-31) 29
%H Hour 24-hour (00-23) 14
%I Hour 12-hour (01-12) 02
%M Minute (00-59) 30
%S Second (00-60) 45
%p AM/PM PM
%z Timezone offset -0500
%Z Timezone name EST
%A Full weekday name Friday
%a Abbreviated weekday Fri

Common Timezone Offsets

Timezone Offset Hours Rational Value
UTC 0 Rational(0, 24)
EST -5 Rational(-5, 24)
CST -6 Rational(-6, 24)
MST -7 Rational(-7, 24)
PST -8 Rational(-8, 24)
JST +9 Rational(9, 24)
CET +1 Rational(1, 24)