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.YearContext;
90 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.YearSpanningWinterContext;
93 * A StructuredDateEvaluator that uses an ANTLR parser to parse the display date,
94 * and an ANTLR listener to generate a structured date from the resulting parse
97 public class ANTLRStructuredDateEvaluator extends StructuredDateBaseListener implements StructuredDateEvaluator {
99 * The result of the evaluation.
101 protected StructuredDateInternal result;
104 * The operation stack. The parse listener methods that are implemented here
105 * pop input parameters off the stack, and push results back on to the stack.
107 protected Stack<Object> stack;
109 public ANTLRStructuredDateEvaluator() {
114 public StructuredDateInternal evaluate(String displayDate) throws StructuredDateFormatException {
115 stack = new Stack<Object>();
117 result = new StructuredDateInternal();
118 result.setDisplayDate(displayDate);
120 // Instantiate a parser from the lowercased display date, so that parsing will be
122 ANTLRInputStream inputStream = new ANTLRInputStream(displayDate.toLowerCase());
123 StructuredDateLexer lexer = new StructuredDateLexer(inputStream);
124 CommonTokenStream tokenStream = new CommonTokenStream(lexer);
125 StructuredDateParser parser = new StructuredDateParser(tokenStream);
127 // Don't try to recover from parse errors, just bail.
128 parser.setErrorHandler(new BailErrorStrategy());
130 // Don't print error messages to the console.
131 parser.removeErrorListeners();
133 // Generate our own custom error messages.
134 parser.addParseListener(this);
137 // Attempt to fulfill the oneDisplayDate rule of the grammar.
138 parser.oneDisplayDate();
140 catch(ParseCancellationException e) {
141 // ParseCancellationException is thrown by the BailErrorStrategy when there is a
142 // parse error, with the underlying RecognitionException as the cause.
143 RecognitionException re = (RecognitionException) e.getCause();
145 throw new StructuredDateFormatException(getErrorMessage(re), re);
148 // The parsing was successful. Return the result.
153 public void exitDisplayDate(DisplayDateContext ctx) {
154 if (ctx.exception != null) return;
156 Date latestDate = (Date) stack.pop();
157 Date earliestDate = (Date) stack.pop();
159 // If the earliest date and the latest date are the same, it's just a "single" date.
160 // There's no need to have the latest, so set it to null.
162 if (earliestDate.equals(latestDate)) {
166 result.setEarliestSingleDate(earliestDate);
167 result.setLatestDate(latestDate);
171 public void exitBeforeOrAfterDate(BeforeOrAfterDateContext ctx) {
172 if (ctx.exception != null) return;
174 Date latestDate = (Date) stack.pop();
175 Date earliestDate = (Date) stack.pop();
177 // Set null eras to the default.
179 if (earliestDate.getEra() == null) {
180 earliestDate.setEra(Date.DEFAULT_ERA);
183 if (latestDate.getEra() == null) {
184 latestDate.setEra(Date.DEFAULT_ERA);
187 // Finalize any deferred calculations.
189 if (latestDate instanceof DeferredDate) {
190 ((DeferredDate) latestDate).resolveDate();
193 if (earliestDate instanceof DeferredDate) {
194 ((DeferredDate) earliestDate).resolveDate();
197 // Calculate the earliest date or end date.
199 if (ctx.BEFORE() != null) {
200 latestDate = earliestDate;
201 earliestDate = DateUtils.getEarliestBeforeDate(earliestDate, latestDate);
203 else if (ctx.AFTER() != null) {
204 earliestDate = latestDate;
205 latestDate = DateUtils.getLatestAfterDate(earliestDate, latestDate);
208 stack.push(earliestDate);
209 stack.push(latestDate);
213 public void exitUncertainDate(UncertainDateContext ctx) {
214 if (ctx.exception != null) return;
216 Date latestDate = (Date) stack.pop();
217 Date earliestDate = (Date) stack.pop();
219 int earliestInterval = DateUtils.getCircaIntervalYears(earliestDate.getYear(), earliestDate.getEra());
220 int latestInterval = DateUtils.getCircaIntervalYears(latestDate.getYear(), latestDate.getEra());
222 // Express the circa interval as a qualifier.
224 // stack.push(earliestDate.withQualifier(QualifierType.MINUS, earliestInterval, QualifierUnit.YEARS));
225 // stack.push(latestDate.withQualifier(QualifierType.PLUS, latestInterval, QualifierUnit.YEARS));
229 // Express the circa interval as an offset calculated into the year.
231 DateUtils.subtractYears(earliestDate, earliestInterval);
232 DateUtils.addYears(latestDate, latestInterval);
234 stack.push(earliestDate);
235 stack.push(latestDate);
239 public void exitCertainDate(CertainDateContext ctx) {
240 if (ctx.exception != null) return;
242 Date latestDate = (Date) stack.pop();
243 Date earliestDate = (Date) stack.pop();
245 // Set null eras to the default.
247 if (earliestDate.getEra() == null) {
248 earliestDate.setEra(Date.DEFAULT_ERA);
251 if (latestDate.getEra() == null) {
252 latestDate.setEra(Date.DEFAULT_ERA);
255 // Finalize any deferred calculations.
257 if (latestDate instanceof DeferredDate) {
258 ((DeferredDate) latestDate).resolveDate();
261 if (earliestDate instanceof DeferredDate) {
262 ((DeferredDate) earliestDate).resolveDate();
265 stack.push(earliestDate);
266 stack.push(latestDate);
270 public void exitHyphenatedRange(HyphenatedRangeContext ctx) {
271 if (ctx.exception != null) return;
273 Date latestEndDate = (Date) stack.pop();
274 stack.pop(); // latestStartDate
275 stack.pop(); // earliestEndDate
276 Date earliestStartDate = (Date) stack.pop();
278 // If no era was explicitly specified for the first date,
279 // make it inherit the era of the second date.
281 if (earliestStartDate.getEra() == null && latestEndDate.getEra() != null) {
282 earliestStartDate.setEra(latestEndDate.getEra());
285 // Finalize any deferred calculations.
287 if (earliestStartDate instanceof DeferredDate) {
288 ((DeferredDate) earliestStartDate).resolveDate();
291 if (latestEndDate instanceof DeferredDate) {
292 ((DeferredDate) latestEndDate).resolveDate();
295 stack.push(earliestStartDate);
296 stack.push(latestEndDate);
300 public void exitNthCenturyRange(NthCenturyRangeContext ctx) {
301 if (ctx.exception != null) return;
303 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
304 Integer endN = (Integer) stack.pop();
305 Part endPart = (Part) stack.pop();
306 Integer startN = (Integer) stack.pop();
307 Part startPart = (Part) stack.pop();
310 era = Date.DEFAULT_ERA;
313 int startYear = DateUtils.nthCenturyToYear(startN);
314 int endYear = DateUtils.nthCenturyToYear(endN);
316 stack.push(startPart == null ? DateUtils.getCenturyStartDate(startYear, era) : DateUtils.getPartialCenturyStartDate(startYear, startPart, era));
317 stack.push(startPart == null ? DateUtils.getCenturyEndDate(startYear, era) : DateUtils.getPartialCenturyEndDate(startYear, startPart, era));
318 stack.push(endPart == null ? DateUtils.getCenturyStartDate(endYear, era) : DateUtils.getPartialCenturyStartDate(endYear, endPart, era));
319 stack.push(endPart == null ? DateUtils.getCenturyEndDate(endYear, era) : DateUtils.getPartialCenturyEndDate(endYear, endPart, era));
323 public void exitMonthInYearRange(MonthInYearRangeContext ctx) {
324 if (ctx.exception != null) return;
326 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
327 Integer year = (Integer) stack.pop();
328 Integer numMonthEnd = (Integer) stack.pop();
329 Integer numMonthStart = (Integer) stack.pop();
331 stack.push(new Date(year, numMonthStart, 1, era));
332 stack.push(new Date(year, numMonthStart, DateUtils.getDaysInMonth(numMonthStart, year, era), era));
333 stack.push(new Date(year, numMonthEnd, 1, era));
334 stack.push(new Date(year, numMonthEnd, DateUtils.getDaysInMonth(numMonthEnd, year, era), era));
338 public void exitQuarterInYearRange(QuarterInYearRangeContext ctx) {
339 if (ctx.exception != null) return;
341 Era era = (Era) stack.pop();
342 Integer year = (Integer) stack.pop();
343 Integer lastQuarter = (Integer) stack.pop();
344 Integer firstQuarter = (Integer) stack.pop();
346 stack.push(DateUtils.getQuarterYearStartDate(firstQuarter, year).withEra(era));
347 stack.push(DateUtils.getQuarterYearEndDate(firstQuarter, year, era).withEra(era));
348 stack.push(DateUtils.getQuarterYearStartDate(lastQuarter, year).withEra(era));
349 stack.push(DateUtils.getQuarterYearEndDate(lastQuarter, year, era).withEra(era));
353 public void exitStrDayInMonthRange(StrDayInMonthRangeContext ctx) {
354 if (ctx.exception != null) return;
356 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
357 Integer year = (Integer) stack.pop();
358 Integer dayOfMonthEnd = (Integer) stack.pop();
359 Integer dayOfMonthStart = (Integer) stack.pop();
360 Integer numMonth = (Integer) stack.pop();
362 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
363 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
364 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
365 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
369 public void exitNumDayInMonthRange(NumDayInMonthRangeContext ctx) {
370 if (ctx.exception != null) return;
372 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
373 Integer year = (Integer) stack.pop();
374 Integer dayOfMonthEnd = (Integer) stack.pop();
375 Integer dayOfMonthStart = (Integer) stack.pop();
376 Integer numMonth = (Integer) stack.pop();
378 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
379 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
380 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
381 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
385 public void exitDate(DateContext ctx) {
386 if (ctx.exception != null) return;
388 // Expect the canonical year-month-day-era ordering
389 // to be on the stack.
391 Era era = (Era) stack.pop();
392 Integer dayOfMonth = (Integer) stack.pop();
393 Integer numMonth = (Integer) stack.pop();
394 Integer year = (Integer) stack.pop();
396 // For the latest date we could either return null, or a copy of the earliest date,
397 // since the UI doesn't care. Use a copy of the earliest date, since it makes
398 // things easier here if we don't have to test for null up the tree.
400 stack.push(new Date(year, numMonth, dayOfMonth, era));
401 stack.push(new Date(year, numMonth, dayOfMonth, era));
405 public void exitNumDate(NumDateContext ctx) {
406 if (ctx.exception != null) return;
408 // This could either be year-month-day, or
409 // month-day-year. Try to determine which,
410 // and reorder the stack into the canonical
411 // year-month-day-era ordering.
413 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
414 Integer num3 = (Integer) stack.pop();
415 Integer num2 = (Integer) stack.pop();
416 Integer num1 = (Integer) stack.pop();
418 // Default to a month-day-year interpretation.
421 int dayOfMonth = num2;
424 if (DateUtils.isValidDate(num3, num1, num2, era)) {
425 // Interpreting as month-day-year produces a valid date. Go with it.
427 else if (DateUtils.isValidDate(num1, num2, num3, era)) {
428 // Interpreting as month-day-year doesn't produce a valid date, but
429 // year-month-day does. Go with year-month-day.
437 stack.push(numMonth);
438 stack.push(dayOfMonth);
443 public void exitStrDate(StrDateContext ctx) {
444 if (ctx.exception != null) return;
446 // Reorder the stack into a canonical ordering,
447 // year-month-day-era.
449 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
450 Integer year = (Integer) stack.pop();
451 Integer dayOfMonth = (Integer) stack.pop();
452 Integer numMonth = (Integer) stack.pop();
455 stack.push(numMonth);
456 stack.push(dayOfMonth);
461 public void exitInvStrDate(InvStrDateContext ctx) {
462 if (ctx.exception != null) return;
464 // Reorder the stack into a canonical ordering,
465 // year-month-day-era.
467 Integer dayOfMonth = (Integer) stack.pop();
468 Integer numMonth = (Integer) stack.pop();
469 Integer year = (Integer) stack.pop();
470 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
473 stack.push(numMonth);
474 stack.push(dayOfMonth);
479 public void exitDayFirstDate(DayFirstDateContext ctx) {
480 if (ctx.exception != null) return ;
482 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
483 Integer year = (Integer) stack.pop();
484 Integer month = (Integer) stack.pop();
485 Integer dayOfMonth = (Integer) stack.pop();
489 stack.push(dayOfMonth);
494 public void exitDayOrYearFirstDate(DayOrYearFirstDateContext ctx) {
495 if (ctx.exception != null) return;
497 Era era = (stack.size() == 3) ? null : (Era) stack.pop();
498 Integer year = (Integer) stack.pop();
499 Integer numMonth = (Integer) stack.pop();
500 Integer dayOfMonth = (Integer) stack.pop();
503 Integer num2 = dayOfMonth;
505 if (DateUtils.isValidDate(num1, numMonth, num2, era)) {
506 // Do nothing, it is already In the right format
507 } else if (DateUtils.isValidDate(num2, numMonth, num1, era)) {
508 // Then the first number is a year. Fix:
514 stack.push(numMonth);
515 stack.push(dayOfMonth);
518 if (dayOfMonth > 31 || dayOfMonth <= 0) {
519 throw new StructuredDateFormatException("unexpected day of month '" + Integer.toString(dayOfMonth) + "'");
522 throw new StructuredDateFormatException("unexpected year '" + Integer.toString(year) + "'");
527 public void exitInvStrDateEraLastDate(InvStrDateEraLastDateContext ctx) {
528 if (ctx.exception != null) return;
530 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
531 Integer dayOfMonth = (Integer) stack.pop();
532 Integer month = (Integer) stack.pop();
533 Integer year = (Integer) stack.pop();
537 stack.push(dayOfMonth);
542 public void exitMonth(MonthContext ctx) {
543 if (ctx.exception != null) return;
545 Era era = (Era) stack.pop();
546 Integer year = (Integer) stack.pop();
547 Integer numMonth = (Integer) stack.pop();
549 stack.push(new Date(year, numMonth, 1, era));
550 stack.push(new Date(year, numMonth, DateUtils.getDaysInMonth(numMonth, year, era), era));
554 public void exitMonthYear(MonthYearContext ctx) {
555 if (ctx.exception != null) return;
557 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
563 public void exitInvMonthYear(InvMonthYearContext ctx) {
564 if (ctx.exception != null) return;
566 // Invert the arguments.
568 Integer numMonth = (Integer) stack.pop();
569 Integer year = (Integer) stack.pop();
570 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
572 stack.push(numMonth);
578 public void exitYearSpanningWinter(YearSpanningWinterContext ctx) {
579 if (ctx.exception != null) return;
581 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
582 Integer endYear = (Integer) stack.pop();
583 Integer startYear = (Integer) stack.pop();
585 stack.push(new Date(startYear, 12, 1).withEra(era));
586 stack.push(DateUtils.getQuarterYearEndDate(1, endYear, era).withEra(era));
590 public void exitPartialYear(PartialYearContext ctx) {
591 if (ctx.exception != null) return;
593 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
594 Integer year = (Integer) stack.pop();
595 Part part = (Part) stack.pop();
597 stack.push(DateUtils.getPartialYearStartDate(part, year).withEra(era));
598 stack.push(DateUtils.getPartialYearEndDate(part, year, era).withEra(era));
602 public void exitQuarterYear(QuarterYearContext ctx) {
603 if (ctx.exception != null) return;
605 Era era = (Era) stack.pop();
606 Integer year = (Integer) stack.pop();
607 Integer quarter = (Integer) stack.pop();
609 stack.push(DateUtils.getQuarterYearStartDate(quarter, year).withEra(era));
610 stack.push(DateUtils.getQuarterYearEndDate(quarter, year, era).withEra(era));
614 public void exitHalfYear(HalfYearContext ctx) {
615 if (ctx.exception != null) return;
617 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
618 Integer year = (Integer) stack.pop();
619 Integer half = (Integer) stack.pop();
621 stack.push(DateUtils.getHalfYearStartDate(half, year).withEra(era));
622 stack.push(DateUtils.getHalfYearEndDate(half, year, era).withEra(era));
626 public void exitInvSeasonYear(InvSeasonYearContext ctx) {
627 if (ctx.exception != null) return;
629 // Invert the arguments.
631 Integer quarter = (Integer) stack.pop();
632 Integer year = (Integer) stack.pop();
633 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
641 public void exitSeasonYear(SeasonYearContext ctx) {
642 if (ctx.exception != null) return;
644 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
650 public void exitYear(YearContext ctx) {
651 if (ctx.exception != null) return;
653 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
654 Integer year = (Integer) stack.pop();
656 stack.push(new Date(year, 1, 1, era));
657 stack.push(new Date(year, 12, 31, era));
661 public void exitPartialDecade(PartialDecadeContext ctx) {
662 if (ctx.exception != null) return;
664 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
665 Integer year = (Integer) stack.pop();
666 Part part = (Part) stack.pop();
669 // If the era was explicitly specified, the start and end years
670 // may be calculated now.
672 stack.push(DateUtils.getPartialDecadeStartDate(year, part, era));
673 stack.push(DateUtils.getPartialDecadeEndDate(year, part, era));
676 // If the era was not explicitly specified, the start and end years
677 // can't be calculated yet. The calculation must be deferred until
678 // later. For example, this partial decade may be the start of a hyphenated
679 // range, where the era will be inherited from the era of the end of
680 // the range; this era won't be known until farther up the parse tree,
681 // when both sides of the range will have been parsed.
683 stack.push(new DeferredPartialDecadeStartDate(year, part));
684 stack.push(new DeferredPartialDecadeEndDate(year, part));
689 public void exitDecade(DecadeContext ctx) {
690 if (ctx.exception != null) return;
692 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
693 Integer year = (Integer) stack.pop();
695 // Calculate the start and end year of the decade, which depends on the era.
698 // If the era was explicitly specified, the start and end years
699 // may be calculated now.
701 stack.push(DateUtils.getDecadeStartDate(year, era));
702 stack.push(DateUtils.getDecadeEndDate(year, era));
705 // If the era was not explicitly specified, the start and end years
706 // can't be calculated yet. The calculation must be deferred until
707 // later. For example, this decade may be the start of a hyphenated
708 // range, where the era will be inherited from the era of the end of
709 // the range; this era won't be known until farther up the parse tree,
710 // when both sides of the range will have been parsed.
712 stack.push(new DeferredDecadeStartDate(year));
713 stack.push(new DeferredDecadeEndDate(year));
718 public void exitPartialCentury(PartialCenturyContext ctx) {
719 if (ctx.exception != null) return;
721 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
722 Integer year = (Integer) stack.pop();
723 Part part = (Part) stack.pop();
726 // If the era was explicitly specified, the start and end years
727 // may be calculated now.
729 stack.push(DateUtils.getPartialCenturyStartDate(year, part, era));
730 stack.push(DateUtils.getPartialCenturyEndDate(year, part, era));
733 // If the era was not explicitly specified, the start and end years
734 // can't be calculated yet. The calculation must be deferred until
735 // later. For example, this partial century may be the start of a hyphenated
736 // range, where the era will be inherited from the era of the end of
737 // the range; this era won't be known until farther up the parse tree,
738 // when both sides of the range will have been parsed.
740 stack.push(new DeferredPartialCenturyStartDate(year, part));
741 stack.push(new DeferredPartialCenturyEndDate(year, part));
746 public void exitQuarterCentury(QuarterCenturyContext ctx) {
747 if (ctx.exception != null) return;
749 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
750 Integer year = (Integer) stack.pop();
751 Integer quarter = (Integer) stack.pop();
754 // If the era was explicitly specified, the start and end years
755 // may be calculated now.
757 stack.push(DateUtils.getQuarterCenturyStartDate(year, quarter, era));
758 stack.push(DateUtils.getQuarterCenturyEndDate(year, quarter, era));
761 // If the era was not explicitly specified, the start and end years
762 // can't be calculated yet. The calculation must be deferred until
763 // later. For example, this century may be the start of a hyphenated
764 // range, where the era will be inherited from the era of the end of
765 // the range; this era won't be known until farther up the parse tree,
766 // when both sides of the range will have been parsed.
768 stack.push(new DeferredQuarterCenturyStartDate(year, quarter));
769 stack.push(new DeferredQuarterCenturyEndDate(year, quarter));
774 public void exitHalfCentury(HalfCenturyContext ctx) {
775 if (ctx.exception != null) return;
777 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
778 Integer year = (Integer) stack.pop();
779 Integer half = (Integer) stack.pop();
782 // If the era was explicitly specified, the start and end years
783 // may be calculated now.
785 stack.push(DateUtils.getHalfCenturyStartDate(year, half, era));
786 stack.push(DateUtils.getHalfCenturyEndDate(year, half, era));
789 // If the era was not explicitly specified, the start and end years
790 // can't be calculated yet. The calculation must be deferred until
791 // later. For example, this half century may be the start of a hyphenated
792 // range, where the era will be inherited from the era of the end of
793 // the range; this era won't be known until farther up the parse tree,
794 // when both sides of the range will have been parsed.
796 stack.push(new DeferredHalfCenturyStartDate(year, half));
797 stack.push(new DeferredHalfCenturyEndDate(year, half));
802 public void exitCentury(CenturyContext ctx) {
803 if (ctx.exception != null) return;
805 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
806 Integer year = (Integer) stack.pop();
809 // If the era was explicitly specified, the start and end years
810 // may be calculated now.
812 stack.push(DateUtils.getCenturyStartDate(year, era));
813 stack.push(DateUtils.getCenturyEndDate(year, era));
816 // If the era was not explicitly specified, the start and end years
817 // can't be calculated yet. The calculation must be deferred until
818 // later. For example, this quarter century may be the start of a hyphenated
819 // range, where the era will be inherited from the era of the end of
820 // the range; this era won't be known until farther up the parse tree,
821 // when both sides of the range will have been parsed.
823 stack.push(new DeferredCenturyStartDate(year));
824 stack.push(new DeferredCenturyEndDate(year));
829 public void exitMillennium(MillenniumContext ctx) {
830 if (ctx.exception != null) return;
832 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
833 Integer n = (Integer) stack.pop();
836 // If the era was explicitly specified, the start and end years
837 // may be calculated now.
839 stack.push(DateUtils.getMillenniumStartDate(n, era));
840 stack.push(DateUtils.getMillenniumEndDate(n, era));
843 // If the era was not explicitly specified, the start and end years
844 // can't be calculated yet. The calculation must be deferred until
845 // later. For example, this millennium may be the start of a hyphenated
846 // range, where the era will be inherited from the era of the end of
847 // the range; this era won't be known until farther up the parse tree,
848 // when both sides of the range will have been parsed.
850 stack.push(new DeferredMillenniumStartDate(n));
851 stack.push(new DeferredMillenniumEndDate(n));
856 public void exitStrCentury(StrCenturyContext ctx) {
857 if (ctx.exception != null) return;
859 Integer n = (Integer) stack.pop();
861 // Convert the nth number to a year number,
862 // and push on the stack.
864 Integer year = DateUtils.nthCenturyToYear(n);
870 public void exitNumCentury(NumCenturyContext ctx) {
871 if (ctx.exception != null) return;
873 // Convert the string to a number,
874 // and push on the stack.
876 Integer year = new Integer(stripEndLetters(ctx.HUNDREDS().getText()));
879 throw new StructuredDateFormatException("unexpected century '" + ctx.HUNDREDS().getText() + "'");
886 public void exitNumDecade(NumDecadeContext ctx) {
887 if (ctx.exception != null) return;
889 // Convert the string to a number,
890 // and push on the stack.
892 Integer year = new Integer(stripEndLetters(ctx.TENS().getText()));
895 throw new StructuredDateFormatException("unexpected decade '" + ctx.TENS().getText() + "'");
902 public void exitNumYear(NumYearContext ctx) {
903 if (ctx.exception != null) return;
905 // Convert the string to a number,
906 // and push on the stack.
908 Integer year = new Integer(ctx.NUMBER().getText());
911 throw new StructuredDateFormatException("unexpected year '" + ctx.NUMBER().getText() + "'");
918 public void exitNumMonth(NumMonthContext ctx) {
919 if (ctx.exception != null) return;
921 // Convert the string a number,
922 // and push on the stack.
924 Integer month = new Integer(ctx.NUMBER().getText());
926 if (month < 1 || month > 12) {
927 throw new StructuredDateFormatException("unexpected month '" + ctx.NUMBER().getText() + "'");
934 public void exitNthHalf(NthHalfContext ctx) {
935 if (ctx.exception != null) return;
937 // Convert LAST to a number (the last half
938 // is the 2nd). If this rule matched the
939 // alternative with nth instead of LAST,
940 // the nth handler will already have pushed
941 // a number on the stack.
943 if (ctx.LAST() != null) {
944 stack.push(new Integer(2));
947 // Check for a valid half.
949 Integer n = (Integer) stack.peek();
951 if (n < 1 || n > 2) {
952 throw new StructuredDateFormatException("unexpected half '" + n + "'");
958 public void exitNthQuarterInYearRange(NthQuarterInYearRangeContext ctx) {
959 if (ctx.exception != null) return;
961 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
967 public void exitStrSeasonInYearRange(StrSeasonInYearRangeContext ctx) {
968 if (ctx.exception != null) return;
970 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
977 public void exitNthQuarterYear(NthQuarterYearContext ctx) {
979 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
985 public void exitNthQuarter(NthQuarterContext ctx) {
986 if (ctx.exception != null) return;
988 // Convert LAST to a number (the last quarter
989 // is the 4th). 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(4));
998 // Check for a valid quarter.
1000 Integer n = (Integer) stack.peek();
1002 if (n < 1 || n > 4) {
1003 throw new StructuredDateFormatException("unexpected quarter '" + n + "'");
1008 public void exitNth(NthContext ctx) {
1009 if (ctx.exception != null) return;
1011 // Convert the string to a number,
1012 // and push on the stack.
1016 if (ctx.NTHSTR() != null) {
1017 n = new Integer(stripEndLetters(ctx.NTHSTR().getText()));
1019 else if (ctx.FIRST() != null) {
1022 else if (ctx.SECOND() != null) {
1025 else if (ctx.THIRD() != null) {
1028 else if (ctx.FOURTH() != null) {
1036 public void exitStrMonth(StrMonthContext ctx) {
1037 if (ctx.exception != null) return;
1039 // Convert the month name to a number,
1040 // and push on the stack.
1042 TerminalNode monthNode = ctx.MONTH();
1044 if (monthNode == null) {
1045 monthNode = ctx.SHORTMONTH();
1048 String monthStr = monthNode.getText();
1050 stack.push(DateUtils.getMonthByName(monthStr));
1054 public void exitStrSeason(StrSeasonContext ctx) {
1055 if (ctx.exception != null) return;
1057 // Convert the season to a quarter number,
1058 // and push on the stack.
1060 Integer quarter = null;
1062 if (ctx.WINTER() != null) {
1065 else if (ctx.SPRING() != null) {
1068 else if (ctx.SUMMER() != null) {
1071 else if (ctx.FALL() != null) {
1075 stack.push(quarter);
1079 public void exitAllOrPartOf(AllOrPartOfContext ctx) {
1080 if (ctx.exception != null) return;
1082 // If a part was specified, it will have been
1083 // pushed on the stack in exitPartOf(). If not,
1084 // push null on the stack.
1086 if (ctx.partOf() == null) {
1092 public void exitPartOf(PartOfContext ctx) {
1093 if (ctx.exception != null) return;
1095 // Convert the token to a Part,
1096 // and push on the stack.
1100 if (ctx.EARLY() != null) {
1103 else if (ctx.MIDDLE() != null) {
1106 else if (ctx.LATE() != null) {
1114 public void exitEra(EraContext ctx) {
1115 if (ctx.exception != null) return;
1117 // Convert the token to an Era,
1118 // and push on the stack.
1122 if (ctx.BC() != null) {
1125 else if (ctx.AD() != null) {
1133 public void exitNumDayOfMonth(NumDayOfMonthContext ctx) {
1134 if (ctx.exception != null) return;
1136 // Convert the numeric string to an Integer,
1137 // and push on the stack.
1139 Integer dayOfMonth = new Integer(ctx.NUMBER().getText());
1141 if (dayOfMonth == 0 || dayOfMonth > 31) {
1142 throw new StructuredDateFormatException("unexpected day of month '" + ctx.NUMBER().getText() + "'");
1145 stack.push(dayOfMonth);
1149 public void exitNum(NumContext ctx) {
1150 if (ctx.exception != null) return;
1152 // Convert the numeric string to an Integer,
1153 // and push on the stack.
1155 Integer num = new Integer(ctx.NUMBER().getText());
1160 protected String getErrorMessage(RecognitionException re) {
1161 String message = "";
1163 Parser recognizer = (Parser) re.getRecognizer();
1164 TokenStream tokens = recognizer.getInputStream();
1166 if (re instanceof NoViableAltException) {
1167 NoViableAltException e = (NoViableAltException) re;
1168 Token startToken = e.getStartToken();
1169 String input = (startToken.getType() == Token.EOF ) ? "end of text" : quote(tokens.getText(startToken, e.getOffendingToken()));
1171 message = "no viable date format found at " + input;
1173 else if (re instanceof InputMismatchException) {
1174 InputMismatchException e = (InputMismatchException) re;
1175 message = "did not expect " + getTokenDisplayString(e.getOffendingToken()) + " while looking for " +
1176 e.getExpectedTokens().toString(recognizer.getTokenNames());
1178 else if (re instanceof FailedPredicateException) {
1179 FailedPredicateException e = (FailedPredicateException) re;
1180 String ruleName = recognizer.getRuleNames()[recognizer.getContext().getRuleIndex()];
1182 message = "failed predicate " + ruleName + ": " + e.getMessage();
1188 protected String quote(String text) {
1189 return "'" + text + "'";
1192 protected String getTokenDisplayString(Token token) {
1195 if (token == null) {
1196 string = "[no token]";
1199 String text = token.getText();
1202 if (token.getType() == Token.EOF ) {
1203 string = "end of text";
1206 string = "[" + token.getType() + "]";
1210 string = quote(text);
1217 protected String stripEndLetters(String input) {
1218 return input.replaceAll("[^\\d]+$", "");
1221 public static void main(String[] args) {
1222 StructuredDateEvaluator evaluator = new ANTLRStructuredDateEvaluator();
1224 for (String displayDate : args) {
1226 evaluator.evaluate(displayDate);
1227 } catch (StructuredDateFormatException e) {
1228 e.printStackTrace();