package com.linkedin.thirdeye.client; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.Days; import org.joda.time.Duration; import com.google.common.collect.Range; import com.linkedin.thirdeye.api.TimeGranularity; import com.linkedin.thirdeye.api.TimeRange; import org.joda.time.Hours; /** * Not to be confused with {@link TimeRange}. This class handles splitting time windows into * appropriate {@link Range} objects. */ public class TimeRangeUtils { public static List<Range<DateTime>> computeTimeRanges(TimeGranularity granularity, DateTime start, DateTime end) { List<Range<DateTime>> timeranges = new ArrayList<>(); if (granularity == null) { timeranges.add(Range.closedOpen(start, end)); return timeranges; } DateTime current = start; DateTime newCurrent = null; // Duration duration = new Duration(granularity.toMillis()); while (current.isBefore(end)) { // newCurrent = current.plus(granularity.toMillis()); newCurrent = increment(current, granularity); timeranges.add(Range.closedOpen(current, newCurrent)); current = newCurrent; } return timeranges; } public static DateTime increment(DateTime input, TimeGranularity granularity) { DateTime output; switch (granularity.getUnit()) { case DAYS: output = input.plusDays(granularity.getSize()); break; case HOURS: output = input.plusHours(granularity.getSize()); break; case MILLISECONDS: output = input.plusMillis(granularity.getSize()); break; case MINUTES: output = input.plusMinutes(granularity.getSize()); break; case SECONDS: output = input.plusSeconds(granularity.getSize()); break; default: throw new IllegalArgumentException("Timegranularity:" + granularity + " not supported"); } return output; } /** * Given time granularity and start time (with local time zone information), returns the bucket * index of the current time (with local time zone information). * * The reason to use this method to calculate the bucket index is to align the shifted data point * due to daylight saving time to the correct bucket index. Note that this method have no effect * if the input time use UTC timezone. * * For instance, considering March 13th 2016, the day DST takes effect. Assume that our daily * data whose timestamp is aligned at 0 am at each day, then the data point on March 14th would * be actually aligned to 13th's bucket. Because the two data point only has 23 hours difference. * Therefore, we cannot calculate the bucket index simply divide the difference between timestamps * by millis of 24 hours. * * We don't need to consider the case of HOURS because the size of a bucket does not change when * the time granularity is smaller than DAYS. In DAYS, the bucket size could be 23, 24, or 25 * hours due to DST. In HOURS or anything smaller, the bucket size does not change. Hence, we * simply compute the bucket index using one fixed bucket size (i.e., interval). * * @param granularity the time granularity of the bucket * @param start the start time of the first bucket * @param current the current time * @return the bucket index of current time */ public static int computeBucketIndex(TimeGranularity granularity, DateTime start, DateTime current) { int index = -1; switch (granularity.getUnit()) { case DAYS: Days d = Days.daysBetween(start.toLocalDate(), current.toLocalDate()); index = d.getDays() / granularity.getSize(); break; default: long interval = granularity.toMillis(); index = (int) ((current.getMillis() - start.getMillis()) / interval); } return index; } public static void main(String[] args) { TimeGranularity granularity = new TimeGranularity(1, TimeUnit.DAYS); // FAILS FOR UTC time zone DateTimeZone utc = DateTimeZone.forID("UTC"); DateTime baselineStart = new DateTime(1478415600000L, utc); DateTime baselineEnd = new DateTime(1478678400000L, utc); DateTime currentStart = new DateTime(1479024000000L, utc); DateTime currentEnd = new DateTime(1479283200000L, utc); List<Range<DateTime>> currentTimeRanges = TimeRangeUtils.computeTimeRanges(granularity, currentStart, currentEnd); List<Range<DateTime>> baselineTimeRanges = TimeRangeUtils.computeTimeRanges(granularity, baselineStart, baselineEnd); System.out.println(currentTimeRanges); System.out.println(baselineTimeRanges); // WORKS FOR PST DateTimeZone pacificTZ = DateTimeZone.forID("America/Los_Angeles"); baselineStart = new DateTime(1478415600000L, pacificTZ); baselineEnd = new DateTime(1478678400000L, pacificTZ); currentStart = new DateTime(1479024000000L, pacificTZ); currentEnd = new DateTime(1479283200000L, pacificTZ); currentTimeRanges = TimeRangeUtils.computeTimeRanges(granularity, currentStart, currentEnd); baselineTimeRanges = TimeRangeUtils.computeTimeRanges(granularity, baselineStart, baselineEnd); System.out.println(currentTimeRanges); System.out.println(baselineTimeRanges); } }