Java 8 : date and time

Temporal formatting

Parsing a String to get a date/time objects

(Too) Generic way :

DateTimeFormatter df = ...;
TemporalAccessor d = df.parse("dd/mmmm/...)";

Good for generic processing :

DateTimeFormatter df = ...;
LocalDateTime d = df.parse("dd/mmmm/...", LocalDateTime::from);

Good for one shot processing :

DateTimeFormatter df = ...;
LocalDateTime d = LocalDateTime.parse("dd/mmmm/...", df);

Specify constant/not evaluated characters in the formatter

Any unrecognized letter is an error. It is recommended to use single quotes around all characters that you want to output directly (to ensure that future changes do not break your application).
For example to specify the an ISO date time format (T char between date and time) :
Example :

DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXXX");
ZonedDateTime zdt = ZonedDateTime.parse("2021-01-25T00:05:45+0100", dtf);
System.out.println(zdt); // 2021-01-25T00:05:45+01:00

Specify the offset of the timezone in the formatter

X and x :
We use X(XXX) to render/use Z for zulu timezone (UTC+0) while we use x(xxx) to render/use explicit offset +00(:)00 for zulu timezone (UTC+0).
From the javadoc :

Offset X and x: This formats the offset based on the number of pattern letters. One letter outputs just the hour, such as ‘+01’, unless the minute is non-zero in which case the minute is also output, such as ‘+0130’. Two letters outputs the hour and minute, without a colon, such as ‘+0130’. Three letters outputs the hour and minute, with a colon, such as ‘+01:30’. Four letters outputs the hour and minute and optional second, without a colon, such as ‘+013015’. Five letters outputs the hour and minute and optional second, with a colon, such as ‘+01:30:15’.
Six or more letters throws IllegalArgumentException.
Pattern letter ‘X’ (upper case) will output ‘Z’ when the offset to be output would be zero, whereas pattern letter ‘x’ (lower case) will output ‘+00’, ‘+0000’, or ‘+00:00’.

Add an offset to a zonedatetime or datetime

Suppose we have a USA/Ny zdt and we want to get the zdt as USA/Ny minus the USA/Ny offset

    public static void main(String[] args) {
        ZoneId usaNyZoneId = ZoneId.of("America/New_York");
        LocalDateTime localDateTime = LocalDateTime.of(2021, 6, 20, 10, 30, 20, 100);
 
        ZonedDateTime zdtUsaSearch = ZonedDateTime.of(localDateTime, usaNyZoneId);
 
        ZoneOffset offsetUsa = usaNyZoneId.getRules()
                                          .getOffset(localDateTime);
 
 
        ZonedDateTime zdtUsaMinusUsaOffset = zdtUsaSearch.minusSeconds(offsetUsa.getTotalSeconds());
 
        System.out.println("Usa Ny zone date time " + zdtUsaSearch);
        System.out.println("offsetUsa " + offsetUsa);
        System.out.println("Usa Ny zone date time minus Usa Ny Offset " + zdtUsaMinusUsaOffset);
 
    }

Output

Usa Ny zone date time 2021-06-20T10:30:20.000000100-04:00[America/New_York]
offsetUsa -04:00
Usa Ny zone date time minus Usa Ny Offset 2021-06-20T14:30:20.000000100-04:00[America/New_York]

Jackson hints

Specify a datetime formatter for serialization

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssXXXX")
private ZonedDateTime zdt;

Specify the default timezone formatting

With Jackson, there exists a TimeZone for date formatting. The default value used is UTC (NOT default TimeZone of JVM).
To specify a different default timeZone :

import java.time.ZoneId;
import java.util.TimeZone;
 
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
 
@Configuration
public class MyJacksonConfig {
 
    @Autowired
    public void config(ObjectMapper objectMapper) {
        objectMapper.setTimeZone(TimeZone.getTimeZone(ZoneId.of("Europe/Paris")));
    }
 
}

Self explanatory unit tests

package davidxxx.dateandtime.example;
 
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
 
import javax.sound.midi.Soundbank;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.time.temporal.ChronoField;
import java.time.temporal.UnsupportedTemporalTypeException;
import java.util.Locale;
 
public class DateAndTimeTest {
 
  @Test
  void localDateTime_has_no_timezone() {
    LocalDateTime localDateTime = LocalDateTime.of(2018, 11, 30, 15, 10, 50);
    Assertions.assertThat(localDateTime).isEqualTo("2018-11-30T15:10:50");
    Assertions.assertThat(localDateTime.get(ChronoField.DAY_OF_MONTH)).isEqualTo(30);
 
    // No timezone assertions
    Assertions.assertThat(localDateTime.isSupported(ChronoField.OFFSET_SECONDS)).isFalse();
    Assertions.assertThatExceptionOfType(UnsupportedTemporalTypeException.class)
              .isThrownBy(() -> localDateTime.get(ChronoField.OFFSET_SECONDS));
 
    Assertions.assertThat(localDateTime.isSupported(ChronoField.INSTANT_SECONDS)).isFalse();
    Assertions.assertThatExceptionOfType(UnsupportedTemporalTypeException.class)
              .isThrownBy(() -> localDateTime.getLong(ChronoField.INSTANT_SECONDS));
  }
 
  @Test
  void localDateTime_needs_an_offset_or_zone_param_to_be_converted_to_zone_aware_temporal() {
    LocalDateTime localDateTime = LocalDateTime.of(2018, 11, 30, 15, 10, 50);
 
    // to Instant with +2h offset
    Assertions.assertThat(localDateTime.toInstant(ZoneOffset.of("+02:00"))).isEqualTo("2018-11-30T13:10:50Z");
 
    //to epoch sec with  +1h offset
    Assertions.assertThat(localDateTime.toEpochSecond(ZoneOffset.of("+01:00"))).isEqualTo(1543587050L);
 
    //to zonedDateTime with Europe/Paris zone (~= +1h offset in winter)
    Assertions.assertThat(localDateTime.atZone(ZoneId.of("Europe/Paris")))
              .isEqualTo("2018-11-30T15:10:50+01:00[Europe/Paris]");
  }
 
  @Test
  void zoneDateTime_has_timezone() {
    ZonedDateTime zonedDateTime = ZonedDateTime.of(2018, 11, 30, 15, 10, 50, 0, ZoneId.of("Europe/Paris"));
    Assertions.assertThat(zonedDateTime).isEqualTo("2018-11-30T15:10:50+01:00[Europe/Paris]"); // offset of 1h
 
    //  timezone assertions
    for (ChronoField f : ChronoField.values()) {
      Assertions.assertThat(zonedDateTime.isSupported(f));
    }
 
    Assertions.assertThat(zonedDateTime.get(ChronoField.OFFSET_SECONDS)).isEqualTo(3600); // offset of 1h = 60 * 60
    Assertions.assertThat(zonedDateTime.getLong(ChronoField.INSTANT_SECONDS))
              .isEqualTo(1543587050L); // epoch time (nb of second since 1970)
  }
 
  @Test
  void zoneDateTime_can_be_converted_to_instant() {
    ZonedDateTime zonedDateTime = ZonedDateTime.of(2018, 11, 30, 15, 10, 50, 0, ZoneId.of("Europe/Paris"));
    Assertions.assertThat(zonedDateTime.toInstant())
              .isEqualTo("2018-11-30T14:10:50Z");
  }
 
  @Test
  void instant_can_be_formatted_thanks_to_a_timezone_param() {
    Instant instant = Instant.ofEpochSecond(1543587050L); // eq to  2018-11-30T14:10:50Z
 
    // According to a locale formatting (+1h for Paris)
    Assertions.assertThat(
        DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(Locale.FRENCH)
                         .withZone(ZoneId.of("Europe/Paris")).format(instant))
              .isEqualTo("30 nov. 2018 à 15:10:50");
 
    // According to a custom pattern and a zone (here Paris)
    Assertions.assertThat(
        DateTimeFormatter.ofPattern("yyyy-MM-dd'TTTT'HH:mm:ssXXXX")
                         .withZone(ZoneId.of("Europe/Paris")).format(instant))
              .isEqualTo("2018-11-30TTTT15:10:50+0100");
 
  }
}
Ce contenu a été publié dans Non classé. Vous pouvez le mettre en favoris avec ce permalien.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *