4

From what I understand, java.util.Date stores date as milliseconds from Jan 1, 1970, 00:00...

So, I've tried this code below:

public void testDateFormatBehavior()
{
    DateFormat dfNoDay = new SimpleDateFormat(
            "MMM d H:m:s zzz yyyy"
            );
    // This one should be correct as IST = GMT+5.30
    String expStrDateBegIST1 = "Jan 01 05:30:01 IST 1970";
    // Instead, this one seems to do the conversion to
    // Jan 01 00:00:00 GMT 1970
    String expStrDateBegIST2 = "Jan 01 02:00:01 IST 1970";
    String expStrDateBegUTC = "Jan 01 00:00:01 GMT 1970";
    String expStrDateBegCET = "Jan 01 01:00:00 CET 1970";
    // Should convert to Jan 01 06:00:00 GMT 1970 as CST = GMT-6
    String expStrDateBegCST = "Jan 01 00:00:00 CST 1970"; 
    // This is EST, which is GMT+6... 
    String expStrDateBegEST = "Jan 01 10:00:00 EST 1970"; 
    try {
        Date dBegIST1 = dfNoDay.parse(expStrDateBegIST1);
        Date dBegIST2 = dfNoDay.parse(expStrDateBegIST2);
        Date dBegUTC = dfNoDay.parse(expStrDateBegUTC);
        Date dBegCET = dfNoDay.parse(expStrDateBegCET);
        Date dBegCST = dfNoDay.parse(expStrDateBegCST);
        Date dBegEST = dfNoDay.parse(expStrDateBegEST);
        System.out.println("IST1 milliseconds: " + dBegIST1.getTime());
        System.out.println("IST2 milliseconds: " + dBegIST2.getTime());
        System.out.println("UTC milliseconds: " + dBegUTC.getTime());
        System.out.println("CET milliseconds: " + dBegCET.getTime());
        System.out.println("CST milliseconds: " + dBegCST.getTime());
        System.out.println("EST milliseconds: " + dBegEST.getTime());
    } catch (ParseException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

The output:

IST1 milliseconds: 12601000
IST2 milliseconds: 1000
UTC milliseconds: 1000
CET milliseconds: 0
CST milliseconds: 21600000
EST milliseconds: 0

UTC milliseconds line is correct as we specified 00:00:01 seconds starting from Jan 1 1970. CET is correct. CST is correct as that amount of milliseconds is 6 hours after Jan 1 1970.

However, IST conversion is weird.

http://wwp.greenwichmeantime.com/to/ist/to-gmt/index.htm

IST seems to be GMT + 5:30. In my Java code, it thinks it is GMT + 2:00 instead.

Also, EST is incorrect. It thinks EST is GMT+10:00, not GMT+6:00. GMT+10:00 is AEST, not EST. http://wwp.greenwichmeantime.com/time-zone/australia/time-zones/eastern-standard-time/

Is there something I'm doing wrong?

2 Answers2

5

The Timezone javadoc explains:

Three-letter time zone IDs

For compatibility with JDK 1.1.x, some other three-letter time zone IDs (such as "PST", "CTT", "AST") are also supported. However, their use is deprecated because the same abbreviation is often used for multiple time zones (for example, "CST" could be U.S. "Central Standard Time" and "China Standard Time"), and the Java platform can then only recognize one of them.


The problem is that you're assuming that time zone abbreviations consistently designate a particular timezone.

"IST" is ambiguous : Indian standard time / Irish standard time / Israeli standard time. Similarly, "EST" can mean Eastern US standard time or Eastern Australian standard time.


Joda-time's timezone list shows the unambiguous Olson names and the present timezone offset. You would be better off using Joda-time instead of java.util classes, and using the Olson names to identify timezones.

Should I use Java date and time classes or go with a 3rd party library like Joda Time? discusses the reasons why many choose to use Joda-time.

Community
  • 1
  • 1
Mike Samuel
  • 118,113
  • 30
  • 216
  • 245
0

tl;dr

ZonedDateTime.parse(   // Parse string as `ZonedDateTime` object.
    "1969-12-31T19:00-05:00[America/New_York]"   // This zone on that date is five hours behind UTC. 
)                      // So last day of 1969 at 7 PM there is simultaneously first moment of January 1, 1970 in UTC (00:00:00).
.toInstant()           // .toString() --> 1970-01-01T00:00:00Z
.toEpochMilli()        // --> zero seconds since epoch, as we are *at* the epoch.

0

Details

The accepted Answer by Samuel is correct.

Avoid legacy date-time classes

The modern approach uses the java.time classes that supplanted the troublesome old legacy date-time classes such as Date.

Time zone

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( "Africa/Tunis" ) ;  

ISO 8601

Use standard ISO 8601 formats when serializing date-time values to text.

The java.time classes use the ISO 8601 formats by default when parsing/generating strings. So no need to specify a formatting pattern.

The ZonedDateTime class wisely extends the standard format by appending the name of the time zone in square brackets.

String input = "1970-01-01T05:30:01+05:30[Asia/Kolkata]" ;  // "Jan 01 05:30:01 IST 1970"
ZonedDateTime zdt = ZonedDateTime.parse( input ) ;
String output = zdt.toString() ;  // Generate string in standard ISO 8601 format.

1970-01-01T05:30:01+05:30[Asia/Kolkata]

To view that same moment through the lens of the wall-clock time of UTC, extract a Instant. We expect one second into the day of UTC, as India time on that date is five and a half hours ahead of UTC.

Instant instant = zdt.toInstant() ;

instant.toString(): 1970-01-01T00:00:01Z

I suggest avoiding tracking time as a count-from-epoch as it is ambiguous and error-prone. But if you insist, you can get a count of milliseconds since the epoch of first moment of 1970 in UTC (1970-01-01T00:00:00Z).

In this case we expect a result of one thousand milliseconds (1 second).

long millisSinceEpoch = instant.toEpochMilli() ;

1000

To see a moment in a time zone of the east coast of North America:

String input = "1970-01-01T10:00:00-05:00[America/New_York]";  // "Jan 01 10:00:00 EST 1970"
ZoneId z = ZoneId.of( "America/New_York" );
ZonedDateTime zdt = ZonedDateTime.parse( input );
Instant instant = zdt.toInstant();
long millisSinceEpoch = instant.toEpochMilli();

zdt.toString(): 1970-01-01T10:00-05:00[America/New_York]

instant.toString(): 1970-01-01T15:00:00Z

millisSinceEpoch: 54000000

Or another moment in same zone. Here we try 7 PM on the night before the epoch reference. The America/New_York zone on that date was five hours behind UTC. So when adjusted to UTC, we land right on the stroke of midnight, the first moment of the epoch reference date, 1970-01-01T00:00:00Z. That means 1969-12-31T19:00-05:00[America/New_York] is zero seconds from the epoch.

String input = "1969-12-31T19:00-05:00[America/New_York]" ;
ZoneId z = ZoneId.of( "America/New_York" );
ZonedDateTime zdt = ZonedDateTime.parse( input );
Instant instant = zdt.toInstant();
long millisSinceEpoch = instant.toEpochMilli();

zdt.toString(): 1969-12-31T19:00-05:00[America/New_York]

instant.toString(): 1970-01-01T00:00:00Z

millisSinceEpoch: 0


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.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154