I am unable to find a way to read timezone value in PostgreSQL column of type timestamp with time zone.
JDBC offers method java.sql.ResultSet#getTimestamp(int, java.util.Calendar)
but I must provide my own calendar. I have see no way to obtain that calendar from timestamp field I am reading.
I am working on system that stores time data of multiple timezones. I need to save data with timezone information, and be able to read that timezone back from database.
Is it possible without hacks like
storing timezone value in another field
storing date as string, like 'Wed 17 Dec 07:37:16 1997 PST'
I am using JDBC 41 (JDBC4 Postgresql Driver, Version 9.4-1201), java 8.
The PostgreSQL documentation here says that:
For timestamp with time zone, the internally stored value is always in UTC (Universal Coordinated Time, traditionally known as Greenwich Mean Time, GMT).
So there is no need to "store the time zone [that corresponds to the timestamp value]" per se; the timestamp with time zone values are always stored as UTC.
Also, there is no need to "obtain the Calendar from the timestamp field". The purpose of the Calendar is for you to define the particular timezone that you want to work with in your Java application.
In other words, timestamp with timezone does not store values from various timezones (e.g., some in EST, others in PST, etc.), it converts everything to UTC when the timestamp values are inserted.
Accepted answer is true and accurate. timestamp with time type does not store timezone information in the field, and it is not possible to extract it.
If interested in timezone of the timestamp, it must be stored in separately (in other field, or in custom column type).
At first glance, it looks like that timezone may be extracted from timestamp with timezone using function extract(timezone from field), but it is not the case.
That function just gives 'time zone offset from UTC, measured in seconds'. Important (and not stated in documentation) part is that the offset is measured from current timezone (set by session SET SESSION TIME ZONE, or server timezone if not set). It is not offset that was used when saving field.
Related
I have a java application that uses Spring JDBC to store data into Oracle. The data includes timestamps/dates which we use to query certain data in the future.
The date fields are defined like so in the DDL SQL file :
JobExecution {
START_TIME TIMESTAMP DEFAULT NULL ,
END_TIME TIMESTAMP DEFAULT NULL
...
}
Java code to update these fields looks like so :
lastJobExecution.setEndTime(new Date(System.currentTimeMillis()));
new Date(System.currentTimeMillis()) stores current time in UTC format as per documentation below. The documentation of System.currentTimeMillis() says that it returns the following :
the difference, measured in milliseconds, between the current time and
midnight, January 1, 1970 UTC.
The documentation of Date says the following :
Allocates a Date object and initializes it to represent the specified
number of milliseconds since the standard base time known as "the
epoch", namely January 1, 1970, 00:00:00 GMT.
Params: date – the milliseconds since January 1, 1970, 00:00:00 GMT.
See Also: System.currentTimeMillis()
However, when I connect to the oracle database using SQL developer, the date seems to be stored as per the local time and not UTC.
Questions :
Does Oracle TIMESTAMP adjust the date as per the local time? I can't find this explicitly written anywhere in the Oracle documentation.
If yes what would be the best way to handle this. One way could be to convert this to UTC every time I read the data from this table. Another way could be to store it in the UTC format itself.
No, the TIMESTAMP data type does not store any time zone information.
See Datetime Data Types documentation.
Oracle provides two timestamp data type supporting time zones:
TIMESTAMP WITH TIME ZONE
As the name implies it stores the timestamp with time zone information. Time zone can be give as region name (e.g. Europe/Zurich) or as UTC offset. Note, you cannot create an index on such column directly. Instead Oracle creates a virtual column of SYS_EXTRACT_UTC(<your column>) and index is created on this virtual column. You may need to adapt your queries accordingly.
Often when you work with time zones, then a common approach is to store all times as UTC times and the client converts it to local times. This is exactly provided by second data type:
TIMESTAMP WITH LOCAL TIME ZONE
In a TIMESTAMP WITH LOCAL TIME ZONE all values are stored at DBTIMEZONE (which defaults to UTC) but values are always displayed in current user session time zone.
Another note, all comparison (e.g. <, >, =, >=, <=) of TIMESTAMP WITH [LOCAL] TIME ZONE values are performed on according UTC value.
I want to update a database table which uses date type to timestamp with timezone type in such a way that the old dates get correct timezone information.
The plain cast is not good for me because if the time zone is for example UTC+2 hours (UTC+1 + 1 hour for daylightsaving) and I try to cast dates to timestamp with timezone, all the dates in the database table got the same +2 hour as timezone offset, regardless if it's a summer time or winter time date.
I already can write an SQL query which can determine about a date if it is in daylightsaving time or not, IF I know the current time zone in string format, e.g. 'Europe/Berlin'. The problem is that dbtimezone and sessiontimezone can be stored in other formats, too (+02:00, CET, etc). I cannot easily set the current sessiontimezone in a static way, because there are customers in several places on the globe with their own databases, but using a common update script.
Express method for timestamp can not help neither, because it cannot map the offset to named time zones.
I've seen a solution which uses java stored procedure to get the OS’s timezone instead of Oracles timezone. Unfortunately we use Oracle 12c, which contains an older JRE (I think it's 1.6 version). So, although Java 1.8 handles the timezones and daylight saving well (it uses updated tzmapping table), it does not work for me. I tryed it and if I run a test from Netbeans, then it gives me back the right time zone ID (in Europe/Berlin format), but even if it is accepted by Oracle SQL Developer, SQLPlus (which we use for running update scrips), it displays only +02:00.
I've tryied to use JodaTime (recompiled onto Java 1.6 in order to be accepted by SQL*Plus). The latest JodaTimes uses its own mapping table in theory. I read here on StackOverflow that if it cannot gather the time zone from user.timezone variable, then it turns to java.util, which is not good, as I mentioned. And if it does not succeed, then uses UTC. But it's not clear to me why it cannot get timezone from user.timezone systemm variable. Is it a permission problem maybe?
Or how could I possibly solve this issue? Thank you!
If the data is already in an Oracle SQL table, and you must convert to a timestamp with time zone (for example, in a new column you created in the same table), you do not need to go explicitly to the OS, or to use Java or any other thing, other than the Oracle database itself.
It is not clear from your question if you must assume the "date" was meant to be in the server time zone (you mention "the database" which normally means the server) or the client time zone (you mention "session" which means the client). Either way:
update <your_table>
set <timestamp_with_time_zone_col> =
from_tz(cast<date_col> as timestamp, dbtimezone)
;
or use sessiontimezone as the second argument, if that's what you need.
This assumes that the database (and/or the session) time zone is set up properly in the db, respectively in the client. If it isn't / they aren't, that needs to be fixed first. Oracle is perfectly capable of handling daytime savings time, if the parameters are set correctly in the first place. (And if they aren't, it's not clear why you would try to get your operation to be "more correct" than the database supports in the first place.)
Example: in the WITH clause below, I simulate a table with a column dt in data type date. Then I convert that to be a timestamp with time zone, in my session's (client) time zone.
with
my_table ( dt ) as (
select to_date('2018-06-20 14:30:00', 'yyyy-mm-dd hh24:mi:ss') from dual
)
select dt,
from_tz(cast(dt as timestamp), sessiontimezone) as ts_with_tz
from my_table
;
DT TS_WITH_TZ
------------------- -------------------------------------------------
2018-06-20 14:30:00 2018-06-20 14:30:00.000000000 AMERICA/LOS_ANGELES
The question in your title
How to gather timezone of operating system from Oracle database in string format?
is easy to answer. Run this statement:
SELECT TO_CHAR(SYSTIMESTAMP, 'tzr') FROM dual;
But I assume you have a different problem, however I don't fully understand it.
When you have a column of TIMESTAMP WITH TIME ZONE then the time zone information is available. Time zones like +02:00 do not have any daylight savings, it is always 2 hour ahead UTC, no matter if summer or winter. Timezones like Europe/Berlin or CET apply Daylight Saving Times.
If you have a time for example 2018-06-22 10:00:00+02:00 then you simply don't know whether this means Europe/Berlin with Daylight Saving Time on or Africa/Cairo which is always +02:00 hours ahead UTC - you have no possibility to retrieve such information!
If you have data in column of DATE (or TIMESTAMP) then you don't have any time zone information at all, thus you cannot convert such values to TIMESTAMP WITH TIME ZONE without further information.
Storing times in timezone of operating system is rather useless. Either store them in UTC or use data type TIMESTAMP WITH LOCAL TIME ZONE. Data in TIMESTAMP WITH LOCAL TIME ZONE are stored in DBTIMEZONE (which is recommended to be set as UTC but actually not relevant for you) and always and only shown in current user SESSIONTIMEZONE.
We are persisting a Java Calendar object into an Oracle TIMESTAMP column using the getTimeInMillis() method. Does Oracle persist this data with a time zone?
I'm assuming not as we are not using the TIMESTAMP WITH TIMEZONE data type. If not, when you query SELECT MY_TIMESTAMP FROM MYTABLE what timezone will Oracle associate with this field?
And what is the correct way to 'cast' this to the timezone you are interested in (ie: UTC)?
getTimeInMillis()
is independent of the calendar system and the timezone. So in whichever timezone you specify the time in, the time is not gonna vary and getTimeInMillis() will get the accurate time.
And on the persistence -
yes the oracle persist the data with the timezone. But it will add the timezone offset to the value saved.
So that's brings us to the next question of choosing the right
timezone -
it is ideal to choose the native timezone. As this scenario is a combination of oracle and java its best to use UTC
Cheers!
I have these two classes
class Source {
// mapped to TIMESTAMP
#Version
#Column(columnDefinition="TIMESTAMP(3) DEFAULT '2016-01-01'")
Instant myInstant;
}
class Destination {
// mapped to DATETIME
#Basic(optional=true)
Instant myInstant;
}
When using Hibernate, I assign
destination.myInstant = source.myInstant;
and then the stored value is smaller by one hour than the original - both according to the command line MySQL client and in Java. My current timezone is UTC+1, so the reason is obviously a timezone conversion.
There are a few places where this can be fixed, but I'm looking for the best practice. The server should work world-wide, so it should continue to use UTC internally, right?
Should I just change the column type to TIMESTAMP? Then, why does Instant by default map to DATETIME?
According to this article, Instant does map to TIMESTAMP, but in my case it did not. Why?
If you want to work with timezones and Java 8 I would recommend using ZonedDateTime or OffsetTimeZone (the latter being prefered when working with Hibernate). For older versions use Calendar.
When you instance it should go by default with the timezone of your computer.
Check if the database is timestamp with or without timezone.
The default you set is also without timezone, and if it is "with timezone" it should automatically add the database's offset.
I hope some of this works. Here's how I did in one of my projects.
#Column(name = "registration_time")
private OffsetDateTime registrationTime;
[...]
subscriber.setRegistrationTime(OffsetDateTime.now());
In MySQL 5 & above, TIMESTAMP values are converted from the current time zone to UTC for storage, and converted back from UTC to the current time zone for retrieval. This occurs only for the TIMESTAMP data type but not for DATETIME. This is the reason you are seeing the difference while assigning a TIMESTAMP to DATETIME. So, having both the columns of same type should work. Hibernate by default maps InstantType to database TIMESTAMP type. Though you could use it for both TIMESTAMP and DATETIME in MYSQL, they are handled differently.
I'm struggling from a couple of hours to understand what's going on with the TimeStamps in my code.
Both the Oracle DB and the java application are in PDT
Select from DB:
select id, time_stamp from some_Table where id = '3de392d69c69434eb907f1c0d2802bf0';
3de392d69c69434eb907f1c0d2802bf0 09-DEC-2014 12.45.41.354000000 PM
select id, time_stamp at time zone 'UTC' from some_Table where id = '3de392d69c69434eb907f1c0d2802bf0';
3de392d69c69434eb907f1c0d2802bf0 09-DEC-2014 12.45.41.354000000 PM
The field in the Oracle database is TimeStamp, hence no timezone information is stored.
Timestamp dbTimeStamp = dbRecord.getLastLoginTime();
System.out.println(dbTimeStamp.toString()); // 2014-12-09 12:16:50.365
System.out.println(dbTimeStamp.getTime()); // 1418156210365 --> Tue Dec 09 2014 20:16:50 UTC?
According to the documentation, getTime()
Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT
represented by this Timestamp object.
Why are 8 hours (PDT - UTC) of extra time added to the response of getTime() ?
TimeStamp.toString() internally uses Date.getHours() whose javadoc states:
Returns the hour represented by this Date object. The
returned value is a number (0 through 23)
representing the hour within the day that contains or begins
with the instant in time represented by this Date
object, as interpreted in the local time zone.
So toString is using your local time zone whereas getDate doesn't.
These two are consistent with each other. The getTime() method gives you the absolute millicecond value, which you chose to interpret in UTC. The toString() method gives you that same millisecond value interpreted in the associated timezone. So it is not getTime() which is adding the time, but toString() which is subtracting it. This is not really documented, but that is how it behaves.
The most important takeaway should be not to rely on Timestamp.toString because it is misleading. The whole timezone mechanism within Date (and Timestamp is a subclass) has been deprecated a long time ago. Instead use just the getTime() value and have it formatted by other APIs, such as Java 8 Date/Time API.
Update
Apparently, the toString() output is actually the correct one, which for me is just one small addition to the thick catalog of all things wrong with Java's date/time handling. You probably receive the timestamp from the database as a formatted string, not the millisecond value. JDBC then parses that into a millisecond value according to the timezone associated with the Timestamp instance, such that the output of toString() matches what was returned by the database, and the actual millisecond value being secondary.
Thanks to the answers above and the references on SO. This answer finally helped me in understanding where I was going wrong in understanding TimeStamps.
Qouting from the linked answer
Note: Timestamp.valueOf("2010-10-23 12:05:16"); means "create a timestamp with the given time in the default timezone".
TimeStamp represents an instant of time. By default, that instant of time in the current Timezone.
The timestamps being written to the DB were UTC instants. i.e. current UTC time was being written. Hence, no matter where the application was deployed, the value being written to the DB was the same TimeStamp.
However, while reading the TimeStamp generated assumes the default TimeZone as read from the deployment JVM. Hence, the value read was the instant in PST timezone. The actual UTC Value being 8 hours more than the PST time. Hence, the difference.
TimeStamp.getTime() returns milliseconds from UTC.
TimeStamp.toString() returns the representation of time in the current TimeZone. Thanks #marko-topolnik
To take an example,
Value in the DB : 2014-12-09 12:16:50.365
When this value is read in a TimeStamp, the instant is 2014-12-09 12:16:50.365 in PST
Convert this to UTC, it would be 2014-12-09 20:16:50
Hence, the solution was to add the TimeZone offset to the values read from the database to get the instants as UTC TimeStamps.
The key here was "TimeStamp is a time instant without TimeZone information. The timestamp is assumed to be relative to the default system TimeZone." - It took me a really long while to comprehend this.