content: /**
   * Copyright 2010 The Apache Software Foundation
   *
   * 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 com.alibaba.wasp.jdbc.value;
  
  import com.alibaba.wasp.SQLErrorCode;
  import com.alibaba.wasp.jdbc.JdbcException;
  import com.alibaba.wasp.util.DateTimeUtils;
  import com.alibaba.wasp.util.MathUtils;
  
  import java.math.BigDecimal;
  import java.sql.Date;
  import java.sql.PreparedStatement;
  import java.sql.SQLException;
  import java.sql.Timestamp;
  import java.util.Calendar;
  import java.util.TimeZone;
  
  /**
   * Implementation of the TIMESTAMP data type.
   */
  public class ValueTimestamp extends Value {
  
      /**
       * The precision in digits.
       */
      public static final int PRECISION = 23;
  
      /**
       * The display size of the textual representation of a timestamp.
       * Example: 2001-01-01 23:59:59.000
       */
      static final int DISPLAY_SIZE = 23;
  
      /**
       * The default scale for timestamps.
       */
      static final int DEFAULT_SCALE = 10;
  
      private final long dateValue;
      private final long nanos;
  
      private ValueTimestamp(long dateValue, long nanos) {
          this.dateValue = dateValue;
          this.nanos = nanos;
      }
  
      /**
       * Get or create a date value for the given date.
       *
       * @param dateValue the date value
       * @param nanos the nanoseconds
       * @return the value
       */
      public static ValueTimestamp fromDateValueAndNanos(long dateValue, long nanos) {
          return new ValueTimestamp(dateValue, nanos);
      }
  
      /**
       * Get or create a timestamp value for the given timestamp.
       *
       * @param timestamp the timestamp
       * @return the value
       */
      public static ValueTimestamp get(Timestamp timestamp) {
          long ms = timestamp.getTime();
          long nanos = timestamp.getNanos() % 1000000;
          long dateValue = DateTimeUtils.dateValueFromDate(ms);
          nanos += DateTimeUtils.nanosFromDate(ms);
          return fromDateValueAndNanos(dateValue, nanos);
      }
  
      /**
       * Parse a string to a ValueTimestamp. This method supports the format
       * +/-year-month-day hour:minute:seconds.fractional and an optional timezone
       * part.
       *
       * @param s the string to parse
       * @return the date
       */
      public static ValueTimestamp parse(String s) {
          try {
              return parseTry(s);
          } catch (Exception e) {
              throw JdbcException.get(SQLErrorCode.INVALID_DATETIME_CONSTANT_2,
                  e, "TIMESTAMP", s);
          }
      }
  
      private static ValueTimestamp parseTry(String s) {
          int dateEnd = s.indexOf(' ');
          if (dateEnd < 0) {
              // ISO 8601 compatibility
              dateEnd = s.indexOf('T');
          }
          int timeStart;
          if (dateEnd < 0) {
              dateEnd = s.length();
              timeStart = -1;
          } else {
              timeStart = dateEnd + 1;
          }
          long dateValue = DateTimeUtils.parseDateValue(s, 0, dateEnd);
          long nanos;
          if (timeStart < 0) {
              nanos = 0;
          } else {
              int timeEnd = s.length();
              TimeZone tz = null;
              if (s.endsWith("Z")) {
                  tz = TimeZone.getTimeZone("UTC");
                  timeEnd--;
              } else {
                  int timeZoneStart = s.indexOf('+', dateEnd);
                  if (timeZoneStart < 0) {
                      timeZoneStart = s.indexOf('-', dateEnd);
                  }
                  if (timeZoneStart >= 0) {
                      String tzName = "GMT" + s.substring(timeZoneStart);
                      tz = TimeZone.getTimeZone(tzName);
                      if (!tz.getID().startsWith(tzName)) {
                          throw new IllegalArgumentException(tzName);
                      }
                      timeEnd = timeZoneStart;
                  } else {
                      timeZoneStart = s.indexOf(' ', dateEnd + 1);
                      if (timeZoneStart > 0) {
                          String tzName = s.substring(timeZoneStart + 1);
                          tz = TimeZone.getTimeZone(tzName);
                          if (!tz.getID().startsWith(tzName)) {
                              throw new IllegalArgumentException(tzName);
                          }
                          timeEnd = timeZoneStart;
                      }
                  }
              }
              nanos = DateTimeUtils.parseTimeNanos(s, dateEnd + 1, timeEnd, true);
              if (tz != null) {
                  int year = DateTimeUtils.yearFromDateValue(dateValue);
                  int month = DateTimeUtils.monthFromDateValue(dateValue);
                  int day = DateTimeUtils.dayFromDateValue(dateValue);
                  long ms = nanos / 1000000;
                  nanos -= ms * 1000000;
                  long second = ms / 1000;
                  ms -= second * 1000;
                  int minute = (int) (second / 60);
                  second -= minute * 60;
                  int hour = minute / 60;
                  minute -= hour * 60;
                  long millis = DateTimeUtils.getMillis(tz, year, month, day, hour, minute, (int) second, (int) ms);
                  ms = DateTimeUtils.convertToLocal(new Date(millis), Calendar.getInstance(TimeZone.getTimeZone("UTC")));
                  long md = DateTimeUtils.MILLIS_PER_DAY;
                  long absoluteDay = (ms >= 0 ? ms : ms - md + 1) / md;
                  ms -= absoluteDay * md;
                  nanos += ms * 1000000;
              }
          }
          return ValueTimestamp.fromDateValueAndNanos(dateValue, nanos);
      }
  
      public long getDateValue() {
          return dateValue;
      }
  
      public long getNanos() {
          return nanos;
      }
  
      public Timestamp getTimestamp() {
          return DateTimeUtils.convertDateValueToTimestamp(dateValue, nanos);
      }
  
      public int getType() {
          return Value.TIMESTAMP;
      }
  
      public String getString() {
          StringBuilder buff = new StringBuilder(DISPLAY_SIZE);
          ValueDate.appendDate(buff, dateValue);
          buff.append(' ');
          ValueTime.appendTime(buff, nanos, true);
          return buff.toString();
      }
  
      public String getSQL() {
          return "TIMESTAMP '" + getString() + "'";
      }
  
      public long getPrecision() {
          return PRECISION;
      }
  
      public int getScale() {
          return DEFAULT_SCALE;
      }
  
      public int getDisplaySize() {
          return DISPLAY_SIZE;
      }
  
      public Value convertScale(boolean onlyToSmallerScale, int targetScale) {
          if (targetScale >= DEFAULT_SCALE) {
              return this;
          }
          if (targetScale < 0) {
              throw JdbcException.getInvalidValueException("scale", targetScale);
          }
          long n = nanos;
          BigDecimal bd = BigDecimal.valueOf(n);
          bd = bd.movePointLeft(9);
          bd = MathUtils.setScale(bd, targetScale);
          bd = bd.movePointRight(9);
          long n2 = bd.longValue();
          if (n2 == n) {
              return this;
          }
          return fromDateValueAndNanos(dateValue, n2);
      }
  
      protected int compareSecure(Value o, CompareMode mode) {
          ValueTimestamp t = (ValueTimestamp) o;
          int c = MathUtils.compareLong(dateValue, t.dateValue);
          if (c != 0) {
              return c;
          }
          return MathUtils.compareLong(nanos, t.nanos);
      }
  
      public boolean equals(Object other) {
          if (this == other) {
              return true;
          } else if (!(other instanceof ValueTimestamp)) {
              return false;
          }
          ValueTimestamp x = (ValueTimestamp) other;
          return dateValue == x.dateValue && nanos == x.nanos;
      }
  
      public int hashCode() {
          return (int) (dateValue ^ (dateValue >>> 32) ^ nanos ^ (nanos >>> 32));
      }
  
      public Object getObject() {
          return getTimestamp();
      }
  
      public void set(PreparedStatement prep, int parameterIndex) throws SQLException {
          prep.setTimestamp(parameterIndex, getTimestamp());
      }
  
  
  }
  