]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
6328de05e5d5dd2005a12b0ce13cdafdff51778f
[tmp/jakarta-migration.git] /
1 package org.collectionspace.services.structureddate.antlr;
2
3 import java.util.regex.Matcher;
4 import java.util.regex.Pattern;
5 import java.util.Stack;
6
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.PartialEraRangeContext;
80 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.PartialYearContext;
81 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.RomanDateContext;
82 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterCenturyContext;
83 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterInYearRangeContext;
84 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.QuarterYearContext;
85 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.RomanMonthContext;
86 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.SeasonYearContext;
87 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrCenturyContext;
88 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrDateContext;
89 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrDayInMonthRangeContext;
90 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrMonthContext;
91 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrSeasonContext;
92 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.StrSeasonInYearRangeContext;
93 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.UncalibratedDateContext;
94 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.UncertainDateContext;
95 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.UnknownDateContext;
96 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.YearContext;
97 import org.collectionspace.services.structureddate.antlr.StructuredDateParser.YearSpanningWinterContext;
98
99 /**
100  * A StructuredDateEvaluator that uses an ANTLR parser to parse the display date,
101  * and an ANTLR listener to generate a structured date from the resulting parse
102  * tree.
103  */
104 public class ANTLRStructuredDateEvaluator extends StructuredDateBaseListener implements StructuredDateEvaluator {
105         /**
106          * The result of the evaluation.
107          */
108         protected StructuredDateInternal result;
109         private final int BP_ZERO_YEAR = 1950;
110
111         /**
112          * The operation stack. The parse listener methods that are implemented here
113          * pop input parameters off the stack, and push results back on to the stack.
114          */
115         protected Stack<Object> stack;
116
117         public ANTLRStructuredDateEvaluator() {
118
119         }
120
121         /**
122          * Normalizes a display date for evaluation.
123          * - Remove leading and trailing whitespace
124          * - Remove leading and trailing braces
125          * - Convert to lowercase
126          *
127          * @param displayDate
128          * @return The normalized display date
129          */
130         protected String normalizeDisplayDate(String displayDate) {
131                 String normalDisplayDate =
132                         displayDate
133                                 .replaceAll("^[\\[\\(\\{\\s]+|[\\]\\)\\}\\s]+$", "")
134                                 .toLowerCase();
135
136                 return normalDisplayDate;
137         }
138
139         @Override
140         public StructuredDateInternal evaluate(String displayDate) throws StructuredDateFormatException {
141                 stack = new Stack<Object>();
142
143                 result = new StructuredDateInternal();
144                 result.setDisplayDate(displayDate);
145
146                 // Instantiate a parser from the normalized display date.
147                 ANTLRInputStream inputStream = new ANTLRInputStream(normalizeDisplayDate(displayDate));
148                 StructuredDateLexer lexer = new StructuredDateLexer(inputStream);
149                 CommonTokenStream tokenStream = new CommonTokenStream(lexer);
150                 StructuredDateParser parser = new StructuredDateParser(tokenStream);
151
152                 // Don't try to recover from parse errors, just bail.
153                 parser.setErrorHandler(new BailErrorStrategy());
154
155                 // Don't print error messages to the console.
156                 parser.removeErrorListeners();
157
158                 // Generate our own custom error messages.
159                 parser.addParseListener(this);
160
161                 try {
162                         // Attempt to fulfill the oneDisplayDate rule of the grammar.
163                         parser.oneDisplayDate();
164                 }
165                 catch(ParseCancellationException e) {
166                         // ParseCancellationException is thrown by the BailErrorStrategy when there is a
167                         // parse error, with the underlying RecognitionException as the cause.
168                         RecognitionException re = (RecognitionException) e.getCause();
169
170                         throw new StructuredDateFormatException(getErrorMessage(re), re);
171                 }
172
173                 result.computeScalarValues();
174
175                 // The parsing was successful. Return the result.
176                 return result;
177         }
178
179         @Override
180         public void exitDisplayDate(DisplayDateContext ctx) {
181                 if (ctx.exception != null) return;
182
183                 Date latestDate = (Date) stack.pop();
184                 Date earliestDate = (Date) stack.pop();
185
186                 if (earliestDate.getYear() != null || earliestDate.getYear() != null) {
187                         int compareResult = DateUtils.compareDates(earliestDate, latestDate);
188                         if (compareResult == 1) {
189                                 Date temp;
190                                 temp = earliestDate;
191                                 earliestDate = latestDate;
192                                 latestDate = temp;
193
194                                 // Check to see if the dates were reversed AND calculated. If they were
195                                 // Then this probably means the absolute earliestDate should have month and day as "1"
196                                 // and the latestDate momth 12, day 31.
197                                 if ((earliestDate.getMonth() == 12 && earliestDate.getDay() == 31) &&
198                                         (latestDate.getMonth() == 1 && latestDate.getDay() == 1)) {
199                                                 earliestDate.setMonth(1);
200                                                 earliestDate.setDay(1);
201                                                 latestDate.setMonth(12);
202                                                 latestDate.setDay(31);
203                                         }
204                         }
205                 }
206
207                 // If the earliest date and the latest date are the same, it's just a "single" date.
208                 // There's no need to have the latest, so set it to null.
209
210                 if (earliestDate.equals(latestDate)) {
211                         latestDate = null;
212                 }
213
214                 result.setEarliestSingleDate(earliestDate);
215                 result.setLatestDate(latestDate);
216         }
217
218         @Override
219         public void exitBeforeOrAfterDate(BeforeOrAfterDateContext ctx) {
220                 if (ctx.exception != null) return;
221
222                 Date latestDate = (Date) stack.pop();
223                 Date earliestDate = (Date) stack.pop();
224
225                 // Set null eras to the default.
226
227                 if (earliestDate.getEra() == null) {
228                         earliestDate.setEra(Date.DEFAULT_ERA);
229                 }
230
231                 if (latestDate.getEra() == null) {
232                         latestDate.setEra(Date.DEFAULT_ERA);
233                 }
234
235                 // Finalize any deferred calculations.
236
237                 if (latestDate instanceof DeferredDate) {
238                         ((DeferredDate) latestDate).resolveDate();
239                 }
240
241                 if (earliestDate instanceof DeferredDate) {
242                         ((DeferredDate) earliestDate).resolveDate();
243                 }
244
245                 // Calculate the earliest date or end date.
246
247                 if (ctx.BEFORE() != null) {
248                         latestDate = earliestDate;
249                         earliestDate = DateUtils.getEarliestBeforeDate(earliestDate, latestDate);
250                 }
251                 else if (ctx.AFTER() != null) {
252                         earliestDate = latestDate;
253                         latestDate = DateUtils.getLatestAfterDate(earliestDate, latestDate);
254                 }
255
256                 stack.push(earliestDate);
257                 stack.push(latestDate);
258         }
259
260         @Override
261         public void exitUncertainDate(UncertainDateContext ctx) {
262                 if (ctx.exception != null) return;
263
264                 Date latestDate = (Date) stack.pop();
265                 Date earliestDate = (Date) stack.pop();
266
267
268                 int earliestInterval = DateUtils.getCircaIntervalYears(earliestDate.getYear(), earliestDate.getEra());
269                 int latestInterval = DateUtils.getCircaIntervalYears(latestDate.getYear(), latestDate.getEra());
270
271                 // Express the circa interval as a qualifier.
272
273                 // stack.push(earliestDate.withQualifier(QualifierType.MINUS, earliestInterval, QualifierUnit.YEARS));
274                 // stack.push(latestDate.withQualifier(QualifierType.PLUS, latestInterval, QualifierUnit.YEARS));
275
276                 // OR:
277
278                 // Express the circa interval as an offset calculated into the year.
279
280                 DateUtils.subtractYears(earliestDate, earliestInterval);
281                 DateUtils.addYears(latestDate, latestInterval);
282
283                 stack.push(earliestDate);
284                 stack.push(latestDate);
285         }
286
287         @Override
288         public void exitCertainDate(CertainDateContext ctx) {
289                 if (ctx.exception != null) return;
290
291                 Date latestDate = (Date) stack.pop();
292                 Date earliestDate = (Date) stack.pop();
293
294                 // Set null eras to the default.
295
296                 if (earliestDate.getEra() == null) {
297                         earliestDate.setEra(Date.DEFAULT_ERA);
298                 }
299
300                 if (latestDate.getEra() == null) {
301                         latestDate.setEra(Date.DEFAULT_ERA);
302                 }
303
304                 // Finalize any deferred calculations.
305
306                 if (latestDate instanceof DeferredDate) {
307                         ((DeferredDate) latestDate).resolveDate();
308                 }
309
310                 if (earliestDate instanceof DeferredDate) {
311                         ((DeferredDate) earliestDate).resolveDate();
312                 }
313
314                 stack.push(earliestDate);
315                 stack.push(latestDate);
316         }
317
318         @Override
319         public void exitHyphenatedRange(HyphenatedRangeContext ctx) {
320                 if (ctx.exception != null) return;
321
322                 Date latestEndDate = (Date) stack.pop();
323                 stack.pop(); // latestStartDate
324                 stack.pop(); // earliestEndDate
325                 Date earliestStartDate = (Date) stack.pop();
326
327                 // If no era was explicitly specified for the first date,
328                 // make it inherit the era of the second date.
329
330                 if (earliestStartDate.getEra() == null && latestEndDate.getEra() != null) {
331                         earliestStartDate.setEra(latestEndDate.getEra());
332                 }
333
334                 // Finalize any deferred calculations.
335
336                 if (earliestStartDate instanceof DeferredDate) {
337                         ((DeferredDate) earliestStartDate).resolveDate();
338                 }
339
340                 if (latestEndDate instanceof DeferredDate) {
341                         ((DeferredDate) latestEndDate).resolveDate();
342                 }
343
344                 stack.push(earliestStartDate);
345                 stack.push(latestEndDate);
346         }
347
348         @Override
349         public void exitNthCenturyRange(NthCenturyRangeContext ctx) {
350                 if (ctx.exception != null) return;
351
352                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
353                 Integer endN = (Integer) stack.pop();
354                 Part endPart = (Part) stack.pop();
355                 Integer startN = (Integer) stack.pop();
356                 Part startPart = (Part) stack.pop();
357
358                 if (era == null) {
359                         era = Date.DEFAULT_ERA;
360                 }
361
362                 int startYear = DateUtils.nthCenturyToYear(startN);
363                 int endYear = DateUtils.nthCenturyToYear(endN);
364
365                 stack.push(startPart == null ? DateUtils.getCenturyStartDate(startYear, era) : DateUtils.getPartialCenturyStartDate(startYear, startPart, era));
366                 stack.push(startPart == null ? DateUtils.getCenturyEndDate(startYear, era) : DateUtils.getPartialCenturyEndDate(startYear, startPart, era));
367                 stack.push(endPart == null ? DateUtils.getCenturyStartDate(endYear, era) : DateUtils.getPartialCenturyStartDate(endYear, endPart, era));
368                 stack.push(endPart == null ? DateUtils.getCenturyEndDate(endYear, era) : DateUtils.getPartialCenturyEndDate(endYear, endPart, era));
369         }
370
371         @Override
372         public void exitMonthInYearRange(MonthInYearRangeContext ctx) {
373                 if (ctx.exception != null) return;
374
375                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
376                 Integer year = (Integer) stack.pop();
377                 Integer numMonthEnd = (Integer) stack.pop();
378                 Integer numMonthStart = (Integer) stack.pop();
379
380                 stack.push(new Date(year, numMonthStart, 1, era));
381                 stack.push(new Date(year, numMonthStart, DateUtils.getDaysInMonth(numMonthStart, year, era), era));
382                 stack.push(new Date(year, numMonthEnd, 1, era));
383                 stack.push(new Date(year, numMonthEnd, DateUtils.getDaysInMonth(numMonthEnd, year, era), era));
384         }
385
386         @Override
387         public void exitQuarterInYearRange(QuarterInYearRangeContext ctx) {
388                 if (ctx.exception != null) return;
389
390                 Era era = (Era) stack.pop();
391                 Integer year = (Integer) stack.pop();
392                 Integer lastQuarter = (Integer) stack.pop();
393                 Integer firstQuarter = (Integer) stack.pop();
394
395                 stack.push(DateUtils.getQuarterYearStartDate(firstQuarter, year).withEra(era));
396                 stack.push(DateUtils.getQuarterYearEndDate(firstQuarter, year, era).withEra(era));
397                 stack.push(DateUtils.getQuarterYearStartDate(lastQuarter, year).withEra(era));
398                 stack.push(DateUtils.getQuarterYearEndDate(lastQuarter, year, era).withEra(era));
399         }
400
401         @Override
402         public void exitStrDayInMonthRange(StrDayInMonthRangeContext ctx) {
403                 if (ctx.exception != null) return;
404
405                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
406                 Integer year = (Integer) stack.pop();
407                 Integer dayOfMonthEnd = (Integer) stack.pop();
408                 Integer dayOfMonthStart = (Integer) stack.pop();
409                 Integer numMonth = (Integer) stack.pop();
410
411                 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
412                 stack.push(new Date(year, numMonth, dayOfMonthStart, era));
413                 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
414                 stack.push(new Date(year, numMonth, dayOfMonthEnd, era));
415         }
416
417         @Override
418         public void exitNumDayInMonthRange(NumDayInMonthRangeContext ctx) {
419                 if (ctx.exception != null) return;
420
421                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
422                 Integer num1 = (Integer) stack.pop();
423                 Integer num2 = (Integer) stack.pop();
424                 Integer num3 = (Integer) stack.pop();
425                 Integer num4 = (Integer) stack.pop();
426
427                 /* We can distinguish whether it is M/D-D/Y (Case 1) or M/Y-M/Y (Case 2) by checking if
428                         The num1, num4, num2 and num1, num4, num3 are valid dates, since this would equate to
429                         a set of two ranges. For examples: 04/13-19/1995 would be 04/13/1995-04/19/1995. If both these
430                         dates are valid, we know that it shouldn't be interpreted as 04/01/13 - 19/31/1995 since these arent valid dates!
431                 */
432
433                 Integer lateYear = num1;
434                 Integer earlyMonth = num4;
435                 Integer dayOfMonthEnd = num2;
436                 Integer dayOfMonthStart = num3;
437
438                 if (DateUtils.isValidDate(num1, num4, num2, era) && DateUtils.isValidDate(num1, num4, num3, era)) {
439                         // No need to alter the arguments, so just push to the stack
440                         stack.push(new Date(lateYear, earlyMonth, dayOfMonthStart, era));
441                         stack.push(new Date(lateYear, earlyMonth, dayOfMonthStart, era));
442                         stack.push(new Date(lateYear, earlyMonth, dayOfMonthEnd, era));
443                         stack.push(new Date(lateYear, earlyMonth, dayOfMonthEnd, era));
444
445                 } else {
446                         // Separated these by case, since it makes the code more legible
447                         Integer latestMonth = num2;
448                         Integer earliestYear = num3;
449
450                         stack.push(new Date(earliestYear, earlyMonth, 1, era)); // Earliest Early Date
451                         stack.push(new Date(earliestYear, earlyMonth, DateUtils.getDaysInMonth(earlyMonth, earliestYear, era), era)); // Latest Early Date
452                         stack.push(new Date(lateYear, latestMonth, 1, era)); // Earliest Latest Date
453                         stack.push(new Date(lateYear, latestMonth, DateUtils.getDaysInMonth(latestMonth, lateYear, era), era)); // Latest Late Date
454
455                 }
456         }
457
458         @Override
459         public void exitDate(DateContext ctx) {
460                 if (ctx.exception != null) return;
461
462                 // Expect the canonical year-month-day-era ordering
463                 // to be on the stack.
464
465                 Era era = (stack.size() == 3) ? null : (Era) stack.pop();
466                 Integer dayOfMonth = (Integer) stack.pop();
467                 Integer numMonth = (Integer) stack.pop();
468                 Integer year = (Integer) stack.pop();
469
470                 // For the latest date we could either return null, or a copy of the earliest date,
471                 // since the UI doesn't care. Use a copy of the earliest date, since it makes
472                 // things easier here if we don't have to test for null up the tree.
473
474                 stack.push(new Date(year, numMonth, dayOfMonth, era));
475                 stack.push(new Date(year, numMonth, dayOfMonth, era));
476         }
477
478         @Override
479         public void exitNumDate(NumDateContext ctx) {
480                 if (ctx.exception != null) return;
481
482                 // This could either be year-month-day, or
483                 // month-day-year. Try to determine which,
484                 // and reorder the stack into the canonical
485                 // year-month-day-era ordering.
486
487                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
488                 Integer num3 = (Integer) stack.pop();
489                 Integer num2 = (Integer) stack.pop();
490                 Integer num1 = (Integer) stack.pop();
491
492                 // Default to a month-day-year interpretation.
493
494                 int numMonth = num1;
495                 int dayOfMonth = num2;
496                 int year = num3;
497
498                 if (DateUtils.isValidDate(num3, num1, num2, era)) {
499                         // Interpreting as month-day-year produces a valid date. Go with it.
500                 }
501                 else if (DateUtils.isValidDate(num1, num2, num3, era)) {
502                         // Interpreting as month-day-year doesn't produce a valid date, but
503                         // year-month-day does. Go with year-month-day.
504
505                         year = num1;
506                         numMonth = num2;
507                         dayOfMonth = num3;
508                 }
509
510                 stack.push(year);
511                 stack.push(numMonth);
512                 stack.push(dayOfMonth);
513                 stack.push(era);
514         }
515
516         @Override
517         public void exitStrDate(StrDateContext ctx) {
518                 if (ctx.exception != null) return;
519
520                 // Reorder the stack into a canonical ordering,
521                 // year-month-day-era.
522
523                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
524                 Integer year = (Integer) stack.pop();
525                 Integer dayOfMonth = (Integer) stack.pop();
526                 Integer numMonth = (Integer) stack.pop();
527
528                 stack.push(year);
529                 stack.push(numMonth);
530                 stack.push(dayOfMonth);
531                 stack.push(era);
532         }
533
534         @Override
535         public void exitInvStrDate(InvStrDateContext ctx) {
536                 if (ctx.exception != null) return;
537
538                 // Reorder the stack into a canonical ordering,
539                 // year-month-day-era.
540
541                 Integer dayOfMonth = (Integer) stack.pop();
542                 Integer numMonth = (Integer) stack.pop();
543                 Integer year = (Integer) stack.pop();
544                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
545
546                 stack.push(year);
547                 stack.push(numMonth);
548                 stack.push(dayOfMonth);
549                 stack.push(era);
550         }
551
552         @Override
553         public void exitDayFirstDate(DayFirstDateContext ctx) {
554                 if (ctx.exception != null) return ;
555
556                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
557                 Integer year = (Integer) stack.pop();
558                 Integer month = (Integer) stack.pop();
559                 Integer dayOfMonth = (Integer) stack.pop();
560
561                 stack.push(year);
562                 stack.push(month);
563                 stack.push(dayOfMonth);
564                 stack.push(era);
565         }
566
567         @Override
568         public void exitDayOrYearFirstDate(DayOrYearFirstDateContext ctx) {
569                 if (ctx.exception != null) return;
570
571                 Era era = null;
572                 Integer num2 = (Integer) stack.pop();
573                 Integer numMonth = (Integer) stack.pop();
574                 Integer num1 = (Integer) stack.pop();
575
576                 Integer year = num1;
577                 Integer dayOfMonth = num2;
578
579                 if (DateUtils.isValidDate(num1, numMonth, num2, era)) {
580                         // The first number is a year. Already correct
581                 } else if (DateUtils.isValidDate(num2, numMonth, num1, era)) {
582                         // The second number is a year.
583                         year = num2;
584                         dayOfMonth = num1;
585                 }
586
587                 stack.push(year);
588                 stack.push(numMonth);
589                 stack.push(dayOfMonth);
590
591                 if (dayOfMonth > 31 || dayOfMonth <= 0) {
592                         throw new StructuredDateFormatException("unexpected day of month '" + Integer.toString(dayOfMonth) + "'");
593                 }
594                 if (year == 0) {
595                         throw new StructuredDateFormatException("unexpected year '" + Integer.toString(year) + "'");
596                 }
597         }
598
599         @Override
600         public void exitInvStrDateEraLastDate(InvStrDateEraLastDateContext ctx) {
601                 if (ctx.exception != null) return;
602
603                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
604                 Integer dayOfMonth = (Integer) stack.pop();
605                 Integer month = (Integer) stack.pop();
606                 Integer year = (Integer) stack.pop();
607
608                 stack.push(year);
609                 stack.push(month);
610                 stack.push(dayOfMonth);
611                 stack.push(era);
612         }
613
614         @Override
615         public void exitMonth(MonthContext ctx) {
616                 if (ctx.exception != null) return;
617
618                 Era era = (Era) stack.pop();
619                 Integer year = (Integer) stack.pop();
620                 Integer numMonth = (Integer) stack.pop();
621
622                 stack.push(new Date(year, numMonth, 1, era));
623                 stack.push(new Date(year, numMonth, DateUtils.getDaysInMonth(numMonth, year, era), era));
624         }
625
626         @Override
627         public void exitMonthYear(MonthYearContext ctx) {
628                 if (ctx.exception != null) return;
629
630                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
631
632                 stack.push(era);
633         }
634
635         @Override
636         public void exitInvMonthYear(InvMonthYearContext ctx) {
637                 if (ctx.exception != null) return;
638
639                 // Invert the arguments.
640
641                 Integer numMonth = (Integer) stack.pop();
642                 Integer year = (Integer) stack.pop();
643                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
644
645                 stack.push(numMonth);
646                 stack.push(year);
647                 stack.push(era);
648         }
649
650         @Override
651         public void exitYearSpanningWinter(YearSpanningWinterContext ctx) {
652                 if (ctx.exception != null) return;
653
654                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
655                 Integer endYear = (Integer) stack.pop();
656                 Integer startYear = (Integer) stack.pop();
657
658                 stack.push(new Date(startYear, 12, 1).withEra(era));
659                 stack.push(DateUtils.getQuarterYearEndDate(1, endYear, era).withEra(era));
660         }
661
662         @Override
663         public void exitPartialYear(PartialYearContext ctx) {
664                 if (ctx.exception != null) return;
665
666                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
667                 Integer year = (Integer) stack.pop();
668                 Part part = (Part) stack.pop();
669
670                 stack.push(DateUtils.getPartialYearStartDate(part, year).withEra(era));
671                 stack.push(DateUtils.getPartialYearEndDate(part, year, era).withEra(era));
672         }
673
674         @Override
675         public void exitQuarterYear(QuarterYearContext ctx) {
676                 if (ctx.exception != null) return;
677
678                 Era era = (Era) stack.pop();
679                 Integer year = (Integer) stack.pop();
680                 Integer quarter = (Integer) stack.pop();
681
682                 stack.push(DateUtils.getQuarterYearStartDate(quarter, year).withEra(era));
683                 stack.push(DateUtils.getQuarterYearEndDate(quarter, year, era).withEra(era));
684         }
685
686         @Override
687         public void exitHalfYear(HalfYearContext ctx) {
688                 if (ctx.exception != null) return;
689
690                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
691                 Integer year = (Integer) stack.pop();
692                 Integer half = (Integer) stack.pop();
693
694                 stack.push(DateUtils.getHalfYearStartDate(half, year).withEra(era));
695                 stack.push(DateUtils.getHalfYearEndDate(half, year, era).withEra(era));
696         }
697
698         @Override
699         public void exitInvSeasonYear(InvSeasonYearContext ctx) {
700                 if (ctx.exception != null) return;
701
702                 // Invert the arguments.
703
704                 Integer quarter = (Integer) stack.pop();
705                 Integer year = (Integer) stack.pop();
706                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
707
708                 stack.push(quarter);
709                 stack.push(year);
710                 stack.push(era);
711         }
712
713         @Override
714         public void exitSeasonYear(SeasonYearContext ctx) {
715                 if (ctx.exception != null) return;
716
717                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
718                 stack.push(era);
719
720         }
721
722         @Override
723         public void exitYear(YearContext ctx) {
724                 if (ctx.exception != null) return;
725
726                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
727                 Integer year = (Integer) stack.pop();
728
729                 stack.push(new Date(year, 1, 1, era));
730                 stack.push(new Date(year, 12, 31, era));
731         }
732
733         @Override
734         public void exitPartialDecade(PartialDecadeContext ctx) {
735                 if (ctx.exception != null) return;
736
737                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
738                 Integer year = (Integer) stack.pop();
739                 Part part = (Part) stack.pop();
740
741                 if (era != null) {
742                         // If the era was explicitly specified, the start and end years
743                         // may be calculated now.
744
745                         stack.push(DateUtils.getPartialDecadeStartDate(year, part, era));
746                         stack.push(DateUtils.getPartialDecadeEndDate(year, part, era));
747                 }
748                 else {
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 partial decade 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.
755
756                         stack.push(new DeferredPartialDecadeStartDate(year, part));
757                         stack.push(new DeferredPartialDecadeEndDate(year, part));
758                 }
759         }
760
761         @Override
762         public void exitDecade(DecadeContext ctx) {
763                 if (ctx.exception != null) return;
764
765                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
766                 Integer year = (Integer) stack.pop();
767
768                 // Calculate the start and end year of the decade, which depends on the era.
769
770                 if (era != null) {
771                         // If the era was explicitly specified, the start and end years
772                         // may be calculated now.
773
774                         stack.push(DateUtils.getDecadeStartDate(year, era));
775                         stack.push(DateUtils.getDecadeEndDate(year, era));
776                 }
777                 else {
778                         // If the era was not explicitly specified, the start and end years
779                         // can't be calculated yet. The calculation must be deferred until
780                         // later. For example, this decade may be the start of a hyphenated
781                         // range, where the era will be inherited from the era of the end of
782                         // the range; this era won't be known until farther up the parse tree,
783                         // when both sides of the range will have been parsed.
784
785                         stack.push(new DeferredDecadeStartDate(year));
786                         stack.push(new DeferredDecadeEndDate(year));
787                 }
788         }
789
790         @Override
791         public void exitPartialCentury(PartialCenturyContext ctx) {
792                 if (ctx.exception != null) return;
793
794                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
795                 Integer year = (Integer) stack.pop();
796                 Part part = (Part) stack.pop();
797
798                 if (era != null) {
799                         // If the era was explicitly specified, the start and end years
800                         // may be calculated now.
801
802                         stack.push(DateUtils.getPartialCenturyStartDate(year, part, era));
803                         stack.push(DateUtils.getPartialCenturyEndDate(year, part, era));
804                 }
805                 else {
806                         // If the era was not explicitly specified, the start and end years
807                         // can't be calculated yet. The calculation must be deferred until
808                         // later. For example, this partial century may be the start of a hyphenated
809                         // range, where the era will be inherited from the era of the end of
810                         // the range; this era won't be known until farther up the parse tree,
811                         // when both sides of the range will have been parsed.
812
813                         stack.push(new DeferredPartialCenturyStartDate(year, part));
814                         stack.push(new DeferredPartialCenturyEndDate(year, part));
815                 }
816         }
817
818         @Override
819         public void exitQuarterCentury(QuarterCenturyContext ctx) {
820                 if (ctx.exception != null) return;
821
822                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
823                 Integer year = (Integer) stack.pop();
824                 Integer quarter = (Integer) stack.pop();
825
826                 if (era != null) {
827                         // If the era was explicitly specified, the start and end years
828                         // may be calculated now.
829
830                         stack.push(DateUtils.getQuarterCenturyStartDate(year, quarter, era));
831                         stack.push(DateUtils.getQuarterCenturyEndDate(year, quarter, era));
832                 }
833                 else {
834                         // If the era was not explicitly specified, the start and end years
835                         // can't be calculated yet. The calculation must be deferred until
836                         // later. For example, this century may be the start of a hyphenated
837                         // range, where the era will be inherited from the era of the end of
838                         // the range; this era won't be known until farther up the parse tree,
839                         // when both sides of the range will have been parsed.
840
841                         stack.push(new DeferredQuarterCenturyStartDate(year, quarter));
842                         stack.push(new DeferredQuarterCenturyEndDate(year, quarter));
843                 }
844         }
845
846         @Override
847         public void exitHalfCentury(HalfCenturyContext ctx) {
848                 if (ctx.exception != null) return;
849
850                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
851                 Integer year = (Integer) stack.pop();
852                 Integer half = (Integer) stack.pop();
853
854                 if (era != null) {
855                         // If the era was explicitly specified, the start and end years
856                         // may be calculated now.
857
858                         stack.push(DateUtils.getHalfCenturyStartDate(year, half, era));
859                         stack.push(DateUtils.getHalfCenturyEndDate(year, half, era));
860                 }
861                 else {
862                         // If the era was not explicitly specified, the start and end years
863                         // can't be calculated yet. The calculation must be deferred until
864                         // later. For example, this half century may be the start of a hyphenated
865                         // range, where the era will be inherited from the era of the end of
866                         // the range; this era won't be known until farther up the parse tree,
867                         // when both sides of the range will have been parsed.
868
869                         stack.push(new DeferredHalfCenturyStartDate(year, half));
870                         stack.push(new DeferredHalfCenturyEndDate(year, half));
871                 }
872         }
873
874         @Override
875         public void exitCentury(CenturyContext ctx) {
876                 if (ctx.exception != null) return;
877
878                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
879                 Integer year = (Integer) stack.pop();
880
881                 if (era != null) {
882                         // If the era was explicitly specified, the start and end years
883                         // may be calculated now.
884
885                         stack.push(DateUtils.getCenturyStartDate(year, era));
886                         stack.push(DateUtils.getCenturyEndDate(year, era));
887                 }
888                 else {
889                         // If the era was not explicitly specified, the start and end years
890                         // can't be calculated yet. The calculation must be deferred until
891                         // later. For example, this quarter century may be the start of a hyphenated
892                         // range, where the era will be inherited from the era of the end of
893                         // the range; this era won't be known until farther up the parse tree,
894                         // when both sides of the range will have been parsed.
895
896                         stack.push(new DeferredCenturyStartDate(year));
897                         stack.push(new DeferredCenturyEndDate(year));
898                 }
899         }
900
901         @Override
902         public void exitMillennium(MillenniumContext ctx) {
903                 if (ctx.exception != null) return;
904
905                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
906                 Integer n = (Integer) stack.pop();
907
908                 if (era != null) {
909                         // If the era was explicitly specified, the start and end years
910                         // may be calculated now.
911
912                         stack.push(DateUtils.getMillenniumStartDate(n, era));
913                         stack.push(DateUtils.getMillenniumEndDate(n, era));
914                 }
915                 else {
916                         // If the era was not explicitly specified, the start and end years
917                         // can't be calculated yet. The calculation must be deferred until
918                         // later. For example, this millennium may be the start of a hyphenated
919                         // range, where the era will be inherited from the era of the end of
920                         // the range; this era won't be known until farther up the parse tree,
921                         // when both sides of the range will have been parsed.
922
923                         stack.push(new DeferredMillenniumStartDate(n));
924                         stack.push(new DeferredMillenniumEndDate(n));
925                 }
926         }
927
928         @Override
929         public void exitStrCentury(StrCenturyContext ctx) {
930                 if (ctx.exception != null) return;
931
932                 Integer n = (Integer) stack.pop();
933
934                 // Convert the nth number to a year number,
935                 // and push on the stack.
936
937                 Integer year = DateUtils.nthCenturyToYear(n);
938
939                 stack.push(year);
940         }
941
942         @Override
943         public void exitNumCentury(NumCenturyContext ctx) {
944                 if (ctx.exception != null) return;
945
946                 // Convert the string to a number,
947                 // and push on the stack.
948
949                 Integer year = new Integer(stripEndLetters(ctx.HUNDREDS().getText()));
950
951                 if (year == 0) {
952                         throw new StructuredDateFormatException("unexpected century '" + ctx.HUNDREDS().getText() + "'");
953                 }
954
955                 stack.push(year);
956         }
957
958         @Override
959         public void exitNumDecade(NumDecadeContext ctx) {
960                 if (ctx.exception != null) return;
961
962                 // Convert the string to a number,
963                 // and push on the stack.
964
965                 Integer year = new Integer(stripEndLetters(ctx.TENS().getText()));
966
967                 if (year == 0) {
968                         throw new StructuredDateFormatException("unexpected decade '" + ctx.TENS().getText() + "'");
969                 }
970
971                 stack.push(year);
972         }
973
974         @Override
975         public void exitNumYear(NumYearContext ctx) {
976                 if (ctx.exception != null) return;
977
978                 // Convert the string to a number,
979                 // and push on the stack.
980
981                 Integer year = new Integer(ctx.getText().replaceAll(",", ""));
982
983                 if (year == 0) {
984                         throw new StructuredDateFormatException("unexpected year '" + ctx.NUMBER().getText() + "'");
985                 }
986
987                 stack.push(year);
988         }
989
990         @Override
991         public void exitNumMonth(NumMonthContext ctx) {
992                 if (ctx.exception != null) return;
993
994                 // Convert the string a number,
995                 // and push on the stack.
996
997                 Integer month = new Integer(ctx.NUMBER().getText());
998
999                 if (month < 1 || month > 12) {
1000                         throw new StructuredDateFormatException("unexpected month '" + ctx.NUMBER().getText() + "'");
1001                 }
1002
1003                 stack.push(month);
1004         }
1005
1006         @Override
1007         public void exitNthHalf(NthHalfContext ctx) {
1008                 if (ctx.exception != null) return;
1009
1010                 // Convert LAST to a number (the last half
1011                 // is the 2nd). If this rule matched the
1012                 // alternative with nth instead of LAST,
1013                 // the nth handler will already have pushed
1014                 // a number on the stack.
1015
1016                 if (ctx.LAST() != null) {
1017                         stack.push(new Integer(2));
1018                 }
1019
1020                 // Check for a valid half.
1021
1022                 Integer n = (Integer) stack.peek();
1023
1024                 if (n < 1 || n > 2) {
1025                         throw new StructuredDateFormatException("unexpected half '" + n + "'");
1026                 }
1027         }
1028
1029
1030         @Override
1031         public void exitNthQuarterInYearRange(NthQuarterInYearRangeContext ctx) {
1032                 if (ctx.exception != null) return;
1033
1034                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1035
1036                 stack.push(era);
1037         }
1038
1039         @Override
1040         public void exitStrSeasonInYearRange(StrSeasonInYearRangeContext ctx) {
1041                 if (ctx.exception != null) return;
1042
1043                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1044
1045                 stack.push(era);
1046
1047         }
1048
1049         @Override
1050         public void exitNthQuarterYear(NthQuarterYearContext ctx) {
1051
1052                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1053
1054                 stack.push(era);
1055         }
1056
1057         @Override
1058         public void exitNthQuarter(NthQuarterContext ctx) {
1059                 if (ctx.exception != null) return;
1060
1061                 // Convert LAST to a number (the last quarter
1062                 // is the 4th). If this rule matched the
1063                 // alternative with nth instead of LAST,
1064                 // the nth handler will already have pushed
1065                 // a number on the stack.
1066
1067                 if (ctx.LAST() != null) {
1068                         stack.push(new Integer(4));
1069                 }
1070
1071                 // Check for a valid quarter.
1072
1073                 Integer n = (Integer) stack.peek();
1074
1075                 if (n < 1 || n > 4) {
1076                         throw new StructuredDateFormatException("unexpected quarter '" + n + "'");
1077                 }
1078         }
1079
1080         @Override
1081         public void exitNth(NthContext ctx) {
1082                 if (ctx.exception != null) return;
1083
1084                 // Convert the string to a number,
1085                 // and push on the stack.
1086
1087                 Integer n = null;
1088
1089                 if (ctx.NTHSTR() != null) {
1090                         n = new Integer(stripEndLetters(ctx.NTHSTR().getText()));
1091                 }
1092                 else if (ctx.FIRST() != null) {
1093                         n = 1;
1094                 }
1095                 else if (ctx.SECOND() != null) {
1096                         n = 2;
1097                 }
1098                 else if (ctx.THIRD() != null) {
1099                         n = 3;
1100                 }
1101                 else if (ctx.FOURTH() != null) {
1102                         n = 4;
1103                 }
1104
1105                 stack.push(n);
1106         }
1107
1108         @Override
1109         public void exitStrMonth(StrMonthContext ctx) {
1110                 if (ctx.exception != null) return;
1111
1112                 // Convert the month name to a number,
1113                 // and push on the stack.
1114
1115                 TerminalNode monthNode = ctx.MONTH();
1116
1117                 if (monthNode == null) {
1118                         monthNode = ctx.SHORTMONTH();
1119                 }
1120
1121                 String monthStr = monthNode.getText();
1122
1123                 stack.push(DateUtils.getMonthByName(monthStr));
1124         }
1125
1126         @Override
1127         public void exitStrSeason(StrSeasonContext ctx) {
1128                 if (ctx.exception != null) return;
1129
1130                 // Convert the season to a quarter number,
1131                 // and push on the stack.
1132
1133                 Integer quarter = null;
1134
1135                 if (ctx.WINTER() != null) {
1136                         quarter = 1;
1137                 }
1138                 else if (ctx.SPRING() != null) {
1139                         quarter = 2;
1140                 }
1141                 else if (ctx.SUMMER() != null) {
1142                         quarter = 3;
1143                 }
1144                 else if (ctx.FALL() != null) {
1145                         quarter = 4;
1146                 }
1147
1148                 stack.push(quarter);
1149         }
1150
1151         @Override
1152         public void exitAllOrPartOf(AllOrPartOfContext ctx) {
1153                 if (ctx.exception != null) return;
1154
1155                 // If a part was specified, it will have been
1156                 // pushed on the stack in exitPartOf(). If not,
1157                 // push null on the stack.
1158
1159                 if (ctx.partOf() == null) {
1160                         stack.push(null);
1161                 }
1162         }
1163
1164         @Override
1165         public void exitPartOf(PartOfContext ctx) {
1166                 if (ctx.exception != null) return;
1167
1168                 // Convert the token to a Part,
1169                 // and push on the stack.
1170
1171                 Part part = null;
1172
1173                 if (ctx.EARLY() != null) {
1174                         part = Part.EARLY;
1175                 }
1176                 else if (ctx.MIDDLE() != null) {
1177                         part = Part.MIDDLE;
1178                 }
1179                 else if (ctx.LATE() != null) {
1180                         part = Part.LATE;
1181                 }
1182
1183                 stack.push(part);
1184         }
1185
1186         @Override
1187         public void exitEra(EraContext ctx) {
1188                 if (ctx.exception != null) return;
1189
1190                 // Convert the token to an Era,
1191                 // and push on the stack.
1192
1193                 Era era = null;
1194
1195                 if (ctx.BC() != null) {
1196                         era = Era.BCE;
1197                 }
1198                 else if (ctx.AD() != null) {
1199                         era = Era.CE;
1200                 }
1201
1202                 stack.push(era);
1203         }
1204
1205         @Override
1206         public void exitNumDayOfMonth(NumDayOfMonthContext ctx) {
1207                 if (ctx.exception != null) return;
1208
1209                 // Convert the numeric string to an Integer,
1210                 // and push on the stack.
1211
1212                 Integer dayOfMonth = new Integer(ctx.NUMBER().getText());
1213
1214                 if (dayOfMonth == 0 || dayOfMonth > 31) {
1215                         throw new StructuredDateFormatException("unexpected day of month '" + ctx.NUMBER().getText() + "'");
1216                 }
1217
1218                 stack.push(dayOfMonth);
1219         }
1220
1221         @Override
1222         public void exitPartialEraRange(PartialEraRangeContext ctx) {
1223                 if (ctx.exception != null) return;
1224
1225                 Integer secondYear = (Integer) stack.pop();
1226                 Integer secondMonth = (Integer) stack.pop();
1227                 Integer secondDay = (Integer) stack.pop();
1228
1229                 Era era = (Era) stack.pop();
1230                 Integer firstYear = (Integer) stack.pop();
1231                 Integer firstMonth = (Integer) stack.pop();
1232                 Integer firstDay = (Integer) stack.pop();
1233
1234                 stack.push(new Date(secondYear, secondMonth, secondDay, null));
1235                 stack.push(new Date(firstYear, firstMonth, firstDay, era));
1236         }
1237
1238         @Override
1239         public void exitNum(NumContext ctx) {
1240                 if (ctx.exception != null) return;
1241
1242                 // Convert the numeric string to an Integer,
1243                 // and push on the stack.
1244
1245                 Integer num = new Integer(ctx.getText().replaceAll(",", ""));
1246
1247                 stack.push(num);
1248         }
1249
1250         @Override
1251         public void exitRomanMonth(RomanMonthContext ctx) {
1252                 int num = DateUtils.romanToDecimal(ctx.ROMANMONTH().getText());
1253
1254                 stack.push(num);
1255         }
1256
1257         @Override
1258         public void exitRomanDate(RomanDateContext ctx) {
1259                 if (ctx.exception != null) return;
1260
1261                 Era era = (ctx.era() == null) ? null : (Era) stack.pop();
1262                 Integer year = (Integer) stack.pop();
1263                 Integer month = (Integer) stack.pop();
1264                 Integer day = (Integer) stack.pop();
1265
1266                 stack.push(year);
1267                 stack.push(month);
1268                 stack.push(day);
1269                 stack.push(era);
1270         }
1271
1272         @Override
1273         public void exitUnknownDate(UnknownDateContext ctx) {
1274                 if (ctx.exception != null) return;
1275
1276                 // Dummy dates
1277                 stack.push(new Date());
1278                 stack.push(new Date());
1279         }
1280
1281         public void exitUncalibratedDate(UncalibratedDateContext ctx) {
1282                 if (ctx.exception != null) return;
1283
1284                 Integer adjustmentDate = (Integer) stack.pop();
1285                 Integer mainYear = (Integer) stack.pop();
1286
1287                 Integer earliestYear = (BP_ZERO_YEAR - mainYear) - adjustmentDate;
1288                 Integer latestYear = (BP_ZERO_YEAR - mainYear) + adjustmentDate;
1289
1290                 // If negative, then BC, else AD
1291                 Era earliestEra = earliestYear < 0 ? Era.BCE : Era.CE;
1292                 Era latestEra = latestYear < 0 ? Era.BCE : Era.CE;
1293
1294                 stack.push(new Date(Math.abs(earliestYear), 1, 1, earliestEra)); // Earliest Early Date
1295                 stack.push(new Date(Math.abs(latestYear), 12, DateUtils.getDaysInMonth(12, Math.abs(latestYear), latestEra), latestEra)); // Latest Late Date
1296
1297         }
1298
1299         protected String getErrorMessage(RecognitionException re) {
1300                 String message = "";
1301
1302                 Parser recognizer = (Parser) re.getRecognizer();
1303                 TokenStream tokens = recognizer.getInputStream();
1304
1305                 if (re instanceof NoViableAltException) {
1306                         NoViableAltException e = (NoViableAltException) re;
1307                         Token startToken = e.getStartToken();
1308                         String input = (startToken.getType() == Token.EOF ) ? "end of text" : quote(tokens.getText(startToken, e.getOffendingToken()));
1309
1310                         message = "no viable date format found at " + input;
1311                 }
1312                 else if (re instanceof InputMismatchException) {
1313                         InputMismatchException e = (InputMismatchException) re;
1314                         message = "did not expect " + getTokenDisplayString(e.getOffendingToken()) + " while looking for " +
1315                                   e.getExpectedTokens().toString(recognizer.getTokenNames());
1316                 }
1317                 else if (re instanceof FailedPredicateException) {
1318                         FailedPredicateException e = (FailedPredicateException) re;
1319                         String ruleName = recognizer.getRuleNames()[recognizer.getContext().getRuleIndex()];
1320
1321                         message = "failed predicate " + ruleName + ": " + e.getMessage();
1322                 }
1323
1324                 return message;
1325         }
1326
1327         protected String quote(String text) {
1328                 return "'" + text + "'";
1329         }
1330
1331         protected String getTokenDisplayString(Token token) {
1332                 String string;
1333
1334                 if (token == null) {
1335                         string = "[no token]";
1336                 }
1337                 else {
1338                         String text = token.getText();
1339
1340                         if (text == null) {
1341                                 if (token.getType() == Token.EOF ) {
1342                                         string = "end of text";
1343                                 }
1344                                 else {
1345                                         string = "[" + token.getType() + "]";
1346                                 }
1347                         }
1348                         else {
1349                                 string = quote(text);
1350                         }
1351                 }
1352
1353                 return string;
1354         }
1355
1356         protected String stripEndLetters(String input) {
1357                 return input.replaceAll("[^\\d]+$", "");
1358         }
1359
1360         public static void main(String[] args) {
1361                 StructuredDateEvaluator evaluator = new ANTLRStructuredDateEvaluator();
1362
1363                 for (String displayDate : args) {
1364                         try {
1365                                 evaluator.evaluate(displayDate);
1366                         } catch (StructuredDateFormatException e) {
1367                                 e.printStackTrace();
1368                         }
1369                 }
1370         }
1371 }