From String without timezone to time with Europe/Berlin

Viewed 780

I've tried the whole day to parse a date, which I've got from a System as a String without Timezone to a String with Timezone and applied time difference

Original String: "01/27/2021 14:47:29"

Target String: "2021-01-27T13:47:29.000+01:00"

Problem: The target System can not change the format. I need to apply, that it substracts automatically the correct amount of hours, depending on summer/winter time. So it's not a solution to just subtract -1.

I've tried with several This is my last try which is almost correct, but it does not change the hours correctly:

DateTimeFormatter fmt = DateTimeFormat.forPattern("MM/dd/yyyy HH:mm:ss").withZone(DateTimeZone.forID(
                "Europe/Berlin"));
DateTime dt = fmt.parseDateTime(lastModifid);
dt.toString()

Result is "2021-01-27T14:47:29.000+01:00"

Isn't there an easy solution to apply these time differences?

3 Answers

The answer (since Java 8) is using ZonedDateTime. I don't think you need to do any "timezone arithmetic" to do what you need. Simply convert the time from one zone into the other like this:

    ZoneId sourceTZ = ZoneId.of(...);    //String denoting source time zone
    ZoneId targetTZ = ZoneId.of(...);  //String denoting target time zone
     
    LocalDateTime now = LocalDateTime.now();
     
    ZonedDateTime sourceTime = now.atZone(sourceTZ);       
    ZonedDateTime targetTime = sourceTime.withZoneSameInstant(targetTZ);

After this, you can use DateTimeFormatter to format the timestamp string however you want.

java.time

I am just spelling out the good suggestion by hfontanez a little bit more. I am first declaring:

private static final DateTimeFormatter formatter
        = DateTimeFormatter.ofPattern("MM/dd/uuuu HH:mm:ss");
private static final ZoneId targetZone = ZoneId.of("Europe/Berlin");

Then processing your string goes like this:

    String originalString = "01/27/2021 14:47:29";
    
    OffsetDateTime dateTime = LocalDateTime.parse(originalString, formatter)
            .atOffset(ZoneOffset.UTC);
    String targetString = dateTime.atZoneSameInstant(targetZone)
            .format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
    
    System.out.println(targetString);

And output is:

2021-01-27T15:47:29+01:00

You asked for 13:47:29.000 and I gave you 15:47:29. Why?

  • I assumed that the original string was in UTC, so in the code I explicitly told Java to interpret it in this way. If this assumption is correct, then it seems that you have misunderstood. The offsets for German time (Europe/Berlin), standard +01:00 and +02:00 during summer time (DST), mean that Germany is 1 or 2 hours ahead of UTC, so we need to add, not subtract, 1 or 2 hours. And as hfontanez already said, the library is doing it correctly for us.
  • The format you asked for and that Joda-Time produces too is ISO 8601. According to ISO 8601 the decimals on the seconds are optional when they are zero, so for your purpose you should not need the .000 part of the target string.

Like hfontanez I am using java.time, the modern Java date and time API. Why? I consider java.time the good successor of Joda-Time. The Joda-Time homepage itself says (boldface is original):

Note that from Java SE 8 onwards, users are asked to migrate to java.time (JSR-310) - a core part of the JDK which replaces this project.

What went wrong in your code?

You were applying .withZone(DateTimeZone.forID("Europe/Berlin")) to the formatter that you used for parsing your original string. This is the same as telling Joda-Time that the original string is already in German time. So from that Joda-Time decides that no conversion of the time is necessary and just gives you the same hour of day back. Instead we first need to specify in which time zone the original string is and then convert it to German time.

Links

Using Java SE 8 Date and Time API

The following code

DateTimeFormatter fmt = DateTimeFormatter.ofPattern("MM/dd/uuuu HH:mm:ss", Locale.ENGLISH)
                                    .withZone(ZoneId.of("Africa/Johannesburg"));
ZonedDateTime zdtSource = ZonedDateTime.parse(strSource, fmt);

does the same thing as

DateTimeFormatter fmt = DateTimeFormatter.ofPattern("MM/dd/uuuu HH:mm:ss", Locale.ENGLISH);
ZonedDateTime zdtSource = LocalDateTime.parse(strSource, fmt)
                                    .atZone(ZoneId.of("Africa/Johannesburg"));

which means that applying a timezone with the parser is a way of asking it to parse the date-time string as a local date-time and stick the timezone to it.

Now that you have understood this concept, let's discuss the requirement that you posted.

I've got from a System as a String without Timezone to a String with Timezone and applied time difference

Original String: "01/27/2021 14:47:29"

Target String: "2021-01-27T13:47:29.000+01:00"

Problem: The target System can not change the format. I need to apply, that it substracts automatically the correct amount of hours, depending on summer/winter time. So it's not a solution to just subtract -1.

There are two things here to understand:

  1. It is possible to convert the given local date-time into the target date-time only when the given local date-time is from a timezone which has a timezone offset of UTC+02:00 e.g. Africa/Johannesburg. The following diagram describes the timezone of Europe/Berlin enter image description here
  2. The ZonedDateTime has been designed to adjust the date-time automatically as per summer/winter time; so, you do not need to do anything explicitly.

Enough talking; let's see some code!

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        String strSource = "01/27/2021 14:47:29";
        DateTimeFormatter fmtInput = DateTimeFormatter.ofPattern("MM/dd/uuuu HH:mm:ss", Locale.ENGLISH);
        ZonedDateTime zdtSource = LocalDateTime.parse(strSource, fmtInput)
                                        .atZone(ZoneId.of("Africa/Johannesburg"));
        System.out.println(zdtSource);

        ZonedDateTime zdtTarget = zdtSource.withZoneSameInstant(ZoneId.of("Europe/Berlin"));
        // Default format
        System.out.println(zdtTarget);
        // Custom format
        DateTimeFormatter fmtOutput = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSXXX", Locale.ENGLISH);
        System.out.println(zdtTarget.format(fmtOutput));
    }
}

Output:

2021-01-27T14:47:29+02:00[Africa/Johannesburg]
2021-01-27T13:47:29+01:00[Europe/Berlin]
2021-01-27T13:47:29.000+01:00

Learn more about the modern date-time API from Trail: Date Time.

Using Joda-Time API

Note: Check the following notice at the Home Page of Joda-Time

Joda-Time is the de facto standard date and time library for Java prior to Java SE 8. Users are now asked to migrate to java.time (JSR-310).

However, just for the sake of completeness, I've written the following code using Joda-time API:

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

public class Main {
    public static void main(String[] args) {
        String strSource = "01/27/2021 14:47:29";
        DateTimeFormatter fmtInput = DateTimeFormat.forPattern("MM/dd/yyyy HH:mm:ss")
                                        .withZone(DateTimeZone.forID("Africa/Johannesburg"));

        DateTime dtSource = fmtInput.parseDateTime(strSource);
        System.out.println(dtSource);

        DateTime dtTarget = dtSource.withZone(DateTimeZone.forID("Europe/Berlin"));
        System.out.println(dtTarget);
    }
}

Output:

2021-01-27T14:47:29.000+02:00
2021-01-27T13:47:29.000+01:00
Related