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.
Related
I'm writing a validation and I have an hash with this structure
elements.map{ |e| [e.id,e.coverable.published_at] }.to_h
=> {305=>Fri, 17 Apr 2020 15:23:00 CEST +02:00,
306=>Fri, 17 Apr 2020 13:00:00 CEST +02:00,
307=>Fri, 17 Apr 2020 09:20:00 CEST +02:00,
308=>Fri, 17 Apr 2020 12:59:00 CEST +02:00,
309=>Fri, 17 Apr 2020 11:39:00 CEST +02:00}
I have a reference date...
published_at
=> Mon, 04 May 2020 23:51:00 CEST +02:00
I have to check if any of the element has a published_at datetime value bigger than my published_at.
Is there a short way to do that?
Try something like this
elements.any? { |e| e.coverable.published_at > your_published_at }
In case you need the element which passes the condition use find
element = elements.find { |e| e.coverable.published_at > your_published_at }
# if element is not nil such element is present
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
I create a hash with months as keys and timelaps as values
biens_delai[bien_date.mon] = b.delai
I get this result without month parsing
{Wed, 18 Jan 2017=>3.0, Sat, 25 Feb 2017=>2.0, Fri, 17 Mar 2017=>3.0, Sat, 25 Mar 2017=>5.0, Tue, 18 Apr 2017=>2.0, Thu, 29 Jun 2017=>2.0}
In March i have 2 values but when i parse by month i get the most high value and i want a addition of 2 values for March not the most high
{1=>3.0, 2=>2.0, 3=>5.0, 4=>2.0, 6=>2.0}
That's not the high value which you are getting, the values are getting overwritten, try the following
biens_delai[bien_date.mon] = biens_delai[bien_date.mon].to_f + b.delai
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.
Simple question, but I can't find a good or definitive answer. What is the best and most efficient way to combine Ruby Date and Time objects (objects, not strings) into a single DateTime object?
I found this, but it's not as elegant you would hope:
d = Date.new(2012, 8, 29)
t = Time.now
dt = DateTime.new(d.year, d.month, d.day, t.hour, t.min, t.sec, t.zone)
By the way, the ruby Time object also stores a year, month, and day, so you would be throwing that away when you create the DateTime.
When using seconds_since_midnight, changes in daylight savings time can lead to unexpected results.
Time.zone = 'America/Chicago'
t = Time.zone.parse('07:00').seconds_since_midnight.seconds
d1 = Time.zone.parse('2016-11-06').to_date # Fall back
d2 = Time.zone.parse('2016-11-07').to_date # Normal day
d3 = Time.zone.parse('2017-03-12').to_date # Spring forward
d1 + t
#=> Sun, 06 Nov 2016 06:00:00 CST -06:00
d2 + t
#=> Mon, 07 Nov 2016 07:00:00 CST -06:00
d3 + t
#=> Sun, 12 Mar 2017 08:00:00 CDT -05:00
Here's an alternative, similar to #selva-raj's answer above, using string interpolation, strftime, and parse. %F is equal to %Y-%m-%d and %T is equal to %H:%M:%S.
Time.zone = 'America/Chicago'
t = Time.zone.parse('07:00')
d1 = Time.zone.parse('2016-11-06').to_date # Fall back
d2 = Time.zone.parse('2016-11-07').to_date # Normal day
d3 = Time.zone.parse('2017-03-12').to_date # Spring forward
Time.zone.parse("#{d1.strftime('%F')} #{t.strftime('%T')}")
#=> Sun, 06 Nov 2016 07:00:00 CST -06:00
Time.zone.parse("#{d2.strftime('%F')} #{t.strftime('%T')}")
#=> Sun, 07 Nov 2016 07:00:00 CST -06:00
Time.zone.parse("#{d3.strftime('%F')} #{t.strftime('%T')}")
#=> Sun, 12 Mar 2017 07:00:00 CDT -05:00
Simple:
Date.new(2015, 2, 10).to_datetime + Time.parse("16:30").seconds_since_midnight.seconds
# => Object: Tue, 10 Feb 2015 16:30:00 +0000
You gotta love Ruby!
If using Rails, try any of these:
d = Date.new(2014, 3, 1)
t = Time.parse("16:30")
dt = d + t.seconds_since_midnight.seconds
# => ActiveSupport::TimeWithZone
dt = (d + t.seconds_since_midnight.seconds).to_datetime
# => DateTime
dt = DateTime.new(d.year, d.month, d.day, t.hour, t.min, t.sec)
# => DateTime
If you are using Ruby on Rails, this works great.
I built a method to extend the DateTime class to combine a date and a time. It takes the zone from the date so that it does not end up an hour off with daylight savings time.
Also, for convenience, I like being able to pass in strings as well.
class DateTime
def self.combine(d, t)
# pass in a date and time or strings
d = Date.parse(d) if d.is_a? String
t = Time.zone.parse(t) if t.is_a? String
# + 12 hours to make sure we are in the right zone
# (eg. PST and PDT switch at 2am)
zone = (Time.zone.parse(d.strftime("%Y-%m-%d")) + 12.hours ).zone
new(d.year, d.month, d.day, t.hour, t.min, t.sec, zone)
end
end
So you can do:
DateTime.combine(3.weeks.ago, "9am")
or
DateTime.combine("2015-3-26", Time.current)
etc...
I found another way, I hope this is correct.
datetojoin=Time.parse(datetime).strftime("%Y-%m-%d")
timetojoin=Time.parse(time).strftime("%T")
joined_datetime = Time.parse(datetojoin +" "+ timetojoin).strftime("%F %T")
Any thoughts? Please share.