1 | /* |
2 | * Copyright 2001-2006 Stephen Colebourne |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | */ |
16 | package org.joda.time; |
17 | |
18 | import java.io.Serializable; |
19 | |
20 | import org.joda.time.base.BaseInterval; |
21 | import org.joda.time.chrono.ISOChronology; |
22 | import org.joda.time.format.ISODateTimeFormat; |
23 | import org.joda.time.format.ISOPeriodFormat; |
24 | |
25 | /** |
26 | * Interval is the standard implementation of an immutable time interval. |
27 | * <p> |
28 | * A time interval represents a period of time between two instants. |
29 | * Intervals are inclusive of the start instant and exclusive of the end. |
30 | * The end instant is always greater than or equal to the start instant. |
31 | * <p> |
32 | * Intervals have a fixed millisecond duration. |
33 | * This is the difference between the start and end instants. |
34 | * The duration is represented separately by {@link ReadableDuration}. |
35 | * As a result, intervals are not comparable. |
36 | * To compare the length of two intervals, you should compare their durations. |
37 | * <p> |
38 | * An interval can also be converted to a {@link ReadablePeriod}. |
39 | * This represents the difference between the start and end points in terms of fields |
40 | * such as years and days. |
41 | * <p> |
42 | * Interval is thread-safe and immutable. |
43 | * |
44 | * @author Brian S O'Neill |
45 | * @author Sean Geoghegan |
46 | * @author Stephen Colebourne |
47 | * @author Julen Parra |
48 | * @since 1.0 |
49 | */ |
50 | public final class Interval |
51 | extends BaseInterval |
52 | implements ReadableInterval, Serializable { |
53 | |
54 | /** Serialization version */ |
55 | private static final long serialVersionUID = 4922451897541386752L; |
56 | |
57 | //----------------------------------------------------------------------- |
58 | /** |
59 | * Constructs an interval from a start and end instant with the ISO |
60 | * default chronology in the default time zone. |
61 | * |
62 | * @param startInstant start of this interval, as milliseconds from 1970-01-01T00:00:00Z. |
63 | * @param endInstant end of this interval, as milliseconds from 1970-01-01T00:00:00Z. |
64 | * @throws IllegalArgumentException if the end is before the start |
65 | */ |
66 | public Interval(long startInstant, long endInstant) { |
67 | super(startInstant, endInstant, null); |
68 | } |
69 | |
70 | /** |
71 | * Constructs an interval from a start and end instant with the ISO |
72 | * default chronology in the specified time zone. |
73 | * |
74 | * @param startInstant start of this interval, as milliseconds from 1970-01-01T00:00:00Z. |
75 | * @param endInstant end of this interval, as milliseconds from 1970-01-01T00:00:00Z. |
76 | * @param zone the time zone to use, null means default zone |
77 | * @throws IllegalArgumentException if the end is before the start |
78 | * @since 1.5 |
79 | */ |
80 | public Interval(long startInstant, long endInstant, DateTimeZone zone) { |
81 | super(startInstant, endInstant, ISOChronology.getInstance(zone)); |
82 | } |
83 | |
84 | /** |
85 | * Constructs an interval from a start and end instant with the |
86 | * specified chronology. |
87 | * |
88 | * @param chronology the chronology to use, null is ISO default |
89 | * @param startInstant start of this interval, as milliseconds from 1970-01-01T00:00:00Z. |
90 | * @param endInstant end of this interval, as milliseconds from 1970-01-01T00:00:00Z. |
91 | * @throws IllegalArgumentException if the end is before the start |
92 | */ |
93 | public Interval(long startInstant, long endInstant, Chronology chronology) { |
94 | super(startInstant, endInstant, chronology); |
95 | } |
96 | |
97 | /** |
98 | * Constructs an interval from a start and end instant. |
99 | * <p> |
100 | * The chronology used is that of the start instant. |
101 | * |
102 | * @param start start of this interval, null means now |
103 | * @param end end of this interval, null means now |
104 | * @throws IllegalArgumentException if the end is before the start |
105 | */ |
106 | public Interval(ReadableInstant start, ReadableInstant end) { |
107 | super(start, end); |
108 | } |
109 | |
110 | /** |
111 | * Constructs an interval from a start instant and a duration. |
112 | * |
113 | * @param start start of this interval, null means now |
114 | * @param duration the duration of this interval, null means zero length |
115 | * @throws IllegalArgumentException if the end is before the start |
116 | * @throws ArithmeticException if the end instant exceeds the capacity of a long |
117 | */ |
118 | public Interval(ReadableInstant start, ReadableDuration duration) { |
119 | super(start, duration); |
120 | } |
121 | |
122 | /** |
123 | * Constructs an interval from a millisecond duration and an end instant. |
124 | * |
125 | * @param duration the duration of this interval, null means zero length |
126 | * @param end end of this interval, null means now |
127 | * @throws IllegalArgumentException if the end is before the start |
128 | * @throws ArithmeticException if the start instant exceeds the capacity of a long |
129 | */ |
130 | public Interval(ReadableDuration duration, ReadableInstant end) { |
131 | super(duration, end); |
132 | } |
133 | |
134 | /** |
135 | * Constructs an interval from a start instant and a time period. |
136 | * <p> |
137 | * When forming the interval, the chronology from the instant is used |
138 | * if present, otherwise the chronology of the period is used. |
139 | * |
140 | * @param start start of this interval, null means now |
141 | * @param period the period of this interval, null means zero length |
142 | * @throws IllegalArgumentException if the end is before the start |
143 | * @throws ArithmeticException if the end instant exceeds the capacity of a long |
144 | */ |
145 | public Interval(ReadableInstant start, ReadablePeriod period) { |
146 | super(start, period); |
147 | } |
148 | |
149 | /** |
150 | * Constructs an interval from a time period and an end instant. |
151 | * <p> |
152 | * When forming the interval, the chronology from the instant is used |
153 | * if present, otherwise the chronology of the period is used. |
154 | * |
155 | * @param period the period of this interval, null means zero length |
156 | * @param end end of this interval, null means now |
157 | * @throws IllegalArgumentException if the end is before the start |
158 | * @throws ArithmeticException if the start instant exceeds the capacity of a long |
159 | */ |
160 | public Interval(ReadablePeriod period, ReadableInstant end) { |
161 | super(period, end); |
162 | } |
163 | |
164 | /** |
165 | * Constructs a time interval by converting or copying from another object. |
166 | * <p> |
167 | * The recognised object types are defined in |
168 | * {@link org.joda.time.convert.ConverterManager ConverterManager} and |
169 | * include ReadableInterval and String. |
170 | * The String formats are described by {@link ISODateTimeFormat#dateTimeParser()} |
171 | * and {@link ISOPeriodFormat#standard()}, and may be 'datetime/datetime', |
172 | * 'datetime/period' or 'period/datetime'. |
173 | * |
174 | * @param interval the time interval to copy |
175 | * @throws IllegalArgumentException if the interval is invalid |
176 | */ |
177 | public Interval(Object interval) { |
178 | super(interval, null); |
179 | } |
180 | |
181 | /** |
182 | * Constructs a time interval by converting or copying from another object, |
183 | * overriding the chronology. |
184 | * <p> |
185 | * The recognised object types are defined in |
186 | * {@link org.joda.time.convert.ConverterManager ConverterManager} and |
187 | * include ReadableInterval and String. |
188 | * The String formats are described by {@link ISODateTimeFormat#dateTimeParser()} |
189 | * and {@link ISOPeriodFormat#standard()}, and may be 'datetime/datetime', |
190 | * 'datetime/period' or 'period/datetime'. |
191 | * |
192 | * @param interval the time interval to copy |
193 | * @param chronology the chronology to use, null means ISO default |
194 | * @throws IllegalArgumentException if the interval is invalid |
195 | */ |
196 | public Interval(Object interval, Chronology chronology) { |
197 | super(interval, chronology); |
198 | } |
199 | |
200 | //----------------------------------------------------------------------- |
201 | /** |
202 | * Get this interval as an immutable <code>Interval</code> object |
203 | * by returning <code>this</code>. |
204 | * |
205 | * @return <code>this</code> |
206 | */ |
207 | public Interval toInterval() { |
208 | return this; |
209 | } |
210 | |
211 | //----------------------------------------------------------------------- |
212 | /** |
213 | * Gets the overlap between this interval and another interval. |
214 | * <p> |
215 | * Intervals are inclusive of the start instant and exclusive of the end. |
216 | * An interval overlaps another if it shares some common part of the |
217 | * datetime continuum. This method returns the amount of the overlap, |
218 | * only if the intervals actually do overlap. |
219 | * If the intervals do not overlap, then null is returned. |
220 | * <p> |
221 | * When two intervals are compared the result is one of three states: |
222 | * (a) they abut, (b) there is a gap between them, (c) they overlap. |
223 | * The abuts state takes precedence over the other two, thus a zero duration |
224 | * interval at the start of a larger interval abuts and does not overlap. |
225 | * <p> |
226 | * The chronology of the returned interval is the same as that of |
227 | * this interval (the chronology of the interval parameter is not used). |
228 | * Note that the use of the chronology was only correctly implemented |
229 | * in version 1.3. |
230 | * |
231 | * @param interval the interval to examine, null means now |
232 | * @return the overlap interval, null if no overlap |
233 | * @since 1.1 |
234 | */ |
235 | public Interval overlap(ReadableInterval interval) { |
236 | interval = DateTimeUtils.getReadableInterval(interval); |
237 | if (overlaps(interval) == false) { |
238 | return null; |
239 | } |
240 | long start = Math.max(getStartMillis(), interval.getStartMillis()); |
241 | long end = Math.min(getEndMillis(), interval.getEndMillis()); |
242 | return new Interval(start, end, getChronology()); |
243 | } |
244 | |
245 | //----------------------------------------------------------------------- |
246 | /** |
247 | * Gets the gap between this interval and another interval. |
248 | * The other interval can be either before or after this interval. |
249 | * <p> |
250 | * Intervals are inclusive of the start instant and exclusive of the end. |
251 | * An interval has a gap to another interval if there is a non-zero |
252 | * duration between them. This method returns the amount of the gap only |
253 | * if the intervals do actually have a gap between them. |
254 | * If the intervals overlap or abut, then null is returned. |
255 | * <p> |
256 | * When two intervals are compared the result is one of three states: |
257 | * (a) they abut, (b) there is a gap between them, (c) they overlap. |
258 | * The abuts state takes precedence over the other two, thus a zero duration |
259 | * interval at the start of a larger interval abuts and does not overlap. |
260 | * <p> |
261 | * The chronology of the returned interval is the same as that of |
262 | * this interval (the chronology of the interval parameter is not used). |
263 | * Note that the use of the chronology was only correctly implemented |
264 | * in version 1.3. |
265 | * |
266 | * @param interval the interval to examine, null means now |
267 | * @return the gap interval, null if no gap |
268 | * @since 1.1 |
269 | */ |
270 | public Interval gap(ReadableInterval interval) { |
271 | interval = DateTimeUtils.getReadableInterval(interval); |
272 | long otherStart = interval.getStartMillis(); |
273 | long otherEnd = interval.getEndMillis(); |
274 | long thisStart = getStartMillis(); |
275 | long thisEnd = getEndMillis(); |
276 | if (thisStart > otherEnd) { |
277 | return new Interval(otherEnd, thisStart, getChronology()); |
278 | } else if (otherStart > thisEnd) { |
279 | return new Interval(thisEnd, otherStart, getChronology()); |
280 | } else { |
281 | return null; |
282 | } |
283 | } |
284 | |
285 | //----------------------------------------------------------------------- |
286 | /** |
287 | * Does this interval abut with the interval specified. |
288 | * <p> |
289 | * Intervals are inclusive of the start instant and exclusive of the end. |
290 | * An interval abuts if it starts immediately after, or ends immediately |
291 | * before this interval without overlap. |
292 | * A zero duration interval abuts with itself. |
293 | * <p> |
294 | * When two intervals are compared the result is one of three states: |
295 | * (a) they abut, (b) there is a gap between them, (c) they overlap. |
296 | * The abuts state takes precedence over the other two, thus a zero duration |
297 | * interval at the start of a larger interval abuts and does not overlap. |
298 | * <p> |
299 | * For example: |
300 | * <pre> |
301 | * [09:00 to 10:00) abuts [08:00 to 08:30) = false (completely before) |
302 | * [09:00 to 10:00) abuts [08:00 to 09:00) = true |
303 | * [09:00 to 10:00) abuts [08:00 to 09:01) = false (overlaps) |
304 | * |
305 | * [09:00 to 10:00) abuts [09:00 to 09:00) = true |
306 | * [09:00 to 10:00) abuts [09:00 to 09:01) = false (overlaps) |
307 | * |
308 | * [09:00 to 10:00) abuts [10:00 to 10:00) = true |
309 | * [09:00 to 10:00) abuts [10:00 to 10:30) = true |
310 | * |
311 | * [09:00 to 10:00) abuts [10:30 to 11:00) = false (completely after) |
312 | * |
313 | * [14:00 to 14:00) abuts [14:00 to 14:00) = true |
314 | * [14:00 to 14:00) abuts [14:00 to 15:00) = true |
315 | * [14:00 to 14:00) abuts [13:00 to 14:00) = true |
316 | * </pre> |
317 | * |
318 | * @param interval the interval to examine, null means now |
319 | * @return true if the interval abuts |
320 | * @since 1.1 |
321 | */ |
322 | public boolean abuts(ReadableInterval interval) { |
323 | if (interval == null) { |
324 | long now = DateTimeUtils.currentTimeMillis(); |
325 | return (getStartMillis() == now || getEndMillis() == now); |
326 | } else { |
327 | return (interval.getEndMillis() == getStartMillis() || |
328 | getEndMillis() == interval.getStartMillis()); |
329 | } |
330 | } |
331 | |
332 | //----------------------------------------------------------------------- |
333 | /** |
334 | * Creates a new interval with the same start and end, but a different chronology. |
335 | * |
336 | * @param chronology the chronology to use, null means ISO default |
337 | * @return an interval with a different chronology |
338 | */ |
339 | public Interval withChronology(Chronology chronology) { |
340 | if (getChronology() == chronology) { |
341 | return this; |
342 | } |
343 | return new Interval(getStartMillis(), getEndMillis(), chronology); |
344 | } |
345 | |
346 | /** |
347 | * Creates a new interval with the specified start millisecond instant. |
348 | * |
349 | * @param startInstant the start instant for the new interval |
350 | * @return an interval with the end from this interval and the specified start |
351 | * @throws IllegalArgumentException if the resulting interval has end before start |
352 | */ |
353 | public Interval withStartMillis(long startInstant) { |
354 | if (startInstant == getStartMillis()) { |
355 | return this; |
356 | } |
357 | return new Interval(startInstant, getEndMillis(), getChronology()); |
358 | } |
359 | |
360 | /** |
361 | * Creates a new interval with the specified start instant. |
362 | * |
363 | * @param start the start instant for the new interval, null means now |
364 | * @return an interval with the end from this interval and the specified start |
365 | * @throws IllegalArgumentException if the resulting interval has end before start |
366 | */ |
367 | public Interval withStart(ReadableInstant start) { |
368 | long startMillis = DateTimeUtils.getInstantMillis(start); |
369 | return withStartMillis(startMillis); |
370 | } |
371 | |
372 | /** |
373 | * Creates a new interval with the specified start millisecond instant. |
374 | * |
375 | * @param endInstant the end instant for the new interval |
376 | * @return an interval with the start from this interval and the specified end |
377 | * @throws IllegalArgumentException if the resulting interval has end before start |
378 | */ |
379 | public Interval withEndMillis(long endInstant) { |
380 | if (endInstant == getEndMillis()) { |
381 | return this; |
382 | } |
383 | return new Interval(getStartMillis(), endInstant, getChronology()); |
384 | } |
385 | |
386 | /** |
387 | * Creates a new interval with the specified end instant. |
388 | * |
389 | * @param end the end instant for the new interval, null means now |
390 | * @return an interval with the start from this interval and the specified end |
391 | * @throws IllegalArgumentException if the resulting interval has end before start |
392 | */ |
393 | public Interval withEnd(ReadableInstant end) { |
394 | long endMillis = DateTimeUtils.getInstantMillis(end); |
395 | return withEndMillis(endMillis); |
396 | } |
397 | |
398 | //----------------------------------------------------------------------- |
399 | /** |
400 | * Creates a new interval with the specified duration after the start instant. |
401 | * |
402 | * @param duration the duration to add to the start to get the new end instant, null means zero |
403 | * @return an interval with the start from this interval and a calculated end |
404 | * @throws IllegalArgumentException if the duration is negative |
405 | */ |
406 | public Interval withDurationAfterStart(ReadableDuration duration) { |
407 | long durationMillis = DateTimeUtils.getDurationMillis(duration); |
408 | if (durationMillis == toDurationMillis()) { |
409 | return this; |
410 | } |
411 | Chronology chrono = getChronology(); |
412 | long startMillis = getStartMillis(); |
413 | long endMillis = chrono.add(startMillis, durationMillis, 1); |
414 | return new Interval(startMillis, endMillis, chrono); |
415 | } |
416 | |
417 | /** |
418 | * Creates a new interval with the specified duration before the end instant. |
419 | * |
420 | * @param duration the duration to add to the start to get the new end instant, null means zero |
421 | * @return an interval with the start from this interval and a calculated end |
422 | * @throws IllegalArgumentException if the duration is negative |
423 | */ |
424 | public Interval withDurationBeforeEnd(ReadableDuration duration) { |
425 | long durationMillis = DateTimeUtils.getDurationMillis(duration); |
426 | if (durationMillis == toDurationMillis()) { |
427 | return this; |
428 | } |
429 | Chronology chrono = getChronology(); |
430 | long endMillis = getEndMillis(); |
431 | long startMillis = chrono.add(endMillis, durationMillis, -1); |
432 | return new Interval(startMillis, endMillis, chrono); |
433 | } |
434 | |
435 | //----------------------------------------------------------------------- |
436 | /** |
437 | * Creates a new interval with the specified period after the start instant. |
438 | * |
439 | * @param period the period to add to the start to get the new end instant, null means zero |
440 | * @return an interval with the start from this interval and a calculated end |
441 | * @throws IllegalArgumentException if the period is negative |
442 | */ |
443 | public Interval withPeriodAfterStart(ReadablePeriod period) { |
444 | if (period == null) { |
445 | return withDurationAfterStart(null); |
446 | } |
447 | Chronology chrono = getChronology(); |
448 | long startMillis = getStartMillis(); |
449 | long endMillis = chrono.add(period, startMillis, 1); |
450 | return new Interval(startMillis, endMillis, chrono); |
451 | } |
452 | |
453 | /** |
454 | * Creates a new interval with the specified period before the end instant. |
455 | * |
456 | * @param period the period to add to the start to get the new end instant, null means zero |
457 | * @return an interval with the start from this interval and a calculated end |
458 | * @throws IllegalArgumentException if the period is negative |
459 | */ |
460 | public Interval withPeriodBeforeEnd(ReadablePeriod period) { |
461 | if (period == null) { |
462 | return withDurationBeforeEnd(null); |
463 | } |
464 | Chronology chrono = getChronology(); |
465 | long endMillis = getEndMillis(); |
466 | long startMillis = chrono.add(period, endMillis, -1); |
467 | return new Interval(startMillis, endMillis, chrono); |
468 | } |
469 | |
470 | } |