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)}
Related
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
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
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
i have two dates i.e.
start date = "2013-06-01"
end date = "2013-12-01"
I am trying to achieve how many times mondays and tuesdays occur within this date range?
specific_days = 2 #monday and tuesday
total days = end_date - start_date
total_weeks = total_days/7
total_specific_days = total_weeks * specific_days
but this doesnt assures the mondays and tuesdays occurences
Use wday method of Date class that returns the day of week (0-6, Sunday is zero), so Mon is 1 and Tue is 2.
require 'date'
dates = [*Date.new(2014,1,1)..Date.new(2014,10,1)]
p dates.count{|d| (d.wday == 1) || (d.wday == 2) }
Result:
#=> 78
Updated
To compare array and range sets:
require 'date'
require 'benchmark'
d1 = [*Date.new(2013,2,11)..Date.new(2013,12,25)]
d2 = Date.new(2013,2,11)..Date.new(2013,12,25)
n = 10000
Benchmark.bm do |x|
x.report('array') { n.times do d1.count{|d| (d.wday == 1) || (d.wday == 2) }; end }
x.report('range') { n.times do d2.count{|d| (d.wday == 1) || (d.wday == 2) }; end }
end
Result:
user system total real
array 0.405000 0.000000 0.405000 ( 0.419041)
range 1.498000 0.000000 1.498000 ( 1.505150)
A bit more verbose using Date#monday?, Date#tuesday? and Enumerable#count:
require 'date'
start_date = Date.parse("2013-06-01")
end_date = Date.parse("2013-12-01")
(start_date..end_date).count { |d| d.monday? || d.tuesday? }
#=> 78
This question is very similar to How do I get all Sundays between two dates in Ruby?.
Get all days between this dates and select the days, which weekday number is 1 or 2. Sunday is the first weekday (= 0).
require 'date'
start_date = DateTime.parse('2013-06-01')
end_date = DateTime.parse('2013-12-01')
result = (start_date..end_date).to_a.select { |d| [1,2].include?(d.wday) }
result.count
The result is 52 times.
=> (Date.parse(start_date)..Date.parse(end_date)).count{|d| d.wday == 1 || d.wday == 2 }
# 52
I'm trying to write web ui tests to choose date from jquery calender based on user input (watir-webdriver), how can find no of months years between two give dates, i searched few solution couldn't get what i want
date1 = Date::strptime("2013-09-19", "%Y-%m-%d")
date2 = Date::strptime("2013-09-25", "%Y-%m-%d")
date3 = Date::strptime("2013-10-01", "%Y-%m-%d")
date4 = Date::strptime("2014-01-20", "%Y-%m-%d")
date5 = Date::strptime("2014-12-01", "%Y-%m-%d")
desired output
diff between date1,date2 -- 0 yrs, 0 month(s)
diff between date1,date3 -- 0 yrs, 1 month(s)
diff between date1,date4 -- 0 yrs, 4 month(s)
diff between date1,date5 -- 1 yrs, 3 month(s)
i checked time_diff gem also
I'd calculate the difference in months (be aware that we ignore day differences here) and then calculate the number of years by dividing that number by 12:
##
# Calculates the difference in years and month between two dates
# Returns an array [year, month]
def date_diff(date1,date2)
month = (date2.year * 12 + date2.month) - (date1.year * 12 + date1.month)
month.divmod(12)
end
date_diff date1, date4 #=> [0, 4]
date_diff date1, date2 #=> [0, 0]
date_diff date1, date3 #=> [0, 1]
date_diff date1, date5 #=> [1, 3]
Here is my attempt. Works in multiple units:
def date_diff(date1, date2, units=:months)
seconds_between = (date2.to_i - date1.to_i).abs
days_between = seconds_between / 60 / 60 / 24
case units
when :days
days_between.floor
when :months
(days_between / 30).floor
when :years
(days_between / 365).floor
else
seconds_between.floor
end
end
Usage:
date_diff(Time.now, 10.years.ago - 77.days, :years) #=> 10
date_diff(10.years.ago - 77.days, Time.now, :months) #=> 124
date_diff(10.years.ago - 77.days, Time.now, :days) #=> 3730
I took this from the TimeDifference gem but it works so nicely that I thought I'd share. If you're using Rails, make a class called TimeDifference with the following code:
class TimeDifference
private_class_method :new
def self.between(start_time:, end_time:)
new(start_time, end_time)
end
def in_seconds
#time_diff
end
def in_minutes
in_component(:minutes)
end
def in_hours
in_component(:hours)
end
def in_days
in_component(:days)
end
def in_weeks
in_component(:weeks)
end
def in_months
(#time_diff / (1.day * 30.42)).round(2)
end
def in_years
in_component(:years)
end
private
def initialize(start_time, end_time)
start_time = time_in_seconds(start_time)
end_time = time_in_seconds(end_time)
#time_diff = (end_time - start_time).abs
end
def time_in_seconds(time)
time.to_time.to_f
end
def in_component(component)
(#time_diff / 1.send(component)).round(2)
end
end
And then simply call:
start_time = DateTime.parse('2 June, 1999 9:00:00')
end_time = DateTime.parse('19 April, 2021 9:00:00')
time_difference = TimeDifference.between(
start_time: start_time,
end_time: end_time
)
time_difference.in_days
=> 7992.0
time_difference.in_months
=> 262.72
time_difference.in_years
=> 21.88
Note: if you're not using Rails you might have to require ActiveSupport.