views:

7527

answers:

9

Hello there,

I have a Ruby DateTime which gets filled from a form. Additionally I have n hours from the form as well. I'd like to subtract those n hours from the previous DateTime. (To get a time range).

DateTime has two methods "-" and "<<" to subtract day and month, but not hour. (API). Any suggestions how I can do that?

A: 

DateTime can't do this, but time can:

t = Time.now
t = t-hours*60

Note that Time also stores date information, it's all a little strange.

If you have to work with DateTime

DateTime.commercial(date.year,date.month,date.day,date.hour-x,date.minute,date.second)

might work, but is ugly. The doc says DateTime is immutable, so I'm not even sure about - and <<

MattW.
A: 

Yes,

"DateTime objects are immutable once created." - it's like the String class in Java. Nevertheless: method "-": "If x is a Numeric value, create a new Date object that is x days earlier than the current one.", so we could work with that.

I thought about your idea, too but I'm not sure how ruby converts a negative value from "hour-x". Is that what makes it ugly for you?

Well, I try it out. Let's see what the DateTime class does ;)

Tillmann Carlos Bielefeld
+1  A: 

EDIT: Take a look at this question before you decide to use the approach I've outlined here. It seems it may not be best practice to modify the behavior of a base class in Ruby (which I can understand). So, take this answer with a grain of salt...


MattW's answer was the first thing I thought of, but I also didn't like it very much.

I suppose you could make it less ugly by patching DateTime and Fixnum to do what you want:

require 'date'

# A placeholder class for holding a set number of hours.
# Used so we can know when to change the behavior
# of DateTime#-() by recognizing when hours are explicitly passed in.

class Hours
   attr_reader :value

   def initialize(value)
      @value = value
   end
end

# Patch the #-() method to handle subtracting hours
# in addition to what it normally does

class DateTime

   alias old_subtract -

   def -(x) 
      case x
        when Hours; return DateTime.new(year, month, day, hour-x.value, min, sec)
        else;       return self.old_subtract(x)
      end
   end

end

# Add an #hours attribute to Fixnum that returns an Hours object. 
# This is for syntactic sugar, allowing you to write "someDate - 4.hours" for example

class Fixnum
   def hours
      Hours.new(self)
   end
end

Then you can write your code like this:

some_date = some_date - n.hours

where n is the number of hours you want to substract from some_date

Mike Spross
This is a complete solution. I don't know if the question actually asked about it or if he didn't had discovered that the new operator is"Negative values of h, min, and sec are treating as counting backwards from the end of the next larger unit "
Jonke
+1  A: 

You didn't say what use you need to make of the value you get, but what about just dividing your hours by 24 so you're subtracting a fraction of a day?

mydatetime = DateTime.parse(formvalue)
nhoursbefore = mydatetime - n / 24.0
glenn mcdonald
+3  A: 

You could do this.

adjusted_datetime = (datetime_from_form.to_time - n.hours).to_datetime
Daniel Beardsley
A: 

I like using the helpers in active_support it makes it really clean and easy to read, examples below.

require 'active_support'

last_accessed = 2.hours.ago last_accessed = 2.weeks.ago last_accessed = 1.days.ago

there might be a way to use that kind of syntax to do what you are looking for, if the current date is used.

danmayer
+1  A: 

You can just subtract less than one whole day.

two_hours_ago = DateTime.now - (2/24.0)

This (of course) works for minutes and anything else to..

hours: DateTime.now - (#hours#/24.0)

minutes: DateTime.now - (#min#/1440.0)

seconds: DateTime.now - (#sec#/86400.0)

A: 

n/24.0 trick won't work properly as floats are eventually rounded:

>> DateTime.parse('2009-06-04 02:00:00').step(DateTime.parse('2009-06-04 05:00:00'),1.0/24){|d| puts d}
2009-06-04T02:00:00+00:00
2009-06-04T03:00:00+00:00
2009-06-04T03:59:59+00:00
2009-06-04T04:59:59+00:00

You can, however, use Rational class instead:

>> DateTime.parse('2009-06-04 02:00:00').step(DateTime.parse('2009-06-04 05:00:00'),Rational(1,24)){|d| puts d}
2009-06-04T02:00:00+00:00
2009-06-04T03:00:00+00:00
2009-06-04T04:00:00+00:00
2009-06-04T05:00:00+00:00
Mladen Jablanović
A: 

Use "+/-" to add/substract days (and fractions of days):

two_hours_ago = DateTime.now - 2/24 twelve_hours_in_future = DateTime.now + 3/24

Use ">>/<<" to add/substract months:

next_month = DateTime.now >> 1 three_months_ago = DateTime.now << 3

jacko