Past the hours of head-banging for this one.
I am attempting to separate the query model from PG for the model start_time (stored in UTC) by day group.
Morning (12a - 12p)
Afternoon (12p - 5p)
Evening (5p - 12a)
I've tried scope methods, queries by instance methods, and overall class methods. All of which return the day group by UTC (not the local time zone for the scheduled event)
# event.rb
def self.morning
startday = 0
midday = 12
Event.where("extract(hour from start_time) >= ? AND extract(hour from start_time) < ?", startday, midday)
end
Also tried,
def self.afternoon
midday = "12:00:00"
eveday = "17:00:00"
Event.where("start_time::time >= ? AND start_time::time < ?", midday, eveday)
end
When console prompting (and generally throughout the app) I call event.start_time is successfully returned in the local time zone (set in the application.rb file)
But unless called outside of the model, the start_time continues to query as UTC.
I do not want to preset the DB timezone (as this is bad practice and the app is used globally)
Edit
As an example in the view, I am calling
<% events.morning.order("start_time ASC").each do |fit_class| %>
...
where,
events = #events = Event.all # passed through a partial
Just add scopes for Event model with time zone checks
class Event < ApplicationRecord
scope :morning, -> { where(
"start_time >= ? AND start_time < ?",
formatted_time(0), formatted_time(12))
}
scope :afternoon, -> { where(
"start_time >= ? AND start_time < ?",
formatted_time(12), formatted_time(17))
}
scope :evening, -> { where(
"start_time >= ? AND start_time < ?",
formatted_time(17), formatted_time(24))
}
private
def formatted_time(hour = 0)
Time.zone.parse(Date.current.to_s).change(hour: hour)
end
end
You don't have to modify any other time zone settings for this query. Hope it helps!
For onlookers, my temporary solution is as follows (MUCH research into the PostgreSQL docs):
def self.morning
startday = 0
midday = 12
Event.where("extract(hour from start_time - interval '6 hours') >= ? AND extract(hour from start_time - interval '6 hours') < ?", startday, midday)
end
def self.afternoon
midday = 12
eveday = 17
Event.where("extract(hour from start_time - interval '6 hours') >= ? AND extract(hour from start_time - interval '6 hours') < ?", midday, eveday)
end
def self.evening
eveday = 17
endday = 24
Event.where("extract(hour from start_time - interval '6 hours') >= ? AND extract(hour from start_time - interval '6 hours') < ?", eveday, endday)
end
This required me to preset the application time zone.
# application.rb
config.time_zone = "Central Time (US & Canada)"
I will change post the full-solution update when discovered!
Related
I am making a payments arrangement, for an order, where a client can choose the day of the month to pay. And can also change it afterwards. For this I used the advance method for date, and it works correctly when creating, but fails when updating. Here is my code:
Creating, if day is 31, it properly changes to last day of month if the month doesn't have a day 31.
months = params[:payment][:months].to_i
pay_day = params[:payment][:pay_day].to_i
today = Date.today
first_date = today.change(months: 1).change(day: pay_day)
months_between = (today.year * 12 + today.month) - (first_date.year * 12 + first_date.month)
quota = (invoice_total)/months
payment_number = 1
months.times do |i|
payment = Payment.new(invoice: current_invoice, payment_type: PaymentType.find(params[:payment][:payment_type_id]), payment_number: payment_number, value: quota, max_payment_date: first_date.advance(months: +(months_between+i+1)))
payment.save
payment_number = payment_number + 1
end
Update code, it fails in, for example, February with "invalid date", but updates january correctly.
pay_day = params[:payment][:pay_day].to_i
#invoice.payments.each do |payment|
if payment.actual_payment_date.nil?
reference_month = payment.max_payment_date.change(months: 1).change(day: pay_day)
months_between = (payment.max_payment_date.year * 12 + payment.max_payment_date.month) - (reference_month.year * 12 + reference_month.month)
payment.update_attributes(max_payment_date: reference_month.advance(months: +(months_between)))
payment.save
end
end
It is basically the same code, does anyone know why in the first part it works correctly, but not the second part?
Thanks
ActiveSupport to the rescue:
def correct_day(day, date = Date.today)
end_of_month = date.end_of_month.day
day < end_of_month ? day : end_of_month
end
Lets test it with [timecop:
Timecop.freeze(Date.new(2000, 02)) do
correct_day(32) # => 29 - 2000 was a leap year.
correct_day(15) # => 15
end
Timecop.freeze(Date.new(2015, 10, 11)) do
correct_day(32) # => 31
correct_day(15) # => 15
end
This is how you would implement it in a model:
class Invoice < ActiveRecord::Base
has_many :payments
# Creates a payment for each month
# #param [Integer] months
# #param [Ingeger] preferred_day - defaults to the last day of the month
def split_payments(months = 12, preferred_day = 31)
(1..months).map do |i|
payments.create(
pay_day: calculate_payment_date(created_at + i.months, preferred_day),
payment_number: i
# ... More attributes
)
end
end
def change_payment_date(preferred_day)
payments.sort_by(:payment_number).map do |payment|
date = created_at + payment.payment_number.months
payment.update_attributes(
pay_day: calculate_payment_date(date, preferred_day)
)
end
end
private
def calculate_payment_date(date, preferred_day)
end_of_month = date.end_of_month
pref = date.beginning_of_month + (preferred_day - 1).days
pref < end_of_month ? pref : end_of_month
end
end
I made a scope help me to select objects
scope :best_of_the_month, ->(year, month) do
time = Time.new(year, month)
start_time = time.beginning_of_month
end_time = time.end_of_month
where("created_at > ? AND created_at < ?", start_time, end_time).where("likes > ?", 15).where("rating > ?", 4.85).order_by_rating.to_a.uniq(&:author)
end
Then, I want to loop through this method, from 2014/1 to now. How can I do it?
Maybe something like this:
start_date = Date.create(2014,1).month
end_date = Date.today.month
#monthly_videos = []
(start_date..end_date).each do |year, month|
videos = Video.best_of_the_month(year, month)
#monthly_videos << videos
end
I find a solution here, How to Loop through Months in Ruby on Rails. But it seems about looping through the days. Not the month
With the best_of_the_month scope defined to take month and year as params, the following code should work:
date = Date.new(2014,1,1)
#monthly_videos = []
while true
videos = Video.best_of_the_month(date.year, date.month)
#monthly_videos << videos
date += 1.month
break if date == Date.today.beginning_of_month
end
You could use the Date#next_month method
date = Date.new(2014,1,1)
final_date = Date.new(Date.today.year, Date.today.month)
#monthly_video = []
loop do
#monthly_videos << Video.best_of_the_month(date.year, date.month)
break if date == final_date
date = date.next_month
end
I've got a ScheduleTime ActiveRecord object, and time is of column_type :time.
I'm attempting to retrieve objects that match the current hour and minute
ScheduleTime.all.select do |s|
now = Time.now
s.time.hour == now.hour && s.time.min == now.min
end
Can this be done with SQL in a where clause?
EDIT:
I've gotten closer, i can query by the hour:
ScheduleTime.where("date_part('hour', time) = ?", Time.now.hour)
EDIT#2:
This is my current implementation...
class ScheduleTime < ActiveRecord::Base
belongs_to :schedule
validates_presence_of :schedule_id, :time
scope :by_hour, ->(hour) { where("date_part('hour', time) = ?", hour) }
scope :by_minute, ->(minute) {where("date_part('minute', time) = ?", minute) }
scope :by_time, ->(time) { by_hour(time.hour).by_minute(time.min) }
scope :now, -> { by_time(Time.now) }
end
The final version looks fine. Just make sure to use Time.zone.now instead of Time.now as the former doesn't take your application's configured time zone into account.
I'm currently working on an Appointment system and building it with Ruby on Rails. I have an Appointment model and appointments controller where on the index, I want to show a list of appointments for that day, separated by 30 minute chunks.
I have a basic working version and I've got a ruby method that adds a class on the table row which shows the if the current 30 minute chunk is the current time or not.
The issue is, it sets the row class as "current_time" when the time is anywhere between the start and end of the hour which isn't what I want.
def date_class(time)
now = DateTime.now.utc
if (now.beginning_of_hour..(now.end_of_hour - 0.5.hours)).cover?(time)
"current_time"
elsif ((now.beginning_of_hour + 0.5.hours)..now.end_of_hour).cover?(time)
"current_time"
elsif (now.beginning_of_day..now.end_of_hour).cover?(time)
"past"
else
"future"
end
end
Any ideas?
The screenshot below and shows that the code works fine and shows true or false correctly.
http://s.deanpcmad.com/2014/uifGf.png
Although it has only been tested with instances of class Time for now, the time_frame gem could be an alternative solution for this kind of problem:
require 'time_frame'
def date_class(time)
now = Time.now.utc
frame = TimeFrame.new(min: now.beginning_of_hour, duration: 29.minutes + 59.seconds)
frame = frame.shift_by(30.minutes) if now.min >= 30
return 'past' if frame.deviation_of(time) < 0.minutes
return 'current_time' if frame.cover?(time)
'future'
end
# Demo: Building 30.minutes interval blocks and print out the date class used by each block:
frame = TimeFrame.new(
min: (Time.now.utc - 2.hours).beginning_of_hour,
max: (Time.now.utc + 2.hours).beginning_of_hour
)
frame.split_by_interval(30.minutes).each do |interval|
puts "#{interval.min} -> #{date_class(interval.min)}"
end
Wouldn't this work for you?
def date_class(time)
now = DateTime.now.utc
return "past" if time < now.beginning_of_hour
return "current_time" if now.hour == time.hour && now.min < 30 && time.min < 30
return "current_time" if now.hour == time.hour && now.min >= 30 && time.min >= 30
return "future"
end
I am sure there is a better way, but this would also work I think
I have Shift model.
--- !ruby/object:Shift
attributes:
id:
starts_at:
ends_at:
I want to add singelton method to create shifts for each day in given quarter.
class Shift
def self.open_quarter(number, year)
starts_at = "08:00"
ends_at = "08:00"
...
end
end
How to implement that in best way? I want that each shifts starts_at 8.00 am and finish 8.00 am on next day.
def self.open_quarter(number, year)
start_time = Time.new(year, number*3 - 2, 1, 8)
while start_time.month <= number*3 && start_time.year == year
Shift.create{starts_at: start_time, ends_at: start_time += 24.hours}
end
end
make sure to set the correct timezone when using Time.new. Default is current timezone (see docs). You can also use Time.utc.
def self.open_quarter(number, year)
starts_at = "08:00 am"
ends_at = "08:00 pm"
quarter_start = Date.new(year, (number * 3)).beginning_of_quarter
quarter_end = Date.new(year, (number * 3)).end_of_quarter
(quarter_end - quarter_start).to_i.times do |n|
start_shift = "#{(quarter_start + n).to_s} #{starts_at}".to_datetime
end_shift = "#{(quarter_start + n).to_s} #{ends_at}".to_datetime
Shift.create(starts_at: start_shift, ends_at: end_shift)
end
end