get next/previous month from a Time object - ruby

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

Related

How to get the current month with Sequel

I would like recover a list of entries for the current month with Sequel.
I tried:
Entry.where(:date >= Date.month).sum(:duration)
or
Entry.where(:date.like('%/06/2013')).sum(:duration)
and other ways, but none of them seemed to work.
If you want all entries the current month and the current year, it's probably easiest to use a range:
d = Date.today
Entry.where(:date=> Date.new(d.year, d.month)...(Date.new(d.year, d.month) >> 1)).sum(:duration)
If you want the current month in any year, Sequel has built in support for this:
Entry.where(Sequel.extract(:month, :date) => Date.today.month).sum(:duration)
You'll need to think in terms of how a database thinks, and how Sequel turns Ruby ranges into SQL:
require 'date'
today = Date.today # => #<Date: 2013-07-03 ((2456477j,0s,0n),+0s,2299161j)>
first_of_month = (today - today.day) + 1
first_of_month # => #<Date: 2013-07-01 ((2456475j,0s,0n),+0s,2299161j)>
next_month = today + 31 # => #<Date: 2013-08-03 ((2456508j,0s,0n),+0s,2299161j)>
last_of_month = next_month - next_month.day # => #<Date: 2013-07-31 ((2456505j,0s,0n),+0s,2299161j)>
last_of_month # => #<Date: 2013-07-31 ((2456505j,0s,0n),+0s,2299161j)>
Entry.where(:date => [first_of_month .. last_of_month]).sum(:duration)
I'd show you the SQL output, but I don't know the database type you're using, and, well, I'm lazy.
Often, you can play tricks inside the DB by truncating "now" to remove the day, then finding all timestamps whose truncated date matches it. That's a lot more specific to the DBM than using Sequel, which already knows how to deal with ranges when converting them to a "between"-type statement.

looking for a method that will show tomorrow's date in ruby

Though, I already got the answer to my question, I decided to edit it.
I was looking for any method in Ruby that can show tomorrow's date.
It's ok if it will show the time as well, I will format its output.
The Time.now gives current date, time and timezone:
Time.now
=> 2013-06-11 13:09:02 +0900
How can I use this method to get a date for tomorrow?
It's ok if there are other methods that can do it.
require 'date'
tomorrow = Date.today + 1
tomorrow is a date object. You can print it in the format you want.
Try:
Time.now + 24*60*60
(Edited: xaxxon is right. My earlier version used Rails' functionality)
ruby 2.6+ helper method
Date.tomorrow
Similar to Stu Gla:
today = Time.now
tomorrow = today + (60 * 60 * 24)
you can then use the .strftime method to format... example:
puts tomorrow.strftime("%F")
# => 2014-08-07

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

Does Ruby have an equivalent to TimeSpan in C#?

In C# there is a TimeSpan class. It represents a period of time and is returned from many date manipulation options. You can create one and add or subtract from a date etc.
In Ruby and specifically rails there seems to be lots of date and time classes but nothing that represents a span of time?
Ideally I'd like an object that I could use for outputting formatted dates easily enough using the standard date formatting options.
eg.
ts.to_format("%H%M")
Is there such a class?
Even better would be if I could do something like
ts = end_date - start_date
I am aware that subtracting of two dates results in the number of seconds separating said dates and that I could work it all out from that.
You can do something similar like this:
irb(main):001:0> require 'time' => true
irb(main):002:0> initial = Time.now => Tue Jun 19 08:19:56 -0400 2012
irb(main):003:0> later = Time.now => Tue Jun 19 08:20:05 -0400 2012
irb(main):004:0> span = later - initial => 8.393871
irb(main):005:0>
This just returns a time in seconds which isn't all that pretty to look at, you can use the strftime() function to make it look pretty:
irb(main):010:0> Time.at(span).gmtime.strftime("%H:%M:%S") => "00:00:08"
Something like this? https://github.com/abhidsm/time_diff
require 'time_diff'
time_diff_components = Time.diff(start_date_time, end_date_time)
No, it doesn't. You can just add seconds or use advance method.
end_date - start_date will have Float type
In the end I forked the suggestion in #tokland's answer. Not quite sure how to make it a proper gem but it's currently working for me:
Timespan fork of time_diff
Not yet #toxaq... but I've started something!
https://gist.github.com/thatandyrose/6180560
class TimeSpan
attr_accessor :milliseconds
def self.from_milliseconds(milliseconds)
me = TimeSpan.new
me.milliseconds = milliseconds
return me
end
def self.from_seconds(seconds)
TimeSpan.from_milliseconds(seconds.to_d * 1000)
end
def self.from_minutes(minutes)
TimeSpan.from_milliseconds(minutes.to_d * 60000)
end
def self.from_hours(hours)
TimeSpan.from_milliseconds(hours.to_d * 3600000)
end
def self.from_days(days)
TimeSpan.from_milliseconds(days.to_d * 86400000)
end
def self.from_years(years)
TimeSpan.from_days(years.to_d * 365.242)
end
def self.diff(start_date_time, end_date_time)
TimeSpan.from_seconds(end_date_time - start_date_time)
end
def seconds
self.milliseconds.to_d * 0.001
end
def minutes
self.seconds.to_d * 0.0166667
end
def hours
self.minutes.to_d * 0.0166667
end
def days
self.hours.to_d * 0.0416667
end
def years
self.days.to_d * 0.00273791
end
end

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.

Resources