View Javadoc
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
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.util.Calendar;
24  import java.util.Date;
25  import java.util.GregorianCalendar;
26  import java.util.Locale;
27  import java.util.TimeZone;
28  
29  import ch.qos.logback.core.spi.ContextAwareBase;
30  
31  /**
32   * RollingCalendar is a helper class to
33   * {@link ch.qos.logback.core.rolling.TimeBasedRollingPolicy } or similar
34   * timed-based rolling policies. Given a periodicity type and the current time,
35   * it computes the start of the next interval (i.e. the triggering date).
36   *
37   * @author Ceki Gülcü
38   */
39  public class RollingCalendar extends GregorianCalendar {
40  
41      private static final long serialVersionUID = -5937537740925066161L;
42  
43      // The gmtTimeZone is used only in computeCheckPeriod() method.
44      static final TimeZone GMT_TIMEZONE = TimeZone.getTimeZone("GMT");
45  
46      PeriodicityType periodicityType = PeriodicityType.ERRONEOUS;
47      String datePattern;
48  
49      public RollingCalendar(String datePattern) {
50          super();
51          this.datePattern = datePattern;
52          this.periodicityType = computePeriodicityType();
53      }
54  
55      public RollingCalendar(String datePattern, TimeZone tz, Locale locale) {
56          super(tz, locale);
57          this.datePattern = datePattern;
58          this.periodicityType = computePeriodicityType();
59      }
60  
61      public PeriodicityType getPeriodicityType() {
62          return periodicityType;
63      }
64  
65      // This method computes the roll over period by looping over the
66      // periods, starting with the shortest, and stopping when the r0 is
67      // different from from r1, where r0 is the epoch formatted according
68      // the datePattern (supplied by the user) and r1 is the
69      // epoch+nextMillis(i) formatted according to datePattern. All date
70      // formatting is done in GMT and not local format because the test
71      // logic is based on comparisons relative to 1970-01-01 00:00:00
72      // GMT (the epoch).
73      public PeriodicityType computePeriodicityType() {
74  
75          GregorianCalendar calendar = new GregorianCalendar(GMT_TIMEZONE, Locale.getDefault());
76  
77          // set sate to 1970-01-01 00:00:00 GMT
78          Date epoch = new Date(0);
79  
80          if (datePattern != null) {
81              for (PeriodicityType i : PeriodicityType.VALID_ORDERED_LIST) {
82                  SimpleDateFormat simpleDateFormat = new SimpleDateFormat(datePattern);
83                  simpleDateFormat.setTimeZone(GMT_TIMEZONE); // all date formatting done in GMT
84  
85                  String r0 = simpleDateFormat.format(epoch);
86  
87                  Date next = innerGetEndOfThisPeriod(calendar, i, epoch);
88                  String r1 = simpleDateFormat.format(next);
89  
90                  // System.out.println("Type = "+i+", r0 = "+r0+", r1 = "+r1);
91                  if ((r0 != null) && (r1 != null) && !r0.equals(r1)) {
92                      return i;
93                  }
94              }
95          }
96          // we failed
97          return PeriodicityType.ERRONEOUS;
98      }
99  
100     public boolean isCollisionFree() {
101         switch (periodicityType) {
102         case TOP_OF_HOUR:
103             // isolated hh or KK
104             return !collision(12 * MILLIS_IN_ONE_HOUR);
105 
106         case TOP_OF_DAY:
107             // EE or uu
108             if (collision(7 * MILLIS_IN_ONE_DAY))
109                 return false;
110             // isolated dd
111             if (collision(31 * MILLIS_IN_ONE_DAY))
112                 return false;
113             // DD
114             if (collision(365 * MILLIS_IN_ONE_DAY))
115                 return false;
116             return true;
117         case TOP_OF_WEEK:
118             // WW
119             if (collision(34 * MILLIS_IN_ONE_DAY))
120                 return false;
121             // isolated ww
122             if (collision(366 * MILLIS_IN_ONE_DAY))
123                 return false;
124             return true;
125         default:
126             return true;
127         }
128     }
129 
130     private boolean collision(long delta) {
131         SimpleDateFormat simpleDateFormat = new SimpleDateFormat(datePattern);
132         simpleDateFormat.setTimeZone(GMT_TIMEZONE); // all date formatting done in GMT
133         Date epoch0 = new Date(0);
134         String r0 = simpleDateFormat.format(epoch0);
135         Date epoch12 = new Date(delta);
136         String r12 = simpleDateFormat.format(epoch12);
137 
138         return r0.equals(r12);
139     }
140 
141     public void printPeriodicity(ContextAwareBase cab) {
142         switch (periodicityType) {
143         case TOP_OF_MILLISECOND:
144             cab.addInfo("Roll-over every millisecond.");
145             break;
146 
147         case TOP_OF_SECOND:
148             cab.addInfo("Roll-over every second.");
149             break;
150 
151         case TOP_OF_MINUTE:
152             cab.addInfo("Roll-over every minute.");
153             break;
154 
155         case TOP_OF_HOUR:
156             cab.addInfo("Roll-over at the top of every hour.");
157             break;
158 
159         case HALF_DAY:
160             cab.addInfo("Roll-over at midday and midnight.");
161             break;
162 
163         case TOP_OF_DAY:
164             cab.addInfo("Roll-over at midnight.");
165             break;
166 
167         case TOP_OF_WEEK:
168             cab.addInfo("Rollover at the start of week.");
169             break;
170 
171         case TOP_OF_MONTH:
172             cab.addInfo("Rollover at start of every month.");
173             break;
174 
175         default:
176             cab.addInfo("Unknown periodicity.");
177         }
178     }
179 
180     public long periodBarriersCrossed(long start, long end) {
181         if (start > end)
182             throw new IllegalArgumentException("Start cannot come before end");
183 
184         long startFloored = getStartOfCurrentPeriodWithGMTOffsetCorrection(start, getTimeZone());
185         long endFloored = getStartOfCurrentPeriodWithGMTOffsetCorrection(end, getTimeZone());
186 
187         long diff = endFloored - startFloored;
188 
189         switch (periodicityType) {
190 
191         case TOP_OF_MILLISECOND:
192             return diff;
193         case TOP_OF_SECOND:
194             return diff / MILLIS_IN_ONE_SECOND;
195         case TOP_OF_MINUTE:
196             return diff / MILLIS_IN_ONE_MINUTE;
197         case TOP_OF_HOUR:
198 
199             return (int) diff / MILLIS_IN_ONE_HOUR;
200         case TOP_OF_DAY:
201             return diff / MILLIS_IN_ONE_DAY;
202         case TOP_OF_WEEK:
203             return diff / MILLIS_IN_ONE_WEEK;
204         case TOP_OF_MONTH:
205             return diffInMonths(start, end);
206         default:
207             throw new IllegalStateException("Unknown periodicity type.");
208         }
209     }
210 
211     public static int diffInMonths(long startTime, long endTime) {
212         if (startTime > endTime)
213             throw new IllegalArgumentException("startTime cannot be larger than endTime");
214         Calendar startCal = Calendar.getInstance();
215         startCal.setTimeInMillis(startTime);
216         Calendar endCal = Calendar.getInstance();
217         endCal.setTimeInMillis(endTime);
218         int yearDiff = endCal.get(Calendar.YEAR) - startCal.get(Calendar.YEAR);
219         int monthDiff = endCal.get(Calendar.MONTH) - startCal.get(Calendar.MONTH);
220         return yearDiff * 12 + monthDiff;
221     }
222 
223     static private Date innerGetEndOfThisPeriod(Calendar cal, PeriodicityType periodicityType, Date now) {
224         return innerGetEndOfNextNthPeriod(cal, periodicityType, now, 1);
225     }
226 
227     static private Date innerGetEndOfNextNthPeriod(Calendar cal, PeriodicityType periodicityType, Date now, int numPeriods) {
228         cal.setTime(now);
229         switch (periodicityType) {
230         case TOP_OF_MILLISECOND:
231             cal.add(Calendar.MILLISECOND, numPeriods);
232             break;
233 
234         case TOP_OF_SECOND:
235             cal.set(Calendar.MILLISECOND, 0);
236             cal.add(Calendar.SECOND, numPeriods);
237             break;
238 
239         case TOP_OF_MINUTE:
240             cal.set(Calendar.SECOND, 0);
241             cal.set(Calendar.MILLISECOND, 0);
242             cal.add(Calendar.MINUTE, numPeriods);
243             break;
244 
245         case TOP_OF_HOUR:
246             cal.set(Calendar.MINUTE, 0);
247             cal.set(Calendar.SECOND, 0);
248             cal.set(Calendar.MILLISECOND, 0);
249             cal.add(Calendar.HOUR_OF_DAY, numPeriods);
250             break;
251 
252         case TOP_OF_DAY:
253             cal.set(Calendar.HOUR_OF_DAY, 0);
254             cal.set(Calendar.MINUTE, 0);
255             cal.set(Calendar.SECOND, 0);
256             cal.set(Calendar.MILLISECOND, 0);
257             cal.add(Calendar.DATE, numPeriods);
258             break;
259 
260         case TOP_OF_WEEK:
261             cal.set(Calendar.DAY_OF_WEEK, cal.getFirstDayOfWeek());
262             cal.set(Calendar.HOUR_OF_DAY, 0);
263             cal.set(Calendar.MINUTE, 0);
264             cal.set(Calendar.SECOND, 0);
265             cal.set(Calendar.MILLISECOND, 0);
266             cal.add(Calendar.WEEK_OF_YEAR, numPeriods);
267             break;
268 
269         case TOP_OF_MONTH:
270             cal.set(Calendar.DATE, 1);
271             cal.set(Calendar.HOUR_OF_DAY, 0);
272             cal.set(Calendar.MINUTE, 0);
273             cal.set(Calendar.SECOND, 0);
274             cal.set(Calendar.MILLISECOND, 0);
275             cal.add(Calendar.MONTH, numPeriods);
276             break;
277 
278         default:
279             throw new IllegalStateException("Unknown periodicity type.");
280         }
281 
282         return cal.getTime();
283     }
284 
285     public Date getEndOfNextNthPeriod(Date now, int periods) {
286         return innerGetEndOfNextNthPeriod(this, this.periodicityType, now, periods);
287     }
288 
289     public Date getNextTriggeringDate(Date now) {
290         return getEndOfNextNthPeriod(now, 1);
291     }
292 
293     public long getStartOfCurrentPeriodWithGMTOffsetCorrection(long now, TimeZone timezone) {
294         Date toppedDate;
295 
296         // there is a bug in Calendar which prevents it from
297         // computing the correct DST_OFFSET when the time changes
298         {
299             Calendar aCal = Calendar.getInstance(timezone);
300             aCal.setTimeInMillis(now);
301             toppedDate = getEndOfNextNthPeriod(aCal.getTime(), 0);
302         }
303         Calendar secondCalendar = Calendar.getInstance(timezone);
304         secondCalendar.setTimeInMillis(toppedDate.getTime());
305         long gmtOffset = secondCalendar.get(Calendar.ZONE_OFFSET) + secondCalendar.get(Calendar.DST_OFFSET);
306         return toppedDate.getTime() + gmtOffset;
307     }
308 }