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) |