All Downloads are FREE. Search and download functionalities are using the official Maven repository.

studio.raptor.sqlparser.fast.value.ValueTimestampTimeZone Maven / Gradle / Ivy

/*
 * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, and the
 * EPL 1.0 (http://h2database.com/html/license.html). Initial Developer: H2
 * Group
 */
package studio.raptor.sqlparser.fast.value;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.TimeZone;
import studio.raptor.sqlparser.fast.api.ErrorCode;
import studio.raptor.sqlparser.fast.api.TimestampWithTimeZone;
import studio.raptor.sqlparser.fast.message.ParseException;
import studio.raptor.sqlparser.fast.util.DateTimeUtils;
import studio.raptor.sqlparser.fast.util.MathUtils;
import studio.raptor.sqlparser.fast.util.StringUtils;

/**
 * Implementation of the TIMESTAMP WITH TIME ZONE data type.
 *
 * @see  ISO 8601 Time zone
 * designators
 */
public class ValueTimestampTimeZone extends Value {

  /**
   * The precision in digits.
   */
  public static final int PRECISION = 30;

  /**
   * The display size of the textual representation of a timestamp. Example:
   * 2001-01-01 23:59:59.000 +10:00
   */
  static final int DISPLAY_SIZE = 30;

  /**
   * The default scale for timestamps.
   */
  static final int DEFAULT_SCALE = 10;

  private static final TimeZone GMT_TIMEZONE = TimeZone.getTimeZone("GMT");

  /**
   * A bit field with bits for the year, month, and day (see DateTimeUtils for
   * encoding)
   */
  private final long dateValue;
  /**
   * The nanoseconds since midnight.
   */
  private final long timeNanos;
  /**
   * Time zone offset from UTC in minutes, range of -12hours to +12hours
   */
  private final short timeZoneOffsetMins;

  private ValueTimestampTimeZone(long dateValue, long timeNanos,
      short timeZoneOffsetMins) {
    if (timeNanos < 0 || timeNanos >= 24L * 60 * 60 * 1000 * 1000 * 1000) {
      throw new IllegalArgumentException(
          "timeNanos out of range " + timeNanos);
    }
    if (timeZoneOffsetMins < (-12 * 60)
        || timeZoneOffsetMins >= (12 * 60)) {
      throw new IllegalArgumentException(
          "timeZoneOffsetMins out of range " + timeZoneOffsetMins);
    }
    this.dateValue = dateValue;
    this.timeNanos = timeNanos;
    this.timeZoneOffsetMins = timeZoneOffsetMins;
  }

  /**
   * Get or create a date value for the given date.
   *
   * @param dateValue the date value, a bit field with bits for the year, month, and day
   * @param timeNanos the nanoseconds since midnight
   * @param timeZoneOffsetMins the timezone offset in minutes
   * @return the value
   */
  public static ValueTimestampTimeZone fromDateValueAndNanos(long dateValue,
      long timeNanos, short timeZoneOffsetMins) {
    return (ValueTimestampTimeZone) Value.cache(new ValueTimestampTimeZone(
        dateValue, timeNanos, timeZoneOffsetMins));
  }

  /**
   * Get or create a timestamp value for the given timestamp.
   *
   * @param timestamp the timestamp
   * @return the value
   */
  public static ValueTimestampTimeZone get(TimestampWithTimeZone timestamp) {
    return fromDateValueAndNanos(timestamp.getYMD(),
        timestamp.getNanosSinceMidnight(),
        timestamp.getTimeZoneOffsetMins());
  }

  /**
   * 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 ValueTimestampTimeZone parse(String s) {
    try {
      return parseTry(s);
    } catch (Exception e) {
      throw ParseException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2, e,
          "TIMESTAMP WITH TIME ZONE", s);
    }
  }

  private static ValueTimestampTimeZone 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;
    short tzMinutes = 0;
    if (timeStart < 0) {
      nanos = 0;
    } else {
      int timeEnd = s.length();
      if (s.endsWith("Z")) {
        timeEnd--;
      } else {
        int timeZoneStart = s.indexOf('+', dateEnd);
        if (timeZoneStart < 0) {
          timeZoneStart = s.indexOf('-', dateEnd);
        }
        TimeZone tz = null;
        if (timeZoneStart >= 0) {
          String tzName = "GMT" + s.substring(timeZoneStart);
          tz = TimeZone.getTimeZone(tzName);
          if (!tz.getID().startsWith(tzName)) {
            throw new IllegalArgumentException(
                tzName + " (" + tz.getID() + "?)");
          }
          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;
          }
        }
        if (tz != null) {
          long millis = DateTimeUtils
              .convertDateValueToMillis(GMT_TIMEZONE, dateValue);
          tzMinutes = (short) (tz.getOffset(millis) / 1000 / 60);
        }
      }
      nanos = DateTimeUtils.parseTimeNanos(s, dateEnd + 1, timeEnd, true);
    }
    return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, nanos,
        tzMinutes);
  }

  /**
   * Append a time zone to the string builder.
   *
   * @param buff the target string builder
   * @param tz the time zone in minutes
   */
  private static void appendTimeZone(StringBuilder buff, short tz) {
    if (tz < 0) {
      buff.append('-');
      tz = (short) -tz;
    } else {
      buff.append('+');
    }
    int hours = tz / 60;
    tz -= hours * 60;
    int mins = tz;
    StringUtils.appendZeroPadded(buff, 2, hours);
    if (mins != 0) {
      buff.append(':');
      StringUtils.appendZeroPadded(buff, 2, mins);
    }
  }

  /**
   * A bit field with bits for the year, month, and day (see DateTimeUtils for
   * encoding).
   *
   * @return the data value
   */
  public long getDateValue() {
    return dateValue;
  }

  /**
   * The nanoseconds since midnight.
   *
   * @return the nanoseconds
   */
  public long getTimeNanos() {
    return timeNanos;
  }

  /**
   * The timezone offset in minutes.
   *
   * @return the offset
   */
  public short getTimeZoneOffsetMins() {
    return timeZoneOffsetMins;
  }

  @Override
  public Timestamp getTimestamp() {
    throw new UnsupportedOperationException("unimplemented");
  }

  @Override
  public int getType() {
    return Value.TIMESTAMP_TZ;
  }

  @Override
  public String getString() {
    StringBuilder buff = new StringBuilder(DISPLAY_SIZE);
    ValueDate.appendDate(buff, dateValue);
    buff.append(' ');
    ValueTime.appendTime(buff, timeNanos, true);
    appendTimeZone(buff, timeZoneOffsetMins);
    return buff.toString();
  }

  @Override
  public String getSQL() {
    return "TIMESTAMP WITH TIME ZONE '" + getString() + "'";
  }

  @Override
  public long getPrecision() {
    return PRECISION;
  }

  @Override
  public int getScale() {
    return DEFAULT_SCALE;
  }

  @Override
  public int getDisplaySize() {
    return DISPLAY_SIZE;
  }

  @Override
  protected int compareSecure(Value o, CompareMode mode) {
    ValueTimestampTimeZone t = (ValueTimestampTimeZone) o;
    // We are pretending that the dateValue is in UTC because that gives us
    // a stable sort even if the DST database changes.

    // convert to minutes and add timezone offset
    long a = DateTimeUtils.convertDateValueToMillis(
        TimeZone.getTimeZone("UTC"), dateValue) /
        (1000L * 60L);
    long ma = timeNanos / (1000L * 1000L * 1000L * 60L);
    a += ma;
    a -= timeZoneOffsetMins;

    // convert to minutes and add timezone offset
    long b = DateTimeUtils.convertDateValueToMillis(
        TimeZone.getTimeZone("UTC"), t.dateValue) /
        (1000L * 60L);
    long mb = t.timeNanos / (1000L * 1000L * 1000L * 60L);
    b += mb;
    b -= t.timeZoneOffsetMins;

    // compare date
    int c = MathUtils.compareLong(a, b);
    if (c != 0) {
      return c;
    }
    // compare time
    long na = timeNanos - (ma * 1000L * 1000L * 1000L * 60L);
    long nb = t.timeNanos - (mb * 1000L * 1000L * 1000L * 60L);
    return MathUtils.compareLong(na, nb);
  }

  @Override
  public boolean equals(Object other) {
    if (this == other) {
      return true;
    } else if (!(other instanceof ValueTimestampTimeZone)) {
      return false;
    }
    ValueTimestampTimeZone x = (ValueTimestampTimeZone) other;
    return dateValue == x.dateValue && timeNanos == x.timeNanos
        && timeZoneOffsetMins == x.timeZoneOffsetMins;
  }

  @Override
  public int hashCode() {
    return (int) (dateValue ^ (dateValue >>> 32) ^ timeNanos
        ^ (timeNanos >>> 32) ^ timeZoneOffsetMins);
  }

  @Override
  public Object getObject() {
    return new TimestampWithTimeZone(dateValue, timeNanos,
        timeZoneOffsetMins);
  }

  @Override
  public void set(PreparedStatement prep, int parameterIndex)
      throws SQLException {
    prep.setString(parameterIndex, getString());
  }

  @Override
  public Value add(Value v) {
    throw ParseException.getUnsupportedException(
        "manipulating TIMESTAMP WITH TIME ZONE values is unsupported");
  }

  @Override
  public Value subtract(Value v) {
    throw ParseException.getUnsupportedException(
        "manipulating TIMESTAMP WITH TIME ZONE values is unsupported");
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy