I saw here on SO a few related questions (like this one and of course, this one)... Essentially, what I want is to store date-time as UTC, and let application user choose the time zone he wants to display date-time in.
Since it seems that date-time fields are affected by the underlying JDBC driver, I wonder if this is an acceptable way to go about storing UTC date-time:
Set both MySQL and Application server machine to UTC time zone (no need to separate)
Both MySQL and JVM should pick up underlying system time settings (if not instructed otherwise)
Use DATETIME table columns on MySQL side
Use java.util.Date as corresponding mapping on Hibernate side (I guess java.sql.Timestamp could be used too)
Let the application worry about interpreting date-time fields - i.e. let the user choose preferred time zone
Is this OK?
EDIT
To clarify - here I meant to refer to timestamps created strictly on the server (e.g.date-time of record creation). So the application server instantiates Date objects (new Date() equals current date-time on the server, and this is really time zone agnostic).
Now if a client user wants to supply some date for searching/filtering purposes, here is where the transformation from client-local time to UTC should take place, IMHO...
I would suggest another simple approach which would independent of machine timezone settings.
Instead of setting the timezone of the server machine, set the timezone of JVM. This can be done via system properties. On Windows example would as follows
set JAVA_OPTS=%JAVA_OPTS% -Duser.timezone=GMT
For MySQL here is the reference for doing it.
Now this this make sure that all your date-time are in GMT
Keep the timezone as configurable property OR it can be user dependent as well. So you store timezone for each user if the users belong to different geographies.
Whenever, a date is needed, after you select it from the database, apply the timezone to get the correct time.
The advantage of this approach is that this will work for the all the timezone users. Meaning the user will see the correct time as per their timezone.
Use locales to implement internationalization.
Related
Subsequent to my previous question:
show timezone - difference between pgAdmin and JDBC results
I can connect to a PostgreSQL server in any time zone via pgAdmin and get it to return its time zone via:
show timezone;
However, if I use JDBC and make the same call, it does not return the time zone text of the external server as expected. It returns the time zone of my client machine, which is not my expected behavior. I expect the same behavior as pgAdmin, that it returns the time zone of the external server as text.
I understand why JDBC would use my client timezone for date-time operations. That makes perfect sense, but when asking an external server for its timezone, then it makes no sense for it to return my client time zone.
I really want to get the time zone of an external server via JDBC without knowing beforehand what that time zone is and having to set the timezone of my JVM manually, as that defeats the point of the call I'm trying to make.
Is there any alternative way to do this, rather than using show timezone?
Time in PostgreSQL is always stored in UTC, the session timezone is what is used for conversion for a connection. This session config can have a default configuration on the server.
If you really need to know it, use
select * from pg_file_settings where name = 'timezone'
This will only have a value if it is explicitly configured, otherwise the default is GMT.
See also 8.5.3. Time Zones:
All timezone-aware dates and times are stored internally in UTC. They
are converted to local time in the zone specified by the TimeZone
configuration parameter before being displayed to the client.
This configuration setting specifies the default session timezone, it is not actually a 'server timezone'); its configuration does not actually have an influence on how PostgreSQL server handles dates in general.
A connection library can override this for a session by specifying the TimeZone connection property, or by executing set time zone '...' at a later point.
So this is not actually specific to the PostgreSQL JDBC driver: it applies to PostgreSQL in general, but the PostgreSQL JDBC driver always does this (see ConnectionFactoryImpl) to comply with JDBC specification requirements that dates and times need to be in the default JVM timezone.
I'm using an Oracle server, which I don't control, in another time zone (US, Central). There is a table with a DATE column, which gets a default value of SYSDATE when things are inserted (I.e., the column is always set with the system's time, without the programmer needing to set it explicitly).
I'm in a different time zone (US, East Coast). How do I read this column with JDBC and get an Epoch/UTC time?
When I call getTimestamp().getTime() (on the ResultSet), I get the time converted to local time. That is, if something was inserted at 10 am on the server, it is 11 am here (and 4 pm in UTC). When I call getTimestamp().getTime() I get an epoch of 10 am local time (which is 3 pm UTC).
I have a partial answer.
Calendar sqlServerCalendar = Calendar.getInstance(TimeZone.getTimeZone("CST"));
long t = rs.getTimestamp("timestampcolumn", sqlServerCalendar).getTime();
This is not ideal, because I have to specify the server's time zone (which I'd move into settings). But it does seem to return the answer I want. Is there a way I can query the server's time zone at run-time, rather than needing a setting? Or, is there a better way to do this? I would prefer a solution to not have to know (via hard-coded values, or settings) which time zone it or the server are in (so it will work unmodified if run in different time zones, and/or if the server moves).
You can pass the JVM parameter -Duser.timezone such as java -Duser.timezone=GMT and that should do the trick. But again one important thing to keep in mind about setting the timezone for the entire JVM is that it affects everything.
If you just need to set only for time zones when retrieving date, time or timestamp objects. you can do it like
Calendar mycal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
then on the result set
Date correctDate = resultset.getDate("Timestampcolumn",mycal);
I believe you should also be able to set the timezone using TimeZone.setDefault() as well.
In java, I want to know that what is the best practice to keep date info for display, query, report etc. It seems that if we persist as long, all timezone dependency will be removed and we will keep 'persist globally, display locally' principle since Date object automatically converts long to current timezone.
But what is the advantage of persisting as Date object?
Do I loose any info other than info owner's timezone?
Can I get any wrong info when DLS takes into account?
Difference between persisting as UTC date and long is just readable db info?
Depending on your database you should use either TIMESTAMP WITH TIMEZONE or you convert it to UTC time and store it as long.
The first one relies on the DB to handle it correctly (the DB will, but will your DB driver? You have to test this for your setup). The second one makes it a manual process, you will get the correct result in the end but will have more hazzle with it because you have to take care about everything.
Inside Java you might want to use Calendar over Date because there you can specify the TIMEZONE etc. manually, thus you are able to display Dates in timezones different to your own easier.
It seems (and maybe I'm wrong) that if you want to preserve the timezone of when something happened with JDBC and Postgres you need to store the timezone separately from the timestamp.
That is I would prefer to give my ORM/JDBC/JPA a Java Calendar (or Joda DataTime) with say timezone America/New_York to a Postgres timestampz field. AND I would expect on retrieval regardless of the Servers timezone (or defaulting to UTC) to give me back a Calendar with timezone America/New_York. But just looking at most JDBC code (and things that depend on it that doesn't happen).
Is this correct?
This seems ridiculous that I would need to store the tz in another field when postgres supports it.
Thus it seems like the only two options are:
Select the timestampz Postgres column as a java.util.String and parse it.
Store the timezone as a separate field.
Option number one and two one would require some sort of conversion interceptors for my SQL mapping / ORM libraries.
What is the best solution for JDBC ?
What is the best solution for JPA (if different than JDBC)?
When you store a timestamp with time zone (timestamptz) it's converted to UTC for storage in the DB. When retrieved, it's converted to the client's current timezone, not the timezone it was originally in. It's a point in time, basically.
There is also timestamp without time zone (timestamp). This is not subject to conversion, but does not carry a timestamp with it. If you store a timestamp with your client time zone set to UTC, then retrieve it when the client time zone is '+08:00', you get the same value. That's half what you want, in that it preserves the raw time value.
The names and behaviours are awful and confusing, but set by the SQL standard.
You must store the time zone separately if you wish to record a point in time at a particular time zone. I'd recommend storing it as an INTERVAL with a CHECK constraint limiting it to be colname BETWEEN INTERVAL '-12' HOUR + INTERVAL '1' SECOND AND INTERVAL '12' HOUR. That definition rejects -12:00 and accepts +12:00; I'm not totally sure that's right, so check.
You could either store the timestamp of local time at that time zone (what I'd probably do), or store the timestamptz of the UTC time when the event occurred plus an offset that lets you convert it to local time.
Either will work fine for JDBC. For JPA, it'll depend on how well your provider understands and maps interval types. Ideally you want a transient generated field in your entity that reconstructs the Calendar instance you want using the timestamp and interval stored in the database.
EclipseLink supports storing the timezone in Oracle, I think you could get it to be stored in Postgres as well if you customized your PostgreSQLPlatform.
Changes to time zones do occur from time to time, I know every year in the UK there seems to be talk that we should scrap GMT or use "double summer time".
When these changes occur, I guess that any dates obtained via the TimeZone or Calendar based classes on an old version of Java will potentially give you incorrect data. Short of just updating your Java version all the time is there a way around this e.g. like an always up-to-date patch file you can put into you application?
Also, is the problem fixable for Applets in the same way - since your not directly in control of the Java version / update on which the application is run?
The best solution is to upgrade to the latest JRE and JDK.
If this is not possible for whatever reason, SUN makes a tool available for updating the timezone table to the latest one. You can find the TZUpdater here
The documentation of the Java TimeZone and derived SimpleTimeZone classes state that...
The class holds an offset from GMT, called raw offset, and start and end rules for a daylight saving time
schedule. Since it only holds single values for each, it cannot handle historical changes in the offset from
GMT and the daylight saving schedule, except that the setStartYear method can specify the year when the
daylight saving time schedule starts in effect.
This means that if your application does not rely on historical dates/times, you won't have issues if the TimeZone object you're using at the time that these objects are generated are up to date.
However...
If your application contains any sort of database of historical dates/times, then when a new Time Zone rule goes into effect, you application will erroneously interpret your historical information and display incorrect results since the TimeZone object it will be using is not the same as the TimeZone object that was used at the time your historical data was generated.
If it is important to you to maintain historically accurate dates/times (such as, for example, billing records to government agencies that have very sharply defined parameters for billing eligibility) then you'll need to store not only your historical date/time information in your database, but information that can be used to recreate the time zone rules that were in effect at that time as well. There are a few possible solutions.
The Java SimpleTimeZone class has a constructor that will allow you to create a TimeZone object with a single set of time zone and daylight savings rules:
SimpleTimeZone(int rawOffset, String ID, int startMonth, int startDay, int startDayOfWeek, int startTime,
int endMonth, int endDay, int endDayOfWeek, int endTime, int dstSavings)
Constructs a SimpleTimeZone with the given base time zone offset from GMT, time zone ID, and rules for
starting and ending the daylight time.
So, just make sure you store all the date/time information in your database as UTC timestamps. I like to store timestamps in databases as "Modified Julian Day Numbers" which are able to store calendar dates and times to millisecond precision in a double. Modified Julian Days numbers are always defined in UTC. Whatever though, as long as your info is in UTC, you're standard.
Then, you can create a table of all the arguments needed to create the SimpleTimeZone objects ever used in your database, or you can simply persist all the objects used by your database with the object persistence API of your liking. With a foreign key that indicates the time zone rules used for the relevant data, you can assure that your database application can report historically accurate local times.
Mark Rhodes is right if something at time zone was changed (like summertime on mine) it will return even wrong time. So end users should update their JREs.
PS: additionaly you can fine TimeZone Update Utility for IBM JRE here
http://www.ibm.com/developerworks/java/jdk/dst/jtzu.html
The java date reads out the time of the system. This time is normally sync with an timeserver, so, normally, there won't be problems.
The timezone API figures out daylight savings for you.
http://docs.oracle.com/javase/1.4.2/docs/api/java/util/TimeZone.html