如何将时间段划分为相等的时间间隔并找到当前时间段?

Sav*_*ior 26 java jodatime java-time

我需要为很多用户安排定期工作.此作业将以固定速率运行,间隔时间.我希望在该时间间隔内统一分配每个用户的作业执行.例如,如果间隔是4天,我将使用一致的散列函数和每个用户的标识符来同时安排作业,例如.每隔4天,第3天.

间隔是相对于所有用户都相同的原始时刻.给定这样的原点瞬间,像Instant#EPOCH或其他一些常数值,如何找到当前间隔的开始日期?

我可以

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);
Run Code Online (Sandbox Code Playgroud)

然后我可以使用它cursor来调度Instant相对于当前间隔开始的作业.

这里有很多数学,我不确定在任何地方转换到毫秒都会维持实际日期.是否有更精确的方法来划分两个时刻之间的时间并找到我们目前所处的那一个(细分)?

Cod*_*der 12

如果你只想在这里减少数学,你可以使用余数而不是除法和乘法.

long millisSinceIntervalStart = sinceOrigin % millisPerInterval;
Instant startNext = now.minusMillis(millisSinceIntervalStart);
Run Code Online (Sandbox Code Playgroud)

在这里,您不必计算自原点以来经过的间隔数.只需从intervalStart开始计算时间并从当前时间中减去它.

此外,您startNext似乎表示当前间隔的开始,而不是下一个间隔.正确?


Jon*_*eet 5

假设你真的对瞬间和持续时间感兴趣(即与周期,日期,时区等无关),那么你的代码应该没问题.在这种情况下,我实际上要提前几毫秒......数学在这里很简单.

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);
}
Run Code Online (Sandbox Code Playgroud)

这假设你不必担心now以前epoch- 在这一点上你必须做一些工作,因为你想要分区操作的底线,而不是截断为0.

(如果你只想要开始,你可以回来new Instant(start).)


Boh*_*ian 5

我觉得你过于复杂了.您不需要知道代码建议的程度.

您只需要回答"此对象何时应该下次运行?",这样答案在统计上均匀分布在区间内且一致(不依赖于"现在",除了下一次运行总是在"现在"之后).

这种方法可以:

public static long nextRun(long origin, long interval, Object obj) {
    long nextRunTime = origin + (System.currentTimeMillis() - origin)
       / interval * interval + Math.abs(obj.hashCode() % interval);
    return nextRunTime > System.currentTimeMillis() ? nextRunTime : nextRunTime + interval;
}
Run Code Online (Sandbox Code Playgroud)

此方法在下次使用它运行对象时返回,hashCode()以确定应在其计划的持续时间内的位置,然后返回将发生的下一个实际时间.

小实施说明:Math.abs(obj.hashCode() % interval)用来代替Math.abs(obj.hashCode()) % interval防止hashCode()返回Integer.MIN_VALUE和了解Math.abs(Integer.MIN_VALUE) == Integer.MIN_VALUE


如果您需要java.time在API中使用这些类,这里的代码相同,但带有java.time参数和返回类型:

public static Instant nextRun(Instant origin, Duration interval, Object target) {
    long start = origin.toEpochMilli();
    long width = interval.toMillis();
    long nextRunTime = start + (System.currentTimeMillis() - start)
       / width * width + Math.abs(target.hashCode() % width);
    nextRunTime = nextRunTime > System.currentTimeMillis() ? nextRunTime : nextRunTime + width;
    return Instant.ofEpochMilli(nextRunTime);
}
Run Code Online (Sandbox Code Playgroud)

为了帮助理解数学,这里有一个较长的版本,其中组件计算被细分并分配给有意义的变量名称:

public static Instant nextRun(Instant origin, Duration duration, Object target) {
    long now = System.currentTimeMillis();
    long start = origin.toEpochMilli();
    long intervalWidth = duration.toMillis();
    long ageSinceOrigin = now - start;
    long totalCompleteDurations = ageSinceOrigin / intervalWidth * intervalWidth;
    long mostRecentIntervalStart = start + totalCompleteDurations;
    long offsetInDuration = Math.abs(target.hashCode() % intervalWidth);
    long nextRun = mostRecentIntervalStart + offsetInDuration;
    // schedule for next duration if this duration's time has already passed
    if (nextRun < now) { 
        nextRun += intervalWidth;
    }
    return Instant.ofEpochMilli(nextRun);
}
Run Code Online (Sandbox Code Playgroud)