Ruby: Grouping a date range by year and month - ruby

I am creating a range for each month in my example range.
example_range = (Time.zone.today..2.years.from_now)
Output should look like so:
=> [Wed, 03 Aug 2016..Wed, 31 Aug 2016, Thu, 01 Sep 2016..Fri,
30 Sep 2016, Sat, 01 Oct 2016..Mon, 03 Oct 2016, ...]
At the moment I'm doing this, which doesn't work for ranges longer than a year, because the grouping will put January '16 and January '17 in one group.
example_range.group_by(&:month).each { |_, month| month.first..month.last }
I also tried this, but ruby segfaults on this for some reason...
example_range.group_by(&:year).map{ |ary| ary.group_by(&:month)}
Does anyone know a more beautiful (or at least working) way of doing this?

How is this:
example_range.group_by {|date| [date.year, date.month] }.map {|_, month| month.first..month.last }
If you are using Active Support (Rails), this will also work:
example_range.group_by(&:beginning_of_month).map {|_, month| month.first..month.last }

The best solution I think is this:
example_range.group_by {|date| date.month.to_s + "-" + date.year.to_s}
You can adjust the way you need.

Related

I can't get the 15th weekday in ruby

I cant get the next 15th day but not the working day.
DateTime.now.next_day(+15).strftime('%d %^B %Y')
how can i get the next 15th weekday?
You're just adding 15 days to the current date. What you want is to adjust the date:
date = DateTime.now
if (date.mday > 15)
date = date.next_month
end
date = date.next_day(15 - date.mday)
Where that adjusts to be the 15th of the next month if it's already past the 15th of the current month.
Now this can be extended to be an Enumerator:
def each_mday(mday, from: nil)
from ||= DateTime.now
Enumerator.new do |y|
loop do
if (from.mday > mday)
from = from.next_month
end
from = from.next_day(mday - from.mday)
y << from
from += 1
end
end
end
Which makes it possible to find the first day matching particular criteria, like being a weekday:
each_mday(15, from: Date.parse('2019-06-14')).find { |d| (1..5).include?(d.wday) }
Where that returns July 15th, as June 15th is a weekend.
The from argument is optional but useful for testing cases like this to ensure it's working correctly.
15.times.reduce(Date.civil 2019, 03, 24) do |acc, _|
begin
acc += 1
end while [0, 6].include? acc.wday
acc
end
#⇒ #<Date: 2019-04-12 ((2458586j,0s,0n),+0s,2299161j)>
So you want to add 15 business days from the current date. You can go with iGian or Aleksei vanilla ruby answers or use business_time gem:
15.business_days.from_now
If I understood correctly, you want to get next Monday if you hit Saturday or Sunday. Since wday gives you 0 for Sun and 6 for Sat, you can use it to as a conditional to add days towards Monday.
def date_add_next_week_day(days)
date = (Date.today + days)
date += 1 if date.wday == 6
date += 1 if date.wday == 0
date
end
date_add_next_week_day(15).strftime('%d %^B %Y')
If I get the point you need to find the 15th day after a specified date, skipping weekends.
One possible option is to define the skipping_weekend hash like this, considering Date.html#wday:
skip_weekend = { 6 => 2, 0 => 1}
skip_weekend.default = 0
Then:
next15 = DateTime.now.next_day(15)
next15_working = next15.next_day(skip_weekend[next15.wday]).strftime('%d %B %Y')
Now if next15 falls on a working day, next15_working is the same day (hash defaults to 0), otherwise it skips 2 days if Saturday (6th week day, hash maps to 2) or 1 day if Sunday (0th week day, hash maps to 1)
I assume that, given a starting date, ds (a Date object), and a positive integer n, the problem is determine a later date, dt, such that between ds+1 and dt, inclusive, there n weekdays.
require 'date'
def given_date_plus_week_days(dt, week_days)
wday = dt.wday
weeks, days = (week_days + {0=>4, 6=>4}.fetch(wday, wday-1)).divmod(5)
dt - (wday.zero? ? 6 : (wday - 1)) + 7*weeks + days
end
The variable wday is assigned to the day of week for the start date, dt. The start date is moved back to the previous Monday, unless it falls on a Monday, in which case it is not changed. That is reflected in the expression
wday.zero? ? 6 : (wday - 1)
which is subtracted from dt. The number of week days is correspondingly adjusted to
week_days + { 0=>4, 6=>4 }.fetch(wday, wday-1)
The remaining calculations are straightforward.
def display(start_str, week_days)
start = Date.parse(start_str)
7.times.map { |i| start + i }.each do |ds|
de = given_date_plus_week_days(ds, week_days)
puts "#{ds.strftime("%a, %b %d, %Y")} + #{week_days} -> #{de.strftime("%a, %b %d, %Y")}"
end
end
display("April 8", 15)
Mon, Apr 08, 2019 + 15 -> Mon, Apr 29, 2019
Tue, Apr 09, 2019 + 15 -> Tue, Apr 30, 2019
Wed, Apr 10, 2019 + 15 -> Wed, May 01, 2019
Thu, Apr 11, 2019 + 15 -> Thu, May 02, 2019
Fri, Apr 12, 2019 + 15 -> Fri, May 03, 2019
Sat, Apr 13, 2019 + 15 -> Fri, May 03, 2019
Sun, Apr 14, 2019 + 15 -> Fri, May 03, 2019
display("April 8", 17)
Mon, Apr 08, 2019 + 17 -> Wed, May 01, 2019
Tue, Apr 09, 2019 + 17 -> Thu, May 02, 2019
Wed, Apr 10, 2019 + 17 -> Fri, May 03, 2019
Thu, Apr 11, 2019 + 17 -> Mon, May 06, 2019
Fri, Apr 12, 2019 + 17 -> Tue, May 07, 2019
Sat, Apr 13, 2019 + 17 -> Tue, May 07, 2019
Sun, Apr 14, 2019 + 17 -> Tue, May 07, 2019

Ruby Mongoid: How to convert a date into age?

I have a date in Mongoid user collection field dob.
I want to get those users whose age is between 18-30.
but i don't know how to get age from date in Mongoid.
here is the query:
User.in(gender: gender_group).between(dob: 18..30)
Considering a user is 30 years old if he has not yet celebrated his 31st birthday, following would be your logic:
today = Date.today
=> Mon, 07 May 2018
aged_30 = today - 31.years + 1.day
=> Fri, 08 May 1987
aged_18 = today - 18.years
=> Sun, 07 May 2000
User.in(gender: gender_group).between(dob: aged_30..aged_18)

How to get current day name from Date.today?

How does someone get the name of the day from Date.today?
The below should be equal too "Fri" or "Friday":
Date.today #=> Fri, 20 Nov 2015
Use the following:
Date.today.strftime("%A")

Comparing elements inside array of ranges

So given that I have this array of ranges:
[
[0] Mon, 29 Dec 2014 07:30:00 PST -08:00..Mon, 29 Dec 2014 10:59:59 PST -08:00,
[1] Mon, 29 Dec 2014 12:30:01 PST -08:00..Mon, 29 Dec 2014 15:00:00 PST -08:00,
[2] Mon, 29 Dec 2014 07:30:00 PST -08:00..Mon, 29 Dec 2014 08:59:59 PST -08:00,
[3] Mon, 29 Dec 2014 10:30:01 PST -08:00..Mon, 29 Dec 2014 15:00:00 PST -08:00
]
How do I compare ranges that have the same minimum value and remove that element if the maximum value is greater than the other?
Two ways, where a is the array of ranges:
#1
a.each_with_object({}) { |r,h| h.update({ r.first=>r }) { |_,ov,nv|
[ov,nv].min_by(&:last) } }.values
#2
a.group_by(&:first).values.map { |r| r.min_by(&:last) }
Admittedly, this will be slow:
your_array.group_by do |range|
range.min
end.each do |min_value, ranges|
least_max = ranges.map(&:max).min
ranges.delete_if{ |range| range.max != least_max }
end.values
The following might be faster and also will delete things from your original array:
min_maxes = {}
your_array.each do |range|
min = range.min
max = range.max
if min_maxes[min].nil? || (min_maxes[min] > max)
min_maxes[min] = max
end
end
your_array.delete_if do |range|
min_maxes[range.min] < range.max
end
If there is multimap data structure we can handle this scenario easily. It is a hashing implementation using binary tree and elements are ordered in keys. And it allows duplicate keys. It is there in C++, not sure is there anything similar in Ruby. Since question tagged 'data structure' hopefully my answer spread some lights.
For your case, you can consider lower range as key and upper range as value. If there is a collision in lower range you can easily identify that and compare the values of collided records and delete one if necessary.

Ruby Convert Date

I have a page that is returning this as a date new Date(1357106400000) (which I believe is Javascript).
How do I convert that using Ruby. I've tried:
Date.new(1357106400000)
DateTime.new(1357106400000)
and many others, but I can't get the correct date to display.
The date returned should be 12/09/2012
I believe you've got the time in milliseconds since the epoch, and the function you're looking for is Time#at. But you need to down-convert into seconds before calling. For example:
[holt#Michaela ~]$ irb
irb(main):001:0> Time.at(1357106400000)
=> Wed Dec 28 00:00:00 +0000 44974
irb(main):002:0> Time.at(1357106400000 / 1000)
=> Wed Jan 02 06:00:00 +0000 2013
irb(main):003:0>
Not the exact day you thought it was, but probably still correct. Hope that helps!
In Javascript:
> new Date(1357106400000)
Wed Jan 02 2013 06:00:00 GMT+0000 (GMT Standard Time)
In Ruby:
> require 'date'
> DateTime.strptime("1357106400000", "%Q")
=> #<DateTime: 2013-01-02T06:00:00+00:00 ((2456295j,21600s,0n),+0s,2299161j)>

Resources