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

net.snowflake.client.core.arrow.ArrowResultUtil Maven / Gradle / Ivy

/*
 * Copyright (c) 2012-2019 Snowflake Computing Inc. All rights reserved.
 */

package net.snowflake.client.core.arrow;

import java.sql.Date;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.util.Calendar;
import java.util.TimeZone;
import net.snowflake.client.core.ResultUtil;
import net.snowflake.client.core.SFException;
import net.snowflake.client.jdbc.ErrorCode;
import net.snowflake.client.jdbc.SnowflakeTimestampWithTimezone;
import net.snowflake.client.log.ArgSupplier;
import net.snowflake.client.log.SFLogger;
import net.snowflake.client.log.SFLoggerFactory;
import net.snowflake.common.core.CalendarCache;

/** Result utility methods specifically for Arrow format */
public class ArrowResultUtil {
  private static final SFLogger logger = SFLoggerFactory.getLogger(ArrowResultUtil.class);

  private static final int[] POWERS_OF_10 = {
    1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000
  };

  public static final int MAX_SCALE_POWERS_OF_10 = 9;

  public static long powerOfTen(int pow) {
    long val = 1;
    while (pow > MAX_SCALE_POWERS_OF_10) {
      val *= POWERS_OF_10[MAX_SCALE_POWERS_OF_10];
      pow -= MAX_SCALE_POWERS_OF_10;
    }
    return val * POWERS_OF_10[pow];
  }

  public static String getStringFormat(int scale) {
    StringBuilder sb = new StringBuilder();
    return sb.append("%.").append(scale).append('f').toString();
  }

  /**
   * new method to get Date from integer
   *
   * @param day
   * @return Date
   */
  public static Date getDate(int day) {
    LocalDate localDate = LocalDate.ofEpochDay(day);
    return Date.valueOf(localDate);
  }

  /**
   * Method to get Date from integer using timezone offsets
   *
   * @param day
   * @param oldTz
   * @param newTz
   * @return
   * @throws SFException
   */
  public static Date getDate(int day, TimeZone oldTz, TimeZone newTz) throws SFException {
    try {
      // return the date adjusted to the JVM default time zone
      long milliSecsSinceEpoch = (long) day * ResultUtil.MILLIS_IN_ONE_DAY;

      long milliSecsSinceEpochNew =
          milliSecsSinceEpoch + moveToTimeZoneOffset(milliSecsSinceEpoch, oldTz, newTz);

      Date preDate = new Date(milliSecsSinceEpochNew);

      // if date is on or before 1582-10-04, apply the difference
      // by (H-H/4-2) where H is the hundreds digit of the year according to:
      // http://en.wikipedia.org/wiki/Gregorian_calendar
      Date newDate = ResultUtil.adjustDate(preDate);
      logger.debug(
          "Adjust date from {} to {}",
          (ArgSupplier) preDate::toString,
          (ArgSupplier) newDate::toString);
      return newDate;
    } catch (NumberFormatException ex) {
      throw new SFException(ErrorCode.INTERNAL_ERROR, "Invalid date value: " + day);
    }
  }

  /**
   * simplified moveToTimeZone method
   *
   * @param milliSecsSinceEpoch
   * @param oldTZ
   * @param newTZ
   * @return offset
   */
  private static long moveToTimeZoneOffset(
      long milliSecsSinceEpoch, TimeZone oldTZ, TimeZone newTZ) {
    if (oldTZ.hasSameRules(newTZ)) {
      // same time zone
      return 0;
    }
    int offsetMillisInOldTZ = oldTZ.getOffset(milliSecsSinceEpoch);

    Calendar calendar = CalendarCache.get(oldTZ);
    calendar.setTimeInMillis(milliSecsSinceEpoch);

    int millisecondWithinDay =
        ((calendar.get(Calendar.HOUR_OF_DAY) * 60 + calendar.get(Calendar.MINUTE)) * 60
                    + calendar.get(Calendar.SECOND))
                * 1000
            + calendar.get(Calendar.MILLISECOND);

    int era = calendar.get(Calendar.ERA);
    int year = calendar.get(Calendar.YEAR);
    int month = calendar.get(Calendar.MONTH);
    int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
    int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);

    int offsetMillisInNewTZ =
        newTZ.getOffset(era, year, month, dayOfMonth, dayOfWeek, millisecondWithinDay);

    int offsetMillis = offsetMillisInOldTZ - offsetMillisInNewTZ;
    return offsetMillis;
  }

  /**
   * move the input timestamp form oldTZ to newTZ
   *
   * @param ts
   * @param oldTZ
   * @param newTZ
   * @return timestamp in newTZ
   */
  public static Timestamp moveToTimeZone(Timestamp ts, TimeZone oldTZ, TimeZone newTZ) {
    long offset = moveToTimeZoneOffset(ts.getTime(), oldTZ, newTZ);
    if (offset == 0) {
      return ts;
    }
    int nanos = ts.getNanos();
    ts = new Timestamp(ts.getTime() + offset);
    ts.setNanos(nanos);
    return ts;
  }

  /**
   * generate Java Timestamp object
   *
   * @param epoch the value since epoch time
   * @param scale the scale of the value
   * @return
   */
  public static Timestamp toJavaTimestamp(long epoch, int scale) {
    long seconds = epoch / powerOfTen(scale);
    int fraction = (int) ((epoch % powerOfTen(scale)) * powerOfTen(9 - scale));
    if (fraction < 0) {
      // handle negative case here
      seconds--;
      fraction += 1000000000;
    }
    return createTimestamp(seconds, fraction, TimeZone.getDefault(), false);
  }

  /**
   * check whether the input seconds out of the scope of Java timestamp
   *
   * @param seconds
   * @return
   */
  public static boolean isTimestampOverflow(long seconds) {
    return seconds < Long.MIN_VALUE / powerOfTen(3) || seconds > Long.MAX_VALUE / powerOfTen(3);
  }

  /**
   * create Java timestamp using seconds since epoch and fraction in nanoseconds For example,
   * 1232.234 represents as epoch = 1232 and fraction = 234,000,000 For example, -1232.234
   * represents as epoch = -1233 and fraction = 766,000,000 For example, -0.13 represents as epoch =
   * -1 and fraction = 870,000,000
   *
   * @param seconds
   * @param fraction
   * @param timezone - The timezone being used for the toString() formatting
   * @param timezone -
   * @return java timestamp object
   */
  public static Timestamp createTimestamp(
      long seconds, int fraction, TimeZone timezone, boolean useSessionTz) {
    // If JDBC_TREAT_TIMESTAMP_NTZ_AS_UTC=true, set timezone to UTC to get
    // timestamp object. This will avoid moving the timezone and creating
    // daylight savings offset errors.
    if (useSessionTz) {
      return new SnowflakeTimestampWithTimezone(
          seconds * ArrowResultUtil.powerOfTen(3), fraction, timezone);
    }
    Timestamp ts = new Timestamp(seconds * ArrowResultUtil.powerOfTen(3));
    ts.setNanos(fraction);
    return ts;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy