32

I want to create a DateTime instance that lies 20 minutes and 10 seconds in the future. I tried around with Time and DateTime in irb, but can't seem to figure out a way that really makes sense. I can only add days to DateTime objects and only add seconds to the Time objects.

Isn't there a better way than to always convert the time I want to add into seconds?

FlyingFoX
  • 3,379
  • 3
  • 32
  • 49

5 Answers5

65

A Time is a number of seconds since an epoch whereas a DateTime is a number of days since an epoch which is why adding 1 to a DateTime adds a whole day. You can however add fractions of a day, for example

d = DateTime.now
d + Rational(10, 86400)

Will add 10 seconds to d (since there are 86400 seconds in a day).

If you are using Rails, Active Support adds some helper methods and you can do

d + 20.minutes + 10.seconds

Which will do the right thing is d is a DateTime or a Time. You can use Active Support on its own, and these days you can pull in just the bits you need. I seem to recall that this stuff is in activesupport/duration. I believe there are a few other gems that offer help with time handling too.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Frederick Cheung
  • 83,189
  • 8
  • 152
  • 174
  • 6
    Personally I prefer writing out `60 * 60 * 24` to make it a little more obvious where the magic number came from, even though it will obviously be very slightly slower at runtime. – Andrew Marshall Apr 07 '12 at 17:11
  • 1
    @AndrewMarshall Agreed, and unless one is performing this `DateTime` manipulation millions upon millions of times, two more multiplication operations are not worth *micro-optimizing*. Don't even think about it until you run into a performance issue. Even better, how about just declaring a constant at the start of the code? `SECONDS_PER_DAY = 60 * 60 * 24`, and use that. Same could be done for `MINUTERS_PER_DAY = 60 * 24` and `HOURS_PER_DAY = 24`, though maybe the last one isn't necessary, but it's good to be consistent and just list them all. – Chris Cirefice Jan 18 '16 at 16:20
  • Active Support's Core Extensions make it easy to cherry-pick extensions to Time, Date and DateTime. https://guides.rubyonrails.org/active_support_core_extensions.html – the Tin Man Feb 17 '20 at 20:15
5

Assuming you have required Active Support or you're working in a Rails project. A very simple and readable way to do this in Ruby is:

DateTime + 5.minutes
Time + 5.minutes

Also works with seconds, hours, days, weeks, months, years.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
alex
  • 4,922
  • 7
  • 37
  • 51
4

Just use the Active Support Time extensions. They are very convenient and less error prone than trying to do this by hand. You can import just the module you need:

# gem 'activesupport'
require 'active_support/core_ext/numeric/time.rb'
DateTime.now + 20.minutes

N.B: Yes, this goes against the StackOverflow party line of staying away from 3rd party libraries, but you shouldn't avoid using libraries when they are practically standard, reduce your risk significantly, and provide better code clarity.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
seo
  • 1,959
  • 24
  • 18
  • StackOverflow has no problem with using 3rd party libraries, they want us to not recommend them, however Active Support is part of Rails and is a very integrated system used regularly with Ruby. It's quite on-topic. – the Tin Man Feb 17 '20 at 20:10
3

Pure Ruby (no Rails)

class Numeric
  def minutes; self/1440.0 end
  alias :minute :minutes

  def seconds; self/86400.0 end
  alias :second :seconds
end

Where 1440 is the number of minutes and 86400 is the number of seconds in a day. Based on how Rails does.

Then you can just let the magic happen:

d + 20.minutes + 10.seconds

Source: https://github.com/rails/rails/blob/v6.0.3.1/activesupport/lib/active_support/core_ext/numeric/time.rb

rodvlopes
  • 895
  • 1
  • 8
  • 18
1

As noted above, you can add seconds to a Time object, so if you call to_time on a DateTime object, you can add seconds to it:

DateTime.strptime("11/19/2019 18:50","%m/%d/%Y %H:%M") + 1 => adds a day

(DateTime.strptime("11/19/2019 18:50","%m/%d/%Y %H:%M").to_time) +1 => adds a second

This doesn't require adding gems.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
wondersz1
  • 757
  • 8
  • 15
  • 1
    +1 Super simple. This is great for doing `DateTime.now.to_time + (60 * x)` to create times `x` minutes into the future. Thanks – James Jan 13 '21 at 20:14