how to compare current time in informix stored proc ?
lets sat I hv a p_time param I want to compare this with a current time
like if p_time < current_time then
--do somehting
anyone?
thanks
Comparing dates only:
if (v_some_date < today) then
--...
end if
Comparing dates with time:
if (v_some_dtime > current) then
--...
end if
Related
I have a series of Appointments where the date and time is stored under start_date as DateTime. I'd like to categorize them as starting in the Morning, Daytime, or Evening. I created an array of hashes with labels and ranges I used for a SQL statement where I convert the start_date records into seconds using CAST(EXTRACT (EPOCH FROM start_time) AS INT)
TIME_RANGES = [
{label: "Morning", min: 0, max: 32399},
{label: "Daytime", min: 32400, max: 61199},
{label: "Evening", min: 61200, max: 86399}
]
cases = TIME_RANGES.map do |r|
"when CAST (EXTRACT (EPOCH FROM start_date) AS INT) % 86400 between '#{r[:min]}' and '#{r[:max]}' then '#{r[:label]}'"
end
time_ranges = Appointment.select("count(*) as n, case #{time_cases.join(' ')} end as time_range").group('time_range')
This takes the number of seconds in a day (86400) and labels the appointments based on the modulo of the start_date with 86400. However, a number of appointments take place in different timezones, but they're all stored as UTC. So an appointment at 08:00 AM EST is equivalent to one at 07:00 AM CST, but both are stored internally as 12:00 PM UTC. This would cause the appointments to be incorrectly labelled as "Daytime" when they're intended to be "Morning" (from the perspective of the User booking it).
Ideally, I would like some way to convert the start_date based on the User's timezone to make it look like it occurred in UTC. So I would want a 12:00 PM EST appointment to be labelled as if it were a 12:00 PM UTC appointment instead of 04:00 PM UTC. More specifically, I would like to subtract 14400 seconds from the converted start_date before performing the modulo.
I can join Appoinments to Users, which contains the User's timezone. How can I incorporate this information into my query above, so that a modified start_date is used for each record, depending on the User's timezone in that same record?
I know I could accomplish this with a loop of each timezone and adding/substracting a specific amount of seconds in each loop, then combining the results of all the loops, but I was wondering if there was a way to do it in one query.
Per my comment, I am assuming we have three tables: appointments, users, and preferences. In appointments we have start_date and user_id. In users we have preference_id. In preferences we have some column that names the time zone, so I'll call that tz_name.
Note: Postgres timezone functions are messy. I would highly recommend you read up on them. This excellent article is a good place to start.
It is possible to use pure SQL to generate the time ranges and return a grouped result. A pure SQL solution would be best if you need to label and group many records (thousands or more) at a time.
Assuming you are working with 1000 records or fewer at a time, you'll probably want to use Rails scopes, as this will give you an ActiveRecord result. Then you'll do your grouping using Ruby's Array methods.
That solution would look something like this:
# app/user.rb
class User < ApplicationRecord
belongs_to :preference
has_many :appointments
end
# app/appointment.rb
class Appointment < ApplicationRecord
belongs_to :user
scope :with_seconds, lambda {
joins(user: :preference)
.select('appointments.*, extract(epoch from timezone(tz_name, start_date::timestamptz)::time)::int as seconds')
}
# This method is optional. If it is excluded, calling #seconds
# on an appointment instance will raise an error if the
# .with_seconds scope has not been applied.
def seconds
attributes['seconds']
end
def time_range
return nil unless seconds.present?
if seconds.between?(0, 32_399)
'Morning'
elsif seconds.between?(32_400, 61_199)
'Daytime'
else
'Evening'
end
end
end
The select portion of the scope probably deserves some explanation.
start_date::timestamptz: Take the start_date, which is stored as a Postgres timestamp, and convert it into a Postgres timestamp with time zone in the time zone of the Postgres server (presumably UTC).
timezone(tz_name, start_date::timestamptz): Convert the timestamp with time zone back into a timestamp type in the local time of the tz_name time zone.
timezone(tz_name, start_date::timestamptz)::time: Drop the date and keep the time component.
Then we extract epoch from that time component, which converts it into seconds.
Finally we convert the result to an integer to avoid anything falling through the cracks when we determine the time range.
Now you can do:
Appointment.all.with_seconds.group_by(&:time_range)
or
user = User.first
user.appointments.with_seconds.group_by(&:time_range)
For a pure SQL solution that will return ids grouped under the three time ranges, add this method to your Appointment model:
def self.grouped_by_time_range
current_scope = with_seconds.to_sql
query = <<~SQL
with converted_seconds as (#{current_scope})
select array_agg(id) as ids, case when seconds < 32400 then 'Morning'
when seconds < 61200 then 'Daytime'
else 'Evening' end as time_range
from converted_seconds
group by time_range
SQL
result = ActiveRecord::Base.connection.execute(query.squish)
result.to_a
end
If you don't need strictly SQL based solution you might use Ruby's select method to extract this appointments as in this example:
(I am assuming there is some kind of tz_name field in appointment model which holds timezone name)
morning_appointments = Appointment.all.select do |a|
a.start_date.in_time_zone(a.tz_name).hour > 6 && a.start_date.in_time_zone(a.tz_name).hour < 20
end
Edit:
Thanks #moveson for pointing out my mistake, I changed solution a bit.
I have a model, Product, which has both a :created_at timestamp and an :expiration_in_days attribute. Products are considered expired a certain number of days after their creation. How do I write a query that only returns products that have not expired?
Rails 4, Ruby 2.1, PG 0.17
I have been trying queries like this, with no success:
#product.rb
def self.not_expired
where('created_at + expiration_in_days * 86400 >= ?', Time.now)
end
I have a pure Ruby version that works fine, it's just slower:
#product.rb
def self.not_expired
select{ |p| (p.created_at.to_i + p.expiration_in_days * 86400) >= Time.now.to_i}
end
note, the 86400 calculation converts the the :expiration_in_days integer into seconds
Any pointers on more advanced Rails queries than the documentation (http://guides.rubyonrails.org/active_record_querying.html) would be very welcome as well.
Thanks in advance.
Try this:
def self.not_expired
where("created_at + (expiration_in_days * 86400)::text::interval >= ?", Time.now)
end
UPDATE: Add references
You can learn more about date and time function here.
Given your case has a special requirement, which is the value of expiration_in_days is a column in the table, we cannot use created_at + interval expiration_in_days day. Instead, we need to type cast its value to interval. But you can't type cast straight to an integer, that's why we cast it to text first.
A + B > C is true if A <= C - B
So, instead of trying to add the expiration time to created_at, subtract the expiration time from Time.now.
def expiration_threshold
Time.now - amount_of_time # I'm not sure what type to use
end
def self.not_expired
where( "created_at > ? ", expiration_threshold )
end
I've always been a little stumped about what type Rails/Ruby will want when dealing with various dates/times, you may have to play around.
I'm not sure if this would work but try something like this
def self.not_expired
where("TIMESTAMP created_at + INTERVAL '1 day' * expiration_in_days >= NOW()")
end
Might be a somewhat specific situation. And I kinda know how to do it in PHP/MySQL. But I was wondering if there was a faster way to do the following scenario:
A user has activities with a start- and end_date. (The activity starts at 12-10-2013 12:00:00 and ends at 12-10-2013 12:15:00 for example.)
Whenever the user creates a new activity. I want to check all the activities the user is part of(user has_many: activities) and see if none of the dates intersect with the date given for the new activity.
Since I'm pretty new to Rails I really don't know where to start searching for date comparisons and all...
Thanks in advance
An overlap is defined as another activity for which the end date is greater than or equal to the new activity's start date, and for which the start date is less than or equal to the new activity's end date.
Since you only want to detect whether such a record already exists, an appropriate test would be:
if Activity.where("starts_at <= ?" , new_activity_end_date ).
where("ends_at >= ?" , new_activity_start_date).
exists?
You can use regular comparators for date/times in Ruby (ie: >, < and ==).
Something like the following should do what you are looking for:
if current_user.activities.where("(starts_at <= ? AND ends_at >= ?) OR (starts_at >= ? AND starts_at <= ?)", start_datetime, start_datetime, start_datetime, end_datetime).count
# There exist activities that fall between start_datetime and end_datetime
else
# There exist no such activities
end
(starts_at <= start_datetime AND ends_at >= start_datetime) checks whether an event starts before and ends after start_datetime.
(starts_at >= start_datetime AND starts_at <= end_datetime) checks whether an event starts between start_datetime and end_datetime.
We must check that all this three kinds of intersection do not happen:
our activity must not happen within another activity
our activity must not start before another ends
our activity must not ends after another starts
We can do that using something like this:
class Activity < ActiveRecord::Base
validate :must_not_be_intersected
def intersected?
params = {
:start => start_date,
:end => end_date
}
Activity::where('start_date <= :start AND end_date >= :end', params)
.where('(start_date >= :start AND start_date <= :end) OR (end_date >= :start AND end_date <= :end)', params)
.exists?
end
private
def must_not_be_intersected
errors.add :base, 'Other task running on the same period' if intersected?
end
end
my Project model has 2 datetime atttributes: start_date and end_date.
Now I want all projects where the current time is in between these dates.
I tried something like this with the start_date to start with:
#projects = Project.where(:start_date <= Time.now)
But this returns an error:
comparison of Symbol with Time failed
Any ideas? Thanks!
Unlike some ORMs, active record doesn't augment the symbol class with methods to allow expressions other than equality to be expressed in this way. You just have to do
Project.where('start_date <= ?', Time.now)
The squeal gem adds this sort of stuff and allows you to write
Project.where{start_date < Time.now}
You can't do this: :start_date <= Time.now. You're comparing a symbol and a date with the <= operator.
If you want to add a condition to your query, pass it as a string:
Project.where("start_date <= ?", Time.now);
Unfortunately, with a where clause comparing dates, you'll have to drop into SQL. Try something like this instead:
#projects = Project.where(['projects.start_date <= ?', Time.now])
I'm using Ruby on Rails 3 and I have a "visit" model which stores a check_in and check_out datetime and I need to search through visits in a general date range and count the number of "visitors present" grouped by all hours of the day.
...i.e. I need something like:
8:00am - 8:59am : 12 visitors
9:00am - 9:59am : 5 visitors
10:00am - 10:59am : 4 visitors
...given a table of visits with a check in and check out time stored.
The idea is to take check-in and check-out times for "visits" and then determine how many visitors (assuming each visit logs one visitor, which it does by policy) were visiting during any given hour of the day in order to find out peak visiting times.
I've tried setting up queries like:
eight_am_visits = Visit.where("EXTRACT(HOUR_MINUTE FROM check_in) <= 859").where("EXTRACT(HOUR_MINUTE FROM check_out) >= 800")
...and haven't quite hit on it because Rails stores dates in such an odd fashion (in UTC, which it will convert on database query) and it doesn't seem to be doing that conversion when I use something like EXTRACT in SQL...
...any idea how I can do this?
Looks like you're not actually interested in the Visit objects at all. If you just want a simple summary then push AR out of the way and let the database do the work:
# In visit.rb
def self.check_in_summary(date)
connection.select_rows(%Q{
select extract(hour from check_in), count(*)
from visits
where cast(check_in as date) = '#{date.iso8601}'
group by extract(hour from check_in)
}).inject([ ]) do |a, r|
a << { :hour => r[0].to_i, :n => r[1].to_i }
end
end
Then a = Visit.check_in_summary(Date.today - 1) will give you the summary for yesterday without doing any extra work. That demo implementation will, of course, have holes in the array for hours without any checkins but that is easy to resolve (if desired):
def self.check_in_summary(date)
connection.select_rows(%Q{
select extract(hour from check_in), count(*)
from visits
where cast(check_in as date) = '#{date.iso8601}'
group by extract(hour from check_in)
}).each_with_object([0]*24) do |r, a| # Don't forget the arg order change!
a[r[0].to_i] = r[1].to_i
end
end
That version returns an array with 24 elements (one for each zero-based hour) whose values are the number of checkins within that hour.
Don't be afraid to drop down to SQL when it is convenient, AREL is just one tool and you should have more than one tool in your toolbox. Also, don't be afraid to add extra data mangling and summarizing methods to your models, your models should have an interface that allows you to clearly express your intent in the rest of your code.
Maybe something like that?!
t = Time.now
eight_am_visits = Visit.all(:conditions => ['check_in > ? and check_in < ?', Time.utc(t.year, t.month, t.day, 8), Time.utc(t.year, t.month, t.day, 8, 59)])
EDIT:
Or you can grab all visits by day and filter it in Rails:
t = Time.now
visits = Visit.all(:conditions => ['created_at > ? and created_at < ?', Time.utc(t.year, t.month, t.day - 1), Time.utc(t.year, t.month, t.day + 1)])
visits_by_hour = []
(0..23).each do |h|
visits_by_hour << visits.map {|e| e if e.created_at > Time.utc(t.year, t.month, t.day, h) && e.created_at < Time.utc(t.year, t.month, t.day, h, 59)}.count
end
And in view:
<% visits_by_hour.each_with_index do |h, v| %>
<%= "#{h}:00 - #{h}:59: #{v} visitors" %>
<% end %>
Thanks for your help Olexandr and mu, I managed to figure something out with the insight you gave me here.
I came up with this, and it seems to work:
#grab the data here, this is nice because
#I can get other stats out of it (which I don't show here)
#visits = Visit.where(:check_in => #start_date..#end_date, :check_out => #start_date..#end_date).where("check_out IS NOT NULL");
#Here we go
#visitors_present_by_hour = {}
(0..23).each do |h|
# o.o Ooooooh.... o_o Hee-hee! ^_^
#visitors_present_by_hour[h] = #visits.collect{|v| v.id if v.check_in.hour <= h and v.check_out.hour >= h}.compact.count
end
Then I can just dump out that hash in my view.
It seems the solution was a bit simpler than I thought, and doing it this way actually makes rails do the time conversions from UTC.
So, I could just collect all the visits which have hours in the hour range, then compact out the nils and count what's left. I was surprised once I hit on it. I didn't need any custom SQL at all as I thought I would (unless this is completely wrong, but it seems to be working with some test data).
Thanks guys!