How do you divide a time period into equal intervals and find the current one?

I need to schedule a periodic job for lots of users. This job will run at a fixed rate, the interval. I want to distribute the execution of the job for each user uniformly over that interval. For example, if the interval is 4 days, I'd use a consistent hashing function with an identifier for each user to schedule the job at the same time, eg. every 4 days, on the 3rd day.

The interval is relative to an origin instant that is the same for all users. Given such an origin instant, like Instant#EPOCH or some other constant value, how do I find the start date of the current interval?

I can do

Instant now = Instant.now();
Instant origin = Instant.EPOCH;
Duration interval = Duration.ofDays(4);

Duration duration = Duration.between(origin, now);
long sinceOrigin = duration.toMillis();
long millisPerInterval = interval.toMillis();

long intervalsSince = sinceOrigin / millisPerInterval;
Instant startNext = origin.plus(interval.multipliedBy(intervalsSince));

int cursor = distributionStrategy.distribute(hashCode, millisPerInterval);

I can then use the cursor to schedule the job at an Instant relative to the start of the current interval.

There's a lot of math here and I'm not sure the transformation to milliseconds everywhere will uphold actual dates. Is there a more precise way of dividing the time between two instants and finding the one (the subdivision) we are in currently?

Jon Skeet
people
quotationmark

Assuming you're actually interested in instants and durations (i.e. nothing to do with periods, dates, time zones etc) then your code should be fine. I'd actually go into milliseconds earlier in this case... the maths is all simple here.

Interval getInterval(Instant epoch, Duration duration, Instant now) {
    long epochMillis = epoch.getMillis();
    long durationMillis = duration.getMillis();

    long millisSinceEpoch = now.getMillis() - epochMillis;        
    long periodNumber = millisSinceEpoch / durationMillis;
    long start = epochMillis + periodNumber * durationMillis;
    return new Interval(start, start + durationMillis);
}

This assumes you don't need to worry about now being before epoch - at which point you'd have to do a bit of work as you want the floor of the division operation, rather than truncation towards 0.

(If you only want the start, you could just return new Instant(start).)

people

See more on this question at Stackoverflow