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