1 package org.collectionspace.services.structureddate.antlr;
3 import java.util.regex.Matcher;
4 import java.util.regex.Pattern;
5 import java.util.Stack;
7 import org.antlr.v4.runtime.ANTLRInputStream;
8 import org.antlr.v4.runtime.BailErrorStrategy;
9 import org.antlr.v4.runtime.CommonTokenStream;
10 import org.antlr.v4.runtime.FailedPredicateException;
11 import org.antlr.v4.runtime.InputMismatchException;
12 import org.antlr.v4.runtime.NoViableAltException;
13 import org.antlr.v4.runtime.Parser;
14 import org.antlr.v4.runtime.RecognitionException;
15 import org.antlr.v4.runtime.Token;
16 import org.antlr.v4.runtime.TokenStream;
17 import org.antlr.v4.runtime.misc.ParseCancellationException;
18 import org.antlr.v4.runtime.tree.TerminalNode;
19 import org.collectionspace.services.structureddate.Date;
20 import org.collectionspace.services.structureddate.DateUtils;
21 import org.collectionspace.services.structureddate.DeferredCenturyEndDate;
22 import org.collectionspace.services.structureddate.DeferredCenturyStartDate;
23 import org.collectionspace.services.structureddate.DeferredDate;
24 import org.collectionspace.services.structureddate.DeferredDecadeEndDate;
25 import org.collectionspace.services.structureddate.DeferredDecadeStartDate;
26 import org.collectionspace.services.structureddate.DeferredHalfCenturyEndDate;
27 import org.collectionspace.services.structureddate.DeferredHalfCenturyStartDate;
28 import org.collectionspace.services.structureddate.DeferredMillenniumEndDate;
29 import org.collectionspace.services.structureddate.DeferredMillenniumStartDate;
30 import org.collectionspace.services.structureddate.DeferredPartialCenturyEndDate;
31 import org.collectionspace.services.structureddate.DeferredPartialCenturyStartDate;
32 import org.collectionspace.services.structureddate.DeferredPartialDecadeEndDate;
33 import org.collectionspace.services.structureddate.DeferredPartialDecadeStartDate;
34 import org.collectionspace.services.structureddate.DeferredQuarterCenturyEndDate;
35 import org.collectionspace.services.structureddate.DeferredQuarterCenturyStartDate;
36 import org.collectionspace.services.structureddate.Era;
37 import org.collectionspace.services.structureddate.Part;
38 import org.collectionspace.services.structureddate.StructuredDateInternal;
39 import org.collectionspace.services.structureddate.StructuredDateEvaluator;
40 import org.collectionspace.services.structureddate.StructuredDateFormatException;
41 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.AllOrPartOfContext;
42 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.BeforeOrAfterDateContext;
43 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.CenturyContext;
44 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.CertainDateContext;
45 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.DateContext;
46 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.DayFirstDateContext;
47 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.DayOrYearFirstDateContext;
48 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.DecadeContext;
49 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.DisplayDateContext;
50 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.EraContext;
51 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.HalfCenturyContext;
52 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.HalfYearContext;
53 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.HyphenatedRangeContext;
54 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.InvMonthYearContext;
55 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.InvSeasonYearContext;
56 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.InvStrDateContext;
57 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.InvStrDateEraLastDateContext;
58 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.MillenniumContext;
59 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.MonthContext;
60 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.MonthInYearRangeContext;
61 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.MonthYearContext;
62 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthCenturyRangeContext;
63 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthContext;
64 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthHalfContext;
65 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthQuarterContext;
66 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthQuarterInYearRangeContext;
67 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthQuarterYearContext;
68 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumCenturyContext;
69 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumContext;
70 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDateContext;
71 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDayInMonthRangeContext;
72 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDayOfMonthContext;
73 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDecadeContext;
74 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumMonthContext;
75 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumYearContext;
76 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartOfContext;
77 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartialCenturyContext;
78 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartialDecadeContext;
79 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartialEraRangeContext;
80 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartialYearContext;
81 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.RomanDateContext;
82 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterCenturyContext;
83 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterInYearRangeContext;
84 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterYearContext;
85 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.RomanMonthContext;
86 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.SeasonYearContext;
87 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrCenturyContext;
88 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrDateContext;
89 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrDayInMonthRangeContext;
90 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrMonthContext;
91 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrSeasonContext;
92 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrSeasonInYearRangeContext;
93 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.UncalibratedDateContext;
94 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.UncertainDateContext;
95 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.UnknownDateContext;
96 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.YearContext;
97 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.YearSpanningWinterContext;
100 * A StructuredDateEvaluator that uses an ANTLR parser to parse the display date,
101 * and an ANTLR listener to generate a structured date from the resulting parse
104 public class ANTLRStructuredDateEvaluator extends StructuredDateBaseListener implements StructuredDateEvaluator {
106 * The result of the evaluation.
108 protected StructuredDateInternal result;
109 private final int BP_ZERO_YEAR = 1950;
112 * The operation stack. The parse listener methods that are implemented here
113 * pop input parameters off the stack, and push results back on to the stack.
115 protected Stack<Object> stack;
117 public ANTLRStructuredDateEvaluator() {
122 * Normalizes a display date for evaluation.
123 * - Remove leading and trailing whitespace
124 * - Remove leading and trailing braces
125 * - Convert to lowercase
128 * @return The normalized display date
130 protected String normalizeDisplayDate(String displayDate) {
131 String normalDisplayDate =
133 .replaceAll("^[\\[\\(\\{\\s]+|[\\]\\)\\}\\s]+$", "")
136 return normalDisplayDate;
140 public StructuredDateInternal evaluate(String displayDate) throws StructuredDateFormatException {
141 stack = new Stack<Object>();
143 result = new StructuredDateInternal();
144 result.setDisplayDate(displayDate);
146 // Instantiate a parser from the normalized display date.
147 ANTLRInputStream inputStream = new ANTLRInputStream(normalizeDisplayDate(displayDate));
148 StructuredDateLexer lexer = new StructuredDateLexer(inputStream);
149 CommonTokenStream tokenStream = new CommonTokenStream(lexer);
150 StructuredDateParser parser = new StructuredDateParser(tokenStream);
152 // Don't try to recover from parse errors, just bail.
153 parser.setErrorHandler(new BailErrorStrategy());
155 // Don't print error messages to the console.
156 parser.removeErrorListeners();
158 // Generate our own custom error messages.
159 parser.addParseListener(this);
162 // Attempt to fulfill the oneDisplayDate rule of the grammar.
163 parser.oneDisplayDate();
165 catch(ParseCancellationException e) {
166 // ParseCancellationException is thrown by the BailErrorStrategy when there is a
167 // parse error, with the underlying RecognitionException as the cause.
168 RecognitionException re = (RecognitionException) e.getCause();
170 throw new StructuredDateFormatException(getErrorMessage(re), re);
173 result.computeScalarValues();
175 // The parsing was successful. Return the result.
180 public void exitDisplayDate(DisplayDateContext ctx) {
181 if (ctx.exception != null) return;
183 Date latestDate = (Date) stack.pop();
184 Date earliestDate = (Date) stack.pop();
186 if (earliestDate.getYear() != null || earliestDate.getYear() != null) {
187 int compareResult = DateUtils.compareDates(earliestDate, latestDate);
188 if (compareResult == 1) {
191 earliestDate = latestDate;
194 // Check to see if the dates were reversed AND calculated. If they were
195 // Then this probably means the absolute earliestDate should have month and day as "1"
196 // and the latestDate momth 12, day 31.
197 if ((earliestDate.getMonth() == 12 && earliestDate.getDay() == 31) &&
198 (latestDate.getMonth() == 1 && latestDate.getDay() == 1)) {
199 earliestDate.setMonth(1);
200 earliestDate.setDay(1);
201 latestDate.setMonth(12);
202 latestDate.setDay(31);
207 // If the earliest date and the latest date are the same, it's just a "single" date.
208 // There's no need to have the latest, so set it to null.
210 if (earliestDate.equals(latestDate)) {
214 result.setEarliestSingleDate(earliestDate);
215 result.setLatestDate(latestDate);
219 public void exitBeforeOrAfterDate(BeforeOrAfterDateContext ctx) {
220 if (ctx.exception != null) return;
222 Date latestDate = (Date) stack.pop();
223 Date earliestDate = (Date) stack.pop();
225 // Set null eras to the default.
227 if (earliestDate.getEra() == null) {
228 earliestDate.setEra(Date.DEFAULT_ERA);
231 if (latestDate.getEra() == null) {
232 latestDate.setEra(Date.DEFAULT_ERA);
235 // Finalize any deferred calculations.
237 if (latestDate instanceof DeferredDate) {
238 ((DeferredDate) latestDate).resolveDate();
241 if (earliestDate instanceof DeferredDate) {
242 ((DeferredDate) earliestDate).resolveDate();
245 // Calculate the earliest date or end date.
247 if (ctx.BEFORE() != null) {
248 latestDate = earliestDate;
249 earliestDate = DateUtils.getEarliestBeforeDate(earliestDate, latestDate);
251 else if (ctx.AFTER() != null) {
252 earliestDate = latestDate;
253 latestDate = DateUtils.getLatestAfterDate(earliestDate, latestDate);
256 stack.push(earliestDate);
257 stack.push(latestDate);
261 public void exitUncertainDate(UncertainDateContext ctx) {
262 if (ctx.exception != null) return;
264 Date latestDate = (Date) stack.pop();
265 Date earliestDate = (Date) stack.pop();
268 int earliestInterval = DateUtils.getCircaIntervalYears(earliestDate.getYear(), earliestDate.getEra());
269 int latestInterval = DateUtils.getCircaIntervalYears(latestDate.getYear(), latestDate.getEra());
271 // Express the circa interval as a qualifier.
273 // stack.push(earliestDate.withQualifier(QualifierType.MINUS, earliestInterval, QualifierUnit.YEARS));
274 // stack.push(latestDate.withQualifier(QualifierType.PLUS, latestInterval, QualifierUnit.YEARS));
278 // Express the circa interval as an offset calculated into the year.
280 DateUtils.subtractYears(earliestDate, earliestInterval);
281 DateUtils.addYears(latestDate, latestInterval);
283 stack.push(earliestDate);
284 stack.push(latestDate);
288 public void exitCertainDate(CertainDateContext ctx) {
289 if (ctx.exception != null) return;
291 Date latestDate = (Date) stack.pop();
292 Date earliestDate = (Date) stack.pop();
294 // Set null eras to the default.
296 if (earliestDate.getEra() == null) {
297 earliestDate.setEra(Date.DEFAULT_ERA);
300 if (latestDate.getEra() == null) {
301 latestDate.setEra(Date.DEFAULT_ERA);
304 // Finalize any deferred calculations.
306 if (latestDate instanceof DeferredDate) {
307 ((DeferredDate) latestDate).resolveDate();
310 if (earliestDate instanceof DeferredDate) {
311 ((DeferredDate) earliestDate).resolveDate();
314 stack.push(earliestDate);
315 stack.push(latestDate);
319 public void exitHyphenatedRange(HyphenatedRangeContext ctx) {
320 if (ctx.exception != null) return;
322 Date latestEndDate = (Date) stack.pop();
323 stack.pop(); // latestStartDate
324 stack.pop(); // earliestEndDate
325 Date earliestStartDate = (Date) stack.pop();
327 // If no era was explicitly specified for the first date,
328 // make it inherit the era of the second date.
330 if (earliestStartDate.getEra() == null && latestEndDate.getEra() != null) {
331 earliestStartDate.setEra(latestEndDate.getEra());
334 // Finalize any deferred calculations.
336 if (earliestStartDate instanceof DeferredDate) {
337 ((DeferredDate) earliestStartDate).resolveDate();
340 if (latestEndDate instanceof DeferredDate) {
341 ((DeferredDate) latestEndDate).resolveDate();
344 stack.push(earliestStartDate);
345 stack.push(latestEndDate);
349 public void exitNthCenturyRange(NthCenturyRangeContext ctx) {
350 if (ctx.exception != null) return;
352 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
353 Integer endN = (Integer) stack.pop();
354 Part endPart = (Part) stack.pop();
355 Integer startN = (Integer) stack.pop();
356 Part startPart = (Part) stack.pop();
359 era = Date.DEFAULT_ERA;
362 int startYear = DateUtils.nthCenturyToYear(startN);
363 int endYear = DateUtils.nthCenturyToYear(endN);
365 stack.push(startPart == null ? DateUtils.getCenturyStartDate(startYear, era) : DateUtils.getPartialCenturyStartDate(startYear, startPart, era));
366 stack.push(startPart == null ? DateUtils.getCenturyEndDate(startYear, era) : DateUtils.getPartialCenturyEndDate(startYear, startPart, era));
367 stack.push(endPart == null ? DateUtils.getCenturyStartDate(endYear, era) : DateUtils.getPartialCenturyStartDate(endYear, endPart, era));
368 stack.push(endPart == null ? DateUtils.getCenturyEndDate(endYear, era) : DateUtils.getPartialCenturyEndDate(endYear, endPart, era));
372 public void exitMonthInYearRange(MonthInYearRangeContext ctx) {
373 if (ctx.exception != null) return;
375 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
376 Integer year = (Integer) stack.pop();
377 Integer numMonthEnd = (Integer) stack.pop();
378 Integer numMonthStart = (Integer) stack.pop();
380 stack.push(new Date(year, numMonthStart, 1, era));
381 stack.push(new Date(year, numMonthStart, DateUtils.getDaysInMonth(numMonthStart, year, era), era));
382 stack.push(new Date(year, numMonthEnd, 1, era));
383 stack.push(new Date(year, numMonthEnd, DateUtils.getDaysInMonth(numMonthEnd, year, era), era));
387 public void exitQuarterInYearRange(QuarterInYearRangeContext ctx) {
388 if (ctx.exception != null) return;
390 Era era = (Era) stack.pop();
391 Integer year = (Integer) stack.pop();
392 Integer lastQuarter = (Integer) stack.pop();
393 Integer firstQuarter = (Integer) stack.pop();
395 stack.push(DateUtils.getQuarterYearStartDate(firstQuarter, year).withEra(era));
396 stack.push(DateUtils.getQuarterYearEndDate(firstQuarter, year, era).withEra(era));
397 stack.push(DateUtils.getQuarterYearStartDate(lastQuarter, year).withEra(era));
398 stack.push(DateUtils.getQuarterYearEndDate(lastQuarter, year, era).withEra(era));
402 public void exitStrDayInMonthRange(StrDayInMonthRangeContext ctx) {
403 if (ctx.exception != null) return;
405 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
406 Integer year = (Integer) stack.pop();
407 Integer dayOfMonthEnd = (Integer) stack.pop();
408 Integer dayOfMonthStart = (Integer) stack.pop();
409 Integer numMonth = (Integer) stack.pop();
411 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
412 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
413 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
414 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
418 public void exitNumDayInMonthRange(NumDayInMonthRangeContext ctx) {
419 if (ctx.exception != null) return;
421 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
422 Integer num1 = (Integer) stack.pop();
423 Integer num2 = (Integer) stack.pop();
424 Integer num3 = (Integer) stack.pop();
425 Integer num4 = (Integer) stack.pop();
427 /* We can distinguish whether it is M/D-D/Y (Case 1) or M/Y-M/Y (Case 2) by checking if
428 The num1, num4, num2 and num1, num4, num3 are valid dates, since this would equate to
429 a set of two ranges. For examples: 04/13-19/1995 would be 04/13/1995-04/19/1995. If both these
430 dates are valid, we know that it shouldn't be interpreted as 04/01/13 - 19/31/1995 since these arent valid dates!
433 Integer lateYear = num1;
434 Integer earlyMonth = num4;
435 Integer dayOfMonthEnd = num2;
436 Integer dayOfMonthStart = num3;
438 if (DateUtils.isValidDate(num1, num4, num2, era) && DateUtils.isValidDate(num1, num4, num3, era)) {
439 // No need to alter the arguments, so just push to the stack
440 stack.push(new Date(lateYear, earlyMonth, dayOfMonthStart, era));
441 stack.push(new Date(lateYear, earlyMonth, dayOfMonthStart, era));
442 stack.push(new Date(lateYear, earlyMonth, dayOfMonthEnd, era));
443 stack.push(new Date(lateYear, earlyMonth, dayOfMonthEnd, era));
446 // Separated these by case, since it makes the code more legible
447 Integer latestMonth = num2;
448 Integer earliestYear = num3;
450 stack.push(new Date(earliestYear, earlyMonth, 1, era)); // Earliest Early Date
451 stack.push(new Date(earliestYear, earlyMonth, DateUtils.getDaysInMonth(earlyMonth, earliestYear, era), era)); // Latest Early Date
452 stack.push(new Date(lateYear, latestMonth, 1, era)); // Earliest Latest Date
453 stack.push(new Date(lateYear, latestMonth, DateUtils.getDaysInMonth(latestMonth, lateYear, era), era)); // Latest Late Date
459 public void exitDate(DateContext ctx) {
460 if (ctx.exception != null) return;
462 // Expect the canonical year-month-day-era ordering
463 // to be on the stack.
465 Era era = (stack.size() == 3) ? null : (Era) stack.pop();
466 Integer dayOfMonth = (Integer) stack.pop();
467 Integer numMonth = (Integer) stack.pop();
468 Integer year = (Integer) stack.pop();
470 // For the latest date we could either return null, or a copy of the earliest date,
471 // since the UI doesn't care. Use a copy of the earliest date, since it makes
472 // things easier here if we don't have to test for null up the tree.
474 stack.push(new Date(year, numMonth, dayOfMonth, era));
475 stack.push(new Date(year, numMonth, dayOfMonth, era));
479 public void exitNumDate(NumDateContext ctx) {
480 if (ctx.exception != null) return;
482 // This could either be year-month-day, or
483 // month-day-year. Try to determine which,
484 // and reorder the stack into the canonical
485 // year-month-day-era ordering.
487 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
488 Integer num3 = (Integer) stack.pop();
489 Integer num2 = (Integer) stack.pop();
490 Integer num1 = (Integer) stack.pop();
492 // Default to a month-day-year interpretation.
495 int dayOfMonth = num2;
498 if (DateUtils.isValidDate(num3, num1, num2, era)) {
499 // Interpreting as month-day-year produces a valid date. Go with it.
501 else if (DateUtils.isValidDate(num1, num2, num3, era)) {
502 // Interpreting as month-day-year doesn't produce a valid date, but
503 // year-month-day does. Go with year-month-day.
511 stack.push(numMonth);
512 stack.push(dayOfMonth);
517 public void exitStrDate(StrDateContext ctx) {
518 if (ctx.exception != null) return;
520 // Reorder the stack into a canonical ordering,
521 // year-month-day-era.
523 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
524 Integer year = (Integer) stack.pop();
525 Integer dayOfMonth = (Integer) stack.pop();
526 Integer numMonth = (Integer) stack.pop();
529 stack.push(numMonth);
530 stack.push(dayOfMonth);
535 public void exitInvStrDate(InvStrDateContext ctx) {
536 if (ctx.exception != null) return;
538 // Reorder the stack into a canonical ordering,
539 // year-month-day-era.
541 Integer dayOfMonth = (Integer) stack.pop();
542 Integer numMonth = (Integer) stack.pop();
543 Integer year = (Integer) stack.pop();
544 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
547 stack.push(numMonth);
548 stack.push(dayOfMonth);
553 public void exitDayFirstDate(DayFirstDateContext ctx) {
554 if (ctx.exception != null) return ;
556 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
557 Integer year = (Integer) stack.pop();
558 Integer month = (Integer) stack.pop();
559 Integer dayOfMonth = (Integer) stack.pop();
563 stack.push(dayOfMonth);
568 public void exitDayOrYearFirstDate(DayOrYearFirstDateContext ctx) {
569 if (ctx.exception != null) return;
572 Integer num2 = (Integer) stack.pop();
573 Integer numMonth = (Integer) stack.pop();
574 Integer num1 = (Integer) stack.pop();
577 Integer dayOfMonth = num2;
579 if (DateUtils.isValidDate(num1, numMonth, num2, era)) {
580 // The first number is a year. Already correct
581 } else if (DateUtils.isValidDate(num2, numMonth, num1, era)) {
582 // The second number is a year.
588 stack.push(numMonth);
589 stack.push(dayOfMonth);
591 if (dayOfMonth > 31 || dayOfMonth <= 0) {
592 throw new StructuredDateFormatException("unexpected day of month '" + Integer.toString(dayOfMonth) + "'");
595 throw new StructuredDateFormatException("unexpected year '" + Integer.toString(year) + "'");
600 public void exitInvStrDateEraLastDate(InvStrDateEraLastDateContext ctx) {
601 if (ctx.exception != null) return;
603 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
604 Integer dayOfMonth = (Integer) stack.pop();
605 Integer month = (Integer) stack.pop();
606 Integer year = (Integer) stack.pop();
610 stack.push(dayOfMonth);
615 public void exitMonth(MonthContext ctx) {
616 if (ctx.exception != null) return;
618 Era era = (Era) stack.pop();
619 Integer year = (Integer) stack.pop();
620 Integer numMonth = (Integer) stack.pop();
622 stack.push(new Date(year, numMonth, 1, era));
623 stack.push(new Date(year, numMonth, DateUtils.getDaysInMonth(numMonth, year, era), era));
627 public void exitMonthYear(MonthYearContext ctx) {
628 if (ctx.exception != null) return;
630 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
636 public void exitInvMonthYear(InvMonthYearContext ctx) {
637 if (ctx.exception != null) return;
639 // Invert the arguments.
641 Integer numMonth = (Integer) stack.pop();
642 Integer year = (Integer) stack.pop();
643 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
645 stack.push(numMonth);
651 public void exitYearSpanningWinter(YearSpanningWinterContext ctx) {
652 if (ctx.exception != null) return;
654 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
655 Integer endYear = (Integer) stack.pop();
656 Integer startYear = (Integer) stack.pop();
658 stack.push(new Date(startYear, 12, 1).withEra(era));
659 stack.push(DateUtils.getQuarterYearEndDate(1, endYear, era).withEra(era));
663 public void exitPartialYear(PartialYearContext ctx) {
664 if (ctx.exception != null) return;
666 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
667 Integer year = (Integer) stack.pop();
668 Part part = (Part) stack.pop();
670 stack.push(DateUtils.getPartialYearStartDate(part, year).withEra(era));
671 stack.push(DateUtils.getPartialYearEndDate(part, year, era).withEra(era));
675 public void exitQuarterYear(QuarterYearContext ctx) {
676 if (ctx.exception != null) return;
678 Era era = (Era) stack.pop();
679 Integer year = (Integer) stack.pop();
680 Integer quarter = (Integer) stack.pop();
682 stack.push(DateUtils.getQuarterYearStartDate(quarter, year).withEra(era));
683 stack.push(DateUtils.getQuarterYearEndDate(quarter, year, era).withEra(era));
687 public void exitHalfYear(HalfYearContext ctx) {
688 if (ctx.exception != null) return;
690 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
691 Integer year = (Integer) stack.pop();
692 Integer half = (Integer) stack.pop();
694 stack.push(DateUtils.getHalfYearStartDate(half, year).withEra(era));
695 stack.push(DateUtils.getHalfYearEndDate(half, year, era).withEra(era));
699 public void exitInvSeasonYear(InvSeasonYearContext ctx) {
700 if (ctx.exception != null) return;
702 // Invert the arguments.
704 Integer quarter = (Integer) stack.pop();
705 Integer year = (Integer) stack.pop();
706 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
714 public void exitSeasonYear(SeasonYearContext ctx) {
715 if (ctx.exception != null) return;
717 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
723 public void exitYear(YearContext ctx) {
724 if (ctx.exception != null) return;
726 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
727 Integer year = (Integer) stack.pop();
729 stack.push(new Date(year, 1, 1, era));
730 stack.push(new Date(year, 12, 31, era));
734 public void exitPartialDecade(PartialDecadeContext ctx) {
735 if (ctx.exception != null) return;
737 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
738 Integer year = (Integer) stack.pop();
739 Part part = (Part) stack.pop();
742 // If the era was explicitly specified, the start and end years
743 // may be calculated now.
745 stack.push(DateUtils.getPartialDecadeStartDate(year, part, era));
746 stack.push(DateUtils.getPartialDecadeEndDate(year, part, era));
749 // If the era was not explicitly specified, the start and end years
750 // can't be calculated yet. The calculation must be deferred until
751 // later. For example, this partial decade may be the start of a hyphenated
752 // range, where the era will be inherited from the era of the end of
753 // the range; this era won't be known until farther up the parse tree,
754 // when both sides of the range will have been parsed.
756 stack.push(new DeferredPartialDecadeStartDate(year, part));
757 stack.push(new DeferredPartialDecadeEndDate(year, part));
762 public void exitDecade(DecadeContext ctx) {
763 if (ctx.exception != null) return;
765 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
766 Integer year = (Integer) stack.pop();
768 // Calculate the start and end year of the decade, which depends on the era.
771 // If the era was explicitly specified, the start and end years
772 // may be calculated now.
774 stack.push(DateUtils.getDecadeStartDate(year, era));
775 stack.push(DateUtils.getDecadeEndDate(year, era));
778 // If the era was not explicitly specified, the start and end years
779 // can't be calculated yet. The calculation must be deferred until
780 // later. For example, this decade may be the start of a hyphenated
781 // range, where the era will be inherited from the era of the end of
782 // the range; this era won't be known until farther up the parse tree,
783 // when both sides of the range will have been parsed.
785 stack.push(new DeferredDecadeStartDate(year));
786 stack.push(new DeferredDecadeEndDate(year));
791 public void exitPartialCentury(PartialCenturyContext ctx) {
792 if (ctx.exception != null) return;
794 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
795 Integer year = (Integer) stack.pop();
796 Part part = (Part) stack.pop();
799 // If the era was explicitly specified, the start and end years
800 // may be calculated now.
802 stack.push(DateUtils.getPartialCenturyStartDate(year, part, era));
803 stack.push(DateUtils.getPartialCenturyEndDate(year, part, era));
806 // If the era was not explicitly specified, the start and end years
807 // can't be calculated yet. The calculation must be deferred until
808 // later. For example, this partial century may be the start of a hyphenated
809 // range, where the era will be inherited from the era of the end of
810 // the range; this era won't be known until farther up the parse tree,
811 // when both sides of the range will have been parsed.
813 stack.push(new DeferredPartialCenturyStartDate(year, part));
814 stack.push(new DeferredPartialCenturyEndDate(year, part));
819 public void exitQuarterCentury(QuarterCenturyContext ctx) {
820 if (ctx.exception != null) return;
822 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
823 Integer year = (Integer) stack.pop();
824 Integer quarter = (Integer) stack.pop();
827 // If the era was explicitly specified, the start and end years
828 // may be calculated now.
830 stack.push(DateUtils.getQuarterCenturyStartDate(year, quarter, era));
831 stack.push(DateUtils.getQuarterCenturyEndDate(year, quarter, era));
834 // If the era was not explicitly specified, the start and end years
835 // can't be calculated yet. The calculation must be deferred until
836 // later. For example, this century may be the start of a hyphenated
837 // range, where the era will be inherited from the era of the end of
838 // the range; this era won't be known until farther up the parse tree,
839 // when both sides of the range will have been parsed.
841 stack.push(new DeferredQuarterCenturyStartDate(year, quarter));
842 stack.push(new DeferredQuarterCenturyEndDate(year, quarter));
847 public void exitHalfCentury(HalfCenturyContext ctx) {
848 if (ctx.exception != null) return;
850 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
851 Integer year = (Integer) stack.pop();
852 Integer half = (Integer) stack.pop();
855 // If the era was explicitly specified, the start and end years
856 // may be calculated now.
858 stack.push(DateUtils.getHalfCenturyStartDate(year, half, era));
859 stack.push(DateUtils.getHalfCenturyEndDate(year, half, era));
862 // If the era was not explicitly specified, the start and end years
863 // can't be calculated yet. The calculation must be deferred until
864 // later. For example, this half century may be the start of a hyphenated
865 // range, where the era will be inherited from the era of the end of
866 // the range; this era won't be known until farther up the parse tree,
867 // when both sides of the range will have been parsed.
869 stack.push(new DeferredHalfCenturyStartDate(year, half));
870 stack.push(new DeferredHalfCenturyEndDate(year, half));
875 public void exitCentury(CenturyContext ctx) {
876 if (ctx.exception != null) return;
878 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
879 Integer year = (Integer) stack.pop();
882 // If the era was explicitly specified, the start and end years
883 // may be calculated now.
885 stack.push(DateUtils.getCenturyStartDate(year, era));
886 stack.push(DateUtils.getCenturyEndDate(year, era));
889 // If the era was not explicitly specified, the start and end years
890 // can't be calculated yet. The calculation must be deferred until
891 // later. For example, this quarter century may be the start of a hyphenated
892 // range, where the era will be inherited from the era of the end of
893 // the range; this era won't be known until farther up the parse tree,
894 // when both sides of the range will have been parsed.
896 stack.push(new DeferredCenturyStartDate(year));
897 stack.push(new DeferredCenturyEndDate(year));
902 public void exitMillennium(MillenniumContext ctx) {
903 if (ctx.exception != null) return;
905 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
906 Integer n = (Integer) stack.pop();
909 // If the era was explicitly specified, the start and end years
910 // may be calculated now.
912 stack.push(DateUtils.getMillenniumStartDate(n, era));
913 stack.push(DateUtils.getMillenniumEndDate(n, era));
916 // If the era was not explicitly specified, the start and end years
917 // can't be calculated yet. The calculation must be deferred until
918 // later. For example, this millennium may be the start of a hyphenated
919 // range, where the era will be inherited from the era of the end of
920 // the range; this era won't be known until farther up the parse tree,
921 // when both sides of the range will have been parsed.
923 stack.push(new DeferredMillenniumStartDate(n));
924 stack.push(new DeferredMillenniumEndDate(n));
929 public void exitStrCentury(StrCenturyContext ctx) {
930 if (ctx.exception != null) return;
932 Integer n = (Integer) stack.pop();
934 // Convert the nth number to a year number,
935 // and push on the stack.
937 Integer year = DateUtils.nthCenturyToYear(n);
943 public void exitNumCentury(NumCenturyContext ctx) {
944 if (ctx.exception != null) return;
946 // Convert the string to a number,
947 // and push on the stack.
949 Integer year = new Integer(stripEndLetters(ctx.HUNDREDS().getText()));
952 throw new StructuredDateFormatException("unexpected century '" + ctx.HUNDREDS().getText() + "'");
959 public void exitNumDecade(NumDecadeContext ctx) {
960 if (ctx.exception != null) return;
962 // Convert the string to a number,
963 // and push on the stack.
965 Integer year = new Integer(stripEndLetters(ctx.TENS().getText()));
968 throw new StructuredDateFormatException("unexpected decade '" + ctx.TENS().getText() + "'");
975 public void exitNumYear(NumYearContext ctx) {
976 if (ctx.exception != null) return;
978 // Convert the string to a number,
979 // and push on the stack.
981 Integer year = new Integer(ctx.getText().replaceAll(",", ""));
984 throw new StructuredDateFormatException("unexpected year '" + ctx.NUMBER().getText() + "'");
991 public void exitNumMonth(NumMonthContext ctx) {
992 if (ctx.exception != null) return;
994 // Convert the string a number,
995 // and push on the stack.
997 Integer month = new Integer(ctx.NUMBER().getText());
999 if (month < 1 || month > 12) {
1000 throw new StructuredDateFormatException("unexpected month '" + ctx.NUMBER().getText() + "'");
1007 public void exitNthHalf(NthHalfContext ctx) {
1008 if (ctx.exception != null) return;
1010 // Convert LAST to a number (the last half
1011 // is the 2nd). If this rule matched the
1012 // alternative with nth instead of LAST,
1013 // the nth handler will already have pushed
1014 // a number on the stack.
1016 if (ctx.LAST() != null) {
1017 stack.push(new Integer(2));
1020 // Check for a valid half.
1022 Integer n = (Integer) stack.peek();
1024 if (n < 1 || n > 2) {
1025 throw new StructuredDateFormatException("unexpected half '" + n + "'");
1031 public void exitNthQuarterInYearRange(NthQuarterInYearRangeContext ctx) {
1032 if (ctx.exception != null) return;
1034 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1040 public void exitStrSeasonInYearRange(StrSeasonInYearRangeContext ctx) {
1041 if (ctx.exception != null) return;
1043 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1050 public void exitNthQuarterYear(NthQuarterYearContext ctx) {
1052 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1058 public void exitNthQuarter(NthQuarterContext ctx) {
1059 if (ctx.exception != null) return;
1061 // Convert LAST to a number (the last quarter
1062 // is the 4th). If this rule matched the
1063 // alternative with nth instead of LAST,
1064 // the nth handler will already have pushed
1065 // a number on the stack.
1067 if (ctx.LAST() != null) {
1068 stack.push(new Integer(4));
1071 // Check for a valid quarter.
1073 Integer n = (Integer) stack.peek();
1075 if (n < 1 || n > 4) {
1076 throw new StructuredDateFormatException("unexpected quarter '" + n + "'");
1081 public void exitNth(NthContext ctx) {
1082 if (ctx.exception != null) return;
1084 // Convert the string to a number,
1085 // and push on the stack.
1089 if (ctx.NTHSTR() != null) {
1090 n = new Integer(stripEndLetters(ctx.NTHSTR().getText()));
1092 else if (ctx.FIRST() != null) {
1095 else if (ctx.SECOND() != null) {
1098 else if (ctx.THIRD() != null) {
1101 else if (ctx.FOURTH() != null) {
1109 public void exitStrMonth(StrMonthContext ctx) {
1110 if (ctx.exception != null) return;
1112 // Convert the month name to a number,
1113 // and push on the stack.
1115 TerminalNode monthNode = ctx.MONTH();
1117 if (monthNode == null) {
1118 monthNode = ctx.SHORTMONTH();
1121 String monthStr = monthNode.getText();
1123 stack.push(DateUtils.getMonthByName(monthStr));
1127 public void exitStrSeason(StrSeasonContext ctx) {
1128 if (ctx.exception != null) return;
1130 // Convert the season to a quarter number,
1131 // and push on the stack.
1133 Integer quarter = null;
1135 if (ctx.WINTER() != null) {
1138 else if (ctx.SPRING() != null) {
1141 else if (ctx.SUMMER() != null) {
1144 else if (ctx.FALL() != null) {
1148 stack.push(quarter);
1152 public void exitAllOrPartOf(AllOrPartOfContext ctx) {
1153 if (ctx.exception != null) return;
1155 // If a part was specified, it will have been
1156 // pushed on the stack in exitPartOf(). If not,
1157 // push null on the stack.
1159 if (ctx.partOf() == null) {
1165 public void exitPartOf(PartOfContext ctx) {
1166 if (ctx.exception != null) return;
1168 // Convert the token to a Part,
1169 // and push on the stack.
1173 if (ctx.EARLY() != null) {
1176 else if (ctx.MIDDLE() != null) {
1179 else if (ctx.LATE() != null) {
1187 public void exitEra(EraContext ctx) {
1188 if (ctx.exception != null) return;
1190 // Convert the token to an Era,
1191 // and push on the stack.
1195 if (ctx.BC() != null) {
1198 else if (ctx.AD() != null) {
1206 public void exitNumDayOfMonth(NumDayOfMonthContext ctx) {
1207 if (ctx.exception != null) return;
1209 // Convert the numeric string to an Integer,
1210 // and push on the stack.
1212 Integer dayOfMonth = new Integer(ctx.NUMBER().getText());
1214 if (dayOfMonth == 0 || dayOfMonth > 31) {
1215 throw new StructuredDateFormatException("unexpected day of month '" + ctx.NUMBER().getText() + "'");
1218 stack.push(dayOfMonth);
1222 public void exitPartialEraRange(PartialEraRangeContext ctx) {
1223 if (ctx.exception != null) return;
1225 Integer secondYear = (Integer) stack.pop();
1226 Integer secondMonth = (Integer) stack.pop();
1227 Integer secondDay = (Integer) stack.pop();
1229 Era era = (Era) stack.pop();
1230 Integer firstYear = (Integer) stack.pop();
1231 Integer firstMonth = (Integer) stack.pop();
1232 Integer firstDay = (Integer) stack.pop();
1234 stack.push(new Date(secondYear, secondMonth, secondDay, null));
1235 stack.push(new Date(firstYear, firstMonth, firstDay, era));
1239 public void exitNum(NumContext ctx) {
1240 if (ctx.exception != null) return;
1242 // Convert the numeric string to an Integer,
1243 // and push on the stack.
1245 Integer num = new Integer(ctx.getText().replaceAll(",", ""));
1251 public void exitRomanMonth(RomanMonthContext ctx) {
1252 int num = DateUtils.romanToDecimal(ctx.ROMANMONTH().getText());
1258 public void exitRomanDate(RomanDateContext ctx) {
1259 if (ctx.exception != null) return;
1261 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1262 Integer year = (Integer) stack.pop();
1263 Integer month = (Integer) stack.pop();
1264 Integer day = (Integer) stack.pop();
1273 public void exitUnknownDate(UnknownDateContext ctx) {
1274 if (ctx.exception != null) return;
1277 stack.push(new Date());
1278 stack.push(new Date());
1281 public void exitUncalibratedDate(UncalibratedDateContext ctx) {
1282 if (ctx.exception != null) return;
1284 Integer adjustmentDate = (Integer) stack.pop();
1285 Integer mainYear = (Integer) stack.pop();
1287 Integer earliestYear = (BP_ZERO_YEAR - mainYear) - adjustmentDate;
1288 Integer latestYear = (BP_ZERO_YEAR - mainYear) + adjustmentDate;
1290 // If negative, then BC, else AD
1291 Era earliestEra = earliestYear < 0 ? Era.BCE : Era.CE;
1292 Era latestEra = latestYear < 0 ? Era.BCE : Era.CE;
1294 stack.push(new Date(Math.abs(earliestYear), 1, 1, earliestEra)); // Earliest Early Date
1295 stack.push(new Date(Math.abs(latestYear), 12, DateUtils.getDaysInMonth(12, Math.abs(latestYear), latestEra), latestEra)); // Latest Late Date
1299 protected String getErrorMessage(RecognitionException re) {
1300 String message = "";
1302 Parser recognizer = (Parser) re.getRecognizer();
1303 TokenStream tokens = recognizer.getInputStream();
1305 if (re instanceof NoViableAltException) {
1306 NoViableAltException e = (NoViableAltException) re;
1307 Token startToken = e.getStartToken();
1308 String input = (startToken.getType() == Token.EOF ) ? "end of text" : quote(tokens.getText(startToken, e.getOffendingToken()));
1310 message = "no viable date format found at " + input;
1312 else if (re instanceof InputMismatchException) {
1313 InputMismatchException e = (InputMismatchException) re;
1314 message = "did not expect " + getTokenDisplayString(e.getOffendingToken()) + " while looking for " +
1315 e.getExpectedTokens().toString(recognizer.getTokenNames());
1317 else if (re instanceof FailedPredicateException) {
1318 FailedPredicateException e = (FailedPredicateException) re;
1319 String ruleName = recognizer.getRuleNames()[recognizer.getContext().getRuleIndex()];
1321 message = "failed predicate " + ruleName + ": " + e.getMessage();
1327 protected String quote(String text) {
1328 return "'" + text + "'";
1331 protected String getTokenDisplayString(Token token) {
1334 if (token == null) {
1335 string = "[no token]";
1338 String text = token.getText();
1341 if (token.getType() == Token.EOF ) {
1342 string = "end of text";
1345 string = "[" + token.getType() + "]";
1349 string = quote(text);
1356 protected String stripEndLetters(String input) {
1357 return input.replaceAll("[^\\d]+$", "");
1360 public static void main(String[] args) {
1361 StructuredDateEvaluator evaluator = new ANTLRStructuredDateEvaluator();
1363 for (String displayDate : args) {
1365 evaluator.evaluate(displayDate);
1366 } catch (StructuredDateFormatException e) {
1367 e.printStackTrace();