Video transcript & code
For instance, calculating the date a year ago today.
Or two weeks into the future.
require "date" require "active_support/core_ext/time" Time.now.next_year # => 2017-07-22 12:31:12 -0400 Time.now.weeks_ago(2) # => 2016-07-08 12:31:12 -0400
If, after using all of these convenient methods, you found yourself working on a non-Rails project, you might have been rudely surprised to find that Time is a much less helpful object on its own.
Time.now.next_year # => NoMethodError: undefined method `next_year' for 2016-07-22 12:32:23 -0400:Time # ~> NoMethodError # ~> undefined method `next_year' for 2016-07-22 12:32:23 -0400:Time # ~> # ~> xmptmp-in16026-RP.rb:1:in `<main>'
The most help we get for doing date calculations from the core
Time class is the ability to add or subtract a number of seconds.
So, for instance, we might try to find the date a year ago today by calculating the number of seconds in a year, and then subtracting that number.
Time.now - (365 * 24 * 60 * 60) # => 2015-07-23 12:43:00 -0400
But this is ugly, and it's also a technique that's prone to inaccuracies. For instance, it doesn't take into account leap years or leap seconds.
We can see this reflected in the fact that it has a #to_i conversion which returns this internal counter.
What is this mysterious Epoch that
Time objects are counting up from? We can find out by creating a new
Time at the zero point and stripping it of the local time zone.
Time.now.to_i # => 1469205406 Time.at(0).utc # => 1970-01-01 00:00:00 UTC
As we can see, the UNIX Epoch is January 1, 1970.
Ruby does have a class that's dedicated to doing more advanced date calculations; it's just not the
It's the Date class, contained in the
date standard library.
But chances are, in most cases we'll already have a
Time object to work with. Even if that object really represents a date.
We can convert a preexisting
Time to a
Date using the #to_date method that the
date library adds.
Then, we can calculate the date a year in the past using prev_year.
If we need more than one year in the past, we can pass a count.
Similarly, there are methods for moving to the past or future by months.
There's also a shortcut for month math: the left-shift and right-shift operators are overloaded to offset a
Date by a number of years into the past or future.
…and by days.
Day calculations have another shortcut: the addition and subtraction operators.
require "date" Date.today # => #<Date: 2016-07-22 ((2457592j,0s,0n),+0s,2299161j)> t = Time.new(2016, 7, 22) # => 2016-07-22 00:00:00 -0400 d = t.to_date # => #<Date: 2016-07-22 ((2457592j,0s,0n),+0s,2299161j)> d.prev_year # => #<Date: 2015-07-22 ((2457226j,0s,0n),+0s,2299161j)> d.prev_year(2) # => #<Date: 2014-07-22 ((2456861j,0s,0n),+0s,2299161j)> d.next_month(6) # => #<Date: 2017-01-22 ((2457776j,0s,0n),+0s,2299161j)> d.prev_month(3) # => #<Date: 2016-04-22 ((2457501j,0s,0n),+0s,2299161j)> d << 6 # => #<Date: 2016-01-22 ((2457410j,0s,0n),+0s,2299161j)> d >> 3 # => #<Date: 2016-10-22 ((2457684j,0s,0n),+0s,2299161j)> d.next_day(7) # => #<Date: 2016-07-29 ((2457599j,0s,0n),+0s,2299161j)> d.prev_day(14) # => #<Date: 2016-07-08 ((2457578j,0s,0n),+0s,2299161j)> d + 7 # => #<Date: 2016-07-29 ((2457599j,0s,0n),+0s,2299161j)> d - 14 # => #<Date: 2016-07-08 ((2457578j,0s,0n),+0s,2299161j)> d.prev_year.to_time # => 2015-07-22 00:00:00 -0400
One point to bear in mind when converting time objects to date objects is that in the process, we're throwing away any time-of-day information.
If we want to keep this part of our time objects intact, for instance if we want to calculate a year ago to the hour, minute, and second, we can switch to converting to DateTime instead of to Date. All the same operations are still available, but the time of day and time zone is preserved.
require "date" t = Time.new(2016, 7, 22, 13, 4) # => 2016-07-22 13:04:00 -0400 d = t.to_datetime # => #<DateTime: 2016-07-22T13:04:00-04:00 ((2457592j,61440s,0n),-14400s,2299161j)> d.prev_year.to_time # => 2015-07-22 13:04:00 -0400
The date calculations available on
DateTime still aren't quite as rich as those available from ActiveSupport. But they take care of most basic date calculation needs, without having to resort to painful and inaccurate arithmetic using only numbers of seconds.
So now you know what to do if you ever need to perform date calculations outside of a Rails project. Today's episode has also provided a glimpse into why Ruby has Date and DateTime classes that are separate from the Time class.
Time is just a lightweight wrapper over a count of seconds. Whereas the
DateTime objects encapsulate a true concept of a calendar date, including all the fiddly details of different calendar systems, leap years, and everything else that goes into getting date calculations right.