I have two time columns stored in a Postgresql database: open_time and close_time. I'm trying to find out if the current time, ignoring the date, is between the two times, ignoring the dates.
This code compares the dates as well as the time:
current_time = Time.now
if current_time.between?(store.open_time, store.close_time)
puts "IN BETWEEN"
end
It doesn't work, for example, when current_time # => 2018-06-06 23:59:49 -0600 and open_time # => 2000-01-01 22:59:00 UTC.
How do I get it to not include the dates, and just compare the times?
require 'time'
TIME_FMT = "%H%M%S"
def store_open_now?(open_time, close_time)
nt = Time.now.strftime(TIME_FMT)
ot = open_time.strftime(TIME_FMT)
ct = close_time.strftime(TIME_FMT)
ot <= ct ? (nt >= ot && nt <= ct) : (nt >= ot || nt <= ct)
end
As I write, the time is now about 32 minutes past midnight.
Time.now.strftime(TIME_FMT)
#=> "003252"
Suppose
open_time = DateTime.parse("09:00")
#=> #<DateTime: 2018-06-07T09:00:00+00:00 ((2458277j,32400s,0n),
# +0s,2299161j)>
close_time = DateTime.parse("17:00")
#=> #<DateTime: 2018-06-07T17:00:00+00:00 ((2458277j,61200s,0n),
# +0s,2299161j)>
Then
open_time.strftime(TIME_FMT)
#=> "090000"
close_time.strftime(TIME_FMT)
#=> "170000"
store_open_now?(open_time, close_time)
#=> false
Now suppose the open time is the same, but the close time is later.
close_time = DateTime.parse("01:00")
#=> #<DateTime: 2018-06-07T01:00:00+00:00 ((2458277j,3600s,0n),
# +0s,2299161j)>
Then
close_time.strftime(TIME_FMT)
#=> "010000"
store_open_now?(open_time, close_time)
#=> true
Perhaps you want something like this:
current_time = Time.now
open_time = store.open_time
close_time = store.close_time
current_time -= current_time.beginning_of_day
open_time -= open_time.beginning_of_day
close_time -= close_time.beginning_of_day
if current_time.between?(open_time, close_time)
puts "IN BETWEEN"
end
or
current_time = Time.now
open_time = store.open_time
close_time = store.close_time
current_time = [current_time.hour, current_time.min, current_time.sec]
open_time = [open_time.hour, open_time.min, open_time.sec]
close_time = [close_time.hour, close_time.min, close_time.sec]
if open_time <=> current_time == -1 and current_time <=> close_time == -1
puts "IN BETWEEN"
end
You could CAST() your datetime to time by using,
cast(tbl_store.open_time as time) as SomeVariable
cast(tbl_store.close_time as time) as SomeOtherVariable
That would give you the time only instead of the full datetime value that you had to begin with, which is what you wanted.
You can then use the same logic with your curtime() between to the get value that you were looking for.
Example:
SELECT
CAST(tbl_store.open_time as TIME) as open_time,
CAST(tbl_store.close_time as TIME) as close_time,
CURTIME() BETWEEN (cast(tbl_store.open_time as TIME)) AND (cast(tbl_store.close_time as TIME)) as time_between
FROM
tbl_store
Working SQL Fiddle
You can change the schema build in the fiddle to test the datetime values you desire.
Note that if you ever have a logic that will include midnight time, you will have to make a CASE WHEN logic against that, else it will fail and return 0, whereas it should return 1.
You can take advantage of ranges and how numeric strings are compared
r = Range.new('09:00', '18:00')
r.include?('08:59') # => false
r.include?('09:01') # => true
r.include?('18:01') # => false
Then we could use
open_hours_range = Range.new(open_time.strftime('%R'), close_time.strftime('%R'))
shop_open? = open_hours_range.include?(Time.now.strftime('%R'))
Related
I have an array of dates. I need to sort those dates by their closeness to some test_date. The dates in the array can be before or after that test_date.
My simple ruby program isn't getting it right. My strategy is to convert each Date to a unix timestamp. Then I do a custom sort by the absolute value of the difference between the dates and the test_date.
Here is that program:
require 'date'
test_date = DateTime.new(2017,2,3,4,5,6)
date1 = Date.new(2017,1,6)
date2 = Date.new(2017,1,5)
date3 = Date.new(2017,2,5)
date4 = Date.new(2017,2,6)
date5 = Date.new(2017,2,9)
date6 = Date.new(2017,2,1)
date7 = Date.new(2017,2,2)
dates_ary = date1, date2, date3, date4, date5, date6, date7
sorted_dates = dates_ary.sort do |d1, d2|
if( (d1.to_time.to_i - test_date.to_time.to_i).abs <= (d2.to_time.to_i - test_date.to_time.to_i).abs)
d1 <=> d2
else
d2 <=> d1
end
end
puts "expected order: "
puts "2017-02-02"
# pointed out in comments that 2017-02-05 will be closer than 2017-02-01 due to test_date having present hours, minutes, and seconds.
puts "2017-02-05"
puts "2017-02-01"
puts "2017-02-06"
puts "2017-02-09"
puts "2017-01-06"
puts "2017-01-05"
puts ''
puts 'actual order:'
sorted_dates.each {|d| puts d}
Also: if there is some existing ruby or rails method that sorts dates by their closeness to another date, let me know. Thanks!
The if is redundant. Ruby will handle it:
sorted_dates = dates.sort do |d1, d2|
(d1.to_time.to_i - test_date.to_time.to_i).abs <=> (d2.to_time.to_i - test_date.to_time.to_i).abs
end
Better:
sorted_dates = dates.sort_by do |date|
(date.to_time.to_i - test_date.to_time.to_i).abs
end
Better:
sorted_dates = dates.sort_by { |date| (date - test_date).abs }
I use Elasticsearch where I have one index per day, and I want my Ruby on Rails application to query documents in a given period by specifying the smallest and most precise list of indices.
I can't find the code to get that list of indices. Let me explain it:
Consider a date formatted in YYYY-MM-DD.
You can use the joker * at the end of the date string. E.g. 2016-07-2* describes all the dates from 2016-07-20 to 2016-07-29.
Now, consider a period represented by a start date and an end date.
The code must return the smallest possible array of dates representing the period.
Let's use an example. For the following period:
start date: 2014-11-29
end date: 2016-10-13
The code must return an array containing the following strings:
2014-11-29
2014-11-30
2014-12-*
2015-*
2016-0*
2016-10-0*
2016-10-10
2016-10-11
2016-10-12
2016-10-13
It's better (but I'll still take a unoptimized code rather than nothing) if:
The code returns the most precise list of dates (i.e. doesn't return dates with a joker that describes a period starting before the start date, or ending after the end date)
The code returns the smallest list possible (i.e. ["2016-09-*"] is better than ["2016-09-0*", "2016-09-1*", "2016-09-2*", "2016-09-30"]
Any idea?
Okay, after more thinking and the help of a coworker, I may have a solution. Probably not totally optimized, but still...
def get_indices_from_period(start_date_str, end_date_str)
dates = {}
dates_strings = []
start_date = Date.parse(start_date_str)
end_date = Date.parse(end_date_str)
# Create a hash with, for each year and each month of the period: {:YYYY => {:MMMM => [DD1, DD2, DD3...]}}
(start_date..end_date).collect do |date|
year, month, day = date.year, date.month, date.day
dates[year] ||= {}
dates[year][month] ||= []
dates[year][month] << day
end
dates.each do |year, days_in_year|
start_of_year = Date.new(year, 1, 1)
max_number_of_days_in_year = (start_of_year.end_of_year - start_of_year).to_i + 1
number_of_days_in_year = days_in_year.collect{|month, days_in_month| days_in_month}.flatten.size
if max_number_of_days_in_year == number_of_days_in_year
# Return index formatted as YYYY-* if full year
dates_strings << "#{year}-*"
else
days_in_year.each do |month, days_in_month|
formatted_month = format('%02d', month)
if Time.days_in_month(month, year) == days_in_month.size
# Return index formatted as YYYY-MM-* if full month
dates_strings << "#{year}-#{formatted_month}-*"
else
decades_in_month = {}
days_in_month.each do |day|
decade = day / 10
decades_in_month[decade] ||= []
decades_in_month[decade] << day
end
decades_in_month.each do |decade, days_in_decade|
if (decade == 0 && days_in_decade.size == 9) ||
((decade == 1 || decade == 2) && days_in_decade.size == 10)
# Return index formatted as YYYY-MM-D* if full decade
dates_strings << "#{year}-#{formatted_month}-#{decade}*"
else
# Return index formatted as YYYY-MM-DD
dates_strings += days_in_decade.collect{|day| "#{year}-#{formatted_month}-#{format('%02d', day)}"}
end
end
end
end
end
end
return dates_strings
end
Test call:
get_indices_from_period('2014-11-29', '2016-10-13')
=> ["2014-11-29", "2014-11-30", "2014-12-*", "2015-*", "2016-01-*", "2016-02-*", "2016-03-*", "2016-04-*", "2016-05-*", "2016-06-*", "2016-07-*", "2016-08-*", "2016-09-*", "2016-10-0*", "2016-10-10", "2016-10-11", "2016-10-12", "2016-10-13"]
So, given a DateTime object, and a fixed time, I want to get the next occurrence of the fixed time after the given DateTime object.
For example, given the date of 14th March, 2016, 4:00pm, and the time of 5:15pm, I want to return 14th March, 2016 5:15pm.
However, given the date of 14th March, 2016, 6:00pm, and the time of 5:15pm, I want to return 15th March, 2016, 5:15pm, since that's the next occurrence.
So far, I've written this code:
# Given fixed_time and date_time
new_time = date_time
if fixed_time.utc.strftime("%H%M%S%N") >= date_time.utc.strftime("%H%M%S%N")
new_time = DateTime.new(
date_time.year,
date_time.month,
date_time.day,
fixed_time.hour,
fixed_time.min,
fixed_time.sec
)
else
next_day = date_time.beginning_of_day + 1.day
new_time = DateTime.new(
next_day.year,
next_day.month,
next_day.day,
fixed_time.hour,
fixed_time.min,
fixed_time.sec
)
end
# Return new_time
It works, but is there a better way?
I would construct the new date time just once and add 1 day if needed:
# Given fixed_time and date_time
new_date_time = DateTime.new(
date_time.year,
date_time.month,
date_time.day,
fixed_time.hour,
fixed_time.min,
fixed_time.sec
)
# add 1 day if new date prior to the given date
new_date_time += 1.day if new_date_time < date_time
Here's a little stab at refactoring it to remove some of the redundancy:
# Given fixed_time and date_time
base_date = date_time.to_date
if fixed_time.to_time.utc.strftime("%T%N") <= date_time.to_time.utc.strftime("%T%N")
base_date = base_date.next_day
end
new_time = DateTime.new(
base_date.year,
base_date.month,
base_date.day,
fixed_time.hour,
fixed_time.min,
fixed_time.sec
)
# Return new_time
The biggest changes are that the base_date is determined before the new_time is created, so that it can be used there.
I also used the next_day method on DateTime to get the next day, and used the "%T" format specifier as a shortcut for "%H:%M:%S"
Here's a little test program that to show that it works:
require "date"
def next_schedule(fixed_time, date_time)
# Given fixed_time and date_time
base_date = date_time.to_date
if fixed_time.to_time.utc.strftime("%T%N") <= date_time.to_time.utc.strftime("%T%N")
base_date = base_date.next_day
end
new_time = DateTime.new(
base_date.year,
base_date.month,
base_date.day,
fixed_time.hour,
fixed_time.min,
fixed_time.sec
)
# Return new_time
end
StartTime = DateTime.strptime("2016-02-14 17:15:00", "%F %T")
Dates = [
"2016-03-14 16:00:00",
"2016-03-14 18:00:00"
]
Dates.each do |current_date|
scheduled = next_schedule(StartTime, DateTime.strptime(current_date, "%F %T"))
puts "Scheduled: #{scheduled.strftime('%F %T')}"
end
The output of this is:
Scheduled: 2016-03-14 17:15:00
Scheduled: 2016-03-15 17:15:00
It's using the test cases described in the question, and it gets the expected answers.
There is the following times:
now = "2014-01-24T15:58:07.169+04:00",
start = "2000-01-01T10:00:00Z",
end = "2000-01-01T16:00:00Z"
I need to check if now is between start and end. I use the following code:
Range.new(start, end).cover?(now)
Unfortunately, this code returns false for my data. What am I doing wrong? How can I fix it? Thanks.
Well, I would use between? method. Because it's faster than cover? and include? variants. Here's an example:
yesterday = Date.yesterday
today = Date.today
tomorrow = Date.tomorrow
today.between?(yesterday, tomorrow) #=> true
Here's a gist with performance tests Include?, Cover? or Between?
Update
According to your recent comment, you want to compare 'only time' without date. If I get you correctly, there's a way to do it - strftime. But before that, to make comparison correctly, you need to convert all your datetimes to a single timezone (for example, using utc). Here's an example:
start_time_with_date = Time.parse('2000-01-01T16:00:00Z').utc
end_time_with_date = Time.parse('2014-01-24T15:58:07.169+04:00').utc
start_time = start_time_with_date.strftime('%I:%M:%S') #=> '04:00:00'
end_time = end_time_with_date.strftime('%I:%M:%S') #=> '11:58:07'
current_time = Time.now.utc.strftime('%I:%M:%S') #=> '01:45:27' (my current time)
current_time.between?(start_time, end_time) #=> false
And yes. Sadly, it's a string comparison.
You can use Range#cover? with time objects.
start = Time.parse('2000-01-01T10:00:00Z')
end_time = Time.parse('2000-01-01T16:00:00Z')
now = Time.parse('2014-01-24T15:58:07.169+04:00')
(start..end_time).cover?(now)
You're currently using strings, Ruby cannot know you're speaking about time.
I see the only variant, to define additional method to Range:
class Range
def time_cover? now
(b,e,n) = [ self.begin.utc.strftime( "%H%M%S%N" ),
self.end.utc.strftime( "%H%M%S%N" ),
now.utc.strftime( "%H%M%S%N" ) ]
if b < e
b <= n && e >= n
else
e <= n && b >= n
end
end
end
now = Time.parse "2014-01-24T15:58:07.169+04:00"
s = Time.parse "2000-01-01T10:00:00Z"
e = Time.parse "2000-01-01T16:00:00Z"
Range.new(s, e).time_cover?(now)
# => true
your date time(now) is not in between start and end time
Having pulled donations from the past two years, I'm trying to derive the sum of those donations per month, storing the keys (each month) and the values (the sum of donations for each month) in an array of hashes. I would like the keys to be numbers 1 to 24 (1 being two years ago and 24 being this month) and if there are no donations for a given month, the value would be zero for that month. How would I do this as an array of hashes in Ruby/Rails?
This is my variable with the donations already in it.
donations = Gift.where(:date => (Date.today - 2.years)..Date.today)
the following gives you a hash, with keys '2013/09" , etc...
monthly_donations = {}
date = Time.now
while date > 2.years.ago do
range = date.beginning_of_month..date.end_of_month
monthly_donations[ "{#date.year}/#{date.month}" ] = Giftl.sum(:column, :conditions => {created_at >= range})
date -= 30.days
end
To select the records in that time-span, this should be enough:
donations = Gift.where("date >= #{2.years.ago}")
you can also do this:
donations = Gift.where("date >= :start_date AND date <= :end_date",
{start_date: 2.years.ago, end_date: Time.now} )
See also: 2.2.1 "Placeholder Conditions"
http://guides.rubyonrails.org/active_record_querying.html
To sum-up a column in the database record, you can then do this:
sum = Gift.sum(:column , :conditions => {created_at >= 2.years.ago})
First, we need a function to find the difference in months from the current time.
def month_diff(date)
(Date.current.year * 12 + Date.current.month) - (date.year * 12 + date.month)
end
Then we iterate through #donation, assuming that :amount is used to store the value of each donation:
q = {}
#donations.each do |donation|
date = month_diff(donation.date)
if q[date].nil?
q[date] = donation.amount
else
q[date] += donation.amount
end
end
I found a good solution that covered all the bases--#user1185563's solution didn't bring in months without donations and #Tilo's called the database 24 times, but I very much appreciated the ideas! I'm sure this could be done more efficiently, but I created the hash with 24 elements (key: beginning of each month, value: 0) and then iterated through the donations and added their amounts to the hash in the appropriate position.
def monthly_hash
monthly_hash = {}
date = 2.years.ago
i = 0
while date < Time.now do
monthly_hash["#{date.beginning_of_month}"] = 0
date += 1.month
i += 1
end
return monthly_hash
end
#monthly_hash = monthly_hash
#donations.each do |donation|
#monthly_hash["#{donation.date.beginning_of_month}"] += donation.amount
end