Rails Activerecord where date from 20 to 20 - ruby-on-rails

I have to display only the works between the 20th of one month to the 20th of next month (each period from 20 to 20 of every month)
I see two cases:
1.- If I`m at 15 of any month(or lower than 20) I have to display from 20 of last month to today.
2.- If I´m at 25 of any month(or higher than 20) I Have to display from 20 of this month to today.
I don't know the syntax for that
Something like this
if Date.today.day > 20
#works = Work.where(created_at: "20 of this month to Date.today")
else
#works = Work.where(created_at: "20 of last month to Date.today")
end

date = Date.today.change(day: 20)
date -= 1.month if date > Date.today
#works = Work.where(created_at: date..(date + 1.month))
Depending on your specific requirements, the last condition could be any of the following:
date..(date + 1.month)
date...(date + 1.month)
date..date.today
date...date.today

Something like this I did once (this is written from memory, not tested)
if Date.today.day > 20
start_day = Date.new(Date.today.year, Date.today.month, 20)
end_day = start_day + 1.month
else
start_day = Date.new(Date.today.year, Date.today.month, 20) - 1.month
end_day = Date.new(Date.today.year, Date.today.month, 20)
end
Work.where(created_at: start_day..end_day)

It would look something like this:
# The ".." sets a range, (from)..(to)
if Date.today.day > 20
#works = Work.where(created_at: (Date.today - 20)..(Date.today))
else
#works = Work.where(created_at: (Date.today.change(day: 20))..(Date.today))
end
You may also want to check the documentation for additional DateTime calculations :)

Thanks to all, I took your ideas and this work for me
if Date.today.day > 20
#works = Work.where(created_at: ((Date.new((Date.today.year),(Date.today.month),21)))..Date.today)
else
#works = Work.where(created_at: ((Date.new((Date.today.year),(Date.today.month),21)) - 1.month)..Date.today)
end

Related

Order date on day and month

I have users with a birth_date and now I want to query all users who have their birthday within the next 10 days. I can't just order on birth_date because then I will get this:
01-01-1960
18-12-1975
16-12-1998
Instead of the desired result:
16-12-1998
18-12-1975
01-01-1960
So how can I only order on the day and month, but not the year?
This works in postgreSQL...
start_month = Date.today.month
start_day = Date.today.day
end_month = (Date.today + 10).month
end_day = (Date.today + 10).day
if start_month == end_month
#users = User.where("DATE_PART('month', birth_date) = ? AND DATE_PART('day', birth_date) >= ? AND DATE_PART('day', birth_date) <= ?", start_month, start_day, end_day)
else
#users = User.where("(DATE_PART('month', birth_date) = ? AND DATE_PART('day', birth_date) >= ?) OR (DATE_PART('month', birth_date) = ? AND DATE_PART('day', birth_date) <= ?)", start_month, start_day, end_month, end_day)
end
#users.order("DATE_PART('month', birth_date), DATE_PART('day', birth_date)")
This selects all records with birthdays within the next 10 days and sorts them.
If ten days in the future is still the same month (say on December 14) it selects December between 14 and 24... if it's in a future month (say on December 25) it selects December to end of month and January from beginning to the 4th.
A ruby implementation. Note that it may not be as performant as the DB version...
ids_of_next_10_days = User.all.pluck(:id, :birth_date).map do |user_info|
next_birthday = user_info[1].change(year: Time.now.year)
next_birthday = next_birthday.change(year: Time.now.year + 1) if next_birthday < Date.today
next_birthday.between?(Date.today, Date.today + 10) ? user_info[0] : nil
end.compact
User.where(id: ids_of_next_10_days)
On the Ruby side you can select needed users at first(Postgresql to_char is used):
today = Date.current
dates = (today ... today + 10.days).map { |d| d.strftime('%m%d') }
dates << '0229' if dates.include?('0228') # update by SteveTurczyn
users = User.where("to_char(birth_date, 'MMDD') in (?)", dates).to_a
then sort them:
users.sort_by! do |user|
user_yday = user.birth_date.yday
user_yday >= today.yday ? user_yday : user_yday + 366
end

Any option to post-process returned value in long conditional, other than setting variables for each statement?

def some_method(x)
if x == 1
date = Date.today
elsif x == 5
date = Date.today + 2
else
date = Date.today - 2
end
date + 20
end
For visual clarity, is it possible somehow to omit date = for each statement and catch whatever the returned value is from the conditional and add 20 to it?
(The code is for example purpose, my own code has 10 if-statements.)
def some_method(x)
date = if x == 1
Date.today
elsif x == 5
Date.today + 2
else
Date.today - 2
end
date + 20
end
If you have 10 if statements it is probably better to refactor code using case-when like this:
def some_method(x)
date = case x
when 1; Date.today
when 5; Date.today + 2
else; Date.today - 2
end
date + 20
end

Ruby: given a date find the next 2nd or 4th Tuesday

I can't seem to find an elegant way to do this...
Given a date how can I find the next Tuesday that is either the 2nd or the 4th Tuesday of the calendar month?
For example:
Given 2012-10-19 then return 2012-10-23
or
Given 2012-10-31 then return 2012-11-13
October November
Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa
1 2 3 4 5 6 1 2 3
7 8 9 10 11 12 13 4 5 6 7 8 9 10
14 15 16 17 18 19 20 11 12 13 14 15 16 17
21 22 23 24 25 26 27 18 19 20 21 22 23 24
28 29 30 31 25 26 27 28 29 30
Scroll to the bottom if you just want to see what the end result can look like..
Using code snippets from some date processing work I've done recently in ruby 1.9.3.
Some upgrades to DateTime:
require 'date'
class DateTime
ALL_DAYS = [ 'sunday', 'monday', 'tuesday',
'wednesday', 'thursday', 'friday', 'saturday' ]
def next_week
self + (7 - self.wday)
end
def next_wday (n)
n > self.wday ? self + (n - self.wday) : self.next_week.next_day(n)
end
def nth_wday (n, i)
current = self.next_wday(n)
while (i > 0)
current = current.next_wday(n)
i = i - 1
end
current
end
def first_of_month
self - self.mday + 1
end
def last_of_month
self.first_of_month.next_month - 1
end
end
method_missing Tricks:
I have also supplemented the class with some method missing tricks to map calls from next_tuesday to next_wday(2) andnth_tuesday(2)tonth_wday(2, 2)`, which makes the next snippet easier on the eyes.
class DateTime
# ...
def method_missing (sym, *args, &block)
day = sym.to_s.gsub(/^(next|nth)_(?<day>[a-zA-Z]+)$/i, '\k<day>')
dindex = ALL_DAYS.include?(day) ? ALL_DAYS.index(day.downcase) : nil
if (sym =~ /^next_[a-zA-Z]+$/i) && dindex
self.send(:next_wday, dindex)
elsif (sym =~ /^nth_[a-zA-Z]+$/i) && dindex
self.send(:nth_wday, dindex, args[0])
else
super(sym, *args, &block)
end
end
def respond_to? (sym)
day = sym.to_s.gsub(/^(next|nth)_(?<day>[a-zA-Z]+)$/i, '\k<day>')
(((sym =~ /^next_[a-zA-Z]+$/i) || (sym =~ /^nth_[a-zA-Z]+$/i)) && ALL_DAYS.include?(day)) || super(sym)
end
end
Example:
Given a date:
today = DateTime.now
second_tuesday = (today.first_of_month - 1).nth_tuesday(2)
fourth_tuesday = (today.first_of_month - 1).nth_tuesday(4)
if today == second_tuesday
puts "Today is the second tuesday of this month!"
elsif today == fourth_tuesday
puts "Today is the fourth tuesday of this month!"
else
puts "Today is not interesting."
end
You could also edit method_missing to handle calls such as :second_tuesday_of_this_month, :fourth_tuesday_of_this_month, etc. I'll post the code here if I decide to write it at a later date.
Take a look at Chronic or Tickle, both are gems for parsing complex times and dates. Tickle in particular will parse recurring times (I think it uses Chronic as well).
Check out this gem, you might be able to figure out your answer
https://github.com/mojombo/chronic/
Since you already use Rails, you don't need the includes, but this works in pure Ruby as well for reference.
require 'rubygems'
require 'active_support/core_ext'
d = DateTime.parse('2012-10-19')
result = nil
valid_weeks = [d.beginning_of_month.cweek + 1, d.beginning_of_month.cweek + 3]
if valid_weeks.include?(d.next_week(:tuesday).cweek)
result = d.next_week(:tuesday)
else
result = d.next_week.next_week(:tuesday)
end
puts result
I think you should probably use a library if you're needing to branch out into more interesting logic, but if what you've described is all you need, the code below should help
SECONDS_PER_DAY = 60 * 60 * 24
def find_tuesday_datenight(now)
tuesdays = [*-31..62].map { |i| now + (SECONDS_PER_DAY * i) }
.select { |d| d.tuesday? }
.group_by { |d| d.month }
[tuesdays[now.month][1], tuesdays[now.month][-2], tuesdays[(now.month + 1) % 12][1]]
.find {|d| d.yday > now.yday }
end
Loop through the last month and next month, grab the tuesdays, group by month, take the 2nd and the 2nd last tuesday of the current month (If you actually do want the 4th tuesday, just change -2 to 3) and the 2nd tuesday of the next month and then choose the first one after the provided date.
Here's some tests, 4 tuesdays in month, 5 tuesdays in month, random, and your examples:
[[2013, 5, 1], [2013, 12, 1], [2012, 10, 1], [2012, 10, 19], [2012, 10, 31]].each do |t|
puts "#{t} => #{find_tuesday_datenight(Time.new *t)}"
end
which produces
[2013, 5, 1] => 2013-05-14 00:00:00 +0800
[2013, 12, 1] => 2013-12-10 00:00:00 +0800
[2012, 10, 1] => 2012-10-09 00:00:00 +0800
[2012, 10, 19] => 2012-10-23 00:00:00 +0800
[2012, 10, 31] => 2012-11-13 00:00:00 +0800
I'm sure it could be simplified, and I'd love to hear some suggestions :) (way too late &tired to even bother figuring out what the actual range should be for valid dates i.e. smaller than -31..62)
so here is the code that will resolve a weekday for a given week in a month (what you asked for with little sugar). You should not have problems if you are running inside rails framework. Otherwise make sure you have active_support gem installed. Method name is stupid so feel free to change it :)
usage: get_next_day_of_week(some_date, :friday, 1)
require 'rubygems'
require 'active_support/core_ext'
def get_next_day_of_week(date, day_name, count)
next_date = date + (-date.days_to_week_start(day_name.to_sym) % 7)
while (next_date.mday / 7) != count - 1 do
next_date = next_date + 7
end
next_date
end
I use the following to calculate Microsoft's patch Tuesday date. It was adapted from some C# code.
require 'date'
#find nth iteration of given day (day specified in 'weekday' variable)
findnthday = 2
#Ruby wday number (days are numbered 0-7 beginning with Sunday)
weekday = 2
today = Time.now
todayM = today.month
todayY = today.year
StrtMonth = DateTime.new(todayY,todayM ,1)
while StrtMonth.wday != weekday do
StrtMonth = StrtMonth + 1;
end
PatchTuesday = StrtMonth + (7 * (findnthday - 1))

finding which timerange includes given time

I have time ranges, for example
normal time : 03:15 - 17:00
discounttime : 17:00 - 23:10
night time : 23:10 - 03:15
And I have to find to which range current time belongs_to.
Before I had such code:
when (3*60+15)..(17*60+00)
puts "normal_time"
when (17*60+00)..(23*60+10)
puts "disc_time"
else
puts "night_time"
end
But this code is not working if I have
normal time : 07:15 - 19:00
discounttime : 19:00 - 01:10
night time : 01:10 - 07:15
Which code should I use to make both configs working?
Should I use 2 days duration?
Dirty Solution
I know that the code is not optimized, but it works. I'll refactor it
time = Time.now
a = [:normal_time, :disc_time, :night_time]
b = a + [a[0]]
pp = Priceplan.last # has times sets for :normal_time, :disc_time, :night_time
check = time.hour*60+time.min
a.each_with_index do |e, i|
starts_at = pp.send(e).hour * 60 + pp.send(e).min
ends_at = pp.send(b[i+1]).hour * 60 + pp.send(b[i+1]).min
if starts_at < ends_at
if (starts_at..ends_at).include?(check)
p "ok!!!!!!!! at #{e}"
break
end
else
if (starts_at..(24*60)).include?(check)
p "ok!!!!!!!! at #{e} (divided first)"
break
elsif (0..ends_at).include?(check)
p "ok!!!!!!!! at #{e} (divided second)"
break
end
end
end
Ranges always run from low numbers to higher numbers. When you have a range like: 19:00-01:10 you're creating an invalid range. To fix it just divide up the ranges so that you have 19:00-23:59 and then a new range 00:00-01:10 to cover the rest of the timespan.
You can try using today midnight as a base point
def today_midnight
Time.now.utc.midnight
end
normal time : where(:belongs_to => (today_midnight + 7.hours + 15.minutes) .. (today_midnight + 19.hours))
discount time : where(:belongs_to => (today_midnight + 1.hours + 10.minutes) .. (today_midnight + 19.hours))
night time: where(:belongs_to => (today_midnight + 1.hour + 10.minutes) .. (today_midnight + 7.hours + 15.minutes))
Though long, I believe writing in such a manner makes the code a lot more readable. i.e. a year after, if you'll be able to understand whats going on by quickly looking at the code.

Ruby next run in X minutes

I have a ruby on rails app, and there is a cron running in the background.
The cron job runs every 10 minutes on the 10 minutes, so 9:00, 9:10, 9:20, 9:30 and so on.
In my rails app, I want to show when the cron will next run.
So I will have, "Cron will next run at: 9:20PM"
I just can't figure out how to get this in ruby.
Thanks,
Andrew
def next_10_minutes
nxt = Time.now+(10-Time.now.min%10).minute
nxt.strftime("%H:%M")
end
next_10_minutes
#=> "00:30"
or little more flexible and monkey patching
class Time
def self.next_10_minutes
self.now+(10-Time.now.min%10).minute
end
end
Time.next_10_minutes.strftime("%H:%M")
#=> "00:40"
This is quite simple:
get the current minutes: min = DateTime.now.min
round to the upper ten minute:
nextTick = ((min/10.0).ceil*10)
print the difference:
diff = nextTick - min
hour = DateTime.now.hour
if nextTick == 60
nextTick = 0
hour = (hour + 1) % 24
end
print "Next run in #{diff} minutes (at #{hour}:#{nextTick})"
Try it here: http://codepad.org/5vxlg6kF
def next_tick(now)
min10 = ((now.min / 10) + 1)*10
if min10 == 60
Time.mktime(now.year, now.month, now.day, now.hour + 1, 0)
else
Time.mktime(now.year, now.month, now.day, now.hour, min10)
end
end
p next_tick(Time.now)

Resources