Time

Time for non-Time Lords – Part 4

Time is hard. Any developer who tells you differently is either lying; thinks they understand it when they really don’t; or is a bona fide Time Lord. In this series we’ll explore some of the issues that time can pose, and take a look at some tools that we can use to help make time handling a little easier.

Previously we’ve looked at the basic use of the JSR 310 time and date APIs, what other benefits do we get? Remember those tricky leap year issues we looked at in an earlier article? Let’s take a look at whether they are going to be an issue if we use JSR 310.

To help us explore this we can actually use the Year class which, strangely enough, represents a year. This actually has a rather useful isLeap() method which does precisely what we need. Here we create a list of different years and display whether or not they are leap years:

listOf(2016, 2107, 2000, 1900, 2100, 2400).forEach { year ->
    println("$year is a leap year: ${Year.of(year).isLeap}")
}

If we run this we get the following output:

I: 2016 is a leap year: true
I: 2107 is a leap year: false
I: 2000 is a leap year: true
I: 1900 is a leap year: false
I: 2100 is a leap year: false
I: 2400 is a leap year: true

To recap on the reasoning behind this:

  • 2016 is evenly divisible by 4 so is a leap year
  • 2017 is not evenly divisible by 4 so is not a leap year
  • 2000 is evenly divisible by 4, is evenly divisible by 100 and is evenly divisible by 400 so is a leap year
  • 1900 is evenly divisible by 4, is evenly divisible by 100 and is not evenly divisible by 400 so is not a leap year
  • 2100 is evenly divisible by 4, is evenly divisible by 100 and is not evenly divisible by 400 so is not a leap year
  • 2400 is evenly divisible by 4, is evenly divisible by 100 and is evenly divisible by 400 so is a leap year

So we get leap year handling correctly done for us using these APIs.

The other thing we looked at earlier was using time units to make our APIs easier to understand by allowing the caller to specify the units of the time value it is supplying. The approach we looked at earlier is perfectly valid and there is absolutely no reason to change that approach. However, JSR 310 does provide a similar mechanism for specifying time units.

ChronoUnit is an enum containing various units, and each is resolvable to a Duration. We can display all of these in nanoseconds:

ChronoUnit.values()
        .filter { it.duration < ChronoUnit.MILLENNIA.duration }
        .forEach { unit ->
            println("$unit is ${unit.duration.toNanos()} ns")
        }

The reason for the filter is that the duration in nanoseconds is stored in a Long and this overflows if we try and convert a MILLENNIA or larger duration to nanoseconds.

The output of this is:

I: Nanos is 1 ns
I: Micros is 1000 ns
I: Millis is 1000000 ns
I: Seconds is 1000000000 ns
I: Minutes is 60000000000 ns
I: Hours is 3600000000000 ns
I: HalfDays is 43200000000000 ns
I: Days is 86400000000000 ns
I: Weeks is 604800000000000 ns
I: Months is 2629746000000000 ns
I: Years is 31556952000000000 ns
I: Decades is 315569520000000000 ns
I: Centuries is 3155695200000000000 ns

Resolving the unit to a duration enables us to convert to whatever units we like – so the caller specifies the units it provides and is agnostic about the units required by the method implementation; and the method implementation is agnostic about the units it is called with, but has a simple mechanism to convert them to the units it requires.

Let’s alter our earlier example to use ChronoUnit instead of TimeUnit:

text.alpha = 0f
text.animate()
        .alpha(1f)
        .setDuration(1, ChronoUnit.SECONDS)
        .start()

The caller is almost identical – we just change second argument to a ChronoUnit instead of a TimeUnit.

The implementation of this method is also pretty straightforward:

fun ViewPropertyAnimator.setDuration(durationInUnits: Long, unit: ChronoUnit): ViewPropertyAnimator =
        apply {
            duration = unit.duration.toMillis() * durationInUnits
        }

We get the Duration for the unit, convert that to milliseconds (the value that we use internally), and multiply this by the number of units.

I will re-iterate that this offers no benefits over TimeUnit other than being consistent in the use of JSR 310 APIs.

So far we’ve ignored an important aspect of handling date and time values: Timezones. In the final article in this series we’ll take a look at that.

The source code for this article is available here.

© 2017, Mark Allison. All rights reserved.

Copyright © 2017 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.