@@ -70,30 +70,44 @@ public class Bucket extends GroupingFunction.EvaluatableGroupingFunction
7070
7171 // TODO maybe we should just cover the whole of representable dates here - like ten years, 100 years, 1000 years, all the way up.
7272 // That way you never end up with more than the target number of buckets.
73- private static final Function <ZoneId , Rounding > LARGEST_HUMAN_DATE_ROUNDING = (zoneId ) -> Rounding .builder (
74- Rounding .DateTimeUnit .YEAR_OF_CENTURY
75- ).timeZone (zoneId ).build ();
76- private static final List <Function <ZoneId , Rounding >> HUMAN_DATE_ROUNDINGS = List .of (
77- (zoneId ) -> Rounding .builder (Rounding .DateTimeUnit .MONTH_OF_YEAR ).timeZone (zoneId ).build (),
78- (zoneId ) -> Rounding .builder (Rounding .DateTimeUnit .WEEK_OF_WEEKYEAR ).timeZone (zoneId ).build (),
79- (zoneId ) -> Rounding .builder (Rounding .DateTimeUnit .DAY_OF_MONTH ).timeZone (zoneId ).build (),
80- (zoneId ) -> Rounding .builder (TimeValue .timeValueHours (12 )).timeZone (zoneId ).build (),
81- (zoneId ) -> Rounding .builder (TimeValue .timeValueHours (3 )).timeZone (zoneId ).build (),
82- (zoneId ) -> Rounding .builder (TimeValue .timeValueHours (1 )).timeZone (zoneId ).build (),
83- (zoneId ) -> Rounding .builder (TimeValue .timeValueMinutes (30 )).timeZone (zoneId ).build (),
84- (zoneId ) -> Rounding .builder (TimeValue .timeValueMinutes (10 )).timeZone (zoneId ).build (),
85- (zoneId ) -> Rounding .builder (TimeValue .timeValueMinutes (5 )).timeZone (zoneId ).build (),
86- (zoneId ) -> Rounding .builder (TimeValue .timeValueMinutes (1 )).timeZone (zoneId ).build (),
87- (zoneId ) -> Rounding .builder (TimeValue .timeValueSeconds (30 )).timeZone (zoneId ).build (),
88- (zoneId ) -> Rounding .builder (TimeValue .timeValueSeconds (10 )).timeZone (zoneId ).build (),
89- (zoneId ) -> Rounding .builder (TimeValue .timeValueSeconds (5 )).timeZone (zoneId ).build (),
90- (zoneId ) -> Rounding .builder (TimeValue .timeValueSeconds (1 )).timeZone (zoneId ).build (),
91- (zoneId ) -> Rounding .builder (TimeValue .timeValueMillis (100 )).timeZone (zoneId ).build (),
92- (zoneId ) -> Rounding .builder (TimeValue .timeValueMillis (50 )).timeZone (zoneId ).build (),
93- (zoneId ) -> Rounding .builder (TimeValue .timeValueMillis (10 )).timeZone (zoneId ).build (),
94- (zoneId ) -> Rounding .builder (TimeValue .timeValueMillis (1 )).timeZone (zoneId ).build ()
73+ private static final Unit YEAR_OF_CENTURY = Unit .from (Rounding .DateTimeUnit .YEAR_OF_CENTURY );
74+ private static final Unit MONTH_OF_YEAR = Unit .from (Rounding .DateTimeUnit .MONTH_OF_YEAR );
75+ private static final Unit WEEK_OF_WEEKYEAR = Unit .from (Rounding .DateTimeUnit .WEEK_OF_WEEKYEAR );
76+ private static final List <Unit > DAY_OF_MONTH_OR_FINER = List .of (
77+ Unit .from (Rounding .DateTimeUnit .DAY_OF_MONTH ),
78+ Unit .from (TimeValue .timeValueHours (12 )),
79+ Unit .from (TimeValue .timeValueHours (3 )),
80+ Unit .from (TimeValue .timeValueHours (1 )),
81+ Unit .from (TimeValue .timeValueMinutes (30 )),
82+ Unit .from (TimeValue .timeValueMinutes (10 )),
83+ Unit .from (TimeValue .timeValueMinutes (5 )),
84+ Unit .from (TimeValue .timeValueMinutes (1 )),
85+ Unit .from (TimeValue .timeValueSeconds (30 )),
86+ Unit .from (TimeValue .timeValueSeconds (10 )),
87+ Unit .from (TimeValue .timeValueSeconds (5 )),
88+ Unit .from (TimeValue .timeValueSeconds (1 )),
89+ Unit .from (TimeValue .timeValueMillis (100 )),
90+ Unit .from (TimeValue .timeValueMillis (50 )),
91+ Unit .from (TimeValue .timeValueMillis (10 )),
92+ Unit .from (TimeValue .timeValueMillis (1 ))
9593 );
9694
95+ private record Unit (Function <ZoneId , Rounding > fn ) implements Function <ZoneId , Rounding > {
96+
97+ public static Unit from (Rounding .DateTimeUnit unit ) {
98+ return new Unit (zoneId -> Rounding .builder (unit ).timeZone (zoneId ).build ());
99+ }
100+
101+ public static Unit from (TimeValue interval ) {
102+ return new Unit (zoneId -> Rounding .builder (interval ).timeZone (zoneId ).build ());
103+ }
104+
105+ @ Override
106+ public Rounding apply (ZoneId zoneId ) {
107+ return fn .apply (zoneId );
108+ }
109+ }
110+
97111 private final Configuration configuration ;
98112 private final Expression field ;
99113 private final Expression buckets ;
@@ -331,16 +345,36 @@ public Rounding.Prepared getDateRounding(FoldContext foldContext, Long min, Long
331345
332346 private record DateRoundingPicker (int buckets , long from , long to , ZoneId zoneId ) {
333347 Rounding pickRounding () {
334- Rounding prev = LARGEST_HUMAN_DATE_ROUNDING .apply (zoneId );
335- for (Function <ZoneId , Rounding > roundingMaker : HUMAN_DATE_ROUNDINGS ) {
336- Rounding r = roundingMaker .apply (zoneId );
337- if (roundingIsOk (r )) {
338- prev = r ;
348+ Rounding best = findLastOk (DAY_OF_MONTH_OR_FINER );
349+ if (best != null ) {
350+ return best ;
351+ }
352+ Rounding week = WEEK_OF_WEEKYEAR .apply (zoneId );
353+ if (roundingIsOk (week )) {
354+ return week ;
355+ }
356+ Rounding month = MONTH_OF_YEAR .apply (zoneId );
357+ if (roundingIsOk (month )) {
358+ return month ;
359+ }
360+ return YEAR_OF_CENTURY .apply (zoneId );
361+ }
362+
363+ private Rounding findLastOk (List <Unit > candidates ) {
364+ int low = 0 ;
365+ int high = candidates .size () - 1 ;
366+ Rounding best = null ;
367+ while (low <= high ) {
368+ int mid = (low + high ) >>> 1 ;
369+ Rounding candidate = candidates .get (mid ).apply (zoneId );
370+ if (roundingIsOk (candidate )) {
371+ best = candidate ;
372+ low = mid + 1 ;
339373 } else {
340- return prev ;
374+ high = mid - 1 ;
341375 }
342376 }
343- return prev ;
377+ return best ;
344378 }
345379
346380 /**
0 commit comments