com.metamx.druid.PeriodGranularity.java Source code

Java tutorial

Introduction

Here is the source code for com.metamx.druid.PeriodGranularity.java

Source

/*
 * Druid - a distributed column store.
 * Copyright (C) 2012  Metamarkets Group Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package com.metamx.druid;

import org.codehaus.jackson.annotate.JsonCreator;
import org.codehaus.jackson.annotate.JsonProperty;
import org.joda.time.Chronology;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Period;
import org.joda.time.chrono.ISOChronology;

public class PeriodGranularity extends BaseQueryGranularity {
    private final Period period;
    private final Chronology chronology;
    private final long origin;
    private final boolean hasOrigin;

    private final boolean isCompound;

    @JsonCreator
    public PeriodGranularity(@JsonProperty("period") Period period, @JsonProperty("origin") DateTime origin,
            @JsonProperty("timeZone") DateTimeZone tz) {
        this.period = period;
        this.chronology = tz == null ? ISOChronology.getInstanceUTC() : ISOChronology.getInstance(tz);
        if (origin == null) {
            // default to origin in given time zone when aligning multi-period granularities
            this.origin = new DateTime(0, DateTimeZone.UTC).withZoneRetainFields(chronology.getZone()).getMillis();
            this.hasOrigin = false;
        } else {
            this.origin = origin.getMillis();
            this.hasOrigin = true;
        }
        this.isCompound = isCompoundPeriod(period);
    }

    @JsonProperty("period")
    public Period getPeriod() {
        return period;
    }

    @JsonProperty("timeZone")
    public DateTimeZone getTimeZone() {
        return chronology.getZone();
    }

    @JsonProperty("origin")
    public DateTime getOrigin() {
        return hasOrigin ? new DateTime(origin) : null;
    }

    @Override
    public DateTime toDateTime(long t) {
        return new DateTime(t, chronology.getZone());
    }

    @Override
    public long next(long t) {
        return chronology.add(period, t, 1);
    }

    @Override
    public long truncate(long t) {
        if (isCompound) {
            try {
                return truncateMillisPeriod(t);
            } catch (UnsupportedOperationException e) {
                return truncateCompoundPeriod(t);
            }
        }

        final int years = period.getYears();
        if (years > 0) {
            if (years > 1 || hasOrigin) {
                int y = chronology.years().getDifference(t, origin);
                y -= y % years;
                long tt = chronology.years().add(origin, y);
                // always round down to the previous period (for timestamps prior to origin)
                if (t < tt)
                    t = chronology.years().add(tt, -years);
                else
                    t = tt;
                return t;
            } else {
                return chronology.year().roundFloor(t);
            }
        }

        final int months = period.getMonths();
        if (months > 0) {
            if (months > 1 || hasOrigin) {
                int m = chronology.months().getDifference(t, origin);
                m -= m % months;
                long tt = chronology.months().add(origin, m);
                // always round down to the previous period (for timestamps prior to origin)
                if (t < tt)
                    t = chronology.months().add(tt, -months);
                else
                    t = tt;
                return t;
            } else {
                return chronology.monthOfYear().roundFloor(t);
            }
        }

        final int weeks = period.getWeeks();
        if (weeks > 0) {
            if (weeks > 1 || hasOrigin) {
                // align on multiples from origin
                int w = chronology.weeks().getDifference(t, origin);
                w -= w % weeks;
                long tt = chronology.weeks().add(origin, w);
                // always round down to the previous period (for timestamps prior to origin)
                if (t < tt)
                    t = chronology.weeks().add(tt, -weeks);
                else
                    t = tt;
                return t;
            } else {
                t = chronology.dayOfWeek().roundFloor(t);
                // default to Monday as beginning of the week
                return chronology.dayOfWeek().set(t, 1);
            }
        }

        final int days = period.getDays();
        if (days > 0) {
            if (days > 1 || hasOrigin) {
                // align on multiples from origin
                int d = chronology.days().getDifference(t, origin);
                d -= d % days;
                long tt = chronology.days().add(origin, d);
                // always round down to the previous period (for timestamps prior to origin)
                if (t < tt)
                    t = chronology.days().add(tt, -days);
                else
                    t = tt;
                return t;
            } else {
                t = chronology.hourOfDay().roundFloor(t);
                return chronology.hourOfDay().set(t, 0);
            }
        }

        final int hours = period.getHours();
        if (hours > 0) {
            if (hours > 1 || hasOrigin) {
                // align on multiples from origin
                long h = chronology.hours().getDifferenceAsLong(t, origin);
                h -= h % hours;
                long tt = chronology.hours().add(origin, h);
                // always round down to the previous period (for timestamps prior to origin)
                if (t < tt)
                    t = chronology.hours().add(tt, -hours);
                else
                    t = tt;
                return t;
            } else {
                t = chronology.minuteOfHour().roundFloor(t);
                return chronology.minuteOfHour().set(t, 0);
            }
        }

        final int minutes = period.getMinutes();
        if (minutes > 0) {
            // align on multiples from origin
            if (minutes > 1 || hasOrigin) {
                long m = chronology.minutes().getDifferenceAsLong(t, origin);
                m -= m % minutes;
                long tt = chronology.minutes().add(origin, m);
                // always round down to the previous period (for timestamps prior to origin)
                if (t < tt)
                    t = chronology.minutes().add(tt, -minutes);
                else
                    t = tt;
                return t;
            } else {
                t = chronology.secondOfMinute().roundFloor(t);
                return chronology.secondOfMinute().set(t, 0);
            }
        }

        final int seconds = period.getSeconds();
        if (seconds > 0) {
            // align on multiples from origin
            if (seconds > 1 || hasOrigin) {
                long s = chronology.seconds().getDifferenceAsLong(t, origin);
                s -= s % seconds;
                long tt = chronology.seconds().add(origin, s);
                // always round down to the previous period (for timestamps prior to origin)
                if (t < tt)
                    t = chronology.seconds().add(tt, -seconds);
                else
                    t = tt;
                return t;
            } else {
                return chronology.millisOfSecond().set(t, 0);
            }
        }

        final int millis = period.getMillis();
        if (millis > 0) {
            if (millis > 1) {
                long ms = chronology.millis().getDifferenceAsLong(t, origin);
                ms -= ms % millis;
                long tt = chronology.millis().add(origin, ms);
                // always round down to the previous period (for timestamps prior to origin)
                if (t < tt)
                    t = chronology.millis().add(tt, -millis);
                else
                    t = tt;
                return t;
            } else {
                return t;
            }
        }

        return t;
    }

    private static boolean isCompoundPeriod(Period period) {
        int[] values = period.getValues();
        boolean single = false;
        for (int v : values) {
            if (v > 0) {
                if (single)
                    return true;
                single = true;
            }
        }
        return false;
    }

    private long truncateCompoundPeriod(long t) {
        long current;
        if (t >= origin) {
            long next = origin;
            do {
                current = next;
                next = chronology.add(period, current, 1);
            } while (t >= next);
        } else {
            current = origin;
            do {
                current = chronology.add(period, current, -1);
            } while (t < current);
        }
        return current;
    }

    private long truncateMillisPeriod(long t) {
        // toStandardDuration assumes days are always 24h, and hours are always 60 minutes,
        // which may not always be the case, e.g if there are daylight saving changes.
        if (chronology.days().isPrecise() && chronology.hours().isPrecise()) {
            final long millis = period.toStandardDuration().getMillis();
            t -= t % millis + origin % millis;
            return t;
        } else {
            throw new UnsupportedOperationException(
                    "Period cannot be converted to milliseconds as some fields mays vary in length with chronology "
                            + chronology.toString());
        }
    }

    @Override
    public byte[] cacheKey() {
        return (period.toString() + ":" + chronology.getZone().toString()).getBytes();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        PeriodGranularity that = (PeriodGranularity) o;

        if (hasOrigin != that.hasOrigin) {
            return false;
        }
        if (origin != that.origin) {
            return false;
        }
        if (!chronology.equals(that.chronology)) {
            return false;
        }
        if (!period.equals(that.period)) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int result = period.hashCode();
        result = 31 * result + chronology.hashCode();
        result = 31 * result + (int) (origin ^ (origin >>> 32));
        result = 31 * result + (hasOrigin ? 1 : 0);
        return result;
    }

    @Override
    public String toString() {
        return "PeriodGranularity{" + "period=" + period + ", timeZone=" + chronology.getZone() + ", origin="
                + (hasOrigin ? origin : "null") + '}';
    }
}