/*
* Copyright 2001-2006 Stephen Colebourne
*
* 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 org.joda.time;
import java.io.
Serializable;
import org.joda.time.base.
BaseInterval;
import org.joda.time.chrono.
ISOChronology;
import org.joda.time.format.
DateTimeFormatter;
import org.joda.time.format.
ISODateTimeFormat;
import org.joda.time.format.
ISOPeriodFormat;
import org.joda.time.format.
PeriodFormatter;
/**
* Interval is the standard implementation of an immutable time interval.
* <p>
* A time interval represents a period of time between two instants.
* Intervals are inclusive of the start instant and exclusive of the end.
* The end instant is always greater than or equal to the start instant.
* <p>
* Intervals have a fixed millisecond duration.
* This is the difference between the start and end instants.
* The duration is represented separately by {@link ReadableDuration}.
* As a result, intervals are not comparable.
* To compare the length of two intervals, you should compare their durations.
* <p>
* An interval can also be converted to a {@link ReadablePeriod}.
* This represents the difference between the start and end points in terms of fields
* such as years and days.
* <p>
* Interval is thread-safe and immutable.
*
* @author Brian S O'Neill
* @author Sean Geoghegan
* @author Stephen Colebourne
* @author Julen Parra
* @since 1.0
*/
public final class
Interval
extends
BaseInterval
implements
ReadableInterval,
Serializable {
/** Serialization version */
private static final long
serialVersionUID = 4922451897541386752L;
//-----------------------------------------------------------------------
/**
* Parses an {@code Interval} from the specified string.
* <p>
* The String formats are described by {@link ISODateTimeFormat#dateTimeParser()}
* and {@link ISOPeriodFormat#standard()}, and may be 'datetime/datetime',
* 'datetime/period' or 'period/datetime'.
* <p>
* This method operates by parsing in the default time-zone.
* Any offset contained within the string being parsed will be normalised to the
* offset of the default time-zone. See also {@link #parseWithOffset(String)}.
*
* @param str the string to parse, not null
* @since 2.0
*/
public static
Interval parse(
String str) {
return new
Interval(
str);
}
/**
* Parses an {@code Interval} from the specified string, using any offset it contains.
* <p>
* The String formats are described by
* {@link ISODateTimeFormat#dateTimeParser()}{@code .withOffsetParsed()}
* and {@link ISOPeriodFormat#standard()}, and may be 'datetime/datetime',
* 'datetime/period' or 'period/datetime'.
* <p>
* Sometimes this method and {@code new Interval(str)} return different results.
* This can be confusing as the difference is not visible in {@link #toString()}.
* <p>
* When passed a string without an offset, such as '2010-06-30T01:20/P1D',
* both the constructor and this method use the default time-zone.
* As such, {@code Interval.parseWithOffset("2010-06-30T01:20/P1D")} and
* {@code new Interval("2010-06-30T01:20/P1D"))} are equal.
* <p>
* However, when this method is passed a string with an offset,
* the offset is directly parsed and stored.
* As such, {@code Interval.parseWithOffset("2010-06-30T01:20+02:00/P1D")} and
* {@code new Interval("2010-06-30T01:20+02:00/P1D"))} are NOT equal.
* The object produced via this method has a zone of {@code DateTimeZone.forOffsetHours(2)}.
* The object produced via the constructor has a zone of {@code DateTimeZone.getDefault()}.
*
* @param str the string to parse, not null
* @since 2.9
*/
public static
Interval parseWithOffset(
String str) {
int
separator =
str.
indexOf('/');
if (
separator < 0) {
throw new
IllegalArgumentException("Format requires a '/' separator: " +
str);
}
String leftStr =
str.
substring(0,
separator);
if (
leftStr.
length() <= 0) {
throw new
IllegalArgumentException("Format invalid: " +
str);
}
String rightStr =
str.
substring(
separator + 1);
if (
rightStr.
length() <= 0) {
throw new
IllegalArgumentException("Format invalid: " +
str);
}
DateTimeFormatter dateTimeParser =
ISODateTimeFormat.
dateTimeParser().
withOffsetParsed();
PeriodFormatter periodParser =
ISOPeriodFormat.
standard();
DateTime start = null;
Period period = null;
// before slash
char
c =
leftStr.
charAt(0);
if (
c == 'P' ||
c == 'p') {
period =
periodParser.
withParseType(
PeriodType.
standard()).
parsePeriod(
leftStr);
} else {
start =
dateTimeParser.
parseDateTime(
leftStr);
}
// after slash
c =
rightStr.
charAt(0);
if (
c == 'P' ||
c == 'p') {
if (
period != null) {
throw new
IllegalArgumentException("Interval composed of two durations: " +
str);
}
period =
periodParser.
withParseType(
PeriodType.
standard()).
parsePeriod(
rightStr);
return new
Interval(
start,
period);
} else {
DateTime end =
dateTimeParser.
parseDateTime(
rightStr);
if (
period != null) {
return new
Interval(
period,
end);
} else {
return new
Interval(
start,
end);
}
}
}
//-----------------------------------------------------------------------
/**
* Constructs an interval from a start and end instant with the ISO
* default chronology in the default time zone.
*
* @param startInstant start of this interval, as milliseconds from 1970-01-01T00:00:00Z.
* @param endInstant end of this interval, as milliseconds from 1970-01-01T00:00:00Z.
* @throws IllegalArgumentException if the end is before the start
*/
public
Interval(long
startInstant, long
endInstant) {
super(
startInstant,
endInstant, null);
}
/**
* Constructs an interval from a start and end instant with the ISO
* default chronology in the specified time zone.
*
* @param startInstant start of this interval, as milliseconds from 1970-01-01T00:00:00Z.
* @param endInstant end of this interval, as milliseconds from 1970-01-01T00:00:00Z.
* @param zone the time zone to use, null means default zone
* @throws IllegalArgumentException if the end is before the start
* @since 1.5
*/
public
Interval(long
startInstant, long
endInstant,
DateTimeZone zone) {
super(
startInstant,
endInstant,
ISOChronology.
getInstance(
zone));
}
/**
* Constructs an interval from a start and end instant with the
* specified chronology.
*
* @param chronology the chronology to use, null is ISO default
* @param startInstant start of this interval, as milliseconds from 1970-01-01T00:00:00Z.
* @param endInstant end of this interval, as milliseconds from 1970-01-01T00:00:00Z.
* @throws IllegalArgumentException if the end is before the start
*/
public
Interval(long
startInstant, long
endInstant,
Chronology chronology) {
super(
startInstant,
endInstant,
chronology);
}
/**
* Constructs an interval from a start and end instant.
* <p>
* The chronology used is that of the start instant.
*
* @param start start of this interval, null means now
* @param end end of this interval, null means now
* @throws IllegalArgumentException if the end is before the start
*/
public
Interval(
ReadableInstant start,
ReadableInstant end) {
super(
start,
end);
}
/**
* Constructs an interval from a start instant and a duration.
*
* @param start start of this interval, null means now
* @param duration the duration of this interval, null means zero length
* @throws IllegalArgumentException if the end is before the start
* @throws ArithmeticException if the end instant exceeds the capacity of a long
*/
public
Interval(
ReadableInstant start,
ReadableDuration duration) {
super(
start,
duration);
}
/**
* Constructs an interval from a millisecond duration and an end instant.
*
* @param duration the duration of this interval, null means zero length
* @param end end of this interval, null means now
* @throws IllegalArgumentException if the end is before the start
* @throws ArithmeticException if the start instant exceeds the capacity of a long
*/
public
Interval(
ReadableDuration duration,
ReadableInstant end) {
super(
duration,
end);
}
/**
* Constructs an interval from a start instant and a time period.
* <p>
* When forming the interval, the chronology from the instant is used
* if present, otherwise the chronology of the period is used.
*
* @param start start of this interval, null means now
* @param period the period of this interval, null means zero length
* @throws IllegalArgumentException if the end is before the start
* @throws ArithmeticException if the end instant exceeds the capacity of a long
*/
public
Interval(
ReadableInstant start,
ReadablePeriod period) {
super(
start,
period);
}
/**
* Constructs an interval from a time period and an end instant.
* <p>
* When forming the interval, the chronology from the instant is used
* if present, otherwise the chronology of the period is used.
*
* @param period the period of this interval, null means zero length
* @param end end of this interval, null means now
* @throws IllegalArgumentException if the end is before the start
* @throws ArithmeticException if the start instant exceeds the capacity of a long
*/
public
Interval(
ReadablePeriod period,
ReadableInstant end) {
super(
period,
end);
}
/**
* Constructs a time interval by converting or copying from another object.
* <p>
* The recognised object types are defined in
* {@link org.joda.time.convert.ConverterManager ConverterManager} and
* include ReadableInterval and String.
* The String formats are described by {@link ISODateTimeFormat#dateTimeParser()}
* and {@link ISOPeriodFormat#standard()}, and may be 'datetime/datetime',
* 'datetime/period' or 'period/datetime'.
*
* @param interval the time interval to copy
* @throws IllegalArgumentException if the interval is invalid
*/
public
Interval(
Object interval) {
super(
interval, null);
}
/**
* Constructs a time interval by converting or copying from another object,
* overriding the chronology.
* <p>
* The recognised object types are defined in
* {@link org.joda.time.convert.ConverterManager ConverterManager} and
* include ReadableInterval and String.
* The String formats are described by {@link ISODateTimeFormat#dateTimeParser()}
* and {@link ISOPeriodFormat#standard()}, and may be 'datetime/datetime',
* 'datetime/period' or 'period/datetime'.
*
* @param interval the time interval to copy
* @param chronology the chronology to use, null means ISO default
* @throws IllegalArgumentException if the interval is invalid
*/
public
Interval(
Object interval,
Chronology chronology) {
super(
interval,
chronology);
}
//-----------------------------------------------------------------------
/**
* Get this interval as an immutable <code>Interval</code> object
* by returning <code>this</code>.
*
* @return <code>this</code>
*/
public
Interval toInterval() {
return this;
}
//-----------------------------------------------------------------------
/**
* Gets the overlap between this interval and another interval.
* <p>
* Intervals are inclusive of the start instant and exclusive of the end.
* An interval overlaps another if it shares some common part of the
* datetime continuum. This method returns the amount of the overlap,
* only if the intervals actually do overlap.
* If the intervals do not overlap, then null is returned.
* <p>
* When two intervals are compared the result is one of three states:
* (a) they abut, (b) there is a gap between them, (c) they overlap.
* The abuts state takes precedence over the other two, thus a zero duration
* interval at the start of a larger interval abuts and does not overlap.
* <p>
* The chronology of the returned interval is the same as that of
* this interval (the chronology of the interval parameter is not used).
* Note that the use of the chronology was only correctly implemented
* in version 1.3.
*
* @param interval the interval to examine, null means now
* @return the overlap interval, null if no overlap
* @since 1.1
*/
public
Interval overlap(
ReadableInterval interval) {
interval =
DateTimeUtils.
getReadableInterval(
interval);
if (
overlaps(
interval) == false) {
return null;
}
long
start =
Math.
max(
getStartMillis(),
interval.
getStartMillis());
long
end =
Math.
min(
getEndMillis(),
interval.
getEndMillis());
return new
Interval(
start,
end,
getChronology());
}
//-----------------------------------------------------------------------
/**
* Gets the gap between this interval and another interval.
* The other interval can be either before or after this interval.
* <p>
* Intervals are inclusive of the start instant and exclusive of the end.
* An interval has a gap to another interval if there is a non-zero
* duration between them. This method returns the amount of the gap only
* if the intervals do actually have a gap between them.
* If the intervals overlap or abut, then null is returned.
* <p>
* When two intervals are compared the result is one of three states:
* (a) they abut, (b) there is a gap between them, (c) they overlap.
* The abuts state takes precedence over the other two, thus a zero duration
* interval at the start of a larger interval abuts and does not overlap.
* <p>
* The chronology of the returned interval is the same as that of
* this interval (the chronology of the interval parameter is not used).
* Note that the use of the chronology was only correctly implemented
* in version 1.3.
*
* @param interval the interval to examine, null means now
* @return the gap interval, null if no gap
* @since 1.1
*/
public
Interval gap(
ReadableInterval interval) {
interval =
DateTimeUtils.
getReadableInterval(
interval);
long
otherStart =
interval.
getStartMillis();
long
otherEnd =
interval.
getEndMillis();
long
thisStart =
getStartMillis();
long
thisEnd =
getEndMillis();
if (
thisStart >
otherEnd) {
return new
Interval(
otherEnd,
thisStart,
getChronology());
} else if (
otherStart >
thisEnd) {
return new
Interval(
thisEnd,
otherStart,
getChronology());
} else {
return null;
}
}
//-----------------------------------------------------------------------
/**
* Does this interval abut with the interval specified.
* <p>
* Intervals are inclusive of the start instant and exclusive of the end.
* An interval abuts if it starts immediately after, or ends immediately
* before this interval without overlap.
* A zero duration interval abuts with itself.
* <p>
* When two intervals are compared the result is one of three states:
* (a) they abut, (b) there is a gap between them, (c) they overlap.
* The abuts state takes precedence over the other two, thus a zero duration
* interval at the start of a larger interval abuts and does not overlap.
* <p>
* For example:
* <pre>
* [09:00 to 10:00) abuts [08:00 to 08:30) = false (completely before)
* [09:00 to 10:00) abuts [08:00 to 09:00) = true
* [09:00 to 10:00) abuts [08:00 to 09:01) = false (overlaps)
*
* [09:00 to 10:00) abuts [09:00 to 09:00) = true
* [09:00 to 10:00) abuts [09:00 to 09:01) = false (overlaps)
*
* [09:00 to 10:00) abuts [10:00 to 10:00) = true
* [09:00 to 10:00) abuts [10:00 to 10:30) = true
*
* [09:00 to 10:00) abuts [10:30 to 11:00) = false (completely after)
*
* [14:00 to 14:00) abuts [14:00 to 14:00) = true
* [14:00 to 14:00) abuts [14:00 to 15:00) = true
* [14:00 to 14:00) abuts [13:00 to 14:00) = true
* </pre>
*
* @param interval the interval to examine, null means now
* @return true if the interval abuts
* @since 1.1
*/
public boolean
abuts(
ReadableInterval interval) {
if (
interval == null) {
long
now =
DateTimeUtils.
currentTimeMillis();
return (
getStartMillis() ==
now ||
getEndMillis() ==
now);
} else {
return (
interval.
getEndMillis() ==
getStartMillis() ||
getEndMillis() ==
interval.
getStartMillis());
}
}
//-----------------------------------------------------------------------
/**
* Creates a new interval with the same start and end, but a different chronology.
*
* @param chronology the chronology to use, null means ISO default
* @return an interval with a different chronology
*/
public
Interval withChronology(
Chronology chronology) {
if (
getChronology() ==
chronology) {
return this;
}
return new
Interval(
getStartMillis(),
getEndMillis(),
chronology);
}
/**
* Creates a new interval with the specified start millisecond instant.
*
* @param startInstant the start instant for the new interval
* @return an interval with the end from this interval and the specified start
* @throws IllegalArgumentException if the resulting interval has end before start
*/
public
Interval withStartMillis(long
startInstant) {
if (
startInstant ==
getStartMillis()) {
return this;
}
return new
Interval(
startInstant,
getEndMillis(),
getChronology());
}
/**
* Creates a new interval with the specified start instant.
*
* @param start the start instant for the new interval, null means now
* @return an interval with the end from this interval and the specified start
* @throws IllegalArgumentException if the resulting interval has end before start
*/
public
Interval withStart(
ReadableInstant start) {
long
startMillis =
DateTimeUtils.
getInstantMillis(
start);
return
withStartMillis(
startMillis);
}
/**
* Creates a new interval with the specified end millisecond instant.
*
* @param endInstant the end instant for the new interval
* @return an interval with the start from this interval and the specified end
* @throws IllegalArgumentException if the resulting interval has end before start
*/
public
Interval withEndMillis(long
endInstant) {
if (
endInstant ==
getEndMillis()) {
return this;
}
return new
Interval(
getStartMillis(),
endInstant,
getChronology());
}
/**
* Creates a new interval with the specified end instant.
*
* @param end the end instant for the new interval, null means now
* @return an interval with the start from this interval and the specified end
* @throws IllegalArgumentException if the resulting interval has end before start
*/
public
Interval withEnd(
ReadableInstant end) {
long
endMillis =
DateTimeUtils.
getInstantMillis(
end);
return
withEndMillis(
endMillis);
}
//-----------------------------------------------------------------------
/**
* Creates a new interval with the specified duration after the start instant.
*
* @param duration the duration to add to the start to get the new end instant, null means zero
* @return an interval with the start from this interval and a calculated end
* @throws IllegalArgumentException if the duration is negative
*/
public
Interval withDurationAfterStart(
ReadableDuration duration) {
long
durationMillis =
DateTimeUtils.
getDurationMillis(
duration);
if (
durationMillis ==
toDurationMillis()) {
return this;
}
Chronology chrono =
getChronology();
long
startMillis =
getStartMillis();
long
endMillis =
chrono.
add(
startMillis,
durationMillis, 1);
return new
Interval(
startMillis,
endMillis,
chrono);
}
/**
* Creates a new interval with the specified duration before the end instant.
*
* @param duration the duration to subtract from the end to get the new start instant, null means zero
* @return an interval with the end from this interval and a calculated start
* @throws IllegalArgumentException if the duration is negative
*/
public
Interval withDurationBeforeEnd(
ReadableDuration duration) {
long
durationMillis =
DateTimeUtils.
getDurationMillis(
duration);
if (
durationMillis ==
toDurationMillis()) {
return this;
}
Chronology chrono =
getChronology();
long
endMillis =
getEndMillis();
long
startMillis =
chrono.
add(
endMillis,
durationMillis, -1);
return new
Interval(
startMillis,
endMillis,
chrono);
}
//-----------------------------------------------------------------------
/**
* Creates a new interval with the specified period after the start instant.
*
* @param period the period to add to the start to get the new end instant, null means zero
* @return an interval with the start from this interval and a calculated end
* @throws IllegalArgumentException if the period is negative
*/
public
Interval withPeriodAfterStart(
ReadablePeriod period) {
if (
period == null) {
return
withDurationAfterStart(null);
}
Chronology chrono =
getChronology();
long
startMillis =
getStartMillis();
long
endMillis =
chrono.
add(
period,
startMillis, 1);
return new
Interval(
startMillis,
endMillis,
chrono);
}
/**
* Creates a new interval with the specified period before the end instant.
*
* @param period the period to subtract from the end to get the new start instant, null means zero
* @return an interval with the end from this interval and a calculated start
* @throws IllegalArgumentException if the period is negative
*/
public
Interval withPeriodBeforeEnd(
ReadablePeriod period) {
if (
period == null) {
return
withDurationBeforeEnd(null);
}
Chronology chrono =
getChronology();
long
endMillis =
getEndMillis();
long
startMillis =
chrono.
add(
period,
endMillis, -1);
return new
Interval(
startMillis,
endMillis,
chrono);
}
}