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);
171 public void exitUnknownDate(UnknownDateContext ctx) {
172 if (ctx.exception != null) return;
175 stack.push(new Date());
176 stack.push(new Date());
180 public void exitBeforeOrAfterDate(BeforeOrAfterDateContext ctx) {
181 if (ctx.exception != null) return;
183 Date latestDate = (Date) stack.pop();
184 Date earliestDate = (Date) stack.pop();
186 // Set null eras to the default.
188 if (earliestDate.getEra() == null) {
189 earliestDate.setEra(Date.DEFAULT_ERA);
192 if (latestDate.getEra() == null) {
193 latestDate.setEra(Date.DEFAULT_ERA);
196 // Finalize any deferred calculations.
198 if (latestDate instanceof DeferredDate) {
199 ((DeferredDate) latestDate).resolveDate();
202 if (earliestDate instanceof DeferredDate) {
203 ((DeferredDate) earliestDate).resolveDate();
206 // Calculate the earliest date or end date.
208 if (ctx.BEFORE() != null) {
209 latestDate = earliestDate;
210 earliestDate = DateUtils.getEarliestBeforeDate(earliestDate, latestDate);
212 else if (ctx.AFTER() != null) {
213 earliestDate = latestDate;
214 latestDate = DateUtils.getLatestAfterDate(earliestDate, latestDate);
217 stack.push(earliestDate);
218 stack.push(latestDate);
222 public void exitUncertainDate(UncertainDateContext ctx) {
223 if (ctx.exception != null) return;
225 Date latestDate = (Date) stack.pop();
226 Date earliestDate = (Date) stack.pop();
228 int earliestInterval = DateUtils.getCircaIntervalYears(earliestDate.getYear(), earliestDate.getEra());
229 int latestInterval = DateUtils.getCircaIntervalYears(latestDate.getYear(), latestDate.getEra());
231 // Express the circa interval as a qualifier.
233 // stack.push(earliestDate.withQualifier(QualifierType.MINUS, earliestInterval, QualifierUnit.YEARS));
234 // stack.push(latestDate.withQualifier(QualifierType.PLUS, latestInterval, QualifierUnit.YEARS));
238 // Express the circa interval as an offset calculated into the year.
240 DateUtils.subtractYears(earliestDate, earliestInterval);
241 DateUtils.addYears(latestDate, latestInterval);
243 stack.push(earliestDate);
244 stack.push(latestDate);
248 public void exitCertainDate(CertainDateContext ctx) {
249 if (ctx.exception != null) return;
251 Date latestDate = (Date) stack.pop();
252 Date earliestDate = (Date) stack.pop();
254 // Set null eras to the default.
256 if (earliestDate.getEra() == null) {
257 earliestDate.setEra(Date.DEFAULT_ERA);
260 if (latestDate.getEra() == null) {
261 latestDate.setEra(Date.DEFAULT_ERA);
264 // Finalize any deferred calculations.
266 if (latestDate instanceof DeferredDate) {
267 ((DeferredDate) latestDate).resolveDate();
270 if (earliestDate instanceof DeferredDate) {
271 ((DeferredDate) earliestDate).resolveDate();
274 stack.push(earliestDate);
275 stack.push(latestDate);
279 public void exitHyphenatedRange(HyphenatedRangeContext ctx) {
280 if (ctx.exception != null) return;
282 Date latestEndDate = (Date) stack.pop();
283 stack.pop(); // latestStartDate
284 stack.pop(); // earliestEndDate
285 Date earliestStartDate = (Date) stack.pop();
287 // If no era was explicitly specified for the first date,
288 // make it inherit the era of the second date.
290 if (earliestStartDate.getEra() == null && latestEndDate.getEra() != null) {
291 earliestStartDate.setEra(latestEndDate.getEra());
294 // Finalize any deferred calculations.
296 if (earliestStartDate instanceof DeferredDate) {
297 ((DeferredDate) earliestStartDate).resolveDate();
300 if (latestEndDate instanceof DeferredDate) {
301 ((DeferredDate) latestEndDate).resolveDate();
304 stack.push(earliestStartDate);
305 stack.push(latestEndDate);
309 public void exitNthCenturyRange(NthCenturyRangeContext ctx) {
310 if (ctx.exception != null) return;
312 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
313 Integer endN = (Integer) stack.pop();
314 Part endPart = (Part) stack.pop();
315 Integer startN = (Integer) stack.pop();
316 Part startPart = (Part) stack.pop();
319 era = Date.DEFAULT_ERA;
322 int startYear = DateUtils.nthCenturyToYear(startN);
323 int endYear = DateUtils.nthCenturyToYear(endN);
325 stack.push(startPart == null ? DateUtils.getCenturyStartDate(startYear, era) : DateUtils.getPartialCenturyStartDate(startYear, startPart, era));
326 stack.push(startPart == null ? DateUtils.getCenturyEndDate(startYear, era) : DateUtils.getPartialCenturyEndDate(startYear, startPart, era));
327 stack.push(endPart == null ? DateUtils.getCenturyStartDate(endYear, era) : DateUtils.getPartialCenturyStartDate(endYear, endPart, era));
328 stack.push(endPart == null ? DateUtils.getCenturyEndDate(endYear, era) : DateUtils.getPartialCenturyEndDate(endYear, endPart, era));
332 public void exitMonthInYearRange(MonthInYearRangeContext ctx) {
333 if (ctx.exception != null) return;
335 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
336 Integer year = (Integer) stack.pop();
337 Integer numMonthEnd = (Integer) stack.pop();
338 Integer numMonthStart = (Integer) stack.pop();
340 stack.push(new Date(year, numMonthStart, 1, era));
341 stack.push(new Date(year, numMonthStart, DateUtils.getDaysInMonth(numMonthStart, year, era), era));
342 stack.push(new Date(year, numMonthEnd, 1, era));
343 stack.push(new Date(year, numMonthEnd, DateUtils.getDaysInMonth(numMonthEnd, year, era), era));
347 public void exitQuarterInYearRange(QuarterInYearRangeContext ctx) {
348 if (ctx.exception != null) return;
350 Era era = (Era) stack.pop();
351 Integer year = (Integer) stack.pop();
352 Integer lastQuarter = (Integer) stack.pop();
353 Integer firstQuarter = (Integer) stack.pop();
355 stack.push(DateUtils.getQuarterYearStartDate(firstQuarter, year).withEra(era));
356 stack.push(DateUtils.getQuarterYearEndDate(firstQuarter, year, era).withEra(era));
357 stack.push(DateUtils.getQuarterYearStartDate(lastQuarter, year).withEra(era));
358 stack.push(DateUtils.getQuarterYearEndDate(lastQuarter, year, era).withEra(era));
362 public void exitStrDayInMonthRange(StrDayInMonthRangeContext ctx) {
363 if (ctx.exception != null) return;
365 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
366 Integer year = (Integer) stack.pop();
367 Integer dayOfMonthEnd = (Integer) stack.pop();
368 Integer dayOfMonthStart = (Integer) stack.pop();
369 Integer numMonth = (Integer) stack.pop();
371 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
372 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
373 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
374 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
378 public void exitNumDayInMonthRange(NumDayInMonthRangeContext ctx) {
379 if (ctx.exception != null) return;
381 Era era = (Era) stack.pop();
382 Integer num1 = (Integer) stack.pop();
383 Integer num2 = (Integer) stack.pop();
384 Integer num3 = (Integer) stack.pop();
385 Integer num4 = (Integer) stack.pop();
387 /* We can distinguish whether it is M/D-D/Y (Case 1) or M/Y-M/Y (Case 2) by checking if
388 The num1, num4, num2 and num1, num4, num3 are valid dates, since this would equate to
389 a set of two ranges. For examples: 04/13-19/1995 would be 04/13/1995-04/19/1995. If both these
390 dates are valid, we know that it shouldn't be interpreted as 04/01/13 - 19/31/1995 since these arent valid dates!
394 Integer lateYear = num1;
395 Integer earlyMonth = num4;
396 Integer dayOfMonthEnd = num2;
397 Integer dayOfMonthStart = num3;
399 if (DateUtils.isValidDate(num1, num4, num2, era) && DateUtils.isValidDate(num1, num4, num3, era)) {
400 // No need to alter the arguments, so just push to the stack
401 stack.push(new Date(lateYear, earlyMonth, dayOfMonthStart, era));
402 stack.push(new Date(lateYear, earlyMonth, dayOfMonthStart, era));
403 stack.push(new Date(lateYear, earlyMonth, dayOfMonthEnd, era));
404 stack.push(new Date(lateYear, earlyMonth, dayOfMonthEnd, era));
407 // Separated these by case, since it makes the code more legible
408 Integer latestMonth = num2;
409 Integer earliestYear = num3;
411 stack.push(new Date(earliestYear, earlyMonth, 1, era)); // Earliest Early Date
412 stack.push(new Date(earliestYear, earlyMonth, DateUtils.getDaysInMonth(earlyMonth, earliestYear, era), era)); // Latest Early Date
413 stack.push(new Date(lateYear, latestMonth, 1, era)); // Earliest Latest Date
414 stack.push(new Date(lateYear, latestMonth, DateUtils.getDaysInMonth(latestMonth, lateYear, era), era)); // Latest Late Date
420 public void exitDate(DateContext ctx) {
421 if (ctx.exception != null) return;
423 // Expect the canonical year-month-day-era ordering
424 // to be on the stack.
426 Era era = (stack.size() == 3) ? null : (Era) stack.pop();
427 Integer dayOfMonth = (Integer) stack.pop();
428 Integer numMonth = (Integer) stack.pop();
429 Integer year = (Integer) stack.pop();
431 // For the latest date we could either return null, or a copy of the earliest date,
432 // since the UI doesn't care. Use a copy of the earliest date, since it makes
433 // things easier here if we don't have to test for null up the tree.
435 stack.push(new Date(year, numMonth, dayOfMonth, era));
436 stack.push(new Date(year, numMonth, dayOfMonth, era));
440 public void exitNumDate(NumDateContext ctx) {
441 if (ctx.exception != null) return;
443 // This could either be year-month-day, or
444 // month-day-year. Try to determine which,
445 // and reorder the stack into the canonical
446 // year-month-day-era ordering.
448 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
449 Integer num3 = (Integer) stack.pop();
450 Integer num2 = (Integer) stack.pop();
451 Integer num1 = (Integer) stack.pop();
453 // Default to a month-day-year interpretation.
456 int dayOfMonth = num2;
459 if (DateUtils.isValidDate(num3, num1, num2, era)) {
460 // Interpreting as month-day-year produces a valid date. Go with it.
462 else if (DateUtils.isValidDate(num1, num2, num3, era)) {
463 // Interpreting as month-day-year doesn't produce a valid date, but
464 // year-month-day does. Go with year-month-day.
472 stack.push(numMonth);
473 stack.push(dayOfMonth);
478 public void exitStrDate(StrDateContext ctx) {
479 if (ctx.exception != null) return;
481 // Reorder the stack into a canonical ordering,
482 // year-month-day-era.
484 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
485 Integer year = (Integer) stack.pop();
486 Integer dayOfMonth = (Integer) stack.pop();
487 Integer numMonth = (Integer) stack.pop();
490 stack.push(numMonth);
491 stack.push(dayOfMonth);
496 public void exitInvStrDate(InvStrDateContext ctx) {
497 if (ctx.exception != null) return;
499 // Reorder the stack into a canonical ordering,
500 // year-month-day-era.
502 Integer dayOfMonth = (Integer) stack.pop();
503 Integer numMonth = (Integer) stack.pop();
504 Integer year = (Integer) stack.pop();
505 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
508 stack.push(numMonth);
509 stack.push(dayOfMonth);
514 public void exitDayFirstDate(DayFirstDateContext ctx) {
515 if (ctx.exception != null) return ;
517 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
518 Integer year = (Integer) stack.pop();
519 Integer month = (Integer) stack.pop();
520 Integer dayOfMonth = (Integer) stack.pop();
524 stack.push(dayOfMonth);
529 public void exitDayOrYearFirstDate(DayOrYearFirstDateContext ctx) {
530 if (ctx.exception != null) return;
533 Integer num2 = (Integer) stack.pop();
534 Integer numMonth = (Integer) stack.pop();
535 Integer num1 = (Integer) stack.pop();
538 Integer dayOfMonth = num2;
540 if (DateUtils.isValidDate(num1, numMonth, num2, era)) {
541 // The first number is a year. Already correct
542 } else if (DateUtils.isValidDate(num2, numMonth, num1, era)) {
543 // The second number is a year.
549 stack.push(numMonth);
550 stack.push(dayOfMonth);
552 if (dayOfMonth > 31 || dayOfMonth <= 0) {
553 throw new StructuredDateFormatException("unexpected day of month '" + Integer.toString(dayOfMonth) + "'");
556 throw new StructuredDateFormatException("unexpected year '" + Integer.toString(year) + "'");
561 public void exitInvStrDateEraLastDate(InvStrDateEraLastDateContext ctx) {
562 if (ctx.exception != null) return;
564 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
565 Integer dayOfMonth = (Integer) stack.pop();
566 Integer month = (Integer) stack.pop();
567 Integer year = (Integer) stack.pop();
571 stack.push(dayOfMonth);
576 public void exitMonth(MonthContext ctx) {
577 if (ctx.exception != null) return;
579 Era era = (Era) stack.pop();
580 Integer year = (Integer) stack.pop();
581 Integer numMonth = (Integer) stack.pop();
583 stack.push(new Date(year, numMonth, 1, era));
584 stack.push(new Date(year, numMonth, DateUtils.getDaysInMonth(numMonth, year, era), era));
588 public void exitMonthYear(MonthYearContext ctx) {
589 if (ctx.exception != null) return;
591 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
597 public void exitInvMonthYear(InvMonthYearContext ctx) {
598 if (ctx.exception != null) return;
600 // Invert the arguments.
602 Integer numMonth = (Integer) stack.pop();
603 Integer year = (Integer) stack.pop();
604 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
606 stack.push(numMonth);
612 public void exitYearSpanningWinter(YearSpanningWinterContext ctx) {
613 if (ctx.exception != null) return;
615 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
616 Integer endYear = (Integer) stack.pop();
617 Integer startYear = (Integer) stack.pop();
619 stack.push(new Date(startYear, 12, 1).withEra(era));
620 stack.push(DateUtils.getQuarterYearEndDate(1, endYear, era).withEra(era));
624 public void exitPartialYear(PartialYearContext ctx) {
625 if (ctx.exception != null) return;
627 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
628 Integer year = (Integer) stack.pop();
629 Part part = (Part) stack.pop();
631 stack.push(DateUtils.getPartialYearStartDate(part, year).withEra(era));
632 stack.push(DateUtils.getPartialYearEndDate(part, year, era).withEra(era));
636 public void exitQuarterYear(QuarterYearContext ctx) {
637 if (ctx.exception != null) return;
639 Era era = (Era) stack.pop();
640 Integer year = (Integer) stack.pop();
641 Integer quarter = (Integer) stack.pop();
643 stack.push(DateUtils.getQuarterYearStartDate(quarter, year).withEra(era));
644 stack.push(DateUtils.getQuarterYearEndDate(quarter, year, era).withEra(era));
648 public void exitHalfYear(HalfYearContext ctx) {
649 if (ctx.exception != null) return;
651 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
652 Integer year = (Integer) stack.pop();
653 Integer half = (Integer) stack.pop();
655 stack.push(DateUtils.getHalfYearStartDate(half, year).withEra(era));
656 stack.push(DateUtils.getHalfYearEndDate(half, year, era).withEra(era));
660 public void exitInvSeasonYear(InvSeasonYearContext ctx) {
661 if (ctx.exception != null) return;
663 // Invert the arguments.
665 Integer quarter = (Integer) stack.pop();
666 Integer year = (Integer) stack.pop();
667 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
675 public void exitSeasonYear(SeasonYearContext ctx) {
676 if (ctx.exception != null) return;
678 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
684 public void exitYear(YearContext ctx) {
685 if (ctx.exception != null) return;
687 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
688 Integer year = (Integer) stack.pop();
690 stack.push(new Date(year, 1, 1, era));
691 stack.push(new Date(year, 12, 31, era));
695 public void exitPartialDecade(PartialDecadeContext ctx) {
696 if (ctx.exception != null) return;
698 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
699 Integer year = (Integer) stack.pop();
700 Part part = (Part) stack.pop();
703 // If the era was explicitly specified, the start and end years
704 // may be calculated now.
706 stack.push(DateUtils.getPartialDecadeStartDate(year, part, era));
707 stack.push(DateUtils.getPartialDecadeEndDate(year, part, era));
710 // If the era was not explicitly specified, the start and end years
711 // can't be calculated yet. The calculation must be deferred until
712 // later. For example, this partial decade may be the start of a hyphenated
713 // range, where the era will be inherited from the era of the end of
714 // the range; this era won't be known until farther up the parse tree,
715 // when both sides of the range will have been parsed.
717 stack.push(new DeferredPartialDecadeStartDate(year, part));
718 stack.push(new DeferredPartialDecadeEndDate(year, part));
723 public void exitDecade(DecadeContext ctx) {
724 if (ctx.exception != null) return;
726 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
727 Integer year = (Integer) stack.pop();
729 // Calculate the start and end year of the decade, which depends on the era.
732 // If the era was explicitly specified, the start and end years
733 // may be calculated now.
735 stack.push(DateUtils.getDecadeStartDate(year, era));
736 stack.push(DateUtils.getDecadeEndDate(year, era));
739 // If the era was not explicitly specified, the start and end years
740 // can't be calculated yet. The calculation must be deferred until
741 // later. For example, this decade may be the start of a hyphenated
742 // range, where the era will be inherited from the era of the end of
743 // the range; this era won't be known until farther up the parse tree,
744 // when both sides of the range will have been parsed.
746 stack.push(new DeferredDecadeStartDate(year));
747 stack.push(new DeferredDecadeEndDate(year));
752 public void exitPartialCentury(PartialCenturyContext ctx) {
753 if (ctx.exception != null) return;
755 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
756 Integer year = (Integer) stack.pop();
757 Part part = (Part) stack.pop();
760 // If the era was explicitly specified, the start and end years
761 // may be calculated now.
763 stack.push(DateUtils.getPartialCenturyStartDate(year, part, era));
764 stack.push(DateUtils.getPartialCenturyEndDate(year, part, era));
767 // If the era was not explicitly specified, the start and end years
768 // can't be calculated yet. The calculation must be deferred until
769 // later. For example, this partial century may be the start of a hyphenated
770 // range, where the era will be inherited from the era of the end of
771 // the range; this era won't be known until farther up the parse tree,
772 // when both sides of the range will have been parsed.
774 stack.push(new DeferredPartialCenturyStartDate(year, part));
775 stack.push(new DeferredPartialCenturyEndDate(year, part));
780 public void exitQuarterCentury(QuarterCenturyContext ctx) {
781 if (ctx.exception != null) return;
783 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
784 Integer year = (Integer) stack.pop();
785 Integer quarter = (Integer) stack.pop();
788 // If the era was explicitly specified, the start and end years
789 // may be calculated now.
791 stack.push(DateUtils.getQuarterCenturyStartDate(year, quarter, era));
792 stack.push(DateUtils.getQuarterCenturyEndDate(year, quarter, era));
795 // If the era was not explicitly specified, the start and end years
796 // can't be calculated yet. The calculation must be deferred until
797 // later. For example, this century may be the start of a hyphenated
798 // range, where the era will be inherited from the era of the end of
799 // the range; this era won't be known until farther up the parse tree,
800 // when both sides of the range will have been parsed.
802 stack.push(new DeferredQuarterCenturyStartDate(year, quarter));
803 stack.push(new DeferredQuarterCenturyEndDate(year, quarter));
808 public void exitHalfCentury(HalfCenturyContext ctx) {
809 if (ctx.exception != null) return;
811 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
812 Integer year = (Integer) stack.pop();
813 Integer half = (Integer) stack.pop();
816 // If the era was explicitly specified, the start and end years
817 // may be calculated now.
819 stack.push(DateUtils.getHalfCenturyStartDate(year, half, era));
820 stack.push(DateUtils.getHalfCenturyEndDate(year, half, era));
823 // If the era was not explicitly specified, the start and end years
824 // can't be calculated yet. The calculation must be deferred until
825 // later. For example, this half century may be the start of a hyphenated
826 // range, where the era will be inherited from the era of the end of
827 // the range; this era won't be known until farther up the parse tree,
828 // when both sides of the range will have been parsed.
830 stack.push(new DeferredHalfCenturyStartDate(year, half));
831 stack.push(new DeferredHalfCenturyEndDate(year, half));
836 public void exitCentury(CenturyContext ctx) {
837 if (ctx.exception != null) return;
839 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
840 Integer year = (Integer) stack.pop();
843 // If the era was explicitly specified, the start and end years
844 // may be calculated now.
846 stack.push(DateUtils.getCenturyStartDate(year, era));
847 stack.push(DateUtils.getCenturyEndDate(year, era));
850 // If the era was not explicitly specified, the start and end years
851 // can't be calculated yet. The calculation must be deferred until
852 // later. For example, this quarter century may be the start of a hyphenated
853 // range, where the era will be inherited from the era of the end of
854 // the range; this era won't be known until farther up the parse tree,
855 // when both sides of the range will have been parsed.
857 stack.push(new DeferredCenturyStartDate(year));
858 stack.push(new DeferredCenturyEndDate(year));
863 public void exitMillennium(MillenniumContext ctx) {
864 if (ctx.exception != null) return;
866 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
867 Integer n = (Integer) stack.pop();
870 // If the era was explicitly specified, the start and end years
871 // may be calculated now.
873 stack.push(DateUtils.getMillenniumStartDate(n, era));
874 stack.push(DateUtils.getMillenniumEndDate(n, era));
877 // If the era was not explicitly specified, the start and end years
878 // can't be calculated yet. The calculation must be deferred until
879 // later. For example, this millennium may be the start of a hyphenated
880 // range, where the era will be inherited from the era of the end of
881 // the range; this era won't be known until farther up the parse tree,
882 // when both sides of the range will have been parsed.
884 stack.push(new DeferredMillenniumStartDate(n));
885 stack.push(new DeferredMillenniumEndDate(n));
890 public void exitStrCentury(StrCenturyContext ctx) {
891 if (ctx.exception != null) return;
893 Integer n = (Integer) stack.pop();
895 // Convert the nth number to a year number,
896 // and push on the stack.
898 Integer year = DateUtils.nthCenturyToYear(n);
904 public void exitNumCentury(NumCenturyContext ctx) {
905 if (ctx.exception != null) return;
907 // Convert the string to a number,
908 // and push on the stack.
910 Integer year = new Integer(stripEndLetters(ctx.HUNDREDS().getText()));
913 throw new StructuredDateFormatException("unexpected century '" + ctx.HUNDREDS().getText() + "'");
920 public void exitNumDecade(NumDecadeContext ctx) {
921 if (ctx.exception != null) return;
923 // Convert the string to a number,
924 // and push on the stack.
926 Integer year = new Integer(stripEndLetters(ctx.TENS().getText()));
929 throw new StructuredDateFormatException("unexpected decade '" + ctx.TENS().getText() + "'");
936 public void exitNumYear(NumYearContext ctx) {
937 if (ctx.exception != null) return;
939 // Convert the string to a number,
940 // and push on the stack.
942 Integer year = new Integer(ctx.NUMBER().getText());
945 throw new StructuredDateFormatException("unexpected year '" + ctx.NUMBER().getText() + "'");
952 public void exitNumMonth(NumMonthContext ctx) {
953 if (ctx.exception != null) return;
955 // Convert the string a number,
956 // and push on the stack.
958 Integer month = new Integer(ctx.NUMBER().getText());
960 if (month < 1 || month > 12) {
961 throw new StructuredDateFormatException("unexpected month '" + ctx.NUMBER().getText() + "'");
968 public void exitNthHalf(NthHalfContext ctx) {
969 if (ctx.exception != null) return;
971 // Convert LAST to a number (the last half
972 // is the 2nd). If this rule matched the
973 // alternative with nth instead of LAST,
974 // the nth handler will already have pushed
975 // a number on the stack.
977 if (ctx.LAST() != null) {
978 stack.push(new Integer(2));
981 // Check for a valid half.
983 Integer n = (Integer) stack.peek();
985 if (n < 1 || n > 2) {
986 throw new StructuredDateFormatException("unexpected half '" + n + "'");
992 public void exitNthQuarterInYearRange(NthQuarterInYearRangeContext ctx) {
993 if (ctx.exception != null) return;
995 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1001 public void exitStrSeasonInYearRange(StrSeasonInYearRangeContext ctx) {
1002 if (ctx.exception != null) return;
1004 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1011 public void exitNthQuarterYear(NthQuarterYearContext ctx) {
1013 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1019 public void exitNthQuarter(NthQuarterContext ctx) {
1020 if (ctx.exception != null) return;
1022 // Convert LAST to a number (the last quarter
1023 // is the 4th). If this rule matched the
1024 // alternative with nth instead of LAST,
1025 // the nth handler will already have pushed
1026 // a number on the stack.
1028 if (ctx.LAST() != null) {
1029 stack.push(new Integer(4));
1032 // Check for a valid quarter.
1034 Integer n = (Integer) stack.peek();
1036 if (n < 1 || n > 4) {
1037 throw new StructuredDateFormatException("unexpected quarter '" + n + "'");
1042 public void exitNth(NthContext ctx) {
1043 if (ctx.exception != null) return;
1045 // Convert the string to a number,
1046 // and push on the stack.
1050 if (ctx.NTHSTR() != null) {
1051 n = new Integer(stripEndLetters(ctx.NTHSTR().getText()));
1053 else if (ctx.FIRST() != null) {
1056 else if (ctx.SECOND() != null) {
1059 else if (ctx.THIRD() != null) {
1062 else if (ctx.FOURTH() != null) {
1070 public void exitStrMonth(StrMonthContext ctx) {
1071 if (ctx.exception != null) return;
1073 // Convert the month name to a number,
1074 // and push on the stack.
1076 TerminalNode monthNode = ctx.MONTH();
1078 if (monthNode == null) {
1079 monthNode = ctx.SHORTMONTH();
1082 String monthStr = monthNode.getText();
1084 stack.push(DateUtils.getMonthByName(monthStr));
1088 public void exitStrSeason(StrSeasonContext ctx) {
1089 if (ctx.exception != null) return;
1091 // Convert the season to a quarter number,
1092 // and push on the stack.
1094 Integer quarter = null;
1096 if (ctx.WINTER() != null) {
1099 else if (ctx.SPRING() != null) {
1102 else if (ctx.SUMMER() != null) {
1105 else if (ctx.FALL() != null) {
1109 stack.push(quarter);
1113 public void exitAllOrPartOf(AllOrPartOfContext ctx) {
1114 if (ctx.exception != null) return;
1116 // If a part was specified, it will have been
1117 // pushed on the stack in exitPartOf(). If not,
1118 // push null on the stack.
1120 if (ctx.partOf() == null) {
1126 public void exitPartOf(PartOfContext ctx) {
1127 if (ctx.exception != null) return;
1129 // Convert the token to a Part,
1130 // and push on the stack.
1134 if (ctx.EARLY() != null) {
1137 else if (ctx.MIDDLE() != null) {
1140 else if (ctx.LATE() != null) {
1148 public void exitEra(EraContext ctx) {
1149 if (ctx.exception != null) return;
1151 // Convert the token to an Era,
1152 // and push on the stack.
1156 if (ctx.BC() != null) {
1159 else if (ctx.AD() != null) {
1167 public void exitNumDayOfMonth(NumDayOfMonthContext ctx) {
1168 if (ctx.exception != null) return;
1170 // Convert the numeric string to an Integer,
1171 // and push on the stack.
1173 Integer dayOfMonth = new Integer(ctx.NUMBER().getText());
1175 if (dayOfMonth == 0 || dayOfMonth > 31) {
1176 throw new StructuredDateFormatException("unexpected day of month '" + ctx.NUMBER().getText() + "'");
1179 stack.push(dayOfMonth);
1183 public void exitNum(NumContext ctx) {
1184 if (ctx.exception != null) return;
1186 // Convert the numeric string to an Integer,
1187 // and push on the stack.
1189 Integer num = new Integer(ctx.NUMBER().getText());
1194 protected String getErrorMessage(RecognitionException re) {
1195 String message = "";
1197 Parser recognizer = (Parser) re.getRecognizer();
1198 TokenStream tokens = recognizer.getInputStream();
1200 if (re instanceof NoViableAltException) {
1201 NoViableAltException e = (NoViableAltException) re;
1202 Token startToken = e.getStartToken();
1203 String input = (startToken.getType() == Token.EOF ) ? "end of text" : quote(tokens.getText(startToken, e.getOffendingToken()));
1205 message = "no viable date format found at " + input;
1207 else if (re instanceof InputMismatchException) {
1208 InputMismatchException e = (InputMismatchException) re;
1209 message = "did not expect " + getTokenDisplayString(e.getOffendingToken()) + " while looking for " +
1210 e.getExpectedTokens().toString(recognizer.getTokenNames());
1212 else if (re instanceof FailedPredicateException) {
1213 FailedPredicateException e = (FailedPredicateException) re;
1214 String ruleName = recognizer.getRuleNames()[recognizer.getContext().getRuleIndex()];
1216 message = "failed predicate " + ruleName + ": " + e.getMessage();
1222 protected String quote(String text) {
1223 return "'" + text + "'";
1226 protected String getTokenDisplayString(Token token) {
1229 if (token == null) {
1230 string = "[no token]";
1233 String text = token.getText();
1236 if (token.getType() == Token.EOF ) {
1237 string = "end of text";
1240 string = "[" + token.getType() + "]";
1244 string = quote(text);
1251 protected String stripEndLetters(String input) {
1252 return input.replaceAll("[^\\d]+$", "");
1255 public static void main(String[] args) {
1256 StructuredDateEvaluator evaluator = new ANTLRStructuredDateEvaluator();
1258 for (String displayDate : args) {
1260 evaluator.evaluate(displayDate);
1261 } catch (StructuredDateFormatException e) {
1262 e.printStackTrace();