package de.invesdwin.util.time.fdate; import java.nio.ByteBuffer; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.TimeZone; import javax.annotation.concurrent.ThreadSafe; import org.joda.time.DateTimeZone; import de.invesdwin.util.collections.iterable.ICloseableIterable; import de.invesdwin.util.collections.iterable.ICloseableIterator; import de.invesdwin.util.error.FastNoSuchElementException; import de.invesdwin.util.error.UnknownArgumentException; import de.invesdwin.util.time.duration.Duration; @ThreadSafe public final class FDates { private static final long MILLISECONDS_IN_DAY = FTimeUnit.MILLISECONDS_IN_DAY; private static final long MILLISECONDS_IN_HOUR = FTimeUnit.MILLISECONDS_IN_HOUR; private static final long MILLISECONDS_IN_MINUTE = FTimeUnit.MILLISECONDS_IN_MINUTE; private static final long MILLISECONDS_IN_SECOND = FTimeUnit.MILLISECONDS_IN_SECOND; private static Calendar templateCalendar; private static TimeZone defaultTimeZone; private static DateTimeZone defaultDateTimeZone; static { setDefaultTimeZone(TimeZone.getDefault()); } private FDates() {} public static void setDefaultTimeZone(final TimeZone defaultTimeZone) { FDates.defaultTimeZone = defaultTimeZone; FDates.defaultDateTimeZone = DateTimeZone.forTimeZone(defaultTimeZone); //CHECKSTYLE:OFF final Calendar cal = Calendar.getInstance(); //CHECKSTYLE:ON cal.clear(); cal.setTimeZone(defaultTimeZone); templateCalendar = cal; } public static TimeZone getDefaultTimeZone() { return defaultTimeZone; } public static DateTimeZone getDefaultDateTimeZone() { return defaultDateTimeZone; } public static Calendar newCalendar() { return (Calendar) templateCalendar.clone(); } public static ICloseableIterable<FDate> iterable(final FDate start, final FDate end, final Duration increment) { return new FDateIterable(start, end, increment.getTimeUnit(), increment.intValue()); } public static ICloseableIterable<FDate> iterable(final FDate start, final FDate end, final FTimeUnit timeUnit, final int incrementAmount) { return new FDateIterable(start, end, timeUnit, incrementAmount); } static class FDateIterable implements ICloseableIterable<FDate> { private final FDate startFinal; private final FDate endFinal; private final FTimeUnit timeUnit; private final int incrementAmount; FDateIterable(final FDate startFinal, final FDate endFinal, final FTimeUnit timeUnit, final int incrementAmount) { this.startFinal = startFinal; this.endFinal = endFinal; this.timeUnit = timeUnit; this.incrementAmount = incrementAmount; if (incrementAmount == 0) { throw new IllegalArgumentException("incrementAmount must not be 0"); } if (startFinal.isBefore(endFinal) && incrementAmount < 0) { throw new IllegalArgumentException("When iterating forward [" + startFinal + " -> " + endFinal + "], incrementAmount [" + incrementAmount + "] needs to be positive."); } else if (startFinal.isAfter(endFinal) && incrementAmount > 0) { throw new IllegalArgumentException("When iterating backward [" + startFinal + " -> " + endFinal + "], incrementAmount [" + incrementAmount + "] needs to be negative."); } } @Override public ICloseableIterator<FDate> iterator() { if (incrementAmount > 0) { return new ICloseableIterator<FDate>() { private FDate spot = startFinal; private boolean first = true; private boolean end = false; @Override public boolean hasNext() { return first || spot.isBefore(endFinal); } @Override public FDate next() { if (first) { first = false; return spot; } else { if (spot.isAfter(endFinal) || end) { throw new FastNoSuchElementException("FDateIterable: incrementing next reached end"); } spot = spot.add(timeUnit, incrementAmount); if (spot.isAfterOrEqualTo(endFinal)) { end = true; return endFinal; } else { return spot; } } } @Override public void remove() { throw new UnsupportedOperationException(); } @Override public void close() { spot = endFinal; first = false; end = true; } }; } else { //reverse return new ICloseableIterator<FDate>() { private boolean first = true; private FDate spot = startFinal; private boolean end = false; @Override public boolean hasNext() { return first || spot.isAfter(endFinal); } @Override public FDate next() { if (first) { first = false; return spot; } else { if (spot.isBefore(endFinal) || end) { throw new FastNoSuchElementException("FDateIterable: decrementing next reached end"); } spot = spot.add(timeUnit, incrementAmount); if (spot.isBeforeOrEqualTo(endFinal)) { end = true; return endFinal; } else { return spot; } } } @Override public void remove() { throw new UnsupportedOperationException(); } @Override public void close() { spot = endFinal; first = false; end = true; } }; } } } public static Object toString(final FDate date) { if (date == null) { return null; } else { return date.toString(); } } public static String toString(final FDate date, final TimeZone timeZone) { if (date == null) { return null; } return date.toString(timeZone); } public static String toString(final FDate date, final String format) { if (date == null) { return null; } return date.toString(format); } public static String toString(final FDate date, final String format, final TimeZone timeZone) { if (date == null) { return null; } return date.toString(format, timeZone); } public static FDate min(final FDate... dates) { FDate minDate = null; for (final FDate date : dates) { minDate = min(minDate, date); } return minDate; } public static FDate min(final Iterable<FDate> dates) { FDate minDate = null; for (final FDate date : dates) { minDate = min(minDate, date); } return minDate; } public static FDate min(final FDate date1, final FDate date2) { if (date1 == null) { return date2; } else if (date2 == null) { return date1; } if (date1.isBefore(date2)) { return date1; } else { return date2; } } public static FDate max(final Iterable<FDate> dates) { FDate maxDate = null; for (final FDate date : dates) { maxDate = max(maxDate, date); } return maxDate; } public static FDate max(final FDate... dates) { FDate maxDate = null; for (final FDate date : dates) { maxDate = max(maxDate, date); } return maxDate; } public static FDate max(final FDate date1, final FDate date2) { if (date1 == null) { return date2; } else if (date2 == null) { return date1; } if (date1.isAfter(date2)) { return date1; } else { return date2; } } public static FDate between(final FDate value, final FDate min, final FDate max) { return max(min(value, max), min); } public static boolean isBetween(final FDate value, final FDate min, final FDate max) { return between(value, min, max).equals(value); } public static boolean isSameYear(final FDate date1, final FDate date2) { return isSameTruncated(date1, date2, FDateField.Year); } public static boolean isSameMonth(final FDate date1, final FDate date2) { return isSameTruncated(date1, date2, FDateField.Month); } public static boolean isSameWeek(final FDate date1, final FDate date2) { return isSameWeekPart(date1, date2, FWeekday.Monday, FWeekday.Sunday); } public static boolean isSameWeekPart(final FDate date1, final FDate date2, final FWeekday statOfWeekPart, final FWeekday endOfWeekPart) { if (date1 == null || date2 == null) { return false; } final FDate startOfWeek = date1.withoutTime().setFWeekday(statOfWeekPart); final FDate endOfWeek = date1.withoutTime().setFWeekday(endOfWeekPart).addDays(1).addMilliseconds(-1); if (startOfWeek.isAfter(endOfWeek)) { throw new IllegalStateException( "startOfWeek [" + startOfWeek + "] should not be after [" + endOfWeek + "]"); } return FDates.isBetween(date2, startOfWeek, endOfWeek); } public static boolean isWeekdayBetween(final FDate date1, final FDate date2, final FWeekday weekday) { final FDate from = date1.withoutTime(); final FDate to = date2.withoutTime(); if (to.isBefore(from)) { return false; } for (final FDate day : iterable(from, to, FTimeUnit.DAYS, 1)) { if (day.getFWeekday() == weekday) { return true; } } return false; } public static boolean isSameDay(final FDate date1, final FDate date2) { return isSameTruncated(date1, date2, FDateField.Day); } public static boolean isSameHour(final FDate date1, final FDate date2) { return isSameTruncated(date1, date2, FDateField.Hour); } public static boolean isSameMinute(final FDate date1, final FDate date2) { return isSameTruncated(date1, date2, FDateField.Minute); } public static boolean isSameSecond(final FDate date1, final FDate date2) { return isSameTruncated(date1, date2, FDateField.Second); } public static boolean isSameMillisecond(final FDate date1, final FDate date2) { if (date1 == null || date2 == null) { return false; } else { return date1.millisValue() == date2.millisValue(); } } private static boolean isSameTruncated(final FDate date1, final FDate date2, final FDateField field) { if (date1 == null || date2 == null) { return false; } return date1.millisValue() == date2.millisValue() || date1.truncate(field).millisValue() == date2.truncate(field).millisValue(); } public static boolean isSamePeriod(final FDate date1, final FDate date2, final FTimeUnit period) { switch (period) { case MILLISECONDS: return isSameMillisecond(date1, date2); case SECONDS: return isSameSecond(date1, date2); case MINUTES: return isSameMinute(date1, date2); case HOURS: return isSameHour(date1, date2); case DAYS: return isSameDay(date1, date2); case WEEKS: return isSameWeek(date1, date2); case MONTHS: return isSameMonth(date1, date2); case YEARS: return isSameYear(date1, date2); default: throw UnknownArgumentException.newInstance(FTimeUnit.class, period); } } public static boolean isSameJulianPeriod(final FDate date1, final FDate date2, final FTimeUnit period) { switch (period) { case MILLISECONDS: return isSameMillisecond(date1, date2); case SECONDS: return isSameJulianSecond(date1, date2); case MINUTES: return isSameJulianMinute(date1, date2); case HOURS: return isSameJulianHour(date1, date2); case DAYS: return isSameJulianDay(date1, date2); case WEEKS: return isSameWeek(date1, date2); case MONTHS: return isSameMonth(date1, date2); case YEARS: return isSameYear(date1, date2); default: throw UnknownArgumentException.newInstance(FTimeUnit.class, period); } } /** * Fast but unprecise variation of isSameDay(). Does not count in daylight saving time. Though does not matter when * working with UTC. */ public static boolean isSameJulianDay(final FDate date1, final FDate date2) { if (date1 == null || date2 == null) { return false; } // Strip out the time part of each date. final long julianDayNumber1 = date1.millisValue() / MILLISECONDS_IN_DAY; final long julianDayNumber2 = date2.millisValue() / MILLISECONDS_IN_DAY; // If they now are equal then it is the same day. return julianDayNumber1 == julianDayNumber2; } /** * Fast but unprecise variation of isSameHour(). Does not count in daylight saving time. Though does not matter when * working with UTC. */ public static boolean isSameJulianHour(final FDate date1, final FDate date2) { if (date1 == null || date2 == null) { return false; } // Strip out the time part of each date. final long julianHourNumber1 = date1.millisValue() / MILLISECONDS_IN_HOUR; final long julianHourNumber2 = date2.millisValue() / MILLISECONDS_IN_HOUR; // If they now are equal then it is the same day. return julianHourNumber1 == julianHourNumber2; } /** * Fast but unprecise variation of isSameMinute(). Does not count in daylight saving time. Though does not matter * when working with UTC. */ public static boolean isSameJulianMinute(final FDate date1, final FDate date2) { if (date1 == null || date2 == null) { return false; } // Strip out the time part of each date. final long julianMinuteNumber1 = date1.millisValue() / MILLISECONDS_IN_MINUTE; final long julianMinuteNumber2 = date2.millisValue() / MILLISECONDS_IN_MINUTE; // If they now are equal then it is the same day. return julianMinuteNumber1 == julianMinuteNumber2; } /** * Fast but unprecise variation of isSameSecond(). Does not count in daylight saving time. Though does not matter * when working with UTC. */ public static boolean isSameJulianSecond(final FDate date1, final FDate date2) { if (date1 == null || date2 == null) { return false; } // Strip out the time part of each date. final long julianSecondNumber1 = date1.millisValue() / MILLISECONDS_IN_SECOND; final long julianSecondNumber2 = date2.millisValue() / MILLISECONDS_IN_SECOND; // If they now are equal then it is the same day. return julianSecondNumber1 == julianSecondNumber2; } public static Date toDate(final FDate date) { if (date != null) { return date.dateValue(); } else { return null; } } public static FDate avg(final FDate first, final FDate second) { return new FDate((first.millisValue() + second.millisValue()) / 2); } public static FDate avg(final FDate... values) { long sum = 0; for (final FDate value : values) { sum += value.millisValue(); } return new FDate(sum / values.length); } public static FDate avg(final Collection<FDate> values) { long sum = 0; for (final FDate value : values) { sum += value.millisValue(); } return new FDate(sum / values.size()); } public static void putFDate(final ByteBuffer buffer, final FDate time) { if (time == null) { buffer.putLong(Long.MIN_VALUE); } else { buffer.putLong(time.millisValue()); } } public static FDate extractFDate(final ByteBuffer buffer, final int index) { final long time = buffer.getLong(index); if (time == Long.MIN_VALUE) { return null; } else { return new FDate(time); } } }