Timestamp.getTime() considers the timestamp value as time in system time zone

10,270

Solution 1

I cannot really assess your understanding, but here's mine:

The Timestamp class inherits from Date. Date is - in line with what you quoted from the Javadoc - just a wrapper for a long value. What happens is that the String is converted (somehow - please update the codefragment if you want to share your mechanism) to a time value. This implicitly or explicitly uses a time zone, but the resulting long value is independent of a time zone, it is the same as when you would have called System.currentTimeMillis() in the time zone used in the conversion at the given time. If you want to control the time zone used for conversion, you can use a SimpleDateFormat and set the time zone as follows:

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
try {
  simpleDateFormat.parse("2016-01-08 08:03:52.0");
} catch (ParseException e) {
  // handle the error here
}

Following your edit (which is missing the definition of the function dateInLong), I have created the following test class in which I have added some console output:

public class test {
  @Test
  public void test() throws ParseException {
    Calendar curCal = new GregorianCalendar(TimeZone.getDefault());
    curCal.setTimeInMillis(System.currentTimeMillis());
    System.out.println("curCal 1:   " + curCal.getTimeInMillis());

    TimeZone fromTz = TimeZone.getDefault();
    curCal.setTimeZone(fromTz);
    System.out.println("curCal 2:   " + curCal.getTimeInMillis());

    TimeZone gmtTZ = TimeZone.getTimeZone("GMT");
    Calendar toCal = new GregorianCalendar(gmtTZ);
    toCal.setTimeInMillis(curCal.getTimeInMillis());

    Date dd = toCal.getTime();
    System.out.println("dd:         " + dd.getTime());
    SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss a", Locale.US);
    format.setTimeZone(gmtTZ);
    String ff = format.format(dd);

    long time = dateInLong(ff, "dd/MM/yyyy hh:mm:ss a");
    System.out.println("time:       " + time);
    java.sql.Timestamp curTimeInGMT = new java.sql.Timestamp(time);
    System.out.println("curTimeGMT: " + curTimeInGMT.getTime());
  }

  private long dateInLong(String dateString, String formatStr) throws ParseException {
    SimpleDateFormat format = new SimpleDateFormat(formatStr);
    return format.parse(dateString).getTime();
  }
}

It produces the following output:

curCal 1:   1452603245943
curCal 2:   1452603245943
dd:         1452603245943
time:       1452599645000
curTimeGMT: 1452599645000

As you can see, the only occasion the inner long value changes is when the date is converted to and from a String (i.e. after invoking dateInLong) namely due to the use of two different time zones (to String: GMT, from String: default - CET here). As long as you pass the inner long value around, the moment in time stays the same - regardless of whether it is wrapped by a Calendar or a descendant of Date).

Solution 2

Two points will make this work much easier:

  • Work with date-time objects rather than strings.
    You should be using JDBC to extract java.sql.Timestamp objects from your database rather than String representations of those date-time values.
  • Use the java.time framework built into Java 8 and later.
    Avoid the old java.util.Date/.Calendar classes.

First we must either specify a formatter by which to parse your input string, or alter it to meet the ISO 8601 standard used by default in java.time. The ISO 8601 format is close to SQL format, replacing the space in the middle with a T.

String input = "2016-01-08 08:03:52.0";
String inputIso8601 = input.replace ( " ", "T" );

Parse that string as a local date-time, which means any locality. The input string lacks any time zone or offset-from-UTC info, so we start with a local and will apply an assumed time zone afterwards.

LocalDateTime localDateTime = LocalDateTime.parse ( inputIso8601 );

Let's apply the assumed time zone. I am arbitrarily choosing Montréal but obviously you need to know and use whatever time zone was intended for that string input. If you are sure that string meant UTC, use the ZoneOffset.UTC.

ZoneId zoneId = ZoneId.of ( "America/Montreal" ); // Or perhaps ZoneOffset.UTC constant.
ZonedDateTime zdt = ZonedDateTime.of ( localDateTime, zoneId );

Now we are ready to convert to a java.sql.Timestamp object, to be sent to the database. That old class has a new method, for converting to/from java.time objects. The conversion requires an Instant object which is a moment on the timeline in UTC. We can extract an Instant from our ZonedDateTime.

Instant instant = zdt.toInstant ( );
java.sql.Timestamp ts = java.sql.Timestamp.from ( instant );

Dump to console.

System.out.println ( "input: " + input + " in ISO 8601: " + inputIso8601 + " is localDateTime: " + localDateTime + " in zoneId: " + zoneId + " is zdt: " + zdt + " gives instant: " + instant + " which converts to java.sql.Timestamp ts: " + ts );

input: 2016-01-08 08:03:52.0 in ISO 8601: 2016-01-08T08:03:52.0 is localDateTime: 2016-01-08T08:03:52 in zoneId: America/Montreal is zdt: 2016-01-08T08:03:52-05:00[America/Montreal] gives instant: 2016-01-08T13:03:52Z which converts to java.sql.Timestamp ts: 2016-01-08 05:03:52.0

Read that console output carefully. Note the time-of-day on ts. This shows the unfortunate behavior of the java.sql.Timestamp method toString to silently apply the JVM’s current default time zone when generating the textual representation of the date-time value. My JVM here had a default time zone of America/Los_Angeles. So the time-of-day was adjusted (confusingly).


About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

With a JDBC driver complying with JDBC 4.2 or later, you may exchange java.time objects directly with your database. No need for strings or java.sql.* classes.

Where to obtain the java.time classes?

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

Share:
10,270
Matchendran
Author by

Matchendran

Updated on June 28, 2022

Comments

  • Matchendran
    Matchendran almost 2 years

    Lets say I have a timestamp value.

    Edit

    Calendar curCal = new GregorianCalendar(TimeZone.getDefault());
        curCal.setTimeInMillis(System.currentTimeMillis());
    
        TimeZone fromTz = TimeZone.getDefault();            
        curCal.setTimeZone(fromTz);            
    
        TimeZone gmtTZ = TimeZone.getTimeZone("GMT");            
        Calendar toCal = new GregorianCalendar(gmtTZ);
        toCal.setTimeInMillis(curCal.getTimeInMillis());
    
        Date dd = toCal.getTime();
        SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss a",Locale.US);
        format.setTimeZone(gmtTZ);
        String ff = format.format(dd);
    
        java.sql.Timestamp curTimeInGMT = new java.sql.Timestamp(dateInLong(ff, "dd/MM/yyyy hh:mm:ss a"));
    

    Now I am getting the milliseconds value of the above time using getTime();

    Long l = t.getTime();
    

    Definition of the getTime() method as per Java docs is Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT represented by this Timestamp object.

    So, from what I have understood by testing so many times, getTime() will give the milliseconds difference between the given time and January 1, 1970, 00:00:00 GMT.

    For getTime() to get the difference from January 1, 1970, 00:00:00 GMT, it need another GMT time. So it needs to convert the given time to GMT time.For that conversion it needs the timezone of the given time. It will consider timezone of the given time as SYSTEM TIME ZONE and it will get the corresponding GMT time and then it will find the difference between the two GMT time and it will return the difference.

    Is my understanding correct?

  • Remigius Stalder
    Remigius Stalder over 8 years
    No problem, I only hope my answer helps to shed some light on the issue.
  • Matchendran
    Matchendran over 8 years
    Sorry for that timestamp value.I copied the time which is stored in the table and pasted it here to give the understanding. We are not declaring the time stamp value like that. My doubt is very simple. When we call getTime(), is it internally converting the given time to GMT time and gets the difference or it just consider the given time as GMT time and gets the difference. Because to find a difference from one GMT it needs another GMT time
  • Remigius Stalder
    Remigius Stalder over 8 years
    The getTime() does not perform any conversion, it just returny the long vlaue. Also, there is no TZ associated with Date nor TimeZone. Again, it might be helpful if you could share how you get the TimeZone instance.
  • Matchendran
    Matchendran over 8 years
    I added the code for getting the timestamp object which we were discussing. Using this I'll get the current GMT time. P.S : Code not written by me. Its a code in our project