Saturday, 18 February 2017

Working with Java 8 Date/Time API

Background

Before Java 8 how do we work with Date/Time. We used java.util.Date class. Like below -

        Date d = new Date();
        System.out.println(d);
        Date cd = Calendar.getInstance().getTime();
        System.out.println(cd);
        System.out.println(d.getDate());
        System.out.println(d.getTime());
        System.out.println(System.currentTimeMillis());//same as getTime above

Output :
Sat Feb 18 10:42:17 IST 2017
Sat Feb 18 10:42:17 IST 2017
18
1487394737708
1487394737728

In Java 8 Date/Time APIs are completely revamped. Most of them are in java.time.* package. We will look at them now.

New Date/Time classes

New Date/Time classes are as follows -
  • LocalDate : Contains date. No time or timezone.
  • LocalTime : Contains time. No date or timezone.
  • LocalDateTime : Contains date and time but not the timezone.
  • ZonedDateTime : Contains date and time with the timezone.
You can test it as follows -

            System.out.println(LocalDate.now());
            System.out.println(LocalTime.now());
            System.out.println(LocalDateTime.now());
            System.out.println(ZonedDateTime.now()); 


For me the output is as follows -
2017-02-18
10:59:48.133
2017-02-18T10:59:48.133
2017-02-18T10:59:48.134+05:30[Asia/Kolkata]

NOTE : Notice how T separator is used to separate Date and time in the output of  LocalDateTime and ZonedDateTime. In the output of ZonedDateTime what you see as +5.30 is relative to GMT. So current time in Asia/Kolkata time zone is 5hours 30 minutes ahead of GMT.


More ways to create Date/Time class instances

LocalDate :
public static LocalDate of(int year, int month, int dayOfMonth)
public static LocalDate of(int year, Month month, int dayOfMonth)

NOTE : month starts with 1 (end at 12) unlike normal convention used Java which is 0 based. Also Month is an enum. You cannot compare it with int.

LocalTime :
public static LocalTime of(int hour, int minute)
public static LocalTime of(int hour, int minute, int second)
public static LocalTime of(int hour, int minute, int second, int nanos)

LocalDateTime:
public static LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute)
public static LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute, int second)
public static LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanos)
public static LocalDateTime of(int year, Month month, int dayOfMonth, int hour, int minute)
public static LocalDateTime of(int year, Month month, int dayOfMonth, int hour, int minute, int second)
public static LocalDateTime of(int year, Month month, int dayOfMonth, int hour, int minute, int second, int nanos)
public static LocalDateTime of(LocalDate date, LocalTime time)

NOTE : Notice how you can can use a LocalDate and LocaleTime instance to create a LocalDateTime instance.

ZonedDateTime :
public static ZonedDateTime of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanos, ZoneId zone)
public static ZonedDateTime of(LocalDate date, LocalTime time, ZoneId zone)
public static ZonedDateTime of(LocalDateTime dateTime, ZoneId zone)

NOTE : Notice how each constructor needs a ZoneId instance.  It basically tells what time zone we are working with. You can print out all the ZoneIds available or filter it to suit your needs.

        ZoneId.getAvailableZoneIds().stream()
        .filter(z -> z.contains("Kolkata"))
        .sorted().forEach(System.out::println);
        ZoneId zone = ZoneId.of("Asia/Kolkata");
        System.out.println(zone);


And it prints :
Asia/Kolkata
Asia/Kolkata

NOTE : Note that there are no explicit constructors. You need to use static method as I have mentioned above. Also you will get DateTimeException if you pass invalid arguments

You can try :
        System.out.println(LocalDate.of(2017, 13, 21));
        System.out.println(LocalDate.of(2017, 2, 29));


You will get :

Exception in thread "main" java.time.DateTimeException: Invalid value for MonthOfYear (valid values 1 - 12): 13
    at java.time.temporal.ValueRange.checkValidValue(ValueRange.java:311)
    at java.time.temporal.ChronoField.checkValidValue(ChronoField.java:703)
    at java.time.LocalDate.of(LocalDate.java:267)
    at HelloWorld.main(HelloWorld.java:83)

and

Exception in thread "main" java.time.DateTimeException: Invalid date 'February 29' as '2017' is not a leap year
    at java.time.LocalDate.create(LocalDate.java:429)
    at java.time.LocalDate.of(LocalDate.java:269)
    at HelloWorld.main(HelloWorld.java:84)

NOTE : 2012 and 2016 are leap years.

Manipulating Date/Time

You can manipulate date/time as follows -

Using Periods

Period is well span of LocalDate. That means span consisting of Year, Month, Day, Week etc. 

Eg.

Period annually = Period.ofYears(1); // every 1 year
Period quarterly = Period.ofMonths(3); // every 3 months
Period everyTwoWeeks = Period.ofWeeks(2); // every 2 weeks
Period everyYearAndAWeek = Period.of(1, 0, 5); // every year and 5 days 


NOTE : There is no time involvement in Periods. You cannot use Period with LocalTime.Also note you cannot concatenate Period methods. Well you can technically but only last one will be picked and used since these are static methods. Eg.
Period incorrectUsage = Period.ofYears(2).ofWeeks(2); // every 2 week

Now lets try printing out a Period value -

System.out.println(Period.of(2, 12, 21));

It outputs : P2Y12M21D

Now lets analyze this string. It starts with a P denoting it's  a period. Then you have Y for years, M for months and D for day. If any of the parameters are not present then Java simply omits it (it will not say 0M).

Eg.

System.out.println(Period.of(2, 0, 21));

prints: P2Y21D
Also values greater than expected limit is fine. You can give months value as 14 and Java takes care of it.

Eg.

System.out.println(Period.of(2, 14, 21));

prints : P2Y14M21D

But while computing it will subtract 14M = 1Y and 2M. You get the point. Let's see one example of how it is used and then we move to next topic -

        LocalDate localDate = LocalDate.of(2017, 8, 2);
        System.out.println(locateDate.plus(Period.of(0, 1, 2)));


print : 2017-09-04
(Adds 0 years, 1 month and 2days to existing date.)

Also note the parameters in duration are Y,M and D. So if you give something like -

        System.out.println(Period.ofWeeks(2));

it will print : P14D

Using Duration

Same as Period but works with LocalTime rather than LocalDate. Like Period began with P Duration begins with PT (Period of time). As Period had parameters - Years,Months,Days - Y,M,D duration has Hours.Minutes,Seconds H,M,S.

Some examples -
Duration twoDays = Duration.ofDays(2); // PT48H
Duration hourly = Duration.ofHours(1); // PT1H
Duration everyTwoMinute = Duration.ofMinutes(2); // PT2M
Duration everyTwentySeconds = Duration.ofSeconds(20); // PT20S
Duration everyMilli = Duration.ofMillis(1); // PT0.001S

Duration and Period Usage 

Do not mix Period with Time and Duration with Date. Period is intended to be used with Date and Duration with Time. Refer following picture for reference.

Working with Instants

Instant class represents a specific moment in time in terms of GMT time zone. Simple example -

        Instant ins1 = Instant.now();
        Thread.sleep(1000);
        Instant ins2 = Instant.now();
        System.out.println(Duration.between(ins1, ins2));

and it prints : PT1.001S
That's a second and some time needed for processing.  You cannot use Instant with LocalDate or LocalTime or LocalDateTime. It has to be ZonedDateTime since it is associated with a time zone.

NOTE : Instant is related to ZonedDateTime but it always corresponds to GMT i.e point in time as per GMT.

Accounting for Daylight Savings Time

Daylight savings time is obeserved in some countries. In this clock is adjusted by an hour twice a year to make better use of the sunlight. This transition happens on Weekend Sunday 2 AM. So when clock is at 2 AM it is moved to 3 AM.

Lets say June 29, 2017, is the weekend that clocks spring ahead for daylight savings time. Now consider following code -

LocalDate date = LocalDate.of(2017, Month.JUNE, 29);
LocalTime time = LocalTime.of(1, 30);
ZoneId usZone = ZoneId.of("US/Eastern");
ZonedDateTime zonedDateTime1 = ZonedDateTime.of(date, time, usZone);
ZonedDateTime zonedDateTime2 = dateTime1.plus(1, ChronoUnit.HOURS);
long hours = ChronoUnit.HOURS.between(zonedDateTime1, zonedDateTime2);
int hours1 = dateTime1.getHour();
int hours2 = dateTime2.getHour();
System.out.println(hours + "," + hours1 + "," + hours2);

This will output 1,1,3. While the values are two hours apart, the time zone offset changes as well, making it only change from 6:30 GMT to 7:30 GMT.

NOTE : Java is smart enough to adjust for daylight savings time. So you don't have to worry about handling it in code. Logic may differ but you don't have to worry that it might cause exception or error.

Related Links


No comments:

Post a Comment

t> UA-39527780-1 back to top