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
Related
I am trying to write a method that takes todays date and if the provided date falls in between DST dates for 2018 (DST is true if date is 03/11/18 2:00am and 11/04/18 2:00am), then it would return the date of the next respective DST change.
I actually have no idea how to approach this other than taking the provided date and writing a case statement around it that iterates through the provided date's given year. and each when holds a different year
# method to take todays date and if the date falls in between DST dates,
# then the method will return the date of the next DST change
def dst_date_change(date)
return case
when date.include? ='2018'
if (date > Time.parse('March 11, 2018 2:00am') && (date < Time.parse('November 4, 2018 2:00am'))
end
when date.include? ='2019'
if
end
when date.include? ='2020'
if
end
when date.include? ='2020'
if
end
else
this is what i currently have. clearly unfinished..
I have interpreted the question as follows: "Given a date (possibly today's date), if at no time on that day it is DST, return nil. Otherwise, return the next date (possibly the same date) that begins as DST and ends as non-DST".
require 'date'
def next_dst_date_change(base_date)
base_date_time = base_date.to_time
next_date_time = (base_date+1).to_time
if base_date_time.dst? || next_date_time.dst?
base_date + 1.step.find { |n| (base_date + n).to_time.dst? == false } - 1
end
end
See Date#to_time, Time#dst?, Numeric#step and Enumerable#find.
Examples
d0 = Date.today
#=> #<Date: 2018-09-24 ((2458386j,0s,0n),+0s,2299161j)>
next_dst_date_change(d0)
#=> #<Date: 2018-11-04 ((2458427j,0s,0n),+0s,2299161j)>
d1 = Date.new(2018, 11, 04)
#=> #<Date: 2018-11-04 ((2458427j,0s,0n),+0s,2299161j)>
next_dst_date_change(d0)
#=> #<Date: 2018-11-04 ((2458427j,0s,0n),+0s,2299161j)>
d2 = d0 + 90
#=> #<Date: 2018-12-23 ((2458476j,0s,0n),+0s,2299161j)>
next_dst_date_change(d2)
#=> nil
d3 = d0 + 365
#=> #<Date: 2019-09-24 ((2458751j,0s,0n),+0s,2299161j)>
next_dst_date_change(d3)
#=> #<Date: 2019-11-03 ((2458791j,0s,0n),+0s,2299161j)>
d4 = Date.new(2018, 3, 10)
#=> #<Date: 2018-03-10 ((2458188j,0s,0n),+0s,2299161j)>
next_dst_date_change(d4)
#=> nil
d5 = Date.new(2018, 3, 11)
next_dst_date_change(d5)
#=> #<Date: 2018-11-04 ((2458427j,0s,0n),+0s,2299161j)>
To speed things up one could do a binary search to determine the next DST-to-non_DST change date, using Range#bsearch. I've assumed that DST ends some time before December 1 of each year.
def next_dst_date_change(base_date)
base_date_time = base_date.to_time
next_date_time = (base_date+1).to_time
if base_date_time.dst? || next_date_time.dst?
diff = (Date.new(base_date.year, 12, 1) - base_date).to_i
base_date + (1..diff).bsearch { |n| !(base_date + n).to_time.dst? } - 1
end
end
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
Need to convert "15307" in to something like "2015-11-03"
I have the following code
def juliantoregular(date1)
temp = "20" + date1[0,2];
year = temp.to_i;
if date1[2,1] == "0"
temp1 = date1[3,2];
else
temp1 = date1[2,3];
end
juliannumber = temp1.to_i;
date1 = Date.ordinal(year,juliannumber)
return date1;
end
Input to the code is String "15307" is there a better way of doing this?
Date.strptime is perfect for this:
str = "15307"
puts Date.strptime(str, "%y%j")
# => 2015-11-03
In the format string "%y%j", %y indicates a two-digit year and %j indicates day of the year.
y, d = "15307".to_i.divmod 1_000 #⇒ [15, 307]
Date.parse("20#{y}-01-01") + (d - 1) #⇒ add days to Jan, 1st
#⇒ #<Date: 2015-11-03 ((2457330j,0s,0n),+0s,2299161j)>
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
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)}