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.
Related
I have a Rails 3 application, how would one avoid an overlap happening due to daylight saving?
My problem is that I am having a form that generate reports. Auditing an inconsistency I noticed that a bunch of transactions show up in the week ending in March 11th also show up in the Week starting on March 12th.
The problem boils down to some thing like this...
Time.zone.parse('2018-03-11').to_datetime.end_of_day.utc
=> Mon, 12 Mar 2018 07:59:59 +0000
Time.zone.parse('2018-03-12').to_datetime.beginning_of_day.utc
=> Mon, 12 Mar 2018 07:00:00 +0000
The 1 hour overlap above seem to be where my problem lies. When checking date ranges (see actual code below) how can I avoid this overlap.
Actual Code
Here is the actual code that resemble filtering by date.
scope :filter_date, lambda { |starts, ends, date, transaction_type = :transaction|
_scope = scoped
starts = Time.zone.parse(starts).to_datetime if starts.class == String and starts.present?
ends = Time.zone.parse(ends).to_datetime.tomorrow if ends.class == String and ends.present?
begin
case date
when 'settled'
transaction_type == "batch" ? date_field = 'deposited_at' : date_field = 'settled_at'
_scope = _scope.order('transactions.'+date_field+' DESC')
_scope = _scope.where("transactions."+date_field+" >= ?", starts) if starts.present?
_scope = _scope.where("transactions."+date_field+" < ?", ends) if ends.present?
else # created, nil, other
_scope = _scope.order('transactions.created_at DESC')
_scope = _scope.where("transactions.created_at >= ?", starts) if starts.present?
_scope = _scope.where("transactions.created_at < ?", ends) if ends.present?
end
end
_scope
}
Stack
Ruby 2.1
Rails 3.2
PG
Question
How can I overcome this overlap of time where the daylight saving takes effect.
Try this solution. I think it should solve your issue:
scope :filter_date, lambda { |starts, ends, date, transaction_type = :transaction|
_scope = scoped
if starts.class == String and starts.present?
starts = Time.zone.parse(starts)
starts += 1.hour if starts.dst?
starts = starts.to_datetime
end
if ends.class == String and ends.present?
ends = Time.zone.parse(ends) + 1.day
ends += 1.hour if ends.dst?
ends = ends.to_datetime
end
begin
case date
when 'settled'
transaction_type == "batch" ? date_field = 'deposited_at' : date_field = 'settled_at'
_scope = _scope.order('transactions.'+date_field+' DESC')
_scope = _scope.where("transactions."+date_field+" >= ?", starts) if starts.present?
_scope = _scope.where("transactions."+date_field+" < ?", ends) if ends.present?
else # created, nil, other
_scope = _scope.order('transactions.created_at DESC')
_scope = _scope.where("transactions.created_at >= ?", starts) if starts.present?
_scope = _scope.where("transactions.created_at < ?", ends) if ends.present?
end
end
_scope
}
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!
I've got some issues with writing my model scope. I want to filter my model objects based on a month in which they are published, i.e model BlogPost :
scope :published_in_month, ->(date) { where(published_date: date.at_beginning_of_month..date.at_end_of_month) }
So this is my controller method :
def list_by_month
#date = Time.parse("#{params[:year]}/#{params[:month]}")
puts "DATE IS #{#date}"
#posts = BlogPost.published_in_month(#date).page(params[:page]).per(10)
render :index
end
So date printed out I see is :
DATE IS 2013-12-01 00:00:00 +0100
But in my log and it the page I see post(s) from wrong month, this is a entry log :
SELECT "blog_posts".* FROM "blog_posts" WHERE ("blog_posts"."published_date" BETWEEN '2013-11-30 23:00:00.000000' AND '2013-12-31 22:59:59.999999') LIMIT 10 OFFSET 0
Where is this 2013-11-30 coming from when my input date is 2013-12-01, and how can I rewrite my scope if I made mistake with it to produce incorrect query
This could help:
scope :from_month, -> { where(created_at: DateTime.now.beginning_of_month..DateTime.now.end_of_month) }
Perhaps it is better to use SQL methods to determine the month of each record:
scope :published_in_month, ->(date) { where("MONTH(created_at) = ?", date.month) }
Postgresql version:
scope :published_in_month, ->(date) { where("DATE_PART('month', timestamp created_at) = ?", date.month) }
(not tested)
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 two models
Customer and Transaction
Customer
has_many :transactions
Transaction
belongs_to :customer
Now I need all the customers having
more than 1 transaction in last 30 days
exactly 1 transaction in last 30 days
Expanding on ScottJShea's answer, I'd use some scopes
scope :one_recent_transaction, :conditions => lambda {
includes(:transactions).where("transactions.date > ?", DateTime.now - 30.days).group("customer.id").having("COUNT(transactions.id) = 1")
}
scope :many_recent_transactions, :conditions => lambda {
includes(:transactions).where("transactions.date > ?", DateTime.now - 30.days).group("customer.id").having("COUNT(transactions.id) > 1")
}
Then use them like this
one_transaction = Customer.one_recent_transaction
many_transactions = Customer.many_recent_transactions
You want to use the HAVING clause. I suggest (I am guessing a bit not knowing your model exactly):
#exactly_one = Customer.where("transaction_date between ? and >", Date.now, Date.now - 30).group("customer.id").having("count(transaction.id) = 1")
#exactly_one = Customer.where("transaction_date between ? and >", Date.now, Date.now - 30).group("customer.id").having("count(transaction.id) > 1")