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.PartialYearContext;
80 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.RomanDateContext;
81 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterCenturyContext;
82 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterInYearRangeContext;
83 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterYearContext;
84 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.RomanMonthContext;
85 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.SeasonYearContext;
86 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrCenturyContext;
87 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrDateContext;
88 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrDayInMonthRangeContext;
89 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrMonthContext;
90 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrSeasonContext;
91 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrSeasonInYearRangeContext;
92 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.UncalibratedDateContext;
93 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.UncertainDateContext;
94 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.UnknownDateContext;
95 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.YearContext;
96 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.YearSpanningWinterContext;
99 * A StructuredDateEvaluator that uses an ANTLR parser to parse the display date,
100 * and an ANTLR listener to generate a structured date from the resulting parse
103 public class ANTLRStructuredDateEvaluator extends StructuredDateBaseListener implements StructuredDateEvaluator {
105 * The result of the evaluation.
107 protected StructuredDateInternal result;
110 * The operation stack. The parse listener methods that are implemented here
111 * pop input parameters off the stack, and push results back on to the stack.
113 protected Stack<Object> stack;
115 public ANTLRStructuredDateEvaluator() {
120 public StructuredDateInternal evaluate(String displayDate) throws StructuredDateFormatException {
121 stack = new Stack<Object>();
123 result = new StructuredDateInternal();
124 result.setDisplayDate(displayDate);
126 // Instantiate a parser from the lowercased display date, so that parsing will be case insensitive
127 ANTLRInputStream inputStream = new ANTLRInputStream(displayDate.toLowerCase());
128 StructuredDateLexer lexer = new StructuredDateLexer(inputStream);
129 CommonTokenStream tokenStream = new CommonTokenStream(lexer);
130 StructuredDateParser parser = new StructuredDateParser(tokenStream);
132 // Don't try to recover from parse errors, just bail.
133 parser.setErrorHandler(new BailErrorStrategy());
135 // Don't print error messages to the console.
136 parser.removeErrorListeners();
138 // Generate our own custom error messages.
139 parser.addParseListener(this);
142 // Attempt to fulfill the oneDisplayDate rule of the grammar.
143 parser.oneDisplayDate();
145 catch(ParseCancellationException e) {
146 // ParseCancellationException is thrown by the BailErrorStrategy when there is a
147 // parse error, with the underlying RecognitionException as the cause.
148 RecognitionException re = (RecognitionException) e.getCause();
150 throw new StructuredDateFormatException(getErrorMessage(re), re);
153 // The parsing was successful. Return the result.
158 public void exitDisplayDate(DisplayDateContext ctx) {
159 if (ctx.exception != null) return;
161 Date latestDate = (Date) stack.pop();
162 Date earliestDate = (Date) stack.pop();
164 if (earliestDate.getYear() != null || earliestDate.getYear() != null) {
165 int compareResult = DateUtils.compareDates(earliestDate, latestDate);
166 if (compareResult == 1) {
169 earliestDate = latestDate;
172 // Check to see if the dates were reversed AND calculated. If they were
173 // Then this probably means the absolute earliestDate should have month and day as "1"
174 // and the latestDate momth 12, day 31.
175 if ((earliestDate.getMonth() == 12 && earliestDate.getDay() == 31) &&
176 (latestDate.getMonth() == 1 && latestDate.getDay() == 1)) {
177 earliestDate.setMonth(1);
178 earliestDate.setDay(1);
179 latestDate.setMonth(12);
180 latestDate.setDay(31);
185 // If the earliest date and the latest date are the same, it's just a "single" date.
186 // There's no need to have the latest, so set it to null.
188 if (earliestDate.equals(latestDate)) {
192 result.setEarliestSingleDate(earliestDate);
193 result.setLatestDate(latestDate);
197 public void exitBeforeOrAfterDate(BeforeOrAfterDateContext ctx) {
198 if (ctx.exception != null) return;
200 Date latestDate = (Date) stack.pop();
201 Date earliestDate = (Date) stack.pop();
203 // Set null eras to the default.
205 if (earliestDate.getEra() == null) {
206 earliestDate.setEra(Date.DEFAULT_ERA);
209 if (latestDate.getEra() == null) {
210 latestDate.setEra(Date.DEFAULT_ERA);
213 // Finalize any deferred calculations.
215 if (latestDate instanceof DeferredDate) {
216 ((DeferredDate) latestDate).resolveDate();
219 if (earliestDate instanceof DeferredDate) {
220 ((DeferredDate) earliestDate).resolveDate();
223 // Calculate the earliest date or end date.
225 if (ctx.BEFORE() != null) {
226 latestDate = earliestDate;
227 earliestDate = DateUtils.getEarliestBeforeDate(earliestDate, latestDate);
229 else if (ctx.AFTER() != null) {
230 earliestDate = latestDate;
231 latestDate = DateUtils.getLatestAfterDate(earliestDate, latestDate);
234 stack.push(earliestDate);
235 stack.push(latestDate);
239 public void exitUncertainDate(UncertainDateContext ctx) {
240 if (ctx.exception != null) return;
242 Date latestDate = (Date) stack.pop();
243 Date earliestDate = (Date) stack.pop();
246 int earliestInterval = DateUtils.getCircaIntervalYears(earliestDate.getYear(), earliestDate.getEra());
247 int latestInterval = DateUtils.getCircaIntervalYears(latestDate.getYear(), latestDate.getEra());
249 // Express the circa interval as a qualifier.
251 // stack.push(earliestDate.withQualifier(QualifierType.MINUS, earliestInterval, QualifierUnit.YEARS));
252 // stack.push(latestDate.withQualifier(QualifierType.PLUS, latestInterval, QualifierUnit.YEARS));
256 // Express the circa interval as an offset calculated into the year.
258 DateUtils.subtractYears(earliestDate, earliestInterval);
259 DateUtils.addYears(latestDate, latestInterval);
261 stack.push(earliestDate);
262 stack.push(latestDate);
266 public void exitCertainDate(CertainDateContext ctx) {
267 if (ctx.exception != null) return;
269 Date latestDate = (Date) stack.pop();
270 Date earliestDate = (Date) stack.pop();
272 // Set null eras to the default.
274 if (earliestDate.getEra() == null) {
275 earliestDate.setEra(Date.DEFAULT_ERA);
278 if (latestDate.getEra() == null) {
279 latestDate.setEra(Date.DEFAULT_ERA);
282 // Finalize any deferred calculations.
284 if (latestDate instanceof DeferredDate) {
285 ((DeferredDate) latestDate).resolveDate();
288 if (earliestDate instanceof DeferredDate) {
289 ((DeferredDate) earliestDate).resolveDate();
292 stack.push(earliestDate);
293 stack.push(latestDate);
297 public void exitHyphenatedRange(HyphenatedRangeContext ctx) {
298 if (ctx.exception != null) return;
300 Date latestEndDate = (Date) stack.pop();
301 stack.pop(); // latestStartDate
302 stack.pop(); // earliestEndDate
303 Date earliestStartDate = (Date) stack.pop();
305 // If no era was explicitly specified for the first date,
306 // make it inherit the era of the second date.
308 if (earliestStartDate.getEra() == null && latestEndDate.getEra() != null) {
309 earliestStartDate.setEra(latestEndDate.getEra());
312 // Finalize any deferred calculations.
314 if (earliestStartDate instanceof DeferredDate) {
315 ((DeferredDate) earliestStartDate).resolveDate();
318 if (latestEndDate instanceof DeferredDate) {
319 ((DeferredDate) latestEndDate).resolveDate();
322 stack.push(earliestStartDate);
323 stack.push(latestEndDate);
327 public void exitNthCenturyRange(NthCenturyRangeContext ctx) {
328 if (ctx.exception != null) return;
330 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
331 Integer endN = (Integer) stack.pop();
332 Part endPart = (Part) stack.pop();
333 Integer startN = (Integer) stack.pop();
334 Part startPart = (Part) stack.pop();
337 era = Date.DEFAULT_ERA;
340 int startYear = DateUtils.nthCenturyToYear(startN);
341 int endYear = DateUtils.nthCenturyToYear(endN);
343 stack.push(startPart == null ? DateUtils.getCenturyStartDate(startYear, era) : DateUtils.getPartialCenturyStartDate(startYear, startPart, era));
344 stack.push(startPart == null ? DateUtils.getCenturyEndDate(startYear, era) : DateUtils.getPartialCenturyEndDate(startYear, startPart, era));
345 stack.push(endPart == null ? DateUtils.getCenturyStartDate(endYear, era) : DateUtils.getPartialCenturyStartDate(endYear, endPart, era));
346 stack.push(endPart == null ? DateUtils.getCenturyEndDate(endYear, era) : DateUtils.getPartialCenturyEndDate(endYear, endPart, era));
350 public void exitMonthInYearRange(MonthInYearRangeContext ctx) {
351 if (ctx.exception != null) return;
353 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
354 Integer year = (Integer) stack.pop();
355 Integer numMonthEnd = (Integer) stack.pop();
356 Integer numMonthStart = (Integer) stack.pop();
358 stack.push(new Date(year, numMonthStart, 1, era));
359 stack.push(new Date(year, numMonthStart, DateUtils.getDaysInMonth(numMonthStart, year, era), era));
360 stack.push(new Date(year, numMonthEnd, 1, era));
361 stack.push(new Date(year, numMonthEnd, DateUtils.getDaysInMonth(numMonthEnd, year, era), era));
365 public void exitQuarterInYearRange(QuarterInYearRangeContext ctx) {
366 if (ctx.exception != null) return;
368 Era era = (Era) stack.pop();
369 Integer year = (Integer) stack.pop();
370 Integer lastQuarter = (Integer) stack.pop();
371 Integer firstQuarter = (Integer) stack.pop();
373 stack.push(DateUtils.getQuarterYearStartDate(firstQuarter, year).withEra(era));
374 stack.push(DateUtils.getQuarterYearEndDate(firstQuarter, year, era).withEra(era));
375 stack.push(DateUtils.getQuarterYearStartDate(lastQuarter, year).withEra(era));
376 stack.push(DateUtils.getQuarterYearEndDate(lastQuarter, year, era).withEra(era));
380 public void exitStrDayInMonthRange(StrDayInMonthRangeContext ctx) {
381 if (ctx.exception != null) return;
383 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
384 Integer year = (Integer) stack.pop();
385 Integer dayOfMonthEnd = (Integer) stack.pop();
386 Integer dayOfMonthStart = (Integer) stack.pop();
387 Integer numMonth = (Integer) stack.pop();
389 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
390 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
391 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
392 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
396 public void exitNumDayInMonthRange(NumDayInMonthRangeContext ctx) {
397 if (ctx.exception != null) return;
399 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
400 Integer num1 = (Integer) stack.pop();
401 Integer num2 = (Integer) stack.pop();
402 Integer num3 = (Integer) stack.pop();
403 Integer num4 = (Integer) stack.pop();
405 /* We can distinguish whether it is M/D-D/Y (Case 1) or M/Y-M/Y (Case 2) by checking if
406 The num1, num4, num2 and num1, num4, num3 are valid dates, since this would equate to
407 a set of two ranges. For examples: 04/13-19/1995 would be 04/13/1995-04/19/1995. If both these
408 dates are valid, we know that it shouldn't be interpreted as 04/01/13 - 19/31/1995 since these arent valid dates!
411 Integer lateYear = num1;
412 Integer earlyMonth = num4;
413 Integer dayOfMonthEnd = num2;
414 Integer dayOfMonthStart = num3;
416 if (DateUtils.isValidDate(num1, num4, num2, era) && DateUtils.isValidDate(num1, num4, num3, era)) {
417 // No need to alter the arguments, so just push to the stack
418 stack.push(new Date(lateYear, earlyMonth, dayOfMonthStart, era));
419 stack.push(new Date(lateYear, earlyMonth, dayOfMonthStart, era));
420 stack.push(new Date(lateYear, earlyMonth, dayOfMonthEnd, era));
421 stack.push(new Date(lateYear, earlyMonth, dayOfMonthEnd, era));
424 // Separated these by case, since it makes the code more legible
425 Integer latestMonth = num2;
426 Integer earliestYear = num3;
428 stack.push(new Date(earliestYear, earlyMonth, 1, era)); // Earliest Early Date
429 stack.push(new Date(earliestYear, earlyMonth, DateUtils.getDaysInMonth(earlyMonth, earliestYear, era), era)); // Latest Early Date
430 stack.push(new Date(lateYear, latestMonth, 1, era)); // Earliest Latest Date
431 stack.push(new Date(lateYear, latestMonth, DateUtils.getDaysInMonth(latestMonth, lateYear, era), era)); // Latest Late Date
437 public void exitDate(DateContext ctx) {
438 if (ctx.exception != null) return;
440 // Expect the canonical year-month-day-era ordering
441 // to be on the stack.
443 Era era = (stack.size() == 3) ? null : (Era) stack.pop();
444 Integer dayOfMonth = (Integer) stack.pop();
445 Integer numMonth = (Integer) stack.pop();
446 Integer year = (Integer) stack.pop();
448 // For the latest date we could either return null, or a copy of the earliest date,
449 // since the UI doesn't care. Use a copy of the earliest date, since it makes
450 // things easier here if we don't have to test for null up the tree.
452 stack.push(new Date(year, numMonth, dayOfMonth, era));
453 stack.push(new Date(year, numMonth, dayOfMonth, era));
457 public void exitNumDate(NumDateContext ctx) {
458 if (ctx.exception != null) return;
460 // This could either be year-month-day, or
461 // month-day-year. Try to determine which,
462 // and reorder the stack into the canonical
463 // year-month-day-era ordering.
465 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
466 Integer num3 = (Integer) stack.pop();
467 Integer num2 = (Integer) stack.pop();
468 Integer num1 = (Integer) stack.pop();
470 // Default to a month-day-year interpretation.
473 int dayOfMonth = num2;
476 if (DateUtils.isValidDate(num3, num1, num2, era)) {
477 // Interpreting as month-day-year produces a valid date. Go with it.
479 else if (DateUtils.isValidDate(num1, num2, num3, era)) {
480 // Interpreting as month-day-year doesn't produce a valid date, but
481 // year-month-day does. Go with year-month-day.
489 stack.push(numMonth);
490 stack.push(dayOfMonth);
495 public void exitStrDate(StrDateContext ctx) {
496 if (ctx.exception != null) return;
498 // Reorder the stack into a canonical ordering,
499 // year-month-day-era.
501 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
502 Integer year = (Integer) stack.pop();
503 Integer dayOfMonth = (Integer) stack.pop();
504 Integer numMonth = (Integer) stack.pop();
507 stack.push(numMonth);
508 stack.push(dayOfMonth);
513 public void exitInvStrDate(InvStrDateContext ctx) {
514 if (ctx.exception != null) return;
516 // Reorder the stack into a canonical ordering,
517 // year-month-day-era.
519 Integer dayOfMonth = (Integer) stack.pop();
520 Integer numMonth = (Integer) stack.pop();
521 Integer year = (Integer) stack.pop();
522 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
525 stack.push(numMonth);
526 stack.push(dayOfMonth);
531 public void exitDayFirstDate(DayFirstDateContext ctx) {
532 if (ctx.exception != null) return ;
534 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
535 Integer year = (Integer) stack.pop();
536 Integer month = (Integer) stack.pop();
537 Integer dayOfMonth = (Integer) stack.pop();
541 stack.push(dayOfMonth);
546 public void exitDayOrYearFirstDate(DayOrYearFirstDateContext ctx) {
547 if (ctx.exception != null) return;
550 Integer num2 = (Integer) stack.pop();
551 Integer numMonth = (Integer) stack.pop();
552 Integer num1 = (Integer) stack.pop();
555 Integer dayOfMonth = num2;
557 if (DateUtils.isValidDate(num1, numMonth, num2, era)) {
558 // The first number is a year. Already correct
559 } else if (DateUtils.isValidDate(num2, numMonth, num1, era)) {
560 // The second number is a year.
566 stack.push(numMonth);
567 stack.push(dayOfMonth);
569 if (dayOfMonth > 31 || dayOfMonth <= 0) {
570 throw new StructuredDateFormatException("unexpected day of month '" + Integer.toString(dayOfMonth) + "'");
573 throw new StructuredDateFormatException("unexpected year '" + Integer.toString(year) + "'");
578 public void exitInvStrDateEraLastDate(InvStrDateEraLastDateContext ctx) {
579 if (ctx.exception != null) return;
581 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
582 Integer dayOfMonth = (Integer) stack.pop();
583 Integer month = (Integer) stack.pop();
584 Integer year = (Integer) stack.pop();
588 stack.push(dayOfMonth);
593 public void exitMonth(MonthContext ctx) {
594 if (ctx.exception != null) return;
596 Era era = (Era) stack.pop();
597 Integer year = (Integer) stack.pop();
598 Integer numMonth = (Integer) stack.pop();
600 stack.push(new Date(year, numMonth, 1, era));
601 stack.push(new Date(year, numMonth, DateUtils.getDaysInMonth(numMonth, year, era), era));
605 public void exitMonthYear(MonthYearContext ctx) {
606 if (ctx.exception != null) return;
608 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
614 public void exitInvMonthYear(InvMonthYearContext ctx) {
615 if (ctx.exception != null) return;
617 // Invert the arguments.
619 Integer numMonth = (Integer) stack.pop();
620 Integer year = (Integer) stack.pop();
621 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
623 stack.push(numMonth);
629 public void exitYearSpanningWinter(YearSpanningWinterContext ctx) {
630 if (ctx.exception != null) return;
632 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
633 Integer endYear = (Integer) stack.pop();
634 Integer startYear = (Integer) stack.pop();
636 stack.push(new Date(startYear, 12, 1).withEra(era));
637 stack.push(DateUtils.getQuarterYearEndDate(1, endYear, era).withEra(era));
641 public void exitPartialYear(PartialYearContext ctx) {
642 if (ctx.exception != null) return;
644 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
645 Integer year = (Integer) stack.pop();
646 Part part = (Part) stack.pop();
648 stack.push(DateUtils.getPartialYearStartDate(part, year).withEra(era));
649 stack.push(DateUtils.getPartialYearEndDate(part, year, era).withEra(era));
653 public void exitQuarterYear(QuarterYearContext ctx) {
654 if (ctx.exception != null) return;
656 Era era = (Era) stack.pop();
657 Integer year = (Integer) stack.pop();
658 Integer quarter = (Integer) stack.pop();
660 stack.push(DateUtils.getQuarterYearStartDate(quarter, year).withEra(era));
661 stack.push(DateUtils.getQuarterYearEndDate(quarter, year, era).withEra(era));
665 public void exitHalfYear(HalfYearContext ctx) {
666 if (ctx.exception != null) return;
668 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
669 Integer year = (Integer) stack.pop();
670 Integer half = (Integer) stack.pop();
672 stack.push(DateUtils.getHalfYearStartDate(half, year).withEra(era));
673 stack.push(DateUtils.getHalfYearEndDate(half, year, era).withEra(era));
677 public void exitInvSeasonYear(InvSeasonYearContext ctx) {
678 if (ctx.exception != null) return;
680 // Invert the arguments.
682 Integer quarter = (Integer) stack.pop();
683 Integer year = (Integer) stack.pop();
684 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
692 public void exitSeasonYear(SeasonYearContext ctx) {
693 if (ctx.exception != null) return;
695 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
701 public void exitYear(YearContext ctx) {
702 if (ctx.exception != null) return;
704 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
705 Integer year = (Integer) stack.pop();
707 stack.push(new Date(year, 1, 1, era));
708 stack.push(new Date(year, 12, 31, era));
712 public void exitPartialDecade(PartialDecadeContext ctx) {
713 if (ctx.exception != null) return;
715 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
716 Integer year = (Integer) stack.pop();
717 Part part = (Part) stack.pop();
720 // If the era was explicitly specified, the start and end years
721 // may be calculated now.
723 stack.push(DateUtils.getPartialDecadeStartDate(year, part, era));
724 stack.push(DateUtils.getPartialDecadeEndDate(year, part, era));
727 // If the era was not explicitly specified, the start and end years
728 // can't be calculated yet. The calculation must be deferred until
729 // later. For example, this partial decade may be the start of a hyphenated
730 // range, where the era will be inherited from the era of the end of
731 // the range; this era won't be known until farther up the parse tree,
732 // when both sides of the range will have been parsed.
734 stack.push(new DeferredPartialDecadeStartDate(year, part));
735 stack.push(new DeferredPartialDecadeEndDate(year, part));
740 public void exitDecade(DecadeContext ctx) {
741 if (ctx.exception != null) return;
743 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
744 Integer year = (Integer) stack.pop();
746 // Calculate the start and end year of the decade, which depends on the era.
749 // If the era was explicitly specified, the start and end years
750 // may be calculated now.
752 stack.push(DateUtils.getDecadeStartDate(year, era));
753 stack.push(DateUtils.getDecadeEndDate(year, era));
756 // If the era was not explicitly specified, the start and end years
757 // can't be calculated yet. The calculation must be deferred until
758 // later. For example, this decade may be the start of a hyphenated
759 // range, where the era will be inherited from the era of the end of
760 // the range; this era won't be known until farther up the parse tree,
761 // when both sides of the range will have been parsed.
763 stack.push(new DeferredDecadeStartDate(year));
764 stack.push(new DeferredDecadeEndDate(year));
769 public void exitPartialCentury(PartialCenturyContext ctx) {
770 if (ctx.exception != null) return;
772 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
773 Integer year = (Integer) stack.pop();
774 Part part = (Part) stack.pop();
777 // If the era was explicitly specified, the start and end years
778 // may be calculated now.
780 stack.push(DateUtils.getPartialCenturyStartDate(year, part, era));
781 stack.push(DateUtils.getPartialCenturyEndDate(year, part, era));
784 // If the era was not explicitly specified, the start and end years
785 // can't be calculated yet. The calculation must be deferred until
786 // later. For example, this partial century may be the start of a hyphenated
787 // range, where the era will be inherited from the era of the end of
788 // the range; this era won't be known until farther up the parse tree,
789 // when both sides of the range will have been parsed.
791 stack.push(new DeferredPartialCenturyStartDate(year, part));
792 stack.push(new DeferredPartialCenturyEndDate(year, part));
797 public void exitQuarterCentury(QuarterCenturyContext ctx) {
798 if (ctx.exception != null) return;
800 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
801 Integer year = (Integer) stack.pop();
802 Integer quarter = (Integer) stack.pop();
805 // If the era was explicitly specified, the start and end years
806 // may be calculated now.
808 stack.push(DateUtils.getQuarterCenturyStartDate(year, quarter, era));
809 stack.push(DateUtils.getQuarterCenturyEndDate(year, quarter, era));
812 // If the era was not explicitly specified, the start and end years
813 // can't be calculated yet. The calculation must be deferred until
814 // later. For example, this century may be the start of a hyphenated
815 // range, where the era will be inherited from the era of the end of
816 // the range; this era won't be known until farther up the parse tree,
817 // when both sides of the range will have been parsed.
819 stack.push(new DeferredQuarterCenturyStartDate(year, quarter));
820 stack.push(new DeferredQuarterCenturyEndDate(year, quarter));
825 public void exitHalfCentury(HalfCenturyContext ctx) {
826 if (ctx.exception != null) return;
828 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
829 Integer year = (Integer) stack.pop();
830 Integer half = (Integer) stack.pop();
833 // If the era was explicitly specified, the start and end years
834 // may be calculated now.
836 stack.push(DateUtils.getHalfCenturyStartDate(year, half, era));
837 stack.push(DateUtils.getHalfCenturyEndDate(year, half, era));
840 // If the era was not explicitly specified, the start and end years
841 // can't be calculated yet. The calculation must be deferred until
842 // later. For example, this half century may be the start of a hyphenated
843 // range, where the era will be inherited from the era of the end of
844 // the range; this era won't be known until farther up the parse tree,
845 // when both sides of the range will have been parsed.
847 stack.push(new DeferredHalfCenturyStartDate(year, half));
848 stack.push(new DeferredHalfCenturyEndDate(year, half));
853 public void exitCentury(CenturyContext ctx) {
854 if (ctx.exception != null) return;
856 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
857 Integer year = (Integer) stack.pop();
860 // If the era was explicitly specified, the start and end years
861 // may be calculated now.
863 stack.push(DateUtils.getCenturyStartDate(year, era));
864 stack.push(DateUtils.getCenturyEndDate(year, era));
867 // If the era was not explicitly specified, the start and end years
868 // can't be calculated yet. The calculation must be deferred until
869 // later. For example, this quarter century may be the start of a hyphenated
870 // range, where the era will be inherited from the era of the end of
871 // the range; this era won't be known until farther up the parse tree,
872 // when both sides of the range will have been parsed.
874 stack.push(new DeferredCenturyStartDate(year));
875 stack.push(new DeferredCenturyEndDate(year));
880 public void exitMillennium(MillenniumContext ctx) {
881 if (ctx.exception != null) return;
883 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
884 Integer n = (Integer) stack.pop();
887 // If the era was explicitly specified, the start and end years
888 // may be calculated now.
890 stack.push(DateUtils.getMillenniumStartDate(n, era));
891 stack.push(DateUtils.getMillenniumEndDate(n, era));
894 // If the era was not explicitly specified, the start and end years
895 // can't be calculated yet. The calculation must be deferred until
896 // later. For example, this millennium may be the start of a hyphenated
897 // range, where the era will be inherited from the era of the end of
898 // the range; this era won't be known until farther up the parse tree,
899 // when both sides of the range will have been parsed.
901 stack.push(new DeferredMillenniumStartDate(n));
902 stack.push(new DeferredMillenniumEndDate(n));
907 public void exitStrCentury(StrCenturyContext ctx) {
908 if (ctx.exception != null) return;
910 Integer n = (Integer) stack.pop();
912 // Convert the nth number to a year number,
913 // and push on the stack.
915 Integer year = DateUtils.nthCenturyToYear(n);
921 public void exitNumCentury(NumCenturyContext ctx) {
922 if (ctx.exception != null) return;
924 // Convert the string to a number,
925 // and push on the stack.
927 Integer year = new Integer(stripEndLetters(ctx.HUNDREDS().getText()));
930 throw new StructuredDateFormatException("unexpected century '" + ctx.HUNDREDS().getText() + "'");
937 public void exitNumDecade(NumDecadeContext ctx) {
938 if (ctx.exception != null) return;
940 // Convert the string to a number,
941 // and push on the stack.
943 Integer year = new Integer(stripEndLetters(ctx.TENS().getText()));
946 throw new StructuredDateFormatException("unexpected decade '" + ctx.TENS().getText() + "'");
953 public void exitNumYear(NumYearContext ctx) {
954 if (ctx.exception != null) return;
956 // Convert the string to a number,
957 // and push on the stack.
959 Integer year = new Integer(ctx.getText().replaceAll(",", ""));
962 throw new StructuredDateFormatException("unexpected year '" + ctx.NUMBER().getText() + "'");
969 public void exitNumMonth(NumMonthContext ctx) {
970 if (ctx.exception != null) return;
972 // Convert the string a number,
973 // and push on the stack.
975 Integer month = new Integer(ctx.NUMBER().getText());
977 if (month < 1 || month > 12) {
978 throw new StructuredDateFormatException("unexpected month '" + ctx.NUMBER().getText() + "'");
985 public void exitNthHalf(NthHalfContext ctx) {
986 if (ctx.exception != null) return;
988 // Convert LAST to a number (the last half
989 // is the 2nd). If this rule matched the
990 // alternative with nth instead of LAST,
991 // the nth handler will already have pushed
992 // a number on the stack.
994 if (ctx.LAST() != null) {
995 stack.push(new Integer(2));
998 // Check for a valid half.
1000 Integer n = (Integer) stack.peek();
1002 if (n < 1 || n > 2) {
1003 throw new StructuredDateFormatException("unexpected half '" + n + "'");
1009 public void exitNthQuarterInYearRange(NthQuarterInYearRangeContext ctx) {
1010 if (ctx.exception != null) return;
1012 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1018 public void exitStrSeasonInYearRange(StrSeasonInYearRangeContext ctx) {
1019 if (ctx.exception != null) return;
1021 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1028 public void exitNthQuarterYear(NthQuarterYearContext ctx) {
1030 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1036 public void exitNthQuarter(NthQuarterContext ctx) {
1037 if (ctx.exception != null) return;
1039 // Convert LAST to a number (the last quarter
1040 // is the 4th). If this rule matched the
1041 // alternative with nth instead of LAST,
1042 // the nth handler will already have pushed
1043 // a number on the stack.
1045 if (ctx.LAST() != null) {
1046 stack.push(new Integer(4));
1049 // Check for a valid quarter.
1051 Integer n = (Integer) stack.peek();
1053 if (n < 1 || n > 4) {
1054 throw new StructuredDateFormatException("unexpected quarter '" + n + "'");
1059 public void exitNth(NthContext ctx) {
1060 if (ctx.exception != null) return;
1062 // Convert the string to a number,
1063 // and push on the stack.
1067 if (ctx.NTHSTR() != null) {
1068 n = new Integer(stripEndLetters(ctx.NTHSTR().getText()));
1070 else if (ctx.FIRST() != null) {
1073 else if (ctx.SECOND() != null) {
1076 else if (ctx.THIRD() != null) {
1079 else if (ctx.FOURTH() != null) {
1087 public void exitStrMonth(StrMonthContext ctx) {
1088 if (ctx.exception != null) return;
1090 // Convert the month name to a number,
1091 // and push on the stack.
1093 TerminalNode monthNode = ctx.MONTH();
1095 if (monthNode == null) {
1096 monthNode = ctx.SHORTMONTH();
1099 String monthStr = monthNode.getText();
1101 stack.push(DateUtils.getMonthByName(monthStr));
1105 public void exitStrSeason(StrSeasonContext ctx) {
1106 if (ctx.exception != null) return;
1108 // Convert the season to a quarter number,
1109 // and push on the stack.
1111 Integer quarter = null;
1113 if (ctx.WINTER() != null) {
1116 else if (ctx.SPRING() != null) {
1119 else if (ctx.SUMMER() != null) {
1122 else if (ctx.FALL() != null) {
1126 stack.push(quarter);
1130 public void exitAllOrPartOf(AllOrPartOfContext ctx) {
1131 if (ctx.exception != null) return;
1133 // If a part was specified, it will have been
1134 // pushed on the stack in exitPartOf(). If not,
1135 // push null on the stack.
1137 if (ctx.partOf() == null) {
1143 public void exitPartOf(PartOfContext ctx) {
1144 if (ctx.exception != null) return;
1146 // Convert the token to a Part,
1147 // and push on the stack.
1151 if (ctx.EARLY() != null) {
1154 else if (ctx.MIDDLE() != null) {
1157 else if (ctx.LATE() != null) {
1165 public void exitEra(EraContext ctx) {
1166 if (ctx.exception != null) return;
1168 // Convert the token to an Era,
1169 // and push on the stack.
1173 if (ctx.BC() != null) {
1176 else if (ctx.AD() != null) {
1184 public void exitNumDayOfMonth(NumDayOfMonthContext ctx) {
1185 if (ctx.exception != null) return;
1187 // Convert the numeric string to an Integer,
1188 // and push on the stack.
1190 Integer dayOfMonth = new Integer(ctx.NUMBER().getText());
1192 if (dayOfMonth == 0 || dayOfMonth > 31) {
1193 throw new StructuredDateFormatException("unexpected day of month '" + ctx.NUMBER().getText() + "'");
1196 stack.push(dayOfMonth);
1200 public void exitNum(NumContext ctx) {
1201 if (ctx.exception != null) return;
1203 // Convert the numeric string to an Integer,
1204 // and push on the stack.
1206 Integer num = new Integer(ctx.getText().replaceAll(",", ""));
1212 public void exitRomanMonth(RomanMonthContext ctx) {
1213 int num = DateUtils.romanToDecimal(ctx.ROMANMONTH().getText());
1219 public void exitRomanDate(RomanDateContext ctx) {
1220 if (ctx.exception != null) return;
1221 System.out.println("I am going in here");
1223 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1224 Integer year = (Integer) stack.pop();
1225 Integer month = (Integer) stack.pop();
1226 Integer day = (Integer) stack.pop();
1235 public void exitUnknownDate(UnknownDateContext ctx) {
1236 if (ctx.exception != null) return;
1239 stack.push(new Date());
1240 stack.push(new Date());
1243 public void exitUncalibratedDate(UncalibratedDateContext ctx) {
1244 if (ctx.exception != null) return;
1246 Integer adjustmentDate = (Integer) stack.pop();
1247 Integer mainYear = (Integer) stack.pop();
1249 Integer upperBound = mainYear + adjustmentDate;
1250 Integer lowerBound = mainYear - adjustmentDate;
1252 Integer currentYear = DateUtils.getCurrentDate().getYear();
1254 Integer earliestYear = currentYear - upperBound;
1255 Integer latestYear = currentYear - lowerBound ;
1257 // If negative, then BC, else AD
1258 Era earliestEra = earliestYear < 0 ? Era.BCE : Era.CE;
1259 Era latestEra = latestYear < 0 ? Era.BCE : Era.CE;
1261 stack.push(new Date(Math.abs(earliestYear), 1, 1, earliestEra)); // Earliest Early Date
1262 stack.push(new Date(Math.abs(latestYear), 12, DateUtils.getDaysInMonth(12, Math.abs(latestYear), latestEra), latestEra)); // Latest Late Date
1266 protected String getErrorMessage(RecognitionException re) {
1267 String message = "";
1269 Parser recognizer = (Parser) re.getRecognizer();
1270 TokenStream tokens = recognizer.getInputStream();
1272 if (re instanceof NoViableAltException) {
1273 NoViableAltException e = (NoViableAltException) re;
1274 Token startToken = e.getStartToken();
1275 String input = (startToken.getType() == Token.EOF ) ? "end of text" : quote(tokens.getText(startToken, e.getOffendingToken()));
1277 message = "no viable date format found at " + input;
1279 else if (re instanceof InputMismatchException) {
1280 InputMismatchException e = (InputMismatchException) re;
1281 message = "did not expect " + getTokenDisplayString(e.getOffendingToken()) + " while looking for " +
1282 e.getExpectedTokens().toString(recognizer.getTokenNames());
1284 else if (re instanceof FailedPredicateException) {
1285 FailedPredicateException e = (FailedPredicateException) re;
1286 String ruleName = recognizer.getRuleNames()[recognizer.getContext().getRuleIndex()];
1288 message = "failed predicate " + ruleName + ": " + e.getMessage();
1294 protected String quote(String text) {
1295 return "'" + text + "'";
1298 protected String getTokenDisplayString(Token token) {
1301 if (token == null) {
1302 string = "[no token]";
1305 String text = token.getText();
1308 if (token.getType() == Token.EOF ) {
1309 string = "end of text";
1312 string = "[" + token.getType() + "]";
1316 string = quote(text);
1323 protected String stripEndLetters(String input) {
1324 return input.replaceAll("[^\\d]+$", "");
1327 public static void main(String[] args) {
1328 StructuredDateEvaluator evaluator = new ANTLRStructuredDateEvaluator();
1330 for (String displayDate : args) {
1332 evaluator.evaluate(displayDate);
1333 } catch (StructuredDateFormatException e) {
1334 e.printStackTrace();