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.DecadeContext;
45 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.DisplayDateContext;
46 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.EraContext;
47 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.HalfCenturyContext;
48 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.HalfYearContext;
49 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.HyphenatedRangeContext;
50 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.InvMonthYearContext;
51 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.InvSeasonYearContext;
52 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.InvStrDateContext;
53 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.MillenniumContext;
54 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.MonthContext;
55 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.MonthInYearRangeContext;
56 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthCenturyRangeContext;
57 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthContext;
58 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthHalfContext;
59 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthQuarterContext;
60 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumCenturyContext;
61 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumContext;
62 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDateContext;
63 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDayInMonthRangeContext;
64 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDayOfMonthContext;
65 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDecadeContext;
66 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumMonthContext;
67 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumYearContext;
68 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartOfContext;
69 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartialCenturyContext;
70 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartialDecadeContext;
71 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartialYearContext;
72 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterCenturyContext;
73 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterInYearRangeContext;
74 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterYearContext;
75 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrCenturyContext;
76 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrDateContext;
77 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrDayInMonthRangeContext;
78 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrMonthContext;
79 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrSeasonContext;
80 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.UncertainDateContext;
81 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.YearContext;
82 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.YearSpanningWinterContext;
85 * A StructuredDateEvaluator that uses an ANTLR parser to parse the display date,
86 * and an ANTLR listener to generate a structured date from the resulting parse
89 public class ANTLRStructuredDateEvaluator extends StructuredDateBaseListener implements StructuredDateEvaluator {
91 * The result of the evaluation.
93 protected StructuredDateInternal result;
96 * The operation stack. The parse listener methods that are implemented here
97 * pop input parameters off the stack, and push results back on to the stack.
99 protected Stack<Object> stack;
101 public ANTLRStructuredDateEvaluator() {
106 public StructuredDateInternal evaluate(String displayDate) throws StructuredDateFormatException {
107 stack = new Stack<Object>();
109 result = new StructuredDateInternal();
110 result.setDisplayDate(displayDate);
112 // Instantiate a parser from the lowercased display date, so that parsing will be
114 ANTLRInputStream inputStream = new ANTLRInputStream(displayDate.toLowerCase());
115 StructuredDateLexer lexer = new StructuredDateLexer(inputStream);
116 CommonTokenStream tokenStream = new CommonTokenStream(lexer);
117 StructuredDateParser parser = new StructuredDateParser(tokenStream);
119 // Don't try to recover from parse errors, just bail.
120 parser.setErrorHandler(new BailErrorStrategy());
122 // Don't print error messages to the console.
123 parser.removeErrorListeners();
125 // Generate our own custom error messages.
126 parser.addParseListener(this);
129 // Attempt to fulfill the oneDisplayDate rule of the grammar.
130 parser.oneDisplayDate();
132 catch(ParseCancellationException e) {
133 // ParseCancellationException is thrown by the BailErrorStrategy when there is a
134 // parse error, with the underlying RecognitionException as the cause.
135 RecognitionException re = (RecognitionException) e.getCause();
137 throw new StructuredDateFormatException(getErrorMessage(re), re);
140 // The parsing was successful. Return the result.
145 public void exitDisplayDate(DisplayDateContext ctx) {
146 if (ctx.exception != null) return;
148 Date latestDate = (Date) stack.pop();
149 Date earliestDate = (Date) stack.pop();
151 // If the earliest date and the latest date are the same, it's just a "single" date.
152 // There's no need to have the latest, so set it to null.
154 if (earliestDate.equals(latestDate)) {
158 result.setEarliestSingleDate(earliestDate);
159 result.setLatestDate(latestDate);
163 public void exitBeforeOrAfterDate(BeforeOrAfterDateContext ctx) {
164 if (ctx.exception != null) return;
166 Date latestDate = (Date) stack.pop();
167 Date earliestDate = (Date) stack.pop();
169 // Set null eras to the default.
171 if (earliestDate.getEra() == null) {
172 earliestDate.setEra(Date.DEFAULT_ERA);
175 if (latestDate.getEra() == null) {
176 latestDate.setEra(Date.DEFAULT_ERA);
179 // Finalize any deferred calculations.
181 if (latestDate instanceof DeferredDate) {
182 ((DeferredDate) latestDate).resolveDate();
185 if (earliestDate instanceof DeferredDate) {
186 ((DeferredDate) earliestDate).resolveDate();
189 // Calculate the earliest date or end date.
191 if (ctx.BEFORE() != null) {
192 latestDate = earliestDate;
193 earliestDate = DateUtils.getEarliestBeforeDate(earliestDate, latestDate);
195 else if (ctx.AFTER() != null) {
196 earliestDate = latestDate;
197 latestDate = DateUtils.getLatestAfterDate(earliestDate, latestDate);
200 stack.push(earliestDate);
201 stack.push(latestDate);
205 public void exitUncertainDate(UncertainDateContext ctx) {
206 if (ctx.exception != null) return;
208 Date latestDate = (Date) stack.pop();
209 Date earliestDate = (Date) stack.pop();
211 int earliestInterval = DateUtils.getCircaIntervalYears(earliestDate.getYear(), earliestDate.getEra());
212 int latestInterval = DateUtils.getCircaIntervalYears(latestDate.getYear(), latestDate.getEra());
214 // Express the circa interval as a qualifier.
216 // stack.push(earliestDate.withQualifier(QualifierType.MINUS, earliestInterval, QualifierUnit.YEARS));
217 // stack.push(latestDate.withQualifier(QualifierType.PLUS, latestInterval, QualifierUnit.YEARS));
221 // Express the circa interval as an offset calculated into the year.
223 DateUtils.subtractYears(earliestDate, earliestInterval);
224 DateUtils.addYears(latestDate, latestInterval);
226 stack.push(earliestDate);
227 stack.push(latestDate);
231 public void exitCertainDate(CertainDateContext ctx) {
232 if (ctx.exception != null) return;
234 Date latestDate = (Date) stack.pop();
235 Date earliestDate = (Date) stack.pop();
237 // Set null eras to the default.
239 if (earliestDate.getEra() == null) {
240 earliestDate.setEra(Date.DEFAULT_ERA);
243 if (latestDate.getEra() == null) {
244 latestDate.setEra(Date.DEFAULT_ERA);
247 // Finalize any deferred calculations.
249 if (latestDate instanceof DeferredDate) {
250 ((DeferredDate) latestDate).resolveDate();
253 if (earliestDate instanceof DeferredDate) {
254 ((DeferredDate) earliestDate).resolveDate();
257 stack.push(earliestDate);
258 stack.push(latestDate);
262 public void exitHyphenatedRange(HyphenatedRangeContext ctx) {
263 if (ctx.exception != null) return;
265 Date latestEndDate = (Date) stack.pop();
266 stack.pop(); // latestStartDate
267 stack.pop(); // earliestEndDate
268 Date earliestStartDate = (Date) stack.pop();
270 // If no era was explicitly specified for the first date,
271 // make it inherit the era of the second date.
273 if (earliestStartDate.getEra() == null && latestEndDate.getEra() != null) {
274 earliestStartDate.setEra(latestEndDate.getEra());
277 // Finalize any deferred calculations.
279 if (earliestStartDate instanceof DeferredDate) {
280 ((DeferredDate) earliestStartDate).resolveDate();
283 if (latestEndDate instanceof DeferredDate) {
284 ((DeferredDate) latestEndDate).resolveDate();
287 stack.push(earliestStartDate);
288 stack.push(latestEndDate);
292 public void exitNthCenturyRange(NthCenturyRangeContext ctx) {
293 if (ctx.exception != null) return;
295 Era era = (Era) stack.pop();
296 Integer endN = (Integer) stack.pop();
297 Part endPart = (Part) stack.pop();
298 Integer startN = (Integer) stack.pop();
299 Part startPart = (Part) stack.pop();
302 era = Date.DEFAULT_ERA;
305 int startYear = DateUtils.nthCenturyToYear(startN);
306 int endYear = DateUtils.nthCenturyToYear(endN);
308 stack.push(startPart == null ? DateUtils.getCenturyStartDate(startYear, era) : DateUtils.getPartialCenturyStartDate(startYear, startPart, era));
309 stack.push(startPart == null ? DateUtils.getCenturyEndDate(startYear, era) : DateUtils.getPartialCenturyEndDate(startYear, startPart, era));
310 stack.push(endPart == null ? DateUtils.getCenturyStartDate(endYear, era) : DateUtils.getPartialCenturyStartDate(endYear, endPart, era));
311 stack.push(endPart == null ? DateUtils.getCenturyEndDate(endYear, era) : DateUtils.getPartialCenturyEndDate(endYear, endPart, era));
315 public void exitMonthInYearRange(MonthInYearRangeContext ctx) {
316 if (ctx.exception != null) return;
318 Era era = (Era) stack.pop();
319 Integer year = (Integer) stack.pop();
320 Integer numMonthEnd = (Integer) stack.pop();
321 Integer numMonthStart = (Integer) stack.pop();
323 stack.push(new Date(year, numMonthStart, 1, era));
324 stack.push(new Date(year, numMonthStart, DateUtils.getDaysInMonth(numMonthStart, year, era), era));
325 stack.push(new Date(year, numMonthEnd, 1, era));
326 stack.push(new Date(year, numMonthEnd, DateUtils.getDaysInMonth(numMonthEnd, year, era), era));
330 public void exitQuarterInYearRange(QuarterInYearRangeContext ctx) {
331 if (ctx.exception != null) return;
333 Era era = (Era) stack.pop();
334 Integer year = (Integer) stack.pop();
335 Integer lastQuarter = (Integer) stack.pop();
336 Integer firstQuarter = (Integer) stack.pop();
338 stack.push(DateUtils.getQuarterYearStartDate(firstQuarter, year).withEra(era));
339 stack.push(DateUtils.getQuarterYearEndDate(firstQuarter, year, era).withEra(era));
340 stack.push(DateUtils.getQuarterYearStartDate(lastQuarter, year).withEra(era));
341 stack.push(DateUtils.getQuarterYearEndDate(lastQuarter, year, era).withEra(era));
345 public void exitStrDayInMonthRange(StrDayInMonthRangeContext ctx) {
346 if (ctx.exception != null) return;
348 Era era = (Era) stack.pop();
349 Integer year = (Integer) stack.pop();
350 Integer dayOfMonthEnd = (Integer) stack.pop();
351 Integer dayOfMonthStart = (Integer) stack.pop();
352 Integer numMonth = (Integer) stack.pop();
354 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
355 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
356 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
357 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
361 public void exitNumDayInMonthRange(NumDayInMonthRangeContext ctx) {
362 if (ctx.exception != null) return;
364 Era era = (Era) stack.pop();
365 Integer year = (Integer) stack.pop();
366 Integer dayOfMonthEnd = (Integer) stack.pop();
367 Integer dayOfMonthStart = (Integer) stack.pop();
368 Integer numMonth = (Integer) stack.pop();
370 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
371 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
372 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
373 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
377 public void exitDate(DateContext ctx) {
378 if (ctx.exception != null) return;
380 // Expect the canonical year-month-day-era ordering
381 // to be on the stack.
383 Era era = (Era) stack.pop();
384 Integer dayOfMonth = (Integer) stack.pop();
385 Integer numMonth = (Integer) stack.pop();
386 Integer year = (Integer) stack.pop();
388 // For the latest date we could either return null, or a copy of the earliest date,
389 // since the UI doesn't care. Use a copy of the earliest date, since it makes
390 // things easier here if we don't have to test for null up the tree.
392 stack.push(new Date(year, numMonth, dayOfMonth, era));
393 stack.push(new Date(year, numMonth, dayOfMonth, era));
398 public void exitNumDate(NumDateContext ctx) {
399 if (ctx.exception != null) return;
401 // This could either be year-month-day, or
402 // month-day-year. Try to determine which,
403 // and reorder the stack into the canonical
404 // year-month-day-era ordering.
406 Era era = (Era) stack.pop();
407 Integer num3 = (Integer) stack.pop();
408 Integer num2 = (Integer) stack.pop();
409 Integer num1 = (Integer) stack.pop();
411 // Default to a month-day-year interpretation.
414 int dayOfMonth = num2;
417 if (DateUtils.isValidDate(num3, num1, num2, era)) {
418 // Interpreting as month-day-year produces a valid date. Go with it.
420 else if (DateUtils.isValidDate(num1, num2, num3, era)) {
421 // Interpreting as month-day-year doesn't produce a valid date, but
422 // year-month-day does. Go with year-month-day.
430 stack.push(numMonth);
431 stack.push(dayOfMonth);
436 public void exitStrDate(StrDateContext ctx) {
437 if (ctx.exception != null) return;
439 // Reorder the stack into a canonical ordering,
440 // year-month-day-era.
442 Era era = (Era) stack.pop();
443 Integer year = (Integer) stack.pop();
444 Integer dayOfMonth = (Integer) stack.pop();
445 Integer numMonth = (Integer) stack.pop();
448 stack.push(numMonth);
449 stack.push(dayOfMonth);
454 public void exitInvStrDate(InvStrDateContext ctx) {
455 if (ctx.exception != null) return;
457 // Reorder the stack into a canonical ordering,
458 // year-month-day-era.
461 boolean eraLast = stack.peek() instanceof Integer;
470 num1 = (Integer) stack.pop(); // year or day
471 num2 = (Integer) stack.pop(); // month
472 num3 = (Integer) stack.pop(); // year or day
473 era = (Era) stack.pop(); // era...
475 era = (Era) stack.pop(); // damn eras
476 num1 = (Integer) stack.pop(); // year or day
477 num2 = (Integer) stack.pop(); // month
478 num3 = (Integer) stack.pop(); // day
481 Integer dayOfMonth = num1;
482 Integer numMonth = num2;
485 if (DateUtils.isValidDate(num3, num2, num1, era)) {
486 // Do nothing, already in the right format
487 } else if (DateUtils.isValidDate(num1, num2, num3, era)) {
493 stack.push(numMonth);
494 stack.push(dayOfMonth);
499 public void exitMonth(MonthContext ctx) {
500 if (ctx.exception != null) return;
502 Era era = (Era) stack.pop();
503 Integer year = (Integer) stack.pop();
504 Integer numMonth = (Integer) stack.pop();
506 stack.push(new Date(year, numMonth, 1, era));
507 stack.push(new Date(year, numMonth, DateUtils.getDaysInMonth(numMonth, year, era), era));
511 public void exitInvMonthYear(InvMonthYearContext ctx) {
512 if (ctx.exception != null) return;
514 // Invert the arguments.
516 Integer numMonth = (Integer) stack.pop();
517 Integer year = (Integer) stack.pop();
518 Era era = (Era) stack.pop();
520 stack.push(numMonth);
526 public void exitYearSpanningWinter(YearSpanningWinterContext ctx) {
527 if (ctx.exception != null) return;
529 Era era = (Era) stack.pop();
530 Integer endYear = (Integer) stack.pop();
531 Integer startYear = (Integer) stack.pop();
533 stack.push(new Date(startYear, 12, 1).withEra(era));
534 stack.push(DateUtils.getQuarterYearEndDate(1, endYear, era).withEra(era));
538 public void exitPartialYear(PartialYearContext ctx) {
539 if (ctx.exception != null) return;
541 Era era = (Era) stack.pop();
542 Integer year = (Integer) stack.pop();
543 Part part = (Part) stack.pop();
545 stack.push(DateUtils.getPartialYearStartDate(part, year).withEra(era));
546 stack.push(DateUtils.getPartialYearEndDate(part, year, era).withEra(era));
550 public void exitQuarterYear(QuarterYearContext ctx) {
551 if (ctx.exception != null) return;
553 Era era = (Era) stack.pop();
554 Integer year = (Integer) stack.pop();
555 Integer quarter = (Integer) stack.pop();
557 stack.push(DateUtils.getQuarterYearStartDate(quarter, year).withEra(era));
558 stack.push(DateUtils.getQuarterYearEndDate(quarter, year, era).withEra(era));
562 public void exitHalfYear(HalfYearContext ctx) {
563 if (ctx.exception != null) return;
565 Era era = (Era) stack.pop();
566 Integer year = (Integer) stack.pop();
567 Integer half = (Integer) stack.pop();
569 stack.push(DateUtils.getHalfYearStartDate(half, year).withEra(era));
570 stack.push(DateUtils.getHalfYearEndDate(half, year, era).withEra(era));
574 public void exitInvSeasonYear(InvSeasonYearContext ctx) {
575 if (ctx.exception != null) return;
577 // Invert the arguments.
579 Integer quarter = (Integer) stack.pop();
580 Integer year = (Integer) stack.pop();
581 Era era = (Era) stack.pop();
589 public void exitYear(YearContext ctx) {
590 if (ctx.exception != null) return;
592 Era era = (Era) stack.pop();
593 Integer year = (Integer) stack.pop();
595 stack.push(new Date(year, 1, 1, era));
596 stack.push(new Date(year, 12, 31, era));
600 public void exitPartialDecade(PartialDecadeContext ctx) {
601 if (ctx.exception != null) return;
603 Era era = (Era) stack.pop();
604 Integer year = (Integer) stack.pop();
605 Part part = (Part) stack.pop();
608 // If the era was explicitly specified, the start and end years
609 // may be calculated now.
611 stack.push(DateUtils.getPartialDecadeStartDate(year, part, era));
612 stack.push(DateUtils.getPartialDecadeEndDate(year, part, era));
615 // If the era was not explicitly specified, the start and end years
616 // can't be calculated yet. The calculation must be deferred until
617 // later. For example, this partial decade may be the start of a hyphenated
618 // range, where the era will be inherited from the era of the end of
619 // the range; this era won't be known until farther up the parse tree,
620 // when both sides of the range will have been parsed.
622 stack.push(new DeferredPartialDecadeStartDate(year, part));
623 stack.push(new DeferredPartialDecadeEndDate(year, part));
628 public void exitDecade(DecadeContext ctx) {
629 if (ctx.exception != null) return;
631 Era era = (Era) stack.pop();
632 Integer year = (Integer) stack.pop();
634 // Calculate the start and end year of the decade, which depends on the era.
637 // If the era was explicitly specified, the start and end years
638 // may be calculated now.
640 stack.push(DateUtils.getDecadeStartDate(year, era));
641 stack.push(DateUtils.getDecadeEndDate(year, era));
644 // If the era was not explicitly specified, the start and end years
645 // can't be calculated yet. The calculation must be deferred until
646 // later. For example, this decade may be the start of a hyphenated
647 // range, where the era will be inherited from the era of the end of
648 // the range; this era won't be known until farther up the parse tree,
649 // when both sides of the range will have been parsed.
651 stack.push(new DeferredDecadeStartDate(year));
652 stack.push(new DeferredDecadeEndDate(year));
657 public void exitPartialCentury(PartialCenturyContext ctx) {
658 if (ctx.exception != null) return;
660 Era era = (Era) stack.pop();
661 Integer year = (Integer) stack.pop();
662 Part part = (Part) stack.pop();
665 // If the era was explicitly specified, the start and end years
666 // may be calculated now.
668 stack.push(DateUtils.getPartialCenturyStartDate(year, part, era));
669 stack.push(DateUtils.getPartialCenturyEndDate(year, part, era));
672 // If the era was not explicitly specified, the start and end years
673 // can't be calculated yet. The calculation must be deferred until
674 // later. For example, this partial century may be the start of a hyphenated
675 // range, where the era will be inherited from the era of the end of
676 // the range; this era won't be known until farther up the parse tree,
677 // when both sides of the range will have been parsed.
679 stack.push(new DeferredPartialCenturyStartDate(year, part));
680 stack.push(new DeferredPartialCenturyEndDate(year, part));
685 public void exitQuarterCentury(QuarterCenturyContext ctx) {
686 if (ctx.exception != null) return;
688 Era era = (Era) stack.pop();
689 Integer year = (Integer) stack.pop();
690 Integer quarter = (Integer) stack.pop();
693 // If the era was explicitly specified, the start and end years
694 // may be calculated now.
696 stack.push(DateUtils.getQuarterCenturyStartDate(year, quarter, era));
697 stack.push(DateUtils.getQuarterCenturyEndDate(year, quarter, era));
700 // If the era was not explicitly specified, the start and end years
701 // can't be calculated yet. The calculation must be deferred until
702 // later. For example, this century may be the start of a hyphenated
703 // range, where the era will be inherited from the era of the end of
704 // the range; this era won't be known until farther up the parse tree,
705 // when both sides of the range will have been parsed.
707 stack.push(new DeferredQuarterCenturyStartDate(year, quarter));
708 stack.push(new DeferredQuarterCenturyEndDate(year, quarter));
713 public void exitHalfCentury(HalfCenturyContext ctx) {
714 if (ctx.exception != null) return;
716 Era era = (Era) stack.pop();
717 Integer year = (Integer) stack.pop();
718 Integer half = (Integer) stack.pop();
721 // If the era was explicitly specified, the start and end years
722 // may be calculated now.
724 stack.push(DateUtils.getHalfCenturyStartDate(year, half, era));
725 stack.push(DateUtils.getHalfCenturyEndDate(year, half, era));
728 // If the era was not explicitly specified, the start and end years
729 // can't be calculated yet. The calculation must be deferred until
730 // later. For example, this half century may be the start of a hyphenated
731 // range, where the era will be inherited from the era of the end of
732 // the range; this era won't be known until farther up the parse tree,
733 // when both sides of the range will have been parsed.
735 stack.push(new DeferredHalfCenturyStartDate(year, half));
736 stack.push(new DeferredHalfCenturyEndDate(year, half));
741 public void exitCentury(CenturyContext ctx) {
742 if (ctx.exception != null) return;
744 Era era = (Era) stack.pop();
745 Integer year = (Integer) stack.pop();
748 // If the era was explicitly specified, the start and end years
749 // may be calculated now.
751 stack.push(DateUtils.getCenturyStartDate(year, era));
752 stack.push(DateUtils.getCenturyEndDate(year, era));
755 // If the era was not explicitly specified, the start and end years
756 // can't be calculated yet. The calculation must be deferred until
757 // later. For example, this quarter century may be the start of a hyphenated
758 // range, where the era will be inherited from the era of the end of
759 // the range; this era won't be known until farther up the parse tree,
760 // when both sides of the range will have been parsed.
762 stack.push(new DeferredCenturyStartDate(year));
763 stack.push(new DeferredCenturyEndDate(year));
768 public void exitMillennium(MillenniumContext ctx) {
769 if (ctx.exception != null) return;
771 Era era = (Era) stack.pop();
772 Integer n = (Integer) stack.pop();
775 // If the era was explicitly specified, the start and end years
776 // may be calculated now.
778 stack.push(DateUtils.getMillenniumStartDate(n, era));
779 stack.push(DateUtils.getMillenniumEndDate(n, era));
782 // If the era was not explicitly specified, the start and end years
783 // can't be calculated yet. The calculation must be deferred until
784 // later. For example, this millennium may be the start of a hyphenated
785 // range, where the era will be inherited from the era of the end of
786 // the range; this era won't be known until farther up the parse tree,
787 // when both sides of the range will have been parsed.
789 stack.push(new DeferredMillenniumStartDate(n));
790 stack.push(new DeferredMillenniumEndDate(n));
795 public void exitStrCentury(StrCenturyContext ctx) {
796 if (ctx.exception != null) return;
798 Integer n = (Integer) stack.pop();
800 // Convert the nth number to a year number,
801 // and push on the stack.
803 Integer year = DateUtils.nthCenturyToYear(n);
809 public void exitNumCentury(NumCenturyContext ctx) {
810 if (ctx.exception != null) return;
812 // Convert the string to a number,
813 // and push on the stack.
815 Integer year = new Integer(stripEndLetters(ctx.HUNDREDS().getText()));
818 throw new StructuredDateFormatException("unexpected century '" + ctx.HUNDREDS().getText() + "'");
825 public void exitNumDecade(NumDecadeContext ctx) {
826 if (ctx.exception != null) return;
828 // Convert the string to a number,
829 // and push on the stack.
831 Integer year = new Integer(stripEndLetters(ctx.TENS().getText()));
834 throw new StructuredDateFormatException("unexpected decade '" + ctx.TENS().getText() + "'");
841 public void exitNumYear(NumYearContext ctx) {
842 if (ctx.exception != null) return;
844 // Convert the string to a number,
845 // and push on the stack.
847 Integer year = new Integer(ctx.NUMBER().getText());
850 throw new StructuredDateFormatException("unexpected year '" + ctx.NUMBER().getText() + "'");
857 public void exitNumMonth(NumMonthContext ctx) {
858 if (ctx.exception != null) return;
860 // Convert the string a number,
861 // and push on the stack.
863 Integer month = new Integer(ctx.NUMBER().getText());
865 if (month < 1 || month > 12) {
866 throw new StructuredDateFormatException("unexpected month '" + ctx.NUMBER().getText() + "'");
873 public void exitNthHalf(NthHalfContext ctx) {
874 if (ctx.exception != null) return;
876 // Convert LAST to a number (the last half
877 // is the 2nd). If this rule matched the
878 // alternative with nth instead of LAST,
879 // the nth handler will already have pushed
880 // a number on the stack.
882 if (ctx.LAST() != null) {
883 stack.push(new Integer(2));
886 // Check for a valid half.
888 Integer n = (Integer) stack.peek();
890 if (n < 1 || n > 2) {
891 throw new StructuredDateFormatException("unexpected half '" + n + "'");
896 public void exitNthQuarter(NthQuarterContext ctx) {
897 if (ctx.exception != null) return;
899 // Convert LAST to a number (the last quarter
900 // is the 4th). If this rule matched the
901 // alternative with nth instead of LAST,
902 // the nth handler will already have pushed
903 // a number on the stack.
905 if (ctx.LAST() != null) {
906 stack.push(new Integer(4));
909 // Check for a valid quarter.
911 Integer n = (Integer) stack.peek();
913 if (n < 1 || n > 4) {
914 throw new StructuredDateFormatException("unexpected quarter '" + n + "'");
919 public void exitNth(NthContext ctx) {
920 if (ctx.exception != null) return;
922 // Convert the string to a number,
923 // and push on the stack.
927 if (ctx.NTHSTR() != null) {
928 n = new Integer(stripEndLetters(ctx.NTHSTR().getText()));
930 else if (ctx.FIRST() != null) {
933 else if (ctx.SECOND() != null) {
936 else if (ctx.THIRD() != null) {
939 else if (ctx.FOURTH() != null) {
947 public void exitStrMonth(StrMonthContext ctx) {
948 if (ctx.exception != null) return;
950 // Convert the month name to a number,
951 // and push on the stack.
953 TerminalNode monthNode = ctx.MONTH();
955 if (monthNode == null) {
956 monthNode = ctx.SHORTMONTH();
959 String monthStr = monthNode.getText();
961 stack.push(DateUtils.getMonthByName(monthStr));
965 public void exitStrSeason(StrSeasonContext ctx) {
966 if (ctx.exception != null) return;
968 // Convert the season to a quarter number,
969 // and push on the stack.
971 Integer quarter = null;
973 if (ctx.WINTER() != null) {
976 else if (ctx.SPRING() != null) {
979 else if (ctx.SUMMER() != null) {
982 else if (ctx.FALL() != null) {
990 public void exitAllOrPartOf(AllOrPartOfContext ctx) {
991 if (ctx.exception != null) return;
993 // If a part was specified, it will have been
994 // pushed on the stack in exitPartOf(). If not,
995 // push null on the stack.
997 if (ctx.partOf() == null) {
1003 public void exitPartOf(PartOfContext ctx) {
1004 if (ctx.exception != null) return;
1006 // Convert the token to a Part,
1007 // and push on the stack.
1011 if (ctx.EARLY() != null) {
1014 else if (ctx.MIDDLE() != null) {
1017 else if (ctx.LATE() != null) {
1025 public void exitEra(EraContext ctx) {
1026 if (ctx.exception != null) return;
1028 // Convert the token to an Era,
1029 // and push on the stack.
1033 if (ctx.BC() != null) {
1036 else if (ctx.AD() != null) {
1044 public void exitNumDayOfMonth(NumDayOfMonthContext ctx) {
1045 if (ctx.exception != null) return;
1047 // Convert the numeric string to an Integer,
1048 // and push on the stack.
1050 Integer dayOfMonth = new Integer(ctx.NUMBER().getText());
1052 if (dayOfMonth == 0 || dayOfMonth > 31) {
1053 throw new StructuredDateFormatException("unexpected day of month '" + ctx.NUMBER().getText() + "'");
1056 stack.push(dayOfMonth);
1060 public void exitNum(NumContext ctx) {
1061 if (ctx.exception != null) return;
1063 // Convert the numeric string to an Integer,
1064 // and push on the stack.
1066 Integer num = new Integer(ctx.NUMBER().getText());
1071 protected String getErrorMessage(RecognitionException re) {
1072 String message = "";
1074 Parser recognizer = (Parser) re.getRecognizer();
1075 TokenStream tokens = recognizer.getInputStream();
1077 if (re instanceof NoViableAltException) {
1078 NoViableAltException e = (NoViableAltException) re;
1079 Token startToken = e.getStartToken();
1080 String input = (startToken.getType() == Token.EOF ) ? "end of text" : quote(tokens.getText(startToken, e.getOffendingToken()));
1082 message = "no viable date format found at " + input;
1084 else if (re instanceof InputMismatchException) {
1085 InputMismatchException e = (InputMismatchException) re;
1086 message = "did not expect " + getTokenDisplayString(e.getOffendingToken()) + " while looking for " +
1087 e.getExpectedTokens().toString(recognizer.getTokenNames());
1089 else if (re instanceof FailedPredicateException) {
1090 FailedPredicateException e = (FailedPredicateException) re;
1091 String ruleName = recognizer.getRuleNames()[recognizer.getContext().getRuleIndex()];
1093 message = "failed predicate " + ruleName + ": " + e.getMessage();
1099 protected String quote(String text) {
1100 return "'" + text + "'";
1103 protected String getTokenDisplayString(Token token) {
1106 if (token == null) {
1107 string = "[no token]";
1110 String text = token.getText();
1113 if (token.getType() == Token.EOF ) {
1114 string = "end of text";
1117 string = "[" + token.getType() + "]";
1121 string = quote(text);
1128 protected String stripEndLetters(String input) {
1129 return input.replaceAll("[^\\d]+$", "");
1132 public static void main(String[] args) {
1133 StructuredDateEvaluator evaluator = new ANTLRStructuredDateEvaluator();
1135 for (String displayDate : args) {
1137 evaluator.evaluate(displayDate);
1138 } catch (StructuredDateFormatException e) {
1139 e.printStackTrace();