Skip to content

Commit d7774f1

Browse files
committed
Allow min/max dates
Provide some form of support for date-times up to the min/max of Long At the margins, calculations will be slightly imperfect UTC is used for the min/max Fixes #297 Fixes #190
1 parent 0a357a8 commit d7774f1

File tree

11 files changed

+458
-53
lines changed

11 files changed

+458
-53
lines changed

RELEASE-NOTES.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ Changes in 2.8.3
3131

3232
- Add Czech period translations [#313]
3333

34+
- Allow DateTime and Interval to refer to values at Long.MIN_VALUE and Long.MAX_VALUE [#297, #190]
35+
A DateTime may be created with any millisecond value, however at the very edges there may be
36+
some undesirable effects, for example alway using UTC instead of the time-zone
37+
3438
- Fix overflow bug in intervals [#315]
3539

3640
- Better error message for malformed tzdb files [#319]

src/main/java/org/joda/time/DateTimeField.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2001-2005 Stephen Colebourne
2+
* Copyright 2001-2015 Stephen Colebourne
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -378,6 +378,20 @@ public abstract class DateTimeField {
378378
*/
379379
public abstract long set(long instant, int value);
380380

381+
/**
382+
* Sets a value in the milliseconds supplied, allowing a little leniency at the margins.
383+
* <p>
384+
* This is primarily an internal method used by parsing.
385+
*
386+
* @param instant the milliseconds from 1970-01-01T00:00:00Z to set in
387+
* @param value the value to set, in the units of the field
388+
* @return the updated milliseconds
389+
* @throws IllegalArgumentException if the value is invalid
390+
*/
391+
public long setExtended(long instant, int value) {
392+
return set(instant, value);
393+
}
394+
381395
/**
382396
* Sets a value using the specified partial instant.
383397
* <p>

src/main/java/org/joda/time/base/BaseDateTime.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2001-2011 Stephen Colebourne
2+
* Copyright 2001-2015 Stephen Colebourne
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -124,10 +124,7 @@ public BaseDateTime(long instant, Chronology chronology) {
124124
super();
125125
iChronology = checkChronology(chronology);
126126
iMillis = checkInstant(instant, iChronology);
127-
// validate not over maximum
128-
if (iChronology.year().isSupported()) {
129-
iChronology.year().set(iMillis, iChronology.year().get(iMillis));
130-
}
127+
adjustForMinMax();
131128
}
132129

133130
//-----------------------------------------------------------------------
@@ -152,6 +149,7 @@ public BaseDateTime(Object instant, DateTimeZone zone) {
152149
Chronology chrono = checkChronology(converter.getChronology(instant, zone));
153150
iChronology = chrono;
154151
iMillis = checkInstant(converter.getInstantMillis(instant, chrono), chrono);
152+
adjustForMinMax();
155153
}
156154

157155
/**
@@ -173,6 +171,7 @@ public BaseDateTime(Object instant, Chronology chronology) {
173171
InstantConverter converter = ConverterManager.getInstance().getInstantConverter(instant);
174172
iChronology = checkChronology(converter.getChronology(instant, chronology));
175173
iMillis = checkInstant(converter.getInstantMillis(instant, chronology), iChronology);
174+
adjustForMinMax();
176175
}
177176

178177
//-----------------------------------------------------------------------
@@ -258,6 +257,13 @@ public BaseDateTime(
258257
long instant = iChronology.getDateTimeMillis(year, monthOfYear, dayOfMonth,
259258
hourOfDay, minuteOfHour, secondOfMinute, millisOfSecond);
260259
iMillis = checkInstant(instant, iChronology);
260+
adjustForMinMax();
261+
}
262+
263+
private void adjustForMinMax() {
264+
if (iMillis == Long.MIN_VALUE || iMillis == Long.MAX_VALUE) {
265+
iChronology = iChronology.withUTC();
266+
}
261267
}
262268

263269
//-----------------------------------------------------------------------

src/main/java/org/joda/time/chrono/BasicChronology.java

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2001-2014 Stephen Colebourne
2+
* Copyright 2001-2015 Stephen Colebourne
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -146,6 +146,7 @@ public DateTimeZone getZone() {
146146
return DateTimeZone.UTC;
147147
}
148148

149+
@Override
149150
public long getDateTimeMillis(
150151
int year, int monthOfYear, int dayOfMonth, int millisOfDay)
151152
throws IllegalArgumentException {
@@ -156,9 +157,10 @@ public long getDateTimeMillis(
156157

157158
FieldUtils.verifyValueBounds
158159
(DateTimeFieldType.millisOfDay(), millisOfDay, 0, DateTimeConstants.MILLIS_PER_DAY - 1);
159-
return getDateMidnightMillis(year, monthOfYear, dayOfMonth) + millisOfDay;
160+
return getDateTimeMillis0(year, monthOfYear, dayOfMonth, millisOfDay);
160161
}
161162

163+
@Override
162164
public long getDateTimeMillis(
163165
int year, int monthOfYear, int dayOfMonth,
164166
int hourOfDay, int minuteOfHour, int secondOfMinute, int millisOfSecond)
@@ -173,12 +175,29 @@ public long getDateTimeMillis(
173175
FieldUtils.verifyValueBounds(DateTimeFieldType.minuteOfHour(), minuteOfHour, 0, 59);
174176
FieldUtils.verifyValueBounds(DateTimeFieldType.secondOfMinute(), secondOfMinute, 0, 59);
175177
FieldUtils.verifyValueBounds(DateTimeFieldType.millisOfSecond(), millisOfSecond, 0, 999);
176-
177-
return getDateMidnightMillis(year, monthOfYear, dayOfMonth)
178-
+ hourOfDay * DateTimeConstants.MILLIS_PER_HOUR
179-
+ minuteOfHour * DateTimeConstants.MILLIS_PER_MINUTE
180-
+ secondOfMinute * DateTimeConstants.MILLIS_PER_SECOND
181-
+ millisOfSecond;
178+
long millisOfDay = hourOfDay * DateTimeConstants.MILLIS_PER_HOUR
179+
+ minuteOfHour * DateTimeConstants.MILLIS_PER_MINUTE
180+
+ secondOfMinute * DateTimeConstants.MILLIS_PER_SECOND
181+
+ millisOfSecond;
182+
return getDateTimeMillis0(year, monthOfYear, dayOfMonth, (int) millisOfDay);
183+
}
184+
185+
private long getDateTimeMillis0(int year, int monthOfYear, int dayOfMonth, int millisOfDay) {
186+
long dayInstant = getDateMidnightMillis(year, monthOfYear, dayOfMonth);
187+
// try reversed calculation from next day for MIN
188+
if (dayInstant == Long.MIN_VALUE) {
189+
dayInstant = getDateMidnightMillis(year, monthOfYear, dayOfMonth + 1);
190+
millisOfDay = millisOfDay - 86400000;
191+
}
192+
// check for limit caused by millisOfDay addition
193+
// even if dayInstant already MIN or MAX, this still works fine with int math
194+
long result = dayInstant + millisOfDay;
195+
if (result < 0 && dayInstant > 0) {
196+
return Long.MAX_VALUE;
197+
} else if (result > 0 && dayInstant < 0) {
198+
return Long.MIN_VALUE;
199+
}
200+
return result;
182201
}
183202

184203
public int getMinimumDaysInFirstWeek() {
@@ -608,10 +627,17 @@ int getDaysInMonthMaxForSet(long instant, int value) {
608627
* @return the milliseconds
609628
*/
610629
long getDateMidnightMillis(int year, int monthOfYear, int dayOfMonth) {
611-
FieldUtils.verifyValueBounds(DateTimeFieldType.year(), year, getMinYear(), getMaxYear());
630+
FieldUtils.verifyValueBounds(DateTimeFieldType.year(), year, getMinYear() - 1, getMaxYear() + 1);
612631
FieldUtils.verifyValueBounds(DateTimeFieldType.monthOfYear(), monthOfYear, 1, getMaxMonth(year));
613632
FieldUtils.verifyValueBounds(DateTimeFieldType.dayOfMonth(), dayOfMonth, 1, getDaysInYearMonth(year, monthOfYear));
614-
return getYearMonthDayMillis(year, monthOfYear, dayOfMonth);
633+
long instant = getYearMonthDayMillis(year, monthOfYear, dayOfMonth);
634+
// check for limit caused by min/max year +1/-1
635+
if (instant < 0 && year == getMaxYear() + 1) {
636+
return Long.MAX_VALUE;
637+
} else if (instant > 0 && year == getMinYear() - 1) {
638+
return Long.MIN_VALUE;
639+
}
640+
return instant;
615641
}
616642

617643
/**

src/main/java/org/joda/time/chrono/BasicYearDateTimeField.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2001-2013 Stephen Colebourne
2+
* Copyright 2001-2015 Stephen Colebourne
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -85,6 +85,13 @@ public long set(long instant, int year) {
8585
return iChronology.setYear(instant, year);
8686
}
8787

88+
@Override
89+
public long setExtended(long instant, int year) {
90+
FieldUtils.verifyValueBounds(
91+
this, year, iChronology.getMinYear() - 1, iChronology.getMaxYear() + 1);
92+
return iChronology.setYear(instant, year);
93+
}
94+
8895
public long getDifferenceAsLong(long minuendInstant, long subtrahendInstant) {
8996
if (minuendInstant < subtrahendInstant) {
9097
return -iChronology.getYearDifference(subtrahendInstant, minuendInstant);

src/main/java/org/joda/time/chrono/ZonedChronology.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2001-2013 Stephen Colebourne
2+
* Copyright 2001-2015 Stephen Colebourne
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -135,9 +135,19 @@ public long getDateTimeMillis(long instant,
135135
* @return the instant from 1970-01-01T00:00:00Z
136136
*/
137137
private long localToUTC(long localInstant) {
138+
if (localInstant == Long.MAX_VALUE) {
139+
return Long.MAX_VALUE;
140+
} else if (localInstant == Long.MIN_VALUE) {
141+
return Long.MIN_VALUE;
142+
}
138143
DateTimeZone zone = getZone();
139144
int offset = zone.getOffsetFromLocal(localInstant);
140145
long utcInstant = localInstant - offset;
146+
if (localInstant > 0 && utcInstant < 0) {
147+
return Long.MAX_VALUE;
148+
} else if (localInstant < 0 && utcInstant > 0) {
149+
return Long.MIN_VALUE;
150+
}
141151
int offsetBasedOnUtc = zone.getOffset(utcInstant);
142152
if (offset != offsetBasedOnUtc) {
143153
throw new IllegalInstantException(localInstant, zone.getID());

src/main/java/org/joda/time/format/DateTimeParserBucket.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2001-2014 Stephen Colebourne
2+
* Copyright 2001-2015 Stephen Colebourne
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -565,7 +565,7 @@ void init(DateTimeField field, String text, Locale locale) {
565565

566566
long set(long millis, boolean reset) {
567567
if (iText == null) {
568-
millis = iField.set(millis, iValue);
568+
millis = iField.setExtended(millis, iValue);
569569
} else {
570570
millis = iField.set(millis, iText, iLocale);
571571
}

src/test/java/org/joda/time/TestAll.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2001-2010 Stephen Colebourne
2+
* Copyright 2001-2015 Stephen Colebourne
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -129,6 +129,7 @@ public static Test suite() {
129129
suite.addTest(TestStringConvert.suite());
130130
suite.addTest(TestSerialization.suite());
131131
suite.addTest(TestIllegalFieldValueException.suite());
132+
suite.addTest(TestMinMaxLong.suite());
132133
return suite;
133134
}
134135

src/test/java/org/joda/time/TestDateTime_Constructors.java

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2001-2010 Stephen Colebourne
2+
* Copyright 2001-2015 Stephen Colebourne
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -243,36 +243,6 @@ public void testConstructor_long2() throws Throwable {
243243
assertEquals(TEST_TIME2, test.getMillis());
244244
}
245245

246-
/**
247-
* Test constructor (long)
248-
*/
249-
public void testConstructor_long_max() throws Throwable {
250-
DateTime dt = new DateTime(292278993, 12, 31, 23, 59, 59, 999);
251-
DateTime test = new DateTime(dt.getMillis());
252-
assertEquals(dt, test);
253-
try {
254-
new DateTime(dt.getMillis() + 1);
255-
fail();
256-
} catch (IllegalFieldValueException ex) {
257-
// expected
258-
}
259-
}
260-
261-
/**
262-
* Test constructor (long)
263-
*/
264-
public void testConstructor_long_min() throws Throwable {
265-
DateTime dt = new DateTime(-292275054, 1, 1, 0, 0);
266-
DateTime test = new DateTime(dt.getMillis());
267-
assertEquals(dt, test);
268-
try {
269-
new DateTime(dt.getMillis() - 1);
270-
fail();
271-
} catch (IllegalFieldValueException ex) {
272-
// expected
273-
}
274-
}
275-
276246
/**
277247
* Test constructor (long, DateTimeZone)
278248
*/

src/test/java/org/joda/time/TestInterval_Constructors.java

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2001-2006 Stephen Colebourne
2+
* Copyright 2001-2015 Stephen Colebourne
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -177,6 +177,56 @@ public void testConstructor_long_long3() throws Throwable {
177177
} catch (IllegalArgumentException ex) {}
178178
}
179179

180+
public void testConstructor_long_long_minMax() throws Throwable {
181+
Interval test = new Interval(Long.MIN_VALUE, Long.MAX_VALUE);
182+
assertEquals(Long.MIN_VALUE, test.getStartMillis());
183+
assertEquals(Long.MAX_VALUE, test.getEndMillis());
184+
assertEquals(new DateTime(Long.MIN_VALUE), test.getStart());
185+
assertEquals(new DateTime(Long.MAX_VALUE), test.getEnd());
186+
assertEquals(ISOChronology.getInstance(), test.getChronology());
187+
assertEquals(test, test.toInterval());
188+
assertEquals("-292275055-05-16T16:56:25.192+00:09:21/292278994-08-17T07:12:55.807Z", test.toString());
189+
try {
190+
test.toDuration();
191+
fail();
192+
} catch (ArithmeticException ex) {}
193+
try {
194+
test.toDurationMillis();
195+
fail();
196+
} catch (ArithmeticException ex) {}
197+
try {
198+
test.toPeriod();
199+
fail();
200+
} catch (RuntimeException ex) {}
201+
}
202+
203+
public void testConstructor_long_long_min() throws Throwable {
204+
Interval test = new Interval(Long.MIN_VALUE, Long.MIN_VALUE + 9);
205+
assertEquals(Long.MIN_VALUE, test.getStartMillis());
206+
assertEquals(Long.MIN_VALUE + 9, test.getEndMillis());
207+
assertEquals(new DateTime(Long.MIN_VALUE), test.getStart());
208+
assertEquals(new DateTime(Long.MIN_VALUE + 9), test.getEnd());
209+
assertEquals(ISOChronology.getInstance(), test.getChronology());
210+
assertEquals(test, test.toInterval());
211+
assertEquals("-292275055-05-16T16:56:25.192+00:09:21/-292275055-05-16T16:56:25.201+00:09:21", test.toString());
212+
assertEquals(9, test.toDurationMillis());
213+
assertEquals(new Duration(9), test.toDuration());
214+
assertEquals(new Period(9), test.toPeriod());
215+
}
216+
217+
public void testConstructor_long_long_max() throws Throwable {
218+
Interval test = new Interval(Long.MAX_VALUE - 9, Long.MAX_VALUE);
219+
assertEquals(Long.MAX_VALUE - 9, test.getStartMillis());
220+
assertEquals(Long.MAX_VALUE, test.getEndMillis());
221+
assertEquals(new DateTime(Long.MAX_VALUE - 9), test.getStart());
222+
assertEquals(new DateTime(Long.MAX_VALUE), test.getEnd());
223+
assertEquals(ISOChronology.getInstance(), test.getChronology());
224+
assertEquals(test, test.toInterval());
225+
assertEquals("292278994-08-17T07:12:55.798Z/292278994-08-17T07:12:55.807Z", test.toString());
226+
assertEquals(9, test.toDurationMillis());
227+
assertEquals(new Duration(9), test.toDuration());
228+
}
229+
180230
//-----------------------------------------------------------------------
181231
public void testConstructor_long_long_Zone() throws Throwable {
182232
DateTime dt1 = new DateTime(2004, 6, 9, 0, 0, 0, 0);

0 commit comments

Comments
 (0)