62

Code:

 SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z");
    sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
    System.out.println(new Date());
    try {
        String d = sdf.format(new Date());
        System.out.println(d);
        System.out.println(sdf.parse(d));
    } catch (Exception e) {
        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
    }

Output:

Thu Aug 08 17:26:32 GMT+08:00 2013
2013.08.08 09:26:32 GMT
Thu Aug 08 17:26:32 GMT+08:00 2013

Note that format() formats the Date correctly to GMT, but parse() lost the GMT details. I know I can use substring() and work around this, but what is the reason underlying this phenomenon?

Here is a duplicate question which doesn't have any answers.

Edit: Let me put the question in another way, what is the way to retrieve a Date object so that its always in GMT?

Community
  • 1
  • 1
Achow
  • 8,600
  • 6
  • 39
  • 49
  • The parse is the same as the first syso, don't you mean the format lost the GMT segment of the String? – Kevin Bowersox Aug 08 '13 at 09:39
  • 4
    What do you mean "the parse lost the GMT"? You give it a date in GMT+0800 and tell it to format it in the timezone GMT, so it does that (note that the time changed). That's exactly what you asked it to do. – T.J. Crowder Aug 08 '13 at 09:39
  • 2
    You are setting the timezone on SimpleDateFormat and is not linked to Date. So println(date) will not have a reference to the set timezone in SimpleDateFormat – Tobrun Aug 08 '13 at 09:40
  • @T.J.Crowder Isn't `Date` based off of `GMT`? So if you specify `GMT` as the timezone, shouldn't the time segments of the printed dates be the same? Just asking for my own understanding. – Kevin Bowersox Aug 08 '13 at 09:43
  • Why doesn't format and parse return the same value in GMT? I am aware that "format" changed the time, I can see it, but not the "parse" – Achow Aug 08 '13 at 09:45
  • 3
    You have a confusion about Date and DateFormat, Date object is always same no matter which time zone you are. DateFormat object is used to format the date where you can specify timezone information. When you called the parse method your time value has changed based on your timezone – Shamim Ahmmed Aug 08 '13 at 09:46
  • 5
    @KevinBowersox: Right, `Date` has no timezone. But when you use `Date#toString`, it uses the JVM's current timezone for formatting. – T.J. Crowder Aug 08 '13 at 09:52
  • 1
    @T.J.Crowder, so what you are saying is that its just the toString which takes the JVM's timezone and prints it accordingly. But what happens when I pass the Date object to a database, how are the bits sent then? There would again be some Date formatting ,I presume?So how do I assure,that I always have the same date in GMT or UTC, wherever I run the code, be it in NY or EU or JP – Achow Aug 08 '13 at 09:57
  • The question you were linking to has now got a good answer, so I am closing this one as a duplicate. – Ole V.V. Dec 05 '21 at 09:57

3 Answers3

67

All I needed was this :

SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));

SimpleDateFormat sdfLocal = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");

try {
    String d = sdf.format(new Date());
    System.out.println(d);
    System.out.println(sdfLocal.parse(d));
} catch (Exception e) {
    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
}

Output : slightly dubious, but I want only the date to be consistent

2013.08.08 11:01:08
Thu Aug 08 11:01:08 GMT+08:00 2013
cheb1k4
  • 2,316
  • 7
  • 26
  • 39
Achow
  • 8,600
  • 6
  • 39
  • 49
  • 4
    would you help me to understand the answer? – Reprator May 06 '16 at 09:19
  • 1
    a `Date` just stores the milliseconds since 1970-01-01 00:00:00 UTC, but no information about a timezone, therefore `d` is correct. When you convert a `Date` to a string, which happens implicitly in `println`, it will be formatted using the default/local timezone, which means, that what happend was: new Date() -> formatted to String in GMT -> parsed as String in GMT -> (same value as new Date()) formatted by implicit `toString()` in `println()` in local timezone. – Thraidh Feb 14 '17 at 15:18
  • It is not working for me. I wanted time in EST format. It is showing 2019-06-26T00:53:20Z and Wed Jun 26 00:53:20 IST 2019. Time are fine but IST should be EST here. – TheNightsWatch Jun 26 '19 at 06:06
9

tl;dr

what is the way to retrieve a Date object so that its always in GMT?

Instant.now() 

Details

You are using troublesome confusing old date-time classes that are now supplanted by the java.time classes.

Instant = UTC

The Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction).

Instant instant = Instant.now() ; // Current moment in UTC.

ISO 8601

To exchange this data as text, use the standard ISO 8601 formats exclusively. These formats are sensibly designed to be unambiguous, easy to process by machine, and easy to read across many cultures by people.

The java.time classes use the standard formats by default when parsing and generating strings.

String output = instant.toString() ;  

2017-01-23T12:34:56.123456789Z

Time zone

If you want to see that same moment as presented in the wall-clock time of a particular region, apply a ZoneId to get a ZonedDateTime.

Specify a proper time zone name in the format of continent/region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 3-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).

ZoneId z = ZoneId.of( "Asia/Singapore" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;  // Same simultaneous moment, same point on the timeline.

See this code live at IdeOne.com.

Notice the eight hour difference, as the time zone of Asia/Singapore currently has an offset-from-UTC of +08:00. Same moment, different wall-clock time.

instant.toString(): 2017-01-23T12:34:56.123456789Z

zdt.toString(): 2017-01-23T20:34:56.123456789+08:00[Asia/Singapore]

Convert

Avoid the legacy java.util.Date class. But if you must, you can convert. Look to new methods added to the old classes.

java.util.Date date = Date.from( instant ) ;

…going the other way…

Instant instant = myJavaUtilDate.toInstant() ;

Date-only

For date-only, use LocalDate.

LocalDate ld = zdt.toLocalDate() ;

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.

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.

Copernicus
  • 29
  • 1
  • 10
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
8

OP's solution to his problem, as he says, has dubious output. That code still shows confusion about representations of time. To clear up this confusion, and make code that won't lead to wrong times, consider this extension of what he did:

public static void _testDateFormatting() {
    SimpleDateFormat sdfGMT1 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
    sdfGMT1.setTimeZone(TimeZone.getTimeZone("GMT"));
    SimpleDateFormat sdfGMT2 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z");
    sdfGMT2.setTimeZone(TimeZone.getTimeZone("GMT"));

    SimpleDateFormat sdfLocal1 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
    SimpleDateFormat sdfLocal2 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z");

    try {
        Date d = new Date();
        String s1 = d.toString();
        String s2 = sdfLocal1.format(d);
        // Store s3 or s4 in database.
        String s3 = sdfGMT1.format(d);
        String s4 = sdfGMT2.format(d);
        // Retrieve s3 or s4 from database, using LOCAL sdf.
        String s5 = sdfLocal1.parse(s3).toString();
        //EXCEPTION String s6 = sdfLocal2.parse(s3).toString();
        String s7 = sdfLocal1.parse(s4).toString();
        String s8 = sdfLocal2.parse(s4).toString();
        // Retrieve s3 from database, using GMT sdf.
        // Note that this is the SAME sdf that created s3.
        Date d2 = sdfGMT1.parse(s3);
        String s9 = d2.toString();
        String s10 = sdfGMT1.format(d2);
        String s11 = sdfLocal2.format(d2);
    } catch (Exception e) {
        e.printStackTrace();
    }       
}

examining values in a debugger:

s1  "Mon Sep 07 06:11:53 EDT 2015" (id=831698113128)    
s2  "2015.09.07 06:11:53" (id=831698114048) 
s3  "2015.09.07 10:11:53" (id=831698114968) 
s4  "2015.09.07 10:11:53 GMT+00:00" (id=831698116112)   
s5  "Mon Sep 07 10:11:53 EDT 2015" (id=831698116944)    
s6  -- omitted, gave parse exception    
s7  "Mon Sep 07 10:11:53 EDT 2015" (id=831698118680)    
s8  "Mon Sep 07 06:11:53 EDT 2015" (id=831698119584)    
s9  "Mon Sep 07 06:11:53 EDT 2015" (id=831698120392)    
s10 "2015.09.07 10:11:53" (id=831698121312) 
s11 "2015.09.07 06:11:53 EDT" (id=831698122256) 

sdf2 and sdfLocal2 include time zone, so we can see what is really going on. s1 & s2 are at 06:11:53 in zone EDT. s3 & s4 are at 10:11:53 in zone GMT -- equivalent to the original EDT time. Imagine we save s3 or s4 in a data base, where we are using GMT for consistency, so we can have times from anywhere in the world, without storing different time zones.

s5 parses the GMT time, but treats it as a local time. So it says "10:11:53" -- the GMT time -- but thinks it is 10:11:53 in local time. Not good.

s7 parses the GMT time, but ignores the GMT in the string, so still treats it as a local time.

s8 works, because now we include GMT in the string, and the local zone parser uses it to convert from one time zone to another.

Now suppose you don't want to store the zone, you want to be able to parse s3, but display it as a local time. The answer is to parse using the same time zone it was stored in -- so use the same sdf as it was created in, sdfGMT1. s9, s10, & s11 are all representations of the original time. They are all "correct". That is, d2 == d1. Then it is only a question of how you want to DISPLAY it. If you want to display what is stored in DB -- GMT time -- then you need to format it using a GMT sdf. Ths is s10.

So here is the final solution, if you don't want to explicitly store with " GMT" in the string, and want to display in GMT format:

public static void _testDateFormatting() {
    SimpleDateFormat sdfGMT1 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
    sdfGMT1.setTimeZone(TimeZone.getTimeZone("GMT"));

    try {
        Date d = new Date();
        String s3 = sdfGMT1.format(d);
        // Store s3 in DB.
        // ...
        // Retrieve s3 from database, using GMT sdf.
        Date d2 = sdfGMT1.parse(s3);
        String s10 = sdfGMT1.format(d2);
    } catch (Exception e) {
        e.printStackTrace();
    }       
}
ToolmakerSteve
  • 18,547
  • 14
  • 94
  • 196