The time is now!

Date/Time APIs in Java 8

Micha Kops / @hascode

Overview

Why new APIs?

Typical problems with the old java.util.Date and java.util.Calendar APIs.

  • They're mutable - this often leads to issues in multi-threaded environments
  • java.util.Date is a DateTime, therefore the need to create java.sql.Date to persist dates
  • Months are zero-indexed (January = 0)
  • Years are rated with two digit from 1900, so new Date().getYear() in year 2016 returns 116
  • No TimeZones associated with Dates
  • Missing classes for modelling other time-related concepts like durations, periods - this leads to programmers using and int as duration and adding a javadoc describing the unit

New Date/Time-Classes

  • LocalDate
  • LocalTime
  • LocalDateTime
  • ZonedDateTime
  • Instant
  • Clock
  • YearMonth
  • MonthDay
  • Year
  • OffsetTime
  • OffsetDateTime

LocalDate

  • represents a date
  • no time
  • no timezone
  • example: my birthday this year
  • new instance created via factory method
                    
LocalDate ld = LocalDate.now();
System.out.println("LocalDate\t:" + ld);
// LocalDate	:2016-06-07
                    
                

LocalTime

  • represents a time
  • no date
  • no timezone
  • example: midnight
  • new instance created via factory method
                    
LocalTime lt = LocalTime.now();
System.out.println("LocalTime\t:" + lt);
// LocalTime	:19:02:15.982
                    
                

LocalDateTime

  • represents a date with time
  • no timezone
  • example: midnight at new year's eve
  • new instance created via factory method
                    
LocalDateTime ldt = LocalDateTime.now();
System.out.println("LocalDateTime\t:" + ldt);
// LocalDateTime	:2016-06-07T19:02:15.982
                    
                

ZonedDateTime

  • represents a date with time and timezone
  • example: meeting at 5:00 p.m., Europe/Berlin
  • new instance created via factory method
                    
ZonedDateTime zdt = ZonedDateTime.now();
System.out.println("ZonedDateTime\t:" + zdt);
// ZonedDateTime	:2016-06-07T19:02:15.983+02:00[Europe/Berlin]
                    
                

Instant

  • represents a specific moment in time (GMT timezone)
  • new instance created via factory method
                    
Instant i = Instant.now();
System.out.println("Instant\t\t:" + i);
// Instant		:2016-06-07T17:02:15.983Z
                    
                

Clock

  • provider for current instant, date and time using time-zone
                    
Clock clock = Clock.systemDefaultZone();
System.out.println("Clock\t\t:" + clock.instant());
// Clock		:2016-06-07T18:23:41.302Z
                    
                

YearMonth

  • a month of a specific year
  • helper methods to transition into a LocalDate
  • new instance created via factory method
                    
YearMonth ym = YearMonth.now();
System.out.println("YearMonth\t:" + ym);
// YearMonth	:2016-06
                    
                

MonthDay

  • a day of a specific month
  • helper methods to transition into a LocalDate
  • new instance created via factory method
                    
MonthDay md = MonthDay.now();
System.out.println("MonthDay\t:" + md);
// MonthDay	:--06-07
                    
                

Year

  • a specific year
  • helper methods to transition into a LocalDate
  • new instance created via factory method
                    
Year year = Year.now();
System.out.println("Year\t\t:" + year);
// Year		:2016
                    
                

OffsetTime

  • time with an offset from greenwich/utc time
  • helper methods to transition into a OffsetDateTime
  • new instance created via factory method
                    
OffsetTime ot = OffsetTime.now();
System.out.println("OffsetTime\t:" + ot);
// OffsetTime	:19:02:15.995+02:00
                    
                

OffsetDateTime

  • date and time with an offset from greenwich/utc time
  • helper methods to transition into a ZonedDateTime
  • new instance created via factory method
                    
OffsetDateTime odt = OffsetDateTime.now();
System.out.println("OffsetDateTime\t:" + odt);
// OffsetDateTime	:2016-06-07T19:02:15.994+02:00
                    
                

Date Manipulation

                
LocalDate xmas = LocalDate.of(2016, Month.DECEMBER, 24);
System.out.printf("x-mas 2016: %s, week-day: %s\n", xmas, xmas.getDayOfWeek());
LocalDate xmas2017 = xmas.plusYears(1);
System.out.printf("x-mas 2017: %s, week-day: %s\n", xmas2017, xmas2017.getDayOfWeek());
LocalDate xmas1999 = xmas2017.minus(18, ChronoUnit.YEARS);
System.out.printf("x-mas 1999: %s, week-day: %s\n", xmas1999, xmas1999.getDayOfWeek());

LocalDate firstNovember1999 = xmas.minusYears(17).minusMonths(1).minusDays(23);
System.out.printf("first november 1999: %s, week-day: %s\n", firstNovember1999,
firstNovember1999.getDayOfWeek());

// x-mas 2016: 2016-12-24, week-day: SATURDAY
// x-mas 2017: 2017-12-24, week-day: SUNDAY
// x-mas 1999: 1999-12-24, week-day: FRIDAY
// first november 1999: 1999-11-01, week-day: MONDAY
                
            

Periods and Durations

  • use Periods when dealing with days, months and years
  • use Durations when dealing with hours, minutes, seconds, nanoseconds..
                
LocalDate start = LocalDate.now();
LocalDate end = start.plusYears(1);
Period between = Period.between(start, end);
System.out.printf("between %s and %s are %s days, written as period: %s\n", start, end,
ChronoUnit.DAYS.between(start, end), between);

LocalDateTime startTime = LocalDateTime.now();
LocalDateTime endTime = startTime.plusMinutes(1);
Duration duration = Duration.ofMinutes(10);
System.out.printf("now it's %s, in 10 minutes it's %s\n", startTime, startTime.plus(duration));
System.out.printf("between %s and %s is %s seconds\n", startTime, endTime,
Duration.between(startTime, endTime).getSeconds());

// between 2016-06-07 and 2017-06-07 are 365 days, written as period: P1Y
// now it's 2016-06-07T19:58:42.275, in 10 minutes it's 2016-06-07T20:08:42.275
// between 2016-06-07T19:58:42.275 and 2016-06-07T19:59:42.275 is 60 seconds
                
            

Formatting and Parsing

                
LocalDate ld = LocalDate.now();
DateTimeFormatter format = DateTimeFormatter.ofPattern("yy/MM/dd");
System.out.printf("%s formatted is %s\n", ld, ld.format(format));

String dateString = "13/12/24";
LocalDate parsed = LocalDate.parse(dateString, format);
System.out.printf("string %s parsed to local-date %s\n", dateString, parsed);

// 2016-06-07 formatted is 16/06/07
// string 13/12/24 parsed to local-date 2013-12-24
                
            

Instants

                
// measuring time of execution
Instant start = Instant.now();
Thread.sleep(2000); // simulate operation
Instant stop = Instant.now();
System.out.printf("operation started at %s, ended at %s, took %s seconds\n", start, stop,
Duration.between(start, stop).getSeconds());

// converting instant to zoned date-time
Instant now = Instant.now();
ZonedDateTime berlin = ZonedDateTime.ofInstant(now, ZoneId.of("Europe/Berlin"));
System.out.printf("instant: %s, berlin: %s\n", now, berlin);
Instant berlinInstant = berlin.toInstant();
System.out.println("instants are equal: " + now.equals(berlinInstant));

// operation started at 2016-06-07T18:20:19.684Z, ended at 2016-06-07T18:20:21.684Z, took 2 seconds
// instant: 2016-06-07T18:20:21.746Z, berlin: 2016-06-07T20:20:21.746+02:00[Europe/Berlin]
// instants are equal: true

                
            

Clocks

                
Clock clock = Clock.systemDefaultZone();
System.out.println(clock.millis());
Thread.sleep(1234);
System.out.println(clock.millis());

System.out.println("timeless clock (always returns the same instant)");
Clock timeless = Clock.fixed(Instant.now(), ZoneId.of("Europe/Berlin"));
System.out.println(timeless.millis());
Thread.sleep(1234);
System.out.println(timeless.millis());
Thread.sleep(1234);
System.out.println(timeless.millis());

// 1465323910179
// 1465323911413
// timeless clock (always returns the same instant)
// 1465323911415
// 1465323911415
// 1465323911415
                
            

Temporal Queries

  • Predefined date queries exist in the class TemporalQueries
                
TemporalQuery<TemporalUnit> query = TemporalQueries.precision();
System.out.printf("LocalDate precision: %s%n", LocalDate.now().query(query));
System.out.printf("LocalDateTime precision: %s%n", LocalDateTime.now().query(query));
System.out.printf("Year precision: %s%n", Year.now().query(query));
System.out.printf("YearMonth precision: %s%n", YearMonth.now().query(query));
System.out.printf("Instant precision: %s%n", Instant.now().query(query));

LocalDate date = LocalDate.of(2016, 11, 22);
boolean isInYear2016 = date.query(t -> {
return t.get(ChronoField.YEAR) == 2016;
});
System.out.printf("date %s is in year 2016: %s\n", date, isInYear2016);

// LocalDate precision: Days
// LocalDateTime precision: Nanos
// Year precision: Years
// YearMonth precision: Months
// Instant precision: Nanos
// date 2016-11-22 is in year 2016: true
                
            

Temporal Adjuster API

                
public class TemporalAdjusterExamples {
    public static void main(String[] args) {
        LocalDate date = LocalDate.of(2016, Month.DECEMBER, 24);

        final LocalDate firstDay = date.with(TemporalAdjusters.firstDayOfMonth());
        System.out.printf("first day of december 2016: %s (%s)\n", firstDay, firstDay.getDayOfWeek());

        final LocalDate lastDay = date.with(TemporalAdjusters.lastDayOfMonth());
        System.out.printf("last day of december 2016: %s (%s)\n", lastDay, lastDay.getDayOfWeek());

        System.out.printf("first monday of december 2016: %s\n",
        date.with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)));

        System.out.printf("last monday of december 2016: %s\n",
        date.with(TemporalAdjusters.lastInMonth(DayOfWeek.MONDAY)));

        // custom temporal adjuster
        LocalDateTime ldt = LocalDateTime.now();
        System.out.printf("%s with custom adjuster is %s", ldt, ldt.with(new ZeroBCYearAdjuster()));
    }

    static class ZeroBCYearAdjuster implements TemporalAdjuster {
        @Override
        public Temporal adjustInto(Temporal temporal) {
            return LocalDateTime.from(temporal).withYear(0);
        }

    }
}

// first day of december 2016: 2016-12-01 (THURSDAY)
// last day of december 2016: 2016-12-31 (SATURDAY)
// first monday of december 2016: 2016-12-05
// last monday of december 2016: 2016-12-26
// 2016-06-07T20:28:47.461 with custom adjuster is 0000-06-07T20:28:47.461
                
            

Time-Zones

                
ZoneId.getAvailableZoneIds()
    .stream()
    .filter(z -> z.contains("Europe"))
    .sorted()
    .forEach(System.out::println);

ZoneId easternUs = ZoneId.of("US/Eastern");
ZonedDateTime timeInParis = ZonedDateTime.now(ZoneId.of("Europe/Paris"));
System.out.println("In Paris, it's " + timeInParis);
ZonedDateTime timeInNY = timeInParis.withZoneSameInstant(easternUs);
System.out.println("Meanwhile in New York, it's: " + timeInNY);

// Europe/Amsterdam
// Europe/Andorra
// Europe/Athens
// Europe/Belfast
// Europe/Belgrade
// Europe/Berlin
// [..]
// Europe/Warsaw
// Europe/Zagreb
// Europe/Zaporozhye
// Europe/Zurich
// In Paris, it's 2016-06-07T20:31:22.208+02:00[Europe/Paris]
// Meanwhile in New York, it's: 2016-06-07T14:31:22.208-04:00[US/Eastern]
                
            

Daylight Saving Time (DST)

                
ZonedDateTime winterTime = ZonedDateTime.of(
    2016, 3, 27, 1, 0, 0, 0,
    ZoneId.of("Europe/Berlin"));
System.out.println("DST change in Germany from winter to summer time, it's " + winterTime);
System.out.println("After adding one hour, it's now " + winterTime.plusHours(2));

// DST change in Germany from winter to summer time, it's 2016-03-27T01:00+01:00[Europe/Berlin]
// After adding one hour, it's now 2016-03-27T04:00+02:00[Europe/Berlin]
                
            

Leap-Seconds

                
System.out.println("1900 is leap year: " + Year.of(1900).isLeap());
System.out.println("2012 is leap year: " + Year.isLeap(2012));

// 1900 is leap year: false
// 2012 is leap year: true
                
            

Helper/Utility Classes

                
// length of year
System.out.println("length of year 2016: " + Year.of(2016).length());
System.out.println("length of year 2016: " + Year.of(2017).length());

// Month day validity checking
System.out.println("Feb 29 is valid for 2016: " + MonthDay.of(Month.FEBRUARY, 29).isValidYear(2016));
System.out.println("Feb 29 is valid for 2017: " + MonthDay.of(Month.FEBRUARY, 29).isValidYear(2017));

// length of year 2016: 366
// length of year 2016: 365
// Feb 29 is valid for 2016: true
// Feb 29 is valid for 2017: false
                
            

Legacy API Compatibility

                
// Date to Instant
Date date1 = new Date();
Instant inst1 = date1.toInstant();
System.out.printf("date %s to instant %s\n", date1, inst1);

// Date from Instant
Date date2 = Date.from(inst1);
System.out.printf("date %s from instant %s equals date %s: %s\n", date2, inst1, date1, date1.equals(date2));

// Calendar to Instant
Calendar cal1 = Calendar.getInstance();
Instant inst2 = cal1.toInstant();
// i hate calendar's toString so much
System.out.printf("calendar %s to instant %s\n", cal1, inst2);

// gregorian calendar from zoned datetime
ZonedDateTime zdt1 = ZonedDateTime.now();
GregorianCalendar gcal1 = GregorianCalendar.from(zdt1);
System.out.printf("calendar %s from zoned-date-time %s\n", gcal1, zdt1);

// gregorian calendar to zoned datetime
ZonedDateTime zdt2 = gcal1.toZonedDateTime();
System.out.printf("converted zoned date-times equal: %s\n", (zdt1.equals(zdt2)));

// TimeZone from ZoneId
ZoneId zoneId1 = ZoneId.of("Europe/Berlin");
TimeZone tz = TimeZone.getTimeZone(zoneId1);
System.out.printf("time-zone from zone-id %s is %s\n", zoneId1, tz);

// java.sql.Date to LocalDate
java.sql.Date sqlDate = new java.sql.Date(date1.getTime());
LocalDate ld = sqlDate.toLocalDate();
System.out.printf("sql-date %s to localdate is: %s\n", sqlDate, ld);

// be careful .. a sql.Date has no time component so this will throw an
// exception
try {
    Instant inst3 = sqlDate.toInstant();
} catch (UnsupportedOperationException e) {
    System.out.println("conversion of java.util.Date to Instant is not possible");
    Instant inst3 = Instant.ofEpochMilli(sqlDate.getTime());
    System.out.printf("sql.Date %s to Instant using Instant.ofEpochMilli is: %s\n", sqlDate, inst3);
}

// date Wed Jun 08 18:14:05 CEST 2016 to instant 2016-06-08T16:14:05.294Z
// date Wed Jun 08 18:14:05 CEST 2016 from instant 2016-06-08T16:14:05.294Z equals date Wed Jun 08 18:14:05 CEST 2016: true
// calendar java.util.GregorianCalendar[time=1465402445368,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Europe/Berlin",offset=3600000,dstSavings=3600000,useDaylight=true,transitions=143,lastRule=java.util.SimpleTimeZone[id=Europe/Berlin,offset=3600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2016,MONTH=5,WEEK_OF_YEAR=24,WEEK_OF_MONTH=2,DAY_OF_MONTH=8,DAY_OF_YEAR=160,DAY_OF_WEEK=4,DAY_OF_WEEK_IN_MONTH=2,AM_PM=1,HOUR=6,HOUR_OF_DAY=18,MINUTE=14,SECOND=5,MILLISECOND=368,ZONE_OFFSET=3600000,DST_OFFSET=3600000] to instant 2016-06-08T16:14:05.368Z
// calendar java.util.GregorianCalendar[time=1465402445376,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Europe/Berlin",offset=3600000,dstSavings=3600000,useDaylight=true,transitions=143,lastRule=java.util.SimpleTimeZone[id=Europe/Berlin,offset=3600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2016,MONTH=5,WEEK_OF_YEAR=23,WEEK_OF_MONTH=2,DAY_OF_MONTH=8,DAY_OF_YEAR=160,DAY_OF_WEEK=4,DAY_OF_WEEK_IN_MONTH=2,AM_PM=1,HOUR=6,HOUR_OF_DAY=18,MINUTE=14,SECOND=5,MILLISECOND=376,ZONE_OFFSET=3600000,DST_OFFSET=3600000] from zoned-date-time 2016-06-08T18:14:05.376+02:00[Europe/Berlin]
// converted zoned date-times equal: true
// time-zone from zone-id Europe/Berlin is sun.util.calendar.ZoneInfo[id="Europe/Berlin",offset=3600000,dstSavings=3600000,useDaylight=true,transitions=143,lastRule=java.util.SimpleTimeZone[id=Europe/Berlin,offset=3600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]]
// sql-date 2016-06-08 to localdate is: 2016-06-08
// conversion of java.util.Date to Instant is not possible
// sql.Date 2016-06-08 to Instant using Instant.ofEpochMilli is: 2016-06-08T16:14:05.294Z
                
            

Exercises

It's today, 3 pm in nice, france, what time is it in new york?

                
ZonedDateTime nice = ZonedDateTime.now(ZoneId.of("Europe/Paris"))
                        .withHour(15)
                        .withMinute(0);
ZonedDateTime ny = nice
                        .withZoneSameInstant(ZoneId.of("US/Eastern"));
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("hh:mm");
System.out.printf("In Nice it's %s, in New-York it's %s\n", nice.format(fmt), ny.format(fmt));

// In Nice it's 03:00, in New-York it's 09:00
                
            

What week day is december 24th in year 3032?

                
System.out.printf("december 24th 3032 is week-day: %s\n",
                    LocalDate.of(3032, 12, 24).getDayOfWeek());
// december 24th 3032 is week-day: MONDAY
                
            

What week day is the first and last day of the current month?

                
LocalDate now = LocalDate.now();
LocalDate firstDayOfMonth = now.with(TemporalAdjusters.firstDayOfMonth());
LocalDate lastDayOfMonth = now.with(TemporalAdjusters.lastDayOfMonth());
System.out.printf("first day of this month is week-day: %s, last day is week-day: %s\n",
firstDayOfMonth.getDayOfWeek(), lastDayOfMonth.getDayOfWeek());

// first day of this month is week-day: WEDNESDAY, last day is week-day: THURSDAY
                
            

How many days lie between may 23th 1999 and february 12th 2000?

                
LocalDate d1 = LocalDate.of(1999, 5, 23);
LocalDate d2 = LocalDate.of(2000, 2, 12);
System.out.printf("between %s and %s lie %s days\n", d1, d2, ChronoUnit.DAYS.between(d1, d2));

// between 1999-05-23 and 2000-02-12 lie 265 days
                
            

What week day tomorrow in 3 weeks and 1 year?

                
DayOfWeek future = LocalDate.now().plusDays(1).plusWeeks(3).plusYears(1).getDayOfWeek();
System.out.printf("tomorrow in 3 weeks and 1 year it's %s\n", future);

// tomorrow in 3 weeks and 1 year it's SATURDAY
                
            

How to convert an Instant to a ZonedDateTime

                
Instant instant = Instant.now();
ZonedDateTime zdtime = instant.atZone(ZoneId.systemDefault());
System.out.printf("instant %s as zoned-date-time: %s\n", instant, zdtime);

// instant 2016-06-09T14:30:31.020Z as zoned-date-time: 2016-06-09T16:30:31.020+02:00[Europe/Berlin]
                
            

How to print the length of each month for a range of two given years?

                
YearMonth from = Year.of(2014).atMonth(Month.JANUARY);
YearMonth to = Year.of(2017).atMonth(Month.DECEMBER);

Period oneMonth = Period.ofMonths(1);
YearMonth cur = YearMonth.from(from);
while (cur.isBefore(to)) {
    System.out.printf("%s has %s days\n", cur, cur.getMonth().length(true));
    cur = cur.plus(oneMonth);
}

2014-01 has 31 days
2014-02 has 29 days
2014-03 has 31 days
2014-04 has 30 days
2014-05 has 31 days
2014-06 has 30 days
2014-07 has 31 days
2014-08 has 31 days
2014-09 has 30 days
2014-10 has 31 days
2014-11 has 30 days
2014-12 has 31 days
2015-01 has 31 days
2015-02 has 29 days
2015-03 has 31 days
2015-04 has 30 days
2015-05 has 31 days
2015-06 has 30 days
2015-07 has 31 days
2015-08 has 31 days
2015-09 has 30 days
2015-10 has 31 days
2015-11 has 30 days
2015-12 has 31 days
2016-01 has 31 days
2016-02 has 29 days
2016-03 has 31 days
2016-04 has 30 days
2016-05 has 31 days
2016-06 has 30 days
2016-07 has 31 days
2016-08 has 31 days
2016-09 has 30 days
2016-10 has 31 days
2016-11 has 30 days
2016-12 has 31 days
2017-01 has 31 days
2017-02 has 29 days
2017-03 has 31 days
2017-04 has 30 days
2017-05 has 31 days
2017-06 has 30 days
2017-07 has 31 days
2017-08 has 31 days
2017-09 has 30 days
2017-10 has 31 days
2017-11 has 30 days
                
            

How to detect if a given date is Friday, 13th

                
TemporalQuery<Boolean> friday13thQuery = (t) -> {
    return t.get(ChronoField.DAY_OF_MONTH) == 13 && t.get(ChronoField.DAY_OF_WEEK) == 5;
};

LocalDate date1 = LocalDate.of(2020, Month.MARCH, 13);
LocalDate date2 = LocalDate.of(2016, Month.JUNE, 24);
System.out.printf("%s is friday-13th: %s, %s is friday-13th: %s\n", date1, date1.query(friday13thQuery), date2,
date2.query(friday13thQuery));

// 2020-03-13 is friday-13th: true, 2016-06-24 is friday-13th: false
                
            

How to read the string 2016/6/23 into a LocalDate

                
DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy/M/dd");
System.out.printf("formatted date is: %s\n", LocalDate.parse("2016/6/23", format));

// formatted date is: 2016-06-23
                
            

Thanks for your attention!