diff --git a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/PeriodicityType.java b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/PeriodicityType.java index bc900f964f..4d8ee26dae 100644 --- a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/PeriodicityType.java +++ b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/PeriodicityType.java @@ -21,6 +21,7 @@ public enum PeriodicityType { // The followed list consists of valid periodicity types in increasing period lengths static PeriodicityType[] VALID_ORDERED_LIST = new PeriodicityType[] { TOP_OF_MILLISECOND, PeriodicityType.TOP_OF_SECOND, PeriodicityType.TOP_OF_MINUTE, PeriodicityType.TOP_OF_HOUR, - PeriodicityType.TOP_OF_DAY, PeriodicityType.TOP_OF_WEEK, PeriodicityType.TOP_OF_MONTH }; + PeriodicityType.HALF_DAY, PeriodicityType.TOP_OF_DAY, PeriodicityType.TOP_OF_WEEK, + PeriodicityType.TOP_OF_MONTH }; } diff --git a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/RollingCalendar.java b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/RollingCalendar.java index a0dcb2c677..4618542d59 100644 --- a/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/RollingCalendar.java +++ b/logback-core/src/main/java/ch/qos/logback/core/rolling/helper/RollingCalendar.java @@ -105,6 +105,10 @@ public boolean isCollisionFree() { // isolated hh or KK return !collision(12 * MILLIS_IN_ONE_HOUR); + case HALF_DAY: + // isolated 'a' (AM/PM) without a date component repeats every day + return !collision(MILLIS_IN_ONE_DAY); + case TOP_OF_DAY: // EE or uu if (collision(7 * MILLIS_IN_ONE_DAY)) @@ -198,6 +202,8 @@ public long periodBarriersCrossed(long start, long end) { return diff / MILLIS_IN_ONE_MINUTE; case TOP_OF_HOUR: return diff / MILLIS_IN_ONE_HOUR; + case HALF_DAY: + return diff / (12 * MILLIS_IN_ONE_HOUR); case TOP_OF_DAY: return diff / MILLIS_IN_ONE_DAY; case TOP_OF_WEEK: @@ -251,6 +257,15 @@ static private Instant innerGetEndOfNextNthPeriod(Calendar cal, PeriodicityType cal.add(Calendar.HOUR_OF_DAY, numPeriods); break; + case HALF_DAY: + // floor to the start of the current half-day (00:00 or 12:00), then advance + cal.set(Calendar.HOUR_OF_DAY, cal.get(Calendar.HOUR_OF_DAY) < 12 ? 0 : 12); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + cal.add(Calendar.HOUR_OF_DAY, numPeriods * 12); + break; + case TOP_OF_DAY: cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); diff --git a/logback-core/src/test/java/ch/qos/logback/core/rolling/helper/RollingCalendarTest.java b/logback-core/src/test/java/ch/qos/logback/core/rolling/helper/RollingCalendarTest.java index 1ab14d036a..7eee2a6ba9 100644 --- a/logback-core/src/test/java/ch/qos/logback/core/rolling/helper/RollingCalendarTest.java +++ b/logback-core/src/test/java/ch/qos/logback/core/rolling/helper/RollingCalendarTest.java @@ -80,6 +80,17 @@ public void testPeriodicity() { assertEquals(PeriodicityType.TOP_OF_HOUR, rc.getPeriodicityType()); } + { + // 'a' (AM/PM) without a finer time token means twice-a-day roll-over + RollingCalendar rc = new RollingCalendar("yyyy-MM-dd-a"); + assertEquals(PeriodicityType.HALF_DAY, rc.getPeriodicityType()); + } + + { + RollingCalendar rc = new RollingCalendar("yyyy-MM-dd a"); + assertEquals(PeriodicityType.HALF_DAY, rc.getPeriodicityType()); + } + { RollingCalendar rc = new RollingCalendar("yyyy-MM-dd"); assertEquals(PeriodicityType.TOP_OF_DAY, rc.getPeriodicityType()); @@ -132,6 +143,23 @@ public void testVaryingNumberOfDailyPeriods() { } } + @Test + public void testVaryingNumberOfHalfDailyPeriods() { + RollingCalendar rc = new RollingCalendar("yyyy-MM-dd-a"); + final long MILLIS_IN_HALF_DAY = 12 * 3600 * 1000; + + for (int p = 20; p > -100; p--) { + long now = 1223325293589L; // Mon Oct 06 22:34:53 CEST 2008 + Instant nowInstant = Instant.ofEpochMilli(now); + Instant result = rc.getEndOfNextNthPeriod(nowInstant, p); + long offset = rc.getTimeZone().getRawOffset() + rc.getTimeZone().getDSTSavings(); + + long origin = now - ((now + offset) % (MILLIS_IN_HALF_DAY)); + long expected = origin + p * MILLIS_IN_HALF_DAY; + assertEquals(expected, result.toEpochMilli(), "p=" + p); + } + } + // Wed Mar 23 23:07:05 CET 2016 final long WED_2016_03_23_T_230705_CET = 1458770825333L; @@ -143,6 +171,8 @@ public void testBarrierCrossingComputation() { WED_2016_03_23_T_230705_CET + 3 * CoreConstants.MILLIS_IN_ONE_MINUTE, 3); checkPeriodBarriersCrossed("yyyy-MM-dd'T'HH", WED_2016_03_23_T_230705_CET, WED_2016_03_23_T_230705_CET + 3 * CoreConstants.MILLIS_IN_ONE_HOUR, 3); + checkPeriodBarriersCrossed("yyyy-MM-dd-a", WED_2016_03_23_T_230705_CET, + WED_2016_03_23_T_230705_CET + 3 * 12 * CoreConstants.MILLIS_IN_ONE_HOUR, 3); checkPeriodBarriersCrossed("yyyy-MM-dd", WED_2016_03_23_T_230705_CET, WED_2016_03_23_T_230705_CET + 3 * CoreConstants.MILLIS_IN_ONE_DAY, 3); } @@ -164,6 +194,10 @@ public void testCollisionFreenes() { checkCollisionFreeness("yyyy-MM-dd KK", false); checkCollisionFreeness("yyyy-MM-dd KK a", true); + // half-daily + checkCollisionFreeness("yyyy-MM-dd-a", true); + checkCollisionFreeness("a", false); + // daily checkCollisionFreeness("yyyy-MM-dd", true); checkCollisionFreeness("yyyy-dd", false);