Query all records from a week in a month - ruby-on-rails

I am creating a Rails 5 app.
In this app I got Survey model. I am able to run queries (using scopes) to get all surveys from a specific month and all from a specific quarter but I want to get all from a specific week in a month too, how can I do that?
These are my quarter and month scopes
scope :period_quarter, -> (year, quarter) { where(created_at: Date.new(year.to_i, 3 * quarter.to_i - 2).all_quarter) }
scope :period_month, -> (year, month) { where(created_at: Date.new(year.to_i, month.to_i).all_month) }
How can I add a scope to get all surveys from a specific week in a month. I will provide year, month and week (1-5).

Consider the same approach you've been using, only with all_week and perhaps beginning_of_week as needed to get the correct start date.

To get a date in a specific week, you could use something like this:
def week(year, month, week_num)
Date.new(year, month, 7 * week_num - 6)
end
week(2018, 8, 1).beginning_of_week # => Mon, 30 Jul 2018
week(2018, 8, 1).end_of_week # => Sun, 05 Aug 2018
week(2018, 8, 1).all_week # => Mon, 30 Jul 2018..Sun, 05 Aug 2018
To use this in a query, you could use it like this:
where(created_at: week(2018, 8, 1).all_week)
This will count the first week in a month as the week where the 1. is. However, if you want it to use the first full week in a month, you can remove the -6.

Related

How to parse week and year with strptime

Usually I don't have issues parsing strings to Time in Rails, however I cannot understand why it's behaving like this:
irb(main):073:0> DateTime.strptime("20/2020", "%V/%Y")
=> Wed, 01 Jan 2020 00:00:00 +0000
I want the 20th week of 2020, not the 1st Jan...
I think that you need to use the commercial method.
require 'date'
week = 20
start_week = Date.commercial(2020, week)
# => Mon, 11 May 2020
puts "#{start_week}"
# => 2020-05-11
In case you need a Range, you can do:
Date.commercial(2020, week).all_week
# => Mon, 11 May 2020..Sun, 17 May 2020
For more information see the documentation.
require 'date'
If week 1 begins on Sunday:
Date.strptime("20/2020", "%U/%Y")
#=> #<Date: 2020-05-17 ((2458987j,0s,0n),+0s,2299161j)>
and on Monday:
d = Date.strptime("20/2020", "%W/%Y")
#=> #<Date: 2020-05-18 ((2458988j,0s,0n),+0s,2299161j)>
and also from the doc Date#strftime (which contains the formatting directives for Date::strptime), "The days in the year before the first week are in week 0".
There is generally a difference between %Y and %G for the year depending on your assumed definition for a week number.
For ISO 8601 weeks (which are commonly used in e.g. Europe), you have to use the week-based year here (%G) rather than the day-=based year (%Y). This is important around the edges of the years. For example, 2021-01-01 is in week 53 of 2020. On this date
%G is 2020, but
%Y is already 2021
If you want to parse an ISO 8601 week, you can thus use the following code to ensur you use the right week and year:
require 'date'
DateTime.strptime("20/2020", "%V/%G")
# => #<DateTime: 2020-05-11T00:00:00+00:00 ((2458981j,0s,0n),+0s,2299161j)>

Why does adding and subtracting 2 months to a date not give back the same date?

I'm a bit confused about this outcome, taking today's date minus 2 months, and then taking that date again and adding two months, does not give me today's date when assign the dates to a variable.
Time.zone
"Eastern Time (US & Canada)"
> today = Date.today.in_time_zone
=> Thu, 31 Aug 2017 00:00:00 EDT -04:00
> a = today - 2.months # This is persisted to the db
=> Fri, 30 Jun 2017 00:00:00 EDT -04:00
> b = a + 2.months
=> Wed, 30 Aug 2017 00:00:00 EDT -04:00
If I however, just use the same object, it moves back and forth properly:
> today = Date.today.in_time_zone
=> Thu, 31 Aug 2017 00:00:00 EDT -04:00
> today - 2.months
=> Fri, 30 Jun 2017 00:00:00 EDT -04:00
> today + 2.months
=> Tue, 31 Oct 2017 00:00:00 EDT -04:00
The problem is obviously when "a" gets saved to a database, and then retrieved later on, and calculated plus 2 months..., it should match today's date.
TL;DR
A month is not a fixed duration. Adding or taking a month does not give the same "time shift" depending on which day you are.
The usual algorithm
to add or take months is the following :
try to land on the same day number (4th, 30th, 31st) as you started, just by changing the month
if you would land on an impossible date (like 31th September, 30th February, 29th February for some years) then just go the maximum allowed day number of this month
This implies that adding some months then taking out the same number of months will not necessarily give you back the same date.
Examples :
31st of some month + 1 month --> One would want to get to the 31th of next month
But if there is no 31st of next month (like for 31th of August, no 31st of September), then what to do ?
Usual interpretation would say that you want to go to the end of the month, this is 30th September (for rent or other monthly subscription, for instance)
But usually, 30th of some month - 1 month --> One would want to get to the 30th of the previous month.
That would lead to .... 30th of August. Not 31th of August.
Hence: some date + 1 month - 1 month does not necessarily give the original date !
Another example :
Start at the 30th of August.
Take a month -> 30th of July
Add a month -> You want to get to 30th of August (same number, next month) or to the end of August ?
The default algorithm will try to give the same day number -> 30th of August (which is more logical now)
Also with days...
Note that the same problem happens with days,but much less often ! When some days don't have the same number of hours, for daylight saving days, when adding and taking same number of days you might not get back to the original date and time as you started from.

Traverse through a certain month in Date with just a given integer

Would it be possible to go to a certain month of the year with just a given integer. For example
date = Date.today
=> Wed, 30 Dec 2015
What if I want to go back to a certain month based on that date and I am just given a number let's say 7 which is July in the Date::MONTHNAMES so would it be possible to do something like
date = Date.today
=> Wed, 30 Dec 2015
date.go_to_month_of(7) # which will bring me back to July 30, 2015
Okay I found it. It's:
date = Date.today
date.change(:month => x)
Hope this helps you!

Rails, day of year to date

I have day of the year. For example 15th day of 2014, or 210th day of 2014. I need to get the Date on the particular day. Is there any library function in Rails/Ruby that I can use, or any other elegant way?
Something like:
15th day of 2014 = 15-Jan-2014
210th day of 2014 = 29-Jul-2014
You can use Date.ordinal for that:
require 'date'
Date.ordinal(2014, 210)
# => #<Date: 2014-07-29 ((2456868j,0s,0n),+0s,2299161j)>
Date.new(2014, 1, 1) + 210
=> Wed, 30 Jul 2014
Edit - you'd need to subtract 1 day. I prefer #wonderb0lt's suggestion.

Ruby on rails active record multiple group record to calculate numbers

I want to calculate number of employee who has commanded to shoot on every day.
Shoot.where("command_type = ?", "shoot").group("DATE(created_at)").group("employee_number").order("DATE(created_at)").count
It will give me a output like
{[Wed, 03 Aug 2011, "7838744451"]=>2, [Wed, 03 Aug 2011, "8055898284"]=>11,[Fri, 05 Aug 2011, "9702553828"]=>1, [Fri, 05 Aug 2011, "9717466677"]=>1,[Fri, 05 Aug 2011, "8055898284"]=>1,[Wed, 06 Aug 2011, "8055898284"]=>5
I want to have an array something like:-
[2,0,3,1] // zero is for the dates when no record is there for that date. and number in the array is that number of the employees that has been ordered to shoot.
For example from array: 2 employee were ordered to shoot on Wed, 3rd.
0 employee were ordered to shoot on 4th and so on...
Also: How can i calculate that a how many times all employees were commanded to shoot in a week/month. Basically 100 employee were ordered to shoot 1st week. 120 employees were order to shoot 2nd week and so on 1st month and 2nd month..
The command you are using returns the daily count by employee. If you want to get the daily count across employees, remove the 2nd group call.
# return [2,3,1]
Shoot.where(:command_type => shoot").group("DATE(created_at)").values
If you want to fill the missing date values, you can use this function:
class Shoot
def self.daily_count_by_type(type = "shoot", range=7.days)
counts = Shoot.where(:command_type => type).group("DATE(created_at)").
count("DISTINCT employee_id")
(range.ago.to_date..Date.today).map {|d| counts[d.to_s] || 0}
end
end
Now
Shoot.daily_count_by_type # for type `shoot`, last 7 days
Shoot.daily_count_by_type("shoot") # for type `shoot`, last 7 days
Shoot.daily_count_by_type("eat", 14.days) # for type `eat`, last 14 days
Shoot.daily_count_by_type("leave") # for type `leave`, last 14 days
Make sure you add an index on DATE(CREATED_AT) to improve the performance.
Edit 1
Based on the comment you need to COUNT the distinct values. I have updated the answer accordingly.

Resources