Contents

Modern Java in Action 6 - Time and Date

Reasons to abandon old API

java.util.Date

  • mutable
  • unintuitive constructor (days, months, minutes and seconds are 0-based, year represented as delta from 1900), values wrap-around
  • cannot be internationalized

java.util.Calendar

  • not thread safe
  • mutable (no clear semantics date change)
  • when to use which?

java.util.DateFormat

  • not thead-safe
  • can only parse Date, not Calendar

New API

/en/posts/modern-java-in-action-6/time.png

java.time package

Classes and interfaces in a new package java.time (modelled after Joda Time classes) provide better way of thinking about and working with time concepts. The most important classess in this package are: LocalDate, LocalTime, LocalDateTime, Instant, Duration, and Period .

LocalDateTime and LocalTime instances don’t contain timezone information. They are:

  • immutable

  • can read fields in two ways:

    • using getters (e.g. date.getYear())
    • using TeporalField enum (e.g. date.get(ChronoField.YEAR))
  • parsing is possible separately for dates and times:

    1
    2
    
    LocalDate date = LocalDate.parse("2022-06-30");
    LocalTime time = LocalTime.parse("12:35:20");
    
  • combine 2022-06-30 12:36:48 and time with LocalDateTime

    1
    2
    3
    4
    5
    
    LocalDateTime dt1 = LocalDateTime.of(2022, Month.JUNE, 30, 12, 35, 20);
    LocalDateTime dt2 = LocalDateTime.of(date, time);
    LocalDateTime dt3 = date.atTime(12, 35, 20);
    LocalDateTime dt4 = date.atTime(time);
    LocalDateTime dt5 = time.atDate(date);
    
  • extract LocalDate and LocalTime from LocalDateTime (dt.toLocalDate() and dt.toLocalTime())

Instant - for machine processing

  • number of seconds since epoch (midninght, Jan 1 1970 UTC)
  • fatcory method Instant.of(num_of_seconds) and Instant.of(num_of_seconds, nano_adjustment)
  • Instant.now() captures intant of current moment
  • cannot provide tempopral details (yers or seconds)

Duration

  • denotes how much time there is between two temporal objects
  • used to represent the amount measured in seconds and nanoseconds
  • created with Duration.between(a1, a2) where a1 and a2 are either:
    • time
    • datetime
    • instant
  • created also with:
    • Duration.ofMinutes(10)
    • Duration.of(2, CroniUnit.MINUTES)

Period

  • represents amount of days, months, years
  • can be created with Period.between
  • also with:
    • Period.ofWeeks(3) - period of three weeks
    • Period.of(10, 2, 3) - 10 years, 2 months and 3 days

Manipulation

It is not a manupulation but a way to receive another LocalDate from existing one:

1
2
3
4
LocalDate birthday = LocalDate.of(1978, 09, 11);
LocalDate this_year_birthday = date1.withYear(2022);
LocalDate  day_after_party = this_year_birthday.withDayOfMonth(12);
LocalDate next_month_after_party = this_year_birthday.with(ChronoField.MONTH_OF_YEAR, 10);

Fun with simple arithmetics

Temporal interface allow to do dates arithmetics:

  • you can plus and minus some TemporalAmount (i.e. Duration or Period) from Temporals
  • you can check how much is left until another Temporal (expressed in given TemporalUnit

Note: ChronoField and ChronoUnit are located in java.time.temporal package.

1
2
3
4
5
// my birthday
var bd = LocalDate.of(2022, 9, 11);
// how  many days left?
System.out.println(LocalDate.now().until(bd, ChronoUnit.DAYS));
//72
  • or what would be next Monday after specific LocalDate:
1
2
jshell> bd.with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY))
$14 ==> 2022-09-12

More fun with TemporalAdjusters

Class TemporalAdjusters allows to find :

  • first or last day of this/next month
  • first or last day in the year
  • first or last day-of-week within a month (first Friday in May)
  • next or previous day of week (next Sunday)

You can create your own adjuster with:

1
public static TemporalAdjuster ofDateAdjuster(UnaryOperator<LocalDate> dateBasedAdjuster)

or implement custo m TemporalAdjuster:

1
2
3
4
@FunctionalInterface
public interface TemporalAdjuster {
    Temporal adjustInto(Temporal temporal);
}

Formatting and parsing

This functionality lives in java.time.format package. Predefined instances of DateTimeFormatter (as static contants) provde common ways for formatting/parsing of dates.

  • formatting
1
2
3
String s2 = bd.format(DateTimeFormatter.ISO_LOCAL_DATE);
System.out.println(s2);
// 2022-09-11
  • parsing
1
2
3
4
LocalDate date2 = LocalDate.parse("2022-09-11", 
  DateTimeFormatter.ISO_LOCAL_DATE);
System.out.println(date2);
// 2022-09-11

Working with timezones

This is the pearl of this package.

  • ZoneId represents a zone in a form “{area}/{city}” (taken from IANA)
  • each might have specific rules determining the time

LocalDateTime can be converted to Instant provided the ZoneId; Instant can be converted to LocalDateTime also only when it is known in what timezone the date is needed:

1
2
3
4
5
6
7
8
jshell> TimeZone warsawZone = TimeZone.getDefault()
warsawZone ==> sun.util.calendar.ZoneInfo[id="Europe/Warsaw",off ... me=3600000,endTimeMode=2]]

jshell> Instant instant = Instant.now();
instant ==> 2022-06-30T14:29:46.492899089Z

jshell> LocalDateTime timeFromInstant = LocalDateTime.ofInstant(instant,warsawZone.toZoneId())
timeFromInstant ==> 2022-06-30T16:29:46.492899089

ZoneOffset can express a time zone with fixed offset from UTC/Greenwich:

1
ZoneOffset newYorkOffset = ZoneOffset.of("-06:00");

which can be used to see hat offset time there is right now:

1
2
LocalDateTime dateTime = LocalDateTime.now();
OffsetDateTime dateTimeInNewYork = OffsetDateTime.of(date, newYorkOffset);

Alternative calendar systems

There are other calendar systems available as well:

ThaiBuddhistDate, MinguoDate, JapaneseDate, and HijrahDate

Example:

1
2
3
4
jshell> LocalDate date = LocalDate.of(2014, Month.MARCH, 18);
   ...> JapaneseDate japaneseDate = JapaneseDate.from(date);
date ==> 2014-03-18
japaneseDate ==> Japanese Heisei 26-03-18

Note

As a refresher on date-time types in PostgreSQL, see my other blog post: postgres - dates