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.runtime.evaluators.functions;
  
  import java.io.DataOutput;
  
  import org.apache.asterix.dataflow.data.nontagged.serde.ADateSerializerDeserializer;
  import org.apache.asterix.dataflow.data.nontagged.serde.ADateTimeSerializerDeserializer;
  import org.apache.asterix.dataflow.data.nontagged.serde.ADayTimeDurationSerializerDeserializer;
  import org.apache.asterix.dataflow.data.nontagged.serde.ADoubleSerializerDeserializer;
  import org.apache.asterix.dataflow.data.nontagged.serde.ADurationSerializerDeserializer;
  import org.apache.asterix.dataflow.data.nontagged.serde.AFloatSerializerDeserializer;
  import org.apache.asterix.dataflow.data.nontagged.serde.AInt16SerializerDeserializer;
  import org.apache.asterix.dataflow.data.nontagged.serde.AInt32SerializerDeserializer;
  import org.apache.asterix.dataflow.data.nontagged.serde.AInt64SerializerDeserializer;
  import org.apache.asterix.dataflow.data.nontagged.serde.AInt8SerializerDeserializer;
  import org.apache.asterix.dataflow.data.nontagged.serde.ATimeSerializerDeserializer;
  import org.apache.asterix.dataflow.data.nontagged.serde.AYearMonthDurationSerializerDeserializer;
  import org.apache.asterix.formats.nontagged.SerializerDeserializerProvider;
  import org.apache.asterix.om.base.AMutableDate;
  import org.apache.asterix.om.base.AMutableDateTime;
  import org.apache.asterix.om.base.AMutableDouble;
  import org.apache.asterix.om.base.AMutableDuration;
  import org.apache.asterix.om.base.AMutableFloat;
  import org.apache.asterix.om.base.AMutableInt16;
  import org.apache.asterix.om.base.AMutableInt32;
  import org.apache.asterix.om.base.AMutableInt64;
  import org.apache.asterix.om.base.AMutableInt8;
  import org.apache.asterix.om.base.AMutableTime;
  import org.apache.asterix.om.base.ANull;
  import org.apache.asterix.om.base.temporal.GregorianCalendarSystem;
  import org.apache.asterix.om.exceptions.ExceptionUtil;
  import org.apache.asterix.om.types.ATypeTag;
  import org.apache.asterix.om.types.BuiltinType;
  import org.apache.asterix.om.types.EnumDeserializer;
  import org.apache.asterix.runtime.evaluators.base.AbstractScalarFunctionDynamicDescriptor;
  import org.apache.asterix.runtime.exceptions.OverflowException;
  import org.apache.asterix.runtime.exceptions.UnderflowException;
  import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
  import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
  import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
  import org.apache.hyracks.api.context.IEvaluatorContext;
  import org.apache.hyracks.api.dataflow.value.ISerializerDeserializer;
  import org.apache.hyracks.api.exceptions.HyracksDataException;
  import org.apache.hyracks.data.std.api.IPointable;
  import org.apache.hyracks.data.std.primitive.VoidPointable;
  import org.apache.hyracks.data.std.util.ArrayBackedValueStorage;
  import org.apache.hyracks.dataflow.common.data.accessors.IFrameTupleReference;
  
  public abstract class AbstractNumericArithmeticEval extends AbstractScalarFunctionDynamicDescriptor {
      private static final long serialVersionUID = 527445160961348706L;
  
      /**
       * abstract method for arithmetic operation between two integer values
       *
       * @param lhs first operand
       * @param rhs second operand
       * @param result result holder
       * @return {@code false} if the result is {@code NULL}, otherwise {@code true}
       */
      protected abstract boolean evaluateInteger(long lhs, long rhs, AMutableInt64 result) throws HyracksDataException;
  
      /**
       * abstract method for arithmetic operation between two floating point values
       *
       * @param lhs first operand
       * @param rhs second operand
       * @param result result holder
       * @return {@code false} if the result is {@code NULL}, otherwise {@code true}
       */
      protected abstract boolean evaluateDouble(double lhs, double rhs, AMutableDouble result)
              throws HyracksDataException;
  
      /**
       * abstract method for arithmetic operation between a time instance (date/time/datetime)
       * and a duration (duration/year-month-duration/day-time-duration)
       *
       * @param chronon first operand
       * @param yearMonth year-month component of the second operand
       * @param dayTime day-time component of the second operand
       * @param result result holder
       * @param ctx evaluator context
       * @return {@code false} if the result is {@code NULL}, otherwise {@code true}
       */
      protected abstract boolean evaluateTimeDurationArithmetic(long chronon, int yearMonth, long dayTime,
              boolean isTimeOnly, AMutableInt64 result, IEvaluatorContext ctx) throws HyracksDataException;
  
      /**
       * abstract method for arithmetic operation between two time instances (date/time/datetime)
       *
       * @param chronon0 first operand
       * @param chronon1 second operand
       * @param result result holder
       * @param ctx evaluator context
       * @return {@code false} if the result is {@code NULL}, otherwise {@code true}
       */
      protected abstract boolean evaluateTimeInstanceArithmetic(long chronon0, long chronon1, AMutableInt64 result,
              IEvaluatorContext ctx) throws HyracksDataException;
  
      @Override
      public IScalarEvaluatorFactory createEvaluatorFactory(final IScalarEvaluatorFactory[] args) {
          return new IScalarEvaluatorFactory() {
              private static final long serialVersionUID = 1L;
  
              @Override
              public IScalarEvaluator createScalarEvaluator(IEvaluatorContext ctx) throws HyracksDataException {
  
                  return new IScalarEvaluator() {
                      private final ArrayBackedValueStorage resultStorage = new ArrayBackedValueStorage();
                      private final DataOutput out = resultStorage.getDataOutput();
                      private final IPointable argPtr0 = new VoidPointable();
                      private final IPointable argPtr1 = new VoidPointable();
                      private final IScalarEvaluator evalLeft = args[0].createScalarEvaluator(ctx);
                      private final IScalarEvaluator evalRight = args[1].createScalarEvaluator(ctx);
                      private final double[] operandsFloating = new double[args.length];
                      private final long[] operandsInteger = new long[args.length];
  
                      private final AMutableFloat aFloat = new AMutableFloat(0);
                      private final AMutableDouble aDouble = new AMutableDouble(0);
                      private final AMutableInt64 aInt64 = new AMutableInt64(0);
                      private final AMutableInt32 aInt32 = new AMutableInt32(0);
                      private final AMutableInt16 aInt16 = new AMutableInt16((short) 0);
                      private final AMutableInt8 aInt8 = new AMutableInt8((byte) 0);
  
                      private final AMutableDuration aDuration = new AMutableDuration(0, 0);
                      private final AMutableDate aDate = new AMutableDate(0);
                      private final AMutableTime aTime = new AMutableTime(0);
                      private final AMutableDateTime aDatetime = new AMutableDateTime(0);
  
                      private final FunctionIdentifier funID = getIdentifier();
  
                      @SuppressWarnings("rawtypes")
                      private final ISerializerDeserializer int8Serde =
                              SerializerDeserializerProvider.INSTANCE.getSerializerDeserializer(BuiltinType.AINT8);
                      @SuppressWarnings("rawtypes")
                      private final ISerializerDeserializer int16Serde =
                              SerializerDeserializerProvider.INSTANCE.getSerializerDeserializer(BuiltinType.AINT16);
                      @SuppressWarnings("rawtypes")
                      private final ISerializerDeserializer int32Serde =
                              SerializerDeserializerProvider.INSTANCE.getSerializerDeserializer(BuiltinType.AINT32);
                      @SuppressWarnings("rawtypes")
                      private final ISerializerDeserializer int64Serde =
                              SerializerDeserializerProvider.INSTANCE.getSerializerDeserializer(BuiltinType.AINT64);
                      @SuppressWarnings("rawtypes")
                      private final ISerializerDeserializer floatSerde =
                              SerializerDeserializerProvider.INSTANCE.getSerializerDeserializer(BuiltinType.AFLOAT);
                      @SuppressWarnings("rawtypes")
                      private final ISerializerDeserializer doubleSerde =
                              SerializerDeserializerProvider.INSTANCE.getSerializerDeserializer(BuiltinType.ADOUBLE);
                      @SuppressWarnings("rawtypes")
                      private final ISerializerDeserializer dateSerde =
                              SerializerDeserializerProvider.INSTANCE.getSerializerDeserializer(BuiltinType.ADATE);
                      @SuppressWarnings("rawtypes")
                      private final ISerializerDeserializer timeSerde =
                              SerializerDeserializerProvider.INSTANCE.getSerializerDeserializer(BuiltinType.ATIME);
                      @SuppressWarnings("rawtypes")
                      private final ISerializerDeserializer dateTimeSerde =
                              SerializerDeserializerProvider.INSTANCE.getSerializerDeserializer(BuiltinType.ADATETIME);
                      @SuppressWarnings("rawtypes")
                      private final ISerializerDeserializer durationSerde =
                              SerializerDeserializerProvider.INSTANCE.getSerializerDeserializer(BuiltinType.ADURATION);
                      @SuppressWarnings("rawtypes")
                      private final ISerializerDeserializer nullSerde =
                              SerializerDeserializerProvider.INSTANCE.getSerializerDeserializer(BuiltinType.ANULL);
  
                      private final byte[] EXP_TYPES = new byte[] { ATypeTag.SERIALIZED_INT8_TYPE_TAG,
                              ATypeTag.SERIALIZED_INT16_TYPE_TAG, ATypeTag.SERIALIZED_INT32_TYPE_TAG,
                              ATypeTag.SERIALIZED_INT64_TYPE_TAG, ATypeTag.SERIALIZED_FLOAT_TYPE_TAG,
                              ATypeTag.SERIALIZED_DOUBLE_TYPE_TAG, ATypeTag.SERIALIZED_DATE_TYPE_TAG,
                              ATypeTag.SERIALIZED_TIME_TYPE_TAG, ATypeTag.SERIALIZED_DATETIME_TYPE_TAG,
                              ATypeTag.SERIALIZED_DURATION_TYPE_TAG, ATypeTag.SERIALIZED_YEAR_MONTH_DURATION_TYPE_TAG,
                              ATypeTag.SERIALIZED_DAY_TIME_DURATION_TYPE_TAG };
  
                      @Override
                      @SuppressWarnings("unchecked")
                      public void evaluate(IFrameTupleReference tuple, IPointable result) throws HyracksDataException {
                          evalLeft.evaluate(tuple, argPtr0);
                          evalRight.evaluate(tuple, argPtr1);
  
                          resultStorage.reset();
  
                          if (PointableHelper.checkAndSetMissingOrNull(result, argPtr0, argPtr1)) {
                              return;
                          }
  
                          ATypeTag argTypeMax = null;
  
                          for (int i = 0; i < 2; i++) {
                              IPointable argPtr = i == 0 ? argPtr0 : argPtr1;
                              byte[] bytes = argPtr.getByteArray();
                              int offset = argPtr.getStartOffset();
  
                              ATypeTag currentType;
                              ATypeTag typeTag = EnumDeserializer.ATYPETAGDESERIALIZER.deserialize(bytes[offset]);
                              switch (typeTag) {
                                  case TINYINT:
                                      currentType = ATypeTag.TINYINT;
                                      operandsInteger[i] = AInt8SerializerDeserializer.getByte(bytes, offset + 1);
                                      operandsFloating[i] = operandsInteger[i];
                                      break;
                                  case SMALLINT:
                                      currentType = ATypeTag.SMALLINT;
                                      operandsInteger[i] = AInt16SerializerDeserializer.getShort(bytes, offset + 1);
                                      operandsFloating[i] = operandsInteger[i];
                                      break;
                                  case INTEGER:
                                      currentType = ATypeTag.INTEGER;
                                      operandsInteger[i] = AInt32SerializerDeserializer.getInt(bytes, offset + 1);
                                      operandsFloating[i] = operandsInteger[i];
                                      break;
                                  case BIGINT:
                                      currentType = ATypeTag.BIGINT;
                                      operandsInteger[i] = AInt64SerializerDeserializer.getLong(bytes, offset + 1);
                                      operandsFloating[i] = operandsInteger[i];
                                      break;
                                  case FLOAT:
                                      currentType = ATypeTag.FLOAT;
                                      operandsFloating[i] = AFloatSerializerDeserializer.getFloat(bytes, offset + 1);
                                      break;
                                  case DOUBLE:
                                      currentType = ATypeTag.DOUBLE;
                                      operandsFloating[i] = ADoubleSerializerDeserializer.getDouble(bytes, offset + 1);
                                      break;
                                  case DATE:
                                  case TIME:
                                  case DATETIME:
                                  case DURATION:
                                  case YEARMONTHDURATION:
                                  case DAYTIMEDURATION:
                                      evaluateTemporalArithmeticOperation();
                                      result.set(resultStorage);
                                      return;
                                  default:
                                      ExceptionUtil.warnTypeMismatch(ctx, sourceLoc, funID, bytes[offset], i, EXP_TYPES);
                                      PointableHelper.setNull(result);
                                      return;
                              }
  
                              if (i == 0 || currentType.ordinal() > argTypeMax.ordinal()) {
                                  argTypeMax = currentType;
                              }
                          }
  
                          ATypeTag resultType = getNumericResultType(argTypeMax);
  
                          long lres;
                          double dres;
                          switch (resultType) {
                              case TINYINT:
                                  if (evaluateInteger(operandsInteger[0], operandsInteger[1], aInt64)) {
                                      lres = aInt64.getLongValue();
                                      if (lres > Byte.MAX_VALUE) {
                                          throw new OverflowException(sourceLoc, getIdentifier());
                                      }
                                      if (lres < Byte.MIN_VALUE) {
                                          throw new UnderflowException(sourceLoc, getIdentifier());
                                      }
                                      aInt8.setValue((byte) lres);
                                      int8Serde.serialize(aInt8, out);
                                  } else {
                                      nullSerde.serialize(ANull.NULL, out);
                                  }
                                  break;
                              case SMALLINT:
                                  if (evaluateInteger(operandsInteger[0], operandsInteger[1], aInt64)) {
                                      lres = aInt64.getLongValue();
                                      if (lres > Short.MAX_VALUE) {
                                          throw new OverflowException(sourceLoc, getIdentifier());
                                      }
                                      if (lres < Short.MIN_VALUE) {
                                          throw new UnderflowException(sourceLoc, getIdentifier());
                                      }
                                      aInt16.setValue((short) lres);
                                      int16Serde.serialize(aInt16, out);
                                  } else {
                                      nullSerde.serialize(ANull.NULL, out);
                                  }
                                  break;
                              case INTEGER:
                                  if (evaluateInteger(operandsInteger[0], operandsInteger[1], aInt64)) {
                                      lres = aInt64.getLongValue();
                                      if (lres > Integer.MAX_VALUE) {
                                          throw new OverflowException(sourceLoc, getIdentifier());
                                      }
                                      if (lres < Integer.MIN_VALUE) {
                                          throw new UnderflowException(sourceLoc, getIdentifier());
                                      }
                                      aInt32.setValue((int) lres);
                                      int32Serde.serialize(aInt32, out);
                                  } else {
                                      nullSerde.serialize(ANull.NULL, out);
                                  }
                                  break;
                              case BIGINT:
                                  if (evaluateInteger(operandsInteger[0], operandsInteger[1], aInt64)) {
                                      int64Serde.serialize(aInt64, out);
                                  } else {
                                      nullSerde.serialize(ANull.NULL, out);
                                  }
                                  break;
                              case FLOAT:
                                  if (evaluateDouble(operandsFloating[0], operandsFloating[1], aDouble)) {
                                      dres = aDouble.getDoubleValue();
                                      if (Double.isFinite(dres)) {
                                          if (dres > Float.MAX_VALUE) {
                                              throw new OverflowException(sourceLoc, getIdentifier());
                                          }
                                          if (dres < -Float.MAX_VALUE) {
                                              throw new UnderflowException(sourceLoc, getIdentifier());
                                          }
                                      }
                                      aFloat.setValue((float) dres);
                                      floatSerde.serialize(aFloat, out);
                                  } else {
                                      nullSerde.serialize(ANull.NULL, out);
                                  }
                                  break;
                              case DOUBLE:
                                  if (evaluateDouble(operandsFloating[0], operandsFloating[1], aDouble)) {
                                      doubleSerde.serialize(aDouble, out);
                                  } else {
                                      nullSerde.serialize(ANull.NULL, out);
                                  }
                                  break;
                          }
                          result.set(resultStorage);
                      }
  
                      @SuppressWarnings("unchecked")
                      private void evaluateTemporalArithmeticOperation() throws HyracksDataException {
                          byte[] bytes1 = argPtr1.getByteArray();
                          int offset1 = argPtr1.getStartOffset();
                          ATypeTag rightType = EnumDeserializer.ATYPETAGDESERIALIZER.deserialize(bytes1[offset1]);
                          byte[] bytes0 = argPtr0.getByteArray();
                          int offset0 = argPtr0.getStartOffset();
                          ATypeTag leftType = EnumDeserializer.ATYPETAGDESERIALIZER.deserialize(bytes0[offset0]);
  
                          if (rightType == leftType) {
                              long leftChronon = 0, rightChronon = 0, dayTime = 0;
                              int yearMonth = 0;
                              boolean yearMonthIsNull = false, dayTimeIsNull = false;
  
                              switch (leftType) {
                                  case DATE:
                                      leftChronon = ADateSerializerDeserializer.getChronon(bytes0, offset0 + 1)
                                              * GregorianCalendarSystem.CHRONON_OF_DAY;
                                      rightChronon = ADateSerializerDeserializer.getChronon(bytes1, offset1 + 1)
                                              * GregorianCalendarSystem.CHRONON_OF_DAY;
                                      break;
                                  case TIME:
                                      leftChronon = ATimeSerializerDeserializer.getChronon(bytes0, offset0 + 1);
                                      rightChronon = ATimeSerializerDeserializer.getChronon(bytes1, offset1 + 1);
                                      break;
                                  case DATETIME:
                                      leftChronon = ADateTimeSerializerDeserializer.getChronon(bytes0, offset0 + 1);
                                      rightChronon = ADateTimeSerializerDeserializer.getChronon(bytes1, offset1 + 1);
                                      break;
                                  case YEARMONTHDURATION:
                                      if (evaluateTimeInstanceArithmetic(
                                              AYearMonthDurationSerializerDeserializer.getYearMonth(bytes0, offset0 + 1),
                                              AYearMonthDurationSerializerDeserializer.getYearMonth(bytes1, offset1 + 1),
                                              aInt64, ctx)) {
                                          yearMonth = (int) aInt64.getLongValue();
                                      } else {
                                          yearMonthIsNull = true;
                                      }
                                      break;
                                  case DAYTIMEDURATION:
                                      leftChronon =
                                              ADayTimeDurationSerializerDeserializer.getDayTime(bytes0, offset0 + 1);
                                      rightChronon =
                                              ADayTimeDurationSerializerDeserializer.getDayTime(bytes1, offset1 + 1);
                                      break;
                                  default:
                                      ExceptionUtil.warnUnsupportedType(ctx, sourceLoc, funID.getName(), rightType);
                                      nullSerde.serialize(ANull.NULL, out);
                                      return;
                              }
  
                              if (evaluateTimeInstanceArithmetic(leftChronon, rightChronon, aInt64, ctx)) {
                                  dayTime = aInt64.getLongValue();
                              } else {
                                  dayTimeIsNull = true;
                              }
  
                              if (yearMonthIsNull || dayTimeIsNull) {
                                  nullSerde.serialize(ANull.NULL, out);
                              } else {
                                  aDuration.setValue(yearMonth, dayTime);
                                  durationSerde.serialize(aDuration, out);
                              }
  
                          } else {
                              long chronon = 0, dayTime = 0;
                              int yearMonth = 0;
                              ATypeTag resultType = null;
                              ISerializerDeserializer serde = null;
  
                              boolean isTimeOnly = false;
  
                              switch (leftType) {
                                  case TIME:
                                      serde = timeSerde;
                                      chronon = ATimeSerializerDeserializer.getChronon(bytes0, offset0 + 1);
                                      isTimeOnly = true;
                                      resultType = ATypeTag.TIME;
                                      switch (rightType) {
                                          case DAYTIMEDURATION:
                                              dayTime = ADayTimeDurationSerializerDeserializer.getDayTime(bytes1,
                                                      offset1 + 1);
                                              break;
                                          case DURATION:
                                              dayTime = ADurationSerializerDeserializer.getDayTime(bytes1, offset1 + 1);
                                              yearMonth =
                                                      ADurationSerializerDeserializer.getYearMonth(bytes1, offset1 + 1);
                                              break;
                                          default:
                                              ExceptionUtil.warnIncompatibleType(ctx, sourceLoc, funID.getName(),
                                                      leftType, rightType);
                                              nullSerde.serialize(ANull.NULL, out);
                                              return;
                                      }
                                      break;
                                  case DATE:
                                      serde = dateSerde;
                                      resultType = ATypeTag.DATE;
                                      chronon = ADateSerializerDeserializer.getChronon(bytes0, offset0 + 1)
                                              * GregorianCalendarSystem.CHRONON_OF_DAY;
                                  case DATETIME:
                                      if (leftType == ATypeTag.DATETIME) {
                                          serde = dateTimeSerde;
                                          resultType = ATypeTag.DATETIME;
                                          chronon = ADateTimeSerializerDeserializer.getChronon(bytes0, offset0 + 1);
                                      }
                                      switch (rightType) {
                                          case DURATION:
                                              yearMonth =
                                                      ADurationSerializerDeserializer.getYearMonth(bytes1, offset1 + 1);
                                              dayTime = ADurationSerializerDeserializer.getDayTime(bytes1, offset1 + 1);
                                              break;
                                          case YEARMONTHDURATION:
                                              yearMonth = AYearMonthDurationSerializerDeserializer.getYearMonth(bytes1,
                                                      offset1 + 1);
                                              break;
                                          case DAYTIMEDURATION:
                                              dayTime = ADayTimeDurationSerializerDeserializer.getDayTime(bytes1,
                                                      offset1 + 1);
                                              break;
                                          default:
                                              ExceptionUtil.warnIncompatibleType(ctx, sourceLoc, funID.getName(),
                                                      leftType, rightType);
                                              nullSerde.serialize(ANull.NULL, out);
                                              return;
                                      }
                                      break;
                                  case YEARMONTHDURATION:
                                      yearMonth =
                                              AYearMonthDurationSerializerDeserializer.getYearMonth(bytes0, offset0 + 1);
                                      switch (rightType) {
                                          case DATETIME:
                                              serde = dateTimeSerde;
                                              resultType = ATypeTag.DATETIME;
                                              chronon = ADateTimeSerializerDeserializer.getChronon(bytes1, offset1 + 1);
                                              break;
                                          case DATE:
                                              serde = dateSerde;
                                              resultType = ATypeTag.DATE;
                                              chronon = ADateSerializerDeserializer.getChronon(bytes1, offset1 + 1)
                                                      * GregorianCalendarSystem.CHRONON_OF_DAY;
                                              break;
                                          default:
                                              ExceptionUtil.warnIncompatibleType(ctx, sourceLoc, funID.getName(),
                                                      leftType, rightType);
                                              nullSerde.serialize(ANull.NULL, out);
                                              return;
                                      }
                                      break;
                                  case DURATION:
                                      yearMonth = ADurationSerializerDeserializer.getYearMonth(bytes0, offset0 + 1);
                                      dayTime = ADurationSerializerDeserializer.getDayTime(bytes0, offset0 + 1);
                                  case DAYTIMEDURATION:
                                      if (leftType == ATypeTag.DAYTIMEDURATION) {
                                          dayTime =
                                                  ADayTimeDurationSerializerDeserializer.getDayTime(bytes0, offset0 + 1);
                                      }
                                      switch (rightType) {
                                          case DATETIME:
                                              serde = dateTimeSerde;
                                              resultType = ATypeTag.DATETIME;
                                              chronon = ADateTimeSerializerDeserializer.getChronon(bytes1, offset1 + 1);
                                              break;
                                          case DATE:
                                              serde = dateSerde;
                                              resultType = ATypeTag.DATE;
                                              chronon = ADateSerializerDeserializer.getChronon(bytes1, offset1 + 1)
                                                      * GregorianCalendarSystem.CHRONON_OF_DAY;
                                              break;
                                          case TIME:
                                              if (yearMonth == 0) {
                                                  serde = timeSerde;
                                                  resultType = ATypeTag.TIME;
                                                  chronon = ATimeSerializerDeserializer.getChronon(bytes1, offset1 + 1);
                                                  isTimeOnly = true;
                                                  break;
                                              }
                                          default:
                                              ExceptionUtil.warnIncompatibleType(ctx, sourceLoc, funID.getName(),
                                                      leftType, rightType);
                                              nullSerde.serialize(ANull.NULL, out);
                                              return;
                                      }
                                      break;
                                  default:
                                      ExceptionUtil.warnIncompatibleType(ctx, sourceLoc, funID.getName(), leftType,
                                              rightType);
                                      nullSerde.serialize(ANull.NULL, out);
                                      return;
                              }
  
                              if (evaluateTimeDurationArithmetic(chronon, yearMonth, dayTime, isTimeOnly, aInt64, ctx)) {
                                  chronon = aInt64.getLongValue();
                                  switch (resultType) {
                                      case DATE:
                                          if (chronon < 0 && chronon % GregorianCalendarSystem.CHRONON_OF_DAY != 0) {
                                              chronon = chronon / GregorianCalendarSystem.CHRONON_OF_DAY - 1;
                                          } else {
                                              chronon = chronon / GregorianCalendarSystem.CHRONON_OF_DAY;
                                          }
                                          aDate.setValue((int) chronon);
                                          serde.serialize(aDate, out);
                                          break;
                                      case TIME:
                                          aTime.setValue((int) chronon);
                                          serde.serialize(aTime, out);
                                          break;
                                      case DATETIME:
                                          aDatetime.setValue(chronon);
                                          serde.serialize(aDatetime, out);
                                          break;
                                      default:
                                          ExceptionUtil.warnIncompatibleType(ctx, sourceLoc, funID.getName(), leftType,
                                                  rightType);
                                          nullSerde.serialize(ANull.NULL, out);
                                          return;
                                  }
                              } else {
                                  nullSerde.serialize(ANull.NULL, out);
                              }
                          }
                      }
                  };
              }
          };
      }
  
      protected ATypeTag getNumericResultType(ATypeTag argTypeMax) {
          return argTypeMax;
      }
  }
  