Oracle DB Timestamp to Java Timestamp : Confusion - java

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.

Related

Does the TIMESTAMP datatype of Oracle store dates adjusted as per the local timezone?

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.

How can I get the UTC-converted Java timestamp of current local time?

Could somebody please help with getting UTC-converted Java timestamp of current local time?
The main goal is to get current date and time, convert into UTC Timestamp and then store in Ignite cache as a Timestamp yyyy-MM-dd hh:mm:ss[.nnnnnnnnn].
My attempt was Timestamp.from(Instant.now()). However, it still considers my local timezone +03:00. I am getting '2020-02-20 10:57:56' as a result instead of desirable '2020-02-20 07:57:56'.
How can I get UTC-converted Timestamp?
You can do it like this :
LocalDateTime localDateTime = Instant.now().atOffset(ZoneOffset.UTC).toLocalDateTime();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
System.out.println(localDateTime.format(formatter));
Don’t use Timestamp
You most probably don’t need a Timestamp. Which is good because the Timestamp class is poorly designed, indeed a true hack on top of the already poorly designed Date class. Both classes are also long outdated. Instead nearly 6 years ago we got java.time, the modern Java date and time API. Since JDBC 4.2 this works with your JDBC driver too, and also with your modern JPA implementation.
Use OffsetDateTime
For a timestamp the recommended datatype in your database is timestamp with time zone. In this case in Java use an OffsetDateTime with an offset of zero (that is, UTC). For example:
OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC);
System.out.println(now);
PreparedStatement statement = yourDatabaseConnection
.prepareStatement("insert into your_table (tswtz) values (?);");
statement.setObject(1, now);
int rowsInserted = statement.executeUpdate();
Example output from the System.out.println() just now:
2020-02-22T13:04:06.320Z
Or use LocalDateTime if your database timestamp is without time zone
From your question I get the impression that the datatype in your database is timestamp without time zone. It’s only the second best option, but you can pass a LocalDateTime to it.
LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC);
The rest is the same as before. Example output:
2020-02-22T13:05:08.776
If you do need an old-fashioned java.sql.Timestamp
You asked for a Timestamp in UTC. A Timestamp is always in UTC. More precisely, it’s a point in time independent of time zone, so converting it into a different time zone does not make sense. Internally it’s implemented as a count of milliseconds and nanoseconds since the epoch. The epoch is defined as the first moment of 1970 in UTC.
The Timestamp class is a confusing class though. One thing that might have confused you is when you print it, thereby implicitly calling its toString method. The toString method uses the default time zone of the JVM for rendering the string, so prints the time in your local time zone. Confusing. If your datatype in SQL is timestamp without time zone, your JDBC driver most probably interprets the Timestamp in your time zone for the conversion into an SQL timestamp. Which in your case is incorrect since your database uses UTC (a recommended practice). I can think of three possible solutions:
Some database engines allow you to set a time zone on the session. I haven’t got any experience with it myself, it’s something I have read; but it may force the correct conversion from your Java Timestamp to your SQL timestamp in UTC to be performed.
You may make an incorrect conversion in Java to compensate for the opposite incorrect conversion being performed between Java and SQL. It’s a hack, not something that I would want to have in my code. I present it as a last resort.
LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC);
Timestamp ts = Timestamp.valueOf(now);
System.out.println(ts);
2020-02-22 13:05:08.776
You notice that it only appears to agree with the UTC time above. It‘s the same result you get from the answer by Vipin Sharma except (1) my code is simpler and (2) you’re getting a higher precision, fraction of second is included.
Have you database generate the current timestamp in UTC instead of generating it in Java.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Related question: Java - Convert java.time.Instant to java.sql.Timestamp without Zone offset
Despite what the Ignite docs say you can pass in a 24hr time.
The docs says yyyy-MM-dd hh:mm:ss[.nnnnnnnnn] so you may be tempted in your code to use this to format your dates but this will lead to times after midday being wrong. Instead, format your dates with yyyy-MM-dd HH:mm:ss[.nnnnnnnnn].
Notice the upper case HH. If you're using ZonedDateTime or Joda's DateTime when you call now with UTC now(UTC) and then toString("yyyy-MM-dd HH:mm:ss") will store the correct time in UTC.

DateTime to java.sql.timestamp conversion is adding +1 hour

I am calling my rest api with following url localhost:8080/api/2016-05-30T10:30:00-05:00/3
The api receives date as String and then convert it into jodatime datetime object like dateTime = DateTime.parse(date); ... debugging this code shows that its resulting in expected value.
However, when i am converting this date to java.sql.timestamp like Timestamp ts = new Timestamp(dateTime.getMillis()); ... the resulting time is 2016-05-30 11:30:00.0 ... why is it adding +1 hour to the time and whats the proper way to convert ?
SOME BACKGROUND
I have saved the time as timestamp in sql table. with timezone (as a string +4:00 or -5:00 for example) in a separate column.
I would receive an ISO8601 time in my url path parameter and based on that I have to fetch the record from db. For that, I will be using two comparison. 1 to match the time and 2 to match the timezone.
The ISO 8601 states that
... the time in New York in winter is UTC−05:00
So it seems daylight saving time is not included in the date string, which means what you get is the correct time, as per the defined timezone (-05:00). New York summer time should be -04:00, as DST is not part of ISO 8601.
You could add an extra DST column in your database or add it as a parameter, or go through the process of checking whether you are in DST, which varies country to country (e.g. Qatar does not uses it) and year by year (e.g Russia has abandoned DST a few years ago), so it's not a viable option...
EDIT:
Another option would be for you to test if the Timezone is currently in DST, as indicated in another answer. You could then apply the -1h offset.
Note that Joda has a minusHours() method you can use, if inDaylightTime() returns true.

Java Date toString contains a timezone... Why?

I wrote some code today in VB6 which would get me the number of milliseconds since 1/1/1970, so I could then send the value off to a java application which would parse that value like new Date(Long.parse(milliseconds)). I understand that the milliseconds the Date(Long) is looking for is the number of milliseconds since epoch in GMT. The machine I am running on is on CDT here in the US. When I get the toString value of the date parsed from the milliseconds, this is the value I get:
Tue Aug 11 15:40:50 CDT 2015
Is the CDT just there because the local machines timezone is CDT? I just think its a little weird that the constructor for Date would assume that a date derived from the milliseconds since epoch in GMT would implicitly be in the local machines timezone, rather than being offset (in this case) by -5 hours.
Is the CDT just there because the local machines timezone is CDT?
The timezone for display purposes is based on the default time zone.
The millis in the Date is relative to epoch and it doesn't have a time zone of its own.
It is taken since 00:00 1/1/1970 GMT or if you prefer 17:00 12/31/1969 CDT.
would implicitly be in the local machines timezone
The use of the local time zone is for display purposes only. Use another time zone or serialize the Date and send it to a machine in another timezone and it will use the local timezone again.
You're correct it's showing CDT in the toString() because your locale indicates that is the correct timezone for you. The Date object itself doesn't care about timezones and is a glorified wrapper around the Unix epoch in milliseconds. Generally you should use toString() for debugging purposes, and use a date formatter to actually display dates to the user (optionally specifying an explicit timezone instead of the one specified by the user's locale).
The Javadoc for Date.toString() only specifies the format of the string, it doesn't actually say anything about which timezone is used. I wouldn't rely on toString() always returning the default Locale in every implementation of Java.
You can use a custom representation of a date by using the correct format
Read this post, it might help you
Change date format in a Java string
The number of milliseconds since epoch began is NOT in the timezone known as UTC. It is not in any time zone. The epoch value is the same in ALL time zones. That epoch millisecond value is the same in London as it is in New York or San Francisco for that instant in time.
The Date function always uses the current default time zone if you don't set one. So for you, yes, the local machines timezone is CDT. Again, the epoch value in CDT is exactly the same as everywhere else on the planet, so there is no real reason to pick UTC when your machine thinks it is in central time.

How to read timezone from 'timestamp with time zone' column?

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.

Categories