How to handle date/times in POST parameters? - ruby-on-rails-3

By default, Rails' config.time_zone is UTC. So, fields like created_at, updated_at are saved in UTC.
Now, presume user's time zone is Pacific Standard Time (PST).
In order to show dates and times in the user's time zone, I have set Time.zone to PST.
Now, when a user enters a date (in user's local time zone, which is PST) in a form, that date is received in the controller according to the setting of Time.zone.
But, I want to save all times in the database in UTC.
So, how would I go about converting any date/time field in-place in the parameters hash to UTC? I'm wondering if there is a solution where Rails can do the conversion automatically?
I would like that any date/time received for any datetime field be converted to UTC, without having to write the conversion code in every controller.
UPDATE
I tested by setting Time.zone to Pacific Time (US & Canada) as well as to Eastern Time (US & Canada) and then saving a model. The created_at time in both cases was in UTC. So that confirms that setting Time.zone has no effect on what time is saved by Rails in the created_at field. That value is effected only by the config.time_zone (which if not defined in application.rb will default to UTC)
I'm so confused by this all!

In the database, all dates are always saved as UTC. The config.time_zone parameter is setting the default value for Time.zone, just for display. So you don't have to do anything to get the behavior you desire, just keep Time.zone set correctly for the user, and rails takes care of the rest.

Related

Is there a "local timezone" suffix?

Is there any convention to add in a local timezone (whatever the user has set it as) in a database?
For example, like I can do the following for UTC time:
2014-01-01 01:02:03Z
Is there something like the following to mean local time?
2014-01-01 01:02:03L
Or some other suffix where it can either pick up the user's system time or take it from a variable that can be set? For example, something like (for Postgres):
ALTER DATABASE postgres SET timezone TO 'Europe/Berlin';
This has nothing to do with Postgres.
The format you're asking about is ISO 8601. Specifically in that format, the absence of a Z or an offset such as -07:00 or +05:30 is defined as "local time".
So what you are looking for is a string without an offset, such as 2014-01-01T01:02:03.
postgres would use the system timezone, or what you called local, if no timezone is specified. see manual
If no time zone is stated in the input string, then it is assumed to be in the time zone indicated by the system's TimeZone parameter, and is converted to UTC using the offset for the timezone zone.
...
Conversions between timestamp without time zone and timestamp with time zone normally assume that the timestamp without time zone value should be taken or given as timezone local time.
Your concept is flawed for a couple of reasons:
A user in Germany connects to a Web server in England that connects to a database server is America. What constitutes local?
More to the point Postgres does not store the time zone in with time zone fields. So you will not recover the entered time zone on data retrieval.
If you are dealing with multiple time zones then the field you need to use is timestamp with time zone. This will rotate entered timestamp values to UTC for storage. You now have a fixed point in time that you can rotate to whatever 'local' time you want on retrieval.
Just treat the undecorated time as a timestamp with time zone:
richard=> SET timezone='Europe/London';
SET
richard=> SELECT '2022-08-27 21:42:22.25891'::timestamptz;
timestamptz
------------------------------
2022-08-27 21:42:22.25891+01
(1 row)
richard=> SET timezone='Europe/Paris';
SET
richard=> SELECT '2022-08-27 21:42:22.25891'::timestamptz;
timestamptz
------------------------------
2022-08-27 21:42:22.25891+02
(1 row)

SQL Datetimeoffset Compare

I have a table storing a time value say "11:00 AM". This is what the client's would want the progress to run every day. The client also has a timezone info table letting me know which time zone its office is located. So "11:00 AM" in Eastern time zone is very different from "11:00 AM" in Pacific.
Then I have a program that checks whether the current time is what the client's desired runtime. This process runs in Eastern time zone always.
I am trying to figure out an easy way to compare whether my current time is actually the client's desired run time. What I am doing now is something like this:
Get the current DateTimeOffset of my environment. This will produce something like '2021-03-15 16:02:22.3112948 -04:00'
Then get the client's run time, which is "11:00 AM" or whatever the value, as well as client's timezone offset by using the sys.time_zone_info table. This give me the actual offset value, taking DST into consideration so that I don't have to calculate whether today is in DST or not.
I then construct a Datetimeoffset value from string by using current date value + client's run time value + the offset value. Then convert this into Eastern time zone using at time zone 'Eastern Standard Time'
By now, I have two values both in Eastern time zone and I can compare them. First question -- do you see any potential issue with my logic? Secondly, is there a better/easier way to do this?
Thanks!

Setting server time correctly - Moqui vs. server

can I adjust time settings in the application so that correct times (in terms of my TZ) are saved? Or do I have to adjust the server time?
Time zones are handled in Moqui in a standard way. The point in time in Timestamp objects has no time zone (milliseconds since epoch) so the time zone only comes into play when converting it to another form, such as parsing a String from user or other input or printing a String... or in some cases saving to and loading from a database.
Some databases use a no timezone internal representation, for others ALL Timestamp, Date, Time objects are saved and loaded using the database time zone. The database time zone is set using the entity-facade.#database-time-zone attribute in the Moqui Conf XML file, or if not set defaults to the server's time zone (ie Java's default time zone).

Get the time the users in their timezone submitted

Our application uses CURRENT_TIMESTAMP to store an event when a user submitted the data. This is stored in a TIMESTAMP WITH LOCAL TIME ZONE type column. Our servers are in PST timezone, but I want to see what time one of our users in Australia submitted the data in Australian time? I am confused how to do this properly? When I query the database right now this column seems to show the date the row was submitted but in PST time for every row.
Something like this:
SELECT
datetime_submitted -- I want this to display the time this value was created in Australian time not PST
FROM my_table
WHERE user = 'AUSTRALIAN';
Use the AT TIME ZONE clause:
select systimestamp as server_timestamp,
systimestamp at time zone 'Australia/Sydney' as australia_timestamp
from dual;
SERVER_TIMESTAMP AUSTRALIA_TIMESTAMP
-------------------------------------- --------------------------------------
12-AUG-16 04.12.23.789000000 PM -05:00 13-AUG-16 07.12.23.789000000 AM AUSTRALIA/SYDNEY
Right now it is 4:12:23 PM on 12 August 2016 at my location (Central time, US); it is already tomorrow in Australia, as you can see from the example. (Reminds me the joke - don't worry about the end of the world coming today, it's already tomorrow in Australia!)
If you want to just look at the time in the user's time zone you can set the session to whatever time zone the user is located in then when you select the times it should give it back to you in that time zone as if you were that user.
% setenv ORA_SDTZ '+09:30'
or
ALTER SESSION SET TIME_ZONE='+9:30';
Reference for more details: https://docs.oracle.com/cd/E11882_01/server.112/e10729/ch4datetime.htm#NLSPG263
Updated
If you only want one column displayed in a different time zone then you can convert it.
To convert the timezone just in the query you can just use CAST(date_field AS TIMESTAMP) AT TIME ZONE 'US/Eastern' AS time_name on the column you want to change.
Reference for more details: https://docs.oracle.com/cd/B19306_01/server.102/b14225/ch4datetime.htm#i1007699
First, your application need to receive exact date time with user local timezone. For example, if you have typical web application with frontend, backend and database, then your frontend need to send exact date time with local user timezone (for example "2016-08-08 03:00:00CST") to your backend. Backend need to save this in a table as-is keeping date, time, timezone information received from frontend. Oracle needs to store date, time and timezone into database as-is so datetime_submitted column should have information about time zone. Suitable data type for it will be TIMESTAMP WITH TIME ZONE, as described here: https://docs.oracle.com/cd/B28359_01/server.111/b28318/datatype.htm#CDEGDFFE
Maybe you did not fully understand data type TIMESTAMP WITH LOCAL TIME ZONE. Data type TIMESTAMP WITH LOCAL TIME ZONE shows time values always and only in current user session time zone.
When you work with time zones then often you get an advise like: "Store all times in UTC time and convert displayed value at application layer." That is exactly how TIMESTAMP WITH LOCAL TIME ZONE works. The mayor difference is, times are not stored at UTC but at DBTIMEZONE (unless you set DBTIMEZONE = UTC, of course). Hence you cannot modify DBTIMEZONE setting once you have any data in a TIMESTAMP WITH LOCAL TIME ZONE column.
You get an error if you try for example TO_CHAR(datetime_submitted, 'hh24:mi:ss TZR'), because time zone region is by definition always your current user session time zone.
The database does not know the time zone of your users in Australia, because all stored times where converted to PST time zone (well, most likely to -08:00 but skip the details here).
If you like to see the values in a different time zone then you have to change your current user session time zone. Typically you do this by ALTER SESSION SET TIME_ZONE=... or by environment variable, resp. Registry setting ORA_SDTZ.
Another solution is to cast to TIMESTAMP WITH TIME ZONE or TIMESTAMP data type.

hsqldb TIMESTAMP field not accepting zone as America/Los Angeles

I have a TIMESTAMP field in an hsqldb table that I want to set to "2015-02-11 16:02:01.488 America/Los_Angeles", but the insert fails even if I set the column to TIMESTAMP WITH TIMEZONE, the reason being hsqldb seems to support '2008-08-08 20:08:08-8:00' format but not spelled out like America/Los_Angeles. Is there way to make the insert accept America/Los_Angeles type zones ?
Sorry, but hsqldb doesn't support working with IANA/Olson time zones directly. You are correct that TIMESTAMP WITH TIMEZONE only supports a time zone offset. You can review the hsqldb docs for confirmation.
Many databases do not support named time zone. Oracle and Postgres support them, but most others do not.
Consider also that while a named time zone can usually determine the offset, there are still cases of ambiguity around the fall-back daylight saving time transition. In other words, if you had "2015-11-01 01:30:00 America/Los_Angeles", you could not deterministically tell whether it was Pacific Daylight Time (UTC-07:00) or Pacific Standard Time (UTC-08:00). This is why usually just the offset is stored.
The converse is also true though. If you only store "-08:00" then you can't deterministically know that it came from "America/Los_Angeles".
Here's a general guideline that will help:
If the local time is unimportant, then just store a TIMESTAMP based on UTC.
If the local time is important, but the value will never be modified, then store a TIMESTAMP WITH TIMEZONE, using the local time and it's associated time zone offset.
If the local time is important AND the value can be modified, then store a TIMESTAMP WITH TIMEZONE in one column, and the time zone name (ie. "America/Los_Angeles") in a second VARCHAR column, or elsewhere in your database. During an edit operation, use the time zone name to calculate the offset of the new value. It might be the same, or it may be different.
See also DateTime vs DateTimeOffset, which presents a similar argument for .Net and/or SQL Server.