davtrinh
davtrinh

Reputation: 53

Java, parse string 'yyyy-MM-dd'T'HH:mm:ss' to ZonedDateTime without changing time

I have a string "2018-07-24T01:30:27". I want to parse this into ZonedDateTime with EST timezone without changing the time.

I have the code...

String foo = "2018-07-24T01:30:27";
ZoneId zone = ZoneId.of("America/New_York");

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
return simpleDateFormat.parse(foo).toInstant().atZone(zone);

The return value is

2018-07-24T02:30:27-04:00[America/New_York]

I've tried creating this class as well, but it didn't help

@Configuration
public class TimeZoneConfig {

    @PostConstruct
    public void init() {

        TimeZone.setDefault(TimeZone.getTimeZone("EST"));

        System.out.println("Date in UTC: " + new Date().toString());
    }
}

Could I get some advice on this please?

Upvotes: 4

Views: 3627

Answers (3)

Basil Bourque
Basil Bourque

Reputation: 338855

tl;dr

LocalDateTime                               // Represents a date with a time-of-day, but lacks a time zone or offset-from-UTC.
.parse( "2018-07-24T01:30:27" )             // Returns a `LocalDateTime`.
.atZone( ZoneId.of( "America/New_York" ) )  // Returns a `ZonedDateTime` object. 

Table of date-time types in Java, both modern and legacy

Use concrete classes in java.time

The Answer by michalk is correct but a bit obtuse. It uses the TemporalAccessor interface explicitly. Generally in Java, this would be a good thing. But the java.time classes were designed for app authors to call the concrete classes rather than the interfaces. To quote the Javadoc:

This interface is a framework-level interface that should not be widely used in application code. Instead, applications should create and pass around instances of concrete types, such as LocalDate. There are many reasons for this, part of which is that implementations of this interface may be in calendar systems other than ISO. See ChronoLocalDate for a fuller discussion of the issues.

LocalDateTime

So let’s do that. First, parse the input as a LocalDateTime. This class does not represent a moment, is not a point on the timeline, because it lacks the context of a time zone or offset.

String input = "2018-07-24T01:30:27" ;
LocalDateTime ldt = LocalDateTime.parse( input ) ;

ZonedDateTime

Provide the context of a time zone.

ZoneId zone = ZoneId.of( "America/New_York" ) ;
ZonedDateTime zdt = ldt.atZone( zone ) ;

Instant

If you want to see that same moment adjusted into UTC, extract a Instant object.

Instant instant = zdt.toInstant() ;

Don’t mess with default time zone

TimeZone.setDefault

Do not set the JVM’s current default time zone except as a last resort. Doing so affects all code in all threads of all apps within that JVM.

Instead, write your java.time code to explicitly specify the desired/expected time zone. Never omit the optional time zone or offset from the various methods.


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.

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

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

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes. Hibernate 5 & JPA 2.2 support java.time.

Where to obtain the java.time classes?

Upvotes: 10

Anonymous
Anonymous

Reputation: 86306

    String foo = "2018-07-24T01:30:27";
    ZoneId zone = ZoneId.of("America/New_York");

    ZonedDateTime result = LocalDateTime.parse(foo).atZone(zone);
    
    System.out.println(result);

When you know how, it is this simple. Output is:

2018-07-24T01:30:27-04:00[America/New_York]

Messages:

  1. Your string is in ISO 8601 format. The classes of java.time parse the most common variants of ISO 8601 natively, without any explicit formatter. So we don’t need one.
  2. Don’t mix old and modern. When you can use java.time, the modern Java date and time API (ZoneId and ZonedDateTime), stay away from the SimpleDateFormat and friends. BTW stay way from them in any case. SimpleDateFormat is a notorious troublemaker of a class.
  3. Don’t set nor rely on the default time zone of the JVM.
    1. You are affecting all other parts of your program and all other programs running in the same JVM, probably adversely.
    2. The default time zone can be changed to something else from any other part of your program and any other program running in the same JVM, so may not stay what you set it to.

What went wrong in your program?

SimpleDateFormat assumed that the string was in the default time zone of the JVM, so converted from that time zone. Next you converted to America/New_York (Eastern Daylight Time or EDT) thus changing the hour of day correspondingly.

Why setting the default time zone of your JVM didn’t work is that TimeZone takes EST, Eastern Standard Time, literally (opposite what it does with CST and PST), but in July New York is on Eastern Daylight Time, so there is still a conversion happening. But as I said, you don’t want to set the default time zone anyway.

Link

Wikipedia article: ISO 8601

Upvotes: 3

Michał Krzywański
Michał Krzywański

Reputation: 16910

Since you want ZonedDateTime consider using DateTimeFormatter from java.time :

String foo = "2018-07-24T01:30:27";
ZoneId zone = ZoneId.of("America/New_York");
        
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");
TemporalAccessor temporalAccessor = dateTimeFormatter.withZone(zone).parse(foo);
return ZonedDateTime.from(temporalAccessor);

The returned ZonedDateTime will be :

2018-07-24T01:30:27-04:00[America/New_York]

Upvotes: 4

Related Questions