Fixed Minute Timers

03 March 2017 ~ blog groovy

A co-worker of mine recently complained about how our metrics reporting component wasn’t smart enough to start the reporting timer on discrete minutes, but rather it just starts reporting at the start time, for example if you start the application at 10:42:34 and report every five minutes, you will get events recorded at the following times:

10:47:34
10:52:34
10:57:34

which can be annoying for a user when you want to determine metric changes over time, especially when using a dashboard tool. While this is a critical defect, it can be a data quality annoyance and it turns out, its not all that hard to fix.

What you want to do is determine the delay between now and the next desired minute-mark, the five-minute mark in our case, and you can do this with a little math:

def delay(final int mark, final LocalTime now = LocalTime.now()){
    int minute = now.minute + 1

    int minuteMark = minute % mark
    minuteMark = minuteMark == 0 ? minute : minute - minuteMark + mark

    int hour = now.hour
    if( minuteMark == 60 ){
        hour++
        minuteMark = 0
    }

    now.until(LocalTime.of(hour, minuteMark, 0, 0), ChronoUnit.MILLIS)
}

where mark is your minute-mark (5), and now is either a time you pass in (mostly for testing) or the current time by default. We need to roll the minute hand forward one to account for how far we are already into that minute and then we figure out what the next minute-mark is (accounting for hour-boundary rollover). Then you determine the difference between that next interval time and the current time, returning that value.

This does not account for the time spent in the method itself, but we are working on the scale of minutes and seconds here so our calculation time should not matter.

A full example of using this to schedule a timer on the five-minute marks follows:

import java.time.*
import java.time.temporal.*
import java.util.concurrent.*

def delay(final int mark, final LocalTime now = LocalTime.now()){
    int minute = now.minute + 1

    int minuteMark = minute % mark
    minuteMark = minuteMark == 0 ? minute : minute - minuteMark + mark

    int hour = now.hour
    if( minuteMark == 60 ){
        hour++
        minuteMark = 0
    }

    now.until(LocalTime.of(hour, minuteMark, 0, 0), ChronoUnit.MILLIS)
}

def now = LocalTime.now()
println "Now: $now"

int mark = 1
long delay = delay(mark,now)
println "Delay: $delay ms"
println "Start: ${now.plus(delay, ChronoUnit.MILLIS)}"

ScheduledExecutorService ses = Executors.newScheduledThreadPool(1)
ses.scheduleAtFixedRate({
    println LocalTime.now()
}, delay, mark*60000, TimeUnit.MILLISECONDS)

which will print out something like:

Now: 08:33:22.611
Delay: 97389 ms
Start: 08:35
08:35:00.088
08:40:00.088
08:45:00.088

These nice clean time bounaries lead to cleaner reporting visualizations. You can use any minute as the mark points, but generally you would use: 1, 5, 10, 15, 20, 30, or 60.


Creative Commons License CoffeaElectronica.com content is copyright © 2016 Christopher J. Stehno and available under a Creative Commons Attribution-ShareAlike 4.0 International License.