|
8 | 8 | import java.time.*; |
9 | 9 | import java.time.format.DateTimeFormatter; |
10 | 10 | import java.time.format.DateTimeFormatterBuilder; |
| 11 | +import java.time.format.DateTimeParseException; |
11 | 12 | import java.time.format.ResolverStyle; |
12 | 13 | import java.time.temporal.ChronoField; |
13 | 14 | import java.time.temporal.ChronoUnit; |
@@ -203,7 +204,7 @@ public StringInfo(String input) { |
203 | 204 | } |
204 | 205 | } |
205 | 206 |
|
206 | | - static class DateInfo { |
| 207 | + static class DateInfo implements Comparable<DateInfo> { |
207 | 208 | public final ValidDescriptor descriptor; |
208 | 209 | public final String precision; |
209 | 210 | public final boolean indexable; |
@@ -266,6 +267,42 @@ private static TemporalAccessor parseDate(String date) { |
266 | 267 | return null; |
267 | 268 | } |
268 | 269 | } |
| 270 | + |
| 271 | + @Override |
| 272 | + public int compareTo(DateInfo o) { |
| 273 | + boolean thisIndexable = this.indexable; |
| 274 | + boolean oIndexable = o.indexable; |
| 275 | + boolean thisIsYears = this.precision.equals(ChronoUnit.YEARS.toString()); |
| 276 | + boolean oIsYears = o.precision.equals(ChronoUnit.YEARS.toString()); |
| 277 | + |
| 278 | + if (thisIndexable && oIndexable) { |
| 279 | + // Compare actual dates with UTC string |
| 280 | + ZonedDateTime thisDate = ZonedDateTime.parse(this.utcDateTimeString); |
| 281 | + ZonedDateTime oDate = ZonedDateTime.parse(o.utcDateTimeString); |
| 282 | + if (thisDate.isEqual(oDate)) { |
| 283 | + return 0; |
| 284 | + } else { |
| 285 | + return thisDate.isBefore(oDate) ? -1 : 1; |
| 286 | + } |
| 287 | + } |
| 288 | + else if ((thisIsYears && oIsYears) || (thisIsYears && oIndexable) || (thisIndexable && oIsYears)) { |
| 289 | + // Compare years only as longs; parse both as string objects since both may not be just a long. |
| 290 | + // Watch out for negative years... |
| 291 | + String thisYearText = this.utcDateTimeString.substring(0, this.utcDateTimeString.indexOf('-', 1)); |
| 292 | + String oYearText = o.utcDateTimeString.substring(0, o.utcDateTimeString.indexOf('-', 1)); |
| 293 | + Long thisYear = Long.parseLong(thisYearText); |
| 294 | + Long oYear = Long.parseLong(oYearText); |
| 295 | + if (thisYear == oYear) { |
| 296 | + return 0; |
| 297 | + } else { |
| 298 | + return thisYear < oYear ? -1 : 1; |
| 299 | + } |
| 300 | + } |
| 301 | + else { |
| 302 | + // One or both has an INVALID search format that is not just due to a paleo year |
| 303 | + throw new DateTimeException("One or both dates being compared have an INVALID format."); |
| 304 | + } |
| 305 | + } |
269 | 306 | } |
270 | 307 |
|
271 | 308 | static boolean indexable(Long year) { |
@@ -337,28 +374,65 @@ static TimeRangeDescriptor rangeDescriptor(DateInfo beginInfo, DateInfo endInfo, |
337 | 374 | ValidDescriptor end = endInfo.descriptor; |
338 | 375 | ValidDescriptor instant = instantInfo.descriptor; |
339 | 376 |
|
340 | | - if (begin == ValidDescriptor.VALID && |
341 | | - end == ValidDescriptor.VALID && |
| 377 | + // A time range cannot be described as an error exists with one or more dates: |
| 378 | + if(begin == ValidDescriptor.INVALID || |
| 379 | + end == ValidDescriptor.INVALID || |
| 380 | + instant == ValidDescriptor.INVALID) { |
| 381 | + return NOT_APPLICABLE; |
| 382 | + } |
| 383 | + |
| 384 | + // Dates are all undefined so range is undefined: |
| 385 | + if (begin == ValidDescriptor.UNDEFINED && |
| 386 | + end == ValidDescriptor.UNDEFINED && |
342 | 387 | instant == ValidDescriptor.UNDEFINED) { |
343 | | - Boolean inOrder = beginLTEEnd(beginInfo, endInfo); |
344 | | - return inOrder == null ? INVALID : inOrder ? BOUNDED : BACKWARDS; |
| 388 | + return UNDEFINED; |
345 | 389 | } |
| 390 | + |
| 391 | + // If begin is valid but end is undefined, this indicates an ongoing range: |
346 | 392 | if (begin == ValidDescriptor.VALID && |
347 | 393 | end == ValidDescriptor.UNDEFINED && |
348 | 394 | instant == ValidDescriptor.UNDEFINED) { |
349 | 395 | return ONGOING; |
350 | 396 | } |
| 397 | + |
| 398 | + // Valid instant is straightforward: |
351 | 399 | if (begin == ValidDescriptor.UNDEFINED && |
352 | 400 | end == ValidDescriptor.UNDEFINED && |
353 | 401 | instant == ValidDescriptor.VALID) { |
354 | 402 | return INSTANT; |
355 | 403 | } |
356 | | - if (begin == ValidDescriptor.UNDEFINED && |
357 | | - end == ValidDescriptor.UNDEFINED && |
| 404 | + |
| 405 | + // Dates describe more than one valid range descriptor, which is ambiguous: |
| 406 | + if ( ( begin == ValidDescriptor.VALID && end == ValidDescriptor.VALID && instant == ValidDescriptor.VALID ) || |
| 407 | + ( begin == ValidDescriptor.VALID && end == ValidDescriptor.UNDEFINED && instant == ValidDescriptor.VALID ) ) { |
| 408 | + return AMBIGUOUS; |
| 409 | + } |
| 410 | + |
| 411 | + // Begin and end dates are independently valid but based on how they compare to each other can describe very |
| 412 | + // different range types: |
| 413 | + if (begin == ValidDescriptor.VALID && |
| 414 | + end == ValidDescriptor.VALID && |
358 | 415 | instant == ValidDescriptor.UNDEFINED) { |
359 | | - return UNDEFINED; |
| 416 | + try { |
| 417 | + int comparator = beginInfo.compareTo(endInfo); |
| 418 | + TimeRangeDescriptor descriptor; |
| 419 | + switch (comparator) { |
| 420 | + case -1: descriptor = BOUNDED; |
| 421 | + break; |
| 422 | + case 0: descriptor = INSTANT; |
| 423 | + break; |
| 424 | + case 1: descriptor = BACKWARDS; |
| 425 | + break; |
| 426 | + default: descriptor = INVALID; |
| 427 | + break; |
| 428 | + } |
| 429 | + return descriptor; |
| 430 | + } catch(DateTimeException e) { |
| 431 | + return INVALID; |
| 432 | + } |
360 | 433 | } |
361 | 434 |
|
| 435 | + // Covers undefined begin date with valid end date which is meaningless, regardless of presence of an instant date |
362 | 436 | return INVALID; |
363 | 437 | } |
364 | 438 |
|
|
0 commit comments