27
27
import org .elasticsearch .common .unit .TimeValue ;
28
28
29
29
import java .io .IOException ;
30
- import java .time .DayOfWeek ;
31
30
import java .time .Instant ;
31
+ import java .time .LocalDate ;
32
32
import java .time .LocalDateTime ;
33
33
import java .time .LocalTime ;
34
34
import java .time .OffsetDateTime ;
39
39
import java .time .temporal .ChronoUnit ;
40
40
import java .time .temporal .IsoFields ;
41
41
import java .time .temporal .TemporalField ;
42
+ import java .time .temporal .TemporalQueries ;
42
43
import java .time .zone .ZoneOffsetTransition ;
44
+ import java .time .zone .ZoneRules ;
43
45
import java .util .List ;
44
46
import java .util .Objects ;
45
47
@@ -185,13 +187,11 @@ static class TimeUnitRounding extends Rounding {
185
187
TimeUnitRounding (DateTimeUnit unit , ZoneId timeZone ) {
186
188
this .unit = unit ;
187
189
this .timeZone = timeZone ;
188
- this .unitRoundsToMidnight = this .unit .field .getBaseUnit ().getDuration ().toMillis () > 60L * 60L * 1000L ;
190
+ this .unitRoundsToMidnight = this .unit .field .getBaseUnit ().getDuration ().toMillis () > 3600000L ;
189
191
}
190
192
191
193
TimeUnitRounding (StreamInput in ) throws IOException {
192
- unit = DateTimeUnit .resolve (in .readByte ());
193
- timeZone = DateUtils .of (in .readString ());
194
- unitRoundsToMidnight = unit .getField ().getBaseUnit ().getDuration ().toMillis () > 60L * 60L * 1000L ;
194
+ this (DateTimeUnit .resolve (in .readByte ()), DateUtils .of (in .readString ()));
195
195
}
196
196
197
197
@ Override
@@ -200,85 +200,67 @@ public byte id() {
200
200
}
201
201
202
202
private LocalDateTime truncateLocalDateTime (LocalDateTime localDateTime ) {
203
- localDateTime = localDateTime .withNano (0 );
204
- assert localDateTime .getNano () == 0 ;
205
- if (unit .equals (DateTimeUnit .SECOND_OF_MINUTE )) {
206
- return localDateTime ;
207
- }
203
+ switch (unit ) {
204
+ case SECOND_OF_MINUTE :
205
+ return localDateTime .withNano (0 );
208
206
209
- localDateTime = localDateTime .withSecond (0 );
210
- assert localDateTime .getSecond () == 0 ;
211
- if (unit .equals (DateTimeUnit .MINUTES_OF_HOUR )) {
212
- return localDateTime ;
213
- }
207
+ case MINUTES_OF_HOUR :
208
+ return LocalDateTime .of (localDateTime .getYear (), localDateTime .getMonthValue (), localDateTime .getDayOfMonth (),
209
+ localDateTime .getHour (), localDateTime .getMinute (), 0 , 0 );
214
210
215
- localDateTime = localDateTime .withMinute (0 );
216
- assert localDateTime .getMinute () == 0 ;
217
- if (unit .equals (DateTimeUnit .HOUR_OF_DAY )) {
218
- return localDateTime ;
219
- }
211
+ case HOUR_OF_DAY :
212
+ return LocalDateTime .of (localDateTime .getYear (), localDateTime .getMonth (), localDateTime .getDayOfMonth (),
213
+ localDateTime .getHour (), 0 , 0 );
220
214
221
- localDateTime = localDateTime .withHour (0 );
222
- assert localDateTime .getHour () == 0 ;
223
- if (unit .equals (DateTimeUnit .DAY_OF_MONTH )) {
224
- return localDateTime ;
225
- }
215
+ case DAY_OF_MONTH :
216
+ LocalDate localDate = localDateTime .query (TemporalQueries .localDate ());
217
+ return localDate .atStartOfDay ();
226
218
227
- if (unit .equals (DateTimeUnit .WEEK_OF_WEEKYEAR )) {
228
- localDateTime = localDateTime .with (ChronoField .DAY_OF_WEEK , 1 );
229
- assert localDateTime .getDayOfWeek () == DayOfWeek .MONDAY ;
230
- return localDateTime ;
231
- }
219
+ case WEEK_OF_WEEKYEAR :
220
+ return LocalDateTime .of (localDateTime .toLocalDate (), LocalTime .MIDNIGHT ).with (ChronoField .DAY_OF_WEEK , 1 );
232
221
233
- localDateTime = localDateTime .withDayOfMonth (1 );
234
- assert localDateTime .getDayOfMonth () == 1 ;
235
- if (unit .equals (DateTimeUnit .MONTH_OF_YEAR )) {
236
- return localDateTime ;
237
- }
222
+ case MONTH_OF_YEAR :
223
+ return LocalDateTime .of (localDateTime .getYear (), localDateTime .getMonthValue (), 1 , 0 , 0 );
238
224
239
- if (unit .equals (DateTimeUnit .QUARTER_OF_YEAR )) {
240
- int quarter = (int ) IsoFields .QUARTER_OF_YEAR .getFrom (localDateTime );
241
- int month = ((quarter - 1 ) * 3 ) + 1 ;
242
- localDateTime = localDateTime .withMonth (month );
243
- assert localDateTime .getMonthValue () % 3 == 1 ;
244
- return localDateTime ;
245
- }
225
+ case QUARTER_OF_YEAR :
226
+ int quarter = (int ) IsoFields .QUARTER_OF_YEAR .getFrom (localDateTime );
227
+ int month = ((quarter - 1 ) * 3 ) + 1 ;
228
+ return LocalDateTime .of (localDateTime .getYear (), month , 1 , 0 , 0 );
246
229
247
- if (unit .equals (DateTimeUnit .YEAR_OF_CENTURY )) {
248
- localDateTime = localDateTime .withMonth (1 );
249
- assert localDateTime .getMonthValue () == 1 ;
250
- return localDateTime ;
251
- }
230
+ case YEAR_OF_CENTURY :
231
+ return LocalDateTime .of (LocalDate .of (localDateTime .getYear (), 1 , 1 ), LocalTime .MIDNIGHT );
252
232
253
- throw new IllegalArgumentException ("NOT YET IMPLEMENTED for unit " + unit );
233
+ default :
234
+ throw new IllegalArgumentException ("NOT YET IMPLEMENTED for unit " + unit );
235
+ }
254
236
}
255
237
256
238
@ Override
257
- public long round (long utcMillis ) {
239
+ public long round (final long utcMillis ) {
240
+ Instant instant = Instant .ofEpochMilli (utcMillis );
258
241
if (unitRoundsToMidnight ) {
259
- final ZonedDateTime zonedDateTime = Instant .ofEpochMilli (utcMillis ).atZone (timeZone );
260
- final LocalDateTime localDateTime = zonedDateTime .toLocalDateTime ();
242
+ final LocalDateTime localDateTime = LocalDateTime .ofInstant (instant , timeZone );
261
243
final LocalDateTime localMidnight = truncateLocalDateTime (localDateTime );
262
244
return firstTimeOnDay (localMidnight );
263
245
} else {
246
+ final ZoneRules rules = timeZone .getRules ();
264
247
while (true ) {
265
- final Instant truncatedTime = truncateAsLocalTime (utcMillis );
266
- final ZoneOffsetTransition previousTransition = timeZone . getRules (). previousTransition (Instant . ofEpochMilli ( utcMillis ) );
248
+ final Instant truncatedTime = truncateAsLocalTime (instant , rules );
249
+ final ZoneOffsetTransition previousTransition = rules . previousTransition (instant );
267
250
268
251
if (previousTransition == null ) {
269
252
// truncateAsLocalTime cannot have failed if there were no previous transitions
270
253
return truncatedTime .toEpochMilli ();
271
254
}
272
255
273
- final long previousTransitionMillis = previousTransition .getInstant ().toEpochMilli ();
274
-
275
- if (truncatedTime != null && previousTransitionMillis <= truncatedTime .toEpochMilli ()) {
256
+ Instant previousTransitionInstant = previousTransition .getInstant ();
257
+ if (truncatedTime != null && previousTransitionInstant .compareTo (truncatedTime ) < 1 ) {
276
258
return truncatedTime .toEpochMilli ();
277
259
}
278
260
279
261
// There was a transition in between the input time and the truncated time. Return to the transition time and
280
262
// round that down instead.
281
- utcMillis = previousTransitionMillis - 1 ;
263
+ instant = previousTransitionInstant . minusNanos ( 1_000_000 ) ;
282
264
}
283
265
}
284
266
}
@@ -289,7 +271,7 @@ private long firstTimeOnDay(LocalDateTime localMidnight) {
289
271
290
272
// Now work out what localMidnight actually means
291
273
final List <ZoneOffset > currentOffsets = timeZone .getRules ().getValidOffsets (localMidnight );
292
- if (currentOffsets .size () >= 1 ) {
274
+ if (currentOffsets .isEmpty () == false ) {
293
275
// There is at least one midnight on this day, so choose the first
294
276
final ZoneOffset firstOffset = currentOffsets .get (0 );
295
277
final OffsetDateTime offsetMidnight = localMidnight .atOffset (firstOffset );
@@ -302,23 +284,23 @@ private long firstTimeOnDay(LocalDateTime localMidnight) {
302
284
}
303
285
}
304
286
305
- private Instant truncateAsLocalTime (long utcMillis ) {
287
+ private Instant truncateAsLocalTime (Instant instant , final ZoneRules rules ) {
306
288
assert unitRoundsToMidnight == false : "truncateAsLocalTime should not be called if unitRoundsToMidnight" ;
307
289
308
- final LocalDateTime truncatedLocalDateTime
309
- = truncateLocalDateTime (Instant . ofEpochMilli ( utcMillis ). atZone ( timeZone ). toLocalDateTime () );
310
- final List <ZoneOffset > currentOffsets = timeZone . getRules () .getValidOffsets (truncatedLocalDateTime );
290
+ LocalDateTime localDateTime = LocalDateTime . ofInstant ( instant , timeZone );
291
+ final LocalDateTime truncatedLocalDateTime = truncateLocalDateTime (localDateTime );
292
+ final List <ZoneOffset > currentOffsets = rules .getValidOffsets (truncatedLocalDateTime );
311
293
312
- if (currentOffsets .size () >= 1 ) {
294
+ if (currentOffsets .isEmpty () == false ) {
313
295
// at least one possibilities - choose the latest one that's still no later than the input time
314
296
for (int offsetIndex = currentOffsets .size () - 1 ; offsetIndex >= 0 ; offsetIndex --) {
315
297
final Instant result = truncatedLocalDateTime .atOffset (currentOffsets .get (offsetIndex )).toInstant ();
316
- if (result .toEpochMilli () <= utcMillis ) {
298
+ if (result .isAfter ( instant ) == false ) {
317
299
return result ;
318
300
}
319
301
}
320
302
321
- assert false : "rounded time not found for " + utcMillis + " with " + this ;
303
+ assert false : "rounded time not found for " + instant + " with " + this ;
322
304
return null ;
323
305
} else {
324
306
// The chosen local time didn't happen. This means we were given a time in an hour (or a minute) whose start
@@ -328,7 +310,7 @@ private Instant truncateAsLocalTime(long utcMillis) {
328
310
}
329
311
330
312
private LocalDateTime nextRelevantMidnight (LocalDateTime localMidnight ) {
331
- assert localMidnight .toLocalTime ().equals (LocalTime .of ( 0 , 0 , 0 ) ) : "nextRelevantMidnight should only be called at midnight" ;
313
+ assert localMidnight .toLocalTime ().equals (LocalTime .MIDNIGHT ) : "nextRelevantMidnight should only be called at midnight" ;
332
314
assert unitRoundsToMidnight : "firstTimeOnDay should only be called if unitRoundsToMidnight" ;
333
315
334
316
switch (unit ) {
@@ -350,8 +332,7 @@ private LocalDateTime nextRelevantMidnight(LocalDateTime localMidnight) {
350
332
@ Override
351
333
public long nextRoundingValue (long utcMillis ) {
352
334
if (unitRoundsToMidnight ) {
353
- final ZonedDateTime zonedDateTime = Instant .ofEpochMilli (utcMillis ).atZone (timeZone );
354
- final LocalDateTime localDateTime = zonedDateTime .toLocalDateTime ();
335
+ final LocalDateTime localDateTime = LocalDateTime .ofInstant (Instant .ofEpochMilli (utcMillis ), timeZone );
355
336
final LocalDateTime earlierLocalMidnight = truncateLocalDateTime (localDateTime );
356
337
final LocalDateTime localMidnight = nextRelevantMidnight (earlierLocalMidnight );
357
338
return firstTimeOnDay (localMidnight );
@@ -433,14 +414,14 @@ public byte id() {
433
414
@ Override
434
415
public long round (final long utcMillis ) {
435
416
final Instant utcInstant = Instant .ofEpochMilli (utcMillis );
436
- final LocalDateTime rawLocalDateTime = Instant . ofEpochMilli ( utcMillis ). atZone ( timeZone ). toLocalDateTime ( );
417
+ final LocalDateTime rawLocalDateTime = LocalDateTime . ofInstant ( utcInstant , timeZone );
437
418
438
419
// a millisecond value with the same local time, in UTC, as `utcMillis` has in `timeZone`
439
420
final long localMillis = utcMillis + timeZone .getRules ().getOffset (utcInstant ).getTotalSeconds () * 1000 ;
440
421
assert localMillis == rawLocalDateTime .toInstant (ZoneOffset .UTC ).toEpochMilli ();
441
422
442
423
final long roundedMillis = roundKey (localMillis , interval ) * interval ;
443
- final LocalDateTime roundedLocalDateTime = Instant .ofEpochMilli (roundedMillis ). atZone ( ZoneOffset .UTC ). toLocalDateTime ( );
424
+ final LocalDateTime roundedLocalDateTime = LocalDateTime . ofInstant ( Instant .ofEpochMilli (roundedMillis ), ZoneOffset .UTC );
444
425
445
426
// Now work out what roundedLocalDateTime actually means
446
427
final List <ZoneOffset > currentOffsets = timeZone .getRules ().getValidOffsets (roundedLocalDateTime );
@@ -485,9 +466,8 @@ private static long roundKey(long value, long interval) {
485
466
@ Override
486
467
public long nextRoundingValue (long time ) {
487
468
int offsetSeconds = timeZone .getRules ().getOffset (Instant .ofEpochMilli (time )).getTotalSeconds ();
488
- return ZonedDateTime .ofInstant (Instant .ofEpochMilli (time ), ZoneOffset .UTC )
489
- .plusSeconds (offsetSeconds )
490
- .plusNanos (interval * 1_000_000 )
469
+ long millis = time + interval + offsetSeconds * 1000 ;
470
+ return ZonedDateTime .ofInstant (Instant .ofEpochMilli (millis ), ZoneOffset .UTC )
491
471
.withZoneSameLocal (timeZone )
492
472
.toInstant ().toEpochMilli ();
493
473
}
0 commit comments