User45
User45

Reputation: 74

Simple Java String to java.util.Date conversion adds unwanted daylight saving

This is my program snippet

import java.lang.Math;
import java.util.Date;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.TimeZone;

public class Main
{

  public static void main(String[] args)
  {
    String dateTime = "2017-03-12 02:46:00";

    // convert string to java.util.Date
    try {
        SimpleDateFormat e = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d = e.parse(dateTime);
        System.out.println(d);
    } catch (ParseException e) {
        e.printStackTrace(); 
    }

  }
}

This is the output of that program

Sun Mar 12 03:46:00 PDT 2017

Expected Output

Sun Mar 12 02:46:00 PDT/PST 2017

Apparently, it is adding daylight saving time which occurs on PST at 2017-03-12 02:00:00

Few things I am bounded.

Edit: To some comment pointing me out how java.util.Date only stores long timestamp. Can you please give me a way where this function works

java.util.Date convertStringToDate(String str) {
   // code to convert String to Date
}

convertStringToDate("2017-03-12 02:46:00");

should give me 2017-03-12 02:46:00 value in Date class? I don't care about what timezone it provides. It should have that value, whatever timezone it is while printing. Again my JVM is in PST.

Upvotes: 1

Views: 1345

Answers (1)

Basil Bourque
Basil Bourque

Reputation: 339502

Use java.time, not legacy date-time classes

You are using troublesome old date-time classes such as java.util.Date that are now legacy, supplanted by the java.time classes.

LocalDateTime

2016-03-12 02:46:00 value …I don't care about what timezone it provides. It should have that value, whatever timezone it is…

If you truly want to represent that date and time-of-day without regard for time zone, use the LocalDateTime class. This class purposely ignores time zone.

To parse, adjust your input string to comply with the ISO 8601 standard formats used by the java.time classes for parsing/generating strings.

String input = "2016-03-12 02:46:00".replace( " " , "T" );
LocalDateTime ldt = LocalDateTime.parse( input );

But beware: By ignoring time zone you lose the meaning of this date+time. Without the context of a time zone, we do not know if you mean the 2 AM in Auckland NZ, or 2 AM in Kolkata India (some hours later), or 2 AM in Paris France (more hours later), or 2 AM in Montréal Québec (still more hours later). A LocalDateTime is a rough idea about possible moments, but is not actually a point on the timeline.

ZonedDateTime

This is the output of that program Sun Mar 12 03:46:00 PDT 2017

Expected Output Sun Mar 12 02:46:00 PDT/PST 2017

Now you contradict yourself.

By including the PDT or PST with your expected output, you mean a specific moment on the timeline perceived through the lens of a particular region’s wall-clock time. This contradicts your statement that you want "2016-03-12 02:46:00" regardless of time zone. It is crucial that you understand this distinction to properly handle date-time work.

If indeed the intent of the string 2016-03-12 02:46:00 is to represent a moment in the wall-clock time of the left coast of north America (as I guess you meant by PDT), then we must parse that string firstly as a LocalDateTime as it lacks any indicator of time zone, but then immediately adjust it into a time zone to get a ZonedDateTime object.

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 or PDT or PST as they are not true time zones, not standardized, and not even unique(!).

Here I arbitrarily chose America/Los_Angeles as the time zone, as your Question does not mention a specific time zone, only “PDT”.

String input = "2017-03-12 02:46:00".replace( " " , "T" );
LocalDateTime ldt = LocalDateTime.parse( input );
ZoneId z = ZoneId.of( "America/Los_Angeles" );
ZonedDateTime zdt = ldt.atZone( z );

But it just so happens that March 12 of 2017 has an anomaly. That is the day when the craziness known as Daylight Saving Time (DST) kicks in. The clocks in much of the left coast of north America at 2 AM jump to 3 AM. There is no two o’clock hour. The day is 23 hours long rather than the usual 24 hours. So your request for 2:46 is asking for a nonexistent moment, an invalid value. The design choice in java.time to resolve this conundrum is to jump forward, following the "Spring Forward" of DST. The result is in the 3 AM hour, 03:46.

See this code run live in IdeOne.com.

input: 2017-03-12T02:46:00

ldt.toString(): 2017-03-12T02:46

zdt.toString(): 2017-03-12T03:46-07:00[America/Los_Angeles]

Note the 2 AM hour becomes the 3 AM hour in that output.

A reasonable person could make arguments for a different design choice in handling this anomaly, such as throwing an Exception. But this is how java.time works. Study the class doc and be sure you understand the behavior on this important topic.

If you want to detect such an anomaly, call toLocalDateTime on the ZonedDateTime object, and compare to the first LocalDateTime. With no anomaly, the pair of LocalDateTime objects will be equal; with an anomaly they will not be equal.


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.

Upvotes: 1

Related Questions