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.RomanNumContext;
84 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.SeasonYearContext;
85 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrCenturyContext;
86 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrDateContext;
87 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrDayInMonthRangeContext;
88 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrMonthContext;
89 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrSeasonContext;
90 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrSeasonInYearRangeContext;
91 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.UncalibratedDateContext;
92 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.UncertainDateContext;
93 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.UnknownDateContext;
94 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.YearContext;
95 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.YearSpanningWinterContext;
98 * A StructuredDateEvaluator that uses an ANTLR parser to parse the display date,
99 * and an ANTLR listener to generate a structured date from the resulting parse
102 public class ANTLRStructuredDateEvaluator extends StructuredDateBaseListener implements StructuredDateEvaluator {
104 * The result of the evaluation.
106 protected StructuredDateInternal result;
109 * The operation stack. The parse listener methods that are implemented here
110 * pop input parameters off the stack, and push results back on to the stack.
112 protected Stack<Object> stack;
114 public ANTLRStructuredDateEvaluator() {
119 public StructuredDateInternal evaluate(String displayDate) throws StructuredDateFormatException {
120 stack = new Stack<Object>();
122 result = new StructuredDateInternal();
123 result.setDisplayDate(displayDate);
125 // Instantiate a parser from the lowercased display date, so that parsing will be case insensitive
126 ANTLRInputStream inputStream = new ANTLRInputStream(displayDate.toLowerCase());
127 StructuredDateLexer lexer = new StructuredDateLexer(inputStream);
128 CommonTokenStream tokenStream = new CommonTokenStream(lexer);
129 StructuredDateParser parser = new StructuredDateParser(tokenStream);
131 // Don't try to recover from parse errors, just bail.
132 parser.setErrorHandler(new BailErrorStrategy());
134 // Don't print error messages to the console.
135 parser.removeErrorListeners();
137 // Generate our own custom error messages.
138 parser.addParseListener(this);
141 // Attempt to fulfill the oneDisplayDate rule of the grammar.
142 parser.oneDisplayDate();
144 catch(ParseCancellationException e) {
145 // ParseCancellationException is thrown by the BailErrorStrategy when there is a
146 // parse error, with the underlying RecognitionException as the cause.
147 RecognitionException re = (RecognitionException) e.getCause();
149 throw new StructuredDateFormatException(getErrorMessage(re), re);
152 // The parsing was successful. Return the result.
157 public void exitDisplayDate(DisplayDateContext ctx) {
158 if (ctx.exception != null) return;
160 Date latestDate = (Date) stack.pop();
161 Date earliestDate = (Date) stack.pop();
163 if (earliestDate.getYear() != null || earliestDate.getYear() != null) {
164 int compareResult = DateUtils.compareDates(earliestDate, latestDate);
165 if (compareResult == 1) {
168 earliestDate = latestDate;
171 // Check to see if the dates were reversed AND calculated. If they were
172 // Then this probably means the absolute earliestDate should have month and day as "1"
173 // and the latestDate momth 12, day 31.
174 if ((earliestDate.getMonth() == 12 && earliestDate.getDay() == 31) &&
175 (latestDate.getMonth() == 1 && latestDate.getDay() == 1)) {
176 earliestDate.setMonth(1);
177 earliestDate.setDay(1);
178 latestDate.setMonth(12);
179 latestDate.setDay(31);
184 // If the earliest date and the latest date are the same, it's just a "single" date.
185 // There's no need to have the latest, so set it to null.
187 if (earliestDate.equals(latestDate)) {
191 result.setEarliestSingleDate(earliestDate);
192 result.setLatestDate(latestDate);
196 public void exitBeforeOrAfterDate(BeforeOrAfterDateContext ctx) {
197 if (ctx.exception != null) return;
199 Date latestDate = (Date) stack.pop();
200 Date earliestDate = (Date) stack.pop();
202 // Set null eras to the default.
204 if (earliestDate.getEra() == null) {
205 earliestDate.setEra(Date.DEFAULT_ERA);
208 if (latestDate.getEra() == null) {
209 latestDate.setEra(Date.DEFAULT_ERA);
212 // Finalize any deferred calculations.
214 if (latestDate instanceof DeferredDate) {
215 ((DeferredDate) latestDate).resolveDate();
218 if (earliestDate instanceof DeferredDate) {
219 ((DeferredDate) earliestDate).resolveDate();
222 // Calculate the earliest date or end date.
224 if (ctx.BEFORE() != null) {
225 latestDate = earliestDate;
226 earliestDate = DateUtils.getEarliestBeforeDate(earliestDate, latestDate);
228 else if (ctx.AFTER() != null) {
229 earliestDate = latestDate;
230 latestDate = DateUtils.getLatestAfterDate(earliestDate, latestDate);
233 stack.push(earliestDate);
234 stack.push(latestDate);
238 public void exitUncertainDate(UncertainDateContext ctx) {
239 if (ctx.exception != null) return;
241 Date latestDate = (Date) stack.pop();
242 Date earliestDate = (Date) stack.pop();
245 int earliestInterval = DateUtils.getCircaIntervalYears(earliestDate.getYear(), earliestDate.getEra());
246 int latestInterval = DateUtils.getCircaIntervalYears(latestDate.getYear(), latestDate.getEra());
248 // Express the circa interval as a qualifier.
250 // stack.push(earliestDate.withQualifier(QualifierType.MINUS, earliestInterval, QualifierUnit.YEARS));
251 // stack.push(latestDate.withQualifier(QualifierType.PLUS, latestInterval, QualifierUnit.YEARS));
255 // Express the circa interval as an offset calculated into the year.
257 DateUtils.subtractYears(earliestDate, earliestInterval);
258 DateUtils.addYears(latestDate, latestInterval);
260 stack.push(earliestDate);
261 stack.push(latestDate);
265 public void exitCertainDate(CertainDateContext ctx) {
266 if (ctx.exception != null) return;
268 Date latestDate = (Date) stack.pop();
269 Date earliestDate = (Date) stack.pop();
271 // Set null eras to the default.
273 if (earliestDate.getEra() == null) {
274 earliestDate.setEra(Date.DEFAULT_ERA);
277 if (latestDate.getEra() == null) {
278 latestDate.setEra(Date.DEFAULT_ERA);
281 // Finalize any deferred calculations.
283 if (latestDate instanceof DeferredDate) {
284 ((DeferredDate) latestDate).resolveDate();
287 if (earliestDate instanceof DeferredDate) {
288 ((DeferredDate) earliestDate).resolveDate();
291 stack.push(earliestDate);
292 stack.push(latestDate);
296 public void exitHyphenatedRange(HyphenatedRangeContext ctx) {
297 if (ctx.exception != null) return;
299 Date latestEndDate = (Date) stack.pop();
300 stack.pop(); // latestStartDate
301 stack.pop(); // earliestEndDate
302 Date earliestStartDate = (Date) stack.pop();
304 // If no era was explicitly specified for the first date,
305 // make it inherit the era of the second date.
307 if (earliestStartDate.getEra() == null && latestEndDate.getEra() != null) {
308 earliestStartDate.setEra(latestEndDate.getEra());
311 // Finalize any deferred calculations.
313 if (earliestStartDate instanceof DeferredDate) {
314 ((DeferredDate) earliestStartDate).resolveDate();
317 if (latestEndDate instanceof DeferredDate) {
318 ((DeferredDate) latestEndDate).resolveDate();
321 stack.push(earliestStartDate);
322 stack.push(latestEndDate);
326 public void exitNthCenturyRange(NthCenturyRangeContext ctx) {
327 if (ctx.exception != null) return;
329 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
330 Integer endN = (Integer) stack.pop();
331 Part endPart = (Part) stack.pop();
332 Integer startN = (Integer) stack.pop();
333 Part startPart = (Part) stack.pop();
336 era = Date.DEFAULT_ERA;
339 int startYear = DateUtils.nthCenturyToYear(startN);
340 int endYear = DateUtils.nthCenturyToYear(endN);
342 stack.push(startPart == null ? DateUtils.getCenturyStartDate(startYear, era) : DateUtils.getPartialCenturyStartDate(startYear, startPart, era));
343 stack.push(startPart == null ? DateUtils.getCenturyEndDate(startYear, era) : DateUtils.getPartialCenturyEndDate(startYear, startPart, era));
344 stack.push(endPart == null ? DateUtils.getCenturyStartDate(endYear, era) : DateUtils.getPartialCenturyStartDate(endYear, endPart, era));
345 stack.push(endPart == null ? DateUtils.getCenturyEndDate(endYear, era) : DateUtils.getPartialCenturyEndDate(endYear, endPart, era));
349 public void exitMonthInYearRange(MonthInYearRangeContext ctx) {
350 if (ctx.exception != null) return;
352 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
353 Integer year = (Integer) stack.pop();
354 Integer numMonthEnd = (Integer) stack.pop();
355 Integer numMonthStart = (Integer) stack.pop();
357 stack.push(new Date(year, numMonthStart, 1, era));
358 stack.push(new Date(year, numMonthStart, DateUtils.getDaysInMonth(numMonthStart, year, era), era));
359 stack.push(new Date(year, numMonthEnd, 1, era));
360 stack.push(new Date(year, numMonthEnd, DateUtils.getDaysInMonth(numMonthEnd, year, era), era));
364 public void exitQuarterInYearRange(QuarterInYearRangeContext ctx) {
365 if (ctx.exception != null) return;
367 Era era = (Era) stack.pop();
368 Integer year = (Integer) stack.pop();
369 Integer lastQuarter = (Integer) stack.pop();
370 Integer firstQuarter = (Integer) stack.pop();
372 stack.push(DateUtils.getQuarterYearStartDate(firstQuarter, year).withEra(era));
373 stack.push(DateUtils.getQuarterYearEndDate(firstQuarter, year, era).withEra(era));
374 stack.push(DateUtils.getQuarterYearStartDate(lastQuarter, year).withEra(era));
375 stack.push(DateUtils.getQuarterYearEndDate(lastQuarter, year, era).withEra(era));
379 public void exitStrDayInMonthRange(StrDayInMonthRangeContext ctx) {
380 if (ctx.exception != null) return;
382 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
383 Integer year = (Integer) stack.pop();
384 Integer dayOfMonthEnd = (Integer) stack.pop();
385 Integer dayOfMonthStart = (Integer) stack.pop();
386 Integer numMonth = (Integer) stack.pop();
388 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
389 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
390 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
391 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
395 public void exitNumDayInMonthRange(NumDayInMonthRangeContext ctx) {
396 if (ctx.exception != null) return;
398 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
399 Integer num1 = (Integer) stack.pop();
400 Integer num2 = (Integer) stack.pop();
401 Integer num3 = (Integer) stack.pop();
402 Integer num4 = (Integer) stack.pop();
404 /* We can distinguish whether it is M/D-D/Y (Case 1) or M/Y-M/Y (Case 2) by checking if
405 The num1, num4, num2 and num1, num4, num3 are valid dates, since this would equate to
406 a set of two ranges. For examples: 04/13-19/1995 would be 04/13/1995-04/19/1995. If both these
407 dates are valid, we know that it shouldn't be interpreted as 04/01/13 - 19/31/1995 since these arent valid dates!
410 Integer lateYear = num1;
411 Integer earlyMonth = num4;
412 Integer dayOfMonthEnd = num2;
413 Integer dayOfMonthStart = num3;
415 if (DateUtils.isValidDate(num1, num4, num2, era) && DateUtils.isValidDate(num1, num4, num3, era)) {
416 // No need to alter the arguments, so just push to the stack
417 stack.push(new Date(lateYear, earlyMonth, dayOfMonthStart, era));
418 stack.push(new Date(lateYear, earlyMonth, dayOfMonthStart, era));
419 stack.push(new Date(lateYear, earlyMonth, dayOfMonthEnd, era));
420 stack.push(new Date(lateYear, earlyMonth, dayOfMonthEnd, era));
423 // Separated these by case, since it makes the code more legible
424 Integer latestMonth = num2;
425 Integer earliestYear = num3;
427 stack.push(new Date(earliestYear, earlyMonth, 1, era)); // Earliest Early Date
428 stack.push(new Date(earliestYear, earlyMonth, DateUtils.getDaysInMonth(earlyMonth, earliestYear, era), era)); // Latest Early Date
429 stack.push(new Date(lateYear, latestMonth, 1, era)); // Earliest Latest Date
430 stack.push(new Date(lateYear, latestMonth, DateUtils.getDaysInMonth(latestMonth, lateYear, era), era)); // Latest Late Date
436 public void exitDate(DateContext ctx) {
437 if (ctx.exception != null) return;
439 // Expect the canonical year-month-day-era ordering
440 // to be on the stack.
442 Era era = (stack.size() == 3) ? null : (Era) stack.pop();
443 Integer dayOfMonth = (Integer) stack.pop();
444 Integer numMonth = (Integer) stack.pop();
445 Integer year = (Integer) stack.pop();
447 // For the latest date we could either return null, or a copy of the earliest date,
448 // since the UI doesn't care. Use a copy of the earliest date, since it makes
449 // things easier here if we don't have to test for null up the tree.
451 stack.push(new Date(year, numMonth, dayOfMonth, era));
452 stack.push(new Date(year, numMonth, dayOfMonth, era));
456 public void exitNumDate(NumDateContext ctx) {
457 if (ctx.exception != null) return;
459 // This could either be year-month-day, or
460 // month-day-year. Try to determine which,
461 // and reorder the stack into the canonical
462 // year-month-day-era ordering.
464 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
465 Integer num3 = (Integer) stack.pop();
466 Integer num2 = (Integer) stack.pop();
467 Integer num1 = (Integer) stack.pop();
469 // Default to a month-day-year interpretation.
472 int dayOfMonth = num2;
475 if (DateUtils.isValidDate(num3, num1, num2, era)) {
476 // Interpreting as month-day-year produces a valid date. Go with it.
478 else if (DateUtils.isValidDate(num1, num2, num3, era)) {
479 // Interpreting as month-day-year doesn't produce a valid date, but
480 // year-month-day does. Go with year-month-day.
486 else if (DateUtils.isValidDate(num3, num2, num1, era)) {
487 // The date was of format day-month-year
493 stack.push(numMonth);
494 stack.push(dayOfMonth);
499 public void exitStrDate(StrDateContext ctx) {
500 if (ctx.exception != null) return;
502 // Reorder the stack into a canonical ordering,
503 // year-month-day-era.
505 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
506 Integer year = (Integer) stack.pop();
507 Integer dayOfMonth = (Integer) stack.pop();
508 Integer numMonth = (Integer) stack.pop();
511 stack.push(numMonth);
512 stack.push(dayOfMonth);
517 public void exitInvStrDate(InvStrDateContext ctx) {
518 if (ctx.exception != null) return;
520 // Reorder the stack into a canonical ordering,
521 // year-month-day-era.
523 Integer dayOfMonth = (Integer) stack.pop();
524 Integer numMonth = (Integer) stack.pop();
525 Integer year = (Integer) stack.pop();
526 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
529 stack.push(numMonth);
530 stack.push(dayOfMonth);
535 public void exitDayFirstDate(DayFirstDateContext ctx) {
536 if (ctx.exception != null) return ;
538 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
539 Integer year = (Integer) stack.pop();
540 Integer month = (Integer) stack.pop();
541 Integer dayOfMonth = (Integer) stack.pop();
545 stack.push(dayOfMonth);
550 public void exitDayOrYearFirstDate(DayOrYearFirstDateContext ctx) {
551 if (ctx.exception != null) return;
554 Integer num2 = (Integer) stack.pop();
555 Integer numMonth = (Integer) stack.pop();
556 Integer num1 = (Integer) stack.pop();
559 Integer dayOfMonth = num2;
561 if (DateUtils.isValidDate(num1, numMonth, num2, era)) {
562 // The first number is a year. Already correct
563 } else if (DateUtils.isValidDate(num2, numMonth, num1, era)) {
564 // The second number is a year.
570 stack.push(numMonth);
571 stack.push(dayOfMonth);
573 if (dayOfMonth > 31 || dayOfMonth <= 0) {
574 throw new StructuredDateFormatException("unexpected day of month '" + Integer.toString(dayOfMonth) + "'");
577 throw new StructuredDateFormatException("unexpected year '" + Integer.toString(year) + "'");
582 public void exitInvStrDateEraLastDate(InvStrDateEraLastDateContext ctx) {
583 if (ctx.exception != null) return;
585 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
586 Integer dayOfMonth = (Integer) stack.pop();
587 Integer month = (Integer) stack.pop();
588 Integer year = (Integer) stack.pop();
592 stack.push(dayOfMonth);
597 public void exitMonth(MonthContext ctx) {
598 if (ctx.exception != null) return;
600 Era era = (Era) stack.pop();
601 Integer year = (Integer) stack.pop();
602 Integer numMonth = (Integer) stack.pop();
604 stack.push(new Date(year, numMonth, 1, era));
605 stack.push(new Date(year, numMonth, DateUtils.getDaysInMonth(numMonth, year, era), era));
609 public void exitMonthYear(MonthYearContext ctx) {
610 if (ctx.exception != null) return;
612 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
618 public void exitInvMonthYear(InvMonthYearContext ctx) {
619 if (ctx.exception != null) return;
621 // Invert the arguments.
623 Integer numMonth = (Integer) stack.pop();
624 Integer year = (Integer) stack.pop();
625 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
627 stack.push(numMonth);
633 public void exitYearSpanningWinter(YearSpanningWinterContext ctx) {
634 if (ctx.exception != null) return;
636 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
637 Integer endYear = (Integer) stack.pop();
638 Integer startYear = (Integer) stack.pop();
640 stack.push(new Date(startYear, 12, 1).withEra(era));
641 stack.push(DateUtils.getQuarterYearEndDate(1, endYear, era).withEra(era));
645 public void exitPartialYear(PartialYearContext ctx) {
646 if (ctx.exception != null) return;
648 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
649 Integer year = (Integer) stack.pop();
650 Part part = (Part) stack.pop();
652 stack.push(DateUtils.getPartialYearStartDate(part, year).withEra(era));
653 stack.push(DateUtils.getPartialYearEndDate(part, year, era).withEra(era));
657 public void exitQuarterYear(QuarterYearContext ctx) {
658 if (ctx.exception != null) return;
660 Era era = (Era) stack.pop();
661 Integer year = (Integer) stack.pop();
662 Integer quarter = (Integer) stack.pop();
664 stack.push(DateUtils.getQuarterYearStartDate(quarter, year).withEra(era));
665 stack.push(DateUtils.getQuarterYearEndDate(quarter, year, era).withEra(era));
669 public void exitHalfYear(HalfYearContext ctx) {
670 if (ctx.exception != null) return;
672 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
673 Integer year = (Integer) stack.pop();
674 Integer half = (Integer) stack.pop();
676 stack.push(DateUtils.getHalfYearStartDate(half, year).withEra(era));
677 stack.push(DateUtils.getHalfYearEndDate(half, year, era).withEra(era));
681 public void exitInvSeasonYear(InvSeasonYearContext ctx) {
682 if (ctx.exception != null) return;
684 // Invert the arguments.
686 Integer quarter = (Integer) stack.pop();
687 Integer year = (Integer) stack.pop();
688 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
696 public void exitSeasonYear(SeasonYearContext ctx) {
697 if (ctx.exception != null) return;
699 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
705 public void exitYear(YearContext ctx) {
706 if (ctx.exception != null) return;
708 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
709 Integer year = (Integer) stack.pop();
711 stack.push(new Date(year, 1, 1, era));
712 stack.push(new Date(year, 12, 31, era));
716 public void exitPartialDecade(PartialDecadeContext ctx) {
717 if (ctx.exception != null) return;
719 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
720 Integer year = (Integer) stack.pop();
721 Part part = (Part) stack.pop();
724 // If the era was explicitly specified, the start and end years
725 // may be calculated now.
727 stack.push(DateUtils.getPartialDecadeStartDate(year, part, era));
728 stack.push(DateUtils.getPartialDecadeEndDate(year, part, era));
731 // If the era was not explicitly specified, the start and end years
732 // can't be calculated yet. The calculation must be deferred until
733 // later. For example, this partial decade may be the start of a hyphenated
734 // range, where the era will be inherited from the era of the end of
735 // the range; this era won't be known until farther up the parse tree,
736 // when both sides of the range will have been parsed.
738 stack.push(new DeferredPartialDecadeStartDate(year, part));
739 stack.push(new DeferredPartialDecadeEndDate(year, part));
744 public void exitDecade(DecadeContext ctx) {
745 if (ctx.exception != null) return;
747 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
748 Integer year = (Integer) stack.pop();
750 // Calculate the start and end year of the decade, which depends on the era.
753 // If the era was explicitly specified, the start and end years
754 // may be calculated now.
756 stack.push(DateUtils.getDecadeStartDate(year, era));
757 stack.push(DateUtils.getDecadeEndDate(year, era));
760 // If the era was not explicitly specified, the start and end years
761 // can't be calculated yet. The calculation must be deferred until
762 // later. For example, this decade may be the start of a hyphenated
763 // range, where the era will be inherited from the era of the end of
764 // the range; this era won't be known until farther up the parse tree,
765 // when both sides of the range will have been parsed.
767 stack.push(new DeferredDecadeStartDate(year));
768 stack.push(new DeferredDecadeEndDate(year));
773 public void exitPartialCentury(PartialCenturyContext ctx) {
774 if (ctx.exception != null) return;
776 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
777 Integer year = (Integer) stack.pop();
778 Part part = (Part) stack.pop();
781 // If the era was explicitly specified, the start and end years
782 // may be calculated now.
784 stack.push(DateUtils.getPartialCenturyStartDate(year, part, era));
785 stack.push(DateUtils.getPartialCenturyEndDate(year, part, era));
788 // If the era was not explicitly specified, the start and end years
789 // can't be calculated yet. The calculation must be deferred until
790 // later. For example, this partial century may be the start of a hyphenated
791 // range, where the era will be inherited from the era of the end of
792 // the range; this era won't be known until farther up the parse tree,
793 // when both sides of the range will have been parsed.
795 stack.push(new DeferredPartialCenturyStartDate(year, part));
796 stack.push(new DeferredPartialCenturyEndDate(year, part));
801 public void exitQuarterCentury(QuarterCenturyContext ctx) {
802 if (ctx.exception != null) return;
804 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
805 Integer year = (Integer) stack.pop();
806 Integer quarter = (Integer) stack.pop();
809 // If the era was explicitly specified, the start and end years
810 // may be calculated now.
812 stack.push(DateUtils.getQuarterCenturyStartDate(year, quarter, era));
813 stack.push(DateUtils.getQuarterCenturyEndDate(year, quarter, era));
816 // If the era was not explicitly specified, the start and end years
817 // can't be calculated yet. The calculation must be deferred until
818 // later. For example, this century may be the start of a hyphenated
819 // range, where the era will be inherited from the era of the end of
820 // the range; this era won't be known until farther up the parse tree,
821 // when both sides of the range will have been parsed.
823 stack.push(new DeferredQuarterCenturyStartDate(year, quarter));
824 stack.push(new DeferredQuarterCenturyEndDate(year, quarter));
829 public void exitHalfCentury(HalfCenturyContext ctx) {
830 if (ctx.exception != null) return;
832 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
833 Integer year = (Integer) stack.pop();
834 Integer half = (Integer) stack.pop();
837 // If the era was explicitly specified, the start and end years
838 // may be calculated now.
840 stack.push(DateUtils.getHalfCenturyStartDate(year, half, era));
841 stack.push(DateUtils.getHalfCenturyEndDate(year, half, era));
844 // If the era was not explicitly specified, the start and end years
845 // can't be calculated yet. The calculation must be deferred until
846 // later. For example, this half century may be the start of a hyphenated
847 // range, where the era will be inherited from the era of the end of
848 // the range; this era won't be known until farther up the parse tree,
849 // when both sides of the range will have been parsed.
851 stack.push(new DeferredHalfCenturyStartDate(year, half));
852 stack.push(new DeferredHalfCenturyEndDate(year, half));
857 public void exitCentury(CenturyContext ctx) {
858 if (ctx.exception != null) return;
860 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
861 Integer year = (Integer) stack.pop();
864 // If the era was explicitly specified, the start and end years
865 // may be calculated now.
867 stack.push(DateUtils.getCenturyStartDate(year, era));
868 stack.push(DateUtils.getCenturyEndDate(year, era));
871 // If the era was not explicitly specified, the start and end years
872 // can't be calculated yet. The calculation must be deferred until
873 // later. For example, this quarter century may be the start of a hyphenated
874 // range, where the era will be inherited from the era of the end of
875 // the range; this era won't be known until farther up the parse tree,
876 // when both sides of the range will have been parsed.
878 stack.push(new DeferredCenturyStartDate(year));
879 stack.push(new DeferredCenturyEndDate(year));
884 public void exitMillennium(MillenniumContext ctx) {
885 if (ctx.exception != null) return;
887 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
888 Integer n = (Integer) stack.pop();
891 // If the era was explicitly specified, the start and end years
892 // may be calculated now.
894 stack.push(DateUtils.getMillenniumStartDate(n, era));
895 stack.push(DateUtils.getMillenniumEndDate(n, era));
898 // If the era was not explicitly specified, the start and end years
899 // can't be calculated yet. The calculation must be deferred until
900 // later. For example, this millennium may be the start of a hyphenated
901 // range, where the era will be inherited from the era of the end of
902 // the range; this era won't be known until farther up the parse tree,
903 // when both sides of the range will have been parsed.
905 stack.push(new DeferredMillenniumStartDate(n));
906 stack.push(new DeferredMillenniumEndDate(n));
911 public void exitStrCentury(StrCenturyContext ctx) {
912 if (ctx.exception != null) return;
914 Integer n = (Integer) stack.pop();
916 // Convert the nth number to a year number,
917 // and push on the stack.
919 Integer year = DateUtils.nthCenturyToYear(n);
925 public void exitNumCentury(NumCenturyContext ctx) {
926 if (ctx.exception != null) return;
928 // Convert the string to a number,
929 // and push on the stack.
931 Integer year = new Integer(stripEndLetters(ctx.HUNDREDS().getText()));
934 throw new StructuredDateFormatException("unexpected century '" + ctx.HUNDREDS().getText() + "'");
941 public void exitNumDecade(NumDecadeContext ctx) {
942 if (ctx.exception != null) return;
944 // Convert the string to a number,
945 // and push on the stack.
947 Integer year = new Integer(stripEndLetters(ctx.TENS().getText()));
950 throw new StructuredDateFormatException("unexpected decade '" + ctx.TENS().getText() + "'");
957 public void exitNumYear(NumYearContext ctx) {
958 if (ctx.exception != null) return;
960 // Convert the string to a number,
961 // and push on the stack.
963 Integer year = new Integer(ctx.getText().replaceAll(",", ""));
966 throw new StructuredDateFormatException("unexpected year '" + ctx.NUMBER().getText() + "'");
973 public void exitNumMonth(NumMonthContext ctx) {
974 if (ctx.exception != null) return;
976 // Convert the string a number,
977 // and push on the stack.
979 Integer month = new Integer(ctx.NUMBER().getText());
981 if (month < 1 || month > 12) {
982 throw new StructuredDateFormatException("unexpected month '" + ctx.NUMBER().getText() + "'");
989 public void exitNthHalf(NthHalfContext ctx) {
990 if (ctx.exception != null) return;
992 // Convert LAST to a number (the last half
993 // is the 2nd). If this rule matched the
994 // alternative with nth instead of LAST,
995 // the nth handler will already have pushed
996 // a number on the stack.
998 if (ctx.LAST() != null) {
999 stack.push(new Integer(2));
1002 // Check for a valid half.
1004 Integer n = (Integer) stack.peek();
1006 if (n < 1 || n > 2) {
1007 throw new StructuredDateFormatException("unexpected half '" + n + "'");
1013 public void exitNthQuarterInYearRange(NthQuarterInYearRangeContext ctx) {
1014 if (ctx.exception != null) return;
1016 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1022 public void exitStrSeasonInYearRange(StrSeasonInYearRangeContext ctx) {
1023 if (ctx.exception != null) return;
1025 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1032 public void exitNthQuarterYear(NthQuarterYearContext ctx) {
1034 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1040 public void exitNthQuarter(NthQuarterContext ctx) {
1041 if (ctx.exception != null) return;
1043 // Convert LAST to a number (the last quarter
1044 // is the 4th). If this rule matched the
1045 // alternative with nth instead of LAST,
1046 // the nth handler will already have pushed
1047 // a number on the stack.
1049 if (ctx.LAST() != null) {
1050 stack.push(new Integer(4));
1053 // Check for a valid quarter.
1055 Integer n = (Integer) stack.peek();
1057 if (n < 1 || n > 4) {
1058 throw new StructuredDateFormatException("unexpected quarter '" + n + "'");
1063 public void exitNth(NthContext ctx) {
1064 if (ctx.exception != null) return;
1066 // Convert the string to a number,
1067 // and push on the stack.
1071 if (ctx.NTHSTR() != null) {
1072 n = new Integer(stripEndLetters(ctx.NTHSTR().getText()));
1074 else if (ctx.FIRST() != null) {
1077 else if (ctx.SECOND() != null) {
1080 else if (ctx.THIRD() != null) {
1083 else if (ctx.FOURTH() != null) {
1091 public void exitStrMonth(StrMonthContext ctx) {
1092 if (ctx.exception != null) return;
1094 // Convert the month name to a number,
1095 // and push on the stack.
1097 TerminalNode monthNode = ctx.MONTH();
1099 if (monthNode == null) {
1100 monthNode = ctx.SHORTMONTH();
1103 String monthStr = monthNode.getText();
1105 stack.push(DateUtils.getMonthByName(monthStr));
1109 public void exitStrSeason(StrSeasonContext ctx) {
1110 if (ctx.exception != null) return;
1112 // Convert the season to a quarter number,
1113 // and push on the stack.
1115 Integer quarter = null;
1117 if (ctx.WINTER() != null) {
1120 else if (ctx.SPRING() != null) {
1123 else if (ctx.SUMMER() != null) {
1126 else if (ctx.FALL() != null) {
1130 stack.push(quarter);
1134 public void exitAllOrPartOf(AllOrPartOfContext ctx) {
1135 if (ctx.exception != null) return;
1137 // If a part was specified, it will have been
1138 // pushed on the stack in exitPartOf(). If not,
1139 // push null on the stack.
1141 if (ctx.partOf() == null) {
1147 public void exitPartOf(PartOfContext ctx) {
1148 if (ctx.exception != null) return;
1150 // Convert the token to a Part,
1151 // and push on the stack.
1155 if (ctx.EARLY() != null) {
1158 else if (ctx.MIDDLE() != null) {
1161 else if (ctx.LATE() != null) {
1169 public void exitEra(EraContext ctx) {
1170 if (ctx.exception != null) return;
1172 // Convert the token to an Era,
1173 // and push on the stack.
1177 if (ctx.BC() != null) {
1180 else if (ctx.AD() != null) {
1188 public void exitNumDayOfMonth(NumDayOfMonthContext ctx) {
1189 if (ctx.exception != null) return;
1191 // Convert the numeric string to an Integer,
1192 // and push on the stack.
1194 Integer dayOfMonth = new Integer(ctx.NUMBER().getText());
1196 if (dayOfMonth == 0 || dayOfMonth > 31) {
1197 throw new StructuredDateFormatException("unexpected day of month '" + ctx.NUMBER().getText() + "'");
1200 stack.push(dayOfMonth);
1204 public void exitNum(NumContext ctx) {
1205 if (ctx.exception != null) return;
1207 // Convert the numeric string to an Integer,
1208 // and push on the stack.
1210 Integer num = new Integer(ctx.getText().replaceAll(",", ""));
1216 public void exitRomanNum(RomanNumContext ctx) {
1218 int num = DateUtils.romanToDecimal(ctx.ROMANNUMBER().getText());
1220 if (num < 1 || num > 12) {
1221 throw new StructuredDateFormatException("unexpected month '" + Integer.toString(num) + "'");
1228 public void exitUnknownDate(UnknownDateContext ctx) {
1229 if (ctx.exception != null) return;
1232 stack.push(new Date());
1233 stack.push(new Date());
1236 public void exitUncalibratedDate(UncalibratedDateContext ctx) {
1237 if (ctx.exception != null) return;
1239 Integer adjustmentDate = (Integer) stack.pop();
1240 Integer mainYear = (Integer) stack.pop();
1242 Integer upperBound = mainYear + adjustmentDate;
1243 Integer lowerBound = mainYear - adjustmentDate;
1245 Integer currentYear = DateUtils.getCurrentDate().getYear();
1247 Integer earliestYear = currentYear - upperBound;
1248 Integer latestYear = currentYear - lowerBound ;
1250 // If negative, then BC, else AD
1251 Era earliestEra = earliestYear < 0 ? Era.BCE : Era.CE;
1252 Era latestEra = latestYear < 0 ? Era.BCE : Era.CE;
1254 stack.push(new Date(Math.abs(earliestYear), 1, 1, earliestEra)); // Earliest Early Date
1255 stack.push(new Date(Math.abs(latestYear), 12, DateUtils.getDaysInMonth(12, Math.abs(latestYear), latestEra), latestEra)); // Latest Late Date
1259 protected String getErrorMessage(RecognitionException re) {
1260 String message = "";
1262 Parser recognizer = (Parser) re.getRecognizer();
1263 TokenStream tokens = recognizer.getInputStream();
1265 if (re instanceof NoViableAltException) {
1266 NoViableAltException e = (NoViableAltException) re;
1267 Token startToken = e.getStartToken();
1268 String input = (startToken.getType() == Token.EOF ) ? "end of text" : quote(tokens.getText(startToken, e.getOffendingToken()));
1270 message = "no viable date format found at " + input;
1272 else if (re instanceof InputMismatchException) {
1273 InputMismatchException e = (InputMismatchException) re;
1274 message = "did not expect " + getTokenDisplayString(e.getOffendingToken()) + " while looking for " +
1275 e.getExpectedTokens().toString(recognizer.getTokenNames());
1277 else if (re instanceof FailedPredicateException) {
1278 FailedPredicateException e = (FailedPredicateException) re;
1279 String ruleName = recognizer.getRuleNames()[recognizer.getContext().getRuleIndex()];
1281 message = "failed predicate " + ruleName + ": " + e.getMessage();
1287 protected String quote(String text) {
1288 return "'" + text + "'";
1291 protected String getTokenDisplayString(Token token) {
1294 if (token == null) {
1295 string = "[no token]";
1298 String text = token.getText();
1301 if (token.getType() == Token.EOF ) {
1302 string = "end of text";
1305 string = "[" + token.getType() + "]";
1309 string = quote(text);
1316 protected String stripEndLetters(String input) {
1317 return input.replaceAll("[^\\d]+$", "");
1320 public static void main(String[] args) {
1321 StructuredDateEvaluator evaluator = new ANTLRStructuredDateEvaluator();
1323 for (String displayDate : args) {
1325 evaluator.evaluate(displayDate);
1326 } catch (StructuredDateFormatException e) {
1327 e.printStackTrace();