1 package org.collectionspace.services.structureddate.antlr;
3 import java.util.regex.Matcher;
4 import java.util.regex.Pattern;
5 import java.util.Stack;
7 import org.antlr.v4.runtime.ANTLRInputStream;
8 import org.antlr.v4.runtime.BailErrorStrategy;
9 import org.antlr.v4.runtime.CommonTokenStream;
10 import org.antlr.v4.runtime.FailedPredicateException;
11 import org.antlr.v4.runtime.InputMismatchException;
12 import org.antlr.v4.runtime.NoViableAltException;
13 import org.antlr.v4.runtime.Parser;
14 import org.antlr.v4.runtime.RecognitionException;
15 import org.antlr.v4.runtime.Token;
16 import org.antlr.v4.runtime.TokenStream;
17 import org.antlr.v4.runtime.misc.ParseCancellationException;
18 import org.antlr.v4.runtime.tree.TerminalNode;
19 import org.collectionspace.services.structureddate.Date;
20 import org.collectionspace.services.structureddate.DateUtils;
21 import org.collectionspace.services.structureddate.DeferredCenturyEndDate;
22 import org.collectionspace.services.structureddate.DeferredCenturyStartDate;
23 import org.collectionspace.services.structureddate.DeferredDate;
24 import org.collectionspace.services.structureddate.DeferredDecadeEndDate;
25 import org.collectionspace.services.structureddate.DeferredDecadeStartDate;
26 import org.collectionspace.services.structureddate.DeferredHalfCenturyEndDate;
27 import org.collectionspace.services.structureddate.DeferredHalfCenturyStartDate;
28 import org.collectionspace.services.structureddate.DeferredMillenniumEndDate;
29 import org.collectionspace.services.structureddate.DeferredMillenniumStartDate;
30 import org.collectionspace.services.structureddate.DeferredPartialCenturyEndDate;
31 import org.collectionspace.services.structureddate.DeferredPartialCenturyStartDate;
32 import org.collectionspace.services.structureddate.DeferredPartialDecadeEndDate;
33 import org.collectionspace.services.structureddate.DeferredPartialDecadeStartDate;
34 import org.collectionspace.services.structureddate.DeferredQuarterCenturyEndDate;
35 import org.collectionspace.services.structureddate.DeferredQuarterCenturyStartDate;
36 import org.collectionspace.services.structureddate.Era;
37 import org.collectionspace.services.structureddate.Part;
38 import org.collectionspace.services.structureddate.StructuredDateInternal;
39 import org.collectionspace.services.structureddate.StructuredDateEvaluator;
40 import org.collectionspace.services.structureddate.StructuredDateFormatException;
41 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.AllOrPartOfContext;
42 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.BeforeOrAfterDateContext;
43 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.CenturyContext;
44 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.CertainDateContext;
45 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.DateContext;
46 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.DayFirstDateContext;
47 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.DayOrYearFirstDateContext;
48 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.DecadeContext;
49 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.DisplayDateContext;
50 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.EraContext;
51 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.HalfCenturyContext;
52 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.HalfYearContext;
53 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.HyphenatedRangeContext;
54 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.InvMonthYearContext;
55 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.InvSeasonYearContext;
56 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.InvStrDateContext;
57 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.InvStrDateEraLastDateContext;
58 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.MillenniumContext;
59 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.MonthContext;
60 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.MonthInYearRangeContext;
61 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.MonthYearContext;
62 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthCenturyRangeContext;
63 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthContext;
64 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthHalfContext;
65 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthQuarterContext;
66 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthQuarterInYearRangeContext;
67 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NthQuarterYearContext;
68 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumCenturyContext;
69 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumContext;
70 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDateContext;
71 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDayInMonthRangeContext;
72 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDayOfMonthContext;
73 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumDecadeContext;
74 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumMonthContext;
75 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.NumYearContext;
76 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartOfContext;
77 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartialCenturyContext;
78 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartialDecadeContext;
79 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartialYearContext;
80 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterCenturyContext;
81 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterInYearRangeContext;
82 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterYearContext;
83 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.SeasonYearContext;
84 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrCenturyContext;
85 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrDateContext;
86 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrDayInMonthRangeContext;
87 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrMonthContext;
88 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrSeasonContext;
89 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrSeasonInYearRangeContext;
90 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.UncalibratedDateContext;
91 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.UncertainDateContext;
92 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.UnknownDateContext;
93 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.YearContext;
94 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.YearSpanningWinterContext;
97 * A StructuredDateEvaluator that uses an ANTLR parser to parse the display date,
98 * and an ANTLR listener to generate a structured date from the resulting parse
101 public class ANTLRStructuredDateEvaluator extends StructuredDateBaseListener implements StructuredDateEvaluator {
103 * The result of the evaluation.
105 protected StructuredDateInternal result;
108 * The operation stack. The parse listener methods that are implemented here
109 * pop input parameters off the stack, and push results back on to the stack.
111 protected Stack<Object> stack;
113 public ANTLRStructuredDateEvaluator() {
118 public StructuredDateInternal evaluate(String displayDate) throws StructuredDateFormatException {
119 stack = new Stack<Object>();
121 result = new StructuredDateInternal();
122 result.setDisplayDate(displayDate);
124 // Instantiate a parser from the lowercased display date, so that parsing will be case insensitive
125 ANTLRInputStream inputStream = new ANTLRInputStream(displayDate.toLowerCase());
126 StructuredDateLexer lexer = new StructuredDateLexer(inputStream);
127 CommonTokenStream tokenStream = new CommonTokenStream(lexer);
128 StructuredDateParser parser = new StructuredDateParser(tokenStream);
130 // Don't try to recover from parse errors, just bail.
131 parser.setErrorHandler(new BailErrorStrategy());
133 // Don't print error messages to the console.
134 parser.removeErrorListeners();
136 // Generate our own custom error messages.
137 parser.addParseListener(this);
140 // Attempt to fulfill the oneDisplayDate rule of the grammar.
141 parser.oneDisplayDate();
143 catch(ParseCancellationException e) {
144 // ParseCancellationException is thrown by the BailErrorStrategy when there is a
145 // parse error, with the underlying RecognitionException as the cause.
146 RecognitionException re = (RecognitionException) e.getCause();
148 throw new StructuredDateFormatException(getErrorMessage(re), re);
151 // The parsing was successful. Return the result.
156 public void exitDisplayDate(DisplayDateContext ctx) {
157 if (ctx.exception != null) return;
159 Date latestDate = (Date) stack.pop();
160 Date earliestDate = (Date) stack.pop();
162 if (earliestDate.getYear() != null || earliestDate.getYear() != null) {
163 int compareResult = DateUtils.compareDates(earliestDate, latestDate);
164 if (compareResult == 1) {
167 earliestDate = latestDate;
170 // Check to see if the dates were reversed AND calculated. If they were
171 // Then this probably means the absolute earliestDate should have month and day as "1"
172 // and the latestDate momth 12, day 31.
173 if ((earliestDate.getMonth() == 12 && earliestDate.getDay() == 31) &&
174 (latestDate.getMonth() == 1 && latestDate.getDay() == 1)) {
175 earliestDate.setMonth(1);
176 earliestDate.setDay(1);
177 latestDate.setMonth(12);
178 latestDate.setDay(31);
183 // If the earliest date and the latest date are the same, it's just a "single" date.
184 // There's no need to have the latest, so set it to null.
186 if (earliestDate.equals(latestDate)) {
190 result.setEarliestSingleDate(earliestDate);
191 result.setLatestDate(latestDate);
195 public void exitBeforeOrAfterDate(BeforeOrAfterDateContext ctx) {
196 if (ctx.exception != null) return;
198 Date latestDate = (Date) stack.pop();
199 Date earliestDate = (Date) stack.pop();
201 // Set null eras to the default.
203 if (earliestDate.getEra() == null) {
204 earliestDate.setEra(Date.DEFAULT_ERA);
207 if (latestDate.getEra() == null) {
208 latestDate.setEra(Date.DEFAULT_ERA);
211 // Finalize any deferred calculations.
213 if (latestDate instanceof DeferredDate) {
214 ((DeferredDate) latestDate).resolveDate();
217 if (earliestDate instanceof DeferredDate) {
218 ((DeferredDate) earliestDate).resolveDate();
221 // Calculate the earliest date or end date.
223 if (ctx.BEFORE() != null) {
224 latestDate = earliestDate;
225 earliestDate = DateUtils.getEarliestBeforeDate(earliestDate, latestDate);
227 else if (ctx.AFTER() != null) {
228 earliestDate = latestDate;
229 latestDate = DateUtils.getLatestAfterDate(earliestDate, latestDate);
232 stack.push(earliestDate);
233 stack.push(latestDate);
237 public void exitUncertainDate(UncertainDateContext ctx) {
238 if (ctx.exception != null) return;
240 Date latestDate = (Date) stack.pop();
241 Date earliestDate = (Date) stack.pop();
244 int earliestInterval = DateUtils.getCircaIntervalYears(earliestDate.getYear(), earliestDate.getEra());
245 int latestInterval = DateUtils.getCircaIntervalYears(latestDate.getYear(), latestDate.getEra());
247 // Express the circa interval as a qualifier.
249 // stack.push(earliestDate.withQualifier(QualifierType.MINUS, earliestInterval, QualifierUnit.YEARS));
250 // stack.push(latestDate.withQualifier(QualifierType.PLUS, latestInterval, QualifierUnit.YEARS));
254 // Express the circa interval as an offset calculated into the year.
256 DateUtils.subtractYears(earliestDate, earliestInterval);
257 DateUtils.addYears(latestDate, latestInterval);
259 stack.push(earliestDate);
260 stack.push(latestDate);
264 public void exitCertainDate(CertainDateContext ctx) {
265 if (ctx.exception != null) return;
267 Date latestDate = (Date) stack.pop();
268 Date earliestDate = (Date) stack.pop();
270 // Set null eras to the default.
272 if (earliestDate.getEra() == null) {
273 earliestDate.setEra(Date.DEFAULT_ERA);
276 if (latestDate.getEra() == null) {
277 latestDate.setEra(Date.DEFAULT_ERA);
280 // Finalize any deferred calculations.
282 if (latestDate instanceof DeferredDate) {
283 ((DeferredDate) latestDate).resolveDate();
286 if (earliestDate instanceof DeferredDate) {
287 ((DeferredDate) earliestDate).resolveDate();
290 stack.push(earliestDate);
291 stack.push(latestDate);
295 public void exitHyphenatedRange(HyphenatedRangeContext ctx) {
296 if (ctx.exception != null) return;
298 Date latestEndDate = (Date) stack.pop();
299 stack.pop(); // latestStartDate
300 stack.pop(); // earliestEndDate
301 Date earliestStartDate = (Date) stack.pop();
303 // If no era was explicitly specified for the first date,
304 // make it inherit the era of the second date.
306 if (earliestStartDate.getEra() == null && latestEndDate.getEra() != null) {
307 earliestStartDate.setEra(latestEndDate.getEra());
310 // Finalize any deferred calculations.
312 if (earliestStartDate instanceof DeferredDate) {
313 ((DeferredDate) earliestStartDate).resolveDate();
316 if (latestEndDate instanceof DeferredDate) {
317 ((DeferredDate) latestEndDate).resolveDate();
320 stack.push(earliestStartDate);
321 stack.push(latestEndDate);
325 public void exitNthCenturyRange(NthCenturyRangeContext ctx) {
326 if (ctx.exception != null) return;
328 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
329 Integer endN = (Integer) stack.pop();
330 Part endPart = (Part) stack.pop();
331 Integer startN = (Integer) stack.pop();
332 Part startPart = (Part) stack.pop();
335 era = Date.DEFAULT_ERA;
338 int startYear = DateUtils.nthCenturyToYear(startN);
339 int endYear = DateUtils.nthCenturyToYear(endN);
341 stack.push(startPart == null ? DateUtils.getCenturyStartDate(startYear, era) : DateUtils.getPartialCenturyStartDate(startYear, startPart, era));
342 stack.push(startPart == null ? DateUtils.getCenturyEndDate(startYear, era) : DateUtils.getPartialCenturyEndDate(startYear, startPart, era));
343 stack.push(endPart == null ? DateUtils.getCenturyStartDate(endYear, era) : DateUtils.getPartialCenturyStartDate(endYear, endPart, era));
344 stack.push(endPart == null ? DateUtils.getCenturyEndDate(endYear, era) : DateUtils.getPartialCenturyEndDate(endYear, endPart, era));
348 public void exitMonthInYearRange(MonthInYearRangeContext ctx) {
349 if (ctx.exception != null) return;
351 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
352 Integer year = (Integer) stack.pop();
353 Integer numMonthEnd = (Integer) stack.pop();
354 Integer numMonthStart = (Integer) stack.pop();
356 stack.push(new Date(year, numMonthStart, 1, era));
357 stack.push(new Date(year, numMonthStart, DateUtils.getDaysInMonth(numMonthStart, year, era), era));
358 stack.push(new Date(year, numMonthEnd, 1, era));
359 stack.push(new Date(year, numMonthEnd, DateUtils.getDaysInMonth(numMonthEnd, year, era), era));
363 public void exitQuarterInYearRange(QuarterInYearRangeContext ctx) {
364 if (ctx.exception != null) return;
366 Era era = (Era) stack.pop();
367 Integer year = (Integer) stack.pop();
368 Integer lastQuarter = (Integer) stack.pop();
369 Integer firstQuarter = (Integer) stack.pop();
371 stack.push(DateUtils.getQuarterYearStartDate(firstQuarter, year).withEra(era));
372 stack.push(DateUtils.getQuarterYearEndDate(firstQuarter, year, era).withEra(era));
373 stack.push(DateUtils.getQuarterYearStartDate(lastQuarter, year).withEra(era));
374 stack.push(DateUtils.getQuarterYearEndDate(lastQuarter, year, era).withEra(era));
378 public void exitStrDayInMonthRange(StrDayInMonthRangeContext ctx) {
379 if (ctx.exception != null) return;
381 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
382 Integer year = (Integer) stack.pop();
383 Integer dayOfMonthEnd = (Integer) stack.pop();
384 Integer dayOfMonthStart = (Integer) stack.pop();
385 Integer numMonth = (Integer) stack.pop();
387 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
388 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
389 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
390 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
394 public void exitNumDayInMonthRange(NumDayInMonthRangeContext ctx) {
395 if (ctx.exception != null) return;
397 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
398 Integer num1 = (Integer) stack.pop();
399 Integer num2 = (Integer) stack.pop();
400 Integer num3 = (Integer) stack.pop();
401 Integer num4 = (Integer) stack.pop();
403 /* We can distinguish whether it is M/D-D/Y (Case 1) or M/Y-M/Y (Case 2) by checking if
404 The num1, num4, num2 and num1, num4, num3 are valid dates, since this would equate to
405 a set of two ranges. For examples: 04/13-19/1995 would be 04/13/1995-04/19/1995. If both these
406 dates are valid, we know that it shouldn't be interpreted as 04/01/13 - 19/31/1995 since these arent valid dates!
409 Integer lateYear = num1;
410 Integer earlyMonth = num4;
411 Integer dayOfMonthEnd = num2;
412 Integer dayOfMonthStart = num3;
414 if (DateUtils.isValidDate(num1, num4, num2, era) && DateUtils.isValidDate(num1, num4, num3, era)) {
415 // No need to alter the arguments, so just push to the stack
416 stack.push(new Date(lateYear, earlyMonth, dayOfMonthStart, era));
417 stack.push(new Date(lateYear, earlyMonth, dayOfMonthStart, era));
418 stack.push(new Date(lateYear, earlyMonth, dayOfMonthEnd, era));
419 stack.push(new Date(lateYear, earlyMonth, dayOfMonthEnd, era));
422 // Separated these by case, since it makes the code more legible
423 Integer latestMonth = num2;
424 Integer earliestYear = num3;
426 stack.push(new Date(earliestYear, earlyMonth, 1, era)); // Earliest Early Date
427 stack.push(new Date(earliestYear, earlyMonth, DateUtils.getDaysInMonth(earlyMonth, earliestYear, era), era)); // Latest Early Date
428 stack.push(new Date(lateYear, latestMonth, 1, era)); // Earliest Latest Date
429 stack.push(new Date(lateYear, latestMonth, DateUtils.getDaysInMonth(latestMonth, lateYear, era), era)); // Latest Late Date
435 public void exitDate(DateContext ctx) {
436 if (ctx.exception != null) return;
438 // Expect the canonical year-month-day-era ordering
439 // to be on the stack.
441 Era era = (stack.size() == 3) ? null : (Era) stack.pop();
442 Integer dayOfMonth = (Integer) stack.pop();
443 Integer numMonth = (Integer) stack.pop();
444 Integer year = (Integer) stack.pop();
446 // For the latest date we could either return null, or a copy of the earliest date,
447 // since the UI doesn't care. Use a copy of the earliest date, since it makes
448 // things easier here if we don't have to test for null up the tree.
450 stack.push(new Date(year, numMonth, dayOfMonth, era));
451 stack.push(new Date(year, numMonth, dayOfMonth, era));
455 public void exitNumDate(NumDateContext ctx) {
456 if (ctx.exception != null) return;
458 // This could either be year-month-day, or
459 // month-day-year. Try to determine which,
460 // and reorder the stack into the canonical
461 // year-month-day-era ordering.
463 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
464 Integer num3 = (Integer) stack.pop();
465 Integer num2 = (Integer) stack.pop();
466 Integer num1 = (Integer) stack.pop();
468 // Default to a month-day-year interpretation.
471 int dayOfMonth = num2;
474 if (DateUtils.isValidDate(num3, num1, num2, era)) {
475 // Interpreting as month-day-year produces a valid date. Go with it.
477 else if (DateUtils.isValidDate(num1, num2, num3, era)) {
478 // Interpreting as month-day-year doesn't produce a valid date, but
479 // year-month-day does. Go with year-month-day.
487 stack.push(numMonth);
488 stack.push(dayOfMonth);
493 public void exitStrDate(StrDateContext ctx) {
494 if (ctx.exception != null) return;
496 // Reorder the stack into a canonical ordering,
497 // year-month-day-era.
499 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
500 Integer year = (Integer) stack.pop();
501 Integer dayOfMonth = (Integer) stack.pop();
502 Integer numMonth = (Integer) stack.pop();
505 stack.push(numMonth);
506 stack.push(dayOfMonth);
511 public void exitInvStrDate(InvStrDateContext ctx) {
512 if (ctx.exception != null) return;
514 // Reorder the stack into a canonical ordering,
515 // year-month-day-era.
517 Integer dayOfMonth = (Integer) stack.pop();
518 Integer numMonth = (Integer) stack.pop();
519 Integer year = (Integer) stack.pop();
520 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
523 stack.push(numMonth);
524 stack.push(dayOfMonth);
529 public void exitDayFirstDate(DayFirstDateContext ctx) {
530 if (ctx.exception != null) return ;
532 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
533 Integer year = (Integer) stack.pop();
534 Integer month = (Integer) stack.pop();
535 Integer dayOfMonth = (Integer) stack.pop();
539 stack.push(dayOfMonth);
544 public void exitDayOrYearFirstDate(DayOrYearFirstDateContext ctx) {
545 if (ctx.exception != null) return;
548 Integer num2 = (Integer) stack.pop();
549 Integer numMonth = (Integer) stack.pop();
550 Integer num1 = (Integer) stack.pop();
553 Integer dayOfMonth = num2;
555 if (DateUtils.isValidDate(num1, numMonth, num2, era)) {
556 // The first number is a year. Already correct
557 } else if (DateUtils.isValidDate(num2, numMonth, num1, era)) {
558 // The second number is a year.
564 stack.push(numMonth);
565 stack.push(dayOfMonth);
567 if (dayOfMonth > 31 || dayOfMonth <= 0) {
568 throw new StructuredDateFormatException("unexpected day of month '" + Integer.toString(dayOfMonth) + "'");
571 throw new StructuredDateFormatException("unexpected year '" + Integer.toString(year) + "'");
576 public void exitInvStrDateEraLastDate(InvStrDateEraLastDateContext ctx) {
577 if (ctx.exception != null) return;
579 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
580 Integer dayOfMonth = (Integer) stack.pop();
581 Integer month = (Integer) stack.pop();
582 Integer year = (Integer) stack.pop();
586 stack.push(dayOfMonth);
591 public void exitMonth(MonthContext ctx) {
592 if (ctx.exception != null) return;
594 Era era = (Era) stack.pop();
595 Integer year = (Integer) stack.pop();
596 Integer numMonth = (Integer) stack.pop();
598 stack.push(new Date(year, numMonth, 1, era));
599 stack.push(new Date(year, numMonth, DateUtils.getDaysInMonth(numMonth, year, era), era));
603 public void exitMonthYear(MonthYearContext ctx) {
604 if (ctx.exception != null) return;
606 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
612 public void exitInvMonthYear(InvMonthYearContext ctx) {
613 if (ctx.exception != null) return;
615 // Invert the arguments.
617 Integer numMonth = (Integer) stack.pop();
618 Integer year = (Integer) stack.pop();
619 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
621 stack.push(numMonth);
627 public void exitYearSpanningWinter(YearSpanningWinterContext ctx) {
628 if (ctx.exception != null) return;
630 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
631 Integer endYear = (Integer) stack.pop();
632 Integer startYear = (Integer) stack.pop();
634 stack.push(new Date(startYear, 12, 1).withEra(era));
635 stack.push(DateUtils.getQuarterYearEndDate(1, endYear, era).withEra(era));
639 public void exitPartialYear(PartialYearContext ctx) {
640 if (ctx.exception != null) return;
642 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
643 Integer year = (Integer) stack.pop();
644 Part part = (Part) stack.pop();
646 stack.push(DateUtils.getPartialYearStartDate(part, year).withEra(era));
647 stack.push(DateUtils.getPartialYearEndDate(part, year, era).withEra(era));
651 public void exitQuarterYear(QuarterYearContext ctx) {
652 if (ctx.exception != null) return;
654 Era era = (Era) stack.pop();
655 Integer year = (Integer) stack.pop();
656 Integer quarter = (Integer) stack.pop();
658 stack.push(DateUtils.getQuarterYearStartDate(quarter, year).withEra(era));
659 stack.push(DateUtils.getQuarterYearEndDate(quarter, year, era).withEra(era));
663 public void exitHalfYear(HalfYearContext ctx) {
664 if (ctx.exception != null) return;
666 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
667 Integer year = (Integer) stack.pop();
668 Integer half = (Integer) stack.pop();
670 stack.push(DateUtils.getHalfYearStartDate(half, year).withEra(era));
671 stack.push(DateUtils.getHalfYearEndDate(half, year, era).withEra(era));
675 public void exitInvSeasonYear(InvSeasonYearContext ctx) {
676 if (ctx.exception != null) return;
678 // Invert the arguments.
680 Integer quarter = (Integer) stack.pop();
681 Integer year = (Integer) stack.pop();
682 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
690 public void exitSeasonYear(SeasonYearContext ctx) {
691 if (ctx.exception != null) return;
693 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
699 public void exitYear(YearContext ctx) {
700 if (ctx.exception != null) return;
702 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
703 Integer year = (Integer) stack.pop();
705 stack.push(new Date(year, 1, 1, era));
706 stack.push(new Date(year, 12, 31, era));
710 public void exitPartialDecade(PartialDecadeContext ctx) {
711 if (ctx.exception != null) return;
713 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
714 Integer year = (Integer) stack.pop();
715 Part part = (Part) stack.pop();
718 // If the era was explicitly specified, the start and end years
719 // may be calculated now.
721 stack.push(DateUtils.getPartialDecadeStartDate(year, part, era));
722 stack.push(DateUtils.getPartialDecadeEndDate(year, part, era));
725 // If the era was not explicitly specified, the start and end years
726 // can't be calculated yet. The calculation must be deferred until
727 // later. For example, this partial decade may be the start of a hyphenated
728 // range, where the era will be inherited from the era of the end of
729 // the range; this era won't be known until farther up the parse tree,
730 // when both sides of the range will have been parsed.
732 stack.push(new DeferredPartialDecadeStartDate(year, part));
733 stack.push(new DeferredPartialDecadeEndDate(year, part));
738 public void exitDecade(DecadeContext ctx) {
739 if (ctx.exception != null) return;
741 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
742 Integer year = (Integer) stack.pop();
744 // Calculate the start and end year of the decade, which depends on the era.
747 // If the era was explicitly specified, the start and end years
748 // may be calculated now.
750 stack.push(DateUtils.getDecadeStartDate(year, era));
751 stack.push(DateUtils.getDecadeEndDate(year, era));
754 // If the era was not explicitly specified, the start and end years
755 // can't be calculated yet. The calculation must be deferred until
756 // later. For example, this decade may be the start of a hyphenated
757 // range, where the era will be inherited from the era of the end of
758 // the range; this era won't be known until farther up the parse tree,
759 // when both sides of the range will have been parsed.
761 stack.push(new DeferredDecadeStartDate(year));
762 stack.push(new DeferredDecadeEndDate(year));
767 public void exitPartialCentury(PartialCenturyContext ctx) {
768 if (ctx.exception != null) return;
770 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
771 Integer year = (Integer) stack.pop();
772 Part part = (Part) stack.pop();
775 // If the era was explicitly specified, the start and end years
776 // may be calculated now.
778 stack.push(DateUtils.getPartialCenturyStartDate(year, part, era));
779 stack.push(DateUtils.getPartialCenturyEndDate(year, part, 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 partial century 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 DeferredPartialCenturyStartDate(year, part));
790 stack.push(new DeferredPartialCenturyEndDate(year, part));
795 public void exitQuarterCentury(QuarterCenturyContext ctx) {
796 if (ctx.exception != null) return;
798 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
799 Integer year = (Integer) stack.pop();
800 Integer quarter = (Integer) stack.pop();
803 // If the era was explicitly specified, the start and end years
804 // may be calculated now.
806 stack.push(DateUtils.getQuarterCenturyStartDate(year, quarter, era));
807 stack.push(DateUtils.getQuarterCenturyEndDate(year, quarter, era));
810 // If the era was not explicitly specified, the start and end years
811 // can't be calculated yet. The calculation must be deferred until
812 // later. For example, this century may be the start of a hyphenated
813 // range, where the era will be inherited from the era of the end of
814 // the range; this era won't be known until farther up the parse tree,
815 // when both sides of the range will have been parsed.
817 stack.push(new DeferredQuarterCenturyStartDate(year, quarter));
818 stack.push(new DeferredQuarterCenturyEndDate(year, quarter));
823 public void exitHalfCentury(HalfCenturyContext ctx) {
824 if (ctx.exception != null) return;
826 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
827 Integer year = (Integer) stack.pop();
828 Integer half = (Integer) stack.pop();
831 // If the era was explicitly specified, the start and end years
832 // may be calculated now.
834 stack.push(DateUtils.getHalfCenturyStartDate(year, half, era));
835 stack.push(DateUtils.getHalfCenturyEndDate(year, half, era));
838 // If the era was not explicitly specified, the start and end years
839 // can't be calculated yet. The calculation must be deferred until
840 // later. For example, this half century may be the start of a hyphenated
841 // range, where the era will be inherited from the era of the end of
842 // the range; this era won't be known until farther up the parse tree,
843 // when both sides of the range will have been parsed.
845 stack.push(new DeferredHalfCenturyStartDate(year, half));
846 stack.push(new DeferredHalfCenturyEndDate(year, half));
851 public void exitCentury(CenturyContext ctx) {
852 if (ctx.exception != null) return;
854 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
855 Integer year = (Integer) stack.pop();
858 // If the era was explicitly specified, the start and end years
859 // may be calculated now.
861 stack.push(DateUtils.getCenturyStartDate(year, era));
862 stack.push(DateUtils.getCenturyEndDate(year, era));
865 // If the era was not explicitly specified, the start and end years
866 // can't be calculated yet. The calculation must be deferred until
867 // later. For example, this quarter century may be the start of a hyphenated
868 // range, where the era will be inherited from the era of the end of
869 // the range; this era won't be known until farther up the parse tree,
870 // when both sides of the range will have been parsed.
872 stack.push(new DeferredCenturyStartDate(year));
873 stack.push(new DeferredCenturyEndDate(year));
878 public void exitMillennium(MillenniumContext ctx) {
879 if (ctx.exception != null) return;
881 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
882 Integer n = (Integer) stack.pop();
885 // If the era was explicitly specified, the start and end years
886 // may be calculated now.
888 stack.push(DateUtils.getMillenniumStartDate(n, era));
889 stack.push(DateUtils.getMillenniumEndDate(n, era));
892 // If the era was not explicitly specified, the start and end years
893 // can't be calculated yet. The calculation must be deferred until
894 // later. For example, this millennium may be the start of a hyphenated
895 // range, where the era will be inherited from the era of the end of
896 // the range; this era won't be known until farther up the parse tree,
897 // when both sides of the range will have been parsed.
899 stack.push(new DeferredMillenniumStartDate(n));
900 stack.push(new DeferredMillenniumEndDate(n));
905 public void exitStrCentury(StrCenturyContext ctx) {
906 if (ctx.exception != null) return;
908 Integer n = (Integer) stack.pop();
910 // Convert the nth number to a year number,
911 // and push on the stack.
913 Integer year = DateUtils.nthCenturyToYear(n);
919 public void exitNumCentury(NumCenturyContext ctx) {
920 if (ctx.exception != null) return;
922 // Convert the string to a number,
923 // and push on the stack.
925 Integer year = new Integer(stripEndLetters(ctx.HUNDREDS().getText()));
928 throw new StructuredDateFormatException("unexpected century '" + ctx.HUNDREDS().getText() + "'");
935 public void exitNumDecade(NumDecadeContext ctx) {
936 if (ctx.exception != null) return;
938 // Convert the string to a number,
939 // and push on the stack.
941 Integer year = new Integer(stripEndLetters(ctx.TENS().getText()));
944 throw new StructuredDateFormatException("unexpected decade '" + ctx.TENS().getText() + "'");
951 public void exitNumYear(NumYearContext ctx) {
952 if (ctx.exception != null) return;
954 // Convert the string to a number,
955 // and push on the stack.
957 Integer year = new Integer(ctx.getText().replaceAll(",", ""));
960 throw new StructuredDateFormatException("unexpected year '" + ctx.NUMBER().getText() + "'");
967 public void exitNumMonth(NumMonthContext ctx) {
968 if (ctx.exception != null) return;
970 // Convert the string a number,
971 // and push on the stack.
973 Integer month = new Integer(ctx.NUMBER().getText());
975 if (month < 1 || month > 12) {
976 throw new StructuredDateFormatException("unexpected month '" + ctx.NUMBER().getText() + "'");
983 public void exitNthHalf(NthHalfContext ctx) {
984 if (ctx.exception != null) return;
986 // Convert LAST to a number (the last half
987 // is the 2nd). If this rule matched the
988 // alternative with nth instead of LAST,
989 // the nth handler will already have pushed
990 // a number on the stack.
992 if (ctx.LAST() != null) {
993 stack.push(new Integer(2));
996 // Check for a valid half.
998 Integer n = (Integer) stack.peek();
1000 if (n < 1 || n > 2) {
1001 throw new StructuredDateFormatException("unexpected half '" + n + "'");
1007 public void exitNthQuarterInYearRange(NthQuarterInYearRangeContext ctx) {
1008 if (ctx.exception != null) return;
1010 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1016 public void exitStrSeasonInYearRange(StrSeasonInYearRangeContext ctx) {
1017 if (ctx.exception != null) return;
1019 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1026 public void exitNthQuarterYear(NthQuarterYearContext ctx) {
1028 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1034 public void exitNthQuarter(NthQuarterContext ctx) {
1035 if (ctx.exception != null) return;
1037 // Convert LAST to a number (the last quarter
1038 // is the 4th). If this rule matched the
1039 // alternative with nth instead of LAST,
1040 // the nth handler will already have pushed
1041 // a number on the stack.
1043 if (ctx.LAST() != null) {
1044 stack.push(new Integer(4));
1047 // Check for a valid quarter.
1049 Integer n = (Integer) stack.peek();
1051 if (n < 1 || n > 4) {
1052 throw new StructuredDateFormatException("unexpected quarter '" + n + "'");
1057 public void exitNth(NthContext ctx) {
1058 if (ctx.exception != null) return;
1060 // Convert the string to a number,
1061 // and push on the stack.
1065 if (ctx.NTHSTR() != null) {
1066 n = new Integer(stripEndLetters(ctx.NTHSTR().getText()));
1068 else if (ctx.FIRST() != null) {
1071 else if (ctx.SECOND() != null) {
1074 else if (ctx.THIRD() != null) {
1077 else if (ctx.FOURTH() != null) {
1085 public void exitStrMonth(StrMonthContext ctx) {
1086 if (ctx.exception != null) return;
1088 // Convert the month name to a number,
1089 // and push on the stack.
1091 TerminalNode monthNode = ctx.MONTH();
1093 if (monthNode == null) {
1094 monthNode = ctx.SHORTMONTH();
1097 String monthStr = monthNode.getText();
1099 stack.push(DateUtils.getMonthByName(monthStr));
1103 public void exitStrSeason(StrSeasonContext ctx) {
1104 if (ctx.exception != null) return;
1106 // Convert the season to a quarter number,
1107 // and push on the stack.
1109 Integer quarter = null;
1111 if (ctx.WINTER() != null) {
1114 else if (ctx.SPRING() != null) {
1117 else if (ctx.SUMMER() != null) {
1120 else if (ctx.FALL() != null) {
1124 stack.push(quarter);
1128 public void exitAllOrPartOf(AllOrPartOfContext ctx) {
1129 if (ctx.exception != null) return;
1131 // If a part was specified, it will have been
1132 // pushed on the stack in exitPartOf(). If not,
1133 // push null on the stack.
1135 if (ctx.partOf() == null) {
1141 public void exitPartOf(PartOfContext ctx) {
1142 if (ctx.exception != null) return;
1144 // Convert the token to a Part,
1145 // and push on the stack.
1149 if (ctx.EARLY() != null) {
1152 else if (ctx.MIDDLE() != null) {
1155 else if (ctx.LATE() != null) {
1163 public void exitEra(EraContext ctx) {
1164 if (ctx.exception != null) return;
1166 // Convert the token to an Era,
1167 // and push on the stack.
1171 if (ctx.BC() != null) {
1174 else if (ctx.AD() != null) {
1182 public void exitNumDayOfMonth(NumDayOfMonthContext ctx) {
1183 if (ctx.exception != null) return;
1185 // Convert the numeric string to an Integer,
1186 // and push on the stack.
1188 Integer dayOfMonth = new Integer(ctx.NUMBER().getText());
1190 if (dayOfMonth == 0 || dayOfMonth > 31) {
1191 throw new StructuredDateFormatException("unexpected day of month '" + ctx.NUMBER().getText() + "'");
1194 stack.push(dayOfMonth);
1198 public void exitNum(NumContext ctx) {
1199 if (ctx.exception != null) return;
1201 // Convert the numeric string to an Integer,
1202 // and push on the stack.
1204 Integer num = new Integer(ctx.getText().replaceAll(",", ""));
1210 public void exitUnknownDate(UnknownDateContext ctx) {
1211 if (ctx.exception != null) return;
1214 stack.push(new Date());
1215 stack.push(new Date());
1218 public void exitUncalibratedDate(UncalibratedDateContext ctx) {
1219 if (ctx.exception != null) return;
1221 Integer adjustmentDate = (Integer) stack.pop();
1222 Integer mainYear = (Integer) stack.pop();
1224 Integer upperBound = mainYear + adjustmentDate;
1225 Integer lowerBound = mainYear - adjustmentDate;
1227 Integer currentYear = DateUtils.getCurrentDate().getYear();
1229 Integer earliestYear = currentYear - upperBound;
1230 Integer latestYear = currentYear - lowerBound ;
1232 // If negative, then BC, else AD
1233 Era earliestEra = earliestYear < 0 ? Era.BCE : Era.CE;
1234 Era latestEra = latestYear < 0 ? Era.BCE : Era.CE;
1236 stack.push(new Date(Math.abs(earliestYear), 1, 1, earliestEra)); // Earliest Early Date
1237 stack.push(new Date(Math.abs(latestYear), 12, DateUtils.getDaysInMonth(12, Math.abs(latestYear), latestEra), latestEra)); // Latest Late Date
1241 protected String getErrorMessage(RecognitionException re) {
1242 String message = "";
1244 Parser recognizer = (Parser) re.getRecognizer();
1245 TokenStream tokens = recognizer.getInputStream();
1247 if (re instanceof NoViableAltException) {
1248 NoViableAltException e = (NoViableAltException) re;
1249 Token startToken = e.getStartToken();
1250 String input = (startToken.getType() == Token.EOF ) ? "end of text" : quote(tokens.getText(startToken, e.getOffendingToken()));
1252 message = "no viable date format found at " + input;
1254 else if (re instanceof InputMismatchException) {
1255 InputMismatchException e = (InputMismatchException) re;
1256 message = "did not expect " + getTokenDisplayString(e.getOffendingToken()) + " while looking for " +
1257 e.getExpectedTokens().toString(recognizer.getTokenNames());
1259 else if (re instanceof FailedPredicateException) {
1260 FailedPredicateException e = (FailedPredicateException) re;
1261 String ruleName = recognizer.getRuleNames()[recognizer.getContext().getRuleIndex()];
1263 message = "failed predicate " + ruleName + ": " + e.getMessage();
1269 protected String quote(String text) {
1270 return "'" + text + "'";
1273 protected String getTokenDisplayString(Token token) {
1276 if (token == null) {
1277 string = "[no token]";
1280 String text = token.getText();
1283 if (token.getType() == Token.EOF ) {
1284 string = "end of text";
1287 string = "[" + token.getType() + "]";
1291 string = quote(text);
1298 protected String stripEndLetters(String input) {
1299 return input.replaceAll("[^\\d]+$", "");
1302 public static void main(String[] args) {
1303 StructuredDateEvaluator evaluator = new ANTLRStructuredDateEvaluator();
1305 for (String displayDate : args) {
1307 evaluator.evaluate(displayDate);
1308 } catch (StructuredDateFormatException e) {
1309 e.printStackTrace();