Compare just time part of DateTime? - sql

I have a restaurant that has hours. Each Hour object as an open_at and a close_at time. How do I compare just the time part of the DateTime in the db with the time of Time.now?
hours.today.where("open_at <= ? AND close_at >= ?", Time.now, Time.now).any?
edit: I tried rephrasing the question:
I have an Hour object h with an attribute close_at of type Time. How can I compare just the time part in the db with Time.current?
2.1.3 :014 > h.close_at.class
=> Time
2.1.3 :015 > h.close_at
=> 2000-01-01 22:44:00 UTC
2.1.3 :016 > Time.current
=> Wed, 15 Oct 2014 17:32:27 UTC +00:00
2.1.3 :017 > h.close_at >= Time.current
=> false
I want the last line to return true. (22:44 > 17:32)

I believe that you are trying check if there exists any hour that has been opened today and closed any time later from now:
Assuming this, you can do the required by following:
Hour.where(:open_at => (Time.current.midnight)..(Time.current) , :closed_at => (Time.current)..(1000.years.from_now) ).any?
Basically the key point is that , you can pass ranges in where clause.
And if you dont have any upper bound or lower bound , you can always use this to define the range:
1000.years.from_now
1000.years.ago
for example, any time later from now could be represented as:
Time.current..(1000.years.from_now)
Obviously you would not be making a app that needs to store time for 1000 years back and forth from now! I believe this approach should workout out in your case.
Edit:
I am sorry for misunderstanding your question. This is the workaround to the problem:
closing_time = h.close_at
# closing_time => 2000-01-01 22:44:00 UTC
closing_time = Time.current.midnight + closing_time.hour.hours + closing_time.min.minutes + closing_time.sec
#this shifts the closing date_time to the current date, without affecting hours, minutes and seconds, So,
# closing_time => 2014-10-15 22:44:00 UTC
current_time = Time.current
# current_time => Wed, 15 Oct 2014 17:32:27 UTC +00:00
closing_time >= current_time
#=> returns true (because 22:44 > 17:32 , with other things remaining the same.)
Hope it helps :)

If you want to compare time IN SQL and if you are using MySQL you can use its TIME() function, from docs:
Extracts the time part of the time or datetime expression expr and returns it as a string.
So you can do:
hours.today.where("TIME(open_at) <= ? AND TIME(close_at) >= ?", Time.now, Time.now).any?

Related

How to add a datefield attribute with integer attribute using ActiveRecord

I have a rails model with two attributes, one is a datetime field and the other is an integer. I'm trying to add a scope using both these fields & it gives me incorrect results.
ModelName.where("model.created_at + model.hours > ?", Time.now)
Model.created_at is of class ActiveSupport::TimeWithZone and Model.hours is Integer.
Sample data
created_at: Mon, 28 Nov 2022 10:16:39.095488000 UTC +00:00,
hours: 5
If you're using PostgreSQL, I think you can use something like:
ModelName.where("created_at + interval '1 hour' * hours > ?", Time.now)
For MySQL:
ModelName.where("DATE_ADD(created_at, INTERVAL hours hour) > ?", Time.now)

Query datetime as text and include different timezones

Short version:
I have 2 users in DB, each created in a different timezone:
User.find(1).created_at
=> Thu, 04 Aug 2016 11:15:29 IDT +03:00
User.find(33).created_at
=> Sun, 01 Jan 2017 17:50:20 IST +02:00
So my table shows 11:15, and 17:50. so for example I would like to search for 17:50, and later 11:15 as text:
search_param = '17:50'
No problem I'll just convert the date to text, then search it, But user won't be found, since the it's saved as UTC:
User.where("to_char(created_at,'DD-Mon-YYYY HH24:MI:SS') ilike ?", "%#{search_param}%").first
=> nil
To find it I'll just apply the offset to my query (adding time zone UTC+2), and indeed user was found:
User.where("to_char(created_at AT TIME ZONE 'UTC+2','DD-Mon-YYYY HH24:MI:SS') ilike ?", "%#{search_param}%").first
=> User #33 #2017-01-01 17:50:20 +0200
BUT some users are saved as UTC+3 and some as UTC+2.. I can't apply both offsets... So if I change search_param to be 11:15 I won't find user_id_1 because I will also need to change UTC+2 to UTC+3
When I use UTC+2 I will only find users which were created as +2 (like User.find(33)).
When I use UTC+3 I will only find users which were created as +3 (like User.find(1)).
My question: How to do a where query- a text search for both users' created_at hour, as they were both saved in a different timezone offset?
Or in this example a query that for a search_param 17:50 will find user_id_33 and for search_param 11:15 will find user_id_1?
More Details:
I notice in DB they are saved as UTC (I think):
User.select("created_at as created_at_db").find(33).created_at_db
=> "2017-01-01 15:50:20.903289"
User.select("created_at as created_at_db").find(1).created_at_db
=> "2016-08-04 08:15:29.171776"
Time setting:
#application.rb
config.time_zone = 'Jerusalem'
config.active_record.default_timezone = :utc
created_at column info:
User.columns.select{|table_col| table_col.name == 'created_at'}
=> [#<ActiveRecord::ConnectionAdapters::PostgreSQLColumn:0x81f9c48
#coder=nil,
#default=nil,
#limit=nil,
#name="created_at",
#null=false,
#precision=nil,
#primary=false,
#scale=nil,
#sql_type="timestamp without time zone",
#type=:datetime>]
You should be able to query your user entities with:
date_trunc('minute', created_at AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Jerusalem')::time = '17:50'
Or, with to_char() (but less index-friendly):
to_char(created_at AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Jerusalem', 'HH24:MI') = '17:50'
The point is that created_at is timestamp (or timestamp without time zone, which is just an alias). First, you can tell PostgreSQL, that interpret its values as they were in UTC, with the <timestamp> AT TIME ZONE <time zone> operator:
created_at AT TIME ZONE 'UTC'
Then, tell PostgreSQL to offset this value as if it were a local time in Asia/Jerusalem with the <timestamp with time zone> AT TIME ZONE <time zone> operator (which is completely different from the operator above with the same name):
created_at AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Jerusalem'
Now, you just need to truncate this value to extract only the hour & minute part.
Maybe worth mentioning that using these:
created_at AT TIME ZONE 'UTC+2'
created_at AT TIME ZONE 'UTC+3'
worked for you accidentally. These create timestamp with time zone values within the -2 and -3 UTC offsets, respectively. Then, because your config.active_record.default_timezone is set to :utc, it is sent to your client (ruby) within the UTC time zone, which means it added 2 and 3 hours, respectively.
Another issue to keep in mind is that in POSIX time zone names, positive offsets are used for locations west of Greenwich. Everywhere else, PostgreSQL follows the ISO-8601 convention that positive timezone offsets are east of Greenwich.

UNIX TIMESTAMP GMT stored in sqlite in Integer, convert to local time zone

in my table in sqlite i have stored a date in timestamp in gmt time zone, in this way:
CREATE TABLE task (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
date INTEGER,
name TEXT
);
when i query the database i want retrieve the date with the local time so:
NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
the now timestamp is of my local time zone, so for example if it's possible i want do this:
SELECT id,date,name FROM task WHERE converttolocal(date) = now;
i want know if exist a function in sqlite like for instance converttolocal to automatically convert the timestamp...
EDIT
i found this:
strftime('%s',date, 'unixepoch', 'localtime')
but if for instance the date gmt in the database is: 1375660800 that is:
GMT: Mon, 05 Aug 2013 00:00:00 GMT
after the function strftime the result is: 1375668000 that correspond at my local time:
(CEST) Mine time zone: 05 agosto 2013 04:00:00
but i want that the result of the strftime function give me this: 1375653600 that is:
Mine time zone: 05 agosto 2013 00:00:00 CEST
Why doesn't give me this result?
To convert from a Unix timestamp to SQLite's default date format, use the unixepoch modifier.
To convert a time from UTC to local time, use the localtime modifier.
To convert from SQLite's default date format to a Unix timestamp, use strftime with the %s format:
SELECT id,date,name FROM task WHERE strftime('%s', date, 'unixepoch', 'localtime') = ?
It might be easier to use SQLite's built-in method to get the current time, which already is in UTC:
SELECT id,date,name FROM task WHERE datetime('%s', date, 'unixepoch') = datetime('now')
However, if you have only dates, you want to ignore the time portion.
In that case, use date instead of datetime:
SELECT id,date,name FROM task WHERE date('%s', date, 'unixepoch') = date('now')
SELECT id,date,name FROM task WHERE datetime('date', 'unixepoch', 'localtime') = datetime('now','unixepoch');
This will work. But the left hand side value will never match with the right hand side and you will never retrieve a value, because you are capturing timestamps, by the time you compare, the timestamp value would have changed.
so , SELECT id,date,name FROM task WHERE converttolocal(date) = now; will never be true .

Rails daylight savings time not accounted for when using DateTime.strptime

I've been working on parsing strings and I have a test case that has been causing problems for me. When parsing a date/time string with strptime, Daylight Savings Time is NOT accounted for. This is a bug as far as I can tell. I can't find any docs on this bug. Here is a test case in the Rails console. This is ruby 1.9.3-p215 and Rails 3.2.2.
1.9.3-p125 :049 > dt = DateTime.strptime("2012-04-15 10:00 Central Time (US & Canada)", "%Y-%m-%d %H:%M %Z")
=> Sun, 15 Apr 2012 10:00:00 -0600
1.9.3-p125 :050 > dt = DateTime.strptime("2012-04-15 10:00 Central Time (US & Canada)", "%Y-%m-%d %H:%M %Z").utc
=> Sun, 15 Apr 2012 16:00:00 +0000
1.9.3-p125 :051 > dt = DateTime.strptime("2012-04-15 10:00 Central Time (US & Canada)", "%Y-%m-%d %H:%M %Z").utc.in_time_zone("Central Time (US & Canada)")
=> Sun, 15 Apr 2012 11:00:00 CDT -05:00
As you can see, I have to convert to utc and then back to the timezone to get DST to be properly interpreted, but then the time is shifted one hour as well, so it's not what I parsed out of the string. Does someone have a workaround to this bug or a more robust way of parsing a date + time + timezone reliably into a DateTime object where daylight savings time is properly represented? Thank you.
Edit:
Ok, I found a workaround, although I'm not sure how robust it is.
Here is an example:
ActiveSupport::TimeZone["Central Time (US & Canada)"].parse "2012-04-15 10:00"
This parses the date/time string into the correct timezone. I'm not sure how robust the parse method is for handling this so I'd like to see if there is a better workaround, but this is my method so far.
This is a frustrating problem. The Rails method you're looking for is Time.zone.parse. First use DateTime.strptime to parse the string, then run it through Time.zone.parse to set the zone. Check out the following console output:
> Time.zone
=> (GMT-06:00) Central Time (US & Canada)
> input_string = "10/12/12 00:00"
> input_format = "%m/%d/%y %H:%M"
> date_with_wrong_zone = DateTime.strptime(input_string, input_format)
=> Fri, 12 Oct 2012 00:00:00 +0000
> correct_date = Time.zone.parse(date_with_wrong_zone.strftime('%Y-%m-%d %H:%M:%S'))
=> Fri, 12 Oct 2012 00:00:00 CDT -05:00
Notice that even though Time.zone's offset is -6 (CST), the end result's offset is -5 (CDT).
Ok, here is the best way I've found to handle this so far. I created a utility method in a lib file.
# Returns a DateTime object in the specified timezone
def self.parse_to_date(date_string, num_hours, timezone)
if timezone.is_a? String
timezone = ActiveSupport::TimeZone[timezone]
end
result = nil
#Chronic.time_class = timezone # Trying out chronic time zone support - so far it doesn't work
the_date = Chronic.parse date_string
if the_date
# Format the date into something that TimeZone can definitely parse
date_string = the_date.strftime("%Y-%m-%d")
result = timezone.parse(date_string) + num_hours.to_f.hours
end
result
end
Note that I add hours onto the time manually because Chronic.parse wasn't as robust as I liked in parsing times - it failed when no trailing zeros were added to a time, for example, as in 8:0 instead of 8:00.
I hope this is useful to someone. Parsing date/time/timzone strings into a valid date seems to be a very common thing, but I was unable to find any parsing code that incorporated all three together.

Still confused about Timezone and conversions

I collect user time zone and a time they want something delivered in that time zone.
My application is set to use Bangkok Time.
application.rb
config.time_zone = 'Bangkok'
I want to store the time the user has entered relative to my timezone.
So say for example the user selects the following:
time = "6:00"
timezone = "(GMT-10:00) Hawaii"
I want to convert this time to the time of my application and save it in the database.
How can I convert from 6AM Hawaii time zone to what time this is in Bangkok?
Let us use your example to demonstrate:
config.time_zone = 'Bangkok'
time = "6:00"
timezone = "Hawaii"
The first thing you need to do is set the timezone of your app to the timezone that the user picked:
Time.zone = "Hawaii"
You then use the this new time zone to parse time time:
hawaii_time = Time.zone.parse("6:00")
If we now print out hawaii_time we get something similar to (we have HST -10:00 which means it is correct):
Mon, 15 Aug 2011 06:00:00 HST -10:00
You now use the rails in_time_zone method to convert to your app's timezone:
local_time = hawaii_time.in_time_zone(Rails.configuration.time_zone)
If we print out local_time we'll get something similar to (we have ICT +07:00 which is correct for Bangkok):
Mon, 15 Aug 2011 23:00:00 ICT +07:00
We can now reset the timezone of our app back to its original value:
Time.zone = Rails.configuration.time_zone