Skip to content

Instantly share code, notes, and snippets.

@ixti
Last active April 18, 2016 21:22
Show Gist options
  • Select an option

  • Save ixti/d2d9267de89420baa992ee06c6fa1771 to your computer and use it in GitHub Desktop.

Select an option

Save ixti/d2d9267de89420baa992ee06c6fa1771 to your computer and use it in GitHub Desktop.

Time after time, I guess that love is blind...

Although result of Fixnum#day, Fixnum#days, Fixnum#week, etc looks like a new Fixnum with seconds in given period it's actually not a Fixnum. Let's take a quick look that might lead us toward pretty wrong direction:

1.week.is_a? Fixnum         # => true
1.week.instance_of? Fixnum  # => true
1.week.class                # => Fixnum
1.week.to_i                 # => 604800
1.week.to_s                 # => "604800"
puts 1.week                 # Prints out: 604800

Looks like a Fixnum, right? No, it's fucking not! Let's see:

604800.inspect              # => "604800"
1.week.inspect              # => "7 days"

Quiet interesting, huh? Anyway, this rant is not about interesting things at all. It's an attempt to help you to not cut shoot your own foot while playing with Rails...

Let's talk about Time first. It provides us with following API:

time = Time.now # => 2016-04-18 17:49:31 +0200
time.class # => Time
time.to_i # => 1460994571

# `time:Time + seconds:Numeric # => new_time:Time`
# Get new Time instance with 1 hour (in seconds) ahead:
time + 60 * 60 # => 2016-04-18 18:49:31 +0200

# `time:Time - seconds:Numeric # => new_time:Time`
# Get new Time instance with 1 day (in seconds) behind:
time - 24 * 60 * 60 # => 2016-04-17 17:49:31 +0200

# `time:Time - other_time:Time # => duration:Float`
# Get time duration in seconds between two times (one day in example below):
Time.new(2016, 2, 1) - Time.new(2016, 1, 31) # => 86400.0

As you can see, when we add/remove Numeric (Float or Integer) from Time it assumes value to be some duration in seconds. Thus it's absolutely safe and natural to call:

time - 1.week

The tricky part comes when you start working with Date:

date = Date.today # => Mon, 18 Apr 2016
date.class # => Date
date.to_i # => fails with NoMethodError

Most important thing to know about Date is that it operates with (!) days:

# date:Date + days:Integer # => new_date:Date
# Get new date instance with 7 days ahead:
date + 7 # => Mon, 25 Apr 2016

# date:Date - days:Integer # => new_date:Date
# Get new date instance with 3 days behind:
date - 3 # => Fri, 15 Apr 2016

# date:Date - other_date:Date # => duration:Rational
# Get amount of days between to dates:
Date.new(2016, 2, 1) - Date.new(2016, 1, 31) # => (1/1)

That brings us to a pretty interesting code:

date - 1.week

Most likely without any background like given in the beginning your guess will be right, it will return new date with 7 days (1 week) behind. But as soon as you will call #to_i on result of 1.week it will become real Integer and it will return new date with 604_800 days behind:

date - 1.week       # => Mon, 11 Apr 2016
date - 1.week.to_i  # => Mon, 29 May 0360

So, every time you work with Time or Date, make sure to:

  • ensure that your variable is either Time or Date
  • if you are working with Date don't use Rails extensions
  • if you are working with Time feel free to use Rails extensions

Why you need to use Rails extensions when you work with Time? Because it provides decent and predictable API:

1.week.ago          # returns Time.now - 1.week
1.month.from_now    # returns Time.now + 1.month

time - 1.month      # => 2016-03-18 15:49:31 +0000
time + 2.months     # => 2016-06-18 15:49:31 +0000

See, how month(s) helper helps you avoid lots of calculations... What if you want to add/remove month from Date. In addition to -/+ which receive amount of days, Date provides << and >> to remove/add months:

date << 3 # => Mon, 18 Jan 2016
date >> 3 # => Mon, 18 Jul 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment