Related
I have a model with a Time attribute
create_table "opened_intervals", force: :cascade do |t|
t.time "start"
t.time "end"
An example of a value would be:
>> oi.start
=> Sat, 01 Jan 2000 06:26:00 UTC +00:00
(I am living in Germany)
If I use the current time, I get following value:
current_time = Time.now
>> current_time
=> 2019-02-14 18:36:12 +0100
If I compare the class of both objects, I have
>> current_time.class
=> Time
>> oi.start.class
=> ActiveSupport::TimeWithZone
In order to make both instances same class, I change the Time class with the .zone method
>> Time.zone.now
=> Thu, 14 Feb 2019 17:38:48 UTC +00:00
>> Time.zone.now.class
=> ActiveSupport::TimeWithZone
Now both instances have same Class.
If I compare them, I get wrong results because of the date:
>> oi.start
=> Sat, 01 Jan 2000 06:26:00 UTC +00:00
>> current_time
=> Thu, 14 Feb 2019 17:40:13 UTC +00:00
>> current_time < oi.end
=> false
I thought about creating a new Time with the hours, minutes and seconds, but then I have the same problem, Ruby always appends a Date.
Of course I could extract the hour, the minutes, the time, create an integer and compare them, but it feels too much.
How can I deal with this issue the Ruby way?
The Time object is always a time with a date? Is there no other way to achieve a Time just with the time?
What would be the best approach for this problem?
I solved it this way:
def to_integer(time)
hours = time.hour < 10 ? "0#{time.hour}" : time.hour
minutes = time.min < 10 ? "0#{time.min}" : time.min
seconds = time.sec < 10 ? "0#{time.sec}" : time.sec
"#{hours}#{minutes}#{seconds}"
end
It feels too much (I could refactor it, but still too much)
to_integer(current_time) < to_integer(oi.end)
You can just modulus the days off to get the remaining time value.
t1 = Time.now
t2 = t2 = Time.at(Time.now.to_i - (5*24*3600)) # later time in day but previous date
tod1 = t.to_i % (24*3600)
=> 69734
tod2 = t2.to_i % (24*3600)
=> 69912
we can clearly see that t2 is a later time of day or clock time if you will and the modulus operation is very clear if you know anything about the unix epoch.
I have a variable which has some future date(for example I have taken 30 days after date here), I want to have a conditional statement based on if this future date is 10 days advanced, I was trying following code, but it doesn't work, what am I doing wrong here?
> future_date = Date.today + 30.day
=> Wed, 16 Jul 2014
> future_date - Date.today > 10.day
=> false # Shouldn't this has been true
future_date - Date.today will always give you an answer in days (30)
You can see this by
future_date - Date.today
=> 30
So you can do...
future_date - Date.today > 10
And that will work fine.
10.days converts the interval into seconds: 864_000 (number of seconds in ten days) and 30 is not greater than 864_000!
I would like to know how to get the current week number from Rails and how do I manipulate it:
Translate the week number into date.
Make an interval based on week number.
Thanks.
Use strftime:
%U - Week number of the year. The week starts with Sunday. (00..53)
%W - Week number of the year. The week starts with Monday. (00..53)
Time.now.strftime("%U").to_i # 43
# Or...
Date.today.strftime("%U").to_i # 43
If you want to add 43 weeks (or days,years,minutes, etc...) to a date, you can use 43.weeks, provided by ActiveSupport:
irb(main):001:0> 43.weeks
=> 301 days
irb(main):002:0> Date.today + 43.weeks
=> Thu, 22 Aug 2013
irb(main):003:0> Date.today + 10.days
=> Sun, 04 Nov 2012
irb(main):004:0> Date.today + 1.years # or 1.year
=> Fri, 25 Oct 2013
irb(main):005:0> Date.today + 5.months
=> Mon, 25 Mar 2013
You are going to want to stay away from strftime("%U") and "%W".
Instead, use Date.cweek.
The problem is, if you ever want to take a week number and convert it to a date, strftime won't give you a value that you can pass back to Date.commercial.
Date.commercial expects a range of values that are 1 based.
Date.strftime("%U|%W") returns a value that is 0 based. You would think you could just +1 it and it would be fine. The problem will hit you at the end of a year when there are 53 weeks. (Like what just happened...)
For example, let's look at the end of Dec 2015 and the results from your two options for getting a week number:
Date.parse("2015-12-31").strftime("%W") = 52
Date.parse("2015-12-31").cweek = 53
Now, let's look at converting that week number to a date...
Date.commercial(2015, 52, 1) = Mon, 21 Dec 2015
Date.commercial(2015, 53, 1) = Mon, 28 Dec 2015
If you blindly just +1 the value you pass to Date.commercial, you'll end up with an invalid date in other situations:
For example, December 2014:
Date.commercial(2014, 53, 1) = ArgumentError: invalid date
If you ever have to convert that week number back to a date, the only surefire way is to use Date.cweek.
date.commercial([cwyear=-4712[, cweek=1[, cwday=1[, start=Date::ITALY]]]]) → date
Creates a date object denoting the given week date.
The week and the day of week should be a negative
or a positive number (as a relative week/day from the end of year/week when negative).
They should not be zero.
For the interval
require 'date'
def week_dates( week_num )
year = Time.now.year
week_start = Date.commercial( year, week_num, 1 )
week_end = Date.commercial( year, week_num, 7 )
week_start.strftime( "%m/%d/%y" ) + ' - ' + week_end.strftime("%m/%d/%y" )
end
puts week_dates(22)
EG: Input (Week Number): 22
Output: 06/12/08 - 06/19/08
credit: Siep Korteling http://www.ruby-forum.com/topic/125140
Date#cweek seems to get the ISO-8601 week number (a Monday-based week) like %V in strftime (mentioned by #Robban in a comment).
For example, the Monday and the Sunday of the week I'm writing this:
[ Date.new(2015, 7, 13), Date.new(2015, 7, 19) ].map { |date|
date.strftime("U: %U - W: %W - V: %V - cweek: #{date.cweek}")
}
# => ["U: 28 - W: 28 - V: 29 - cweek: 29", "U: 29 - W: 28 - V: 29 - cweek: 29"]
Simple question, but I can't find a good or definitive answer. What is the best and most efficient way to combine Ruby Date and Time objects (objects, not strings) into a single DateTime object?
I found this, but it's not as elegant you would hope:
d = Date.new(2012, 8, 29)
t = Time.now
dt = DateTime.new(d.year, d.month, d.day, t.hour, t.min, t.sec, t.zone)
By the way, the ruby Time object also stores a year, month, and day, so you would be throwing that away when you create the DateTime.
When using seconds_since_midnight, changes in daylight savings time can lead to unexpected results.
Time.zone = 'America/Chicago'
t = Time.zone.parse('07:00').seconds_since_midnight.seconds
d1 = Time.zone.parse('2016-11-06').to_date # Fall back
d2 = Time.zone.parse('2016-11-07').to_date # Normal day
d3 = Time.zone.parse('2017-03-12').to_date # Spring forward
d1 + t
#=> Sun, 06 Nov 2016 06:00:00 CST -06:00
d2 + t
#=> Mon, 07 Nov 2016 07:00:00 CST -06:00
d3 + t
#=> Sun, 12 Mar 2017 08:00:00 CDT -05:00
Here's an alternative, similar to #selva-raj's answer above, using string interpolation, strftime, and parse. %F is equal to %Y-%m-%d and %T is equal to %H:%M:%S.
Time.zone = 'America/Chicago'
t = Time.zone.parse('07:00')
d1 = Time.zone.parse('2016-11-06').to_date # Fall back
d2 = Time.zone.parse('2016-11-07').to_date # Normal day
d3 = Time.zone.parse('2017-03-12').to_date # Spring forward
Time.zone.parse("#{d1.strftime('%F')} #{t.strftime('%T')}")
#=> Sun, 06 Nov 2016 07:00:00 CST -06:00
Time.zone.parse("#{d2.strftime('%F')} #{t.strftime('%T')}")
#=> Sun, 07 Nov 2016 07:00:00 CST -06:00
Time.zone.parse("#{d3.strftime('%F')} #{t.strftime('%T')}")
#=> Sun, 12 Mar 2017 07:00:00 CDT -05:00
Simple:
Date.new(2015, 2, 10).to_datetime + Time.parse("16:30").seconds_since_midnight.seconds
# => Object: Tue, 10 Feb 2015 16:30:00 +0000
You gotta love Ruby!
If using Rails, try any of these:
d = Date.new(2014, 3, 1)
t = Time.parse("16:30")
dt = d + t.seconds_since_midnight.seconds
# => ActiveSupport::TimeWithZone
dt = (d + t.seconds_since_midnight.seconds).to_datetime
# => DateTime
dt = DateTime.new(d.year, d.month, d.day, t.hour, t.min, t.sec)
# => DateTime
If you are using Ruby on Rails, this works great.
I built a method to extend the DateTime class to combine a date and a time. It takes the zone from the date so that it does not end up an hour off with daylight savings time.
Also, for convenience, I like being able to pass in strings as well.
class DateTime
def self.combine(d, t)
# pass in a date and time or strings
d = Date.parse(d) if d.is_a? String
t = Time.zone.parse(t) if t.is_a? String
# + 12 hours to make sure we are in the right zone
# (eg. PST and PDT switch at 2am)
zone = (Time.zone.parse(d.strftime("%Y-%m-%d")) + 12.hours ).zone
new(d.year, d.month, d.day, t.hour, t.min, t.sec, zone)
end
end
So you can do:
DateTime.combine(3.weeks.ago, "9am")
or
DateTime.combine("2015-3-26", Time.current)
etc...
I found another way, I hope this is correct.
datetojoin=Time.parse(datetime).strftime("%Y-%m-%d")
timetojoin=Time.parse(time).strftime("%T")
joined_datetime = Time.parse(datetojoin +" "+ timetojoin).strftime("%F %T")
Any thoughts? Please share.
How can I determine the number of days between two Time instances in Ruby?
> earlyTime = Time.at(123)
> laterTime = Time.now
> time_difference = laterTime - earlyTime
I'd like to determine the number of days in time_difference (I'm not worried about fractions of days. Rounding up or down is fine).
Difference of two times is in seconds. Divide it by number of seconds in 24 hours.
(t1 - t2).to_i / (24 * 60 * 60)
require 'date'
days_between = (Date.parse(laterTime.to_s) - Date.parse(earlyTime.to_s)).round
Edit ...or more simply...
require 'date'
(laterTime.to_date - earlyTime.to_date).round
earlyTime = Time.at(123)
laterTime = Time.now
time_difference = laterTime - earlyTime
time_difference_in_days = time_difference / 1.day # just divide by 1.day
[1] pry(main)> earlyTime = Time.at(123)
=> 1970-01-01 01:02:03 +0100
[2] pry(main)> laterTime = Time.now
=> 2014-04-15 11:13:40 +0200
[3] pry(main)> (laterTime.to_date - earlyTime.to_date).to_i
=> 16175
To account for DST (Daylight Saving Time), you'd have to count it by the days. Note that this assumes less than a day is counted as 1 (rounded up):
num = 0
cur = start_time
while cur < end_time
num += 1
cur = cur.advance(:days => 1)
end
return num
Here is a simple answer that works across DST:
numDays = ((laterTime - earlyTime)/(24.0*60*60)).round
60*60 is the number of seconds in an hour
24.0 is the number of hours in a day. It's a float because some days are a little more than 24 hours, some are less. So when we divide by the number of seconds in a day we still have a float, and round will round to the closest integer.
So if we go across DST, either way, we'll still round to the closest day. Even if you're in some weird timezone that changes more than an hour for DST.
in_days (Rails 6.1+)
Rails 6.1 introduces new ActiveSupport::Duration conversion methods like in_seconds, in_minutes, in_hours, in_days, in_weeks, in_months, and in_years.
As a result, now, your problem can be solved as:
date_1 = Time.parse('2020-10-18 00:00:00 UTC')
date_2 = Time.parse('2020-08-13 03:35:38 UTC')
(date_2 - date_1).seconds.in_days.to_i.abs
# => 65
Here is a link to the corresponding PR.
None of these answers will actually work if you don't want to estimate and you want to take into account daylight savings time.
For instance 10 AM on Wednesday before the fall change of clocks and 10 AM the Wednesday afterwards, the time between them would be 1 week and 1 hour. During the spring it would be 1 week minus 1 hour.
In order to get the accurate time you can use the following code
def self.days_between_two_dates later_time, early_time
days_between = (later_time.to_date-early_time.to_date).to_f
later_time_time_of_day_in_seconds = later_time.hour*3600+later_time.min*60+later_time.sec
earlier_time_time_of_day_in_seconds = early_time.hour*3600+early_time.min*60+early_time.sec
days_between + (later_time_time_of_day_in_seconds - early_time_time_of_day_in_seconds)/1.0.day
end