online localdatetime joda examples example dates current create java jodatime java-time

localdatetime - jodatime java 8



¿Cómo divides un período de tiempo en intervalos iguales y encuentras el actual? (5)

Bueno, como ya se ha dicho, encontrar el intervalo de Duration contenida como ya lo está haciendo o usando milis directamente es suficiente para este caso de uso, y las matemáticas involucradas son sencillas. Sin embargo, si tenía un caso de uso que justificaba un intervalo de Period que implicaba horas, así es como podría manejarse:

  1. Convierta el Period en una duración aproximada de horas y úselo para estimar a cuántos intervalos de distancia está el objetivo desde el origen.
  2. Escala el Period según tu estimación. Tratar agregaciones de 24 horas como días adicionales.
  3. Mueva el intervalo de origen por el período escalado. Si el intervalo movido contiene su objetivo, ha terminado. Si no, vuelva a estimar en base a la cantidad que perdió.

Una cosa importante a tener en cuenta al encontrar el intervalo que contiene es que toda la adición del Period debe realizarse desde el origen directamente y no desde intervalos intermedios para la coherencia. Por ejemplo, si tiene un Period de un mes con un origen del 31 de enero, los intervalos inmediatamente posteriores al intervalo de origen deben comenzar el 28 de febrero y el 31 de marzo. Agregar dos meses al 31 de enero producirá correctamente el 31 de marzo, pero agregar un mes al 28 de febrero dará un rendimiento incorrecto el 28 de marzo.

El siguiente es el código para el enfoque anterior. Tenga en cuenta que hay muchas situaciones anómalas para este tipo de cosas, y solo probé algunas de ellas, así que no considere este código como rigurosamente probado.

public static final int NUM_HOURS_IN_DAY = 24; public static final int NUM_HOURS_IN_MONTH = 730; // approximate public ZonedDateTime startOfContainingInterval(ZonedDateTime origin, Period period, int hours, ZonedDateTime target) { return intervalStart(origin, period, hours, containingIntervalNum(origin, period, hours, target)); } public int containingIntervalNum(ZonedDateTime origin, Period period, int hours, ZonedDateTime target) { int intervalNum = 0; ZonedDateTime intervalStart = origin, intervalFinish; long approximatePeriodHours = period.toTotalMonths() * NUM_HOURS_IN_MONTH + period.getDays() * NUM_HOURS_IN_DAY + hours; do { long gap = ChronoUnit.HOURS.between(intervalStart, target); long estimatedIntervalsAway = Math.floorDiv(gap, approximatePeriodHours); intervalNum += estimatedIntervalsAway; intervalStart = intervalStart(origin, period, hours, intervalNum); intervalFinish = intervalStart(origin, period, hours, intervalNum + 1); } while (!(target.isAfter(intervalStart) && target.isBefore(intervalFinish) || target.equals(intervalStart))); return intervalNum; } public ZonedDateTime intervalStart(ZonedDateTime origin, Period period, int hours, int intervalNum) { Period scaledPeriod = period.multipliedBy(intervalNum).plusDays(hours * intervalNum / NUM_HOURS_IN_DAY); long leftoverHours = hours * intervalNum % NUM_HOURS_IN_DAY; return origin.plus(scaledPeriod).plusHours(leftoverHours); }

Necesito programar un trabajo periódico para muchos usuarios. Este trabajo se ejecutará a una tasa fija, el intervalo. Quiero distribuir la ejecución del trabajo para cada usuario de manera uniforme durante ese intervalo. Por ejemplo, si el intervalo es de 4 días, usaría una función de hashing consistente con un identificador para que cada usuario programe el trabajo al mismo tiempo, por ejemplo. cada 4 días, en el 3er día.

El intervalo es relativo a un instante de origen que es el mismo para todos los usuarios. Dado ese instante de origen, como Instant#EPOCH o algún otro valor constante, ¿cómo puedo encontrar la fecha de inicio del intervalo actual?

puedo hacer

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);

Entonces puedo usar el cursor para programar el trabajo en un Instant relativo al inicio del intervalo actual.

Aquí hay muchos cálculos y no estoy seguro de que la transformación a milisegundos en todas partes respete las fechas reales. ¿Hay una manera más precisa de dividir el tiempo entre dos instantes y encontrar el (la subdivisión) en que estamos actualmente?


Creo que estás complicando demasiado las cosas. No necesita saber tanto como sugiere su código.

Solo tiene que responder "¿cuándo debería ejecutarse el siguiente objeto?", De modo que la respuesta se distribuya estadísticamente de manera uniforme en el intervalo y sea coherente (no dependa de "ahora", excepto que la próxima ejecución siempre esté después de "ahora").

Este método hace eso:

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; }

Este método devuelve la próxima vez que el objeto debe ejecutarse utilizando su hashCode() para determinar dónde debe programarse dentro de la duración y luego devuelve la siguiente hora real que sucederá.

Pequeña nota de implementación: se Math.abs(obj.hashCode() % interval) lugar de Math.abs(obj.hashCode()) % interval para protegerse contra el hashCode() devuelve Integer.MIN_VALUE y sabe que Math.abs(Integer.MIN_VALUE) == Integer.MIN_VALUE

Si necesita que se java.time clases java.time en su API, aquí tiene el mismo código pero con los parámetros java.time y el tipo de retorno:

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); }

Para ayudar a entender las matemáticas, aquí hay una versión más larga con los cálculos de los componentes desglosados ​​y asignados a nombres de variables significativos:

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); }


Intentaría definir cada período de tiempo como un objeto con una fecha de inicio y finalización. Luego use un árbol RB para almacenar los objetos del período. Luego puedes navegar por el árbol para una fecha específica:

Si la fecha está dentro del primer período, la has encontrado. si la fecha es anterior a la fecha de inicio del período, navegue al nodo izquierdo y verifique ese período si la fecha es posterior a la fecha de finalización del período, navegue al nodo derecho y verifique ese período


Si solo desea reducir las matemáticas aquí, puede usar el resto en lugar de una división y multiplicación.

long millisSinceIntervalStart = sinceOrigin % millisPerInterval; Instant startNext = now.minusMillis(millisSinceIntervalStart);

Aquí no tiene que calcular el número de intervalos pasados ​​desde el origen. Solo obtén el tiempo transcurrido desde el inicio del intervalo y réstalo del tiempo actual.

Además, su startNext parece indicar el inicio del intervalo actual, no el siguiente intervalo. ¿Correcto?


Suponiendo que esté realmente interesado en instantes y duraciones (es decir, no tiene nada que ver con períodos, fechas, zonas horarias, etc.), entonces su código debería estar bien. En realidad, en milisegundos antes, en este caso ... las matemáticas son muy simples aquí.

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); }

Esto supone que no tiene que preocuparse por estar antes de la epoch , momento en el que tendrá que hacer un poco de trabajo, ya que desea el piso de la operación de división, en lugar de realizar un corte hacia 0.

(Si solo desea el inicio, simplemente puede devolver new Instant(start) .)