package ch.qos.logback.core.rolling.helper;
import static ch.qos.logback.core.CoreConstants.MILLIS_IN_ONE_HOUR;
import static ch.qos.logback.core.CoreConstants.MILLIS_IN_ONE_MINUTE;
import static ch.qos.logback.core.CoreConstants.MILLIS_IN_ONE_SECOND;
import static ch.qos.logback.core.CoreConstants.MILLIS_IN_ONE_WEEK;
import static ch.qos.logback.core.CoreConstants.MILLIS_IN_ONE_DAY;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.TimeZone;
import ch.qos.logback.core.spi.ContextAwareBase;
public class RollingCalendar extends GregorianCalendar {
private static final long serialVersionUID = -5937537740925066161L;
static final TimeZone GMT_TIMEZONE = TimeZone.getTimeZone("GMT");
PeriodicityType periodicityType = PeriodicityType.ERRONEOUS;
String datePattern;
public RollingCalendar(String datePattern) {
super();
this.datePattern = datePattern;
this.periodicityType = computePeriodicityType();
}
public RollingCalendar(String datePattern, TimeZone tz, Locale locale) {
super(tz, locale);
this.datePattern = datePattern;
this.periodicityType = computePeriodicityType();
}
public PeriodicityType getPeriodicityType() {
return periodicityType;
}
public PeriodicityType computePeriodicityType() {
GregorianCalendar calendar = new GregorianCalendar(GMT_TIMEZONE, Locale.getDefault());
Date epoch = new Date(0);
if (datePattern != null) {
for (PeriodicityType i : PeriodicityType.VALID_ORDERED_LIST) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(datePattern);
simpleDateFormat.setTimeZone(GMT_TIMEZONE);
String r0 = simpleDateFormat.format(epoch);
Date next = innerGetEndOfThisPeriod(calendar, i, epoch);
String r1 = simpleDateFormat.format(next);
if ((r0 != null) && (r1 != null) && !r0.equals(r1)) {
return i;
}
}
}
return PeriodicityType.ERRONEOUS;
}
public boolean isCollisionFree() {
switch (periodicityType) {
case TOP_OF_HOUR:
return !collision(12 * MILLIS_IN_ONE_HOUR);
case TOP_OF_DAY:
if (collision(7 * MILLIS_IN_ONE_DAY))
return false;
if (collision(31 * MILLIS_IN_ONE_DAY))
return false;
if (collision(365 * MILLIS_IN_ONE_DAY))
return false;
return true;
case TOP_OF_WEEK:
if (collision(34 * MILLIS_IN_ONE_DAY))
return false;
if (collision(366 * MILLIS_IN_ONE_DAY))
return false;
return true;
default:
return true;
}
}
private boolean collision(long delta) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(datePattern);
simpleDateFormat.setTimeZone(GMT_TIMEZONE);
Date epoch0 = new Date(0);
String r0 = simpleDateFormat.format(epoch0);
Date epoch12 = new Date(delta);
String r12 = simpleDateFormat.format(epoch12);
return r0.equals(r12);
}
public void printPeriodicity(ContextAwareBase cab) {
switch (periodicityType) {
case TOP_OF_MILLISECOND:
cab.addInfo("Roll-over every millisecond.");
break;
case TOP_OF_SECOND:
cab.addInfo("Roll-over every second.");
break;
case TOP_OF_MINUTE:
cab.addInfo("Roll-over every minute.");
break;
case TOP_OF_HOUR:
cab.addInfo("Roll-over at the top of every hour.");
break;
case HALF_DAY:
cab.addInfo("Roll-over at midday and midnight.");
break;
case TOP_OF_DAY:
cab.addInfo("Roll-over at midnight.");
break;
case TOP_OF_WEEK:
cab.addInfo("Rollover at the start of week.");
break;
case TOP_OF_MONTH:
cab.addInfo("Rollover at start of every month.");
break;
default:
cab.addInfo("Unknown periodicity.");
}
}
public long periodBarriersCrossed(long start, long end) {
if (start > end)
throw new IllegalArgumentException("Start cannot come before end");
long startFloored = getStartOfCurrentPeriodWithGMTOffsetCorrection(start, getTimeZone());
long endFloored = getStartOfCurrentPeriodWithGMTOffsetCorrection(end, getTimeZone());
long diff = endFloored - startFloored;
switch (periodicityType) {
case TOP_OF_MILLISECOND:
return diff;
case TOP_OF_SECOND:
return diff / MILLIS_IN_ONE_SECOND;
case TOP_OF_MINUTE:
return diff / MILLIS_IN_ONE_MINUTE;
case TOP_OF_HOUR:
return (int) diff / MILLIS_IN_ONE_HOUR;
case TOP_OF_DAY:
return diff / MILLIS_IN_ONE_DAY;
case TOP_OF_WEEK:
return diff / MILLIS_IN_ONE_WEEK;
case TOP_OF_MONTH:
return diffInMonths(start, end);
default:
throw new IllegalStateException("Unknown periodicity type.");
}
}
public static int diffInMonths(long startTime, long endTime) {
if (startTime > endTime)
throw new IllegalArgumentException("startTime cannot be larger than endTime");
Calendar startCal = Calendar.getInstance();
startCal.setTimeInMillis(startTime);
Calendar endCal = Calendar.getInstance();
endCal.setTimeInMillis(endTime);
int yearDiff = endCal.get(Calendar.YEAR) - startCal.get(Calendar.YEAR);
int monthDiff = endCal.get(Calendar.MONTH) - startCal.get(Calendar.MONTH);
return yearDiff * 12 + monthDiff;
}
static private Date innerGetEndOfThisPeriod(Calendar cal, PeriodicityType periodicityType, Date now) {
return innerGetEndOfNextNthPeriod(cal, periodicityType, now, 1);
}
static private Date innerGetEndOfNextNthPeriod(Calendar cal, PeriodicityType periodicityType, Date now, int numPeriods) {
cal.setTime(now);
switch (periodicityType) {
case TOP_OF_MILLISECOND:
cal.add(Calendar.MILLISECOND, numPeriods);
break;
case TOP_OF_SECOND:
cal.set(Calendar.MILLISECOND, 0);
cal.add(Calendar.SECOND, numPeriods);
break;
case TOP_OF_MINUTE:
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
cal.add(Calendar.MINUTE, numPeriods);
break;
case TOP_OF_HOUR:
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
cal.add(Calendar.HOUR_OF_DAY, numPeriods);
break;
case TOP_OF_DAY:
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
cal.add(Calendar.DATE, numPeriods);
break;
case TOP_OF_WEEK:
cal.set(Calendar.DAY_OF_WEEK, cal.getFirstDayOfWeek());
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
cal.add(Calendar.WEEK_OF_YEAR, numPeriods);
break;
case TOP_OF_MONTH:
cal.set(Calendar.DATE, 1);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
cal.add(Calendar.MONTH, numPeriods);
break;
default:
throw new IllegalStateException("Unknown periodicity type.");
}
return cal.getTime();
}
public Date getEndOfNextNthPeriod(Date now, int periods) {
return innerGetEndOfNextNthPeriod(this, this.periodicityType, now, periods);
}
public Date getNextTriggeringDate(Date now) {
return getEndOfNextNthPeriod(now, 1);
}
public long getStartOfCurrentPeriodWithGMTOffsetCorrection(long now, TimeZone timezone) {
Date toppedDate;
{
Calendar aCal = Calendar.getInstance(timezone);
aCal.setTimeInMillis(now);
toppedDate = getEndOfNextNthPeriod(aCal.getTime(), 0);
}
Calendar secondCalendar = Calendar.getInstance(timezone);
secondCalendar.setTimeInMillis(toppedDate.getTime());
long gmtOffset = secondCalendar.get(Calendar.ZONE_OFFSET) + secondCalendar.get(Calendar.DST_OFFSET);
return toppedDate.getTime() + gmtOffset;
}
}