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 ZoneId gmtZone = ZoneId.of("UTC");
83 if (datePattern != null) {
84 for (PeriodicityType i : PeriodicityType.VALID_ORDERED_LIST) {
85 DateTimeFormatter dtf = DateTimeFormatter.ofPattern(datePattern).withZone(gmtZone);
86
87
88
89
90 String r0 = dtf.format(epoch);
91
92 Instant next = innerGetEndOfThisPeriod(calendar, i, epoch);
93 String r1 = dtf.format(next);
94
95
96 if ((r0 != null) && (r1 != null) && !r0.equals(r1)) {
97 return i;
98 }
99 }
100 }
101
102 return PeriodicityType.ERRONEOUS;
103 }
104
105 public boolean isCollisionFree() {
106 switch (periodicityType) {
107 case TOP_OF_HOUR:
108
109 return !collision(12 * MILLIS_IN_ONE_HOUR);
110
111 case TOP_OF_DAY:
112
113 if (collision(7 * MILLIS_IN_ONE_DAY))
114 return false;
115
116 if (collision(31 * MILLIS_IN_ONE_DAY))
117 return false;
118
119 if (collision(365 * MILLIS_IN_ONE_DAY))
120 return false;
121 return true;
122 case TOP_OF_WEEK:
123
124 if (collision(34 * MILLIS_IN_ONE_DAY))
125 return false;
126
127 if (collision(366 * MILLIS_IN_ONE_DAY))
128 return false;
129 return true;
130 default:
131 return true;
132 }
133 }
134
135 private boolean collision(long delta) {
136 SimpleDateFormat simpleDateFormat = new SimpleDateFormat(datePattern);
137 simpleDateFormat.setTimeZone(GMT_TIMEZONE);
138 Date epoch0 = new Date(0);
139 String r0 = simpleDateFormat.format(epoch0);
140 Date epoch12 = new Date(delta);
141 String r12 = simpleDateFormat.format(epoch12);
142
143 return r0.equals(r12);
144 }
145
146 public void printPeriodicity(ContextAwareBase cab) {
147 switch (periodicityType) {
148 case TOP_OF_MILLISECOND:
149 cab.addInfo("Roll-over every millisecond.");
150 break;
151
152 case TOP_OF_SECOND:
153 cab.addInfo("Roll-over every second.");
154 break;
155
156 case TOP_OF_MINUTE:
157 cab.addInfo("Roll-over every minute.");
158 break;
159
160 case TOP_OF_HOUR:
161 cab.addInfo("Roll-over at the top of every hour.");
162 break;
163
164 case HALF_DAY:
165 cab.addInfo("Roll-over at midday and midnight.");
166 break;
167
168 case TOP_OF_DAY:
169 cab.addInfo("Roll-over at midnight.");
170 break;
171
172 case TOP_OF_WEEK:
173 cab.addInfo("Rollover at the start of week.");
174 break;
175
176 case TOP_OF_MONTH:
177 cab.addInfo("Rollover at start of every month.");
178 break;
179
180 default:
181 cab.addInfo("Unknown periodicity.");
182 }
183 }
184
185 public long periodBarriersCrossed(long start, long end) {
186 if (start > end)
187 throw new IllegalArgumentException("Start cannot come before end");
188
189 long startFloored = getStartOfCurrentPeriodWithGMTOffsetCorrection(start, getTimeZone());
190 long endFloored = getStartOfCurrentPeriodWithGMTOffsetCorrection(end, getTimeZone());
191
192 long diff = endFloored - startFloored;
193
194 switch (periodicityType) {
195
196 case TOP_OF_MILLISECOND:
197 return diff;
198 case TOP_OF_SECOND:
199 return diff / MILLIS_IN_ONE_SECOND;
200 case TOP_OF_MINUTE:
201 return diff / MILLIS_IN_ONE_MINUTE;
202 case TOP_OF_HOUR:
203 return diff / MILLIS_IN_ONE_HOUR;
204 case TOP_OF_DAY:
205 return diff / MILLIS_IN_ONE_DAY;
206 case TOP_OF_WEEK:
207 return diff / MILLIS_IN_ONE_WEEK;
208 case TOP_OF_MONTH:
209 return diffInMonths(start, end);
210 default:
211 throw new IllegalStateException("Unknown periodicity type.");
212 }
213 }
214
215 public static int diffInMonths(long startTime, long endTime) {
216 if (startTime > endTime)
217 throw new IllegalArgumentException("startTime cannot be larger than endTime");
218 Calendar startCal = Calendar.getInstance();
219 startCal.setTimeInMillis(startTime);
220 Calendar endCal = Calendar.getInstance();
221 endCal.setTimeInMillis(endTime);
222 int yearDiff = endCal.get(Calendar.YEAR) - startCal.get(Calendar.YEAR);
223 int monthDiff = endCal.get(Calendar.MONTH) - startCal.get(Calendar.MONTH);
224 return yearDiff * 12 + monthDiff;
225 }
226
227 static private Instant innerGetEndOfThisPeriod(Calendar cal, PeriodicityType periodicityType, Instant instant) {
228 return innerGetEndOfNextNthPeriod(cal, periodicityType, instant, 1);
229 }
230
231 static private Instant innerGetEndOfNextNthPeriod(Calendar cal, PeriodicityType periodicityType, Instant instant,
232 int numPeriods) {
233 cal.setTimeInMillis(instant.toEpochMilli());
234 switch (periodicityType) {
235 case TOP_OF_MILLISECOND:
236 cal.add(Calendar.MILLISECOND, numPeriods);
237 break;
238
239 case TOP_OF_SECOND:
240 cal.set(Calendar.MILLISECOND, 0);
241 cal.add(Calendar.SECOND, numPeriods);
242 break;
243
244 case TOP_OF_MINUTE:
245 cal.set(Calendar.SECOND, 0);
246 cal.set(Calendar.MILLISECOND, 0);
247 cal.add(Calendar.MINUTE, numPeriods);
248 break;
249
250 case TOP_OF_HOUR:
251 cal.set(Calendar.MINUTE, 0);
252 cal.set(Calendar.SECOND, 0);
253 cal.set(Calendar.MILLISECOND, 0);
254 cal.add(Calendar.HOUR_OF_DAY, numPeriods);
255 break;
256
257 case TOP_OF_DAY:
258 cal.set(Calendar.HOUR_OF_DAY, 0);
259 cal.set(Calendar.MINUTE, 0);
260 cal.set(Calendar.SECOND, 0);
261 cal.set(Calendar.MILLISECOND, 0);
262 cal.add(Calendar.DATE, numPeriods);
263 break;
264
265 case TOP_OF_WEEK:
266 cal.set(Calendar.DAY_OF_WEEK, cal.getFirstDayOfWeek());
267 cal.set(Calendar.HOUR_OF_DAY, 0);
268 cal.set(Calendar.MINUTE, 0);
269 cal.set(Calendar.SECOND, 0);
270 cal.set(Calendar.MILLISECOND, 0);
271 cal.add(Calendar.WEEK_OF_YEAR, numPeriods);
272 break;
273
274 case TOP_OF_MONTH:
275 cal.set(Calendar.DATE, 1);
276 cal.set(Calendar.HOUR_OF_DAY, 0);
277 cal.set(Calendar.MINUTE, 0);
278 cal.set(Calendar.SECOND, 0);
279 cal.set(Calendar.MILLISECOND, 0);
280 cal.add(Calendar.MONTH, numPeriods);
281 break;
282
283 default:
284 throw new IllegalStateException("Unknown periodicity type.");
285 }
286
287 return Instant.ofEpochMilli(cal.getTimeInMillis());
288 }
289
290 public Instant getEndOfNextNthPeriod(Instant instant, int periods) {
291 return innerGetEndOfNextNthPeriod(this, this.periodicityType, instant, periods);
292 }
293
294 public Instant getNextTriggeringDate(Instant instant) {
295 return getEndOfNextNthPeriod(instant, 1);
296 }
297
298 public long getStartOfCurrentPeriodWithGMTOffsetCorrection(long now, TimeZone timezone) {
299 Instant toppedInstant;
300
301
302
303 {
304 Calendar aCal = Calendar.getInstance(timezone);
305 aCal.setTimeInMillis(now);
306 Instant instant = Instant.ofEpochMilli(aCal.getTimeInMillis());
307 toppedInstant = getEndOfNextNthPeriod(instant, 0);
308 }
309 Calendar secondCalendar = Calendar.getInstance(timezone);
310 secondCalendar.setTimeInMillis(toppedInstant.toEpochMilli());
311 long gmtOffset = secondCalendar.get(Calendar.ZONE_OFFSET) + secondCalendar.get(Calendar.DST_OFFSET);
312 return toppedInstant.toEpochMilli() + gmtOffset;
313 }
314 }