/*
* Copyright (c) 2015 Spotify AB.
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.spotify.heroic.common;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.spotify.heroic.time.Clock;
import eu.toolchain.serializer.AutoSerialize;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.lang3.time.FastDateFormat;
import java.sql.Date;
import static com.google.common.base.Preconditions.checkArgument;
@AutoSerialize
@Data
@EqualsAndHashCode(of = {"start", "end"})
public class DateRange implements Comparable<DateRange> {
private static final FastDateFormat FORMAT =
FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss.SSS");
private final long start;
private final long end;
public DateRange(long start, long end) {
checkArgument(start >= 0, "start must be a positive number");
if (end < start) {
throw new IllegalArgumentException(
String.format("start (%d) must come before end (%d)", start, end));
}
this.start = start;
this.end = end;
}
public long start() {
return start;
}
public long end() {
return end;
}
@JsonIgnore
public boolean isEmpty() {
return diff() == 0;
}
@JsonIgnore
public boolean isNotEmpty() {
return !isEmpty();
}
public long diff() {
return end - start;
}
/**
* Creates a range that is rounded to the specified interval.
*
* @param interval Interval to round to. Return same range if 0.
* @return Rounded date range.
*/
public DateRange rounded(long interval) {
if (interval <= 0) {
return this;
}
return new DateRange(start - start % interval, end - (end % interval));
}
public boolean overlap(DateRange other) {
if (end < other.start) {
return false;
}
if (start > other.end) {
return false;
}
return true;
}
@Override
public int compareTo(DateRange other) {
return Long.compare(start, other.start);
}
public DateRange join(DateRange other) {
long start = Math.min(this.start, other.start);
long end = Math.max(this.end, other.end);
return new DateRange(start, end);
}
public boolean contains(long t) {
return t >= start && t <= end;
}
/**
* Modify this range with another range.
* <p>
* A modification asserts that the new range is a subset of the current range. Any span which
* would cause the new range to become out of bounds will be cropped.
*
* @param range The constraints to modify this range against.
* @return A new range representing the modified range.
*/
public DateRange modify(DateRange range) {
return modify(range.getStart(), range.getEnd());
}
/**
* Modify the date range so that it fits within the given range (start - end).
*
* @param start Start value to fit this range into.
* @param end End value (exclusive) to fit this range into.
* @return A modified date range that fits within the given range.
*/
public DateRange modify(long start, long end) {
return new DateRange(Math.max(this.start, start), Math.min(this.end, end - 1));
}
public DateRange start(long start) {
return new DateRange(start, this.end);
}
public DateRange end(long end) {
return new DateRange(this.start, end);
}
public DateRange shift(long extent) {
return new DateRange(Math.max(start + extent, 0), Math.max(end + extent, 0));
}
@Override
public String toString() {
final Date start = new Date(this.start);
final Date end = new Date(this.end);
return "{" + FORMAT.format(start) + "}-{" + FORMAT.format(end) + "}";
}
@JsonCreator
public static DateRange create(
@JsonProperty(value = "start", required = true) Long start,
@JsonProperty(value = "end", required = true) Long end
) {
return new DateRange(start, end);
}
public static DateRange now(long now) {
return new DateRange(now, now);
}
public static DateRange now(Clock clock) {
return now(clock.currentTimeMillis());
}
}