Wrong time difference value in Ruby - ruby

I'm trying to add a countdown and I need to calculate the remaining time in millisecond.
Basically I have a button that user can press 24 hours after the last press.
I have
last_press
# => Mon, 10 Jan 2022 10:31:25.000000000 UTC +00:00
And the time difference.
I add 1 day to the last press and I remove the current time
Time.now
# => 2022-01-10 11:50:59 +0100
time_diff = last_press + 1.day - Time.now
If I parse the result is
Time.at(time_diff.to_i.abs).utc.strftime "%H:%M:%S"
# => "00:09:24"
The issue is that time_diff is a float
time_diff = last_meditation + 1.day - Time.now
# => 85180.960988
Basically the calculation is wrong... and I cannot understand why.

Related

Time.now.utc returns next day

Why does Time.now return 2013-12-10 20:49:59 -0600 when Time.now.utc returns 2013-12-11 02:49:59 UTC?
Time.now is your local time.
Time.now.utc is the UTC time. Not your time.
read about UTC here http://en.wikipedia.org/wiki/Coordinated_Universal_Time
Time.now is evaluated in your time zone, which is -0600 or 6 hours behind utc. You'll notice that if you add those 6 hours to the time you get from Time.now, you will get the Time.now.utc result.

Single occurrence event with ice_cube gem using start_time and end_time

There must be something simple being overlooked here...
I've been trying various methods for creating a basic IceCube schedule (https://github.com/seejohnrun/ice_cube). The overall goal is to use IceCube to allow "price schedules" inside a "room reservation" rails application.
The first scenario would be creating a basic schedule with a specific start_time and end_time - occurring only once. IceCube can do this, correct?
The schedule would begin on the start_time and end at the end_time. I would expect to be able to check if dates or times occurs_on? this schedule to determine if a room price should be adjusted.
So in console I've tried creating a basic schedule and would expect it to be occurring 5.days from now since the start_time is Time.now and the end_time is Time.now + 30.days. But it seems to never return true...
1.8.7 :001 > schedule = IceCube::Schedule.new(Time.now, :end_time => Time.now + 30.days)
=> #<IceCube::Schedule:0xb619d604 #all_recurrence_rules=[], #duration=nil, #end_time=Tue Jan 08 09:13:11 -0600 2013, #all_exception_rules=[], #start_time=Sun Dec 09 09:13:11 -0600 2012>
1.8.7 :002 > schedule.occurs_on? Date.today + 5.days
=> false
1.8.7 :005 > schedule.occurs_at? Time.now + 5.days
=> false
1.8.7 :006 > schedule.occurring_at? Time.now + 5.days
=> false
Adding a recurrence rule
1.8.7 :018 > schedule.rrule IceCube::Rule.monthly
=> [#<IceCube::MonthlyRule:0xb687a88c #validations={:base_day=>[#<IceCube::Validations::ScheduleLock::Validation:0xb6875b0c #type=:day>], :base_hour=>[#<IceCube::Validations::ScheduleLock::Validation:0xb6875abc #type=:hour>], :interval=>[#<IceCube::Validations::MonthlyInterval::Validation:0xb6875d28 #interval=1>], :base_min=>[#<IceCube::Validations::ScheduleLock::Validation:0xb6875a6c #type=:min>], :base_sec=>[#<IceCube::Validations::ScheduleLock::Validation:0xb6875a1c #type=:sec>]}, #interval=1>]
Then checking Date.today works...
1.8.7 :025 > schedule.occurs_on? Date.today
=> true
But checking occurs_on? for Date.today + 10.days still returns false... Why?
1.8.7 :026 > schedule.occurs_on? Date.today + 10.days
=> false
So what am I overlooking / doing wrong? Or what is the point of setting an IceCube::Schedule start_time and end_time - they seem to have no effect...?
Does IceCube not work for single occurrence events with a start and end time?
Another example scenario, a room owner wants room prices raised for a holiday season. So the room owner creates a price schedule that starts on Dec 1 2012 and ends Jan 7 2013. (shouldn't have to recur, but could if the owner wanted).
Then when people are searching rooms, the prices would be adjusted if the requested stay occurs_on? a holiday price schedule
Do I need to store the start_time and end_time outside of the schedule and check it manually or something?
Or is there a better suited gem / tool to assist with this kind of schedule management?
You're misunderstanding how schedules and rules work.
Firstly, it's important to understand start_time. Every occurrence of the schedule is based on this, and the schedule returns times that match specific intervals from the start time. The intervals are determined by Rules.
Your example doesn't work because "5 days from now" is not a monthly interval from the schedule's start time. 28, 30, or 31 days from the start time would match, depending on the month.
start = Time.utc(2013, 05, 17, 12, 30, 00) # 2013-05-17 12:30:00 UTC
schedule = IceCube::Schedule.new(start)
schedule.add_recurrence_rule IceCube::Rule.monthly
schedule.occurs_on? start + 5.days #=> false
schedule.occurs_on? start + 31.days #=> true
Secondly, end_time works together with start_time to set the duration of each occurrence. So if your start time is 09:00, and end time is 17:00 then each occurrence will have a duration of 8 hours.
This creates a distinction between occurs_at?(t1) and occurring_at?(t1): the first one is only true when the given time exactly matches the start of an occurrence; the second one is true for any time in the duration. occurs_on?(d1) matches for any time in the given date.
arrival = Time.utc(2013, 5, 1, 9, 0, 0)
departure = Time.utc(2013, 5, 1, 17, 0, 0)
schedule = IceCube::Schedule.new(arrival, end_time: departure)
schedule.add_recurrence_rule IceCube::Rule.weekly.day(1, 2, 3, 4, 5) # M-F
schedule.occurs_at? arrival #=> true
schedule.occurs_at? arrival + 1.second #=> false
schedule.occurring_at? arrival + 1.second #=> true
schedule.occurring_at? departure + 1.second #=> false
For what you're doing, you could try one of two approaches:
A single month-long occurrence
A daily occurrence that ends after a month
This depends on how you need to display or validate times against the schedule. Here's an example of both:
arrival = Time.utc(2013, 5, 1)
departure = Time.utc(2013, 5, 31)
# single occurrence
schedule = IceCube::Schedule.new(arrival, duration: 31.days)
# daily occurrence
schedule = IceCube::Schedule.new(arrival, duration: 1.day)
schedule.add_recurrence_rule IceCube::Rule.daily.until(departure)
After some more testing I think using IceCube's SingleOccurrenceRule is the proper way to have a single occurrence of an event.
To have a schedule that occurs only on the days between the Schedule start_time and end_time I can do something like the following.
Create an IceCube::Schedule with a start and end_time:
1.8.7 :097 > schedule = IceCube::Schedule.new(Time.now, :end_time => Time.now + 30.days)
=> #<IceCube::Schedule:0xb63caabc #all_recurrence_rules=[], #duration=nil, #end_time=Wed Jan 09 00:03:36 -0600 2013, #all_exception_rules=[], #start_time=Mon Dec 10 00:03:36 -0600 2012>
Put all the days that occur within the schedule into an array.
1.8.7 :098 > days_in_schedule = []
=> []
1.8.7 :099 > schedule.start_time.to_date.upto(schedule.end_time.to_date) { |d| puts d; days_in_schedule << d }
Iterate over the array and create a SingleOccurrenceRule for each day in the schedule. Then test a couple dates. Within 30 days, occurs_on? is true, outside of 30 days, occurs_on? is false. This seems correct, except it still returns false when checking if schedule.occurs_on? Date.today. WHY?!?!?
1.8.7 :100 > days_in_schedule.each { |d| schedule.rtime Time.parse(d.to_s) }
1.8.7 :109 > schedule.terminating?
=> true
1.8.7 :110 > schedule.occurs_on? Date.today + 5.days
=> true
1.8.7 :111 > schedule.occurs_on? Date.today + 55.days
=> false
1.8.7 :135 > schedule.occurs_on? Date.today
=> false

How to get leading 0 in seconds?

I'm trying to get hours, minutes and seconds from current time and print it into format "hourminutesecond". For example "121103". But there isn't printed leading zero when I try to do it with next code
irb(main):021:0> ct = Time.now
=> 2012-11-06 12:11:03 +0100
irb(main):022:0> "#{ct.hour}#{ct.min}#{ct.sec}"
=> 12113
Output is "12113" but I want "121103".
Is there method or option for that. I can extract it with regex but just wondering if there is easier way to do it.
You should use time formatting:
ct = Time.now
ct # => Tue Nov 06 15:31:03 +0400 2012
ct.strftime('%H%M%S') # => "153103"
You should accept Sergio's answer as that's the correct way to deal with date/time objects.
However if you must do it using String class you can use rjust method to add a leading zero.
"#{ct.hour}#{ct.min}#{ct.sec}" # 12113
"#{ct.hour}#{ct.min}#{String(ct.sec).rjust(2,"0")}" # 121103

How do I add two weeks to Time.now?

How can I add two weeks to the current Time.now in Ruby? I have a small Sinatra project that uses DataMapper and before saving, I have a field populated with the current time PLUS two weeks, but is not working as needed. Any help is greatly appreciated! I get the following error:
NoMethodError at /
undefined method `weeks' for 2:Fixnum
Here is the code for the Model:
class Job
include DataMapper::Resource
property :id, Serial
property :position, String
property :location, String
property :email, String
property :phone, String
property :description, Text
property :expires_on, Date
property :status, Boolean
property :created_on, DateTime
property :updated_at, DateTime
before :save do
t = Time.now
self.expires_on = t + 2.week
self.status = '0'
end
end
You don't have such nice helpers in plain Ruby. You can add seconds:
Time.now + (2*7*24*60*60)
But, fortunately, there are many date helper libraries out there (or build your own ;) )
Ruby Date class has methods to add days and months in addition to seconds in Time.
An example:
require 'date'
t = DateTime.now
puts t # => 2011-05-06T11:42:26+03:00
# Add 14 days
puts t + 14 # => 2011-05-20T11:42:26+03:00
# Add 2 months
puts t >> 2 # => 2011-07-06T11:42:26+03:00
# And if needed, make Time object out of it
(t + 14).to_time # => 2011-05-20 11:42:26 +0300
require 'rubygems'
require 'active_support/core_ext/numeric/time'
self.expires = 2.weeks.from_now
You have to use seconds to do calculation between dates, but you can use the Time class as a helper to get the seconds from the date part elements.
Time.now + 2.week.to_i
EDIT: As mentioned by #iain you will need Active Support to accomplish usign 2.week.to_i, if you can't (or don't want to) have this dependency you can always use the + operator to add seconds to a Time instance (time + numeric → time docs here)
Time.now + (60 * 60 * 24 * 7 * 2)
I think week/weeks is defined in the active support numeric extension
$ ruby -e 'p Time.now'
2011-05-05 22:27:04 -0400
$ ruby -r active_support/core_ext/numeric -e 'p Time.now + 2.weeks'
2011-05-19 22:27:07 -0400
You can use these 3 patterns
# you have NoMethod Error undefined method
require 'active_support/all'
# Tue, 28 Nov 2017 11:46:37 +0900
Time.now + 2.weeks
# Tue, 28 Nov 2017 11:46:37 +0900
Time.now + 2.week
# Tue Nov 28 11:48:24 +0900 2017
2.weeks.from_now
<%current_time=Time.now
current_time_s=current_time.strftime('%Y-%m-%d %H:%M:%S').to_s #show currrent date time
current_time= Time.now + (60 * 60 * 24 * 7 * 250)
current_time_e=current_time.strftime('%Y-%m-%d %H:%M:%S').to_s #show datetime after week
%>
I like mine too :)
def minor?(dob)
n = DateTime.now
a = DateTime.parse(dob)
a >> 12*18 > n
end
Saves you the trouble of thinking about leap years and seconds. Just works out of the box.

get next/previous month from a Time object

I have a Time object and would like to find the next/previous month. Adding subtracting days does not work as the days per month vary.
time = Time.parse('21-12-2008 10:51 UTC')
next_month = time + 31 * 24 * 60 * 60
Incrementing the month also falls down as one would have to take care of the rolling
time = Time.parse('21-12-2008 10:51 UTC')
next_month = Time.utc(time.year, time.month+1)
time = Time.parse('01-12-2008 10:51 UTC')
previous_month = Time.utc(time.year, time.month-1)
The only thing I found working was
time = Time.parse('21-12-2008 10:51 UTC')
d = Date.new(time.year, time.month, time.day)
d >>= 1
next_month = Time.utc(d.year, d.month, d.day, time.hour, time.min, time.sec, time.usec)
Is there a more elegant way of doing this that I am not seeing?
How would you do it?
Ruby on Rails
Note: This only works in Rails (Thanks Steve!) but I'm keeping it here in case others are using Rails and wish to use these more intuitive methods.
Super simple - thank you Ruby on Rails!
Time.now + 1.month
Time.now - 1.month
Or, another option if it's in relation to the current time (Rails 3+ only).
1.month.from_now
1.month.ago
Personally I prefer using:
Time.now.beginning_of_month - 1.day # previous month
Time.now.end_of_month + 1.day # next month
It always works and is independent from the number of days in a month.
Find more info in this API doc
you can use standard class DateTime
require 'date'
dt = Time.new().to_datetime
=> #<DateTime: 2010-04-23T22:31:39+03:00 (424277622199937/172800000,1/8,2299161)>
dt2 = dt >> 1
=> #<DateTime: 2010-05-23T22:31:39+03:00 (424282806199937/172800000,1/8,2299161)>
t = dt2.to_time
=> 2010-05-23 22:31:39 +0200
There are no built-in methods on Time to do what you want in Ruby. I suggest you write methods to do this work in a module and extend the Time class to make their use simple in the rest of your code.
You can use DateTime, but the methods (<< and >>) are not named in a way that makes their purpose obvious to someone that hasn't used them before.
If you do not want to load and rely on additional libraries you can use something like:
module MonthRotator
def current_month
self.month
end
def month_away
new_month, new_year = current_month == 12 ? [1, year+1] : [(current_month + 1), year]
Time.local(new_year, new_month, day, hour, sec)
end
def month_ago
new_month, new_year = current_month == 1 ? [12, year-1] : [(current_month - 1), year]
Time.local(new_year, new_month, day, hour, sec)
end
end
class Time
include MonthRotator
end
require 'minitest/autorun'
class MonthRotatorTest < MiniTest::Unit::TestCase
describe "A month rotator Time extension" do
it 'should return a next month' do
next_month_date = Time.local(2010, 12).month_away
assert_equal next_month_date.month, 1
assert_equal next_month_date.year, 2011
end
it 'should return previous month' do
previous_month_date = Time.local(2011, 1).month_ago
assert_equal previous_month_date.month, 12
assert_equal previous_month_date.year, 2010
end
end
end
below it works
previous month:
Time.now.months_since(-1)
next month:
Time.now.months_since(1)
I just want to add my plain ruby solution for completeness
replace the format in strftime to desired output
DateTime.now.prev_month.strftime("%Y-%m-%d")
DateTime.now.next_month.strftime("%Y-%m-%d")
You can get the previous month info by this code
require 'time'
time = Time.parse('2021-09-29 12:31 UTC')
time.prev_month.strftime("%b %Y")
You can try convert to datetime.
Time gives you current date, and DateTime allows you to operate with.
Look at this:
irb(main):041:0> Time.new.strftime("%d/%m/%Y")
=> "21/05/2015"
irb(main):040:0> Time.new.to_datetime.prev_month.strftime("%d/%m/%Y")
=> "21/04/2015"
Here is a solution on plain ruby without RoR, works on old ruby versions.
t=Time.local(2000,"jan",1,20,15,1,0);
curmon=t.mon;
prevmon=(Time.local(t.year,t.mon,1,0,0,0,0)-1).mon ;
puts "#{curmon} #{prevmon}"
Some of the solutions assume rails. But, in pure ruby you can do the following
require 'date'
d = Date.now
last_month = d<<1
last_month.strftime("%Y-%m-%d")
Im using the ActiveSupport::TimeZone for this example, but just in case you are using Rails or ActiveSupport it might come in handy.
If you want the previous month you can substract 1 month
time = Time.zone.parse('21-12-2008 10:51 UTC')
time.ago(1.month)
$ irb
irb(main):001:0> time = Time.now
=> 2016-11-21 10:16:31 -0800
irb(main):002:0> year = time.year
=> 2016
irb(main):003:0> month = time.month
=> 11
irb(main):004:0> last_month = month - 1
=> 10
irb(main):005:0> puts time
2016-11-21 10:16:31 -0800
=> nil
irb(main):006:0> puts year
2016
=> nil
irb(main):007:0> puts month
11
=> nil
irb(main):008:0> puts last_month
10
=> nil

Resources