how to get next month date from today using Time - ruby

I need to generate next month date from today. Should I manuallt check if month == 12 and add +1 to d.year or there is easy solution?
Time class did strange math:
>> d = Time.now
=> 2015-12-03 15:15:36 +0300
>> d.month
=> 12
>> d.month.next
=> 13

Date has a method next_month:
require 'date'
Date.today #=> #<Date: 2015-12-03 ((2457360j,0s,0n),+0s,2299161j)>
Date.today.next_month #=> #<Date: 2016-01-03 ((2457391j,0s,0n),+0s,2299161j)>
Date.today.next_month.month #=> 1
To convert a Time instance to a Date, use to_date.

Yes add+1
but do (this month + 1)%12
if ( thisMonth == 12)
nextMonth = 1;
else
nextMonth = thisMonth+1;
This is just to give an idea
I did not see for any edge cases yet

there are two commonly used possible solutions:
1: Date.today+1.month OR for month only (Date.today+1.month).month
2: Date.today.next_month OR for month only Date.today.next_month.month

Related

Find any particular day of the month with Ruby

I'm trying to build in a calendar system into my app. The problem I'm currently on is once a month schedule. Basically if a person schedules on the second Wednesday of the month I want to automatically update the schedule to the second Wednesday of the next month. I've tried just adding + 1.month but that doesn't achieve exactly what I want. Because it moves it to the next month date not day of the week. Does anyone know how I can achieve this. Here is what I'm currently doing that is not right.
def call
schedule.update!(
end_at: end_at_next_month,
start_at: start_at_next_month,
status: "monthly",
arranged: true
)
end
private
def schedule
context.schedule
end
def end_at_next_month
schedule.end_at + 1.month
end
def start_at_next_month
schedule.start_at + 1.month
end
end
require 'date'
def date_of_nth_wday(month, year, wday, nth)
base_date = Date.new(year, month)
base_wday = base_date.wday
ret_date = base_date +
( wday > base_wday ? wday - base_wday : 7 - base_wday + wday) +
7*(nth-1)
ret_date.month == month ? ret_date : nil
end
Obtain the date of the second Sunday of August, 2017
date_of_nth_wday(8, 2017, 0, 2)
#=> #<Date: 2017-08-13 ((2457979j,0s,0n),+0s,2299161j)>
Obtain the date of the third Friday of August, 2017
date_of_nth_wday(8, 2017, 5, 3)
#=> #<Date: 2017-08-18 ((2457984j,0s,0n),+0s,2299161j)>
Obtain the date of the fifth Monday of August, 2017
date_of_nth_wday(8, 2017, 1, 5)
#=> nil (there are only 4 Mondays in August, 2017)
You can try something like this:
DAYS_MAPPING = { 0=>"Sunday",
1=>"Monday",
2=>"Tuesday",
3=>"Wednesday",
4=>"Thursday",
5=>"Friday",
6=>"Saturday" }
# Returns week of month in integer
# for example: 1 for matching day of 1st week of the month
def get_week_number(date)
week_number = date.strftime('%U').to_i - date.beginning_of_month.strftime('%U').to_i
offset = (Date.parse(DAYS_MAPPING[date.wday]) < date.beginning_of_month) ? 0 : 1
week_number + offset
end
def specific_day_of_next_month(schedule)
# wday => day of week in integer
# for example: 0 for Sunday; 1 for Monday & so on ...
wday = schedule.wday
week_number = get_week_number(schedule)
next_month = schedule + 1.month
days = (next_month.beginning_of_month..next_month.end_of_month).select { |d| d.wday == wday }
days[week_number-1]
end
Example 1:
schedule = Date.today
# => Sat, 01 Jul 2017 (First Saturday of July)
specific_day_of_next_month(schedule)
# => Sat, 05 Aug 2017 (First Saturday of August)
Example 2:
schedule = Date.new(2017, 7, 10)
# => Mon, 10 Jul 2017 (Second Monday of July)
specific_day_of_next_month(schedule)
# => Mon, 14 Aug 2017 (Second Monday of August)
Your title is different to your explanation in the text. With my example you don't get the last friday, but you could select the 1,2,3,4,5th wednesday (or any other day) of a month.
require 'date'
class Date
#~ class DateError < ArgumentError; end
#Get the third monday in march 2008: new_by_mday( 2008, 3, 1, 3)
#
#Based on http://forum.ruby-portal.de/viewtopic.php?f=1&t=6157
def self.new_by_mday(year, month, weekday, nr)
raise( ArgumentError, "No number for weekday/nr") unless weekday.respond_to?(:between?) and nr.respond_to?(:between?)
raise( ArgumentError, "Number not in Range 1..5: #{nr}") unless nr.between?(1,5)
raise( ArgumentError, "Weekday not between 0 (Sunday)and 6 (Saturday): #{nr}") unless weekday.between?(0,6)
day = (weekday-Date.new(year, month, 1).wday)%7 + (nr-1)*7 + 1
if nr == 5
lastday = (Date.new(year, (month)%12+1, 1)-1).day # each december has the same no. of days
raise "There are not 5 weekdays with number #{weekday} in month #{month}" if day > lastday
end
Date.new(year, month, day)
end
end
#2nd monday (weekday 1) in july
p Date.new_by_mday(2017,7,1,2)
If you look for a gem: date_tools supports ths function.

How to get start and end dates from a time/date object created using a month or year in ruby?

I have ISO 8601 compliant date strings like "2016" or "2016-09" representing year or months. How can I get start end dates from this.
for example:
2016 -> ["2016-01-01", "2016-12-31"]
2016-09 -> ["2016-09-01", "2016-09-30"]
Thank you
Try this
require 'date'
def iso8601_range(str)
parts = str.scan(/\d+/).map(&:to_i)
date = Date.new(*parts)
case parts.size
when 1
date .. date.next_year - 1
when 2
date .. date.next_month - 1
else
date .. date
end
end
iso8601_range('2016') # => 2016-01-01..2016-12-31
iso8601_range('2016-09') # => 2016-09-01..2016-09-30
iso8601_range('2016-09-20') # => 2016-09-20..2016-09-20
If you are cool with using send you can replace the case statement with
date .. date.send([:next_year,:next_month,:next_day][parts.size - 1]) - 1
require 'date'
def create_start_end(string)
year, month = string.split('-').map(&:to_i)
if month && !month.zero?
[Date.new(year, month, 1).to_s, Date.new(year, month, -1).to_s]
else
[Date.new(year, 1, 1).to_s, Date.new(year, 12, -1).to_s]
end
end
create_start_end('2016')
#=> ["2016-01-01", "2016-12-31"]
create_start_end('2016-01')
#=> ["2016-01-01", "2016-01-31"]
create_start_end('2016-09')
#=> ["2016-09-01", "2016-09-30"]
One more solution in according to #AndreyDeineko :)
require 'date'
def create_date date
date = date.split('-').map(&:to_i)
[Date.new(*date, 1, 1), Date.new(*date, -1, -1)].map(&:to_s)
end

Ruby - How to round Unix Timestamp (Epoch) to nearest month

I am attempting to round UNIX timestamps in Ruby to the nearest whole month. I have the following UNIX timestamps which I'd like to convert as shown--basically if the day of the month is the 15th and onward, it should round up to the next month (e.g. February 23rd rounds up to March 1st; February 9th rounds down to February 1st).
Here are the timestamps I have and the result I need help achieving:
1455846925 (Feburary 19th, 2016) => 1456790400 (March 1st, 2016)
1447476352 (November 14th, 2015) => 1446336000 (November 1st, 2015)
1242487963 (May 16th, 2009) => 1243814400 (June 1st, 2009).
I am okay solely relying on the logic of 1-14 (round down) / 15+ (round up). I realize this won't always take into account the days in a month and I can accept that for this if needed (although a solution that always takes into account the days in a given month is a bonus).
Ruby's DateTime module may be able to do it in combination with modulo of the number of seconds in a month but I'm not quite sure how to put it all together. If I can convert the UNIX timestamp directly without first translating it to a Ruby Date, that is perfectly fine too.
Thank you in advance for your assistance.
This rounds to the nearest second.
require 'time'
def round_to_month(secs)
t1 = Time.at secs
t2 = (t1.to_datetime >> 1).to_time
s1 = Time.new(t1.year, month=t1.month)
s2 = Time.new(t2.year, month=t2.month)
(t1-s1) < (s2-t1) ? s1 : s2
end
round_to_month(1455846925) # round 2016-02-18 17:55:25 -0800
#=> 2016-03-01 00:00:00 -0800
round_to_month(1447476352) # round 2015-11-13 20:45:52 -0800
#=> 2015-11-01 00:00:00 -0700
round_to_month(1242487963) # round 2009-05-16 08:32:43 -0700
#=> 2009-05-01 00:00:00 -0700
Consider
secs = 1455846925
The calculations are as follows:
t1 = Time.at secs
#=> 2016-02-18 17:55:25 -0800
dt = t1.to_datetime
#=> #<DateTime: 2016-02-18T17:55:25-08:00 ((2457438j,6925s,0n),-28800s,2299161j)>
dt_next = dt >> 1
#=> #<DateTime: 2016-03-18T17:55:25-08:00 ((2457467j,6925s,0n),-28800s,2299161j)>
t2 = dt_next.to_time
#=> 2016-03-18 18:55:25 -0700
s1 = Time.new(t1.year, month=t1.month)
#=> Time.new(2016, month=2)
#=> 2016-02-01 00:00:00 -0800
s2 = Time.new(t2.year, month=t2.month)
# Time.new(2016, month=3)
#=> 2016-03-01 00:00:00 -0800
(t1-s1) < (s2-t1) ? s1 : s2
#=> 1533325.0 < 972275.0 ? 2016-02-18 17:55:25 -0800 : 2016-03-01 00:00:00 -0800
#=> 2016-03-01 00:00:00 -0800
It would be easy to convert it to Time object and then convert it back to timestamp
If you're using Rails, this method should do, what you want:
def nearest_month(t)
time = Time.at(t).utc
time = time.next_month if time.day >= 15
time.beginning_of_month.to_i
end
I don't know if this is as accurate as #CarySwoveland's solution, but I like it:
require 'time'
FIFTEEN_DAYS = 15 * 24 * 60 * 60
def round_to_month(secs)
t1 = Time.at(secs + FIFTEEN_DAYS)
Time.new(t1.year, t1.month)
end
p round_to_month(1455846925) # round 2016-02-18 17:55:25 -0800
# => 2016-03-01 00:00:00 -0800
p round_to_month(1447476352) # round 2015-11-13 20:45:52 -0800
# => 2015-11-01 00:00:00 -0700
p round_to_month(1242487963) # round 2009-05-16 08:32:43 -0700
# => 2009-05-01 00:00:00 -0700
If you want it to return a UNIX timestamp instead just tack .to_i onto the last line in the method.
Something like this will work if you use ActiveSupport in Rails:
require 'date'
def round_to_nearest_month(timestamp)
# Convert the unix timestamp into a Ruby DateTime object
datetime = timestamp.to_datetime
# Get the day of the month from the datetime object
day_of_month = datetime.mday
if day_of_month < 15
datetime.at_beginning_of_month
else
datetime.at_beginning_of_month.next_month
end
return datetime
end

Get an array including every 14th day from a given date

In ruby, how can I get every 14th day of the year, going backwards and forwards from a date.
So consider I'm billed for 2 weeks of recycling on today, 6-16-2015. How can I get an array of every recycling billing day this year based on that date.
Date has a step method:
require 'date'
d = Date.strptime("6-16-2015", '%m-%d-%Y') # strange date format
end_year = Date.new(d.year, -1, -1)
p d.step(end_year, 14).to_a
# =>[#<Date: 2015-06-16 ((2457190j,0s,0n),+0s,2299161j)>, #<Date: 2015-06-30 ((2457204j,0s,0n),+0s,2299161j)>, ...
# Going backward:
begin_year = Date.new(d.year, 1, 1)
p d.step(begin_year,-14).to_a
# =>[#<Date: 2015-06-16 ((2457190j,0s,0n),+0s,2299161j)>, #<Date: 2015-06-02 ((2457176j,0s,0n),+0s,2299161j)>,...
A more descriptive and easy to understand solution:
require 'date'
current_date = Date.parse "16-june-15"
start_date = Date.parse '1-jan-15'
end_date = Date.parse '31-dec-15'
interval = 14
result = current_date.step(start_date, -interval).to_a
result.sort!.pop
result += current_date.step(end_date, interval).to_a
You could do that as follows:
require 'date'
date_str = "6-16-2015"
d = Date.strptime(date_str, '%m-%d-%Y')
f = Date.new(d.year)
((f + (f-d).abs % 14)..Date.new(d.year,-1,-1)).step(14).to_a
#=> [#<Date: 2015-01-13 ((2457036j,0s,0n),+0s,2299161j)>,
# #<Date: 2015-01-27 ((2457050j,0s,0n),+0s,2299161j)>,
# ...
# #<Date: 2015-06-16 ((2457190j,0s,0n),+0s,2299161j)>,
# ...
# #<Date: 2015-12-29 ((2457386j,0s,0n),+0s,2299161j)>]
Based on the second sentence of your question, I assume you simply want an array of all dates in the given year that are two-weeks apart and include the given day.
I attempted a mathy modulus biased approach which turned out unexpectedly confusing.
require 'date'
a_recycle_date_string = "6-17-2015"
interval = 14
a_recycle_date = Date.strptime(a_recycle_date_string, '%m-%d-%Y')
current_year = a_recycle_date.year
end_of_year = Date.new(current_year, -1, -1)
# Find out which index of the first interval's days is the first recycle day
# of the year the (1 indexed)
remainder = (a_recycle_date.yday) % interval
# => 0
# make sure remainder 0 is treated as interval-1 so it doesn't louse
# the equation up
n_days_from_first_recycling_yday_of_year = (remainder - 1) % interval
first_recycle_date_this_year = Date.new(current_year,
1,
1 + n_days_from_first_recycling_yday_of_year)
first_recycle_date_this_year.step(end_of_year, interval).to_a

How many Mondays are in an interval of time in Ruby

I have two DateTime objects (date0 and date1).
I want to know how many Mondays are between [date0..date1]
What is the best way to do this in Ruby?
I'd do it differently, using Date#upto and Array#count :
require 'date'
date1 = Date.parse "01/01/2014"
date2 = Date.parse "01/03/2014"
date1.upto(date2).count(&:monday?) # => 8
Use Date#monday?
[1] pry(main)> require 'date'
=> true
[2] pry(main)> date1 = "01/01/2014"
=> "01/01/2014"
[3] pry(main)> date2 = "01/03/2014"
=> "01/03/2014"
(Date.parse(date1)..Date.parse(date2)).select { |x| x.monday? }
=> [#<Date: 2014-01-06 ((2456664j,0s,0n),+0s,2299161j)>,
#<Date: 2014-01-13 ((2456671j,0s,0n),+0s,2299161j)>,
#<Date: 2014-01-20 ((2456678j,0s,0n),+0s,2299161j)>,
#<Date: 2014-01-27 ((2456685j,0s,0n),+0s,2299161j)>,
#<Date: 2014-02-03 ((2456692j,0s,0n),+0s,2299161j)>,
#<Date: 2014-02-10 ((2456699j,0s,0n),+0s,2299161j)>,
#<Date: 2014-02-17 ((2456706j,0s,0n),+0s,2299161j)>,
#<Date: 2014-02-24 ((2456713j,0s,0n),+0s,2299161j)>]
EDIT
The above gives the list of Mondays as date objects but if you need their count then it is:
mondays = (Date.parse(date1)..Date.parse(date2)).select { |x| x.monday? }
mondays.count
=> 8
Array#count
For DateTime logic remains same
[10] pry(main)> date1 = "01/01/2014"
=> "01/01/2014"
[11] pry(main)> date2 = "01/03/2014"
=> "01/03/2014"
[12] pry(main)> (DateTime.parse(date1)..DateTime.parse(date2)).select { |x| x.monday? }.size
=> 8
#Matt showed a O(1) solution, here's my re-implementation with a little more explanation:
Basically, in order to get the number of mondays (or any other day) between two dates, we want to count the number of weeks from the start_date till the end_date and get rid of a couple exceptions (I'll explain below).
Img0: Days of the week, 0 denoting Sunday.
Getting the number of weeks is similar to getting the number of rows in the table above. The only difference is that we'll have an offset, which is start_date's wday.
number_of_rows = (day_diff + start_date.wday) / 7 + 1
We add 1 because given two valid dates, the minimum number of rows is 1.
Next, if our start_date's wday is greater than the day of week (dow in code), we don't want to count that monday in the row, thus the -1. (check Img0 for illustration - the blue area is an interval of days that starts after a monday and ends before the following monday)
Likewise, if the end_date's wday is less than the day of week, we don't want to count the monday in the row.
def day_count(dow, start_date, end_date)
day_diff = (end_date - start_date).to_i
number_of_rows = (day_diff + start_date.wday) / 7 + 1
end_day_index = (day_diff + start_date.wday) % 7
# could have done end_date.wday ; using % 7 to explain
# its existence in #Matt's code
number_of_rows +
( start_date.wday > dow ? -1 : 0 ) +
( end_day_index < dow ? -1 : 0)
end
def monday_count(start_date, end_date)
day_count(1, start_date, end_date)
end
O(1) solution:
require 'date'
(1 + (date1-date0).to_i + (date0.wday+5) % 7) / 7
my_days = [1] # day of the week in 0-6. Sunday is day-of-week 0; Saturday is day-of-week 6.
result = (date0..date1).to_a.select {|k| my_days.include?(k.wday)}

Resources