private DateTimeData addDuration(DateTimeData date, DateTimeData addto, DateTimeData duration) {

    // REVISIT: some code could be shared between normalize() and this method,
    //         however is it worth moving it? The structures are different...
    //

    resetDateObj(duration);
    // add months (may be modified additionaly below)
    int temp = addto.month + date.month;
    duration.month = modulo(temp, 1, 13);
    int carry = fQuotient(temp, 1, 13);

    // add years (may be modified additionaly below)
    duration.year = addto.year + date.year + carry;

    // add seconds
    double dtemp = addto.second + date.second;
    carry = (int) Math.floor(dtemp / 60);
    duration.second = dtemp - carry * 60;

    // add minutes
    temp = addto.minute + date.minute + carry;
    carry = fQuotient(temp, 60);
    duration.minute = mod(temp, 60, carry);

    // add hours
    temp = addto.hour + date.hour + carry;
    carry = fQuotient(temp, 24);
    duration.hour = mod(temp, 24, carry);

    duration.day = addto.day + date.day + carry;

    while (true) {

      temp = maxDayInMonthFor(duration.year, duration.month);
      if (duration.day < 1) { // original duration was negative
        duration.day = duration.day + maxDayInMonthFor(duration.year, duration.month - 1);
        carry = -1;
      } else if (duration.day > temp) {
        duration.day = duration.day - temp;
        carry = 1;
      } else {
        break;
      }
      temp = duration.month + carry;
      duration.month = modulo(temp, 1, 13);
      duration.year = duration.year + fQuotient(temp, 1, 13);
    }

    duration.utc = 'Z';
    return duration;
  }
  /**
   * If timezone present - normalize dateTime [E Adding durations to dateTimes]
   *
   * @param date CCYY-MM-DDThh:mm:ss+03
   */
  protected void normalize(DateTimeData date) {

    // REVISIT: we have common code in addDuration() for durations
    //          should consider reorganizing it.
    //

    // add minutes (from time zone)
    int negate = -1;

    if (DEBUG) {
      System.out.println("==>date.minute" + date.minute);
      System.out.println("==>date.timezoneMin" + date.timezoneMin);
    }
    int temp = date.minute + negate * date.timezoneMin;
    int carry = fQuotient(temp, 60);
    date.minute = mod(temp, 60, carry);

    if (DEBUG) {
      System.out.println("==>carry: " + carry);
    }
    // add hours
    temp = date.hour + negate * date.timezoneHr + carry;
    carry = fQuotient(temp, 24);
    date.hour = mod(temp, 24, carry);
    if (DEBUG) {
      System.out.println("==>date.hour" + date.hour);
      System.out.println("==>carry: " + carry);
    }

    date.day = date.day + carry;

    while (true) {
      temp = maxDayInMonthFor(date.year, date.month);
      if (date.day < 1) {
        date.day = date.day + maxDayInMonthFor(date.year, date.month - 1);
        carry = -1;
      } else if (date.day > temp) {
        date.day = date.day - temp;
        carry = 1;
      } else {
        break;
      }
      temp = date.month + carry;
      date.month = modulo(temp, 1, 13);
      date.year = date.year + fQuotient(temp, 1, 13);
      if (date.year == 0 && !Constants.SCHEMA_1_1_SUPPORT) {
        date.year = (date.timezoneHr < 0 || date.timezoneMin < 0) ? 1 : -1;
      }
    }
    date.utc = 'Z';
  }
  /**
   * Parses date CCYY-MM
   *
   * @param buffer
   * @param start start position
   * @param end end position
   * @param date
   * @exception RuntimeException
   */
  protected int getYearMonth(String buffer, int start, int end, DateTimeData date)
      throws RuntimeException {

    if (buffer.charAt(0) == '-') {
      // REVISIT: date starts with preceding '-' sign
      //          do we have to do anything with it?
      //
      start++;
    }
    int i = indexOf(buffer, start, end, '-');
    if (i == -1) throw new RuntimeException("Year separator is missing or misplaced");
    int length = i - start;
    if (length < 4) {
      throw new RuntimeException("Year must have 'CCYY' format");
    } else if (length > 4 && buffer.charAt(start) == '0') {
      throw new RuntimeException(
          "Leading zeros are required if the year value would otherwise have fewer than four digits; otherwise they are forbidden");
    }
    date.year = parseIntYear(buffer, i);
    if (buffer.charAt(i) != '-') {
      throw new RuntimeException("CCYY must be followed by '-' sign");
    }
    start = ++i;
    i = start + 2;
    date.month = parseInt(buffer, start, i);
    return i; // fStart points right after the MONTH
  }
  /**
   * Validates given date/time object accoring to W3C PR Schema [D.1 ISO 8601 Conventions]
   *
   * @param data
   */
  protected void validateDateTime(DateTimeData data) {

    // REVISIT: should we throw an exception for not valid dates
    //          or reporting an error message should be sufficient?

    /** XML Schema 1.1 - RQ-123: Allow year 0000 in date related types. */
    if (!Constants.SCHEMA_1_1_SUPPORT && data.year == 0) {
      throw new RuntimeException("The year \"0000\" is an illegal year value");
    }

    if (data.month < 1 || data.month > 12) {
      throw new RuntimeException("The month must have values 1 to 12");
    }

    // validate days
    if (data.day > maxDayInMonthFor(data.year, data.month) || data.day < 1) {
      throw new RuntimeException("The day must have values 1 to 31");
    }

    // validate hours
    if (data.hour > 23 || data.hour < 0) {
      if (data.hour == 24 && data.minute == 0 && data.second == 0) {
        data.hour = 0;
        if (++data.day > maxDayInMonthFor(data.year, data.month)) {
          data.day = 1;
          if (++data.month > 12) {
            data.month = 1;
            if (Constants.SCHEMA_1_1_SUPPORT) {
              ++data.year;
            } else if (++data.year == 0) {
              data.year = 1;
            }
          }
        }
      } else {
        throw new RuntimeException("Hour must have values 0-23, unless 24:00:00");
      }
    }

    // validate
    if (data.minute > 59 || data.minute < 0) {
      throw new RuntimeException("Minute must have values 0-59");
    }

    // validate
    if (data.second >= 60 || data.second < 0) {
      throw new RuntimeException("Second must have values 0-59");
    }

    // validate
    if (data.timezoneHr > 14 || data.timezoneHr < -14) {
      throw new RuntimeException("Time zone should have range -14:00 to +14:00");
    } else {
      if ((data.timezoneHr == 14 || data.timezoneHr == -14) && data.timezoneMin != 0)
        throw new RuntimeException("Time zone should have range -14:00 to +14:00");
      else if (data.timezoneMin > 59 || data.timezoneMin < -59)
        throw new RuntimeException("Minute must have values 0-59");
    }
  }
 private void cloneDate(DateTimeData finalValue, DateTimeData tempDate) {
   tempDate.year = finalValue.year;
   tempDate.month = finalValue.month;
   tempDate.day = finalValue.day;
   tempDate.hour = finalValue.hour;
   tempDate.minute = finalValue.minute;
   tempDate.second = finalValue.second;
   tempDate.utc = finalValue.utc;
   tempDate.timezoneHr = finalValue.timezoneHr;
   tempDate.timezoneMin = finalValue.timezoneMin;
 }
 /**
  * Resets object representation of date/time
  *
  * @param data date/time object
  */
 protected void resetDateObj(DateTimeData data) {
   data.year = 0;
   data.month = 0;
   data.day = 0;
   data.hour = 0;
   data.minute = 0;
   data.second = 0;
   data.utc = 0;
   data.timezoneHr = 0;
   data.timezoneMin = 0;
 }
  /**
   * Parses, validates and computes normalized version of duration object
   *
   * @param str The lexical representation of duration object PnYn MnDTnH nMnS
   * @param durationType
   * @return normalized date representation
   * @exception SchemaDateTimeException Invalid lexical representation
   */
  protected DateTimeData parse(String str, int durationType) throws SchemaDateTimeException {
    int len = str.length();
    DateTimeData date = new DateTimeData(str, this);

    int start = 0;
    char c = str.charAt(start++);
    if (c != 'P' && c != '-') {
      throw new SchemaDateTimeException();
    } else {
      date.utc = (c == '-') ? '-' : 0;
      if (c == '-' && str.charAt(start++) != 'P') {
        throw new SchemaDateTimeException();
      }
    }

    int negate = 1;
    // negative duration
    if (date.utc == '-') {
      negate = -1;
    }
    // at least one number and designator must be seen after P
    boolean designator = false;

    int endDate = indexOf(str, start, len, 'T');
    if (endDate == -1) {
      endDate = len;
    } else if (durationType == YEARMONTHDURATION_TYPE) {
      throw new SchemaDateTimeException();
    }

    // find 'Y'
    int end = indexOf(str, start, endDate, 'Y');
    if (end != -1) {

      if (durationType == DAYTIMEDURATION_TYPE) {
        throw new SchemaDateTimeException();
      }

      // scan year
      date.year = negate * parseInt(str, start, end);
      start = end + 1;
      designator = true;
    }

    end = indexOf(str, start, endDate, 'M');
    if (end != -1) {

      if (durationType == DAYTIMEDURATION_TYPE) {
        throw new SchemaDateTimeException();
      }

      // scan month
      date.month = negate * parseInt(str, start, end);
      start = end + 1;
      designator = true;
    }

    end = indexOf(str, start, endDate, 'D');
    if (end != -1) {

      if (durationType == YEARMONTHDURATION_TYPE) {
        throw new SchemaDateTimeException();
      }

      // scan day
      date.day = negate * parseInt(str, start, end);
      start = end + 1;
      designator = true;
    }

    if (len == endDate && start != len) {
      throw new SchemaDateTimeException();
    }
    if (len != endDate) {

      // scan hours, minutes, seconds
      // REVISIT: can any item include a decimal fraction or only seconds?
      //

      end = indexOf(str, ++start, len, 'H');
      if (end != -1) {
        // scan hours
        date.hour = negate * parseInt(str, start, end);
        start = end + 1;
        designator = true;
      }

      end = indexOf(str, start, len, 'M');
      if (end != -1) {
        // scan min
        date.minute = negate * parseInt(str, start, end);
        start = end + 1;
        designator = true;
      }

      end = indexOf(str, start, len, 'S');
      if (end != -1) {
        // scan seconds
        date.second = negate * parseSecond(str, start, end);
        start = end + 1;
        designator = true;
      }
      // no additional data shouls appear after last item
      // P1Y1M1DT is illigal value as well
      if (start != len || str.charAt(--start) == 'T') {
        throw new SchemaDateTimeException();
      }
    }

    if (!designator) {
      throw new SchemaDateTimeException();
    }

    return date;
  }