Date and Time
With no concept of time, our lives would be a mess. Without software programs to constantly manage and record this bizarre aspect of our universe…well, we might actually be better off. But why take the risk?
Some programs manage real-world time on behalf of the people who'd otherwise have to do it themselves: calendars, schedules, and data gatherers for scientific experiments. Other programs use the human concept of time for their own purposes: they may run experiments of their own, making decisions based on microsecond variations. Objects that have nothing to do with time are sometimes given timestamps recording when they were created or last modified. Of the basic data types, a time is the only one that directly corresponds to something in the real world.
Ruby supports the date and time interfaces you might be used to from other programming languages, but on top of them are Ruby-specific idioms that make programming easier. In this chapter, we'll show you how to use those interfaces and idioms, and how to fill in the gaps left by the language as it comes out of the box.
Ruby actually has two different time implementations. There's a set of time libraries written in C that have been around for decades. Like most modern programming languages, Ruby provides a native interface to these C libraries. The libraries are powerful, useful, and reliable, but they also have some significant shortcomings, so Ruby compensates with a second time library written in pure Ruby. The pure Ruby library isn't used for everything because it's slower than the C interface, and it lacks some of the features buried deep in the C library, such as the management of Daylight Saving Time.
The Time class contains Ruby's interface to the C libraries, and it's all you need for most applications. The Time class has a lot of Ruby idiom attached to it, but most of its methods have strange unRuby-like names like strftime and strptime. This is for the benefit of people who are already used to the C library, or one of its other interfaces (like Perl or Python's).
The internal representation of a Time object is a number of seconds before or since "time zero." Time zero for Ruby is the Unix epoch: the first second GMT of January 1, 1970. You can get the current local time with Time.now, or create a Time object from seconds-since-epoch with Time.at.
Time.now # => Sat Mar 18 14:49:30 EST 2006 Time.at(0) # => Wed Dec 31 19:00:00 EST 1969
This numeric internal representation of the time isn't very useful as a human-readable representation. You can get a string representation of a Time, as seen above, or call accessor methods to split up an instant of time according to how humans reckon time:
t = Time.at(0) t.sec # => 0 t.min # => 0 t.hour # => 19 t.day # => 31 t.month # => 12 t.year # => 1969 t.wday # => 3 # Numeric day of week; Sunday is 0 t.yday # => 365 # Numeric day of year t.isdst # => false # Is Daylight Saving Time in # effect? t.zone # => "EST" # Time zone
See Recipe 3.3 for more human-readable ways of slicing and dicing Time objects.
Apart from the awkward method and member names, the biggest shortcoming of the Time class is that on a 32-bit system, its underlying implementation can't handle dates before December 1901 or after January 2037.[1]
[1] A system with a 64-bit time_t can represent a much wider range of times (about half a trillion years):
Time.local(1865,4,9) # => Sun Apr 09 00:00:00 EWT 1865 Time.local(2100,1,1) # => Fri Jan 01 00:00:00 EST 2100
You'll still get into trouble with older times, though, because Time doesn't handle calendrical reform. It'll also give time zones to times that predate the creation of time zones (EWT stands for Eastern War Time, an American timezone used during World War II).
Time.local(1865, 4, 9) # ArgumentError: time out of range Time.local(2100, 1, 1) # ArgumentError: time out of range
To represent those times, you'll need to turn to Ruby's other time implementation: the Date and DateTime classes. You can probably use DateTime for everything, and not use Date at all:
require 'date' DateTime.new(1865, 4, 9).to_s # => "1865-04-09T00:00:00Z" DateTime.new(2100, 1, 1).to_s # => "2100-01-01T00:00:00Z"
Recall that a Time object is stored as a fractional number of seconds since a "time zero" in 1970. The internal representation of a Date or DateTime object is a astronomical Julian date: a fractional number of days since a "time zero" in 4712 BCE, over 6,000 years ago.
# Time zero for the date library: DateTime.new.to_s # => "-4712-01-01T00:00:00Z" # The current date and time: DateTime::now.to_s # => "2006-03-18T14:53:18-0500"
A DateTime object can precisely represent a time further in the past than the universe is old, or further in the future than the predicted lifetime of the universe. When DateTime handles historical dates, it needs to take into account the calendar reform movements that swept the Western world throughout the last 500 years. See Recipe 3.1 for more information on creating Date and DateTime objects.
Clearly DateTime is superior to Time for astronomical and historical applications, but you can use Time for most everyday programs. This table should give you a picture of the relative advantages of Time objects and DateTime objects.
Time |
DateTime |
|
---|---|---|
Date range |
19012037 on 32-bit systems |
Effectively infinite |
Handles Daylight Saving Time |
Yes |
No |
Handles calendar reform |
No |
Yes |
Time zone conversion |
Easy with the tz gem |
Difficult unless you only work with time zone offsets |
Common time formats like RFC822 |
Built-in |
Write them yourself |
Speed |
Faster |
Slower |
Both Time and DateTime objects support niceties like iteration and date arithmetic: you can basically treat them like numbers, because they're stored as numbers internally. But recall that a Time object is stored as a number of seconds, while a DateTime object is stored as a number of days, so the same operations will operate on different time scales on Time and DateTime objects. See Recipes 3.4 and 3.5 for more on this.
So far, we've talked about writing code to manage specific moments in time: a moment in the past or future, or right now. The other use of time is duration, the relationship between two times: "start" and "end," "before" and "after." You can measure duration by subtracting one DateTime object from another, or one Time object from another: you'll get a result measured in days or seconds (see Recipe 3.5). If you want your program to actually experience duration (the difference between now and a time in the future), you can put a thread to sleep for a certain amount of time: see Recipes 3.12 and 3.13.
You'll need duration most often, perhaps, during development. Benchmarking and profiling can measure how long your program took to run, and which parts of it took the longest. These topics are covered in Chapter 17: see Recipes 17.12 and 17.13.