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 public StructuredDateInternal evaluate(String displayDate) throws StructuredDateFormatException {
123 stack = new Stack<Object>();
125 result = new StructuredDateInternal();
126 result.setDisplayDate(displayDate);
128 // Instantiate a parser from the lowercased display date, so that parsing will be case insensitive
129 ANTLRInputStream inputStream = new ANTLRInputStream(displayDate.toLowerCase());
130 StructuredDateLexer lexer = new StructuredDateLexer(inputStream);
131 CommonTokenStream tokenStream = new CommonTokenStream(lexer);
132 StructuredDateParser parser = new StructuredDateParser(tokenStream);
134 // Don't try to recover from parse errors, just bail.
135 parser.setErrorHandler(new BailErrorStrategy());
137 // Don't print error messages to the console.
138 parser.removeErrorListeners();
140 // Generate our own custom error messages.
141 parser.addParseListener(this);
144 // Attempt to fulfill the oneDisplayDate rule of the grammar.
145 parser.oneDisplayDate();
147 catch(ParseCancellationException e) {
148 // ParseCancellationException is thrown by the BailErrorStrategy when there is a
149 // parse error, with the underlying RecognitionException as the cause.
150 RecognitionException re = (RecognitionException) e.getCause();
152 throw new StructuredDateFormatException(getErrorMessage(re), re);
155 // The parsing was successful. Return the result.
160 public void exitDisplayDate(DisplayDateContext ctx) {
161 if (ctx.exception != null) return;
163 Date latestDate = (Date) stack.pop();
164 Date earliestDate = (Date) stack.pop();
166 if (earliestDate.getYear() != null || earliestDate.getYear() != null) {
167 int compareResult = DateUtils.compareDates(earliestDate, latestDate);
168 if (compareResult == 1) {
171 earliestDate = latestDate;
174 // Check to see if the dates were reversed AND calculated. If they were
175 // Then this probably means the absolute earliestDate should have month and day as "1"
176 // and the latestDate momth 12, day 31.
177 if ((earliestDate.getMonth() == 12 && earliestDate.getDay() == 31) &&
178 (latestDate.getMonth() == 1 && latestDate.getDay() == 1)) {
179 earliestDate.setMonth(1);
180 earliestDate.setDay(1);
181 latestDate.setMonth(12);
182 latestDate.setDay(31);
187 // If the earliest date and the latest date are the same, it's just a "single" date.
188 // There's no need to have the latest, so set it to null.
190 if (earliestDate.equals(latestDate)) {
194 result.setEarliestSingleDate(earliestDate);
195 result.setLatestDate(latestDate);
199 public void exitBeforeOrAfterDate(BeforeOrAfterDateContext ctx) {
200 if (ctx.exception != null) return;
202 Date latestDate = (Date) stack.pop();
203 Date earliestDate = (Date) stack.pop();
205 // Set null eras to the default.
207 if (earliestDate.getEra() == null) {
208 earliestDate.setEra(Date.DEFAULT_ERA);
211 if (latestDate.getEra() == null) {
212 latestDate.setEra(Date.DEFAULT_ERA);
215 // Finalize any deferred calculations.
217 if (latestDate instanceof DeferredDate) {
218 ((DeferredDate) latestDate).resolveDate();
221 if (earliestDate instanceof DeferredDate) {
222 ((DeferredDate) earliestDate).resolveDate();
225 // Calculate the earliest date or end date.
227 if (ctx.BEFORE() != null) {
228 latestDate = earliestDate;
229 earliestDate = DateUtils.getEarliestBeforeDate(earliestDate, latestDate);
231 else if (ctx.AFTER() != null) {
232 earliestDate = latestDate;
233 latestDate = DateUtils.getLatestAfterDate(earliestDate, latestDate);
236 stack.push(earliestDate);
237 stack.push(latestDate);
241 public void exitUncertainDate(UncertainDateContext ctx) {
242 if (ctx.exception != null) return;
244 Date latestDate = (Date) stack.pop();
245 Date earliestDate = (Date) stack.pop();
248 int earliestInterval = DateUtils.getCircaIntervalYears(earliestDate.getYear(), earliestDate.getEra());
249 int latestInterval = DateUtils.getCircaIntervalYears(latestDate.getYear(), latestDate.getEra());
251 // Express the circa interval as a qualifier.
253 // stack.push(earliestDate.withQualifier(QualifierType.MINUS, earliestInterval, QualifierUnit.YEARS));
254 // stack.push(latestDate.withQualifier(QualifierType.PLUS, latestInterval, QualifierUnit.YEARS));
258 // Express the circa interval as an offset calculated into the year.
260 DateUtils.subtractYears(earliestDate, earliestInterval);
261 DateUtils.addYears(latestDate, latestInterval);
263 stack.push(earliestDate);
264 stack.push(latestDate);
268 public void exitCertainDate(CertainDateContext ctx) {
269 if (ctx.exception != null) return;
271 Date latestDate = (Date) stack.pop();
272 Date earliestDate = (Date) stack.pop();
274 // Set null eras to the default.
276 if (earliestDate.getEra() == null) {
277 earliestDate.setEra(Date.DEFAULT_ERA);
280 if (latestDate.getEra() == null) {
281 latestDate.setEra(Date.DEFAULT_ERA);
284 // Finalize any deferred calculations.
286 if (latestDate instanceof DeferredDate) {
287 ((DeferredDate) latestDate).resolveDate();
290 if (earliestDate instanceof DeferredDate) {
291 ((DeferredDate) earliestDate).resolveDate();
294 stack.push(earliestDate);
295 stack.push(latestDate);
299 public void exitHyphenatedRange(HyphenatedRangeContext ctx) {
300 if (ctx.exception != null) return;
302 Date latestEndDate = (Date) stack.pop();
303 stack.pop(); // latestStartDate
304 stack.pop(); // earliestEndDate
305 Date earliestStartDate = (Date) stack.pop();
307 // If no era was explicitly specified for the first date,
308 // make it inherit the era of the second date.
310 if (earliestStartDate.getEra() == null && latestEndDate.getEra() != null) {
311 earliestStartDate.setEra(latestEndDate.getEra());
314 // Finalize any deferred calculations.
316 if (earliestStartDate instanceof DeferredDate) {
317 ((DeferredDate) earliestStartDate).resolveDate();
320 if (latestEndDate instanceof DeferredDate) {
321 ((DeferredDate) latestEndDate).resolveDate();
324 stack.push(earliestStartDate);
325 stack.push(latestEndDate);
329 public void exitNthCenturyRange(NthCenturyRangeContext ctx) {
330 if (ctx.exception != null) return;
332 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
333 Integer endN = (Integer) stack.pop();
334 Part endPart = (Part) stack.pop();
335 Integer startN = (Integer) stack.pop();
336 Part startPart = (Part) stack.pop();
339 era = Date.DEFAULT_ERA;
342 int startYear = DateUtils.nthCenturyToYear(startN);
343 int endYear = DateUtils.nthCenturyToYear(endN);
345 stack.push(startPart == null ? DateUtils.getCenturyStartDate(startYear, era) : DateUtils.getPartialCenturyStartDate(startYear, startPart, era));
346 stack.push(startPart == null ? DateUtils.getCenturyEndDate(startYear, era) : DateUtils.getPartialCenturyEndDate(startYear, startPart, era));
347 stack.push(endPart == null ? DateUtils.getCenturyStartDate(endYear, era) : DateUtils.getPartialCenturyStartDate(endYear, endPart, era));
348 stack.push(endPart == null ? DateUtils.getCenturyEndDate(endYear, era) : DateUtils.getPartialCenturyEndDate(endYear, endPart, era));
352 public void exitMonthInYearRange(MonthInYearRangeContext ctx) {
353 if (ctx.exception != null) return;
355 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
356 Integer year = (Integer) stack.pop();
357 Integer numMonthEnd = (Integer) stack.pop();
358 Integer numMonthStart = (Integer) stack.pop();
360 stack.push(new Date(year, numMonthStart, 1, era));
361 stack.push(new Date(year, numMonthStart, DateUtils.getDaysInMonth(numMonthStart, year, era), era));
362 stack.push(new Date(year, numMonthEnd, 1, era));
363 stack.push(new Date(year, numMonthEnd, DateUtils.getDaysInMonth(numMonthEnd, year, era), era));
367 public void exitQuarterInYearRange(QuarterInYearRangeContext ctx) {
368 if (ctx.exception != null) return;
370 Era era = (Era) stack.pop();
371 Integer year = (Integer) stack.pop();
372 Integer lastQuarter = (Integer) stack.pop();
373 Integer firstQuarter = (Integer) stack.pop();
375 stack.push(DateUtils.getQuarterYearStartDate(firstQuarter, year).withEra(era));
376 stack.push(DateUtils.getQuarterYearEndDate(firstQuarter, year, era).withEra(era));
377 stack.push(DateUtils.getQuarterYearStartDate(lastQuarter, year).withEra(era));
378 stack.push(DateUtils.getQuarterYearEndDate(lastQuarter, year, era).withEra(era));
382 public void exitStrDayInMonthRange(StrDayInMonthRangeContext ctx) {
383 if (ctx.exception != null) return;
385 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
386 Integer year = (Integer) stack.pop();
387 Integer dayOfMonthEnd = (Integer) stack.pop();
388 Integer dayOfMonthStart = (Integer) stack.pop();
389 Integer numMonth = (Integer) stack.pop();
391 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
392 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
393 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
394 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
398 public void exitNumDayInMonthRange(NumDayInMonthRangeContext ctx) {
399 if (ctx.exception != null) return;
401 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
402 Integer num1 = (Integer) stack.pop();
403 Integer num2 = (Integer) stack.pop();
404 Integer num3 = (Integer) stack.pop();
405 Integer num4 = (Integer) stack.pop();
407 /* We can distinguish whether it is M/D-D/Y (Case 1) or M/Y-M/Y (Case 2) by checking if
408 The num1, num4, num2 and num1, num4, num3 are valid dates, since this would equate to
409 a set of two ranges. For examples: 04/13-19/1995 would be 04/13/1995-04/19/1995. If both these
410 dates are valid, we know that it shouldn't be interpreted as 04/01/13 - 19/31/1995 since these arent valid dates!
413 Integer lateYear = num1;
414 Integer earlyMonth = num4;
415 Integer dayOfMonthEnd = num2;
416 Integer dayOfMonthStart = num3;
418 if (DateUtils.isValidDate(num1, num4, num2, era) && DateUtils.isValidDate(num1, num4, num3, era)) {
419 // No need to alter the arguments, so just push to the stack
420 stack.push(new Date(lateYear, earlyMonth, dayOfMonthStart, era));
421 stack.push(new Date(lateYear, earlyMonth, dayOfMonthStart, era));
422 stack.push(new Date(lateYear, earlyMonth, dayOfMonthEnd, era));
423 stack.push(new Date(lateYear, earlyMonth, dayOfMonthEnd, era));
426 // Separated these by case, since it makes the code more legible
427 Integer latestMonth = num2;
428 Integer earliestYear = num3;
430 stack.push(new Date(earliestYear, earlyMonth, 1, era)); // Earliest Early Date
431 stack.push(new Date(earliestYear, earlyMonth, DateUtils.getDaysInMonth(earlyMonth, earliestYear, era), era)); // Latest Early Date
432 stack.push(new Date(lateYear, latestMonth, 1, era)); // Earliest Latest Date
433 stack.push(new Date(lateYear, latestMonth, DateUtils.getDaysInMonth(latestMonth, lateYear, era), era)); // Latest Late Date
439 public void exitDate(DateContext ctx) {
440 if (ctx.exception != null) return;
442 // Expect the canonical year-month-day-era ordering
443 // to be on the stack.
445 Era era = (stack.size() == 3) ? null : (Era) stack.pop();
446 Integer dayOfMonth = (Integer) stack.pop();
447 Integer numMonth = (Integer) stack.pop();
448 Integer year = (Integer) stack.pop();
450 // For the latest date we could either return null, or a copy of the earliest date,
451 // since the UI doesn't care. Use a copy of the earliest date, since it makes
452 // things easier here if we don't have to test for null up the tree.
454 stack.push(new Date(year, numMonth, dayOfMonth, era));
455 stack.push(new Date(year, numMonth, dayOfMonth, era));
459 public void exitNumDate(NumDateContext ctx) {
460 if (ctx.exception != null) return;
462 // This could either be year-month-day, or
463 // month-day-year. Try to determine which,
464 // and reorder the stack into the canonical
465 // year-month-day-era ordering.
467 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
468 Integer num3 = (Integer) stack.pop();
469 Integer num2 = (Integer) stack.pop();
470 Integer num1 = (Integer) stack.pop();
472 // Default to a month-day-year interpretation.
475 int dayOfMonth = num2;
478 if (DateUtils.isValidDate(num3, num1, num2, era)) {
479 // Interpreting as month-day-year produces a valid date. Go with it.
481 else if (DateUtils.isValidDate(num1, num2, num3, era)) {
482 // Interpreting as month-day-year doesn't produce a valid date, but
483 // year-month-day does. Go with year-month-day.
491 stack.push(numMonth);
492 stack.push(dayOfMonth);
497 public void exitStrDate(StrDateContext ctx) {
498 if (ctx.exception != null) return;
500 // Reorder the stack into a canonical ordering,
501 // year-month-day-era.
503 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
504 Integer year = (Integer) stack.pop();
505 Integer dayOfMonth = (Integer) stack.pop();
506 Integer numMonth = (Integer) stack.pop();
509 stack.push(numMonth);
510 stack.push(dayOfMonth);
515 public void exitInvStrDate(InvStrDateContext ctx) {
516 if (ctx.exception != null) return;
518 // Reorder the stack into a canonical ordering,
519 // year-month-day-era.
521 Integer dayOfMonth = (Integer) stack.pop();
522 Integer numMonth = (Integer) stack.pop();
523 Integer year = (Integer) stack.pop();
524 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
527 stack.push(numMonth);
528 stack.push(dayOfMonth);
533 public void exitDayFirstDate(DayFirstDateContext ctx) {
534 if (ctx.exception != null) return ;
536 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
537 Integer year = (Integer) stack.pop();
538 Integer month = (Integer) stack.pop();
539 Integer dayOfMonth = (Integer) stack.pop();
543 stack.push(dayOfMonth);
548 public void exitDayOrYearFirstDate(DayOrYearFirstDateContext ctx) {
549 if (ctx.exception != null) return;
552 Integer num2 = (Integer) stack.pop();
553 Integer numMonth = (Integer) stack.pop();
554 Integer num1 = (Integer) stack.pop();
557 Integer dayOfMonth = num2;
559 if (DateUtils.isValidDate(num1, numMonth, num2, era)) {
560 // The first number is a year. Already correct
561 } else if (DateUtils.isValidDate(num2, numMonth, num1, era)) {
562 // The second number is a year.
568 stack.push(numMonth);
569 stack.push(dayOfMonth);
571 if (dayOfMonth > 31 || dayOfMonth <= 0) {
572 throw new StructuredDateFormatException("unexpected day of month '" + Integer.toString(dayOfMonth) + "'");
575 throw new StructuredDateFormatException("unexpected year '" + Integer.toString(year) + "'");
580 public void exitInvStrDateEraLastDate(InvStrDateEraLastDateContext ctx) {
581 if (ctx.exception != null) return;
583 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
584 Integer dayOfMonth = (Integer) stack.pop();
585 Integer month = (Integer) stack.pop();
586 Integer year = (Integer) stack.pop();
590 stack.push(dayOfMonth);
595 public void exitMonth(MonthContext ctx) {
596 if (ctx.exception != null) return;
598 Era era = (Era) stack.pop();
599 Integer year = (Integer) stack.pop();
600 Integer numMonth = (Integer) stack.pop();
602 stack.push(new Date(year, numMonth, 1, era));
603 stack.push(new Date(year, numMonth, DateUtils.getDaysInMonth(numMonth, year, era), era));
607 public void exitMonthYear(MonthYearContext ctx) {
608 if (ctx.exception != null) return;
610 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
616 public void exitInvMonthYear(InvMonthYearContext ctx) {
617 if (ctx.exception != null) return;
619 // Invert the arguments.
621 Integer numMonth = (Integer) stack.pop();
622 Integer year = (Integer) stack.pop();
623 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
625 stack.push(numMonth);
631 public void exitYearSpanningWinter(YearSpanningWinterContext ctx) {
632 if (ctx.exception != null) return;
634 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
635 Integer endYear = (Integer) stack.pop();
636 Integer startYear = (Integer) stack.pop();
638 stack.push(new Date(startYear, 12, 1).withEra(era));
639 stack.push(DateUtils.getQuarterYearEndDate(1, endYear, era).withEra(era));
643 public void exitPartialYear(PartialYearContext ctx) {
644 if (ctx.exception != null) return;
646 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
647 Integer year = (Integer) stack.pop();
648 Part part = (Part) stack.pop();
650 stack.push(DateUtils.getPartialYearStartDate(part, year).withEra(era));
651 stack.push(DateUtils.getPartialYearEndDate(part, year, era).withEra(era));
655 public void exitQuarterYear(QuarterYearContext ctx) {
656 if (ctx.exception != null) return;
658 Era era = (Era) stack.pop();
659 Integer year = (Integer) stack.pop();
660 Integer quarter = (Integer) stack.pop();
662 stack.push(DateUtils.getQuarterYearStartDate(quarter, year).withEra(era));
663 stack.push(DateUtils.getQuarterYearEndDate(quarter, year, era).withEra(era));
667 public void exitHalfYear(HalfYearContext ctx) {
668 if (ctx.exception != null) return;
670 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
671 Integer year = (Integer) stack.pop();
672 Integer half = (Integer) stack.pop();
674 stack.push(DateUtils.getHalfYearStartDate(half, year).withEra(era));
675 stack.push(DateUtils.getHalfYearEndDate(half, year, era).withEra(era));
679 public void exitInvSeasonYear(InvSeasonYearContext ctx) {
680 if (ctx.exception != null) return;
682 // Invert the arguments.
684 Integer quarter = (Integer) stack.pop();
685 Integer year = (Integer) stack.pop();
686 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
694 public void exitSeasonYear(SeasonYearContext ctx) {
695 if (ctx.exception != null) return;
697 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
703 public void exitYear(YearContext ctx) {
704 if (ctx.exception != null) return;
706 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
707 Integer year = (Integer) stack.pop();
709 stack.push(new Date(year, 1, 1, era));
710 stack.push(new Date(year, 12, 31, era));
714 public void exitPartialDecade(PartialDecadeContext ctx) {
715 if (ctx.exception != null) return;
717 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
718 Integer year = (Integer) stack.pop();
719 Part part = (Part) stack.pop();
722 // If the era was explicitly specified, the start and end years
723 // may be calculated now.
725 stack.push(DateUtils.getPartialDecadeStartDate(year, part, era));
726 stack.push(DateUtils.getPartialDecadeEndDate(year, part, era));
729 // If the era was not explicitly specified, the start and end years
730 // can't be calculated yet. The calculation must be deferred until
731 // later. For example, this partial decade may be the start of a hyphenated
732 // range, where the era will be inherited from the era of the end of
733 // the range; this era won't be known until farther up the parse tree,
734 // when both sides of the range will have been parsed.
736 stack.push(new DeferredPartialDecadeStartDate(year, part));
737 stack.push(new DeferredPartialDecadeEndDate(year, part));
742 public void exitDecade(DecadeContext ctx) {
743 if (ctx.exception != null) return;
745 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
746 Integer year = (Integer) stack.pop();
748 // Calculate the start and end year of the decade, which depends on the era.
751 // If the era was explicitly specified, the start and end years
752 // may be calculated now.
754 stack.push(DateUtils.getDecadeStartDate(year, era));
755 stack.push(DateUtils.getDecadeEndDate(year, era));
758 // If the era was not explicitly specified, the start and end years
759 // can't be calculated yet. The calculation must be deferred until
760 // later. For example, this decade may be the start of a hyphenated
761 // range, where the era will be inherited from the era of the end of
762 // the range; this era won't be known until farther up the parse tree,
763 // when both sides of the range will have been parsed.
765 stack.push(new DeferredDecadeStartDate(year));
766 stack.push(new DeferredDecadeEndDate(year));
771 public void exitPartialCentury(PartialCenturyContext ctx) {
772 if (ctx.exception != null) return;
774 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
775 Integer year = (Integer) stack.pop();
776 Part part = (Part) stack.pop();
779 // If the era was explicitly specified, the start and end years
780 // may be calculated now.
782 stack.push(DateUtils.getPartialCenturyStartDate(year, part, era));
783 stack.push(DateUtils.getPartialCenturyEndDate(year, part, era));
786 // If the era was not explicitly specified, the start and end years
787 // can't be calculated yet. The calculation must be deferred until
788 // later. For example, this partial century may be the start of a hyphenated
789 // range, where the era will be inherited from the era of the end of
790 // the range; this era won't be known until farther up the parse tree,
791 // when both sides of the range will have been parsed.
793 stack.push(new DeferredPartialCenturyStartDate(year, part));
794 stack.push(new DeferredPartialCenturyEndDate(year, part));
799 public void exitQuarterCentury(QuarterCenturyContext ctx) {
800 if (ctx.exception != null) return;
802 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
803 Integer year = (Integer) stack.pop();
804 Integer quarter = (Integer) stack.pop();
807 // If the era was explicitly specified, the start and end years
808 // may be calculated now.
810 stack.push(DateUtils.getQuarterCenturyStartDate(year, quarter, era));
811 stack.push(DateUtils.getQuarterCenturyEndDate(year, quarter, era));
814 // If the era was not explicitly specified, the start and end years
815 // can't be calculated yet. The calculation must be deferred until
816 // later. For example, this century may be the start of a hyphenated
817 // range, where the era will be inherited from the era of the end of
818 // the range; this era won't be known until farther up the parse tree,
819 // when both sides of the range will have been parsed.
821 stack.push(new DeferredQuarterCenturyStartDate(year, quarter));
822 stack.push(new DeferredQuarterCenturyEndDate(year, quarter));
827 public void exitHalfCentury(HalfCenturyContext ctx) {
828 if (ctx.exception != null) return;
830 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
831 Integer year = (Integer) stack.pop();
832 Integer half = (Integer) stack.pop();
835 // If the era was explicitly specified, the start and end years
836 // may be calculated now.
838 stack.push(DateUtils.getHalfCenturyStartDate(year, half, era));
839 stack.push(DateUtils.getHalfCenturyEndDate(year, half, era));
842 // If the era was not explicitly specified, the start and end years
843 // can't be calculated yet. The calculation must be deferred until
844 // later. For example, this half century may be the start of a hyphenated
845 // range, where the era will be inherited from the era of the end of
846 // the range; this era won't be known until farther up the parse tree,
847 // when both sides of the range will have been parsed.
849 stack.push(new DeferredHalfCenturyStartDate(year, half));
850 stack.push(new DeferredHalfCenturyEndDate(year, half));
855 public void exitCentury(CenturyContext ctx) {
856 if (ctx.exception != null) return;
858 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
859 Integer year = (Integer) stack.pop();
862 // If the era was explicitly specified, the start and end years
863 // may be calculated now.
865 stack.push(DateUtils.getCenturyStartDate(year, era));
866 stack.push(DateUtils.getCenturyEndDate(year, era));
869 // If the era was not explicitly specified, the start and end years
870 // can't be calculated yet. The calculation must be deferred until
871 // later. For example, this quarter century may be the start of a hyphenated
872 // range, where the era will be inherited from the era of the end of
873 // the range; this era won't be known until farther up the parse tree,
874 // when both sides of the range will have been parsed.
876 stack.push(new DeferredCenturyStartDate(year));
877 stack.push(new DeferredCenturyEndDate(year));
882 public void exitMillennium(MillenniumContext ctx) {
883 if (ctx.exception != null) return;
885 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
886 Integer n = (Integer) stack.pop();
889 // If the era was explicitly specified, the start and end years
890 // may be calculated now.
892 stack.push(DateUtils.getMillenniumStartDate(n, era));
893 stack.push(DateUtils.getMillenniumEndDate(n, era));
896 // If the era was not explicitly specified, the start and end years
897 // can't be calculated yet. The calculation must be deferred until
898 // later. For example, this millennium may be the start of a hyphenated
899 // range, where the era will be inherited from the era of the end of
900 // the range; this era won't be known until farther up the parse tree,
901 // when both sides of the range will have been parsed.
903 stack.push(new DeferredMillenniumStartDate(n));
904 stack.push(new DeferredMillenniumEndDate(n));
909 public void exitStrCentury(StrCenturyContext ctx) {
910 if (ctx.exception != null) return;
912 Integer n = (Integer) stack.pop();
914 // Convert the nth number to a year number,
915 // and push on the stack.
917 Integer year = DateUtils.nthCenturyToYear(n);
923 public void exitNumCentury(NumCenturyContext ctx) {
924 if (ctx.exception != null) return;
926 // Convert the string to a number,
927 // and push on the stack.
929 Integer year = new Integer(stripEndLetters(ctx.HUNDREDS().getText()));
932 throw new StructuredDateFormatException("unexpected century '" + ctx.HUNDREDS().getText() + "'");
939 public void exitNumDecade(NumDecadeContext ctx) {
940 if (ctx.exception != null) return;
942 // Convert the string to a number,
943 // and push on the stack.
945 Integer year = new Integer(stripEndLetters(ctx.TENS().getText()));
948 throw new StructuredDateFormatException("unexpected decade '" + ctx.TENS().getText() + "'");
955 public void exitNumYear(NumYearContext ctx) {
956 if (ctx.exception != null) return;
958 // Convert the string to a number,
959 // and push on the stack.
961 Integer year = new Integer(ctx.getText().replaceAll(",", ""));
964 throw new StructuredDateFormatException("unexpected year '" + ctx.NUMBER().getText() + "'");
971 public void exitNumMonth(NumMonthContext ctx) {
972 if (ctx.exception != null) return;
974 // Convert the string a number,
975 // and push on the stack.
977 Integer month = new Integer(ctx.NUMBER().getText());
979 if (month < 1 || month > 12) {
980 throw new StructuredDateFormatException("unexpected month '" + ctx.NUMBER().getText() + "'");
987 public void exitNthHalf(NthHalfContext ctx) {
988 if (ctx.exception != null) return;
990 // Convert LAST to a number (the last half
991 // is the 2nd). If this rule matched the
992 // alternative with nth instead of LAST,
993 // the nth handler will already have pushed
994 // a number on the stack.
996 if (ctx.LAST() != null) {
997 stack.push(new Integer(2));
1000 // Check for a valid half.
1002 Integer n = (Integer) stack.peek();
1004 if (n < 1 || n > 2) {
1005 throw new StructuredDateFormatException("unexpected half '" + n + "'");
1011 public void exitNthQuarterInYearRange(NthQuarterInYearRangeContext ctx) {
1012 if (ctx.exception != null) return;
1014 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1020 public void exitStrSeasonInYearRange(StrSeasonInYearRangeContext ctx) {
1021 if (ctx.exception != null) return;
1023 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1030 public void exitNthQuarterYear(NthQuarterYearContext ctx) {
1032 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1038 public void exitNthQuarter(NthQuarterContext ctx) {
1039 if (ctx.exception != null) return;
1041 // Convert LAST to a number (the last quarter
1042 // is the 4th). If this rule matched the
1043 // alternative with nth instead of LAST,
1044 // the nth handler will already have pushed
1045 // a number on the stack.
1047 if (ctx.LAST() != null) {
1048 stack.push(new Integer(4));
1051 // Check for a valid quarter.
1053 Integer n = (Integer) stack.peek();
1055 if (n < 1 || n > 4) {
1056 throw new StructuredDateFormatException("unexpected quarter '" + n + "'");
1061 public void exitNth(NthContext ctx) {
1062 if (ctx.exception != null) return;
1064 // Convert the string to a number,
1065 // and push on the stack.
1069 if (ctx.NTHSTR() != null) {
1070 n = new Integer(stripEndLetters(ctx.NTHSTR().getText()));
1072 else if (ctx.FIRST() != null) {
1075 else if (ctx.SECOND() != null) {
1078 else if (ctx.THIRD() != null) {
1081 else if (ctx.FOURTH() != null) {
1089 public void exitStrMonth(StrMonthContext ctx) {
1090 if (ctx.exception != null) return;
1092 // Convert the month name to a number,
1093 // and push on the stack.
1095 TerminalNode monthNode = ctx.MONTH();
1097 if (monthNode == null) {
1098 monthNode = ctx.SHORTMONTH();
1101 String monthStr = monthNode.getText();
1103 stack.push(DateUtils.getMonthByName(monthStr));
1107 public void exitStrSeason(StrSeasonContext ctx) {
1108 if (ctx.exception != null) return;
1110 // Convert the season to a quarter number,
1111 // and push on the stack.
1113 Integer quarter = null;
1115 if (ctx.WINTER() != null) {
1118 else if (ctx.SPRING() != null) {
1121 else if (ctx.SUMMER() != null) {
1124 else if (ctx.FALL() != null) {
1128 stack.push(quarter);
1132 public void exitAllOrPartOf(AllOrPartOfContext ctx) {
1133 if (ctx.exception != null) return;
1135 // If a part was specified, it will have been
1136 // pushed on the stack in exitPartOf(). If not,
1137 // push null on the stack.
1139 if (ctx.partOf() == null) {
1145 public void exitPartOf(PartOfContext ctx) {
1146 if (ctx.exception != null) return;
1148 // Convert the token to a Part,
1149 // and push on the stack.
1153 if (ctx.EARLY() != null) {
1156 else if (ctx.MIDDLE() != null) {
1159 else if (ctx.LATE() != null) {
1167 public void exitEra(EraContext ctx) {
1168 if (ctx.exception != null) return;
1170 // Convert the token to an Era,
1171 // and push on the stack.
1175 if (ctx.BC() != null) {
1178 else if (ctx.AD() != null) {
1186 public void exitNumDayOfMonth(NumDayOfMonthContext ctx) {
1187 if (ctx.exception != null) return;
1189 // Convert the numeric string to an Integer,
1190 // and push on the stack.
1192 Integer dayOfMonth = new Integer(ctx.NUMBER().getText());
1194 if (dayOfMonth == 0 || dayOfMonth > 31) {
1195 throw new StructuredDateFormatException("unexpected day of month '" + ctx.NUMBER().getText() + "'");
1198 stack.push(dayOfMonth);
1202 public void exitPartialEraRange(PartialEraRangeContext ctx) {
1203 if (ctx.exception != null) return;
1205 Integer secondYear = (Integer) stack.pop();
1206 Integer secondMonth = (Integer) stack.pop();
1207 Integer secondDay = (Integer) stack.pop();
1209 Era era = (Era) stack.pop();
1210 Integer firstYear = (Integer) stack.pop();
1211 Integer firstMonth = (Integer) stack.pop();
1212 Integer firstDay = (Integer) stack.pop();
1214 stack.push(new Date(secondYear, secondMonth, secondDay, null));
1215 stack.push(new Date(firstYear, firstMonth, firstDay, era));
1219 public void exitNum(NumContext ctx) {
1220 if (ctx.exception != null) return;
1222 // Convert the numeric string to an Integer,
1223 // and push on the stack.
1225 Integer num = new Integer(ctx.getText().replaceAll(",", ""));
1231 public void exitRomanMonth(RomanMonthContext ctx) {
1232 int num = DateUtils.romanToDecimal(ctx.ROMANMONTH().getText());
1238 public void exitRomanDate(RomanDateContext ctx) {
1239 if (ctx.exception != null) return;
1241 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1242 Integer year = (Integer) stack.pop();
1243 Integer month = (Integer) stack.pop();
1244 Integer day = (Integer) stack.pop();
1253 public void exitUnknownDate(UnknownDateContext ctx) {
1254 if (ctx.exception != null) return;
1257 stack.push(new Date());
1258 stack.push(new Date());
1261 public void exitUncalibratedDate(UncalibratedDateContext ctx) {
1262 if (ctx.exception != null) return;
1264 Integer adjustmentDate = (Integer) stack.pop();
1265 Integer mainYear = (Integer) stack.pop();
1267 Integer earliestYear = (BP_ZERO_YEAR - mainYear) - adjustmentDate;
1268 Integer latestYear = (BP_ZERO_YEAR - mainYear) + adjustmentDate;
1270 // If negative, then BC, else AD
1271 Era earliestEra = earliestYear < 0 ? Era.BCE : Era.CE;
1272 Era latestEra = latestYear < 0 ? Era.BCE : Era.CE;
1274 stack.push(new Date(Math.abs(earliestYear), 1, 1, earliestEra)); // Earliest Early Date
1275 stack.push(new Date(Math.abs(latestYear), 12, DateUtils.getDaysInMonth(12, Math.abs(latestYear), latestEra), latestEra)); // Latest Late Date
1279 protected String getErrorMessage(RecognitionException re) {
1280 String message = "";
1282 Parser recognizer = (Parser) re.getRecognizer();
1283 TokenStream tokens = recognizer.getInputStream();
1285 if (re instanceof NoViableAltException) {
1286 NoViableAltException e = (NoViableAltException) re;
1287 Token startToken = e.getStartToken();
1288 String input = (startToken.getType() == Token.EOF ) ? "end of text" : quote(tokens.getText(startToken, e.getOffendingToken()));
1290 message = "no viable date format found at " + input;
1292 else if (re instanceof InputMismatchException) {
1293 InputMismatchException e = (InputMismatchException) re;
1294 message = "did not expect " + getTokenDisplayString(e.getOffendingToken()) + " while looking for " +
1295 e.getExpectedTokens().toString(recognizer.getTokenNames());
1297 else if (re instanceof FailedPredicateException) {
1298 FailedPredicateException e = (FailedPredicateException) re;
1299 String ruleName = recognizer.getRuleNames()[recognizer.getContext().getRuleIndex()];
1301 message = "failed predicate " + ruleName + ": " + e.getMessage();
1307 protected String quote(String text) {
1308 return "'" + text + "'";
1311 protected String getTokenDisplayString(Token token) {
1314 if (token == null) {
1315 string = "[no token]";
1318 String text = token.getText();
1321 if (token.getType() == Token.EOF ) {
1322 string = "end of text";
1325 string = "[" + token.getType() + "]";
1329 string = quote(text);
1336 protected String stripEndLetters(String input) {
1337 return input.replaceAll("[^\\d]+$", "");
1340 public static void main(String[] args) {
1341 StructuredDateEvaluator evaluator = new ANTLRStructuredDateEvaluator();
1343 for (String displayDate : args) {
1345 evaluator.evaluate(displayDate);
1346 } catch (StructuredDateFormatException e) {
1347 e.printStackTrace();