I am trying to do reporting for a specific period, those periods supported being:
"today": start = DateTime.now.beginning_of_day; end = start.end_of_day
"this week": start = DateTime.now.beginning_of_week; end = start.end_of_week
"this bi-week" (first half or second half of the month, relative to the current date of DateTime.now)
it is very important to be sure that the month is evenly divided, and cweek is not used; the start of the first bi-week should be the first of the month, and the end of the second bi-week should be the last of the month. The dividing date between the two should be the total number of days in the month divided by 2, rounded up (i.e. if there are 31 days, the second bi-week would start on the 15th, and not the 14th of the month).
"this month": start = DateTime.now.beginning_of_month; end = start.end_of_month
"last month": start = (DateTime.now - 1.month).beginning_of_month; end = start.end_of_month
"this quarter": one of each of the four 3-month periods in the year, ie quarters. Ex: from 1/1/1999 to 3/31/1999 or from 10/1/2000 to 12/31/2000.
"this year" = start = DateTime.now.beginning_of_year --> end = start.end_of_year
I'm having trouble calculating the bold date ranges based on the current date (assume DateTime.now).
How can I calculate the bi-weekly and quarterly period relative to the current date, DateTime.now?
Expected output
For the date May 7th, 2018 (5/7/2018):
"this bi-week" should be the period of 5/1/2018 to 5/15/2018
"this quarter" should be the period of 4/1/2018 to 6/30/2018
For the date February 29th, 2020 (2/29/2020), a leap year:
"this bi-week" should be the period of 2/15/2018 to 2/29/2018
"this quarter" should be the period of 1/1/2018 to 3/31/2018
What about this:
This bi-week:
def bi_week_limits_for(date)
days_in_month = Time.days_in_month(date.month, date.year)
# this will round down. 30/31-day months will output 15. 28 / 29-day will output 14. Adjust as per your requirements
middle_day = days_in_month / 2
if date.day <= middle_day
[date.beginning_of_month, date.change(day: middle_day)]
else
[date.change(day: middle_day + 1), date.end_of_month]
end
end
In my console:
pry(main)> bi_week_limits_for Date.parse('29/2/2020')
=> [Sat, 15 Feb 2020, Sat, 29 Feb 2020]
pry(main)> bi_week_limits_for Date.parse('7/5/2018')
=> [Tue, 01 May 2018, Tue, 15 May 2018]
This quarter:
def bi_week_limits_for(date)
[date.beginning_of_quarter, date.end_of_quarter]
end
In my console
pry(main)> date = Date.parse('7/5/2018')
=> Mon, 07 May 2018
pry(main)> quarter_limits_for date
=> [Sun, 01 Apr 2018, Sat, 30 Jun 2018]
pry(main)> date = Date.parse '29/2/2020'
=> Sat, 29 Feb 2020
pry(main)> quarter_limits_for date
=> [Wed, 01 Jan 2020, Tue, 31 Mar 2020]
Reference: https://apidock.com/rails/DateAndTime/Calculations/beginning_of_quarter
Related
I want to get start and end date of month in ruby on rails. User enter integer value from 1..12.
If user enter 1 then JAN start_date,end_date
If user enter 2 then FEB start_date,end_date
If user enter 3 then MARCH start_date,end_date
and so on
There are built-in methods in active_support (part of Rails):
month_number = 5 # user-provided month number
month_beginning = Date.new(Date.today.year, month_number)
# => Sun, 01 May 2016
month_ending = month_beginning.end_of_month
# => Tue, 31 May 2016
I am facing some problem in finding the days between 2 dates.
The scenario is as follow :
time = Time.new
enddate_timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
startdate = #logInfo.updated_at #here updated_at is the column in the db .
What is the best way to find the days ?
Post.where(["date(created_at) BETWEEN ? AND ?", Date.yesterday, Date.tomorrow]
More details: http://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-where
There are several possible solutions. A possibility is to create a Range with the dates, then convert the range into an array
# set the boundaries
today = Time.current
past = 5.days.ago
Note that both boundaries are time instances. We should cast them into dates. I used time(s) because your column is a time.
range = past.to_date..today.to_date
# => Sun, 29 Dec 2013..Fri, 03 Jan 2014
Use to_a to expand the range getting all the days
range.to_a
# => [Sun, 29 Dec 2013, Mon, 30 Dec 2013, Tue, 31 Dec 2013, Wed, 01 Jan 2014, Thu, 02 Jan 2014, Fri, 03 Jan 2014]
range.count
# => 6
You can also enumerate them
range.each { |day| puts day.day }
29
30
31
1
2
3
now = Time.now
future = Time.now + 100 days
while now < future
now = now + 1.day
puts now
end
This will give you the dates, not the days count.
(startdate.beginning_of_day..enddate_timestamp.to_time.beginning_of_day).step(1.day) do |day|
puts day
end
P.S: Performance wise it's not good.
I have an action in a controller where I want to generate a batch of 10 consecutive days for each request.
I keep track of where we are in the batch cycle by passing a variable page, for each batch.
The cycle of batches should start with today.
How can I express this in the simplest way?
Right now I have the following mess, which doesn't even work (each batch, except for the first one, start one day too early):
#page_number = (params[:page_number] || 0).to_i + 1
today = Date.today
batch_amount = 10
first_day_of_current_batch = today + (batch_amount * (#page_number - 1))
days = first_day_of_current_batch..(first_day_of_current_batch + batch_amount)
As I didn't understand completely all your requirements, so I will make a few assumptions:
Pages start with 1, nil is assumed as 1
Today is 27th of October, assuming that batch size is 10 and page number 1, the batch should be Sun, 27 Oct 2013..Wed, 05 Nov 2013
Today is 27th of October, assuming that batch size is 10 and page number 2, the batch should be Sun, 06 Oct 2013..Wed, 15 Nov 2013
As You have quite a complex logic there, it will be wise to extract it to separate object.
It's considered a common and good practice, as it decouples Your code and tests.
Custom class:
# app/services/batch_of_days.rb
class Services::BatchOfDays
class << self
def create(page_nr = 1, batch_size = 10, start_from = Date.today)
new(page_nr.to_i, batch_size, start_from).create
end
end
def initialize(page_nr, batch_size, start_from)
#page_nr = page_nr
#batch_size = batch_size
#start_from = start_from
end
def create
first_day..last_day
end
private
def first_day
#first_day ||= #start_from + ( #page_nr - 1 ) * #batch_size
end
def last_day
first_day + #batch_size - 1
end
end
Usage:
irb(main):262:0> BatchOfDays.create
# => Sun, 27 Oct 2013..Tue, 05 Nov 2013
irb(main):262:0> BatchOfDays.create(1)
# => Sun, 27 Oct 2013..Tue, 05 Nov 2013
irb(main):263:0> BatchOfDays.create(2)
# => Wed, 06 Nov 2013..Fri, 15 Nov 2013
irb(main):264:0> BatchOfDays.create(3)
# => Sat, 16 Nov 2013..Mon, 25 Nov 2013
I recommend numbering your pages starting at 0 (i.e. page #0, the first one, begins at Date.today).
You could then use the following:
#page_number = (params[:page_number] || 0).to_i
batch_amount = 10
first = (#page_number * batch_amount).days.from_now.to_date
last = ((#page_number + 1) * batch_amount - 1).days.from_now.to_date
days = (first..last).to_a
the to_date let you specify the range as a range of consecutive days, and the to_a converts the range into an Array of Date objects, one for each day.
I have a DateTime object representing a particular date, as in "2011-01-15 00:00:00 UTC" to represent January 15th. I would like to produce the range of times in a particular time zone that have the same date.
The method signature would probably be something like
def day_range_for(date, tz)
# ...
end
For example, if I have range_for(DateTime.parse('2011-01-15'), 'CST'), then I want the result to be a range like 2011-01-15 00:00:00 -0600 .. 2011-01-15 23:59:59 -0600.
Can you take the input string instead of the object? If you pass in a DateTime object, its a tad counter intuitive because the time the object represents isn't the actual time you are looking for. It would conform to my expectations if you either passed in the correct, absolute DateTime, or the string itself. The actual meat of the problem is entirely handled by ActiveSupport, which I think you are using since you tagged this question with Rails. How does this look?
def range_for(input, tz=nil)
if tz.is_a?(String)
tz = ActiveSupport::TimeZone.new(tz)
else
tz = Time.zone
end
if input.acts_like?(:date) || input.acts_like?(:time)
d = input.in_time_zone(tz)
else
d = tz.parse(input)
end
return d.beginning_of_day..d.end_of_day
end
Have a look:
ruby-1.9.2-p0 > range_for('2011-01-15', 'Alaska')
=> Sat, 15 Jan 2011 00:00:00 AKST -09:00..Sat, 15 Jan 2011 23:59:59 AKST -09:00
ruby-1.9.2-p0 > range_for(Time.zone.now)
=> Mon, 10 Jan 2011 00:00:00 EST -05:00..Mon, 10 Jan 2011 23:59:59 EST -05:00
ruby-1.9.2-p0 > range_for('2011-01-15', 'EST')
=> Sat, 15 Jan 2011 00:00:00 EST -05:00..Sat, 15 Jan 2011 23:59:59 EST -05:00
How about:
def day_range_for date, zone
(DateTime.new(date.year,date.month,date.day,0,0,0,zone)..DateTime.new(date.year,date.month,date.day,23,59,59,zone))
end
day_range_for(DateTime.parse('2011-01-15'), 'CST')
#=> #<DateTime: 2011-01-15T00:00:00-06:00 (9822307/4,-1/4,2299161)>..#<DateTime: 2011-01-15T23:59:59-06:00 (212161917599/86400,-1/4,2299161)>
I am trying to create a page to display a list of links for each month, grouped into years. The months need to be between two dates, Today, and The date of the first entry.
I am at a brick wall, I have no idea how to create this.
Any help would be massively appriciated
Regards
Adam
Just put what you want inside a range loop and use the Date::MONTHNAMES array like so
(date.year..laterdate.year).each do |y|
mo_start = (date.year == y) ? date.month : 1
mo_end = (laterdate.year == y) ? laterdate.month : 12
(mo_start..mo_end).each do |m|
puts Date::MONTHNAMES[m]
end
end
The following code will add a months_between instance method to the Date class
#!/usr/bin/ruby
require 'date'
class Date
def self.months_between(d1, d2)
months = []
start_date = Date.civil(d1.year, d1.month, 1)
end_date = Date.civil(d2.year, d2.month, 1)
raise ArgumentError unless d1 <= d2
while (start_date < end_date)
months << start_date
start_date = start_date >>1
end
months << end_date
end
end
This is VERY lightly tested, however it returns an Array of dates each date being the 1st day in each affected month.
I don't know if I've completely understood your problem, but some of the following might be useful. I've taken advantage of the extensions to Date provided in ActiveSupport:
d1 = Date.parse("20070617") # => Sun, 17 Jun 2007
d2 = Date.parse("20090529") #=> Fri, 29 May 2009
eom = d1.end_of_month #=> Sat, 30 Jun 2007
mth_ends = [eom] #=> [Sat, 30 Jun 2007]
while eom < d2
eom = eom.advance(:days => 1).end_of_month
mth_ends << eom
end
yrs = mth_ends.group_by{|me| me.year}
The final line uses another handy extension: Array#group_by, which does pretty much exactly what it promises.
d1.year.upto(d2.year) do |yr|
puts "#{yrs[yr].min}, #{yrs[yr].max}"
end
2007-06-30, 2007-12-31
2008-01-31, 2008-12-31
2009-01-31, 2009-05-31
I don't know if the start/end points are as desired, but you should be able to figure out what else you might need.
HTH
Use the date_helper gem which adds the months_between method to the Date class similar to Steve's answer.
xmas = Date.parse("2013-12-25")
hksar_establishment_day = Date.parse("2014-07-01")
Date.months_between(xmas,hksar_establishment_day)
=> [Sun, 01 Dec 2013, Wed, 01 Jan 2014, Sat, 01 Feb 2014, Sat, 01 Mar 2014, Tue, 01 Apr 2014, Thu, 01 May 2014, Sun, 01 Jun 2014, Tue, 01 Jul 2014]