content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *   http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing,
   * software distributed under the License is distributed on an
   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   * KIND, either express or implied.  See the License for the
   * specific language governing permissions and limitations
   * under the License.
   */
  package org.apache.asterix.om.base.temporal;
  
  import java.io.DataOutput;
  import java.io.IOException;
  
  import org.apache.hyracks.api.exceptions.HyracksDataException;
  import org.apache.hyracks.dataflow.common.data.parsers.IValueParser;
  import org.apache.hyracks.dataflow.common.data.parsers.IValueParserFactory;
  
  public class ATimeParserFactory implements IValueParserFactory {
  
      public static final IValueParserFactory INSTANCE = new ATimeParserFactory();
  
      private static final long serialVersionUID = 1L;
  
      private static final String timeErrorMessage = "Wrong Input Format for a Time Value";
  
      private ATimeParserFactory() {
  
      }
  
      @Override
      public IValueParser createValueParser() {
  
          return new IValueParser() {
  
              @Override
              public boolean parse(char[] buffer, int start, int length, DataOutput out) throws HyracksDataException {
                  try {
                      out.writeInt(parseTimePart(buffer, start, length));
                      return true;
                  } catch (IOException ex) {
                      throw HyracksDataException.create(ex);
                  }
              }
          };
      }
  
      /**
       * Parse the given string as a time string, and return the milliseconds represented by the time.
       *
       * @param timeString
       * @param start
       * @param length
       * @return
       * @throws HyracksDataException
       */
      public static int parseTimePart(String timeString, int start, int length) throws HyracksDataException {
          int offset = 0;
  
          int hour = 0, min = 0, sec = 0, millis = 0;
          int timezone = 0;
  
          boolean isExtendedForm = false;
          if (timeString.charAt(start + offset + 2) == ':') {
              isExtendedForm = true;
          }
  
          if (isExtendedForm
                  && (timeString.charAt(start + offset + 2) != ':' || timeString.charAt(start + offset + 5) != ':')) {
              throw new HyracksDataException(timeErrorMessage + ": Missing colon in an extended time format.");
          }
          // hour
          for (int i = 0; i < 2; i++) {
              if ((timeString.charAt(start + offset + i) >= '0' && timeString.charAt(start + offset + i) <= '9')) {
                  hour = hour * 10 + timeString.charAt(start + offset + i) - '0';
              } else {
                  throw new HyracksDataException(timeErrorMessage + ": Non-numeric value in hour field");
              }
          }
  
          if (hour < GregorianCalendarSystem.FIELD_MINS[GregorianCalendarSystem.Fields.HOUR.ordinal()]
                  || hour > GregorianCalendarSystem.FIELD_MAXS[GregorianCalendarSystem.Fields.HOUR.ordinal()]) {
              throw new HyracksDataException(timeErrorMessage + ": hour " + hour);
          }
  
          offset += (isExtendedForm) ? 3 : 2;
  
          // minute
          for (int i = 0; i < 2; i++) {
              if ((timeString.charAt(start + offset + i) >= '0' && timeString.charAt(start + offset + i) <= '9')) {
                  min = min * 10 + timeString.charAt(start + offset + i) - '0';
              } else {
                  throw new HyracksDataException(timeErrorMessage + ": Non-numeric value in minute field");
              }
          }
  
          if (min < GregorianCalendarSystem.FIELD_MINS[GregorianCalendarSystem.Fields.MINUTE.ordinal()]
                  || min > GregorianCalendarSystem.FIELD_MAXS[GregorianCalendarSystem.Fields.MINUTE.ordinal()]) {
              throw new HyracksDataException(timeErrorMessage + ": min " + min);
          }
  
          offset += (isExtendedForm) ? 3 : 2;
  
          // second
          for (int i = 0; i < 2; i++) {
              if ((timeString.charAt(start + offset + i) >= '0' && timeString.charAt(start + offset + i) <= '9')) {
                  sec = sec * 10 + timeString.charAt(start + offset + i) - '0';
              } else {
                  throw new HyracksDataException(timeErrorMessage + ": Non-numeric value in second field");
              }
          }
  
          if (sec < GregorianCalendarSystem.FIELD_MINS[GregorianCalendarSystem.Fields.SECOND.ordinal()]
                  || sec > GregorianCalendarSystem.FIELD_MAXS[GregorianCalendarSystem.Fields.SECOND.ordinal()]) {
              throw new HyracksDataException(timeErrorMessage + ": sec " + sec);
          }
  
          offset += 2;
  
          if ((isExtendedForm && length > offset && timeString.charAt(start + offset) == '.')
                  || (!isExtendedForm && length > offset)) {
  
              offset += (isExtendedForm) ? 1 : 0;
              int i = 0;
              for (; i < 3 && offset + i < length; i++) {
                  if (timeString.charAt(start + offset + i) >= '0' && timeString.charAt(start + offset + i) <= '9') {
                      millis = millis * 10 + timeString.charAt(start + offset + i) - '0';
                  } else {
                      break;
                  }
              }
  
              offset += i;
  
              for (; i < 3; i++) {
                  millis = millis * 10;
              }
  
              // error is thrown if more than three digits are seen for the millisecond part
              if (length > offset && timeString.charAt(start + offset) >= '0'
                      && timeString.charAt(start + offset) <= '9') {
                  throw new HyracksDataException(timeErrorMessage + ": too many fields for millisecond.");
              }
          }
  
          if (length > offset) {
              parseTimezonePart(timeString, start + offset); // parsed, then ignored
          }
  
          return GregorianCalendarSystem.getInstance().getChronon(hour, min, sec, millis);
      }
  
      /**
       * Parse the given string as a time string, and parse the timezone field.
       *
       * @param timeString
       * @param start
       * @return
       * @throws HyracksDataException
       */
      public static int parseTimezonePart(String timeString, int start) throws HyracksDataException {
          int timezone = 0;
  
          if (timeString.charAt(start) != 'Z') {
              if ((timeString.charAt(start) != '+' && timeString.charAt(start) != '-')) {
                  throw new HyracksDataException("Wrong timezone format: missing sign or missing colon for a time zone");
              }
  
              short timezoneHour = 0;
              short timezoneMinute = 0;
  
              for (int i = 0; i < 2; i++) {
                  if ((timeString.charAt(start + 1 + i) >= '0' && timeString.charAt(start + 1 + i) <= '9')) {
                      timezoneHour = (short) (timezoneHour * 10 + timeString.charAt(start + 1 + i) - '0');
                  } else {
                      throw new HyracksDataException(timeErrorMessage + ": Non-numeric value in timezone hour field");
                  }
              }
  
              int temp_offset = (timeString.charAt(start + 3) == ':') ? 1 : 0;
  
              for (int i = 0; i < 2; i++) {
                  if ((timeString.charAt(start + temp_offset + 3 + i) >= '0'
                          && timeString.charAt(start + temp_offset + 3 + i) <= '9')) {
                      timezoneMinute =
                              (short) (timezoneMinute * 10 + timeString.charAt(start + temp_offset + 3 + i) - '0');
                  } else {
                      throw new HyracksDataException(timeErrorMessage + ": Non-numeric value in timezone minute field");
                  }
              }
  
              timezone = (int) (timezoneHour * GregorianCalendarSystem.CHRONON_OF_HOUR
                      + timezoneMinute * GregorianCalendarSystem.CHRONON_OF_MINUTE);
  
              if (timeString.charAt(start) == '+') {
                  timezone *= -1;
              }
          }
          return timezone;
      }
  
      /**
       * Similar to {@link #parseTimePart(String, int, int)} but use a char array as input; although this is almost
       * a copy-and-past code but it avoids object creation.
       *
       * @param timeString
       * @param start
       * @param length
       * @return
       * @throws HyracksDataException
       */
      public static int parseTimePart(char[] timeString, int start, int length) throws HyracksDataException {
  
          int offset = 0;
  
          int hour = 0, min = 0, sec = 0, millis = 0;
  
          boolean isExtendedForm = false;
          if (timeString[start + offset + 2] == ':') {
              isExtendedForm = true;
          }
  
          if (isExtendedForm && (timeString[start + offset + 2] != ':' || timeString[start + offset + 5] != ':')) {
              throw new HyracksDataException(timeErrorMessage + ": Missing colon in an extended time format.");
          }
          // hour
          for (int i = 0; i < 2; i++) {
              if ((timeString[start + offset + i] >= '0' && timeString[start + offset + i] <= '9')) {
                  hour = hour * 10 + timeString[start + offset + i] - '0';
              } else {
                  throw new HyracksDataException(timeErrorMessage + ": Non-numeric value in hour field");
              }
          }
  
          if (hour < GregorianCalendarSystem.FIELD_MINS[GregorianCalendarSystem.Fields.HOUR.ordinal()]
                  || hour > GregorianCalendarSystem.FIELD_MAXS[GregorianCalendarSystem.Fields.HOUR.ordinal()]) {
              throw new HyracksDataException(timeErrorMessage + ": hour " + hour);
          }
  
          offset += (isExtendedForm) ? 3 : 2;
  
          // minute
          for (int i = 0; i < 2; i++) {
              if ((timeString[start + offset + i] >= '0' && timeString[start + offset + i] <= '9')) {
                  min = min * 10 + timeString[start + offset + i] - '0';
              } else {
                  throw new HyracksDataException(timeErrorMessage + ": Non-numeric value in minute field");
              }
          }
  
          if (min < GregorianCalendarSystem.FIELD_MINS[GregorianCalendarSystem.Fields.MINUTE.ordinal()]
                  || min > GregorianCalendarSystem.FIELD_MAXS[GregorianCalendarSystem.Fields.MINUTE.ordinal()]) {
              throw new HyracksDataException(timeErrorMessage + ": min " + min);
          }
  
          offset += (isExtendedForm) ? 3 : 2;
  
          // second
          for (int i = 0; i < 2; i++) {
              if ((timeString[start + offset + i] >= '0' && timeString[start + offset + i] <= '9')) {
                  sec = sec * 10 + timeString[start + offset + i] - '0';
              } else {
                  throw new HyracksDataException(timeErrorMessage + ": Non-numeric value in second field");
              }
          }
  
          if (sec < GregorianCalendarSystem.FIELD_MINS[GregorianCalendarSystem.Fields.SECOND.ordinal()]
                  || sec > GregorianCalendarSystem.FIELD_MAXS[GregorianCalendarSystem.Fields.SECOND.ordinal()]) {
              throw new HyracksDataException(timeErrorMessage + ": sec " + sec);
          }
  
          offset += 2;
  
          if ((isExtendedForm && length > offset && timeString[start + offset] == '.')
                  || (!isExtendedForm && length > offset)) {
  
              offset += (isExtendedForm) ? 1 : 0;
              int i = 0;
              for (; i < 3 && offset + i < length; i++) {
                  if (timeString[start + offset + i] >= '0' && timeString[start + offset + i] <= '9') {
                      millis = millis * 10 + timeString[start + offset + i] - '0';
                  } else {
                      break;
                  }
              }
  
              offset += i;
  
              for (; i < 3; i++) {
                  millis = millis * 10;
              }
  
              // error is thrown if more than three digits are seen for the millisecond part
              if (length > offset && timeString[start + offset] >= '0' && timeString[start + offset] <= '9') {
                  throw new HyracksDataException(timeErrorMessage + ": too many fields for millisecond.");
              }
          }
  
          if (length > offset) {
              parseTimezonePart(timeString, start + offset); // parsed, then ignored
          }
  
          return GregorianCalendarSystem.getInstance().getChronon(hour, min, sec, millis);
      }
  
      /**
       * Similar to {@link #parseTimezonePart(String, int)} but use a char array as input; although this is almost
       * a copy-and-past code but it avoids object creation.
       *
       * @param timeString
       * @param start
       *
       * @return
       * @throws HyracksDataException
       */
      public static int parseTimezonePart(char[] timeString, int start) throws HyracksDataException {
          int timezone = 0;
  
          if (timeString[start] != 'Z') {
              if ((timeString[start] != '+' && timeString[start] != '-')) {
                  throw new HyracksDataException("Wrong timezone format: missing sign or missing colon for a time zone");
              }
  
              short timezoneHour = 0;
              short timezoneMinute = 0;
  
              for (int i = 0; i < 2; i++) {
                  if ((timeString[start + 1 + i] >= '0' && timeString[start + 1 + i] <= '9')) {
                      timezoneHour = (short) (timezoneHour * 10 + timeString[start + 1 + i] - '0');
                  } else {
                      throw new HyracksDataException(timeErrorMessage + ": Non-numeric value in timezone hour field");
                  }
              }
  
              int temp_offset = (timeString[start + 3] == ':') ? 1 : 0;
  
              for (int i = 0; i < 2; i++) {
                  if ((timeString[start + temp_offset + 3 + i] >= '0'
                          && timeString[start + temp_offset + 3 + i] <= '9')) {
                      timezoneMinute = (short) (timezoneMinute * 10 + timeString[start + temp_offset + 3 + i] - '0');
                  } else {
                      throw new HyracksDataException(timeErrorMessage + ": Non-numeric value in timezone minute field");
                  }
              }
  
              timezone = (int) (timezoneHour * GregorianCalendarSystem.CHRONON_OF_HOUR
                      + timezoneMinute * GregorianCalendarSystem.CHRONON_OF_MINUTE);
  
              if (timeString[start] == '+') {
                  timezone *= -1;
              }
          }
          return timezone;
      }
  
      /**
       * Similar to {@link #parseTimePart(String, int, int)} but use a byte array as input; although this is almost
       * a copy-and-past code but it avoids object creation.
       *
       * @param timeString
       * @param start
       * @param length
       * @return
       * @throws HyracksDataException
       */
      public static int parseTimePart(byte[] timeString, int start, int length) throws HyracksDataException {
  
          int offset = 0;
  
          int hour = 0, min = 0, sec = 0, millis = 0;
          int timezone = 0;
  
          boolean isExtendedForm = false;
          if (timeString[start + offset + 2] == ':') {
              isExtendedForm = true;
          }
  
          if (isExtendedForm && (timeString[start + offset + 2] != ':' || timeString[start + offset + 5] != ':')) {
              throw new HyracksDataException(timeErrorMessage + ": Missing colon in an extended time format.");
          }
          // hour
          for (int i = 0; i < 2; i++) {
              if ((timeString[start + offset + i] >= '0' && timeString[start + offset + i] <= '9')) {
                  hour = hour * 10 + timeString[start + offset + i] - '0';
              } else {
                  throw new HyracksDataException(timeErrorMessage + ": Non-numeric value in hour field");
              }
          }
  
          if (hour < GregorianCalendarSystem.FIELD_MINS[GregorianCalendarSystem.Fields.HOUR.ordinal()]
                  || hour > GregorianCalendarSystem.FIELD_MAXS[GregorianCalendarSystem.Fields.HOUR.ordinal()]) {
              throw new HyracksDataException(timeErrorMessage + ": hour " + hour);
          }
  
          offset += (isExtendedForm) ? 3 : 2;
  
          // minute
          for (int i = 0; i < 2; i++) {
              if ((timeString[start + offset + i] >= '0' && timeString[start + offset + i] <= '9')) {
                  min = min * 10 + timeString[start + offset + i] - '0';
              } else {
                  throw new HyracksDataException(timeErrorMessage + ": Non-numeric value in minute field");
              }
          }
  
          if (min < GregorianCalendarSystem.FIELD_MINS[GregorianCalendarSystem.Fields.MINUTE.ordinal()]
                  || min > GregorianCalendarSystem.FIELD_MAXS[GregorianCalendarSystem.Fields.MINUTE.ordinal()]) {
              throw new HyracksDataException(timeErrorMessage + ": min " + min);
          }
  
          offset += (isExtendedForm) ? 3 : 2;
  
          // second
          for (int i = 0; i < 2; i++) {
              if ((timeString[start + offset + i] >= '0' && timeString[start + offset + i] <= '9')) {
                  sec = sec * 10 + timeString[start + offset + i] - '0';
              } else {
                  throw new HyracksDataException(timeErrorMessage + ": Non-numeric value in second field");
              }
          }
  
          if (sec < GregorianCalendarSystem.FIELD_MINS[GregorianCalendarSystem.Fields.SECOND.ordinal()]
                  || sec > GregorianCalendarSystem.FIELD_MAXS[GregorianCalendarSystem.Fields.SECOND.ordinal()]) {
              throw new HyracksDataException(timeErrorMessage + ": sec " + sec);
          }
  
          offset += 2;
  
          if ((isExtendedForm && length > offset && timeString[start + offset] == '.')
                  || (!isExtendedForm && length > offset)) {
  
              offset += (isExtendedForm) ? 1 : 0;
              int i = 0;
              for (; i < 3 && offset + i < length; i++) {
                  if (timeString[start + offset + i] >= '0' && timeString[start + offset + i] <= '9') {
                      millis = millis * 10 + timeString[start + offset + i] - '0';
                  } else {
                      break;
                  }
              }
  
              offset += i;
  
              for (; i < 3; i++) {
                  millis = millis * 10;
              }
  
              // error is thrown if more than three digits are seen for the millisecond part
              if (length > offset && timeString[start + offset] >= '0' && timeString[start + offset] <= '9') {
                  throw new HyracksDataException(timeErrorMessage + ": too many fields for millisecond.");
              }
          }
  
          if (length > offset) {
              parseTimezonePart(timeString, start + offset); // parsed and ignored
          }
  
          return GregorianCalendarSystem.getInstance().getChronon(hour, min, sec, millis);
      }
  
      /**
       * Similar to {@link #parseTimezonePart(String, int)} but use a byte array as input; although this is almost
       * a copy-and-past code but it avoids object creation.
       *
       * @param timeString
       * @param start
       *
       * @return
       * @throws HyracksDataException
       */
      public static int parseTimezonePart(byte[] timeString, int start) throws HyracksDataException {
          int timezone = 0;
  
          if (timeString[start] != 'Z') {
              if ((timeString[start] != '+' && timeString[start] != '-')) {
                  throw new HyracksDataException("Wrong timezone format: missing sign or missing colon for a time zone");
              }
  
              short timezoneHour = 0;
              short timezoneMinute = 0;
  
              for (int i = 0; i < 2; i++) {
                  if ((timeString[start + 1 + i] >= '0' && timeString[start + 1 + i] <= '9')) {
                      timezoneHour = (short) (timezoneHour * 10 + timeString[start + 1 + i] - '0');
                  } else {
                      throw new HyracksDataException(timeErrorMessage + ": Non-numeric value in timezone hour field");
                  }
              }
  
              int temp_offset = (timeString[start + 3] == ':') ? 1 : 0;
  
              for (int i = 0; i < 2; i++) {
                  if ((timeString[start + temp_offset + 3 + i] >= '0'
                          && timeString[start + temp_offset + 3 + i] <= '9')) {
                      timezoneMinute = (short) (timezoneMinute * 10 + timeString[start + temp_offset + 3 + i] - '0');
                  } else {
                      throw new HyracksDataException(timeErrorMessage + ": Non-numeric value in timezone minute field");
                  }
              }
  
              timezone = (int) (timezoneHour * GregorianCalendarSystem.CHRONON_OF_HOUR
                      + timezoneMinute * GregorianCalendarSystem.CHRONON_OF_MINUTE);
  
              if (timeString[start] == '+') {
                  timezone *= -1;
              }
          }
          return timezone;
      }
  
  }
  