Nine Interesting Tidbits About Dates In Ruby

Randall Reed, Jr.
Def Method
Published in
4 min readMar 19, 2018

--

source: https://www.pexels.com/photo/laptop-calendar-and-books-908298/

Note: This article sat in my drafts folder for a long time. I recently had to look up how to determine the weekday-ness of a date, so I decided to finally give this a publish.

1. To use Date objects in Ruby, you must require the ‘date’ library.

# ruby-2.5.0
> Date.new
NameError (uninitialized constant Date
Did you mean? Data)
> require 'date'
=> true
> Date.new
=> #<Date: -4712-01-01 ((0j,0s,0n),+0s,2299161j)>

Unsurprisingly, Date is already included in Rails.

2. Date.new takes arguments in the order year, month, day — the same order as ISO 8601. This format is also sometimes referred to as Big Endian.

# ruby-2.5.0
> Date.new(2018, 3, 19)
=> #<Date: 2018-03-19 ((2458197j,0s,0n),+0s,2299161j)>

You can use -1 to refer to the last day of a month.

#ruby-2.5.0
> Date.new(2018, 3, -1)
=> #<Date: 2018-03-31 ((2458209j,0s,0n),+0s,2299161j)>

There are also some shortcut methods, like Date.today. However, you’ll need to include ActiveSupport if you want access to Date.yesterday or Date.tomorrow.

# ruby-2.5.0
> today = Date.today
=> #<Date: 2018-03-19 ((2458197j,0s,0n),+0s,2299161j)>
> Date.tomorrow
NoMethodError (undefined method `tomorrow' for Date:Class)

3. Attempting to create a date that does not exist will raise an exception.

# ruby-2.5.0
> Date.new(2018, 2, 31)
ArgumentError (invalid date)

If concerned about possible invalid date parameters, you can first check them using Date.valid_date?.

# ruby-2.5.0
> Date.valid_date?(2018, 2, 31)
=> false
Date.valid_date?(2018, 2, 28)
=> true

4. You can access the various parts of the date with methods like .year, .month, and .day.

# ruby-2.5.0
> today.year
=> 2018
> today.month
=> 3
> today.day
=> 19

5. There are boolean methods to determine the day of the week.

# ruby-2.5.0
> today.monday?
=> true
> today.friday?
=> false

You can use .wday to get a zero-based number for the day of the week (with 0 being Sunday and 6 being Saturday). Alternatively, .cwday yields a one-based number (with 1 being Monday and 7 being Sunday).

# ruby-2.5.0
> sunday = Date.new(2018, 3, 18)
=> #<Date: 2018-03-18 ((2458196j,0s,0n),+0s,2299161j)>
> sunday.wday
=> 0
> sunday.cwday
=> 7

6. Date objects respond to the upto or downto messages, so you can iterate over a date range.

# ruby-2.5.0
> sunday.upto(today) { |date| puts date }
2018-03-18
2018-03-19

7. A Date can be created from a string using parse. Impressively, it is compatible with a variety of formats.

# ruby-2.5.0
> Date.parse('2018-03-19')
=> #<Date: 2018-03-19 ((2458197j,0s,0n),+0s,2299161j)>
> Date.parse('20180319')
=> #<Date: 2018-03-19 ((2458197j,0s,0n),+0s,2299161j)>
> Date.parse('19th Mar 2018')
=> #<Date: 2018-03-19 ((2458197j,0s,0n),+0s,2299161j)>
> Date.parse('Monday March 19, 2018')
=> #<Date: 2018-03-19 ((2458197j,0s,0n),+0s,2299161j)>

If you want to be more explicit about your date format, use strptime and pass a date format string. The default format is ‘yyyy-m-dd.’

# ruby-2.5.0
> Date.strptime('2018-03-19')
=> #<Date: 2018-03-19 ((2458197j,0s,0n),+0s,2299161j)>
> Date.strptime('2018/03/19')
ArgumentError (invalid date)
> Date.strptime('March 19, 2018', '%B %d, %Y')
=> #<Date: 2018-03-19 ((2458197j,0s,0n),+0s,2299161j)>

8. strftime reverses this operation, returning a string representation of a date (I always read this as string from time). The same formatting rules apply. By default, to_s formats the date in the same ISO 8601 format we’ve come to expect, as does iso8601.

# ruby-2.5.0
> today.to_s
=> "2018-03-19"
> today.iso8601
=> "2018-03-19"
> today.strftime('%Y-%m-%d')
=> "2018-03-19"
> today.strftime('%B %d, %Y')
=> "March 19, 2018"

However, for the special case of converting a date to a weekday, consider using DAYNAMES. The constant DAYNAMES is an array of the names of the days of the week, and, I would argue, more semantic than strftime in this case.

# ruby-2.5.0
> Date::DAYNAMES
=> ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
> Date::DAYNAMES[today.wday]
=> "Monday"
> today.strftime('%A')
=> "Monday"

Performance wise, DAYNAMES appears to be a clear winner.

# ruby-2.5.0
> require 'benchmark'
=> true
> puts Benchmark.measure { 10000.times { Date::DAYNAMES[today.wday] } }
0.001394 0.000007 0.001401 ( 0.001395)
> puts Benchmark.measure { 10000.times { today.strftime('%A') } }
0.003813 0.000030 0.003843 ( 0.003839)

(You can learn more about the Benchmark module here.)

9. Basic arithmetic can be used with date objects, though next/succ and prev are more semantic ways to accomplish the same goal.

# ruby-2.5.0
> today + 1
=> #<Date: 2018-03-20 ((2458198j,0s,0n),+0s,2299161j)>
> today.next
=> #<Date: 2018-03-20 ((2458198j,0s,0n),+0s,2299161j)>

next_day and prev_day can take a number of days as an argument. Without the argument, they work the same as next and prev.

# ruby-2.5.0
> today + 5
=> #<Date: 2018-03-24 ((2458202j,0s,0n),+0s,2299161j)>
> today.next_day(5)
=> #<Date: 2018-03-24 ((2458202j,0s,0n),+0s,2299161j)>

Need to go further? Try next_month or next_year.

#ruby-2.5.0
> today.next_month(5)
=> #<Date: 2018-08-19 ((2458350j,0s,0n),+0s,2299161j)>
> today.next_year(5)
=> #<Date: 2023-03-19 ((2460023j,0s,0n),+0s,2299161j)>

Finally, the << and >> operators can be used to backtrack or advance a given number of months.

# ruby-2.5.0
> today >> 5
=> #<Date: 2018-08-19 ((2458350j,0s,0n),+0s,2299161j)>

Further Reading

Check out Rob Dodson’s great article about Date, Chronic, and Active Support.

--

--

Randall Reed, Jr.
Def Method

Learning Ruby and building stuff. Developer at @degreed, based in Richmond, VA.