Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check whether a given Instant fits a defined Period

Tags:

java

java-time

What we get is an Instant and a "date-grid" defined by a period (which defines the interval of datapoints, e.g.: Every Month, Every 3 Months, etc.) and a start date where we started that grid.

private Instant getValidDate(Instant request, Instant start, Period period) {
    if(isOnGrid(request, start, period)) {
        return request;
    }
    else {
        return getNextPriorDateOnGrid(request, start, period);
    }
}

An example: Given are the following parameters:

request = Instant("2000-05-02T07:42:00.000Z") //Second May of 2000 7:42 AM
start = Instant("2000-01-01T06:00:00.000Z") //First January of 2000 6:00 AM
period = Period("3M") //Every 3 Months

isOnGrid(request, start, period); //Should return false
getNextPriorDate(request, start, period) //Should return the First April of 2000 6:00 AM

I really have no idea how to get this with reasonable performance (its a critical place in code)

How do you check whether a distant future date (given by the Instant) is exactly on this grid, and if not, what is the next past date that was on the grid?

EDIT: I forgot to mention: All times and dates are assumed to be in UTC Timezone

like image 767
FelixZett Avatar asked Oct 12 '25 17:10

FelixZett


1 Answers

You cannot add Periods to Instants. They have a different "scope".

An Instant i simply represents a point in the timeline, counting the amount of millis/nanos from a specific point in time called "Epoch".
At this instant i, the time at the clock at the wall (even the date in a calendar) differs around the world. It depends on the timezone you are in.

A Period respects different lengths of its representation among different timezones starting at differnt dates. For example: A month lasts 30 days in June but 31 days in August. And it is even more complex if daylight saving shifts occur.
An Instant has no idea, what a "month" actually is. You can parse it from a String and output it to it, but internally it does not represent a human understandable form of a month like 'Jan', 'Feb', ... .

This is, why you have to align an Instant to a LocalDateTime or ZonedDateTime using a ZoneId or an ZoneOffset. Theses classes understand and can work with Periods.

The following code converts your Instants to LocalDateTimes to take into account the above comments:

private static Instant getValidDate2(Instant request, Instant start, Period period)
{
    assert(!request.isBefore(start));

    // multiplication of period only works with days exclusive or
    // zero daypart of period
    assert(period.getDays() == 0 || (period.getMonths() == 0 && period.getYears() == 0));

    ZoneId utcZone = ZoneOffset.UTC;

    LocalDateTime ldstart = LocalDateTime.ofInstant(start, utcZone);
    LocalDateTime ldreq = LocalDateTime.ofInstant(request, utcZone);

    // calculate an approximation of how many periods have to be applied to get near request
    Duration simpleDuration = Duration.between(ldstart, ldstart.plus(period));
    Duration durationToReq = Duration.between(ldstart, ldreq);
    int factor = (int) (durationToReq.toDays() / simpleDuration.toDays()); // rough approximation

    // go near to request by a multiple of period 
    Period jump = Period.of(period.getYears() * factor, period.getMonths() * factor, period.getDays() * factor);
    LocalDateTime ldRunning = ldstart.plus(jump);

    // make sure ldRunning < request
    while (ldRunning.isAfter(ldreq)) {
        ldRunning = ldRunning.minus(period);
    }

    // make sure we pass request and 
    // save the the last date before or equal to request on the grid
    LocalDateTime ldLastbefore = ldRunning;
    while (!ldRunning.isAfter(ldreq)) {            
        ldLastbefore = ldRunning;
        ldRunning = ldRunning.plus(period);
    }

    return ldLastbefore.equals(ldreq) ? request : ldLastbefore.atZone(utcZone).toInstant();
}

Explanation:
To avoid a loop adding period until it gets to request, a rough approximation is done on how often period must be added to start to come to request. A new period being a multiple of the request period is then added and aligned to get the last value of the grid which is less or equal to request. Depending on a comparation between the last value and request, the according instant is returned. In fact, the check is useless besides the fact, that request == request when it was on the grid and not only equal.

Here you can find further informations about java time: https://docs.oracle.com/javase/tutorial/datetime/overview/index.html


Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!