I am using JDK 8 and I played with ZonedDateTime and Timestamp a lot. But still I am not able to get a solution for the problem I am facing.
Lets say I get a formatted Timestamp in GMT (UTC) and my server is located somewhere. Lets say it's set to Asia/Calcutta TimeZone (whose ZoneOffset is +05:30).
How to basically add/subtract my time zone offset to the Timestamp?
Input : 2017-09-13 13:10:30.333
Output: 2017-09-13 18:40:30.333
Explanation : In the input, the timezone offset which is 5hrs 30mts has been added to the input. Also, the input is of type Timestamp and so is the output.
I use ZoneID.systemDefault() function to retrieve the TimeZone known to JVM. In my case, the TimeZone turned out to be Asia/Calcutta.
解决方案
A java.sql.Timestamp doesn't have any timezone information. It just has one value*: the number of nanoseconds since unix epoch (1970-01-01T00:00Z or "January 1st 1970 at midnight in UTC"). You don't convert this value to a timezone because this number is not attached to any specific timezone.
When printing the Timestamp, it calls the toString() method, and this prints the corresponding date and time in the JVM default timezone. But the Timestamp itself doesn't have a timezone attached to it.
Take a look at this article for more info. It talks about java.util.Date, but the concept is the same: these objects don't carry any format or timezone information, so you can't convert them between timezones. What you can change is the representation of those values in different zones.
Example: if I take the 1505308230333000000 as the number of nanoseconds since epoch. This same number represents 13:10 in UTC, 10:10 in São Paulo, 14:10 in London, 22:10 in Tokyo, 18:40 in Calcutta, and so on.
The Timestamp class just keeps the big number value*. This same value corresponds to a different date/time in each timezone, but its value is always the same for everyone.
That's why converting the Timestamp between different zones makes no sense: your Timestamp object already represents both 13:10 in UTC and 18:40 in Calcutta (it contains the big number value that corresponds to these date/times in the respective timezones - changing this value will change the respective local date/times for all timezones).
What you can change is the String representation of this big number value (the corresponding local date/time in a specified timezone).
If you want to get a String with the corresponding date and time in another timezone, though, you can use the java.time classes.
First you need to convert the java.sql.Timestamp to a java.time.Instant, then convert it to a timezone, resulting in a java.time.ZonedDateTime. Finally, I format it to a String, using a java.time.format.DateTimeFormatter:
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
// convert the timestamp to a zoneddatetime
ZonedDateTime z = timestamp.toInstant().atZone(ZoneId.of("Asia/Calcutta"));
// format it
System.out.println(z.format(fmt)); // 2017-09-13 18:40:30.333
The output is:
2017-09-13 18:40:30.333
If you already know what timezone to use (in this case, Asia/Calcutta), don't use the default timezone (ZoneID.systemDefault()) - of course you can use it if you want, just keep in mind that it can be changed without notice, even at runtime, so it's better to always make it explicit which one you're using.
*Actually, the Timestamp keeps the big number value in two fields: one for the seconds and another for the nanoseconds value. But that's an implementation detail, that doesn't change the concept that this value is not attached to any timezone and so there's no point in converting the Timestamp between zones