com.facebook.util.TimeInterval.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.util.TimeInterval.java

Source

/*
 * Copyright (C) 2012 Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.facebook.util;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.joda.time.DateTime;
import org.joda.time.field.FieldUtils;

/**
 * Represents Time intervals either as durations or periods and abstracts out
 * operations on time intervals in the System.
 * <p/>
 * Durations represent a fixed period of time regardless of when
 * they start or end. ie. 1 day will always be 86400 seconds. Instances
 * that represent duration are constructed via {@link #withMillis(long)}.
 * <p/>
 * Periods represent a period of time but the actual time will depend
 * on when the period starts. For example 1 day will be 23 hours on the first
 * day of DST transition and will be 25 hours on the last day of DST transition.
 * Instances that represent periods are constructed via
 * {@link #withTypeAndLength(TimeIntervalType, int)}.
 * <p>
 *   The main operations abstracted out are the computation of start of
 *   an interval and addition / subtraction of the interval from a time instant.
 * </p>
 */
public class TimeInterval {

    /**
     * An infinite time interval has a length of 0 and always returns the
     * interval start as the start of unix time epoch.
     */
    public static final TimeInterval INFINITE = new TimeInterval(null, 0);
    public static final TimeInterval ZERO = new TimeInterval(null, -1);
    private final long length;
    private final TimeIntervalType type;

    private TimeInterval(TimeIntervalType type, long length) {
        this.type = type;
        this.length = length;
    }

    /**
     * Creates a time interval having a fixed duration of time.
     *
     * @param millis the duration for the interval in milliseconds
     * @return the time interval instance.
     * @throws IllegalArgumentException if millis is less than 1.
     */
    public static TimeInterval withMillis(long millis) {
        validateLength(millis);
        return new TimeInterval(null, millis);
    }

    /**
     * Creates a time interval period based on the supplied type. The actual duration
     * of the period will vary depending on the time instant. The period
     * will take into account DST, varying number of days in a month,
     * leap years, etc.
     * <p>
     * Note that if the interval length doesn't divide the maximum value of the
     * interval type equally, the last interval will be of a smaller length
     * than the previous ones. For example if you specify the interval as
     * 40 seconds, the first interval will have the first 40 seconds in a minute
     * and the second interval will have the remaining 20 seconds in the minute.
     * </p>
     * @param type the time interval type, cannot be null.
     * @param length the length of the interval
     * @return the time interval instance.
     * @throws IllegalArgumentException if length is less than 1.
     */
    public static TimeInterval withTypeAndLength(TimeIntervalType type, int length) {
        if (type == null) {
            throw new IllegalArgumentException("type cannot be null");
        }
        validateLength(length);
        return new TimeInterval(type, length);
    }

    /**
     * Used by jackson for serde
     */
    @JsonCreator
    private static TimeInterval fromJson(@JsonProperty("type") TimeIntervalType type,
            @JsonProperty("length") long length) {
        if (type == null) {
            if (length == 0) {
                return INFINITE;
            } else if (length == -1) {
                return ZERO;
            }
        }
        validateLength(length);
        return new TimeInterval(type, length);
    }

    /**
     * Gets the start instant of the time interval that will contain the
     * supplied time instant. Note that the time zone of the supplied instant
     * plays a significant role in computation of the interval.
     *
     * @param instant the time instant
     *
     * @return the start instant of the time interval that will contain the
     * instant in the time zone of the supplied instant. If the TimeInterval is INFINITE
     * unix epoch for the timezone is returned.
     */
    public DateTime getIntervalStart(DateTime instant) {
        // special handling for ZERO and INFINITE
        if (this == ZERO) {
            return instant;
        } else if (this == INFINITE) {
            return new DateTime(1970, 1, 1, 0, 0, 0, 0, instant.getZone());
        }

        if (type == null) {
            // unix epoch for the timezone.
            DateTime startOfTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, instant.getZone());
            long intervalStart = ((instant.getMillis() - startOfTime.getMillis()) / length) * length;
            return startOfTime.plus(intervalStart);
        } else {
            return type.getTimeIntervalStart(instant, length);
        }
    }

    /**
     * Adds supplied multiples of this interval to the supplied instant.
     *
     * @param instant the instant that needs to be added to.
     * @param multiple the multiple value
     * @throws IllegalArgumentException if multiple is less than one.
     * @throws UnsupportedOperationException if the function is invoked on an {@link #INFINITE}
     * object
     *
     */
    public DateTime plus(DateTime instant, int multiple) {
        if (this == INFINITE) {
            throw new IllegalStateException("plus() function is not supported on an infinite TimeInterval");
        } else if (this == ZERO) {
            return instant;
        }

        validateMultiple(multiple);

        if (type == null) {
            return instant.plus(multiple * getLength());
        } else {
            return instant.plus(type.toPeriod(FieldUtils.safeMultiplyToInt(multiple, getLength())));
        }
    }

    /**
     * Subtracts the supplied multiples of this interval from the supplied instant. If the
     * TimeInterval is {@link #INFINITE} the epoch in the timezone of {@code instant} is returned
     *
     * @param instant the instant to subtract from
     * @param multiple the multiple value
     * @throws IllegalArgumentException if multiple is less than one.
     */
    public DateTime minus(DateTime instant, int multiple) {
        if (this == INFINITE) {
            throw new IllegalStateException("minus() function is not supported on an infinite TimeInterval");
        } else if (this == ZERO) {
            return instant;
        }

        validateMultiple(multiple);

        if (type == null) {
            return instant.minus(multiple * getLength());
        } else {
            return instant.minus(type.toPeriod(FieldUtils.safeMultiplyToInt(multiple, getLength())));
        }
    }

    /**
     * If this interval is of type period. Note that for {@link #INFINITE} & {@link #ZERO} time
     * intervals, this method will return false.
     */
    public boolean isPeriod() {
        return type != null;
    }

    /**
     * Returns the length value.
     *
     * @return the length value
     */
    @JsonProperty("length")
    public long getLength() {
        return length;
    }

    /**
     * Returns the interval type. Interval type is null if {@link #isPeriod()} is false.
     *
     * @return the interval type
     */
    @JsonProperty("type")
    public TimeIntervalType getType() {
        return type;
    }

    /**
     * Returns the length of the interval in milliseconds.
     *
     * Note that the length is approximate if the interval was constructed
     * via {@link #withTypeAndLength(TimeIntervalType, int)}.
     *
     * Also note that this method returns zero if the TimeInterval is
     * {@link #INFINITE}, -1 if the TimeInterval is {@link #ZERO}.
     *
     * @return the length in millis
     * @deprecated Usage of this method is not encouraged because this only
     * works if the TimeInterval represents a duration. If the time interval
     * is period, this might return unexpected values.
     */
    @Deprecated
    public long toApproxMillis() {
        if (type == null) {
            return length;
        } else {
            return type.toDurationMillis() * length;
        }
    }

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

        final TimeInterval that = (TimeInterval) o;

        if (length != that.length) {
            return false;
        }
        return type == that.type;

    }

    @Override
    public int hashCode() {
        int result = (int) (length ^ (length >>> 32));
        result = 31 * result + (type != null ? type.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return "TimeInterval{" + "length=" + length + ", type=" + type + '}';
    }

    private static void validateMultiple(int multiple) {
        if (multiple < 0) {
            throw new IllegalArgumentException("Multiple cannot be less that 0 : " + multiple);
        }
    }

    private static void validateLength(long length) {
        if (length < 1) {
            throw new IllegalArgumentException("length cannot be less than one: " + length);
        }
    }

}