How to compare two Time objects only down to the hour - ruby

I want to compare two Time objects only down to the hour, while ignoring the difference of minutes and seconds.
I'm currently using t0.strftime("%Y%m%d%H") == t1.strftime("%Y%m%d%H"), but I don't think this is a good solution.
Is there better way to do this?

You can use this trick in pure Ruby
t0.to_a[2..9] == t1.to_a[2..9]
Where Time#to_a
$> Time.now.to_a
# => [7, 44, 2, 8, 3, 2014, 6, 67, false, "GMT"]
# [ sec, min, hour, day, month, year, wday, yday, isdst, zone ]
So you can check that the times are equals or not up to the level you want and without missing important components of the object like the zone, etc.

If you have ActiveSupport (either through Rails, or just installed as a gem), it includes an extension to the Time class that adds a change method which will truncate times:
$> require "active_support/core_ext/time"
# => true
$> t = Time.now
# => 2014-03-07 21:30:01 -0500
$> t.change(hour: 0)
# => 2014-03-07 00:00:00 -0500
This won't modify the original time value either. So you can do this:
t0.change(minute: 0) == t1.change(minute: 0)
It'll zero out everything at a lower granularity (seconds, etc.).

require 'time'
t1 = Time.new ; sleep 30 ; t2 = Time.new
t1.hour == t2.hour
This should give you a boolean answer.

Related

Get UTC offset for Timezone at given date via Ruby/tzinfo?

This is probably trivial for anybody who knows the tzinfo API:
Given a Timezone object from tzinfo, how can I get the UTC offset at a given point in time (given either in local time of the Timezone or UTC)?
You can use the period_for_local method. For these examples, I'm using the timezone I live in (America/Sao_Paulo), in where the offset is -03:00 during winter (March to October) and -02:00 during summer (Daylight Saving Time):
# Sao Paulo timezone
zone = TZInfo::Timezone.new('America/Sao_Paulo')
# date in January (Brazilia Summer Time - DST)
d = DateTime.new(2017, 1, 1, 10, 0)
period = zone.period_for_local(d)
puts period.offset.utc_total_offset / 3600.0
# date in July (Brazilia Standard Time - not in DST)
d = DateTime.new(2017, 7, 1, 10, 0)
period = zone.period_for_local(d)
puts period.offset.utc_total_offset / 3600.0
The output is:
-2.0
-3.0
The utc_total_offset method returns the offset in seconds, so I divided by 3600 to get the value in hours.
Note that I also used 3600.0 to force the results to be a float. If I just use 3600, the results will be rounded and timezones like Asia/Kolkata (which has an offset of +05:30) will give incorrect results (5 instead of 5.5).
Note that you must be aware of DST changes, because you can have either a gap or a overlap.
In São Paulo timezone, DST starts at October 15th 2017: at midnight, clocks shift forward to 1 AM (and offset changes from -03:00 to -02:00), so all the local times between 00:00 and 01:00 are not valid. In this case, if you try to get the offset, it will get a PeriodNotFound error:
# DST starts at October 15th, clocks shift from midnight to 1 AM
d = DateTime.new(2017, 10, 15, 0, 30)
period = zone.period_for_local(d) # error: TZInfo::PeriodNotFound
When DST ends, at February 18th 2018, at midnight clocks shift back to 11 PM of 17th (and offset changes from -02:00 to -03:00), so the local times between 11 PM and midnight exist twice (in both offsets).
In this case, you must specify which one you want (by setting the second parameter of period_for_local), indicating if you want the offset for DST or not:
# DST ends at February 18th, clocks shift from midnight to 11 PM of 17th
d = DateTime.new(2018, 2, 17, 23, 30)
period = zone.period_for_local(d, true) # get DST offset
puts period.offset.utc_total_offset / 3600.0 # -2.0
period = zone.period_for_local(d, false) # get non-DST offset
puts period.offset.utc_total_offset / 3600.0 # -3.0
If you don't specify the second parameter, you'll get a TZInfo::AmbiguousTime error:
# error: TZInfo::AmbiguousTime (local time exists twice due do DST overlap)
period = zone.period_for_local(d)
It seems in Ruby 1.9.3 there is some hackery (DateTime to Time) involved, with possible loss of precision, but this is my result based on the answer from #Hugo:
module TZInfo
class Timezone
def utc_to_local_zone(dateTime)
return dateTime.to_time.getlocal(self.period_for_utc(dateTime).utc_total_offset)
end
def offset_to_s(dateTime, format = "%z")
return utc_to_local_zone(dateTime).strftime(format)
end
end
end

Get last day of the month in Ruby

I made new object Date.new with args (year, month). After create ruby added 01 number of day to this object by default. Is there any way to add not first day, but last day of month that i passed as arg(e.g. 28 if it will be 02month or 31 if it will be 01month) ?
use Date.civil
With Date.civil(y, m, d) or its alias .new(y, m, d), you can create a new Date object. The values for day (d) and month (m) can be negative in which case they count backwards from the end of the year and the end of the month respectively.
=> Date.civil(2010, 02, -1)
=> Sun, 28 Feb 2010
>> Date.civil(2010, -1, -5)
=> Mon, 27 Dec 2010
To get the end of the month you can also use ActiveSupport's helper end_of_month.
# Require extensions explicitly if you are not in a Rails environment
require 'active_support/core_ext'
p Time.now.utc.end_of_month # => 2013-01-31 23:59:59 UTC
p Date.today.end_of_month # => Thu, 31 Jan 2013
You can find out more on end_of_month in the Rails API Docs.
So I was searching in Google for the same thing here...
I wasn't happy with above so my solution after reading documentation
in RUBY-DOC was:
Example to get 10/31/2014
Date.new(2014,10,1).next_month.prev_day
require "date"
def find_last_day_of_month(_date)
if(_date.instance_of? String)
#end_of_the_month = Date.parse(_date.next_month.strftime("%Y-%m-01")) - 1
else if(_date.instance_of? Date)
#end_of_the_month = _date.next_month.strftime("%Y-%m-01") - 1
end
return #end_of_the_month
end
find_last_day_of_month("2018-01-01")
This is another way to find
You can do something like that:
def last_day_of_month?
(Time.zone.now.month + 1.day) > Time.zone.now.month
end
Time.zone.now.day if last_day-of_month?
This is my Time based solution. I have a personal preference to it compared to Date although the Date solutions proposed above read somehow better.
reference_time ||= Time.now
return (Time.new(reference_time.year, (reference_time.month % 12) + 1) - 1).day
btw for December you can see that year is not flipped. But this is irrelevant for the question because december always has 31 day. And for February year does not need flipping. So if you have another use case that needs year to be correct, then make sure to also change year.
Here is taking the first and third answers to find the last day of the previous month.
today_c = Date.civil(Date.today.prev_month.year, -1, -1)
p today_c

How do I get a range or array for every minute in a day in Ruby 1.9.3?

I know there is the Date#step method, however it wants days for steps. I need a range or array for every minute in a given day (1440 entries).
What's the best, and most effeicient way to do this in Ruby 1.9.3?
Ultimately, I'm going to format the output to be used like this:
00:00:00
00:01:00
00:02:00
...
23:59:00
This might get you started:
0.upto((60 * 24) - 1).each { |m| puts "%02d:%02d:00" % [m / 60, m % 60] }
You can step anyway:
require 'date'
today = Date.today.to_datetime
tomorrow = today+1
min = 1.0/(24*60)
today.step(tomorrow, min){|d| p d.strftime("%H:%M:%S")}

Ruby on Rails 3: Why is the conversion from Date to seconds different from Time to seconds

In Ruby I am trying to convert a Date into a format that is usable by the HighCharts JavaScript charting library. Odd thing is when I convert the Date to seconds it converts differently than when I convert a Time to seconds and differently when I convert a DateTime to seconds. Due to this difference in conversion the dates displayed on the Graph can be as much as 1 date behind.
I am sure this has something to do with Rails and how it handles conversion from UTC to Local. If someone could explain to me the details I would greatly appreciate it.
In my examples below I use the same date '2011/05/02' but the seconds come out to be different.
Examples:
Date.new(2011, 5, 2).to_time.to_i * 1000
=> 1304265600000
=> 05/01/2011
Time.utc(2011, 5, 2).to_i * 1000
=> 1304294400000
=> 05/02/2011
Date.new(2011, 5, 2).to_datetime.to_i * 1000
=> 1304294400000
=> 05/02/2011
ruby-1.9.2-p180 :106 > Time.utc(2011, 5, 2)
=> 2011-05-02 00:00:00 UTC
ruby-1.9.2-p180 :107 > Date.new(2011, 5, 2).to_time
=> 2011-05-02 00:00:00 +0300
Date.to_time generates Time with timezone. That's your difference.
The quick fix/hack that popped instantly into my mind is:
Date.new(2011, 5, 2).to_time.utc.midnight
Edit:
http://apidock.com/rails/Date/to_time
Date.new(2011, 5, 2).to_time(:utc)

Set time part of DateTime in ruby

Say I have a datetime object eg DateTime.now. I want to set hours and minutes to 0 (midnight). How can I do that?
Within a Rails environment:
Thanks to ActiveSupport you can use:
DateTime.now.midnight
DateTime.now.beginning_of_day
OR
DateTime.now.change({ hour: 0, min: 0, sec: 0 })
# More concisely
DateTime.now.change({ hour: 0 })
Within a purely Ruby environment:
now = DateTime.now
DateTime.new(now.year, now.month, now.day, 0, 0, 0, now.zone)
OR
now = DateTime.now
DateTime.parse(now.strftime("%Y-%m-%dT00:00:00%z"))
Nevermind, got it. Need to create a new DateTime:
DateTime.new(now.year, now.month, now.day, 0, 0, 0, 0)
Warning: DateTime.now.midnight and DateTime.now.beginning_of_day return the same value (which is the zero hour of the current day - midnight does not return 24:00:00 as you would expect from its name).
So I am adding this as further info for anyone who might use the accepted answer to calculate midnight x days in the future.
For example, a 14 day free trial that should expire at midnight on the 14th day:
DateTime.now.midnight + 14.days
is the morning of the 14th day, which equates to a 13.x day trial (x is the part of the day left over - if now is noon, then it's 13.5 day trial).
You would actually need to do this:
DateTime.now.midnight + 15.days
to get midnight on the 14th day.
For this reason I always prefer to use beginning_of_day, since that is 00:00:00. Using midnight can be misleading/misunderstood.
If you use it often consider install this gem to improve date parse:
https://github.com/mojombo/chronic
require 'chronic'
Chronic.parse('this 0:00')

Resources