@Override
  public void exitDisplayDate(DisplayDateContext ctx) {
    if (ctx.exception != null) return;

    Date latestDate = (Date) stack.pop();
    Date earliestDate = (Date) stack.pop();

    // If the earliest date and the latest date are the same, it's just a "single" date.
    // There's no need to have the latest, so set it to null.

    if (earliestDate.equals(latestDate)) {
      latestDate = null;
    }

    result.setEarliestSingleDate(earliestDate);
    result.setLatestDate(latestDate);
  }
  @Override
  public void exitHyphenatedRange(HyphenatedRangeContext ctx) {
    if (ctx.exception != null) return;

    Date latestEndDate = (Date) stack.pop();
    stack.pop(); // latestStartDate
    stack.pop(); // earliestEndDate
    Date earliestStartDate = (Date) stack.pop();

    // If no era was explicitly specified for the first date,
    // make it inherit the era of the second date.

    if (earliestStartDate.getEra() == null && latestEndDate.getEra() != null) {
      earliestStartDate.setEra(latestEndDate.getEra());
    }

    // Finalize any deferred calculations.

    if (earliestStartDate instanceof DeferredDate) {
      ((DeferredDate) earliestStartDate).resolveDate();
    }

    if (latestEndDate instanceof DeferredDate) {
      ((DeferredDate) latestEndDate).resolveDate();
    }

    stack.push(earliestStartDate);
    stack.push(latestEndDate);
  }
  @Override
  public void exitCertainDate(CertainDateContext ctx) {
    if (ctx.exception != null) return;

    Date latestDate = (Date) stack.pop();
    Date earliestDate = (Date) stack.pop();

    // Set null eras to the default.

    if (earliestDate.getEra() == null) {
      earliestDate.setEra(Date.DEFAULT_ERA);
    }

    if (latestDate.getEra() == null) {
      latestDate.setEra(Date.DEFAULT_ERA);
    }

    // Finalize any deferred calculations.

    if (latestDate instanceof DeferredDate) {
      ((DeferredDate) latestDate).resolveDate();
    }

    if (earliestDate instanceof DeferredDate) {
      ((DeferredDate) earliestDate).resolveDate();
    }

    stack.push(earliestDate);
    stack.push(latestDate);
  }
  @Override
  public void exitUncertainDate(UncertainDateContext ctx) {
    if (ctx.exception != null) return;

    Date latestDate = (Date) stack.pop();
    Date earliestDate = (Date) stack.pop();

    int earliestInterval =
        DateUtils.getCircaIntervalYears(earliestDate.getYear(), earliestDate.getEra());
    int latestInterval = DateUtils.getCircaIntervalYears(latestDate.getYear(), latestDate.getEra());

    // Express the circa interval as a qualifier.

    // stack.push(earliestDate.withQualifier(QualifierType.MINUS, earliestInterval,
    // QualifierUnit.YEARS));
    // stack.push(latestDate.withQualifier(QualifierType.PLUS, latestInterval,
    // QualifierUnit.YEARS));

    // OR:

    // Express the circa interval as an offset calculated into the year.

    DateUtils.subtractYears(earliestDate, earliestInterval);
    DateUtils.addYears(latestDate, latestInterval);

    stack.push(earliestDate);
    stack.push(latestDate);
  }
  @Override
  public void exitBeforeOrAfterDate(BeforeOrAfterDateContext ctx) {
    if (ctx.exception != null) return;

    Date latestDate = (Date) stack.pop();
    Date earliestDate = (Date) stack.pop();

    // Set null eras to the default.

    if (earliestDate.getEra() == null) {
      earliestDate.setEra(Date.DEFAULT_ERA);
    }

    if (latestDate.getEra() == null) {
      latestDate.setEra(Date.DEFAULT_ERA);
    }

    // Finalize any deferred calculations.

    if (latestDate instanceof DeferredDate) {
      ((DeferredDate) latestDate).resolveDate();
    }

    if (earliestDate instanceof DeferredDate) {
      ((DeferredDate) earliestDate).resolveDate();
    }

    // Calculate the earliest date or end date.

    if (ctx.BEFORE() != null) {
      latestDate = earliestDate;
      earliestDate = DateUtils.getEarliestBeforeDate(earliestDate, latestDate);
    } else if (ctx.AFTER() != null) {
      earliestDate = latestDate;
      latestDate = DateUtils.getLatestAfterDate(earliestDate, latestDate);
    }

    stack.push(earliestDate);
    stack.push(latestDate);
  }