RoR Postgresql timezone group by not working on Heroku - ruby-on-rails

I would like my reports to display in CST while my database is in UTC. For this to work I need to be able to group by a time column and have the db do timezone conversions.
This works on my dev postgresql box:
offset = -5
result = ActiveRecord::Base.connection.execute("select date(srl.created_at AT TIME
ZONE '#{offset.to_s}') AS date, count(*) from server_request_logs srl where
srl.created_at between '#{#from.to_s(:db)}' and '#{#to.to_s(:db)}' group by
date(srl.created_at AT TIME ZONE '#{offset.to_s}')")
on heroku it errors with:
ActiveRecord::StatementInvalid: PGError: ERROR: time zone "-5" not recognized
Its also not working with TZInfo names like 'America/Winnipeg' or supported timezones like 'CST' from http://www.postgresql.org/docs/7.2/static/timezones.html
has anyone been able to get this working?

First off, make sure that you define your timestamp columns and variables as TIMESTAMP WITH TIME ZONE (or timestamptz for short). In PostgreSQL this doesn't actually cause any time stamp to be saved; but makes it a fixed point in time, stored in UTC. You can view it AT TIME ZONE of your choosing with clean semantics. TIMESTAMP WITHOUT TIME ZONE (which is what you get if you just say TIMESTAMP) is not a fixed point in time until it is resolved against a time zone, and is therefore much harder to work with.
The documentation page you cite regarding time zones is from a very old version of PostgreSQL which has gone out of support. Maybe this page will be of more help to you:
http://www.postgresql.org/docs/current/interactive/datetime-config-files.html

Related

Postgresql date difference in table and on web

I have weird situation with my PostgreSQL db date value.
On my web site I have calendar for selecting a date and when I select some future date like "2018-09-23" in PostgreSQL table column it is saved as "2018-09-22 22:00:00"?
Obviously I am missing something. On web site all the time it shows okay time "2018-09-23" but at the table it is minus one day as you see above. Why?
Rails stores DateTime fields in UTC, but without marking their time zone as UTC. This corresponds to the timestamp without time zone type in postgres. So if your time zone is +2, it'll store the time as UTC (+0).
In Rails, Time.zone will return the current local timezone (you can add logic to change this by user, for example). When persisting a datetime, Rails will automatically convert the current Time.zone to UTC. However, it doesn't use the Postgres type that actually includes the time zone data, so it relies on convention to convert back and forth to the user's time zone.
If you really only care about the date, use the date type in your migration instead of Timestamp or DateTime.
Times and dates have a lot of subtle quirks and the "right" behavior depends on your use case. In some applications, you need to deal with "local" time when considering date transitions, and sometimes you need to finesse your application or database logic to think in terms of local time and sometimes you care about UTC time.

Rails, Postgres and Timezone

I have table which have a datetime field named date. When doing a POST in order to insert a new row, the date sent from the client (browser) looks like 2015-11-20T14:30:00+10:00 which is actually a correct date and timezone.
However, inside Postgres this date has been inserted as 2015-11-20 04:30:00.000000, which as you can see, is not at all the same as above. I know the problem is related to the timezone. But I cannot seems to figure out a fix.
For information, I have configured my app timezone :
class Application < Rails::Application
config.time_zone = 'Brisbane'
end
Ideas?
2015-11-20T14:30:00+10:00 means that the local time of 14:30 is 10 hours ahead of UTC. Your database field reflects the correct UTC value of 04:30. This is often the desired behavior, especially if the value represent a timestamp - the date and time something occured (past tense).
In PostgreSQL, there are two different types of timestamp fields (reference)
The TIMESTAMP WITH TIME ZONE field accepts an input that contains a time zone offset. It then converts the value to UTC for storage. On retrieval, it uses the session's timezone setting.
The TIMESTAMP, or TIMESTAMP WITHOUT TIME ZONE simply stores the date and time given, ignoring any offset, and not converting to UTC.
Most of the time, you should indeed use TIMESTAMP WITH TIME ZONE. You should only use TIMESTAMP WITHOUT TIME ZONE if you need to retain the local date and time value, such as in scheduling of future events and calculation of business hours. And for those scenarios, it often makes more sense to split date and time into separate DATE and TIME fields.
One last thing - if you can avoid it, avoid using Rails time zones and use standard tzdb zones. "Australia/Brisbane" is the full tzdb identifier equivalent to the Rails "Brisbane" time zone. Refer to the section on Rails time zones at the bottom of the timezone tag wiki.
I found this gem to be incredibly useful and easy for correctly setting the time https://github.com/kbaum/browser-timezone-rails

handling rails + postgres and timezones

I have an application which uses many different timezones... it sets them in a controller and they change depending on the user. All the times are stored in UTC without a timestamp etc.
My understanding is this is the normal way for Rails to handle timezones. This works fine 99% of the time until i need to do something directly with Postgres then the Timezone becomes a problem.
For example this query is completely wrong except for GMT, for example in Central Time Zone, depending on the hour set, it gets the wrong day:
Events.where("EXTRACT(dow FROM start_at)", Date.today.wday)
Where I'm trying to find all the dates that fall on a certain day.
I'm trying to do something like this. I don't need to search between timezones (they won't be mixed), but I do need to specify the timezone if it's not UTC to get correct results.
User.events.where("EXTRACT(dow FROM start_at AT TIME ZONE ?) = ?", 'UTC', Date.today.wday)
But I'm not sure how to use Time.zone to give me something that will work with TIME ZONE in Postgres.
Time.zone.tzinfo sometimes works... Postgres will work with 'Europe/Warsaw' but Rails returns 'Europe - Warsaw'
In general I'm not having much luck with timezones, any pointers would be appreciated.
Maybe someone else has a better overall solution, but what you need for the particular query is
Time.zone.tzinfo.identifier
Or, in your example:
User.events.where("EXTRACT(dow FROM start_at AT TIME ZONE ?) = ?", Time.zone.tzinfo.identifier, Date.today.wday)
Try using the Ruby TZInfo gem directly, instead of using Rails ActiveSupport::TimeZone.
Alternatively, use the MAPPING constant, as shown in the ActiveSupport::TimeZone documentation, which will take you from a Rails time zone key back to the standard IANA time zone identifier used by Postgres and others.
As Matt Johnson suggested use TZInfo gem directly. This way you can get the correctly formatted time zone identifiers you need to query with PostgreSQL.
For example if you use:
TZInfo::Timezone.all_country_zone_identifiers
This will return an array of correct IANA/Olson time zone identifiers. In other words you will get the correct 'Europe/Warsaw' NOT 'Europe - Warsaw'.

Date time conversion from timezone to timezone in sql server

I having an column of UNIX time stamp in my database table, which comes from a system that is in the Kuwait time zone.
My database server's time zone is Eastern Time US & Canada. Now I need to convert the UNIX time stamp in to Kuwait time zone date value using an SQL query.
Can anyone tell me how I can convert this UNIX time stamp into a Kuwait time zone date value?
Unix timestamps are integer number of seconds since Jan 1st 1970 UTC.
Assuming you mean you have an integer column in your database with this number, then the time zone of your database server is irrelevant.
First convert the timestamp to a datetime type:
SELECT DATEADD(second, yourTimeStamp, '1970-01-01')
This will be the UTC datetime that corresponds to your timestamp.
Then you need to know how to adjust this value to your target time zone. In much of the world, a single zone can have multiple offsets, due to Daylight Saving Time.
Unfortunately, SQL Server has no ability to work work time zones directly. So if you were, for example, using US Pacific time, you would have no way of knowing if you should subtract 7 hours or 8 hours. Other databases (Oracle, Postgres, MySql, etc.) have built-in ways to handle this, but alas, SQL Server does not. So if you are looking for a general purpose solution, you will need to do one of the following:
Import time zone data into a table, and maintain that table as time zone rules change. Use that table with a bunch of custom logic to resolve the offset for a particular date.
Use xp_regread to get at the Windows registry keys that contain time zone data, and again use a bunch of custom logic to resolve the offset for a particular date. Of course, xp_regread is a bad thing to do, requires certain permissions granted, and is not supported or document.
Write a SQLCLR function that uses the TimeZoneInfo class in .Net. Unfortunately, this requires an "unsafe" SQLCLR assembly, and might cause bad things to happen.
IMHO, none of these approaches are very good, and there is no good solution to doing this directly in SQL. The best solution would be to return the UTC value (either the original integer, or the datetime at UTC) to your calling application code, and do the timezone conversion there instead (with, for example, TimeZoneInfo in .Net or similar mechanisms in other platforms).
HOWEVER - you have lucked out in that Kuwait is (and always has been) in a zone that does not change for Daylight Saving Time. It has always been UTC+03:00. So you can simply add three hours and return the result:
SELECT DATEADD(hour, 3, DATEADD(second, yourTimeStamp, '1970-01-01'))
But do recognize that this is not a general purpose solution that will work in any time zone.
If you wanted, you could return one of the other SQL data types, such as datetimeoffset, but this will only help you reflect that the value is three hours offset to whomever might look at it. It won't make the conversion process any different or better.
Updated Answer
I've created a project for supporting time zones in SQL Server. You can install it from here. Then you can simply convert like so:
SELECT Tzdb.UtcToLocal('2015-07-01 00:00:00', 'Asia/Kuwait')
You can use any time zone from the IANA tz database, including those that use daylight saving time.
You can still use the method I showed above to convert from a unix timestamp. Putting them both together:
SELECT Tzdb.UtcToLocal(DATEADD(second, yourTimeStamp, '1970-01-01'), 'Asia/Kuwait')
Updated Again
With SQL Server 2016, there is now built-in support for time zones with the AT TIME ZONE statement. This is also available in Azure SQL Database (v12).
SELECT DATEADD(second, yourTimeStamp, '1970-01-01') AT TIME ZONE 'Arab Standard Time'
More examples in this announcement.

Ruby on Rails and Active Record standalone scripts disagree on database values for :timestamps

I posted a question earlier today when I'd not zeroed in quite so far on the problem. I'll be able to be more concise here.
I'm running RoR 2.1.2, on Windows, with MySQL. The SQL server's native time zone is UTC. My local timezone is Pacific (-0800)
I have a model with a :timestamp type column which I can do things like this with:
record = Record.find(:first)
record.the_time = Time.now()
When I do a "select * from records" in the database, the time shown is eight hours in advance of my local time, which is correct, given that the DB is on UTC. (I have verified that it is 'thinking in utc' with a simple 'select now()' and 'select utc_timestamp()')
This is where the trouble begins. If I display the time in a view:
<%= h record.the_time %>
...then I get back the correct time, displayed in UTC format. If I wrote to the database at 16:40:00 local time, the database showed 00:40:00.
HOWEVER, if I am running a standalone script:
record = Record.find(:first)
puts record.the_time
...then I get back the UTC time that I stored in the database (00:40:00,) but with the local timezone:
Wed Nov 26 00:40:00 (-0800) 2008
...an eight-hour time warp. Why is it that storing the time translates it correctly, but retrieving it does not? If I compare a stored time from the recent past in the DB and compare it to the current time, the current time is less - telling me this isn't just a string conversion issue.
Any ideas?
There is a setting in config/environment.rb that sets a time_zone. Possibly this is not set the same in your script:
# Make Time.zone default to the specified zone, and make Active Record store time values
# in the database in UTC, and return them converted to the specified local zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Uncomment to use default local time.
config.time_zone = 'UTC'
You can also try explicitly specifying the TZ and format:
require 'active_support/core_ext/date/conversions'
record.the_time.utc.to_s(:db)
(or cheat and grab the code fragment from there if you're not using active_support in your standalone script)
Try requiring active_support
Well, you did not comment on my last suggestion to restart your server or your console.
I had a similar problem and it got fixed when I did so.
Also you mention a stand-alone script, but in order to get the time-zone feature of Rails the code needs to run within Rails.
In the end, it was far easier to do this by storing times as an integer (Time.now().to_i()) and converting them back to a time when I needed to display them (Time.at(the_time_the_database_returned_to_me).) This is hardly the best solution, but it was the only one that worked.

Resources