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.MonthYearContext;
57 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthCenturyRangeContext;
58 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthContext;
59 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthHalfContext;
60 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthQuarterContext;
61 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthQuarterInYearRangeContext;
62 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthQuarterYearContext;
63 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumCenturyContext;
64 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumContext;
65 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDateContext;
66 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDayInMonthRangeContext;
67 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDayOfMonthContext;
68 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDecadeContext;
69 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumMonthContext;
70 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumYearContext;
71 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartOfContext;
72 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartialCenturyContext;
73 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartialDecadeContext;
74 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartialYearContext;
75 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterCenturyContext;
76 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterInYearRangeContext;
77 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterYearContext;
78 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrCenturyContext;
79 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrDateContext;
80 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrDayInMonthRangeContext;
81 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrMonthContext;
82 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrSeasonContext;
83 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrSeasonInYearRangeContext;
84 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.UncertainDateContext;
85 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.YearContext;
86 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.YearSpanningWinterContext;
87 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.SeasonYearContext;
91 * A StructuredDateEvaluator that uses an ANTLR parser to parse the display date,
92 * and an ANTLR listener to generate a structured date from the resulting parse
95 public class ANTLRStructuredDateEvaluator extends StructuredDateBaseListener implements StructuredDateEvaluator {
97 * The result of the evaluation.
99 protected StructuredDateInternal result;
102 * The operation stack. The parse listener methods that are implemented here
103 * pop input parameters off the stack, and push results back on to the stack.
105 protected Stack<Object> stack;
107 public ANTLRStructuredDateEvaluator() {
112 public StructuredDateInternal evaluate(String displayDate) throws StructuredDateFormatException {
113 stack = new Stack<Object>();
115 result = new StructuredDateInternal();
116 result.setDisplayDate(displayDate);
118 // Instantiate a parser from the lowercased display date, so that parsing will be
120 ANTLRInputStream inputStream = new ANTLRInputStream(displayDate.toLowerCase());
121 StructuredDateLexer lexer = new StructuredDateLexer(inputStream);
122 CommonTokenStream tokenStream = new CommonTokenStream(lexer);
123 StructuredDateParser parser = new StructuredDateParser(tokenStream);
125 // Don't try to recover from parse errors, just bail.
126 parser.setErrorHandler(new BailErrorStrategy());
128 // Don't print error messages to the console.
129 parser.removeErrorListeners();
131 // Generate our own custom error messages.
132 parser.addParseListener(this);
135 // Attempt to fulfill the oneDisplayDate rule of the grammar.
136 parser.oneDisplayDate();
138 catch(ParseCancellationException e) {
139 // ParseCancellationException is thrown by the BailErrorStrategy when there is a
140 // parse error, with the underlying RecognitionException as the cause.
141 RecognitionException re = (RecognitionException) e.getCause();
143 throw new StructuredDateFormatException(getErrorMessage(re), re);
146 // The parsing was successful. Return the result.
151 public void exitDisplayDate(DisplayDateContext ctx) {
152 if (ctx.exception != null) return;
154 Date latestDate = (Date) stack.pop();
155 Date earliestDate = (Date) stack.pop();
157 // If the earliest date and the latest date are the same, it's just a "single" date.
158 // There's no need to have the latest, so set it to null.
160 if (earliestDate.equals(latestDate)) {
164 result.setEarliestSingleDate(earliestDate);
165 result.setLatestDate(latestDate);
169 public void exitBeforeOrAfterDate(BeforeOrAfterDateContext ctx) {
170 if (ctx.exception != null) return;
172 Date latestDate = (Date) stack.pop();
173 Date earliestDate = (Date) stack.pop();
175 // Set null eras to the default.
177 if (earliestDate.getEra() == null) {
178 earliestDate.setEra(Date.DEFAULT_ERA);
181 if (latestDate.getEra() == null) {
182 latestDate.setEra(Date.DEFAULT_ERA);
185 // Finalize any deferred calculations.
187 if (latestDate instanceof DeferredDate) {
188 ((DeferredDate) latestDate).resolveDate();
191 if (earliestDate instanceof DeferredDate) {
192 ((DeferredDate) earliestDate).resolveDate();
195 // Calculate the earliest date or end date.
197 if (ctx.BEFORE() != null) {
198 latestDate = earliestDate;
199 earliestDate = DateUtils.getEarliestBeforeDate(earliestDate, latestDate);
201 else if (ctx.AFTER() != null) {
202 earliestDate = latestDate;
203 latestDate = DateUtils.getLatestAfterDate(earliestDate, latestDate);
206 stack.push(earliestDate);
207 stack.push(latestDate);
211 public void exitUncertainDate(UncertainDateContext ctx) {
212 if (ctx.exception != null) return;
214 Date latestDate = (Date) stack.pop();
215 Date earliestDate = (Date) stack.pop();
217 int earliestInterval = DateUtils.getCircaIntervalYears(earliestDate.getYear(), earliestDate.getEra());
218 int latestInterval = DateUtils.getCircaIntervalYears(latestDate.getYear(), latestDate.getEra());
220 // Express the circa interval as a qualifier.
222 // stack.push(earliestDate.withQualifier(QualifierType.MINUS, earliestInterval, QualifierUnit.YEARS));
223 // stack.push(latestDate.withQualifier(QualifierType.PLUS, latestInterval, QualifierUnit.YEARS));
227 // Express the circa interval as an offset calculated into the year.
229 DateUtils.subtractYears(earliestDate, earliestInterval);
230 DateUtils.addYears(latestDate, latestInterval);
232 stack.push(earliestDate);
233 stack.push(latestDate);
237 public void exitCertainDate(CertainDateContext ctx) {
238 if (ctx.exception != null) return;
240 Date latestDate = (Date) stack.pop();
241 Date earliestDate = (Date) stack.pop();
243 // Set null eras to the default.
245 if (earliestDate.getEra() == null) {
246 earliestDate.setEra(Date.DEFAULT_ERA);
249 if (latestDate.getEra() == null) {
250 latestDate.setEra(Date.DEFAULT_ERA);
253 // Finalize any deferred calculations.
255 if (latestDate instanceof DeferredDate) {
256 ((DeferredDate) latestDate).resolveDate();
259 if (earliestDate instanceof DeferredDate) {
260 ((DeferredDate) earliestDate).resolveDate();
263 stack.push(earliestDate);
264 stack.push(latestDate);
268 public void exitHyphenatedRange(HyphenatedRangeContext ctx) {
269 if (ctx.exception != null) return;
271 Date latestEndDate = (Date) stack.pop();
272 stack.pop(); // latestStartDate
273 stack.pop(); // earliestEndDate
274 Date earliestStartDate = (Date) stack.pop();
276 // If no era was explicitly specified for the first date,
277 // make it inherit the era of the second date.
279 if (earliestStartDate.getEra() == null && latestEndDate.getEra() != null) {
280 earliestStartDate.setEra(latestEndDate.getEra());
283 // Finalize any deferred calculations.
285 if (earliestStartDate instanceof DeferredDate) {
286 ((DeferredDate) earliestStartDate).resolveDate();
289 if (latestEndDate instanceof DeferredDate) {
290 ((DeferredDate) latestEndDate).resolveDate();
293 stack.push(earliestStartDate);
294 stack.push(latestEndDate);
298 public void exitNthCenturyRange(NthCenturyRangeContext ctx) {
299 if (ctx.exception != null) return;
301 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
302 Integer endN = (Integer) stack.pop();
303 Part endPart = (Part) stack.pop();
304 Integer startN = (Integer) stack.pop();
305 Part startPart = (Part) stack.pop();
308 era = Date.DEFAULT_ERA;
311 int startYear = DateUtils.nthCenturyToYear(startN);
312 int endYear = DateUtils.nthCenturyToYear(endN);
314 stack.push(startPart == null ? DateUtils.getCenturyStartDate(startYear, era) : DateUtils.getPartialCenturyStartDate(startYear, startPart, era));
315 stack.push(startPart == null ? DateUtils.getCenturyEndDate(startYear, era) : DateUtils.getPartialCenturyEndDate(startYear, startPart, era));
316 stack.push(endPart == null ? DateUtils.getCenturyStartDate(endYear, era) : DateUtils.getPartialCenturyStartDate(endYear, endPart, era));
317 stack.push(endPart == null ? DateUtils.getCenturyEndDate(endYear, era) : DateUtils.getPartialCenturyEndDate(endYear, endPart, era));
321 public void exitMonthInYearRange(MonthInYearRangeContext ctx) {
322 if (ctx.exception != null) return;
324 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
325 Integer year = (Integer) stack.pop();
326 Integer numMonthEnd = (Integer) stack.pop();
327 Integer numMonthStart = (Integer) stack.pop();
329 stack.push(new Date(year, numMonthStart, 1, era));
330 stack.push(new Date(year, numMonthStart, DateUtils.getDaysInMonth(numMonthStart, year, era), era));
331 stack.push(new Date(year, numMonthEnd, 1, era));
332 stack.push(new Date(year, numMonthEnd, DateUtils.getDaysInMonth(numMonthEnd, year, era), era));
336 public void exitQuarterInYearRange(QuarterInYearRangeContext ctx) {
337 if (ctx.exception != null) return;
339 Era era = (Era) stack.pop();
340 Integer year = (Integer) stack.pop();
341 Integer lastQuarter = (Integer) stack.pop();
342 Integer firstQuarter = (Integer) stack.pop();
344 stack.push(DateUtils.getQuarterYearStartDate(firstQuarter, year).withEra(era));
345 stack.push(DateUtils.getQuarterYearEndDate(firstQuarter, year, era).withEra(era));
346 stack.push(DateUtils.getQuarterYearStartDate(lastQuarter, year).withEra(era));
347 stack.push(DateUtils.getQuarterYearEndDate(lastQuarter, year, era).withEra(era));
351 public void exitStrDayInMonthRange(StrDayInMonthRangeContext ctx) {
352 if (ctx.exception != null) return;
354 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
355 Integer year = (Integer) stack.pop();
356 Integer dayOfMonthEnd = (Integer) stack.pop();
357 Integer dayOfMonthStart = (Integer) stack.pop();
358 Integer numMonth = (Integer) stack.pop();
360 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
361 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
362 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
363 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
367 public void exitNumDayInMonthRange(NumDayInMonthRangeContext ctx) {
368 if (ctx.exception != null) return;
370 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
371 Integer year = (Integer) stack.pop();
372 Integer dayOfMonthEnd = (Integer) stack.pop();
373 Integer dayOfMonthStart = (Integer) stack.pop();
374 Integer numMonth = (Integer) stack.pop();
376 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
377 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
378 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
379 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
383 public void exitDate(DateContext ctx) {
384 if (ctx.exception != null) return;
386 // Expect the canonical year-month-day-era ordering
387 // to be on the stack.
389 Era era = (Era) stack.pop();
390 Integer dayOfMonth = (Integer) stack.pop();
391 Integer numMonth = (Integer) stack.pop();
392 Integer year = (Integer) stack.pop();
394 // For the latest date we could either return null, or a copy of the earliest date,
395 // since the UI doesn't care. Use a copy of the earliest date, since it makes
396 // things easier here if we don't have to test for null up the tree.
398 stack.push(new Date(year, numMonth, dayOfMonth, era));
399 stack.push(new Date(year, numMonth, dayOfMonth, era));
403 public void exitNumDate(NumDateContext ctx) {
404 if (ctx.exception != null) return;
406 // This could either be year-month-day, or
407 // month-day-year. Try to determine which,
408 // and reorder the stack into the canonical
409 // year-month-day-era ordering.
411 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
412 Integer num3 = (Integer) stack.pop();
413 Integer num2 = (Integer) stack.pop();
414 Integer num1 = (Integer) stack.pop();
416 // Default to a month-day-year interpretation.
419 int dayOfMonth = num2;
422 if (DateUtils.isValidDate(num3, num1, num2, era)) {
423 // Interpreting as month-day-year produces a valid date. Go with it.
425 else if (DateUtils.isValidDate(num1, num2, num3, era)) {
426 // Interpreting as month-day-year doesn't produce a valid date, but
427 // year-month-day does. Go with year-month-day.
435 stack.push(numMonth);
436 stack.push(dayOfMonth);
441 public void exitStrDate(StrDateContext ctx) {
442 if (ctx.exception != null) return;
444 // Reorder the stack into a canonical ordering,
445 // year-month-day-era.
447 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
448 Integer year = (Integer) stack.pop();
449 Integer dayOfMonth = (Integer) stack.pop();
450 Integer numMonth = (Integer) stack.pop();
453 stack.push(numMonth);
454 stack.push(dayOfMonth);
459 public void exitInvStrDate(InvStrDateContext ctx) {
460 if (ctx.exception != null) return;
462 // Reorder the stack into a canonical ordering,
463 // year-month-day-era.
465 Integer dayOfMonth = (Integer) stack.pop();
466 Integer numMonth = (Integer) stack.pop();
467 Integer year = (Integer) stack.pop();
468 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
471 stack.push(numMonth);
472 stack.push(dayOfMonth);
477 // public void exitInvStrDate(InvStrDateContext ctx) {
478 // if (ctx.exception != null) return;
480 // // Reorder the stack into a canonical ordering,
481 // // year-month-day-era.
484 // boolean eraLast = stack.peek() instanceof Integer;
492 // era = (Era) stack.pop(); // damn eras
495 // num1 = (Integer) stack.pop(); // year or day
496 // num2 = (Integer) stack.pop(); // month
497 // num3 = (Integer) stack.pop(); // day
500 // era = (Era) stack.pop();
503 // Integer dayOfMonth = num1;
504 // Integer numMonth = num2;
505 // Integer year = num3;
507 // if (DateUtils.isValidDate(num3, num2, num1, era)) {
508 // // Do nothing, already in the right format (Era Year Month Day)
509 // } else if (DateUtils.isValidDate(num1, num2, num3, era)) {
510 // // Use other format: Day Month Year Era
511 // dayOfMonth = num3;
517 // stack.push(numMonth);
518 // stack.push(dayOfMonth);
521 // if (dayOfMonth > 31 || dayOfMonth <= 0) {
522 // throw new StructuredDateFormatException("unexpected day of month '" + Integer.toString(dayOfMonth) + "'");
525 // throw new StructuredDateFormatException("unexpected year '" + Integer.toString(year) + "'");
530 public void exitMonth(MonthContext ctx) {
531 if (ctx.exception != null) return;
533 Era era = (Era) stack.pop();
534 Integer year = (Integer) stack.pop();
535 Integer numMonth = (Integer) stack.pop();
537 stack.push(new Date(year, numMonth, 1, era));
538 stack.push(new Date(year, numMonth, DateUtils.getDaysInMonth(numMonth, year, era), era));
542 public void exitMonthYear(MonthYearContext ctx) {
543 if (ctx.exception != null) return;
545 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
551 public void exitInvMonthYear(InvMonthYearContext ctx) {
552 if (ctx.exception != null) return;
554 // Invert the arguments.
556 Integer numMonth = (Integer) stack.pop();
557 Integer year = (Integer) stack.pop();
558 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
560 stack.push(numMonth);
566 public void exitYearSpanningWinter(YearSpanningWinterContext ctx) {
567 if (ctx.exception != null) return;
569 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
570 Integer endYear = (Integer) stack.pop();
571 Integer startYear = (Integer) stack.pop();
573 stack.push(new Date(startYear, 12, 1).withEra(era));
574 stack.push(DateUtils.getQuarterYearEndDate(1, endYear, era).withEra(era));
578 public void exitPartialYear(PartialYearContext ctx) {
579 if (ctx.exception != null) return;
581 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
582 Integer year = (Integer) stack.pop();
583 Part part = (Part) stack.pop();
585 stack.push(DateUtils.getPartialYearStartDate(part, year).withEra(era));
586 stack.push(DateUtils.getPartialYearEndDate(part, year, era).withEra(era));
590 public void exitQuarterYear(QuarterYearContext ctx) {
591 if (ctx.exception != null) return;
593 Era era = (Era) stack.pop();
594 Integer year = (Integer) stack.pop();
595 Integer quarter = (Integer) stack.pop();
597 stack.push(DateUtils.getQuarterYearStartDate(quarter, year).withEra(era));
598 stack.push(DateUtils.getQuarterYearEndDate(quarter, year, era).withEra(era));
602 public void exitHalfYear(HalfYearContext ctx) {
603 if (ctx.exception != null) return;
605 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
606 Integer year = (Integer) stack.pop();
607 Integer half = (Integer) stack.pop();
609 stack.push(DateUtils.getHalfYearStartDate(half, year).withEra(era));
610 stack.push(DateUtils.getHalfYearEndDate(half, year, era).withEra(era));
614 public void exitInvSeasonYear(InvSeasonYearContext ctx) {
615 if (ctx.exception != null) return;
617 // Invert the arguments.
619 Integer quarter = (Integer) stack.pop();
620 Integer year = (Integer) stack.pop();
621 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
629 public void exitSeasonYear(SeasonYearContext ctx) {
630 if (ctx.exception != null) return;
632 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
638 public void exitYear(YearContext ctx) {
639 if (ctx.exception != null) return;
641 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
642 Integer year = (Integer) stack.pop();
644 stack.push(new Date(year, 1, 1, era));
645 stack.push(new Date(year, 12, 31, era));
649 public void exitPartialDecade(PartialDecadeContext ctx) {
650 if (ctx.exception != null) return;
652 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
653 Integer year = (Integer) stack.pop();
654 Part part = (Part) stack.pop();
657 // If the era was explicitly specified, the start and end years
658 // may be calculated now.
660 stack.push(DateUtils.getPartialDecadeStartDate(year, part, era));
661 stack.push(DateUtils.getPartialDecadeEndDate(year, part, era));
664 // If the era was not explicitly specified, the start and end years
665 // can't be calculated yet. The calculation must be deferred until
666 // later. For example, this partial decade may be the start of a hyphenated
667 // range, where the era will be inherited from the era of the end of
668 // the range; this era won't be known until farther up the parse tree,
669 // when both sides of the range will have been parsed.
671 stack.push(new DeferredPartialDecadeStartDate(year, part));
672 stack.push(new DeferredPartialDecadeEndDate(year, part));
677 public void exitDecade(DecadeContext ctx) {
678 if (ctx.exception != null) return;
680 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
681 Integer year = (Integer) stack.pop();
683 // Calculate the start and end year of the decade, which depends on the era.
686 // If the era was explicitly specified, the start and end years
687 // may be calculated now.
689 stack.push(DateUtils.getDecadeStartDate(year, era));
690 stack.push(DateUtils.getDecadeEndDate(year, era));
693 // If the era was not explicitly specified, the start and end years
694 // can't be calculated yet. The calculation must be deferred until
695 // later. For example, this decade may be the start of a hyphenated
696 // range, where the era will be inherited from the era of the end of
697 // the range; this era won't be known until farther up the parse tree,
698 // when both sides of the range will have been parsed.
700 stack.push(new DeferredDecadeStartDate(year));
701 stack.push(new DeferredDecadeEndDate(year));
706 public void exitPartialCentury(PartialCenturyContext ctx) {
707 if (ctx.exception != null) return;
709 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
710 Integer year = (Integer) stack.pop();
711 Part part = (Part) stack.pop();
714 // If the era was explicitly specified, the start and end years
715 // may be calculated now.
717 stack.push(DateUtils.getPartialCenturyStartDate(year, part, era));
718 stack.push(DateUtils.getPartialCenturyEndDate(year, part, era));
721 // If the era was not explicitly specified, the start and end years
722 // can't be calculated yet. The calculation must be deferred until
723 // later. For example, this partial century may be the start of a hyphenated
724 // range, where the era will be inherited from the era of the end of
725 // the range; this era won't be known until farther up the parse tree,
726 // when both sides of the range will have been parsed.
728 stack.push(new DeferredPartialCenturyStartDate(year, part));
729 stack.push(new DeferredPartialCenturyEndDate(year, part));
734 public void exitQuarterCentury(QuarterCenturyContext ctx) {
735 if (ctx.exception != null) return;
737 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
738 Integer year = (Integer) stack.pop();
739 Integer quarter = (Integer) stack.pop();
742 // If the era was explicitly specified, the start and end years
743 // may be calculated now.
745 stack.push(DateUtils.getQuarterCenturyStartDate(year, quarter, era));
746 stack.push(DateUtils.getQuarterCenturyEndDate(year, quarter, era));
749 // If the era was not explicitly specified, the start and end years
750 // can't be calculated yet. The calculation must be deferred until
751 // later. For example, this century may be the start of a hyphenated
752 // range, where the era will be inherited from the era of the end of
753 // the range; this era won't be known until farther up the parse tree,
754 // when both sides of the range will have been parsed.
756 stack.push(new DeferredQuarterCenturyStartDate(year, quarter));
757 stack.push(new DeferredQuarterCenturyEndDate(year, quarter));
762 public void exitHalfCentury(HalfCenturyContext ctx) {
763 if (ctx.exception != null) return;
765 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
766 Integer year = (Integer) stack.pop();
767 Integer half = (Integer) stack.pop();
770 // If the era was explicitly specified, the start and end years
771 // may be calculated now.
773 stack.push(DateUtils.getHalfCenturyStartDate(year, half, era));
774 stack.push(DateUtils.getHalfCenturyEndDate(year, half, era));
777 // If the era was not explicitly specified, the start and end years
778 // can't be calculated yet. The calculation must be deferred until
779 // later. For example, this half century may be the start of a hyphenated
780 // range, where the era will be inherited from the era of the end of
781 // the range; this era won't be known until farther up the parse tree,
782 // when both sides of the range will have been parsed.
784 stack.push(new DeferredHalfCenturyStartDate(year, half));
785 stack.push(new DeferredHalfCenturyEndDate(year, half));
790 public void exitCentury(CenturyContext ctx) {
791 if (ctx.exception != null) return;
793 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
794 Integer year = (Integer) stack.pop();
797 // If the era was explicitly specified, the start and end years
798 // may be calculated now.
800 stack.push(DateUtils.getCenturyStartDate(year, era));
801 stack.push(DateUtils.getCenturyEndDate(year, era));
804 // If the era was not explicitly specified, the start and end years
805 // can't be calculated yet. The calculation must be deferred until
806 // later. For example, this quarter century may be the start of a hyphenated
807 // range, where the era will be inherited from the era of the end of
808 // the range; this era won't be known until farther up the parse tree,
809 // when both sides of the range will have been parsed.
811 stack.push(new DeferredCenturyStartDate(year));
812 stack.push(new DeferredCenturyEndDate(year));
817 public void exitMillennium(MillenniumContext ctx) {
818 if (ctx.exception != null) return;
820 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
821 Integer n = (Integer) stack.pop();
824 // If the era was explicitly specified, the start and end years
825 // may be calculated now.
827 stack.push(DateUtils.getMillenniumStartDate(n, era));
828 stack.push(DateUtils.getMillenniumEndDate(n, era));
831 // If the era was not explicitly specified, the start and end years
832 // can't be calculated yet. The calculation must be deferred until
833 // later. For example, this millennium may be the start of a hyphenated
834 // range, where the era will be inherited from the era of the end of
835 // the range; this era won't be known until farther up the parse tree,
836 // when both sides of the range will have been parsed.
838 stack.push(new DeferredMillenniumStartDate(n));
839 stack.push(new DeferredMillenniumEndDate(n));
844 public void exitStrCentury(StrCenturyContext ctx) {
845 if (ctx.exception != null) return;
847 Integer n = (Integer) stack.pop();
849 // Convert the nth number to a year number,
850 // and push on the stack.
852 Integer year = DateUtils.nthCenturyToYear(n);
858 public void exitNumCentury(NumCenturyContext ctx) {
859 if (ctx.exception != null) return;
861 // Convert the string to a number,
862 // and push on the stack.
864 Integer year = new Integer(stripEndLetters(ctx.HUNDREDS().getText()));
867 throw new StructuredDateFormatException("unexpected century '" + ctx.HUNDREDS().getText() + "'");
874 public void exitNumDecade(NumDecadeContext ctx) {
875 if (ctx.exception != null) return;
877 // Convert the string to a number,
878 // and push on the stack.
880 Integer year = new Integer(stripEndLetters(ctx.TENS().getText()));
883 throw new StructuredDateFormatException("unexpected decade '" + ctx.TENS().getText() + "'");
890 public void exitNumYear(NumYearContext ctx) {
891 if (ctx.exception != null) return;
893 // Convert the string to a number,
894 // and push on the stack.
896 Integer year = new Integer(ctx.NUMBER().getText());
899 throw new StructuredDateFormatException("unexpected year '" + ctx.NUMBER().getText() + "'");
906 public void exitNumMonth(NumMonthContext ctx) {
907 if (ctx.exception != null) return;
909 // Convert the string a number,
910 // and push on the stack.
912 Integer month = new Integer(ctx.NUMBER().getText());
914 if (month < 1 || month > 12) {
915 throw new StructuredDateFormatException("unexpected month '" + ctx.NUMBER().getText() + "'");
922 public void exitNthHalf(NthHalfContext ctx) {
923 if (ctx.exception != null) return;
925 // Convert LAST to a number (the last half
926 // is the 2nd). If this rule matched the
927 // alternative with nth instead of LAST,
928 // the nth handler will already have pushed
929 // a number on the stack.
931 if (ctx.LAST() != null) {
932 stack.push(new Integer(2));
935 // Check for a valid half.
937 Integer n = (Integer) stack.peek();
939 if (n < 1 || n > 2) {
940 throw new StructuredDateFormatException("unexpected half '" + n + "'");
946 public void exitNthQuarterInYearRange(NthQuarterInYearRangeContext ctx) {
947 if (ctx.exception != null) return;
949 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
955 public void exitStrSeasonInYearRange(StrSeasonInYearRangeContext ctx) {
956 if (ctx.exception != null) return;
958 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
965 public void exitNthQuarterYear(NthQuarterYearContext ctx) {
967 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
973 public void exitNthQuarter(NthQuarterContext ctx) {
974 if (ctx.exception != null) return;
976 // Convert LAST to a number (the last quarter
977 // is the 4th). If this rule matched the
978 // alternative with nth instead of LAST,
979 // the nth handler will already have pushed
980 // a number on the stack.
982 if (ctx.LAST() != null) {
983 stack.push(new Integer(4));
986 // Check for a valid quarter.
988 Integer n = (Integer) stack.peek();
990 if (n < 1 || n > 4) {
991 throw new StructuredDateFormatException("unexpected quarter '" + n + "'");
996 public void exitNth(NthContext ctx) {
997 if (ctx.exception != null) return;
999 // Convert the string to a number,
1000 // and push on the stack.
1004 if (ctx.NTHSTR() != null) {
1005 n = new Integer(stripEndLetters(ctx.NTHSTR().getText()));
1007 else if (ctx.FIRST() != null) {
1010 else if (ctx.SECOND() != null) {
1013 else if (ctx.THIRD() != null) {
1016 else if (ctx.FOURTH() != null) {
1024 public void exitStrMonth(StrMonthContext ctx) {
1025 if (ctx.exception != null) return;
1027 // Convert the month name to a number,
1028 // and push on the stack.
1030 TerminalNode monthNode = ctx.MONTH();
1032 if (monthNode == null) {
1033 monthNode = ctx.SHORTMONTH();
1036 String monthStr = monthNode.getText();
1038 stack.push(DateUtils.getMonthByName(monthStr));
1042 public void exitStrSeason(StrSeasonContext ctx) {
1043 if (ctx.exception != null) return;
1045 // Convert the season to a quarter number,
1046 // and push on the stack.
1048 Integer quarter = null;
1050 if (ctx.WINTER() != null) {
1053 else if (ctx.SPRING() != null) {
1056 else if (ctx.SUMMER() != null) {
1059 else if (ctx.FALL() != null) {
1063 stack.push(quarter);
1067 public void exitAllOrPartOf(AllOrPartOfContext ctx) {
1068 if (ctx.exception != null) return;
1070 // If a part was specified, it will have been
1071 // pushed on the stack in exitPartOf(). If not,
1072 // push null on the stack.
1074 if (ctx.partOf() == null) {
1080 public void exitPartOf(PartOfContext ctx) {
1081 if (ctx.exception != null) return;
1083 // Convert the token to a Part,
1084 // and push on the stack.
1088 if (ctx.EARLY() != null) {
1091 else if (ctx.MIDDLE() != null) {
1094 else if (ctx.LATE() != null) {
1102 public void exitEra(EraContext ctx) {
1103 if (ctx.exception != null) return;
1105 // Convert the token to an Era,
1106 // and push on the stack.
1110 if (ctx.BC() != null) {
1113 else if (ctx.AD() != null) {
1121 public void exitNumDayOfMonth(NumDayOfMonthContext ctx) {
1122 if (ctx.exception != null) return;
1124 // Convert the numeric string to an Integer,
1125 // and push on the stack.
1127 Integer dayOfMonth = new Integer(ctx.NUMBER().getText());
1129 if (dayOfMonth == 0 || dayOfMonth > 31) {
1130 throw new StructuredDateFormatException("unexpected day of month '" + ctx.NUMBER().getText() + "'");
1133 stack.push(dayOfMonth);
1137 public void exitNum(NumContext ctx) {
1138 if (ctx.exception != null) return;
1140 // Convert the numeric string to an Integer,
1141 // and push on the stack.
1143 Integer num = new Integer(ctx.NUMBER().getText());
1148 protected String getErrorMessage(RecognitionException re) {
1149 String message = "";
1151 Parser recognizer = (Parser) re.getRecognizer();
1152 TokenStream tokens = recognizer.getInputStream();
1154 if (re instanceof NoViableAltException) {
1155 NoViableAltException e = (NoViableAltException) re;
1156 Token startToken = e.getStartToken();
1157 String input = (startToken.getType() == Token.EOF ) ? "end of text" : quote(tokens.getText(startToken, e.getOffendingToken()));
1159 message = "no viable date format found at " + input;
1161 else if (re instanceof InputMismatchException) {
1162 InputMismatchException e = (InputMismatchException) re;
1163 message = "did not expect " + getTokenDisplayString(e.getOffendingToken()) + " while looking for " +
1164 e.getExpectedTokens().toString(recognizer.getTokenNames());
1166 else if (re instanceof FailedPredicateException) {
1167 FailedPredicateException e = (FailedPredicateException) re;
1168 String ruleName = recognizer.getRuleNames()[recognizer.getContext().getRuleIndex()];
1170 message = "failed predicate " + ruleName + ": " + e.getMessage();
1176 protected String quote(String text) {
1177 return "'" + text + "'";
1180 protected String getTokenDisplayString(Token token) {
1183 if (token == null) {
1184 string = "[no token]";
1187 String text = token.getText();
1190 if (token.getType() == Token.EOF ) {
1191 string = "end of text";
1194 string = "[" + token.getType() + "]";
1198 string = quote(text);
1205 protected String stripEndLetters(String input) {
1206 return input.replaceAll("[^\\d]+$", "");
1209 public static void main(String[] args) {
1210 StructuredDateEvaluator evaluator = new ANTLRStructuredDateEvaluator();
1212 for (String displayDate : args) {
1214 evaluator.evaluate(displayDate);
1215 } catch (StructuredDateFormatException e) {
1216 e.printStackTrace();