Generate month end dates for last 12 months - ruby-on-rails

I need a method that generates an array containing the month end date for each of the past 12 months. I've come up with the solution below. It works, however, there's probably a more elegant way to solve this problem. Any suggestions? Is there a more efficient way to generate this array? Any advice would be greatly appreciated.
require 'active_support/time'
...
def months
last_month_end = (Date.today - 1.month).end_of_month
months = [last_month_end]
11.times do
month_end = (last_month_end - 1.month).end_of_month
months << month_end
end
months
end

Usually when you want an array of things start thinking about map. While you're at it why not generalize such a method so you can get back any n months you wish:
def last_end_dates(count = 12)
count.times.map { |i| (Date.today - (i+1).month).end_of_month }
end
>> pp last_end_dates(5)
[Sun, 30 Jun 2013,
Fri, 31 May 2013,
Tue, 30 Apr 2013,
Sun, 31 Mar 2013,
Thu, 28 Feb 2013]

require 'active_support/time'
def months
(1..12).map{|i| (Date.today - i.month).end_of_month}
end

There isn't a specific method, this can be an option however:
(1..12).map { |i| (Date.today - i.month).end_of_month }
Nothing special, but does the job.

require 'active_support/time'
(1..12).map do |m|
m.months.ago.end_of_month
end
Note, if you want correct order of months, you should also call reverse

Related

Latest date occurence for a given month and day [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 3 years ago.
Improve this question
What's the best way to get the last occurrence of a date having the given month and day with Ruby/Rails. The Returned value should be a Date object.
i.e.:
prev_occuring(month, day)
today = Date.today # => Thu, 14 Dec 2019
today.prev_occurring(:june, 1) # => Sat, 1 Jun 2019
or
today = Date.today # => Thu, 9 Jan 2020
today.prev_occurring(:june, 1) # => Sat, 1 Jun 2019
There is more than one way to do it, of course.
But here's one approach:
$> Date.parse('28 dec').then { |date| date.advance(years: (date > Date.today ? -1 : 0)) }
=> Fri, 28 Dec 2018
$> Date.parse('3 feb').then { |date| date.advance(years: (date > Date.today ? -1 : 0)) }
=> Sun, 03 Feb 2019
Alternatively, you could opt to be more explicit (i.e. not rely on Date.parse magic)...
You could use Date::MONTHNAMES to fetch the month number, and then write a little conditional to check whether the given date falls after the current date, in a calendar year.
It would then probably be easiest to just use Date.new with the year/month/day values, rather than Date#advance.
I understand that, given a month expressed as a symbol and a day of that month, the requirement is to return a date object that represents the last date that has the given month and day that is no later than the current date. Here is a pure-Ruby solution.
require 'date'
def last_date(month, day)
date_this_year = Date.strptime("%s%s" % [month.to_s.capitalize, day], '%B%d')
date_this_year <= Date.today ? date_this_year : date_this_year << 12
end
See Date::strptime and Date#<<.
last_date(:june, 1)
#=> #<Date: 2019-06-01 ((2458636j,0s,0n),+0s,2299161j)>
last_date(:december, 23)
#=> #<Date: 2018-12-23 ((2458476j,0s,0n),+0s,2299161j)>
One could alternatively write:
def last_date(month, day)
today = Date.today
date_this_year = Date.new(today.year,
Date::MONTHNAMES.index(month.to_s.capitalize), day)
date_this_year <= today ? date_this_year : date_this_year << 12
end
See Date::new.

Split an array of sequential dates into groups that are more than ten days apart

I would like to split the following array by introducing a split where the difference in dates is > 10 days:
dates = [Date.parse('2017-06-26'), Date.parse('2017-07-04'), Date.parse('2017-11-30')]
#=> [Mon, 26 Jun 2017, Tue, 04 Jul 2017, Thu, 30 Nov 2017]
the result should be the following:
[[Mon, 26 Jun 2017, Tue, 04 Jul 2017], [Thu, 30 Nov 2017]]
So far I've got a very procedural method. It accepts as parameters the array to be split, the minimum difference between members of different groups, and the attribute to evaluate to get the difference. (In the case of my array of Date objects, I would leave off the last parameter, as the value used to determine diffs would just be the Date itself)
def split_by_attribute_diff array, split_size, attribute = :itself
groups = []
current_group = []
previous = current = nil
array.sort_by(&attribute).each do |e|
previous = current
current = e
if previous && current.send(attribute) - previous.send(attribute) > split_size
if current_group.count > 0
groups << current_group
current_group = []
end
end
current_group << current
end
if current_group.count > 0
groups << current_group
end
groups
end
The things I like about this method are that 1) it works, 2) the algorithmic complexity is just that of the sort_by - after the array is sorted, it is traversed just once.
I guess the only thing I don't like is that it looks like it should be simpler. Is there a more Ruby-ish way to accomplish what I'm doing here?
It doesn't have to be hard if you properly leverage the Enumerable library in Ruby and in particular chunk_while which is specifically for carving up an array into little chunks based on a logical test:
require 'date'
dates = %w[
2017-06-26
2017-07-04
2017-11-21
2017-11-30
2017-12-30
].map { |d| Date.parse(d) }
r = dates.chunk_while do |a,b|
a + 10 > b
end
r.to_a.map { |a| a.map { |d| d.strftime('%Y-%m-%d') } }
# => [["2017-06-26", "2017-07-04"], ["2017-11-21", "2017-11-30"], ["2017-12-30"]]
My preference would be to use Enumerable#chunk_while (new in v2.3) or Enumerable#slice_when (new in v2.2) for this and similar problems. If, however, Ruby versions prior to 2.2 must be supported, an approach similar to the following can be used.
require 'date'
def group_em(dates)
fmt = '%Y-%m-%d'
dates.each_with_object([[]]) do |d,a|
if a.last.empty? || Date.strptime(d,fmt) <= Date.strptime(a.last.last,fmt) + 10
a.last << d
else
a << [d]
end
end
end
group_em %w| 2017-06-26 2017-07-04 2017-11-21 2017-11-30 2017-12-30 |
#=> [["2017-06-26", "2017-07-04"], ["2017-11-21", "2017-11-30"], ["2017-12-30"]]

If date is less than a week ago conditional?

I'm trying to create a conditional where if #challenge.deadline (which is a Date) is within the last week (i.e. the last 7 days), then do x, else do y.
I tried:
if #challenge.deadline < 1.week.ago #2017-03-03 01:52:13 -0500
if #challenge.deadline < 7.days.ago.to_date #2017-03-03
if #challenge.deadline < Date.current-7.days #2017-03-03
# All these come up false since I guess it sees 06 as more than 03, but I want the conditional to be based on date such as 06 is less than 7 days ago and therefore should be true
In this example #challenge.deadline equals 2017-03-06
how can I trigger the conditional when #challenge.deadline is a date that has happened within the last 7 days?
"within the last 7 days" describes a range from:
Date.current - 7 #=> Fri, 03 Mar 2017
to:
Date.current #=> Fri, 10 Mar 2017
To check whether #challenge.deadline lies within these bounds, you can use between?:
today = Date.current
if #challenge.deadline.between?(today - 7, today)
# within last 7 days
else
# either before of after
end
Instead of today - 7, you can also use today - 7.days or today - 1.week.
Or, to use an actual range:
today = Date.current
last_week = (today - 7)..today
if last_week.cover?(#challenge.deadline)
# ...
else
# ...
end
If you need this often, you could also consider patching Date:
class Date
def within_last?(duration, date = Date.current)
between?(date - duration, date)
end
end
and check it via:
if #challenge.deadline.within_last?(1.week)
# ...
else
# ...
end
if #challenge.deadline.to_date < 1.week.ago.to_date
do X
else
do Y
end

Get most recently occurring Wednesday?

How would I get the most recently occurring Wednesday, using Ruby (and Rails, if there's a pertinent helper method)?
Ultimately need the actual date (5/1/2013).
time = Time.now
days_to_go_back = (time.wday + 4) % 7
last_wed = days_to_go_back.days.ago
This works in Ruby:
require 'date'
def last_wednesday(date)
date - (date.wday - 3) % 7
end
last_wednesday(Date.today)
# => #<Date: 2013-05-01 ((2456414j,0s,0n),+0s,2299161j)>
In Rails there's beginning_of_week:
Date.today.beginning_of_week(:wednesday)
# => Wed, 01 May 2013
If you are okay with using another gem, I recommend Chronic.
With it, you can get last Wednesday by doing:
Chronic.parse('last Wednesday')
The simplest way (to me) is:
require 'date'
date = Date.today
date -= 1 until date.wednesday?
Pretty straightforward with Date:
require 'date'
today = DateTime.now.to_date
last_wednesday = today.downto(today - 6).select { |d| d.wednesday? }
You can even get the last weekday of your choice like this (here without error handling):
def last_weekday(weekday)
today = Time.now.to_date
today.downto(today-6).select do |d|
d.send((weekday.to_s + "?").to_sym)
end
end

Best way to create random DateTime in Rails

What is the best way to generate a random DateTime in Ruby/Rails? Trying to create a nice seeds.rb file. Going to use it like so:
Foo.create(name: Faker::Lorem.words, description: Faker::Lorem.sentence, start_date: Random.date)
Here is how to create a date in the last 10 years:
rand(10.years).ago
You can also get a date in the future:
rand(10.years).from_now
Update – Rails 4.1+
Rails 4.1 has deprecated the implicit conversion from Numeric => seconds when you call .ago, which the above code depends on. See Rails PR #12389 for more information about this. To avoid a deprecation warning in Rails 4.1 you need to do an explicit conversion to seconds, like so:
rand(10.years).seconds.ago
Here are set of methods for generating a random integer, amount, time/datetime within a range.
def rand_int(from, to)
rand_in_range(from, to).to_i
end
def rand_price(from, to)
rand_in_range(from, to).round(2)
end
def rand_time(from, to=Time.now)
Time.at(rand_in_range(from.to_f, to.to_f))
end
def rand_in_range(from, to)
rand * (to - from) + from
end
Now you can make the following calls.
rand_int(60, 75)
# => 61
rand_price(10, 100)
# => 43.84
rand_time(2.days.ago)
# => Mon Mar 08 21:11:56 -0800 2010
I prefer use (1..500).to_a.rand.days.ago
You are using Faker; why not use one of the methods provided by Faker::Date?
# Random date between dates
# Keyword arguments: from, to
Faker::Date.between(from: 2.days.ago, to: Date.today) #=> "Wed, 24 Sep 2014"
# Random date between dates except for certain date
# Keyword arguments: from, to, excepted
Faker::Date.between_except(from: 1.year.ago, to: 1.year.from_now, excepted: Date.today) #=> "Wed, 24 Sep 2014"
# Random date in the future (up to maximum of N days)
# Keyword arguments: days
Faker::Date.forward(days: 23) # => "Fri, 03 Oct 2014"
# Random date in the past (up to maximum of N days)
# Keyword arguments: days
Faker::Date.backward(days: 14) #=> "Fri, 19 Sep 2014"
# Random birthday date (maximum age between 18 and 65)
# Keyword arguments: min_age, max_age
Faker::Date.birthday(min_age: 18, max_age: 65) #=> "Mar, 28 Mar 1986"
# Random date in current year
Faker::Date.in_date_period #=> #<Date: 2019-09-01>
# Random date for range of year 2018 and month 2
# Keyword arguments: year, month
Faker::Date.in_date_period(year: 2018, month: 2) #=> #<Date: 2018-02-26>
# Random date for range of current year and month 2
# Keyword arguments: month
Faker::Date.in_date_period(month: 2) #=> #<Date: 2019-02-26>
current Faker version: 2.11.0
Here is how to create a date in this month:
day = 1.times.map{ 0+Random.rand(30) }.join.to_i
rand(day.days).ago
Another approach using DateTime's advance
def rand_date
# return a random date within 100 days of today in both past and future directions.
n = rand(-100..100)
Date.today.advance(days: n)
end
This is what I use:
# get random DateTime in last 3 weeks
DateTime.now - (rand * 21)
other way:
(10..20).to_a.sample.years.ago
I haven't tried this myself but you could create a random integer between two dates using the number of seconds since epoch. For example, to get a random date for the last week.
end = Time.now
start = (end - 1.week).to_i
random_date = Time.at(rand(end.to_i - start)) + start
Of course you end up with a Time object instead of a DateTime but I'm sure you can covert from here.
As I already mentioned in another question I think the following code-snippet is more consisent regarding the data-types of the parameters and of the value to be returned. Stackoverflow: How to generate a random date in Ruby?
Anyway this uses the rand() method's internal logic what is the random Date or random Time within a span. Maybe someone has a more efficient way to set the default-parameter to (Time.now.to_date) of the method random_date, so it doesn't need this typecasting.
def random_time from = Time.at(0.0), to = Time.now
rand(from..to)
end
# works quite similar to date :)
def random_date from = Date.new(1970), to = Time.now.to_date
rand(from..to)
end
Edit: this code won't work before ruby v1.9.3
You can pass Time Range to rand
rand(10.weeks.ago..1.day.ago)
Output Example:
=> Fri, 10 Jan 2020 10:28:52 WIB +07:00
Without user faker (cause I'm using an old version of ruby):
Time.zone.now - rand(16..35.years) - rand(1..31).days
My 'ish' gem provides a nice way of handling this:
# plus/minus 5 min of input date
Time.now.ish
# override that time range like this
Time.now.ish(:offset => 1.year)
https://github.com/spilliton/ish

Resources