1 package org.collectionspace.services.structureddate.antlr;
3 import java.util.Stack;
5 import org.antlr.v4.runtime.ANTLRInputStream;
6 import org.antlr.v4.runtime.BailErrorStrategy;
7 import org.antlr.v4.runtime.CommonTokenStream;
8 import org.antlr.v4.runtime.FailedPredicateException;
9 import org.antlr.v4.runtime.InputMismatchException;
10 import org.antlr.v4.runtime.NoViableAltException;
11 import org.antlr.v4.runtime.Parser;
12 import org.antlr.v4.runtime.RecognitionException;
13 import org.antlr.v4.runtime.Token;
14 import org.antlr.v4.runtime.TokenStream;
15 import org.antlr.v4.runtime.misc.ParseCancellationException;
16 import org.antlr.v4.runtime.tree.TerminalNode;
17 import org.collectionspace.services.structureddate.Date;
18 import org.collectionspace.services.structureddate.DateUtils;
19 import org.collectionspace.services.structureddate.DeferredCenturyEndDate;
20 import org.collectionspace.services.structureddate.DeferredCenturyStartDate;
21 import org.collectionspace.services.structureddate.DeferredDate;
22 import org.collectionspace.services.structureddate.DeferredDecadeEndDate;
23 import org.collectionspace.services.structureddate.DeferredDecadeStartDate;
24 import org.collectionspace.services.structureddate.DeferredHalfCenturyEndDate;
25 import org.collectionspace.services.structureddate.DeferredHalfCenturyStartDate;
26 import org.collectionspace.services.structureddate.DeferredMillenniumEndDate;
27 import org.collectionspace.services.structureddate.DeferredMillenniumStartDate;
28 import org.collectionspace.services.structureddate.DeferredPartialCenturyEndDate;
29 import org.collectionspace.services.structureddate.DeferredPartialCenturyStartDate;
30 import org.collectionspace.services.structureddate.DeferredPartialDecadeEndDate;
31 import org.collectionspace.services.structureddate.DeferredPartialDecadeStartDate;
32 import org.collectionspace.services.structureddate.DeferredQuarterCenturyEndDate;
33 import org.collectionspace.services.structureddate.DeferredQuarterCenturyStartDate;
34 import org.collectionspace.services.structureddate.Era;
35 import org.collectionspace.services.structureddate.Part;
36 import org.collectionspace.services.structureddate.StructuredDateInternal;
37 import org.collectionspace.services.structureddate.StructuredDateEvaluator;
38 import org.collectionspace.services.structureddate.StructuredDateFormatException;
39 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.AllOrPartOfContext;
40 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.BeforeOrAfterDateContext;
41 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.CenturyContext;
42 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.CertainDateContext;
43 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.DateContext;
44 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.DayFirstDateContext;
45 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.DayOrYearFirstDateContext;
46 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.DecadeContext;
47 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.DisplayDateContext;
48 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.EraContext;
49 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.HalfCenturyContext;
50 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.HalfYearContext;
51 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.HyphenatedRangeContext;
52 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.InvMonthYearContext;
53 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.InvSeasonYearContext;
54 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.InvStrDateContext;
55 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.InvStrDateEraLastDateContext;
56 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.MillenniumContext;
57 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.MonthContext;
58 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.MonthInYearRangeContext;
59 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.MonthYearContext;
60 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthCenturyRangeContext;
61 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthContext;
62 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthHalfContext;
63 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthQuarterContext;
64 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthQuarterInYearRangeContext;
65 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthQuarterYearContext;
66 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumCenturyContext;
67 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumContext;
68 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDateContext;
69 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDayInMonthRangeContext;
70 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDayOfMonthContext;
71 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDecadeContext;
72 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumMonthContext;
73 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumYearContext;
74 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartOfContext;
75 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartialCenturyContext;
76 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartialDecadeContext;
77 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartialYearContext;
78 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterCenturyContext;
79 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterInYearRangeContext;
80 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterYearContext;
81 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.SeasonYearContext;
82 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrCenturyContext;
83 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrDateContext;
84 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrDayInMonthRangeContext;
85 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrMonthContext;
86 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrSeasonContext;
87 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrSeasonInYearRangeContext;
88 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.UncertainDateContext;
89 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.UnknownDateContext;
90 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.YearContext;
91 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.YearSpanningWinterContext;
94 * A StructuredDateEvaluator that uses an ANTLR parser to parse the display date,
95 * and an ANTLR listener to generate a structured date from the resulting parse
98 public class ANTLRStructuredDateEvaluator extends StructuredDateBaseListener implements StructuredDateEvaluator {
100 * The result of the evaluation.
102 protected StructuredDateInternal result;
105 * The operation stack. The parse listener methods that are implemented here
106 * pop input parameters off the stack, and push results back on to the stack.
108 protected Stack<Object> stack;
110 public ANTLRStructuredDateEvaluator() {
115 public StructuredDateInternal evaluate(String displayDate) throws StructuredDateFormatException {
116 stack = new Stack<Object>();
118 result = new StructuredDateInternal();
119 result.setDisplayDate(displayDate);
121 // Instantiate a parser from the lowercased display date, so that parsing will be
123 ANTLRInputStream inputStream = new ANTLRInputStream(displayDate.toLowerCase());
124 StructuredDateLexer lexer = new StructuredDateLexer(inputStream);
125 CommonTokenStream tokenStream = new CommonTokenStream(lexer);
126 StructuredDateParser parser = new StructuredDateParser(tokenStream);
128 // Don't try to recover from parse errors, just bail.
129 parser.setErrorHandler(new BailErrorStrategy());
131 // Don't print error messages to the console.
132 parser.removeErrorListeners();
134 // Generate our own custom error messages.
135 parser.addParseListener(this);
138 // Attempt to fulfill the oneDisplayDate rule of the grammar.
139 parser.oneDisplayDate();
141 catch(ParseCancellationException e) {
142 // ParseCancellationException is thrown by the BailErrorStrategy when there is a
143 // parse error, with the underlying RecognitionException as the cause.
144 RecognitionException re = (RecognitionException) e.getCause();
146 throw new StructuredDateFormatException(getErrorMessage(re), re);
149 // The parsing was successful. Return the result.
154 public void exitDisplayDate(DisplayDateContext ctx) {
155 if (ctx.exception != null) return;
157 Date latestDate = (Date) stack.pop();
158 Date earliestDate = (Date) stack.pop();
160 // If the earliest date and the latest date are the same, it's just a "single" date.
161 // There's no need to have the latest, so set it to null.
163 if (earliestDate.equals(latestDate)) {
167 result.setEarliestSingleDate(earliestDate);
168 result.setLatestDate(latestDate);
172 public void exitBeforeOrAfterDate(BeforeOrAfterDateContext ctx) {
173 if (ctx.exception != null) return;
175 Date latestDate = (Date) stack.pop();
176 Date earliestDate = (Date) stack.pop();
178 // Set null eras to the default.
180 if (earliestDate.getEra() == null) {
181 earliestDate.setEra(Date.DEFAULT_ERA);
184 if (latestDate.getEra() == null) {
185 latestDate.setEra(Date.DEFAULT_ERA);
188 // Finalize any deferred calculations.
190 if (latestDate instanceof DeferredDate) {
191 ((DeferredDate) latestDate).resolveDate();
194 if (earliestDate instanceof DeferredDate) {
195 ((DeferredDate) earliestDate).resolveDate();
198 // Calculate the earliest date or end date.
200 if (ctx.BEFORE() != null) {
201 latestDate = earliestDate;
202 earliestDate = DateUtils.getEarliestBeforeDate(earliestDate, latestDate);
204 else if (ctx.AFTER() != null) {
205 earliestDate = latestDate;
206 latestDate = DateUtils.getLatestAfterDate(earliestDate, latestDate);
209 stack.push(earliestDate);
210 stack.push(latestDate);
214 public void exitUncertainDate(UncertainDateContext ctx) {
215 if (ctx.exception != null) return;
217 Date latestDate = (Date) stack.pop();
218 Date earliestDate = (Date) stack.pop();
220 int earliestInterval = DateUtils.getCircaIntervalYears(earliestDate.getYear(), earliestDate.getEra());
221 int latestInterval = DateUtils.getCircaIntervalYears(latestDate.getYear(), latestDate.getEra());
223 // Express the circa interval as a qualifier.
225 // stack.push(earliestDate.withQualifier(QualifierType.MINUS, earliestInterval, QualifierUnit.YEARS));
226 // stack.push(latestDate.withQualifier(QualifierType.PLUS, latestInterval, QualifierUnit.YEARS));
230 // Express the circa interval as an offset calculated into the year.
232 DateUtils.subtractYears(earliestDate, earliestInterval);
233 DateUtils.addYears(latestDate, latestInterval);
235 stack.push(earliestDate);
236 stack.push(latestDate);
240 public void exitCertainDate(CertainDateContext ctx) {
241 if (ctx.exception != null) return;
243 Date latestDate = (Date) stack.pop();
244 Date earliestDate = (Date) stack.pop();
246 // Set null eras to the default.
248 if (earliestDate.getEra() == null) {
249 earliestDate.setEra(Date.DEFAULT_ERA);
252 if (latestDate.getEra() == null) {
253 latestDate.setEra(Date.DEFAULT_ERA);
256 // Finalize any deferred calculations.
258 if (latestDate instanceof DeferredDate) {
259 ((DeferredDate) latestDate).resolveDate();
262 if (earliestDate instanceof DeferredDate) {
263 ((DeferredDate) earliestDate).resolveDate();
266 stack.push(earliestDate);
267 stack.push(latestDate);
271 public void exitHyphenatedRange(HyphenatedRangeContext ctx) {
272 if (ctx.exception != null) return;
274 Date latestEndDate = (Date) stack.pop();
275 stack.pop(); // latestStartDate
276 stack.pop(); // earliestEndDate
277 Date earliestStartDate = (Date) stack.pop();
279 // If no era was explicitly specified for the first date,
280 // make it inherit the era of the second date.
282 if (earliestStartDate.getEra() == null && latestEndDate.getEra() != null) {
283 earliestStartDate.setEra(latestEndDate.getEra());
286 // Finalize any deferred calculations.
288 if (earliestStartDate instanceof DeferredDate) {
289 ((DeferredDate) earliestStartDate).resolveDate();
292 if (latestEndDate instanceof DeferredDate) {
293 ((DeferredDate) latestEndDate).resolveDate();
296 stack.push(earliestStartDate);
297 stack.push(latestEndDate);
301 public void exitNthCenturyRange(NthCenturyRangeContext ctx) {
302 if (ctx.exception != null) return;
304 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
305 Integer endN = (Integer) stack.pop();
306 Part endPart = (Part) stack.pop();
307 Integer startN = (Integer) stack.pop();
308 Part startPart = (Part) stack.pop();
311 era = Date.DEFAULT_ERA;
314 int startYear = DateUtils.nthCenturyToYear(startN);
315 int endYear = DateUtils.nthCenturyToYear(endN);
317 stack.push(startPart == null ? DateUtils.getCenturyStartDate(startYear, era) : DateUtils.getPartialCenturyStartDate(startYear, startPart, era));
318 stack.push(startPart == null ? DateUtils.getCenturyEndDate(startYear, era) : DateUtils.getPartialCenturyEndDate(startYear, startPart, era));
319 stack.push(endPart == null ? DateUtils.getCenturyStartDate(endYear, era) : DateUtils.getPartialCenturyStartDate(endYear, endPart, era));
320 stack.push(endPart == null ? DateUtils.getCenturyEndDate(endYear, era) : DateUtils.getPartialCenturyEndDate(endYear, endPart, era));
324 public void exitMonthInYearRange(MonthInYearRangeContext ctx) {
325 if (ctx.exception != null) return;
327 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
328 Integer year = (Integer) stack.pop();
329 Integer numMonthEnd = (Integer) stack.pop();
330 Integer numMonthStart = (Integer) stack.pop();
332 stack.push(new Date(year, numMonthStart, 1, era));
333 stack.push(new Date(year, numMonthStart, DateUtils.getDaysInMonth(numMonthStart, year, era), era));
334 stack.push(new Date(year, numMonthEnd, 1, era));
335 stack.push(new Date(year, numMonthEnd, DateUtils.getDaysInMonth(numMonthEnd, year, era), era));
339 public void exitQuarterInYearRange(QuarterInYearRangeContext ctx) {
340 if (ctx.exception != null) return;
342 Era era = (Era) stack.pop();
343 Integer year = (Integer) stack.pop();
344 Integer lastQuarter = (Integer) stack.pop();
345 Integer firstQuarter = (Integer) stack.pop();
347 stack.push(DateUtils.getQuarterYearStartDate(firstQuarter, year).withEra(era));
348 stack.push(DateUtils.getQuarterYearEndDate(firstQuarter, year, era).withEra(era));
349 stack.push(DateUtils.getQuarterYearStartDate(lastQuarter, year).withEra(era));
350 stack.push(DateUtils.getQuarterYearEndDate(lastQuarter, year, era).withEra(era));
354 public void exitStrDayInMonthRange(StrDayInMonthRangeContext ctx) {
355 if (ctx.exception != null) return;
357 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
358 Integer year = (Integer) stack.pop();
359 Integer dayOfMonthEnd = (Integer) stack.pop();
360 Integer dayOfMonthStart = (Integer) stack.pop();
361 Integer numMonth = (Integer) stack.pop();
363 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
364 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
365 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
366 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
370 public void exitNumDayInMonthRange(NumDayInMonthRangeContext ctx) {
371 if (ctx.exception != null) return;
373 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
374 Integer num1 = (Integer) stack.pop();
375 Integer num2 = (Integer) stack.pop();
376 Integer num3 = (Integer) stack.pop();
377 Integer num4 = (Integer) stack.pop();
379 /* We can distinguish whether it is M/D-D/Y (Case 1) or M/Y-M/Y (Case 2) by checking if
380 The num1, num4, num2 and num1, num4, num3 are valid dates, since this would equate to
381 a set of two ranges. For examples: 04/13-19/1995 would be 04/13/1995-04/19/1995. If both these
382 dates are valid, we know that it shouldn't be interpreted as 04/01/13 - 19/31/1995 since these arent valid dates!
385 Integer lateYear = num1;
386 Integer earlyMonth = num4;
387 Integer dayOfMonthEnd = num2;
388 Integer dayOfMonthStart = num3;
390 if (DateUtils.isValidDate(num1, num4, num2, era) && DateUtils.isValidDate(num1, num4, num3, era)) {
391 // No need to alter the arguments, so just push to the stack
392 stack.push(new Date(lateYear, earlyMonth, dayOfMonthStart, era));
393 stack.push(new Date(lateYear, earlyMonth, dayOfMonthStart, era));
394 stack.push(new Date(lateYear, earlyMonth, dayOfMonthEnd, era));
395 stack.push(new Date(lateYear, earlyMonth, dayOfMonthEnd, era));
398 // Separated these by case, since it makes the code more legible
399 Integer latestMonth = num2;
400 Integer earliestYear = num3;
402 stack.push(new Date(earliestYear, earlyMonth, 1, era)); // Earliest Early Date
403 stack.push(new Date(earliestYear, earlyMonth, DateUtils.getDaysInMonth(earlyMonth, earliestYear, era), era)); // Latest Early Date
404 stack.push(new Date(lateYear, latestMonth, 1, era)); // Earliest Latest Date
405 stack.push(new Date(lateYear, latestMonth, DateUtils.getDaysInMonth(latestMonth, lateYear, era), era)); // Latest Late Date
411 public void exitDate(DateContext ctx) {
412 if (ctx.exception != null) return;
414 // Expect the canonical year-month-day-era ordering
415 // to be on the stack.
417 Era era = (stack.size() == 3) ? null : (Era) stack.pop();
418 Integer dayOfMonth = (Integer) stack.pop();
419 Integer numMonth = (Integer) stack.pop();
420 Integer year = (Integer) stack.pop();
422 // For the latest date we could either return null, or a copy of the earliest date,
423 // since the UI doesn't care. Use a copy of the earliest date, since it makes
424 // things easier here if we don't have to test for null up the tree.
426 stack.push(new Date(year, numMonth, dayOfMonth, era));
427 stack.push(new Date(year, numMonth, dayOfMonth, era));
431 public void exitNumDate(NumDateContext ctx) {
432 if (ctx.exception != null) return;
434 // This could either be year-month-day, or
435 // month-day-year. Try to determine which,
436 // and reorder the stack into the canonical
437 // year-month-day-era ordering.
439 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
440 Integer num3 = (Integer) stack.pop();
441 Integer num2 = (Integer) stack.pop();
442 Integer num1 = (Integer) stack.pop();
444 // Default to a month-day-year interpretation.
447 int dayOfMonth = num2;
450 if (DateUtils.isValidDate(num3, num1, num2, era)) {
451 // Interpreting as month-day-year produces a valid date. Go with it.
453 else if (DateUtils.isValidDate(num1, num2, num3, era)) {
454 // Interpreting as month-day-year doesn't produce a valid date, but
455 // year-month-day does. Go with year-month-day.
463 stack.push(numMonth);
464 stack.push(dayOfMonth);
469 public void exitStrDate(StrDateContext ctx) {
470 if (ctx.exception != null) return;
472 // Reorder the stack into a canonical ordering,
473 // year-month-day-era.
475 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
476 Integer year = (Integer) stack.pop();
477 Integer dayOfMonth = (Integer) stack.pop();
478 Integer numMonth = (Integer) stack.pop();
481 stack.push(numMonth);
482 stack.push(dayOfMonth);
487 public void exitInvStrDate(InvStrDateContext ctx) {
488 if (ctx.exception != null) return;
490 // Reorder the stack into a canonical ordering,
491 // year-month-day-era.
493 Integer dayOfMonth = (Integer) stack.pop();
494 Integer numMonth = (Integer) stack.pop();
495 Integer year = (Integer) stack.pop();
496 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
499 stack.push(numMonth);
500 stack.push(dayOfMonth);
505 public void exitDayFirstDate(DayFirstDateContext ctx) {
506 if (ctx.exception != null) return ;
508 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
509 Integer year = (Integer) stack.pop();
510 Integer month = (Integer) stack.pop();
511 Integer dayOfMonth = (Integer) stack.pop();
515 stack.push(dayOfMonth);
520 public void exitDayOrYearFirstDate(DayOrYearFirstDateContext ctx) {
521 if (ctx.exception != null) return;
524 Integer num2 = (Integer) stack.pop();
525 Integer numMonth = (Integer) stack.pop();
526 Integer num1 = (Integer) stack.pop();
529 Integer dayOfMonth = num2;
531 if (DateUtils.isValidDate(num1, numMonth, num2, era)) {
532 // The first number is a year. Already correct
533 } else if (DateUtils.isValidDate(num2, numMonth, num1, era)) {
534 // The second number is a year.
540 stack.push(numMonth);
541 stack.push(dayOfMonth);
544 if (dayOfMonth > 31 || dayOfMonth <= 0) {
545 throw new StructuredDateFormatException("unexpected day of month '" + Integer.toString(dayOfMonth) + "'");
548 throw new StructuredDateFormatException("unexpected year '" + Integer.toString(year) + "'");
553 public void exitInvStrDateEraLastDate(InvStrDateEraLastDateContext ctx) {
554 if (ctx.exception != null) return;
556 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
557 Integer dayOfMonth = (Integer) stack.pop();
558 Integer month = (Integer) stack.pop();
559 Integer year = (Integer) stack.pop();
563 stack.push(dayOfMonth);
568 public void exitMonth(MonthContext ctx) {
569 if (ctx.exception != null) return;
571 Era era = (Era) stack.pop();
572 Integer year = (Integer) stack.pop();
573 Integer numMonth = (Integer) stack.pop();
575 stack.push(new Date(year, numMonth, 1, era));
576 stack.push(new Date(year, numMonth, DateUtils.getDaysInMonth(numMonth, year, era), era));
580 public void exitMonthYear(MonthYearContext ctx) {
581 if (ctx.exception != null) return;
583 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
589 public void exitInvMonthYear(InvMonthYearContext ctx) {
590 if (ctx.exception != null) return;
592 // Invert the arguments.
594 Integer numMonth = (Integer) stack.pop();
595 Integer year = (Integer) stack.pop();
596 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
598 stack.push(numMonth);
604 public void exitYearSpanningWinter(YearSpanningWinterContext ctx) {
605 if (ctx.exception != null) return;
607 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
608 Integer endYear = (Integer) stack.pop();
609 Integer startYear = (Integer) stack.pop();
611 stack.push(new Date(startYear, 12, 1).withEra(era));
612 stack.push(DateUtils.getQuarterYearEndDate(1, endYear, era).withEra(era));
616 public void exitPartialYear(PartialYearContext ctx) {
617 if (ctx.exception != null) return;
619 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
620 Integer year = (Integer) stack.pop();
621 Part part = (Part) stack.pop();
623 stack.push(DateUtils.getPartialYearStartDate(part, year).withEra(era));
624 stack.push(DateUtils.getPartialYearEndDate(part, year, era).withEra(era));
628 public void exitQuarterYear(QuarterYearContext ctx) {
629 if (ctx.exception != null) return;
631 Era era = (Era) stack.pop();
632 Integer year = (Integer) stack.pop();
633 Integer quarter = (Integer) stack.pop();
635 stack.push(DateUtils.getQuarterYearStartDate(quarter, year).withEra(era));
636 stack.push(DateUtils.getQuarterYearEndDate(quarter, year, era).withEra(era));
640 public void exitHalfYear(HalfYearContext ctx) {
641 if (ctx.exception != null) return;
643 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
644 Integer year = (Integer) stack.pop();
645 Integer half = (Integer) stack.pop();
647 stack.push(DateUtils.getHalfYearStartDate(half, year).withEra(era));
648 stack.push(DateUtils.getHalfYearEndDate(half, year, era).withEra(era));
652 public void exitInvSeasonYear(InvSeasonYearContext ctx) {
653 if (ctx.exception != null) return;
655 // Invert the arguments.
657 Integer quarter = (Integer) stack.pop();
658 Integer year = (Integer) stack.pop();
659 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
667 public void exitSeasonYear(SeasonYearContext ctx) {
668 if (ctx.exception != null) return;
670 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
676 public void exitYear(YearContext ctx) {
677 if (ctx.exception != null) return;
679 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
680 Integer year = (Integer) stack.pop();
682 stack.push(new Date(year, 1, 1, era));
683 stack.push(new Date(year, 12, 31, era));
687 public void exitPartialDecade(PartialDecadeContext ctx) {
688 if (ctx.exception != null) return;
690 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
691 Integer year = (Integer) stack.pop();
692 Part part = (Part) stack.pop();
695 // If the era was explicitly specified, the start and end years
696 // may be calculated now.
698 stack.push(DateUtils.getPartialDecadeStartDate(year, part, era));
699 stack.push(DateUtils.getPartialDecadeEndDate(year, part, era));
702 // If the era was not explicitly specified, the start and end years
703 // can't be calculated yet. The calculation must be deferred until
704 // later. For example, this partial decade may be the start of a hyphenated
705 // range, where the era will be inherited from the era of the end of
706 // the range; this era won't be known until farther up the parse tree,
707 // when both sides of the range will have been parsed.
709 stack.push(new DeferredPartialDecadeStartDate(year, part));
710 stack.push(new DeferredPartialDecadeEndDate(year, part));
715 public void exitDecade(DecadeContext ctx) {
716 if (ctx.exception != null) return;
718 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
719 Integer year = (Integer) stack.pop();
721 // Calculate the start and end year of the decade, which depends on the era.
724 // If the era was explicitly specified, the start and end years
725 // may be calculated now.
727 stack.push(DateUtils.getDecadeStartDate(year, era));
728 stack.push(DateUtils.getDecadeEndDate(year, era));
731 // If the era was not explicitly specified, the start and end years
732 // can't be calculated yet. The calculation must be deferred until
733 // later. For example, this decade may be the start of a hyphenated
734 // range, where the era will be inherited from the era of the end of
735 // the range; this era won't be known until farther up the parse tree,
736 // when both sides of the range will have been parsed.
738 stack.push(new DeferredDecadeStartDate(year));
739 stack.push(new DeferredDecadeEndDate(year));
744 public void exitPartialCentury(PartialCenturyContext ctx) {
745 if (ctx.exception != null) return;
747 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
748 Integer year = (Integer) stack.pop();
749 Part part = (Part) stack.pop();
752 // If the era was explicitly specified, the start and end years
753 // may be calculated now.
755 stack.push(DateUtils.getPartialCenturyStartDate(year, part, era));
756 stack.push(DateUtils.getPartialCenturyEndDate(year, part, era));
759 // If the era was not explicitly specified, the start and end years
760 // can't be calculated yet. The calculation must be deferred until
761 // later. For example, this partial century may be the start of a hyphenated
762 // range, where the era will be inherited from the era of the end of
763 // the range; this era won't be known until farther up the parse tree,
764 // when both sides of the range will have been parsed.
766 stack.push(new DeferredPartialCenturyStartDate(year, part));
767 stack.push(new DeferredPartialCenturyEndDate(year, part));
772 public void exitQuarterCentury(QuarterCenturyContext ctx) {
773 if (ctx.exception != null) return;
775 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
776 Integer year = (Integer) stack.pop();
777 Integer quarter = (Integer) stack.pop();
780 // If the era was explicitly specified, the start and end years
781 // may be calculated now.
783 stack.push(DateUtils.getQuarterCenturyStartDate(year, quarter, era));
784 stack.push(DateUtils.getQuarterCenturyEndDate(year, quarter, era));
787 // If the era was not explicitly specified, the start and end years
788 // can't be calculated yet. The calculation must be deferred until
789 // later. For example, this century may be the start of a hyphenated
790 // range, where the era will be inherited from the era of the end of
791 // the range; this era won't be known until farther up the parse tree,
792 // when both sides of the range will have been parsed.
794 stack.push(new DeferredQuarterCenturyStartDate(year, quarter));
795 stack.push(new DeferredQuarterCenturyEndDate(year, quarter));
800 public void exitHalfCentury(HalfCenturyContext ctx) {
801 if (ctx.exception != null) return;
803 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
804 Integer year = (Integer) stack.pop();
805 Integer half = (Integer) stack.pop();
808 // If the era was explicitly specified, the start and end years
809 // may be calculated now.
811 stack.push(DateUtils.getHalfCenturyStartDate(year, half, era));
812 stack.push(DateUtils.getHalfCenturyEndDate(year, half, era));
815 // If the era was not explicitly specified, the start and end years
816 // can't be calculated yet. The calculation must be deferred until
817 // later. For example, this half century may be the start of a hyphenated
818 // range, where the era will be inherited from the era of the end of
819 // the range; this era won't be known until farther up the parse tree,
820 // when both sides of the range will have been parsed.
822 stack.push(new DeferredHalfCenturyStartDate(year, half));
823 stack.push(new DeferredHalfCenturyEndDate(year, half));
828 public void exitCentury(CenturyContext ctx) {
829 if (ctx.exception != null) return;
831 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
832 Integer year = (Integer) stack.pop();
835 // If the era was explicitly specified, the start and end years
836 // may be calculated now.
838 stack.push(DateUtils.getCenturyStartDate(year, era));
839 stack.push(DateUtils.getCenturyEndDate(year, 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 quarter 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 DeferredCenturyStartDate(year));
850 stack.push(new DeferredCenturyEndDate(year));
855 public void exitMillennium(MillenniumContext ctx) {
856 if (ctx.exception != null) return;
858 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
859 Integer n = (Integer) stack.pop();
862 // If the era was explicitly specified, the start and end years
863 // may be calculated now.
865 stack.push(DateUtils.getMillenniumStartDate(n, era));
866 stack.push(DateUtils.getMillenniumEndDate(n, 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 millennium 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 DeferredMillenniumStartDate(n));
877 stack.push(new DeferredMillenniumEndDate(n));
882 public void exitStrCentury(StrCenturyContext ctx) {
883 if (ctx.exception != null) return;
885 Integer n = (Integer) stack.pop();
887 // Convert the nth number to a year number,
888 // and push on the stack.
890 Integer year = DateUtils.nthCenturyToYear(n);
896 public void exitNumCentury(NumCenturyContext ctx) {
897 if (ctx.exception != null) return;
899 // Convert the string to a number,
900 // and push on the stack.
902 Integer year = new Integer(stripEndLetters(ctx.HUNDREDS().getText()));
905 throw new StructuredDateFormatException("unexpected century '" + ctx.HUNDREDS().getText() + "'");
912 public void exitNumDecade(NumDecadeContext ctx) {
913 if (ctx.exception != null) return;
915 // Convert the string to a number,
916 // and push on the stack.
918 Integer year = new Integer(stripEndLetters(ctx.TENS().getText()));
921 throw new StructuredDateFormatException("unexpected decade '" + ctx.TENS().getText() + "'");
928 public void exitNumYear(NumYearContext ctx) {
929 if (ctx.exception != null) return;
931 // Convert the string to a number,
932 // and push on the stack.
934 Integer year = new Integer(ctx.NUMBER().getText());
937 throw new StructuredDateFormatException("unexpected year '" + ctx.NUMBER().getText() + "'");
944 public void exitNumMonth(NumMonthContext ctx) {
945 if (ctx.exception != null) return;
947 // Convert the string a number,
948 // and push on the stack.
950 Integer month = new Integer(ctx.NUMBER().getText());
952 if (month < 1 || month > 12) {
953 throw new StructuredDateFormatException("unexpected month '" + ctx.NUMBER().getText() + "'");
960 public void exitNthHalf(NthHalfContext ctx) {
961 if (ctx.exception != null) return;
963 // Convert LAST to a number (the last half
964 // is the 2nd). If this rule matched the
965 // alternative with nth instead of LAST,
966 // the nth handler will already have pushed
967 // a number on the stack.
969 if (ctx.LAST() != null) {
970 stack.push(new Integer(2));
973 // Check for a valid half.
975 Integer n = (Integer) stack.peek();
977 if (n < 1 || n > 2) {
978 throw new StructuredDateFormatException("unexpected half '" + n + "'");
984 public void exitNthQuarterInYearRange(NthQuarterInYearRangeContext ctx) {
985 if (ctx.exception != null) return;
987 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
993 public void exitStrSeasonInYearRange(StrSeasonInYearRangeContext ctx) {
994 if (ctx.exception != null) return;
996 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1003 public void exitNthQuarterYear(NthQuarterYearContext ctx) {
1005 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1011 public void exitNthQuarter(NthQuarterContext ctx) {
1012 if (ctx.exception != null) return;
1014 // Convert LAST to a number (the last quarter
1015 // is the 4th). If this rule matched the
1016 // alternative with nth instead of LAST,
1017 // the nth handler will already have pushed
1018 // a number on the stack.
1020 if (ctx.LAST() != null) {
1021 stack.push(new Integer(4));
1024 // Check for a valid quarter.
1026 Integer n = (Integer) stack.peek();
1028 if (n < 1 || n > 4) {
1029 throw new StructuredDateFormatException("unexpected quarter '" + n + "'");
1034 public void exitNth(NthContext ctx) {
1035 if (ctx.exception != null) return;
1037 // Convert the string to a number,
1038 // and push on the stack.
1042 if (ctx.NTHSTR() != null) {
1043 n = new Integer(stripEndLetters(ctx.NTHSTR().getText()));
1045 else if (ctx.FIRST() != null) {
1048 else if (ctx.SECOND() != null) {
1051 else if (ctx.THIRD() != null) {
1054 else if (ctx.FOURTH() != null) {
1062 public void exitStrMonth(StrMonthContext ctx) {
1063 if (ctx.exception != null) return;
1065 // Convert the month name to a number,
1066 // and push on the stack.
1068 TerminalNode monthNode = ctx.MONTH();
1070 if (monthNode == null) {
1071 monthNode = ctx.SHORTMONTH();
1074 String monthStr = monthNode.getText();
1076 stack.push(DateUtils.getMonthByName(monthStr));
1080 public void exitStrSeason(StrSeasonContext ctx) {
1081 if (ctx.exception != null) return;
1083 // Convert the season to a quarter number,
1084 // and push on the stack.
1086 Integer quarter = null;
1088 if (ctx.WINTER() != null) {
1091 else if (ctx.SPRING() != null) {
1094 else if (ctx.SUMMER() != null) {
1097 else if (ctx.FALL() != null) {
1101 stack.push(quarter);
1105 public void exitAllOrPartOf(AllOrPartOfContext ctx) {
1106 if (ctx.exception != null) return;
1108 // If a part was specified, it will have been
1109 // pushed on the stack in exitPartOf(). If not,
1110 // push null on the stack.
1112 if (ctx.partOf() == null) {
1118 public void exitPartOf(PartOfContext ctx) {
1119 if (ctx.exception != null) return;
1121 // Convert the token to a Part,
1122 // and push on the stack.
1126 if (ctx.EARLY() != null) {
1129 else if (ctx.MIDDLE() != null) {
1132 else if (ctx.LATE() != null) {
1140 public void exitEra(EraContext ctx) {
1141 if (ctx.exception != null) return;
1143 // Convert the token to an Era,
1144 // and push on the stack.
1148 if (ctx.BC() != null) {
1151 else if (ctx.AD() != null) {
1159 public void exitNumDayOfMonth(NumDayOfMonthContext ctx) {
1160 if (ctx.exception != null) return;
1162 // Convert the numeric string to an Integer,
1163 // and push on the stack.
1165 Integer dayOfMonth = new Integer(ctx.NUMBER().getText());
1167 if (dayOfMonth == 0 || dayOfMonth > 31) {
1168 throw new StructuredDateFormatException("unexpected day of month '" + ctx.NUMBER().getText() + "'");
1171 stack.push(dayOfMonth);
1175 public void exitNum(NumContext ctx) {
1176 if (ctx.exception != null) return;
1178 // Convert the numeric string to an Integer,
1179 // and push on the stack.
1181 Integer num = new Integer(ctx.NUMBER().getText());
1187 public void exitUnknownDate(UnknownDateContext ctx) {
1188 if (ctx.exception != null) return;
1191 stack.push(new Date());
1192 stack.push(new Date());
1195 protected String getErrorMessage(RecognitionException re) {
1196 String message = "";
1198 Parser recognizer = (Parser) re.getRecognizer();
1199 TokenStream tokens = recognizer.getInputStream();
1201 if (re instanceof NoViableAltException) {
1202 NoViableAltException e = (NoViableAltException) re;
1203 Token startToken = e.getStartToken();
1204 String input = (startToken.getType() == Token.EOF ) ? "end of text" : quote(tokens.getText(startToken, e.getOffendingToken()));
1206 message = "no viable date format found at " + input;
1208 else if (re instanceof InputMismatchException) {
1209 InputMismatchException e = (InputMismatchException) re;
1210 message = "did not expect " + getTokenDisplayString(e.getOffendingToken()) + " while looking for " +
1211 e.getExpectedTokens().toString(recognizer.getTokenNames());
1213 else if (re instanceof FailedPredicateException) {
1214 FailedPredicateException e = (FailedPredicateException) re;
1215 String ruleName = recognizer.getRuleNames()[recognizer.getContext().getRuleIndex()];
1217 message = "failed predicate " + ruleName + ": " + e.getMessage();
1223 protected String quote(String text) {
1224 return "'" + text + "'";
1227 protected String getTokenDisplayString(Token token) {
1230 if (token == null) {
1231 string = "[no token]";
1234 String text = token.getText();
1237 if (token.getType() == Token.EOF ) {
1238 string = "end of text";
1241 string = "[" + token.getType() + "]";
1245 string = quote(text);
1252 protected String stripEndLetters(String input) {
1253 return input.replaceAll("[^\\d]+$", "");
1256 public static void main(String[] args) {
1257 StructuredDateEvaluator evaluator = new ANTLRStructuredDateEvaluator();
1259 for (String displayDate : args) {
1261 evaluator.evaluate(displayDate);
1262 } catch (StructuredDateFormatException e) {
1263 e.printStackTrace();