1
2
3
4
5
6
7
8
9
10
11
12
13
14 package ch.qos.logback.core.rolling.helper;
15
16 import static ch.qos.logback.core.CoreConstants.MILLIS_IN_ONE_HOUR;
17 import static ch.qos.logback.core.CoreConstants.MILLIS_IN_ONE_MINUTE;
18 import static ch.qos.logback.core.CoreConstants.MILLIS_IN_ONE_SECOND;
19 import static ch.qos.logback.core.CoreConstants.MILLIS_IN_ONE_WEEK;
20 import static ch.qos.logback.core.CoreConstants.MILLIS_IN_ONE_DAY;
21
22 import java.text.SimpleDateFormat;
23 import java.time.Instant;
24 import java.time.ZoneId;
25 import java.time.format.DateTimeFormatter;
26 import java.util.Calendar;
27 import java.util.Date;
28 import java.util.GregorianCalendar;
29 import java.util.Locale;
30 import java.util.TimeZone;
31
32 import ch.qos.logback.core.spi.ContextAwareBase;
33
34
35
36
37
38
39
40
41
42 public class RollingCalendar extends GregorianCalendar {
43
44 private static final long serialVersionUID = -5937537740925066161L;
45
46
47 static final TimeZone GMT_TIMEZONE = TimeZone.getTimeZone("GMT");
48
49 PeriodicityType periodicityType = PeriodicityType.ERRONEOUS;
50 String datePattern;
51
52 public RollingCalendar(String datePattern) {
53 super();
54 this.datePattern = datePattern;
55 this.periodicityType = computePeriodicityType();
56 }
57
58 public RollingCalendar(String datePattern, TimeZone tz, Locale locale) {
59 super(tz, locale);
60 this.datePattern = datePattern;
61 this.periodicityType = computePeriodicityType();
62 }
63
64 public PeriodicityType getPeriodicityType() {
65 return periodicityType;
66 }
67
68
69
70
71
72
73
74
75
76 public PeriodicityType computePeriodicityType() {
77
78 GregorianCalendar calendar = new GregorianCalendar(GMT_TIMEZONE, Locale.getDefault());
79
80
81 Instant epoch = Instant.ofEpochMilli(0);
82 if (datePattern != null) {
83 for (PeriodicityType i : PeriodicityType.VALID_ORDERED_LIST) {
84 SimpleDateFormat simpleDateFormat = new SimpleDateFormat(datePattern, Locale.getDefault());
85 simpleDateFormat.setTimeZone(GMT_TIMEZONE);
86
87 String r0 = simpleDateFormat.format(new java.util.Date(0));
88
89 Instant next = innerGetEndOfThisPeriod(calendar, i, epoch);
90 String r1 = simpleDateFormat.format(new java.util.Date(next.toEpochMilli()));
91
92
93 if ((r0 != null) && (r1 != null) && !r0.equals(r1)) {
94 return i;
95 }
96 }
97 }
98
99 return PeriodicityType.ERRONEOUS;
100 }
101
102 public boolean isCollisionFree() {
103 switch (periodicityType) {
104 case TOP_OF_HOUR:
105
106 return !collision(12 * MILLIS_IN_ONE_HOUR);
107
108 case TOP_OF_DAY:
109
110 if (collision(7 * MILLIS_IN_ONE_DAY))
111 return false;
112
113 if (collision(31 * MILLIS_IN_ONE_DAY))
114 return false;
115
116 if (collision(365 * MILLIS_IN_ONE_DAY))
117 return false;
118 return true;
119 case TOP_OF_WEEK:
120
121 if (collision(34 * MILLIS_IN_ONE_DAY))
122 return false;
123
124 if (collision(366 * MILLIS_IN_ONE_DAY))
125 return false;
126 return true;
127 default:
128 return true;
129 }
130 }
131
132 private boolean collision(long delta) {
133 SimpleDateFormat simpleDateFormat = new SimpleDateFormat(datePattern);
134 simpleDateFormat.setTimeZone(GMT_TIMEZONE);
135 Date epoch0 = new Date(0);
136 String r0 = simpleDateFormat.format(epoch0);
137 Date epoch12 = new Date(delta);
138 String r12 = simpleDateFormat.format(epoch12);
139
140 return r0.equals(r12);
141 }
142
143 public void printPeriodicity(ContextAwareBase cab) {
144 switch (periodicityType) {
145 case TOP_OF_MILLISECOND:
146 cab.addInfo("Roll-over every millisecond.");
147 break;
148
149 case TOP_OF_SECOND:
150 cab.addInfo("Roll-over every second.");
151 break;
152
153 case TOP_OF_MINUTE:
154 cab.addInfo("Roll-over every minute.");
155 break;
156
157 case TOP_OF_HOUR:
158 cab.addInfo("Roll-over at the top of every hour.");
159 break;
160
161 case HALF_DAY:
162 cab.addInfo("Roll-over at midday and midnight.");
163 break;
164
165 case TOP_OF_DAY:
166 cab.addInfo("Roll-over at midnight.");
167 break;
168
169 case TOP_OF_WEEK:
170 cab.addInfo("Rollover at the start of week.");
171 break;
172
173 case TOP_OF_MONTH:
174 cab.addInfo("Rollover at start of every month.");
175 break;
176
177 default:
178 cab.addInfo("Unknown periodicity.");
179 }
180 }
181
182 public long periodBarriersCrossed(long start, long end) {
183 if (start > end)
184 throw new IllegalArgumentException("Start cannot come before end");
185
186 long startFloored = getStartOfCurrentPeriodWithGMTOffsetCorrection(start, getTimeZone());
187 long endFloored = getStartOfCurrentPeriodWithGMTOffsetCorrection(end, getTimeZone());
188
189 long diff = endFloored - startFloored;
190
191 switch (periodicityType) {
192
193 case TOP_OF_MILLISECOND:
194 return diff;
195 case TOP_OF_SECOND:
196 return diff / MILLIS_IN_ONE_SECOND;
197 case TOP_OF_MINUTE:
198 return diff / MILLIS_IN_ONE_MINUTE;
199 case TOP_OF_HOUR:
200 return diff / MILLIS_IN_ONE_HOUR;
201 case TOP_OF_DAY:
202 return diff / MILLIS_IN_ONE_DAY;
203 case TOP_OF_WEEK:
204 return diff / MILLIS_IN_ONE_WEEK;
205 case TOP_OF_MONTH:
206 return diffInMonths(start, end);
207 default:
208 throw new IllegalStateException("Unknown periodicity type.");
209 }
210 }
211
212 public static int diffInMonths(long startTime, long endTime) {
213 if (startTime > endTime)
214 throw new IllegalArgumentException("startTime cannot be larger than endTime");
215 Calendar startCal = Calendar.getInstance();
216 startCal.setTimeInMillis(startTime);
217 Calendar endCal = Calendar.getInstance();
218 endCal.setTimeInMillis(endTime);
219 int yearDiff = endCal.get(Calendar.YEAR) - startCal.get(Calendar.YEAR);
220 int monthDiff = endCal.get(Calendar.MONTH) - startCal.get(Calendar.MONTH);
221 return yearDiff * 12 + monthDiff;
222 }
223
224 static private Instant innerGetEndOfThisPeriod(Calendar cal, PeriodicityType periodicityType, Instant instant) {
225 return innerGetEndOfNextNthPeriod(cal, periodicityType, instant, 1);
226 }
227
228 static private Instant innerGetEndOfNextNthPeriod(Calendar cal, PeriodicityType periodicityType, Instant instant,
229 int numPeriods) {
230 cal.setTimeInMillis(instant.toEpochMilli());
231 switch (periodicityType) {
232 case TOP_OF_MILLISECOND:
233 cal.add(Calendar.MILLISECOND, numPeriods);
234 break;
235
236 case TOP_OF_SECOND:
237 cal.set(Calendar.MILLISECOND, 0);
238 cal.add(Calendar.SECOND, numPeriods);
239 break;
240
241 case TOP_OF_MINUTE:
242 cal.set(Calendar.SECOND, 0);
243 cal.set(Calendar.MILLISECOND, 0);
244 cal.add(Calendar.MINUTE, numPeriods);
245 break;
246
247 case TOP_OF_HOUR:
248 cal.set(Calendar.MINUTE, 0);
249 cal.set(Calendar.SECOND, 0);
250 cal.set(Calendar.MILLISECOND, 0);
251 cal.add(Calendar.HOUR_OF_DAY, numPeriods);
252 break;
253
254 case TOP_OF_DAY:
255 cal.set(Calendar.HOUR_OF_DAY, 0);
256 cal.set(Calendar.MINUTE, 0);
257 cal.set(Calendar.SECOND, 0);
258 cal.set(Calendar.MILLISECOND, 0);
259 cal.add(Calendar.DATE, numPeriods);
260 break;
261
262 case TOP_OF_WEEK:
263 cal.set(Calendar.DAY_OF_WEEK, cal.getFirstDayOfWeek());
264 cal.set(Calendar.HOUR_OF_DAY, 0);
265 cal.set(Calendar.MINUTE, 0);
266 cal.set(Calendar.SECOND, 0);
267 cal.set(Calendar.MILLISECOND, 0);
268 cal.add(Calendar.WEEK_OF_YEAR, numPeriods);
269 break;
270
271 case TOP_OF_MONTH:
272 cal.set(Calendar.DATE, 1);
273 cal.set(Calendar.HOUR_OF_DAY, 0);
274 cal.set(Calendar.MINUTE, 0);
275 cal.set(Calendar.SECOND, 0);
276 cal.set(Calendar.MILLISECOND, 0);
277 cal.add(Calendar.MONTH, numPeriods);
278 break;
279
280 default:
281 throw new IllegalStateException("Unknown periodicity type.");
282 }
283
284 return Instant.ofEpochMilli(cal.getTimeInMillis());
285 }
286
287 public Instant getEndOfNextNthPeriod(Instant instant, int periods) {
288 return innerGetEndOfNextNthPeriod(this, this.periodicityType, instant, periods);
289 }
290
291 public Instant getNextTriggeringDate(Instant instant) {
292 return getEndOfNextNthPeriod(instant, 1);
293 }
294
295 public long getStartOfCurrentPeriodWithGMTOffsetCorrection(long now, TimeZone timezone) {
296 Instant toppedInstant;
297
298
299
300 {
301 Calendar aCal = Calendar.getInstance(timezone);
302 aCal.setTimeInMillis(now);
303 Instant instant = Instant.ofEpochMilli(aCal.getTimeInMillis());
304 toppedInstant = getEndOfNextNthPeriod(instant, 0);
305 }
306 Calendar secondCalendar = Calendar.getInstance(timezone);
307 secondCalendar.setTimeInMillis(toppedInstant.toEpochMilli());
308 long gmtOffset = secondCalendar.get(Calendar.ZONE_OFFSET) + secondCalendar.get(Calendar.DST_OFFSET);
309 return toppedInstant.toEpochMilli() + gmtOffset;
310 }
311 }