Chapter 4. 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 idioms attached to it, but most of its methods have strange un-Ruby-like names, such as 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’s 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
# => 2013-10-03 15:13:50 -0700
Time
.
at
(
0
)
# => 1969-12-31 16:00:00 -0800
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 just shown, 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 4.3 for more human-readable ways of slicing and dicing Time
objects.
In Ruby 2.1, the Time
implementation uses a signed 63-bit integer, Bignum
or Rational
. The integer is a number of nanoseconds since the epoch, which can represent 1823-11-12 to 2116-02-20. When Bignum
or Rational
is used (before 1823, after 2116, under a nanosecond), Time
works slower than when Integer
is used.
In Ruby 1.8, 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. 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 “time zero” in 1970. The internal representation of a Date
or DateTime
object is an 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 4.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. Table 4-1 should give you a picture of the relative advantages of Time
objects and DateTime
objects.
Time | DateTime | |
---|---|---|
Date range |
1901–2037 on 32-bit systems |
Effectively infinite |
Handles Daylight Saving Time |
Yes |
No |
Handles calendar reform |
No |
Yes |
Time zone conversion |
Easy with the |
Difficult unless you work only 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 4.4 and 4.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 4.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 4.12 and 4.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 Recipe 19.12 and Recipe 19.13.
4.1 Finding Today’s Date
Solution
The factory method Time.now
creates a Time
object containing the current local time. If you want, you can then convert it to GMT time by calling Time#gmtime
. The gmtime
method actually modifies the underlying time object, though it doesn’t follow the Ruby naming conventions for such methods (it should be called something like gmtime
!):
now
=
Time
.
now
# => 2013-10-03 15:21:59 -0700
now
.
gmtime
# => 2013-10-03 22:21:59 UTC
#The original object was affected by the time zone conversion.
now
# => 2013-10-03 22:21:59 UTC
To create a DateTime
object for the current local time, use the factory method DateTime.now
. Convert a DateTime
object to GMT by calling DateTime#new_offset
with no argument. Unlike Time#gmtime
, this method returns a second DateTime
object instead of modifying the original in place:
require
'date'
now
=
DateTime
.
now
# => #<DateTime: 2013-10-03T15:20:35-07:00>
now
.
to_s
# => "2013-10-03T15:20:35-07:00"
now
.
new_offset
.
to_s
# => "2013-10-03T22:20:35+00:00"
#The original object was not affected by the time zone conversion.
now
.
to_s
# => "2013-10-03T15:20:35-07:00"
Discussion
Both the Time
and DateTime
objects provide accessor methods for the basic ways in which the Western calendar and clock divide a moment in time. Both classes provide year
,
month
, day
, hour
(in 24-hour format), min
, sec
, and zone
accessors. Time#isdst
lets you know if the underlying time of a Time
object has been modified by Daylight Saving Time in its time zone. DateTime
pretends Daylight Saving Time doesn’t exist:
now_time
=
Time
.
new
now_datetime
=
DateTime
.
now
now_time
.
year
# => 2013
now_datetime
.
year
# => 2013
now_time
.
hour
# => 18
now_datetime
.
hour
# => 22
now_time
.
zone
# => "PDT"
now_datetime
.
zone
# => "-07:00"
now_time
.
isdst
# => true
You can see that Time#zone
and DateTime#zone
are a little different. Time#zone
returns a time zone name or abbreviation, and DateTime#zone
returns a numeric offset from GMT in string form. You can call DateTime#offset
to get the GMT offset as a number: a fraction of a day:
now_datetime
.
offset
# => (-7/24)
Both classes can also represent fractions of a second, accessible with Time#usec
(that is, μsec or microseconds) and DateTime#sec_fraction
. In the preceding example, the DateTime
object was created after the Time
object, so the numbers are different even though both objects were created within the same second:
now_time
.
usec
# => 180167
# That is, 180167 microseconds
now_datetime
.
sec_fraction
# => (2057/12500)
The date
library provides a Date
class that is like a DateTime
, without the time. To create a Date
object containing the current date, the best strategy is to create a DateTime
object and use the result in a call to a Date
factory method. DateTime
is actually a subclass of Date
, so you need to do this only if you want to strip time data to make sure it doesn’t get used:
class
Date
def
Date
.
now
return
Date
.
jd
(
DateTime
.
now
.
jd
)
end
end
puts
Date
.
now
# 2013-10-03
In addition to creating a time object for this very moment, you can create one from a string (see Recipe 4.2) or from another time object (see Recipe 4.5). You can also use factory methods to create a time object from its calendar and clock parts: the year, month, day, and so on.
The factory methods Time.local
and Time.gm
take arguments Time
object for that time. For local time, use Time.local
; for GMT, use Time.gm
. All arguments after the year are optional and default to zero:
Time
.
local
(
1999
,
12
,
31
,
23
,
21
,
5
,
1044
)
# => 1999-12-31 23:21:05 -0800
Time
.
gm
(
1999
,
12
,
31
,
23
,
21
,
5
,
22
,
1044
)
# => 1999-12-31 23:21:05 UTC
Time
.
local
(
1991
,
10
,
1
)
# => 1991-10-01 00:00:00 -0700
Time
.
gm
(
2000
)
# => 2000-01-01 00:00:00 UTC
The DateTime
equivalent of Time.local
is the civil
factory method. It takes almost, but not quite, the same arguments as Time.local
:
[
year
,
month
,
day
,
hour
,
minute
,
second
,
timezone_offset
,
date_of_calendar_reform
].
The main differences from Time.local
and Time.gmt
are:
-
There’s no separate
usec
argument for fractions of a second. You can represent fractions of a second by passing in a rational number forsecond
. -
All the arguments are optional. However, the default year is 4712 BCE, which is probably not useful to you.
-
Rather than providing different methods for different time zones, you must pass in an offset from GMT as a fraction of a day. The default is zero, which means that calling
DateTime.civil
with no time zone will give you a time in GMT.
DateTime
.
civil
(
1999
,
12
,
31
,
23
,
21
,
Rational
(
51044
,
100000
))
.
to_s
# => "1999-12-31T23:21:00+00:00"
DateTime
.
civil
(
1991
,
10
,
1
)
.
to_s
# => "1991-10-01T00:00:00+00:00"
DateTime
.
civil
(
2000
)
.
to_s
# => "2000-01-01T00:00:00+00:00"
The simplest way to get the GMT offset for your local time zone is to call offset
on the result of DateTime.now
. Then you can pass the offset into DateTime.civil
:
my_offset
=
DateTime
.
now
.
offset
# => (-7/24)
DateTime
.
civil
(
1999
,
12
,
31
,
23
,
21
,
Rational
(
51044
,
100000
),
my_offset
)
.
to_s
# => "1999-12-31T23:21:00-07:00"
Oh, and there’s the calendar reform thing, too. If you’re using old dates, you may run into a gap caused by a switch from the Julian calendar (which made every fourth year a leap year) to the more accurate Gregorian calendar (which occasionally skips leap years).
This switch happened at different times in different countries, creating differently sized gaps as the local calendar absorbed the extra leap days caused by using the Julian reckoning for so many centuries. Dates created within a particular country’s gap are invalid for that country.
By default, Ruby assumes that Date
objects you create are relative to the Italian calendar, which switched to Gregorian reckoning in 1582. For American and Commonwealth users, Ruby has provided a constant Date::ENGLAND
, which corresponds to the date that England and its colonies adopted the Gregorian calendar. DateTime
’s constructors and factory methods will accept Date::ENGLAND
or Date::ITALY
as an extra argument denoting when calendar reform started in that country. The calendar reform argument can also be any Julian day, letting you handle old dates from any country:
#In Italy, 4 Oct 1582 was immediately followed by 15 Oct 1582.
#
Date
.
new
(
1582
,
10
,
4
)
.
to_s
# => "1582-10-04"
Date
.
new
(
1582
,
10
,
5
)
.
to_s
# ArgumentError: invalid date
Date
.
new
(
1582
,
10
,
4
)
.
succ
.
to_s
# => "1582-10-15"
#In England, 2 Sep 1752 was immediately followed by 14 Sep 1752.
#
Date
.
new
(
1752
,
9
,
2
,
Date
:
:ENGLAND
)
.
to_s
# => "1752-09-02"
Date
.
new
(
1752
,
9
,
3
,
Date
:
:ENGLAND
)
.
to_s
# ArgumentError: invalid date
Date
.
new
(
1752
,
9
,
2
,
DateTime
:
:ENGLAND
)
.
succ
.
to_s
# => "1752-09-14"
Date
.
new
(
1582
,
10
,
5
,
Date
:
:ENGLAND
)
.
to_s
# => "1582-10-05"
You probably won’t need to use Ruby’s Gregorian conversion features: it’s uncommon that computer applications need to deal with old dates that are both known with precision and associated with a particular locale.
4.2 Parsing Dates, Precisely or Fuzzily
Solution
The best solution is to pass the date string into Date.parse
or DateTime.parse
. These methods use heuristics to guess at the format of the string, and they do a pretty good job:
require
'date'
Date
.
parse
(
'2/9/2007'
)
.
to_s
# => "2007-02-09"
DateTime
.
parse
(
'02-09-2007 12:30:44 AM'
)
.
to_s
# => "2007-09-02T00:30:44+00:00"
DateTime
.
parse
(
'02-09-2007 12:30:44 PM EST'
)
.
to_s
# => "2007-09-02T12:30:44-0500"
Date
.
parse
(
'Wednesday, January 10, 2001'
)
.
to_s
# => "2001-01-10"
Discussion
The parse
methods can save you a lot of the drudgework associated with parsing times in other programming languages, but they don’t always give you the results you want. Notice in the first example how Date.parse
assumed that 2/9/2007 was a month-first date instead of a day-first date. Date.parse
also tends to misinterpret two-digit years:
Date
.
parse
(
'2/9/07'
)
.
to_s
# => "2002-09-07"
Let’s say that Date.parse
doesn’t work for you, but you know that all the dates you’re processing will be formatted a certain way. You can create a format string using the standard strftime
directives, and pass it along with a date string into DateTime.strptime
or Date.strptime
. If the date string matches up with the format string, you’ll get a Date
or DateTime
object back. You may already be familiar with this technique, since date formatting is done similarly in many programming languages, as well as the Unix date
command.
Some common date and time formats include:
american_date
=
'%m/%d/%y'
Date
.
strptime
(
'2/9/07'
,
american_date
)
.
to_s
# => "2007-02-09"
DateTime
.
strptime
(
'2/9/05'
,
american_date
)
.
to_s
# => "2005-02-09T00:00:00+00:00"
Date
.
strptime
(
'2/9/68'
,
american_date
)
.
to_s
# => "2068-02-09"
Date
.
strptime
(
'2/9/69'
,
american_date
)
.
to_s
# => "1969-02-09"
european_date
=
'%d/%m/%y'
Date
.
strptime
(
'2/9/07'
,
european_date
)
.
to_s
# => "2007-09-02"
Date
.
strptime
(
'02/09/68'
,
european_date
)
.
to_s
# => "2068-09-02"
Date
.
strptime
(
'2/9/69'
,
european_date
)
.
to_s
# => "1969-09-02"
four_digit_year_date
=
'%m/%d/%Y'
Date
.
strptime
(
'2/9/2007'
,
four_digit_year_date
)
.
to_s
# => "2007-02-09"
Date
.
strptime
(
'02/09/1968'
,
four_digit_year_date
)
.
to_s
# => "1968-02-09"
Date
.
strptime
(
'2/9/69'
,
four_digit_year_date
)
.
to_s
# => "0069-02-09"
date_and_time
=
'%m-%d-%Y %H:%M:%S %Z'
DateTime
.
strptime
(
'02-09-2007 12:30:44 EST'
,
date_and_time
)
.
to_s
# => "2007-02-09T12:30:44-05:00"
DateTime
.
strptime
(
'02-09-2007 12:30:44 PST'
,
date_and_time
)
.
to_s
# => "2007-02-09T12:30:44-08:00"
DateTime
.
strptime
(
'02-09-2007 12:30:44 GMT'
,
date_and_time
)
.
to_s
# => "2007-02-09T12:30:44+00:00"
twelve_hour_clock_time
=
'%m-%d-%Y %I:%M:%S %p'
DateTime
.
strptime
(
'02-09-2007 12:30:44 AM'
,
twelve_hour_clock_time
)
.
to_s
# => "2007-02-09T00:30:44+00:00"
DateTime
.
strptime
(
'02-09-2007 12:30:44 PM'
,
twelve_hour_clock_time
)
.
to_s
# => "2007-02-09T12:30:44+00:00"
word_date
=
'%A, %B %d, %Y'
Date
.
strptime
(
'Wednesday, January 10, 2001'
,
word_date
)
.
to_s
# => "2001-01-10"
If your date strings might be in one of a limited number of formats, try iterating over a list of format strings and attempting to parse the date string with each one in turn. This gives you some of the flexibility of Date.parse
while letting you override the assumptions it makes. Date.parse
is still faster, so if it’ll work, use that:
Date
.
parse
(
'1/10/07'
)
.
to_s
# => "0007-01-10"
Date
.
parse
(
'2007 1 10'
)
.
to_s
# ArgumentError: invalid date
TRY_FORMATS
=
[
'%d/%m/%y'
,
'%Y %m %d'
]
def
try_to_parse
(
s
)
parsed
=
nil
TRY_FORMATS
.
each
do
|
format
|
begin
parsed
=
Date
.
strptime
(
s
,
format
)
break
rescue
ArgumentError
end
end
return
parsed
end
try_to_parse
(
'1/10/07'
)
.
to_s
# => "2007-10-01"
try_to_parse
(
'2007 1 10'
)
.
to_s
# => "2007-01-10"
Several common date formats cannot be reliably represented by strptime
format strings. Ruby defines class methods of Time
for parsing these date strings, so you don’t have to write the code yourself. Each of the following methods returns a Time
object.
Time.rfc822
parses a date string in the format of RFC822/RFC2822, the Internet email standard. In an RFC2822 date, the month and the day of the week are always in English (for instance, Tue and Jul), even if the locale is some other language:
require
'time'
mail_received
=
'Tue, 1 Jul 2003 10:52:37 +0200'
Time
.
rfc822
(
mail_received
)
# => 2003-07-01 01:52:37 -0700
To parse a date in the format of RFC2616, the HTTP standard, use Time.httpdate
. An RFC2616 date is the kind of date you see in HTTP headers like Last-Modified
. As with RFC2822, the month and day abbreviations are always in English:
last_modified
=
'Tue, 05 Sep 2006 16:05:51 GMT'
Time
.
httpdate
(
last_modified
)
# => 2006-09-05 09:05:51 -0700
To parse a date in the format of ISO 8601 or XML Schema, use Time.iso8601
or Time.xmlschema
:
timestamp
=
'2001-04-17T19:23:17.201Z'
t
=
Time
.
iso8601
(
timestamp
)
# => 2001-04-17 19:23:17 UTC
t
.
sec
# => 17
t
.
tv_usec
# => 201000
Don’t confuse these class methods of Time
with the instance methods of the same names. The class methods create Time
objects from strings. The instance methods go the other way, formatting an existing Time
object as a string:
t
=
Time
.
at
(
1000000000
)
# => 2001-09-08 18:46:40 -0700
t
.
rfc822
# => "Sat, 08 Sep 2001 18:46:40 -0700"
t
.
httpdate
# => "Sun, 09 Sep 2001 01:46:40 GMT"
t
.
iso8601
# => "2001-09-08T18:46:40-07:00"
See Also
-
The RDoc for the
Time#strftime
method lists most of the supportedstrftime
directives (ri Time#strftime
); for a more detailed and complete list, see the table in Recipe 4.3, “Printing a Date”
4.3 Printing a Date
Solution
If you just want to look at a date, you can call Time#to_s
or Date#to_s
and not bother with fancy formatting:
require
'date'
Time
.
now
.
to_s
# => "2013-10-04 14:44:17 -0700"
DateTime
.
now
.
to_s
# => "2013-10-04T14:44:24-07:00"
If you need the date in a specific format, you’ll need to define that format as a string containing time-format directives. Pass the format string into Time#strftime
or Date#strftime
. You’ll get back a string in which the formatting directives have been replaced by the corresponding parts of the Time
or DateTime
object.
A formatting directive looks like a percent sign and a letter: %x
. Everything in a format string that’s not a formatting directive is treated as a literal:
Time
.
gm
(
2013
)
.
strftime
(
'The year is %Y!'
)
# => "The year is 2013!"
The Discussion lists all the time formatting directives defined by Time#strftime
and Date#strftime
. Here are some common time-formatting strings, shown against a sample date of about 1:30 in the afternoon, GMT, on the last day of 2013:
time
=
Time
.
gm
(
2013
,
12
,
31
,
13
,
22
,
33
)
american_date
=
'%D'
time
.
strftime
(
american_date
)
# => "12/31/13"
european_date
=
'%d/%m/%y'
time
.
strftime
(
european_date
)
# => "31/12/13"
four_digit_year_date
=
'%m/%d/%Y'
time
.
strftime
(
four_digit_year_date
)
# => "12/31/2013"
date_and_time
=
'%m-%d-%Y %H:%M:%S %Z'
time
.
strftime
(
date_and_time
)
# => "12-31-2013 13:22:33 UTC"
twelve_hour_clock_time
=
'%m-%d-%Y %I:%M:%S %p'
time
.
strftime
(
twelve_hour_clock_time
)
# => "12-31-2013 01:22:33 PM"
word_date
=
'%A, %B %d, %Y'
time
.
strftime
(
word_date
)
# => "Saturday, December 31, 2013"
Discussion
Printed forms, parsers, and people can all be very picky about the formatting of dates. Having a date in a standard format makes dates easier to read and scan for errors. Agreeing on a format also prevents ambiguities: is 4/12 the fourth of December, or the twelfth of April?
If you require time
, your Time
objects will sprout special-purpose formatting methods for common date representation standards: Time#rfc822
, Time#httpdate
, and Time#iso8601
. These make it easy for you to print dates in formats compliant with email, HTTP, and XML standards:
require
'time'
time
=
Time
.
gm
(
2013
,
12
,
31
,
13
,
22
,
33
)
time
.
rfc822
# => "Tue, 31 Dec 2013 13:22:33 -0000"
time
.
httpdate
# => "Sat, 31 Dec 2013 13:22:33 GMT"
time
.
iso8601
# => "2013-12-31T13:22:33Z"
DateTime
provides only one of these three formats. ISO8601 is the default string representation of a DateTime
object (the one you get by calling #to_s
). This means you can easily print DateTime
objects into XML documents without having to convert them into Time
objects.
For the other two formats, your best strategy is to convert the DateTime
into a Time
object (see Recipe 4.9 for details). Even on a system with a 32-bit time counter, your DateTime
objects will probably fit into the 1901–2037 year range supported by Time
, since RFC822 and HTTP dates are almost always used with dates in the recent past or near future.
Sometimes you need to define a custom date format. Time#strftime
and Date#strftime
define many directives for use in format strings. Table 4-2 says what they do. You can combine these in any way within a formatting string.
Some of these may be familiar to you from other programming languages; virtually all languages since C have included a strftime
implementation that uses some of these directives. Some of the directives are unique to Ruby.
Formatting directive | What it does | Example for 13:22:33 on December 31, 2005 |
---|---|---|
|
English day of the week. |
Saturday |
|
Abbreviated English day of the week. |
Sat |
|
English month of the year. |
December |
|
Abbreviated English month of the year. |
Dec |
|
The century part of the year, zero-padded if necessary. |
20 |
|
This prints the date and time in a way that looks like the default string representation of |
Sat Dec 31 13:22:33 2005. |
|
American-style short date format with two-digit year. Equivalent to %m/%d/%y |
12/31/05 |
|
Day of the month, zero-padded. |
31 |
|
Day of the month, not zero-padded. |
31 |
|
Short date format with four-digit year.; equivalent to %Y-%m-%d |
2005-12-31 |
|
Commercial year with century, zero-padded to a minimum of four digits and with a minus sign prepended for dates BCE. (See Recipe 4.11. For the calendar year, use %Y.) |
2005 |
|
Year without century, zero-padded to two digits. |
05 |
|
Hour of the day, 24-hour clock, zero-padded to two digits. |
13 |
|
Abbreviated month of the year; the same as %b. |
Dec |
|
Hour of the day, 12-hour clock, zero-padded to two digits. |
01 |
|
Julian day of the year, padded to three digits (from 001 to 366). |
365 |
|
Hour of the day, 24-hour clock, not zero-padded; like %H but with no padding. |
13 |
|
Hour of the day, 12-hour clock, not zero-padded; like %I but with no padding. |
1 |
|
Minute of the hour, padded to two digits. |
22 |
|
Month of the year, padded to two digits. |
12 |
|
A newline. Don’t use this; just put a newline in the formatting string. |
\n |
|
Lowercase meridian indicator (am or pm). |
pm |
|
Uppercase meridian indicator. Like %P, except gives AM or PM; note, the uppercase P gives the lowercase meridian, and vice versa. |
PM |
|
Short 24-hour time format; equivalent to %H:%M. |
13:22 |
|
Long 12-hour time format; equivalent to %I:%M:%S %p. |
01:22:33 PM |
|
Second of the minute, zero-padded to two digits. |
33 |
|
Seconds since the Unix epoch. |
1136053353 |
|
Long 24-hour time format; equivalent to %H:%M:%S. |
13:22:33 |
|
A tab. Don’t use this; just put a tab in the formatting string. |
\t |
|
Calendar week number of the year. Assumes that the first week of the year starts on the first Sunday; if a date comes before the first Sunday of the year, it’s counted as part of “week zero” and “00” is returned. |
52 |
|
Commercial weekday of the year, from 1 to 7, with Monday being day 1. |
6 |
|
Commercial week number of the year (see Recipe 4.11). |
52 |
|
The same as %V, but if a date is before the first Monday of the year, it’s counted as part of “week zero” and “00” is returned |
52 |
|
Calendar day of the week, from 0 to 6, with Sunday being day 0. |
6 |
|
Preferred representation for the time; equivalent to %H:%M:%S. |
13:22:33 |
|
Preferred representation for the date; equivalent to %m/%d/%y. |
12/31/05 |
|
Year with century, zero-padded to four digits and with a minus sign prepended for dates BCE. |
2005 |
|
Year without century, zero-padded to two digits. |
05 |
|
The time zone abbreviation ( |
GMT for Time, Z for |
|
The time zone as a GMT offset. |
+0000 |
|
A literal percent sign. |
% |
|
European-style date format with month abbreviation; equivalent to %e-%b-%Y. |
31-Dec-2005 |
|
Prints a Date object as though it were a |
Sat Dec 31 13:22:33 Z 2005 |
Date
defines two formatting directives that won’t work at all in Time#strftime
. Both are shortcuts for formatting strings that you could create manually.
If you need a date format for which there’s no formatting directive, you should be able to compensate by writing Ruby code. For instance, suppose you want to format our example date as “The 31st of December”. There’s no special formatting directive to print the day as an ordinal number, but you can use Ruby code to build a formatting string that gives the right answer:
class
Time
def
day_ordinal_suffix
if
day
==
11
or
day
==
12
return
"th"
else
case
day
%
10
when
1
then
return
"st"
when
2
then
return
"nd"
when
3
then
return
"rd"
else
return
"th"
end
end
end
end
time
.
strftime
(
"The %e
#{
time
.
day_ordinal_suffix
}
of %B"
)
# => "The 31st of December"
The actual formatting string differs depending on the date. In this case, it ends up "The %est of %B"
, but for other dates it will be "The %end of %B"
, "The %erd of %B"
, or "The %eth of %B"
.
See Also
-
Time
objects can parse common date formats as well as print them out; see Recipe 4.2, “Parsing Dates, Precisely or Fuzzily,” to see how to parse the output ofstrftime
,rfc822
,httpdate
, andiso8661
4.4 Iterating Over Dates
Solution
All of Ruby’s Time
objects can be used in ranges as though they were numbers. Date
and DateTime
objects iterate in increments of one day.
In Ruby 1.8, Time
objects iterate in increments of one second.
In Ruby 2.1, Time
objects no longer iterate in a range:
require
'date'
(
Date
.
new
(
1776
,
7
,
2
)
.
.
Date
.
new
(
1776
,
7
,
4
))
.
each
{
|
x
|
puts
x
}
# 1776-07-02
# 1776-07-03
# 1776-07-04
span
=
DateTime
.
new
(
1776
,
7
,
2
,
1
,
30
,
15
)
.
.
DateTime
.
new
(
1776
,
7
,
4
,
7
,
0
,
0
)
span
.
each
{
|
x
|
puts
x
}
# 1776-07-02T01:30:15+00:00
# 1776-07-03T01:30:15+00:00
# 1776-07-04T01:30:15+00:00
Ruby’s Date
class defines step
and upto
, the same convenient iterator methods used by numbers:
the_first
=
Date
.
new
(
2004
,
1
,
1
)
the_fifth
=
Date
.
new
(
2004
,
1
,
5
)
the_first
.
upto
(
the_fifth
)
{
|
x
|
puts
x
}
# 2004-01-01
# 2004-01-02
# 2004-01-03
# 2004-01-04
# 2004-01-05
Discussion
Ruby Date
objects are stored internally as numbers, and a range of those objects is treated like a range of numbers. For Date
and DateTime
objects, the internal representation is the Julian day: iterating over a range of those objects adds one day at a time. For Time
objects, the internal representation is the number of seconds since the Unix epoch: iterating over a range of Time
objects adds one second at a time.
Time
doesn’t define the step
and upto
method, but it’s simple to add them:
class
Time
def
step
(
other_time
,
increment
)
raise
ArgumentError
,
"step can't be 0"
if
increment
==
0
increasing
=
self
<
other_time
if
(
increasing
&&
increment
<
0
)
||
(
!
increasing
&&
increment
>
0
)
yield
self
return
end
d
=
self
begin
yield
d
d
+=
increment
end
while
(
increasing
?
d
<=
other_time
:
d
>=
other_time
)
end
def
upto
(
other_time
)
step
(
other_time
,
1
)
{
|
x
|
yield
x
}
end
end
the_first
=
Time
.
local
(
2004
,
1
,
1
)
the_second
=
Time
.
local
(
2004
,
1
,
2
)
the_first
.
step
(
the_second
,
60
*
60
*
6
)
{
|
x
|
puts
x
}
# Thu Jan 01 00:00:00 EST 2004
# Thu Jan 01 06:00:00 EST 2004
# Thu Jan 01 12:00:00 EST 2004
# Thu Jan 01 18:00:00 EST 2004
# Fri Jan 02 00:00:00 EST 2004
the_first
.
upto
(
the_first
)
{
|
x
|
puts
x
}
# Thu Jan 01 00:00:00 EST 2004
4.5 Doing Date Arithmetic
Solution
Adding or subtracting a Time
object and a number adds or subtracts that number of seconds. Adding or subtracting a Date
object and a number adds or subtracts that number of days:
require
'date'
y2k
=
Time
.
gm
(
2000
,
1
,
1
)
# => 2000-01-01 00:00:00 UTC
y2k
+
1
# => 2000-01-01 00:00:01 UTC
y2k
-
1
# => 1999-12-31 23:59:59 UTC
y2k
+
(
60
*
60
*
24
*
365
)
# => 2000-12-31 00:00:00 UTC
y2k_dt
=
DateTime
.
new
(
2000
,
1
,
1
)
(
y2k_dt
+
1
)
.
to_s
# => "2000-01-02T00:00:00+00:00"
(
y2k_dt
-
1
)
.
to_s
# => "1999-12-31T00:00:00+00:00"
(
y2k_dt
+
0
.
5
)
.
to_s
# => "2000-01-01T12:00:00+00:00"
(
y2k_dt
+
365
)
.
to_s
# => "2000-12-31T00:00:00+00:00"
Subtracting one Time
from another gives the interval between the dates, in seconds. Subtracting one Date
from another gives the interval in days:
day_one
=
Time
.
gm
(
1999
,
12
,
31
)
day_two
=
Time
.
gm
(
2000
,
1
,
1
)
day_two
-
day_one
# => 86400.0
day_one
-
day_two
# => -86400.0
day_one
=
DateTime
.
new
(
1999
,
12
,
31
)
day_two
=
DateTime
.
new
(
2000
,
1
,
1
)
day_two
-
day_one
# => (1/1)
day_one
-
day_two
# => (-1/1)
# Compare times from now and 10 seconds in the future.
before_time
=
Time
.
now
before_datetime
=
DateTime
.
now
sleep
(
10
)
Time
.
now
-
before_time
# => 10.003414
DateTime
.
now
-
before_datetime
# => (10005241/86400000000)
The activesupport
gem, a prerequisite of Ruby on Rails, defines many useful functions on Numeric
and Time
for navigating through time:1
gem
'activesupport'
require
'active_support/all'
10
.
days
.
ago
# => 2013-09-24 15:00:33 -0700
1
.
month
.
from_now
# => 2013-11-04 15:00:47 -0800
2
.
weeks
.
since
(
Time
.
local
(
2006
,
1
,
1
))
# => 2006-01-15 00:00:00 -0800
y2k
-
1
.
day
# => 1999-12-31 00:00:00 UTC
y2k
+
6
.
years
# => 2006-01-01 00:00:00 UTC
6
.
years
.
since
y2k
# => 2006-01-01 00:00:00 UTC
Discussion
Ruby’s date arithmetic takes advantage of the fact that Ruby’s Time
objects are stored internally as numbers. Additions to dates and differences between dates are handled by adding to and subtracting the underlying numbers. This is why adding 1 to a Time
object adds one second and adding 1 to a DateTime
object adds one day: a Time
object is stored as a number of seconds since a time zero, and a Date
or DateTime
object is stored as a number of days since a (different) time zero.
Not every arithmetic operation makes sense for dates: you could “multiply two dates” by multiplying the underlying numbers, but that would have no meaning in terms of real time, so Ruby doesn’t define those operators. Once a number takes on aspects of the real world, there are limitations to what you can legitimately do to that number.
Here’s a shortcut for adding or subtracting big chunks of time: using the right-or left-shift operators on a Date
or DateTime
object will add or subtract a certain number of months from the date:
(
y2k_dt
>>
1
)
.
to_s
# => "2000-02-01T00:00:00+00:00"
(
y2k_dt
<<
1
)
.
to_s
# => "1999-12-01T00:00:00+00:00"
You can get similar behavior with activesupport
’s Numeric#month
method, but that method assumes that a “month” is 30 days long, instead of dealing with the lengths of specific months:
y2k
+
1
.
month
# => 2000-02-01 00:00:00 UTC
y2k
-
1
.
month
# => 1999-12-01 00:00:00 UTC
By contrast, if you end up in a month that doesn’t have enough days (for instance, you start on the 31st and then shift to a month that only has 30 days), the standard library will use the last day of the new month:
# Thirty days hath September…
halloween
=
Date
.
new
(
2000
,
10
,
31
)
(
halloween
<<
1
)
.
to_s
# => "2000-09-30"
(
halloween
>>
1
)
.
to_s
# => "2000-11-30"
(
halloween
>>
2
)
.
to_s
# => "2000-12-31"
leap_year_day
=
Date
.
new
(
1996
,
2
,
29
)
(
leap_year_day
<<
1
)
.
to_s
# => "1996-01-29"
(
leap_year_day
>>
1
)
.
to_s
# => "1996-03-29"
(
leap_year_day
>>
12
)
.
to_s
# => "1997-02-28"
(
leap_year_day
<<
12
*
4
)
.
to_s
# => "1992-02-29"
4.6 Counting the Days Since an Arbitrary Date
Problem
You want to see how many days have elapsed since a particular date, or how many remain until a date in the future.
Solution
Subtract the earlier date from the later one. If you’re using Time
objects, the result will be a floating-point number of seconds, so divide by the number of seconds in a day:
def
last_modified
(
file
)
t1
=
File
.
stat
(
file
)
.
ctime
t2
=
Time
.
now
elapsed
=
(
t2
-
t1
)
/
(
60
*
60
*
24
)
puts
"
#{
file
}
was last modified
#{
elapsed
}
days ago."
end
last_modified
(
"/etc/passwd"
)
# /etc/passwd was last modified 135.61505719175926 days ago.
last_modified
(
"/Users/lucas/"
)
# /Users/lucas/ was last modified 6.394927884837963 days ago.
If you’re using DateTime
objects, the result will be a rational number. You’ll probably want to convert it to an integer or floating-point number for display:
require
'date'
def
advent_calendar
(
date
=
DateTime
.
now
)
christmas
=
DateTime
.
new
(
date
.
year
,
12
,
25
)
christmas
=
DateTime
.
new
(
date
.
year
+
1
,
12
,
25
)
if
date
>
christmas
difference
=
(
christmas
-
date
)
.
to_i
if
difference
==
0
puts
"Today is Christmas."
else
puts
"Only
#{
difference
}
day
#{
"s"
unless
difference
==
1
}
until Christmas."
end
end
advent_calendar
(
DateTime
.
new
(
2006
,
12
,
24
))
# Only 1 day until Christmas.
advent_calendar
(
DateTime
.
new
(
2006
,
12
,
25
))
# Today is Christmas.
advent_calendar
(
DateTime
.
new
(
2006
,
12
,
26
))
# Only 364 days until Christmas.
Discussion
Since times are stored internally as numbers, subtracting one from another will give you a number. Since both numbers measure the same thing (time elapsed since some “time zero”), that number will actually mean something: it’ll be the number of seconds or days that separate the two times on the timeline.
Of course, this works with other time intervals as well. To display a difference in hours, for Time
objects divide the difference by the number of seconds in an hour (3,600, or 1.hour
if you’re using Rails). For DateTime
objects, divide by the number of days in an hour (that is, multiply the difference by 24):
sent
=
DateTime
.
new
(
2006
,
10
,
4
,
3
,
15
)
received
=
DateTime
.
new
(
2006
,
10
,
5
,
16
,
33
)
elapsed
=
(
received
-
sent
)
*
24
puts
"You responded to my email
#{
elapsed
.
to_f
}
hours after I sent it."
# You responded to my email 37.3 hours after I sent it.
You can even use divmod
on a time interval to hack it down into smaller and smaller pieces. Once when I was in college, I wrote a script that displayed how much time remained until the finals I should have been studying for. This method gives you a countdown of the days, hours, minutes, and seconds until some scheduled event:
require
'date'
def
remaining
(
date
,
event
)
intervals
=
[[
"day"
,
1
]
,
[
"hour"
,
24
]
,
[
"minute"
,
60
]
,
[
"second"
,
60
]]
elapsed
=
DateTime
.
now
-
date
tense
=
elapsed
>
0
?
"since"
:
"until"
interval
=
1
.
0
parts
=
intervals
.
collect
do
|
name
,
new_interval
|
interval
/=
new_interval
number
,
elapsed
=
elapsed
.
abs
.
divmod
(
interval
)
"
#{
number
.
to_i
}
#{
name
}#{
"s"
unless
number
==
1
}
"
end
puts
"
#{
parts
.
join
(
", "
)
}
#{
tense
}
#{
event
}
."
end
remaining
(
DateTime
.
new
(
2006
,
4
,
15
,
0
,
0
,
0
,
DateTime
.
now
.
offset
),
"the book deadline"
)
# 27 days, 4 hours, 16 minutes, 9 seconds until the book deadline.
remaining
(
DateTime
.
new
(
1999
,
4
,
23
,
8
,
0
,
0
,
DateTime
.
now
.
offset
),
"the Math 114A final"
)
# 2521 days, 11 hours, 43 minutes, 50 seconds since the Math 114A final.
See Also
4.7 Converting Between Time Zones
Solution
The most common time zone conversions are the conversion of system local time to UTC, and the conversion of UTC to local time. These conversions are easy for both Time
and DateTime
objects.
The Time#gmtime
method modifies a Time
object in place, converting it to UTC. The Time#localtime
method converts in the opposite direction:
now
=
Time
.
now
# => 2013-10-10 08:36:41 -0700
now
=
now
.
gmtime
# => 2013-10-10 15:36:41 UTC
now
=
now
.
localtime
# => 2013-10-10 08:36:41 -0700
The DateTime#new_offset
method converts a DateTime
object from one time zone to another. You must pass in the dstination time zone’s offset from UTC; to convert local time to UTC, pass in zero. Since DateTime
objects are immutable, this method creates a new object identical to the old DateTime
object, except for the time zone offset:
require
'date'
local
=
DateTime
.
now
local
.
to_s
# => "2013-10-10T08:37:22-07:00"
utc
=
local
.
new_offset
(
0
)
utc
.
to_s
# => "2013-10-10T15:37:22+00:00"
To convert a UTC DateTime
object to local time, you’ll need to call DateTime#new_offset
and pass in the numeric offset for your local time zone. The easiest way to get this offset is to call offset
on a DateTime
object known to be in local time. The offset will usually be a rational number with a denominator of 24:
local
=
DateTime
.
now
utc
=
local
.
new_offset
local
.
offset
# => (-7/24)
local_from_utc
=
utc
.
new_offset
(
local
.
offset
)
local_from_utc
.
to_s
# => "2013-10-10T08:37:55-07:00"
local
==
local_from_utc
# => true
Discussion
Time
objects created with Time.at
, Time.local
, Time.mktime
, Time.new
, and Time.now
are created using the current system time zone. Time
objects created with Time.gm
and Time.utc
are created using the UTC time zone. Time
objects can represent any time zone, but it’s difficult to use a time zone with Time
other than local time or UTC.
Suppose you need to convert local time to some time zone other than UTC. If you know the UTC offset for the destination time zone, you can represent it as a fraction of a day and pass it into DateTime#new_offset
:
# Convert local (Pacific) time to Eastern time
pacific
=
DateTime
.
now
pacific
.
to_s
# => "2013-10-10T08:39:04-07:00"
eastern_offset
=
Rational
(
-
5
,
24
)
eastern
=
pacific
.
new_offset
(
eastern_offset
)
eastern
.
to_s
# => "2013-10-10T10:39:04-05:00"
DateTime#new_offset
can convert between arbitrary time zone offsets, so for time zone conversions, it’s easiest to use DateTime
objects and convert back to Time
objects if necessary. But DateTime
objects only understand time zones in terms of numeric UTC offsets. How can you convert a date and time to UTC when all you know is that the time zone is called WET, Zulu, or Asia/Taskent?
On Unix systems, you can temporarily change the “system” time zone for the current process. The C library underlying the Time
class knows about an enormous number of time zones (this “zoneinfo” database is usually located in /usr/share/zoneinfo/
, if you want to look at the available time zones). You can tap this knowledge by setting the environment variable TZ
to an appropriate value, forcing the Time
class to act as though your computer were in some other time zone. Here’s a method that uses this trick to convert a Time
object to any time zone supported by the underlying C library:
class
Time
def
convert_zone
(
to_zone
)
original_zone
=
ENV
[
"TZ"
]
utc_time
=
dup
.
gmtime
ENV
[
"TZ"
]
=
to_zone
to_zone_time
=
utc_time
.
localtime
ENV
[
"TZ"
]
=
original_zone
return
to_zone_time
end
end
Let’s do a number of conversions of a local (Eastern) time to other time zones across the world:
t
=
Time
.
at
(
1000000000
)
# => 2001-09-08 18:46:40 -0700
t
.
convert_zone
(
"US/Eastern"
)
# => 2001-09-08 21:46:40 -0400
t
.
convert_zone
(
"US/Alaska"
)
# => 2001-09-08 17:46:40 -0800
t
.
convert_zone
(
"UTC"
)
# => 2001-09-09 01:46:40 +0000
t
.
convert_zone
(
"Turkey"
)
# => 2001-09-09 04:46:40 +0300
Note that some time zones, like India’s, are half an hour offset from most others:
t
.
convert_zone
(
"Asia/Calcutta"
)
# => 2001-09-09 07:16:40 +0530
By setting the TZ
environment variable before creating a Time
object, you can represent the time in any time zone. The following code converts Lagos time to Singapore time, regardless of the “real” underlying time zone:
ENV
[
"TZ"
]
=
"Africa/Lagos"
t
=
Time
.
at
(
1000000000
)
# => 2001-09-09 02:46:40 +0100
ENV
[
"TZ"
]
=
nil
t
.
convert_zone
(
"Singapore"
)
# => 2001-09-09 09:46:40 +0800
# Just to prove it's the same time as before:
t
.
convert_zone
(
"US/Eastern"
)
# => 2001-09-08 21:46:40 -0400
Since the TZ
environment variable is global to a process, you’ll run into problems if you have multiple threads trying to convert time zones at once.
4.8 Checking Whether Daylight Saving Time Is in Effect
Discussion
Time
objects representing UTC times will always return false when isdst
is called, because UTC is the same year-round. Other Time
objects will consult the Daylight Saving Time rules for the time locale used to create the Time
object. This is usually the system locale on the computer you used to create it: see Recipe 4.7 for information on changing it. The following code demonstrates some of the rules pertaining to Daylight Saving Time across the United States:
eastern
=
Time
.
local
(
2006
,
10
,
1
)
# => Sun Oct 01 00:00:00 EDT 2006
eastern
.
isdst
# => true
ENV
[
'TZ'
]
=
'US/Pacific'
pacific
=
Time
.
local
(
2006
,
10
,
1
)
# => Sun Oct 01 00:00:00 PDT 2006
pacific
.
isdst
# => true
# Except for the Navajo Nation, Arizona doesn't use Daylight Saving Time.
ENV
[
'TZ'
]
=
'America/Phoenix'
arizona
=
Time
.
local
(
2006
,
10
,
1
)
# => Sun Oct 01 00:00:00 MST 2006
arizona
.
isdst
# => false
# Finally, restore the original time zone.
ENV
[
'TZ'
]
=
nil
The C library on which Ruby’s Time
class is based handles the complex rules for Daylight Saving Time across the history of a particular time zone or locale.
Daylight Saving Time was mandated across the U.S. in 1918, but abandoned in most locales shortly afterward. The “zoneinfo” file used by the C library contains this information, along with many other rules:
# Daylight saving first took effect on March 31, 1918.
Time
.
local
(
1918
,
3
,
31
)
.
isdst
# => false
Time
.
local
(
1918
,
4
,
1
)
.
isdst
# => true
Time
.
local
(
1919
,
4
,
1
)
.
isdst
# => true
# The federal law was repealed later in 1919, but some places
# continued to use Daylight Saving Time.
ENV
[
'TZ'
]
=
'US/Pacific'
Time
.
local
(
1920
,
4
,
1
)
# => Thu Apr 01 00:00:00 PST 1920
ENV
[
'TZ'
]
=
nil
Time
.
local
(
1920
,
4
,
1
)
# => Thu Apr 01 00:00:00 EDT 1920
# Daylight Saving Time was reintroduced during the Second World War.
Time
.
local
(
1942
,
2
,
9
)
# => Mon Feb 09 00:00:00 EST 1942
Time
.
local
(
1942
,
2
,
10
)
# => Tue Feb 10 00:00:00 EWT 1942
# EWT stands for "Eastern War Time"
A U.S. law passed in 2005 expanded Daylight Saving Time into March and November, beginning in 2007. Depending on how old your zoneinfo file is, Time
objects you create for dates in 2007 and beyond might or might not reflect the new law:
Time
.
local
(
2007
,
3
,
13
)
# => Tue Mar 13 00:00:00 EDT 2007
# Your computer may incorrectly claim this time is EST.
This illustrates a general point. There’s nothing your elected officials love more than passing laws, so you shouldn’t rely on isdst
to be accurate for any Time
objects that represent times a year or more into the future. When that time actually comes around, Daylight Saving Time might obey different rules in your locale.
The Date
class isn’t based on the C library, and knows nothing about time zones or locales, so it also knows nothing about Daylight Saving Time.
4.9 Converting Between Time and DateTime Objects
Problem
You’re working with both DateTime
and Time
objects, created from Ruby’s two standard date/time libraries. You can’t mix these objects in comparisons, iterations, or date arithmetic because they’re incompatible. You want to convert all the objects into one form or another so that you can treat them all the same way.
Solution
To convert a Time
object to a DateTime
, you can use built-in methods.
If you are using Ruby 1.8, you need to add your own methods for conversion:
require
'date'
class
Time
def
to_datetime
# Convert seconds + microseconds into a fractional number of seconds
seconds
=
sec
+
Rational
(
usec
,
10
**
6
)
# Convert a UTC offset measured in minutes to one measured in a
# fraction of a day.
offset
=
Rational
(
utc_offset
,
60
*
60
*
24
)
DateTime
.
new
(
year
,
month
,
day
,
hour
,
min
,
seconds
,
offset
)
end
end
Then you con convert from Time
to DateTime
via the Time#to_datetime
method:
time
=
Time
.
gm
(
2000
,
6
,
4
,
10
,
30
,
22
,
4010
)
# => Sun Jun 04 10:30:22 UTC 2000
time
.
to_datetime
.
to_s
# => "2000-06-04T10:30:22Z"
Converting a DateTime
to a Time
is similar; you just need to decide whether you want the Time
object to use local time or GMT. This code adds the conversion method to Date
, the superclass of DateTime
, so it will work on both Date
and DateTime
objects:
require
'date'
(
datetime
=
DateTime
.
new
(
1990
,
10
,
1
,
22
,
16
,
Rational
(
41
,
2
)))
.
to_s
# => "1990-10-01T22:16:20Z"
datetime
.
to_time
# => 1990-10-01 15:16:20 -0700
Discussion
In Ruby 1.8, Ruby’s two ways of representing dates and times didn’t coexist very well. But since neither can be a total substitute for the other, you’ll probably use them both during your Ruby career.
Since Ruby 2.1, the conversion methods let you get around incompatibilities by simply converting one type to the other:
time
<
datetime
# ArgumentError: comparison of Time with DateTime failed
time
.
to_datetime
<
datetime
# => false
time
<
datetime
.
to_gm_time
# => false
time
-
datetime
# TypeError: can't convert DateTime into Float
(
time
.
to_datetime
-
datetime
)
.
to_f
# => 3533.50973962975 # Measured in days
time
-
datetime
.
to_gm_time
# => 305295241.50401 # Measured in seconds
The methods just defined are reversible: you can convert back and forth between Date
and DateTime
objects without losing accuracy:
time
# => 2000-06-04 10:30:22 UTC
time
.
usec
# => 4010
time
.
to_datetime
.
to_time
# => 2000-06-04 03:30:22 -0700
time
.
to_datetime
.
to_time
.
usec
# => 4010
datetime
.
to_s
# => "1990-10-01T22:16:20+00:00"
datetime
.
to_time
.
to_datetime
.
to_s
# => "1990-10-01T15:16:20-07:00"
Once you can convert between Time
and DateTime
objects, it’s simple to write code that normalizes a mixed array, so that all its elements end up being of the same type. This method tries to turn a mixed array into an array containing only Time
objects. If it encounters a date that won’t fit within the constraints of the Time
class, it starts over and converts the array into an array of DateTime
objects instead (thus losing any information about Daylight Saving Time):
require
'date'
def
normalize_time_types
(
array
)
# Don't do anything if all the objects are already of the same type.
first_class
=
array
[
0
].
class
first_class
=
first_class
.
super
if
first_class
==
DateTime
return
unless
array
.
detect
{
|
x
|
!
x
.
is_a?
(
first_class
)
}
normalized
=
array
.
collect
do
|
t
|
if
t
.
is_a?
(
Date
)
begin
t
.
to_time
rescue
ArgumentError
# Time out of range; convert to DateTimes instead.
convert_to
=
DateTime
break
end
else
t
end
end
unless
normalized
normalized
=
array
.
collect
{
|
t
|
t
.
is_a?
(
Time
)
?
t
.
to_datetime
:
t
}
end
return
normalized
end
When all objects in a mixed array can be represented as either Time
or DateTime
objects, this method makes them all Time
objects:
mixed_array
=
[
Time
.
now
,
DateTime
.
now
]
# => [2013-10-10 09:27:10 -0700,
# #<DateTime: 23556610914534571/9600000000,-5/24,2299161>]
normalize_time_types
(
mixed_array
)
# => [2013-10-10 09:27:10 -0700, 2013-10-10 09:27:10 -0700]
If one of the DateTime
objects can’t be represented as a Time
, normalize_time_types
turns all the objects into DateTime
instances. This code is run on a system with a 32-bit time counter:
mixed_array
<<
DateTime
.
civil
(
1776
,
7
,
4
)
normalize_time_types
(
mixed_array
)
.
collect
{
|
x
|
x
.
to_s
}
# => ["2013-10-10 09:27:10 -0700", "2013-10-10 09:27:10 -0700",
# => "1776-07-03 16:00:00 -0800"]
See Also
4.10 Finding the Day of the Week
Solution
Use the wday
method (supported by both Time
and DateTime
) to find the day of the week as a number between 0 and 6. Sunday is day zero.
The following code yields to a code block the date of every Sunday between two dates. It uses wday
to find the first Sunday following the start date (keeping in mind that the first date may itself be a Sunday). Then it adds seven days at a time to get subsequent Sundays:
def
every_sunday
(
d1
,
d2
)
# You can use 1.day instead of 60*60*24 if you're using Rails.
one_day
=
d1
.
is_a?
(
Time
)
?
60
*
60
*
24
:
1
sunday
=
d1
+
((
7
-
d1
.
wday
)
%
7
)
*
one_day
while
sunday
<
d2
yield
sunday
sunday
+=
one_day
*
7
end
end
def
print_every_sunday
(
d1
,
d2
)
every_sunday
(
d1
,
d2
)
{
|
sunday
|
puts
sunday
.
strftime
(
"%x"
)}
end
print_every_sunday
(
Time
.
local
(
2006
,
1
,
1
),
Time
.
local
(
2006
,
2
,
4
))
# 01/01/06
# 01/08/06
# 01/15/06
# 01/22/06
# 01/29/06
Discussion
The most commonly used parts of a time are its calendar and clock readings: year, day, hour, and so on. Time
and DateTime
let you access these, but they also give you access to a few other aspects of a time: the Julian day of the year (yday
) and, more usefully, the day of the week (wday
).
The every_sunday
method will accept either two Time
objects or two DateTime
objects. The only difference is the number you need to add to an object to increment it by one day. If you’re only going to be using one kind of object, you can simplify the code a little.
To get the day of the week as an English string, use the strftime
directives %A
and %a
:
t
=
Time
.
local
(
2006
,
1
,
1
)
t
.
strftime
(
"%A %A %A!"
)
# => "Sunday Sunday Sunday!"
t
.
strftime
(
"%a %a %a!"
)
# => "Sun Sun Sun!"
You can find the day of the week and the day of the year, but Ruby has no built-in method for finding the week of the year (there is a method to find the commercial week of the year; see Recipe 4.11). If you need such a method, it’s not hard to create one using the day of the year and the day of the week. This code defines a week
method in a module, which it mixes in to both Date
and Time
:
require
'date'
module
Week
def
week
(
yday
+
7
-
wday
)
/
7
end
end
class
Date
include
Week
end
class
Time
include
Week
end
saturday
=
DateTime
.
new
(
2005
,
1
,
1
)
saturday
.
week
# => 0
(
saturday
+
1
)
.
week
# => 1 #Sunday, January 2
(
saturday
-
1
)
.
week
# => 52 #Friday, December 31
4.11 Handling Commercial Dates
Solution
DateTime
offers some methods for working with commercial dates. Date#cwday
gives the commercial day of the week, Date#cweek
gives the commercial week of the year, and Date#cwyear
gives the commercial year.
Consider January 1, 2006. This was the first day of calendar 2006, but since it was a Sunday, it was the last day of commercial 2005:
require
'date'
sunday
=
DateTime
.
new
(
2006
,
1
,
1
)
sunday
.
year
# => 2006
sunday
.
cwyear
# => 2005
sunday
.
cweek
# => 52
sunday
.
wday
# => 0
sunday
.
cwday
# => 7
Commercial 2006 started on the first weekday in 2006:
monday
=
sunday
+
1
monday
.
cwyear
# => 2006
monday
.
cweek
# => 1
Discussion
Unless you’re writing an application that needs to use commercial dates, you probably don’t care about this, but it’s kind of interesting (if you think dates are interesting). The commercial week starts on Monday, not Sunday, because Sunday’s part of the weekend. DateTime#cwday
is just like DateTime#wday
, except it gives Sunday a value of seven instead of zero.
This means that DateTime#cwday
has a range from one to seven instead of from zero to six:
(
sunday
.
.
.
sunday
+
7
)
.
each
do
|
d
|
puts
"
#{
d
.
strftime
(
"%a"
)
}
#{
d
.
wday
}
#{
d
.
cwday
}
"
end
# Sun 0 7
# Mon 1 1
# Tue 2 2
# Wed 3 3
# Thu 4 4
# Fri 5 5
# Sat 6 6
The cweek
and cwyear
methods have to do with the commercial year, which starts on the first Monday of a year. Any days before the first Monday are considered part of the previous commercial year. The example given in the Solution demonstrates this: January 1, 2006 was a Sunday, so by the commercial reckoning it was part of the last week of 2005.
See Also
-
See Recipe 4.3, “Printing a Date,” for the
strftime
directives used to print parts of commercial dates
4.12 Running a Code Block Periodically
Solution
Create a method that runs a code block, then sleeps until it’s time to run the block again:
def
every_n_seconds
(
n
)
loop
do
before
=
Time
.
now
yield
interval
=
n
-
(
Time
.
now
-
before
)
sleep
(
interval
)
if
interval
>
0
end
end
every_n_seconds
(
5
)
do
puts
"At the beep, the time will be
#{
Time
.
now
.
strftime
(
"%X"
)
}
... beep!"
end
# At the beep, the time will be 12:21:28... beep!
# At the beep, the time will be 12:21:33... beep!
# At the beep, the time will be 12:21:38... beep!
# …
Discussion
There are two main times when you’d want to run some code periodically. The first is when you actually want something to happen at a particular interval: say you’re appending your status to a logfile every 10 seconds. The other is when you would prefer for something to happen continuously, but putting it in a tight loop would be bad for system performance. In this case, you compromise by putting some slack time in the loop so that your code isn’t always running.
The implementation of every_n_seconds
deducts from the sleep time the time spent running the code block. This ensures that calls to the code block are spaced evenly apart, as close to the desired interval as possible. If you tell every_n_seconds
to call a code block every five seconds, but the code block takes four seconds to run, every_n_seconds
only sleeps for one second. If the code block takes six seconds to run, every_n_seconds
won’t sleep at all: it’ll come back from a call to the code block, and immediately yield
to the block again.
If you always want to sleep for a certain interval, no matter how long the code block takes to run, you can simplify the code:
def
every_n_seconds
(
n
)
loop
do
yield
sleep
(
n
)
end
end
In most cases, you don’t want every_n_seconds
to take over the main loop of your program. Here’s a version of every_n_seconds
that spawns a separate thread to run your task. If your code block stops the loop with the break
keyword, the thread stops running:
def
every_n_seconds
(
n
)
thread
=
Thread
.
new
do
while
true
before
=
Time
.
now
yield
interval
=
n
-
(
Time
.
now
-
before
)
sleep
(
interval
)
if
interval
>
0
end
end
return
thread
end
In this snippet, we use every_n_seconds
to spy on a file, waiting for people to modify it:
def
monitor_changes
(
file
,
resolution
=
1
)
last_change
=
Time
.
now
every_n_seconds
(
resolution
)
do
check
=
File
.
stat
(
file
)
.
ctime
if
check
>
last_change
yield
file
last_change
=
check
elsif
Time
.
now
-
last_change
>
60
puts
"Nothing's happened for a minute, I'm bored."
break
end
end
end
That example might give output like this, if someone on the system is working on the file /tmp/foo
:
thread
=
monitor_changes
(
"/tmp/foo"
)
{
|
file
|
puts
"Someone changed
#{
file
}
!"
}
# "Someone changed /tmp/foo!"
# "Someone changed /tmp/foo!"
# "Nothing's happened for a minute; I'm bored."
thread
.
status
# => false
4.13 Waiting a Certain Amount of Time
Solution
The Kernel#sleep
method takes a floating-point number and puts the current thread to sleep for some (possibly fractional) number of seconds:
3
.
downto
(
1
)
{
|
i
|
puts
"
#{
i
}
..."
;
sleep
(
1
)
};
puts
"Go!"
# 3...
# 2...
# 1...
# Go!
Time
.
new
# => Sat Mar 18 21:17:58 EST 2013
sleep
(
10
)
Time
.
new
# => Sat Mar 18 21:18:08 EST 2013
sleep
(
1
)
Time
.
new
# => Sat Mar 18 21:18:09 EST 2013
# Sleep for less then a second.
Time
.
new
.
usec
# => 377185
sleep
(
0
.
1
)
Time
.
new
.
usec
# => 479230
Discussion
Timers are often used when a program needs to interact with a source much slower than a computer’s CPU: a network pipe, or human eyes and hands. Rather than constantly poll for new data, a Ruby program can sleep for a fraction of a second between each poll, giving other programs on the CPU a chance to run. That’s not much time by human standards, but sleeping for a fraction of a second at a time can greatly improve a system’s overall performance.
You can pass any floating-point number to sleep
, but that gives an exaggerated picture of how finely you can control a thread’s sleeping time. For instance, you can’t sleep for 10–50 seconds, because it’s physically impossible (that’s less than the Planck time). You can’t sleep for Float::EPSILON
seconds, because that’s almost certainly less than the resolution of your computer’s timer.
You probably can’t even reliably sleep
for a microsecond, even though most modern computer clocks have microsecond precision. By the time your sleep
command is processed by the Ruby interpreter and the thread actually starts waiting for its timer to go off, some small amount of time has already elapsed. At very small intervals, this time can be greater than the time you asked Ruby to sleep in the first place.
Here’s a simple benchmark that shows how long sleep
on your system will actually make a thread sleep. It starts with a sleep
interval of one second, which is fairly accurate. It then sleeps for shorter and shorter intervals, with lessening accuracy each time:
interval
=
1
.
0
10
.
times
do
|
x
|
t1
=
Time
.
new
sleep
(
interval
)
actual
=
Time
.
new
-
t1
difference
=
(
actual
-
interval
)
.
abs
percent_difference
=
difference
/
interval
*
100
printf
(
"Expected: %.9f Actual: %.6f Difference: %.6f (%.2f%%)
\n
"
,
interval
,
actual
,
difference
,
percent_difference
)
interval
/=
10
end
# Expected: 1.000000000 Actual: 0.999420 Difference: 0.000580 (0.06%)
# Expected: 0.100000000 Actual: 0.099824 Difference: 0.000176 (0.18%)
# Expected: 0.010000000 Actual: 0.009912 Difference: 0.000088 (0.88%)
# Expected: 0.001000000 Actual: 0.001026 Difference: 0.000026 (2.60%)
# Expected: 0.000100000 Actual: 0.000913 Difference: 0.000813 (813.00%)
# Expected: 0.000010000 Actual: 0.000971 Difference: 0.000961 (9610.00%)
# Expected: 0.000001000 Actual: 0.000975 Difference: 0.000974 (97400.00%)
# Expected: 0.000000100 Actual: 0.000015 Difference: 0.000015 (14900.00%)
# Expected: 0.000000010 Actual: 0.000024 Difference: 0.000024 (239900.00%)
# Expected: 0.000000001 Actual: 0.000016 Difference: 0.000016 (1599900.00%)
A small amount of the reported time comes from the overhead caused by creating the second Time
object, but not enough to affect these results. On my system, if I tell Ruby to sleep for a millisecond, the time spent running the sleep
call greatly exceeds the time I wanted to sleep
in the first place! According to this benchmark, the shortest length of time for which I can expect sleep
to accurately sleep is about 1/100 of a second.
You might think to get better sleep resolution by putting the CPU into a tight loop with a certain number of repetitions. Apart from the obvious problems (this hurts system performance, and the same loop will run faster over time since computers are always getting faster), this isn’t even reliable.
The operating system doesn’t know you’re trying to run a timing loop: it just sees you using the CPU, and it can interrupt your loop at any time, for any length of time, to let some other process use the CPU. Unless you’re on an embedded operating system where you can control exactly what the CPU does, the only reliable way to wait for a specific period of time is with sleep
.
Waking up early
The sleep
method will end early if the thread that calls it has its run
method called. If you want a thread to sleep until another thread wakes it up, use Thread.stop
:
alarm
=
Thread
.
new
(
self
)
{
sleep
(
5
);
Thread
.
main
.
wakeup
}
puts
"Going to sleep for 1000 seconds at
#{
Time
.
new
}
..."
sleep
(
10000
);
puts
"Woke up at
#{
Time
.
new
}
!"
# Going to sleep for 1000 seconds at Thu Oct 27 14:45:14 PDT 2005...
# Woke up at Thu Oct 27 14:45:19 PDT 2005!
alarm
=
Thread
.
new
(
self
)
{
sleep
(
5
);
Thread
.
main
.
wakeup
}
puts
"Goodbye, cruel world!"
;
Thread
.
stop
;
puts
"I'm back; how'd that happen?"
# Goodbye, cruel world!
# I'm back; how'd that happen?
See Also
-
The Morse Code example in Recipe 23.11, “Allowing Input Editing with Readline,” displays an interesting use of
sleep
4.14 Adding a Timeout to a Long-Running Operation
Solution
Use the built-in timeout
library. The Timeout.timeout
method takes a code block and a deadline (in seconds). If the code block finishes running in time, it returns true. If the deadline passes and the code block is still running, Timeout.timeout
terminates the code block and raises an exception.
The following code would never finish running were it not for the timeout
call. But after five seconds, timeout
raises a Timeout::Error
and execution halts:
# This code will sleep forever... OR WILL IT?
require
'timeout'
before
=
Time
.
now
begin
status
=
Timeout
.
timeout
(
5
)
{
sleep
}
rescue
Timeout
:
:Error
puts
"I only slept for
#{
Time
.
now
-
before
}
seconds."
end
# I only slept for 5.035492 seconds.
Discussion
Sometimes you must make a network connection or take some other action that might be incredibly slow, or that might never complete at all. With a timeout, you can impose an upper limit on how long that operation can take. If it fails, you can try it again later, or forge ahead without the information you were trying to get. Even when you can’t recover, you can report your failure and gracefully exit the program, rather than sitting around forever waiting for the operation to complete.
By default, Timeout.timeout
raises a Timeout::Error
. You can pass in a custom exception class as the second argument to Timeout.timeout
; this saves you from having to rescue the Timeout:Error
just so you can raise some other error that your application knows how to handle.
If the code block had side effects, they will still be visible after the timeout kills the code block:
def
count_for_five_seconds
$counter
=
0
begin
Timeout
:
:timeout
(
5
)
{
loop
{
$counter
+=
1
}
}
rescue
Timeout
:
:Error
puts
"I can count to
#{
$counter
}
in 5 seconds."
end
end
count_for_five_seconds
# I can count to 2532825 in 5 seconds.
$counter
# => 2532825
This may mean that your dataset is now in an inconsistent state.
1 So does the Facets
library.
Get Ruby Cookbook, 2nd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.