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

net.snowflake.common.core.SnowflakeDateTimeFormat Maven / Gradle / Ivy

There is a newer version: 5.1.4
Show newest version
/*
 * Copyright (c) 2012, 2013 Snowflake Computing Inc. All right reserved.
 */
package net.snowflake.common.core;

import net.snowflake.common.util.TimeUtil;

import java.sql.Time;
import java.sql.Timestamp;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.SimpleTimeZone;
import java.util.TimeZone;

/**
 *
 * @author jhuang
 */
public class SnowflakeDateTimeFormat
{
  final static TimeZone GMT = TimeZone.getTimeZone("GMT");

  public enum ElementType
  {
    Year2digit_ElementType("YY", "yy"),
    Year_ElementType("YYYY", "yyyy"),
    Month_ElementType("MM", "MM"),
    MonthAbbrev_ElementType("MON", "MMM"),
    DayOfMonth_ElementType("DD", "dd"),
    DayOfWeekAbbrev_ElementType("DY", "EEE"),
    Hour24_ElementType("HH24", "HH"),
    Hour12_ElementType("HH12", "hh"),
    Hour_ElementType("HH", "HH"),
    Ante_Meridiem_ElementType("AM", "a"),
    Post_Meridiem_ElementType("PM", "a"),
    Minute_ElementType("MI", "mm"),
    Second_ElementType("SS", "ss"),
    MilliSecond_ElementType("FF", ""),  // special code for parsing fractions
    TZOffsetHourColonMin_ElementType("TZH:TZM", "XXX"),
    TZOffsetHourMin_ElementType("TZHTZM", "XX"),
    TZOffsetHourOnly_ElementType("TZH", "X"),
    TZAbbr_ElementType("TZD", "z");

    private String sqlFormat;
    private String javaFormat;  // java SimpleDateFormat

    /**
     * Constructor for ElementType
     *
     * @param sqlFormat
     * @param javaFormat
     */
    ElementType(String sqlFormat, String javaFormat)
    {
      this.sqlFormat = sqlFormat;
      this.javaFormat = javaFormat;
    }

    public String getSqlFormat()
    {
      return sqlFormat;
    }

    public String getJavaFormat()
    {
      return javaFormat;
    }
  }

  public class Fragment
  {
    private String javaFormat;
    private List elements;

    public Fragment(String javaFormat, List elements)
    {
      this.javaFormat = javaFormat;
      this.elements = elements;
    }
  }

  /** Our SQL format */
  String sqlFormat;

  /** If set, we'll use auto-logic during parsing */
  private boolean automaticParsing;

  /** Enables auto-scaling of Epoch time */
  public boolean epochAutoScale = true;

  private List fragments;
  private SimpleDateFormat simpleDateFormat;

  // Precision of the fractions. If -1, type-based.
  private int fractionsLen = -1;
  // Position of the fractions in the format. If -1, fractions are absent.
  private int fractionsPos = -1;
  // Defines if fractions are prefixed with a dot
  private boolean fractionsWithDot = false;
  // Formatter used to parse everything before fractions to find their place
  private SimpleDateFormat fractionsPreFormatter;

  // Formatter used to parse everything before the timezone to find its place.
  // If null, we know it's absent.
  private SimpleDateFormat timezonePreFormatter;
  // If we have timezones, what is their format.
  private ElementType timezoneElementType;

  // If we have a 2-year year
  private boolean has2digitYear = false;

  // What date types this format handles
  int type;
  // Bitmasks to use in type definition
  public static final int DATE = 1;
  public static final int TIME = 2;
  public static final int TIMESTAMP = DATE | TIME;

  // Array of all supported formats, to be used in AUTO parsing.
  // NOTE: generated with genDateFormats, do not edit manually.
  // Should to be in sync with the CPP version
  static final SnowflakeDateTimeFormat acceptedFormats[] = {
    // ISO_DATE_T_HOUR24_MINUTE_SECOND_FRAC_TZHM
    // Ex: "2013-04-28T20:57:01.123456789+07:00"
    new SnowflakeDateTimeFormat(
        "YYYY-MM-DD\"T\"HH24:MI:SS.FFTZH:TZM", TIMESTAMP),
    // ISO_DATE_HOUR24_MINUTE_SECOND_FRAC_TZHCM
    // Ex: "2013-04-28 20:57:01.123456789+07:00"
    new SnowflakeDateTimeFormat(
        "YYYY-MM-DD HH24:MI:SS.FFTZH:TZM", TIMESTAMP),
    // ISO_DATE_HOUR24_MINUTE_SECOND_FRAC_TZH
    // Ex: "2013-04-28 20:57:01.123456789+07"
    new SnowflakeDateTimeFormat(
        "YYYY-MM-DD HH24:MI:SS.FFTZH", TIMESTAMP),
    // ISO_DATE_HOUR24_MINUTE_SECOND_FRAC_TZSHCM
    // Ex: "2013-04-28 20:57:01.123456789 +07:00"
    new SnowflakeDateTimeFormat(
        "YYYY-MM-DD HH24:MI:SS.FF TZH:TZM", TIMESTAMP),
    // ISO_DATE_HOUR24_MINUTE_SECOND_FRAC_TZSHM
    // Ex: "2013-04-28 20:57:01.123456789 +07:00"
    new SnowflakeDateTimeFormat(
        "YYYY-MM-DD HH24:MI:SS.FF TZHTZM", TIMESTAMP),
    // ISO_DATE_HOUR24_MINUTE_SECOND_TZSHCM
    // Ex: "2013-04-28 20:57:01.123456789 +07:00"
    new SnowflakeDateTimeFormat(
        "YYYY-MM-DD HH24:MI:SS TZH:TZM", TIMESTAMP),
    // ISO_DATE_HOUR24_MINUTE_SECOND_TZSHM
    // Ex: "2013-04-28 20:57:01 +0700"
    new SnowflakeDateTimeFormat(
        "YYYY-MM-DD HH24:MI:SS TZHTZM", TIMESTAMP),
    // ISO_DATE_T_HOUR24_MINUTE_SECOND_FRAC
    // Ex: "2013-04-28T20:57:01.123456"
    new SnowflakeDateTimeFormat(
        "YYYY-MM-DD\"T\"HH24:MI:SS.FF", TIMESTAMP),
    // ISO_DATE_HOUR24_MINUTE_SECOND_FRAC2
    // Ex: "2013-04-28 20:57:01.123456"
    new SnowflakeDateTimeFormat(
        "YYYY-MM-DD HH24:MI:SS.FF", TIMESTAMP),
    // ISO_DATE_T_HOUR24_MINUTE_SECOND
    // Ex: "2013-04-28T20:57:01"
    new SnowflakeDateTimeFormat(
        "YYYY-MM-DD\"T\"HH24:MI:SS", TIMESTAMP),
    // ISO_DATE_HOUR24_MINUTE_SECOND2
    // Ex: "2013-04-28 20:57:01"
    new SnowflakeDateTimeFormat(
        "YYYY-MM-DD HH24:MI:SS", TIMESTAMP),
    // ISO_DATE_HOUR24_MINUTE
    // Ex: "2013-04-28T20:57"
    new SnowflakeDateTimeFormat(
        "YYYY-MM-DD\"T\"HH24:MI", TIMESTAMP),
    // ISO_DATE_HOUR24_MINUTE2
    // Ex: "2013-04-28T20:57"
    new SnowflakeDateTimeFormat(
        "YYYY-MM-DD HH24:MI", TIMESTAMP),
    // ISO_DATE_T_HOUR24
    // Ex: "2013-04-28T20"
    new SnowflakeDateTimeFormat(
        "YYYY-MM-DD\"T\"HH24", TIMESTAMP),
    // ISO_DATE_HOUR24_2
    // Ex: "2013-04-28T20"
    new SnowflakeDateTimeFormat(
        "YYYY-MM-DD HH24", TIMESTAMP),
    // ISO_DATE
    // Ex: "2013-04-28"
    new SnowflakeDateTimeFormat(
        "YYYY-MM-DD", TIMESTAMP),
    // ISO_US_SIMPLE_DATE
    // Ex: "17-DEC-1980"
    new SnowflakeDateTimeFormat(
        "DD-MON-YYYY", TIMESTAMP),
    // ISO_US_DATE_ALT1
    // Ex: "12/17/1980"
    new SnowflakeDateTimeFormat(
        "MM/DD/YYYY", TIMESTAMP),
    // ISO_DATE_T_HOUR24_MINUTE_SECOND_TZHCM
    // Ex: "2013-04-28T20:57:01-07:00"
    new SnowflakeDateTimeFormat(
        "YYYY-MM-DD\"T\"HH24:MI:SSTZH:TZM", TIMESTAMP),
    // ISO_DATE_HOUR24_MINUTE_SECOND_TZHCM
    // Ex: "2013-04-28T20:57:01-07:00"
    new SnowflakeDateTimeFormat(
        "YYYY-MM-DD HH24:MI:SSTZH:TZM", TIMESTAMP),
    // ISO_DATE_HOUR24_MINUTE_SECOND_TZH
    // Ex: "2013-04-28T20:57:01-07"
    new SnowflakeDateTimeFormat(
        "YYYY-MM-DD HH24:MI:SSTZH", TIMESTAMP),
    // ISO_DATE_T_HOUR24_MINUTE_TZHCM
    // Ex: "2013-04-28T20:57+07:00"
    new SnowflakeDateTimeFormat(
        "YYYY-MM-DD\"T\"HH24:MITZH:TZM", TIMESTAMP),
    // ISO_DATE_HOUR24_MINUTE_TZHCM
    // Ex: "2013-04-28T20:57+07:00"
    new SnowflakeDateTimeFormat(
        "YYYY-MM-DD HH24:MITZH:TZM", TIMESTAMP),
    // RFC_DATE_HOUR24_MINUTE_SECOND_TZ
    // Ex: "Thu, 21 Dec 2000 16:01:07 +0200"
    new SnowflakeDateTimeFormat(
        "DY, DD MON YYYY HH24:MI:SS TZHTZM", TIMESTAMP),
    // RFC_DATE_HOUR24_MINUTE_SECOND_FRAC_TZ
    // Ex: "Thu, 21 Dec 2000 16:01:07 +0200"
    new SnowflakeDateTimeFormat(
        "DY, DD MON YYYY HH24:MI:SS.FF TZHTZM", TIMESTAMP),
    // RFC_DATE_HOUR12_MINUTE_SECOND_MERIDIEM_TZ
    // Ex: "Thu, 21 Dec 2000 04:01:07 PM +0200"
    new SnowflakeDateTimeFormat(
        "DY, DD MON YYYY HH12:MI:SS AM TZHTZM", TIMESTAMP),
    // RFC_DATE_HOUR12_MINUTE_SECOND_FRAC_MERIDIEM_TZ
    // Ex: "Thu, 21 Dec 2000 04:01:07 PM +0200"
    new SnowflakeDateTimeFormat(
        "DY, DD MON YYYY HH12:MI:SS.FF AM TZHTZM", TIMESTAMP),
    // RFC_DATE_HOUR24_MINUTE_SECOND
    // Ex: "Thu, 21 Dec 2000 16:01:07 +0200"
    new SnowflakeDateTimeFormat(
        "DY, DD MON YYYY HH24:MI:SS", TIMESTAMP),
    // RFC_DATE_HOUR24_MINUTE_SECOND_FRAC
    // Ex: "Thu, 21 Dec 2000 16:01:07 +0200"
    new SnowflakeDateTimeFormat(
        "DY, DD MON YYYY HH24:MI:SS.FF", TIMESTAMP),
    // RFC_DATE_HOUR12_MINUTE_SECOND_MERIDIEM
    // Ex: "Thu, 21 Dec 2000 04:01:07 PM +0200"
    new SnowflakeDateTimeFormat(
        "DY, DD MON YYYY HH12:MI:SS AM", TIMESTAMP),
    // RFC_DATE_HOUR12_MINUTE_SECOND_FRAC_MERIDIEM
    // Ex: "Thu, 21 Dec 2000 04:01:07 PM +0200"
    new SnowflakeDateTimeFormat(
        "DY, DD MON YYYY HH12:MI:SS.FF AM", TIMESTAMP),
    // TWITTER_DATE_HOUR24_MIN_SEC_TZ_YEAR
    // Twitter timestamp format. Ex: Mon Jul 08 18:09:51 +0000 2013
    new SnowflakeDateTimeFormat(
        "DY MON DD HH24:MI:SS TZHTZM YYYY", TIMESTAMP),
    // ALT_DATE_HOUR24_MIN_SEC
    // Ex: "2/18/2008 02:36:48"
    new SnowflakeDateTimeFormat(
        "MM/DD/YYYY HH24:MI:SS", TIMESTAMP),
    // ISO_HOUR24_MINUTE_SECOND_FRAC_TZ
    // Ex: "20:57:01.123456789+07:00"
    new SnowflakeDateTimeFormat(
        "HH24:MI:SS.FFTZH:TZM", TIME),
    // ISO_HOUR24_MINUTE_SECOND_FRAC
    // Ex: "20:57:01.123456789"
    new SnowflakeDateTimeFormat(
        "HH24:MI:SS.FF", TIME),
    // RFC_HOUR12_MINUTE_SECOND_FRAC_MERIDIEM
    // Ex: "07:57:01.123456789 PM"
    new SnowflakeDateTimeFormat(
        "HH12:MI:SS.FF AM", TIME),
    // ISO_HOUR24_MINUTE_SECOND
    // Ex: "20:57:01"
    new SnowflakeDateTimeFormat(
        "HH24:MI:SS", TIME),
    // RFC_HOUR12_MINUTE_SECOND_MERIDIEM
    // Ex: "04:01:07 PM"
    new SnowflakeDateTimeFormat(
        "HH12:MI:SS AM", TIME),
    // ISO_HOUR24_MINUTE
    // Ex: "20:57"
    new SnowflakeDateTimeFormat(
        "HH24:MI", TIME),
    // RFC_HOUR12_MINUTE_MERIDIEM
    // Ex: "04:01 PM"
    new SnowflakeDateTimeFormat(
        "HH12:MI AM", TIME),
  };


  public SnowflakeDateTimeFormat(String sqlFormat)
  {
    this(sqlFormat, TIMESTAMP);
  }

  public SnowflakeDateTimeFormat(String sqlFormat, int type)
  {
    // limit sql format length
    if (sqlFormat != null && sqlFormat.length() > 1024)
    {
      throw new IllegalArgumentException("timestamp format too long");
    }

    this.type = type;
    this.sqlFormat = sqlFormat;
    fragments = new ArrayList<>();
    if (sqlFormat.compareToIgnoreCase("auto") == 0)
    {
      automaticParsing = true;
    } else {
      automaticParsing = false;
      compile(sqlFormat);
      if (fragments.size() == 0)
      {
        // Nothing has been parsed, create an empty SimpleDateFormat to unify
        // the rest of the code.
        simpleDateFormat = new SimpleDateFormat("");
      }
      else
      {
        // assert we only produce one fragment which uses SimpleDateFormat
        assert fragments.size() == 1;
        simpleDateFormat = new SimpleDateFormat(toSimpleDateTimePattern());
      }
    }
  }

  public String getSqlFormat()
  {
    return sqlFormat;
  }

  private void createNewFragment(String javaTimestampFormat,
                                 List elementTypes)
  {
    fragments.add(new Fragment(javaTimestampFormat, elementTypes));
  }

  /**
   * Add the java format for the element to the java format string builder
   * Add the element to the list of elements.
   * @param element
   * @param javaTimestampFormat
   * @param elementTypes
   * @return
   * Return the length of the sql format corresponding to the element.
   */
  private int addElement(ElementType element,
                          StringBuilder javaTimestampFormat,
                          List elementTypes)
  {
    javaTimestampFormat.append(
          element.getJavaFormat());

    elementTypes.add(element);

    return element.getSqlFormat().length();
  }

  /**
   * Adds a raw character to a string format by quoting it
   * @param stringFormat
   * @param charToAdd
   */
  private void addRawChar(StringBuilder stringFormat,
                          char charToAdd)
  {
    int curSize = stringFormat.length();
    if (charToAdd == '\'')
    {
      // Special code for "'"
      stringFormat.append("''");
    }
    else if (curSize > 2
        && stringFormat.charAt(curSize - 1) == '\''
        && stringFormat.charAt(curSize - 2) != '\'')
    {
      // Previous character was "raw", combine them
      if (fractionsPos == curSize)
      {
        // We're deleting a character before fractions, need to adjust for that
        fractionsPos--;
      }
      stringFormat.deleteCharAt(curSize - 1);
      stringFormat.append("" + charToAdd + "'");
    }
    else
    {
      stringFormat.append("'" + charToAdd + "'");
    }
  }

  /**
   * A function to parse SQL timestamp format and generate timestamp
   * fragments.
   *
   * @param sqlTimestampFormat
   * @return n/a
   */
  private void compile(String sqlTimestampFormat)
  {
    StringBuilder javaTimestampFormat = new StringBuilder();
    List elementTypes = new ArrayList();

    int idx = 0;

    String formatUpperCase = sqlTimestampFormat.toUpperCase();

    while (idx < formatUpperCase.length())
    {
      switch(formatUpperCase.charAt(idx))
      {
        case 'A':
          if (formatUpperCase.substring(idx).startsWith(
                  ElementType.Ante_Meridiem_ElementType.getSqlFormat()))
          {
            idx += addElement(ElementType.Ante_Meridiem_ElementType,
                              javaTimestampFormat,
                              elementTypes);
          }
          else
          {
            addRawChar(javaTimestampFormat, sqlTimestampFormat.charAt(idx++));
          }
          break;

        case 'D':
         if (formatUpperCase.substring(idx).startsWith(
                 ElementType.DayOfMonth_ElementType.getSqlFormat()))
          {
            idx += addElement(ElementType.DayOfMonth_ElementType,
                              javaTimestampFormat,
                              elementTypes);
          }
          else if (formatUpperCase.substring(idx).startsWith(
                  ElementType.DayOfWeekAbbrev_ElementType.getSqlFormat()))
          {
            idx += addElement(ElementType.DayOfWeekAbbrev_ElementType,
                              javaTimestampFormat,
                              elementTypes);
          }
          else
          {
            addRawChar(javaTimestampFormat, sqlTimestampFormat.charAt(idx++));
          }
          break;

        case 'H':
          if (formatUpperCase.substring(idx).startsWith(
                  ElementType.Hour24_ElementType.getSqlFormat()))
          {
            idx += addElement(ElementType.Hour24_ElementType,
                              javaTimestampFormat,
                              elementTypes);
          }
          else if (formatUpperCase.substring(idx).startsWith(
                  ElementType.Hour12_ElementType.getSqlFormat()))
          {
            idx += addElement(ElementType.Hour12_ElementType,
                              javaTimestampFormat,
                              elementTypes);
          }
          else if (formatUpperCase.substring(idx).startsWith(
                  ElementType.Hour_ElementType.getSqlFormat()))
          {
            idx += addElement(ElementType.Hour_ElementType,
                              javaTimestampFormat,
                              elementTypes);
          }
          else
          {
            addRawChar(javaTimestampFormat, sqlTimestampFormat.charAt(idx++));
          }
          break;

        case 'M':
          if (formatUpperCase.substring(idx).startsWith(
                  ElementType.Month_ElementType.getSqlFormat()))
          {
            idx += addElement(ElementType.Month_ElementType,
                              javaTimestampFormat,
                              elementTypes);
          }
          else if (formatUpperCase.substring(idx).startsWith(
                  ElementType.Minute_ElementType.getSqlFormat()))
          {
            idx += addElement(ElementType.Minute_ElementType,
                              javaTimestampFormat,
                              elementTypes);
          }
          else if (formatUpperCase.substring(idx).startsWith(
                  ElementType.MonthAbbrev_ElementType.getSqlFormat()))
          {
            idx += addElement(ElementType.MonthAbbrev_ElementType,
                              javaTimestampFormat,
                              elementTypes);
          }
          else
          {
            addRawChar(javaTimestampFormat, sqlTimestampFormat.charAt(idx++));
          }
          break;

        case 'P':
          if (formatUpperCase.substring(idx).startsWith(
                  ElementType.Post_Meridiem_ElementType.getSqlFormat()))
          {
            idx += addElement(ElementType.Post_Meridiem_ElementType,
                              javaTimestampFormat,
                              elementTypes);
          }
          else
          {
            addRawChar(javaTimestampFormat, sqlTimestampFormat.charAt(idx++));
          }
          break;

        case 'S':
          if (formatUpperCase.substring(idx).startsWith(
                  ElementType.Second_ElementType.getSqlFormat()))
          {
            idx += addElement(ElementType.Second_ElementType,
                              javaTimestampFormat,
                              elementTypes);
          }
          else
          {
            addRawChar(javaTimestampFormat, sqlTimestampFormat.charAt(idx++));
          }
          break;

        case 'T':
        {
          if (formatUpperCase.substring(idx).startsWith(
                  ElementType.TZOffsetHourColonMin_ElementType.getSqlFormat()))
          {
            timezonePreFormatter =
                new SimpleDateFormat(javaTimestampFormat.toString());
            timezoneElementType = ElementType.TZOffsetHourColonMin_ElementType;
            idx += addElement(ElementType.TZOffsetHourColonMin_ElementType,
                              javaTimestampFormat,
                              elementTypes);
          }
          else if (formatUpperCase.substring(idx).startsWith(
                  ElementType.TZOffsetHourMin_ElementType.getSqlFormat()))
          {
            timezonePreFormatter =
                new SimpleDateFormat(javaTimestampFormat.toString());
            timezoneElementType = ElementType.TZOffsetHourMin_ElementType;
            idx += addElement(ElementType.TZOffsetHourMin_ElementType,
                              javaTimestampFormat,
                              elementTypes);
          }
          else if (formatUpperCase.substring(idx).startsWith(
                  ElementType.TZOffsetHourOnly_ElementType.getSqlFormat()))
          {
            timezonePreFormatter =
                new SimpleDateFormat(javaTimestampFormat.toString());
            timezoneElementType = ElementType.TZOffsetHourOnly_ElementType;
            idx += addElement(ElementType.TZOffsetHourOnly_ElementType,
                              javaTimestampFormat,
                              elementTypes);
          }
          else if (formatUpperCase.substring(idx).startsWith(
                  ElementType.TZAbbr_ElementType.getSqlFormat()))
          {
            timezonePreFormatter =
                new SimpleDateFormat(javaTimestampFormat.toString());
            timezoneElementType = ElementType.TZAbbr_ElementType;
            idx += addElement(ElementType.TZAbbr_ElementType,
                              javaTimestampFormat,
                              elementTypes);
          }
          else
          {
            addRawChar(javaTimestampFormat, sqlTimestampFormat.charAt(idx++));
          }
        }
        break;

        case 'Y':
          // It's important 4-digit year goes before 2-digit year
          if (formatUpperCase.substring(idx).startsWith(
                  ElementType.Year_ElementType.getSqlFormat()))
          {
            idx += addElement(ElementType.Year_ElementType,
                              javaTimestampFormat,
                              elementTypes);
          }
          else if (formatUpperCase.substring(idx).startsWith(
                  ElementType.Year2digit_ElementType.getSqlFormat()))
          {
            has2digitYear = true;
            idx += addElement(ElementType.Year2digit_ElementType,
                              javaTimestampFormat,
                              elementTypes);
          }
          else
          {
            addRawChar(javaTimestampFormat, sqlTimestampFormat.charAt(idx++));
          }
          break;

        case '.':
          if (idx + 1 < formatUpperCase.length()
              && formatUpperCase.substring(idx + 1).startsWith(
                  ElementType.MilliSecond_ElementType.getSqlFormat()))
          {
            // Will be FF, just mark that there's a dot before FF
            fractionsWithDot = true;
            idx++;
          }
          else
          {
            addRawChar(javaTimestampFormat, sqlTimestampFormat.charAt(idx++));
          }
          break;

        case 'F':
          if (formatUpperCase.substring(idx).startsWith(
                  ElementType.MilliSecond_ElementType.getSqlFormat()))
          {
            idx += ElementType.MilliSecond_ElementType.getSqlFormat().length();
            // @todo Handle multiple occurences?
            // Construct formatter to find fractions position.
            fractionsPreFormatter =
                new SimpleDateFormat(javaTimestampFormat.toString());
            // Save fractions information
            fractionsPos = javaTimestampFormat.toString().length();
            fractionsLen = -1;
            // Check if FF is followed by the length specification (e.g. FF3)
            if (idx < formatUpperCase.length() &&
                Character.isDigit(formatUpperCase.charAt(idx)))
            {
              fractionsLen = Character.digit(formatUpperCase.charAt(idx), 10);
              idx++;
            }
          }
          else
          {
            addRawChar(javaTimestampFormat, sqlTimestampFormat.charAt(idx++));
          }
          break;

        case '\"':
          // two double quotes become a single double quote;
          // in all other cases, replace double quotes with single quotes;
          // single quotes are Java's way of quoting things in a datetime format string

          int endIdx = idx+1;

          while (endIdx < sqlTimestampFormat.length() && sqlTimestampFormat.charAt(endIdx) != '\"')
          {
            endIdx++;
          }

          if (endIdx == sqlTimestampFormat.length())
          {
            throw new IllegalArgumentException("Unterminated '\"'");
          }

          if (endIdx == idx+1)
          {
            // two double quotes = a single double quote
            javaTimestampFormat.append("\"");
          }
          else
          {
            // replace double quote with a single quote
            javaTimestampFormat.append("'");
            javaTimestampFormat.append(sqlTimestampFormat.substring(idx+1, endIdx));
            javaTimestampFormat.append("'");
          }

          idx = endIdx+1;

          break;

        default:
          addRawChar(javaTimestampFormat, sqlTimestampFormat.charAt(idx++));
          break;
      }
    }

    if (!elementTypes.isEmpty() ||
        javaTimestampFormat.length() > 0 ||
        fractionsLen > 0)
    {
      createNewFragment(javaTimestampFormat.toString(), elementTypes);
    }
  }

  public final String toSimpleDateTimePattern()
  {
    if (automaticParsing)
    {
      return null;
    }

    if (fragments.size() == 0)
    {
      return "";
    }
    else if (fragments.size() == 1)
    {
      return fragments.get(0).javaFormat;
    }
    else
    {
      return fragments.get(0).javaFormat + "FFF" +
             fragments.get(1).javaFormat;
    }
  }

  public String format(Timestamp timestamp, String timeZoneId, int scale)
  {
    return format(timestamp,
                  (timeZoneId == null) ? GMT : TimeZone.getTimeZone(timeZoneId),
                  scale);
  }

  public String format(Timestamp timestamp, TimeZone timeZone, int scale)
  {
    return format(timestamp, timeZone, timestamp.getNanos(), scale);
  }

  public String format(Date date, String timeZoneId)
  {
    return format(date, (timeZoneId == null)
                         ? GMT : TimeZone.getTimeZone(timeZoneId));
  }

  public String format(Date date, TimeZone timeZone)
  {
    return format(date, timeZone, 0, 0);
  }

  public String format(SFTime sfTime, int scale)
  {
    return format(new Time(sfTime.getFractionalSeconds(3)), GMT,
        sfTime.getNanosecondsWithinSecond(), scale);
  }

  // Private function performing actual formatting
  private String format(Date timestampOrDate,
                        TimeZone timeZone,
                        int nanos,
                        int scale)
  {
    SimpleDateFormat formatter;

    if (fractionsPos >= 0)
    { // Construct a special formatter, with nanos embedded
      assert fragments.size() == 1;
      if (fractionsLen >= 0)
        scale = fractionsLen;
      String nanoStr = String.format("%1$09d", nanos).substring(0, scale);
      String oldFormat = this.fragments.get(0).javaFormat;
      if (fractionsWithDot)
        nanoStr = "." + nanoStr;
      String newDateFormat = oldFormat.substring(0, fractionsPos)
                             +  nanoStr
                             + oldFormat.substring(fractionsPos);
      formatter = new SimpleDateFormat(newDateFormat);
    }
    else
    {
      if (simpleDateFormat == null)
      {
        throw new IllegalArgumentException(
            "formatter is null. automaticParsing: " + automaticParsing);
      }
      formatter = simpleDateFormat;
    }
    formatter.setCalendar(CalendarCache.get(timeZone));
    return formatter.format(timestampOrDate);
  }

  /**
   * @param stringToParse     String to parse
   * @param timeZone          TimeZone to perform parsing in
   * @param centuryBoundary   If > 0, defines the century boundary
   * @param ignoreTimezone    If the string contains a timezone, ignore it
   * @param cCompatibility
   *   corrects incompatibilities between Java and C libraries for timestamps
   *   that are ambiguous (e.g. '2016-11-06 01:30') or illegal (e.g. '2016-03-13
   *   02:30') due to DST rules.
   * @return                  Parsed timestamp, or null if can't parse.
   */
  private SFTimestamp tryParsing(String stringToParse,
                                 TimeZone timeZone,
                                 int centuryBoundary,
                                 boolean ignoreTimezone,
                                 boolean cCompatibility)
  {
    int nanos = 0;

    if (fractionsPos >= 0)
    { // Special logic for strings with fractions
      // First, parse the first fragment
      assert fractionsPreFormatter != null;
      ParsePosition pp = new ParsePosition(0);
      Date firstDate = fractionsPreFormatter.parse(stringToParse, pp);
      if (firstDate == null || pp.getIndex() == 0)
        return null;
      // Now, we try to parse fractional seconds
      int idx = pp.getIndex();
      int fracIdx = idx;
      boolean dotOK = true;
      if (fractionsWithDot)
      {
        // Check if there's a dot
        if (stringToParse.length() <= idx
            || stringToParse.charAt(idx) != '.')
        {
          dotOK = false;
        }
        else
        {
          idx++;
        }
      }
      int mul = 100 * 1000 * 1000;  // counting in nanosecnods
      boolean hadDigits = false;  // There was some digit to scan
      while (idx < stringToParse.length()
             && Character.isDigit(stringToParse.charAt(idx)))
      {
        if (mul > 0)
        {
          nanos += mul * Character.digit(stringToParse.charAt(idx), 10);
          mul /= 10;
        }
        idx++;
        hadDigits = true;
      }
      if (hadDigits && !dotOK)
        return null;
      // Now we have the fractions. Construct a new parser and a new
      // stringToParse and
      stringToParse = stringToParse.substring(0, fracIdx)
                      + stringToParse.substring(idx);
    }
    if (simpleDateFormat == null)
    {
      throw new IllegalArgumentException(
          "formatter is null. automaticParsing: " + automaticParsing);
    }
    simpleDateFormat.setCalendar(CalendarCache.get(timeZone));
    if (centuryBoundary > 0 && has2digitYear)
    {
      // Need to specify in which century we're working
      simpleDateFormat.set2DigitYearStart(
              new Date(centuryBoundary - 1900, 0, 1));
    }

    ParsePosition pp = new ParsePosition(0);
    Date date = simpleDateFormat.parse(stringToParse, pp);

    if (date == null || pp.getIndex() != stringToParse.length())
    {
      return null;
    }

    // If we had a time zone, also parse it to get originating time zone
    if (timezonePreFormatter != null)
    {
      pp = new ParsePosition(0);
      timezonePreFormatter.parse(stringToParse, pp);
      int idx = pp.getIndex();
      // Parse timezone at index.
      // Apparently parsing just timezone gives the local-epoch date
      String inputString = stringToParse.substring(idx);
      String formatString = timezoneElementType.javaFormat;
      SimpleDateFormat tzFormatter = new SimpleDateFormat(formatString);
      pp = new ParsePosition(0);
      // This has to parse
      Date tzDate = tzFormatter.parse(inputString, pp);
      int timeZoneOffset = - (int)tzDate.getTime();
      if (ignoreTimezone)
      {
        // Need to adjust the moment in time, to remove the impact of the
        // timezone that influenced the current date value.
        long ms = date.getTime();
        ms += timeZoneOffset;
        date = new Date(ms);
      }
      else
      {
        // Construct the real source timezone and use it to generate output
        timeZone = new SimpleTimeZone(timeZoneOffset, "GENERATED" + timeZoneOffset);
      }
    }

    if (cCompatibility)
    {
      if (TimeUtil.isDSTAmbiguous(date.getTime(), timeZone))
      {
        // For an ambiguous timestamp, such as '2016-11-06 01:30' in
        // America/Los_Angeles:
        // - C chooses the earlier instant, i.e.
        //   2016-11-06 01:30 -0700 or 2016-11-06 08:30 Z
        // - Java chooses the later instant, i.e.
        //   2016-11-06 01:30 -0800 or 2016-11-06 09:30 Z
        // For compatibility, we need to bring back the Java timestamp by an
        // hour.

        date = new Date(date.getTime() - timeZone.getDSTSavings());
      }

      // Note: We can't detect DST-illegal timestamps without reimplementing
      // SimpleDateFormat.
    }


    // Construct a Timestamp
    SFTimestamp ts = SFTimestamp.fromDate(date, nanos, timeZone);
    return ts;
  }

  /**
   * Parses a string into an SFTimestamp
   * @param stringToParse   String to parse
   * @param timeZone        Timezone to use if it's not found in the string.
   *                        If null, GMT is used by default.
   * @param centuryBoundary If > 0, defines the century boundary.
   * @param ignoreTimezone  If the string contains a timezone, ignore it.
   * @return Parsed SFTimestamp. Can be null if nothing parsed.
   *         If the string had a timezone, it will be set.
   */
  public SFTimestamp parse(String stringToParse,
                           TimeZone timeZone,
                           int centuryBoundary,
                           boolean ignoreTimezone)
  {
    return parse(stringToParse, timeZone, centuryBoundary, ignoreTimezone,
        false);
  }


  /**
   * Parses a string into an SFTimestamp
   * @param stringToParse   String to parse
   * @param timeZone        Timezone to use if it's not found in the string.
   *                        If null, GMT is used by default.
   * @param centuryBoundary If > 0, defines the century boundary.
   * @param ignoreTimezone  If the string contains a timezone, ignore it.
   * @param cCompatibility
   *   corrects incompatibilities between Java and C libraries for timestamps
   *   that are ambiguous (e.g. '2016-11-06 01:30') or illegal (e.g. '2016-03-13
   *   02:30') due to DST rules.
   *
   * @return Parsed SFTimestamp. Can be null if nothing parsed.
   *         If the string had a timezone, it will be set.
   */
  public SFTimestamp parse(String stringToParse,
                           TimeZone timeZone,
                           int centuryBoundary,
                           boolean ignoreTimezone,
                           boolean cCompatibility)
  {
    timeZone = (timeZone == null) ? GMT : timeZone;

    SFTimestamp result = null;

    if (automaticParsing)
    {
      // First try parsing as an integer - this allows loading unix-epoch based
      // times
      try {
        // 1000 years in seconds
        long SECONDS_LIMIT_FOR_EPOCH = 31536000000L;
        long MILLISECONDS_LIMIT_FOR_EPOCH =
              SECONDS_LIMIT_FOR_EPOCH * 1000;
        long MICROSECONDS_LIMIT_FOR_EPOCH =
              SECONDS_LIMIT_FOR_EPOCH * 1000000;
        // Parse as long
        long val = Long.parseLong(stringToParse);
        if (!epochAutoScale)
        {
          return SFTimestamp.fromMilliseconds(val * 1000, GMT);
        }
        else
        {
          if (val > -SECONDS_LIMIT_FOR_EPOCH
              && val < SECONDS_LIMIT_FOR_EPOCH)
          {
            return SFTimestamp.fromMilliseconds(val * 1000, GMT);
          }
          else if (val > -MILLISECONDS_LIMIT_FOR_EPOCH
                 && val < MILLISECONDS_LIMIT_FOR_EPOCH)
          {
            return SFTimestamp.fromMilliseconds(val, GMT);
          }
          else if (val > -MICROSECONDS_LIMIT_FOR_EPOCH
                   && val < MICROSECONDS_LIMIT_FOR_EPOCH)
          {
            return SFTimestamp.fromNanoseconds(val * 1000, GMT);
          }
          else
          {
            return SFTimestamp.fromNanoseconds(val, GMT);
          }
        }
      } catch (NumberFormatException e) {
        // Parsing as long failed, do nothing, we'll try parsing as a date
      }

      // Try different date formats - use simpleDateFormat formatting
      for (SnowflakeDateTimeFormat format : acceptedFormats)
      {
        // We synchronize on format, as concurrent queries might be accessing
        // the same static format concurrently if "auto" setting is on.
        // Our formats do have a state that is modified during parsing, hence
        // we need to protect it to avoid corruption (see SNOW-4009).
        synchronized(format) {
          result = format.tryParsing(stringToParse, timeZone, centuryBoundary,
                                     ignoreTimezone, cCompatibility);
        }
        if (result != null)
        {
          break;
        }
      }
    } else {
      result = tryParsing(stringToParse, timeZone, centuryBoundary,
                          ignoreTimezone, cCompatibility);
    }
    return result;
  }

  public SFTimestamp parse(String stringToParse,
                           String timeZoneId,
                           int centuryBoundary,
                           boolean ignoreTimezone)
  {
    return parse(stringToParse,
                 (timeZoneId == null) ? GMT : TimeZone.getTimeZone(timeZoneId),
                 centuryBoundary,
                 ignoreTimezone);
  }

  public SFTimestamp parse(String stringToParse)
  {
    return parse(stringToParse, GMT, 0, false);
  }

  /**
   * Returns effective String format for TIMESTAMP_*_OUTPUT_FORMAT, assuming
   * that if the specialized format is empty, the generic format should be used.
   * @param specialized Specialized format, e.g. TIMESTAMP_NTZ_OUTPUT_FORMAT
   * @param generic     Generic format, usually TIMESTAMP_OUTPUT_FORMAT
   * @return            Effective format
   */
  static public String effectiveSpecializedTimestampFormat(String specialized,
                                                           String generic)
  {
    if (specialized == null || specialized.length() == 0)
      return generic;
    return specialized;
  }


}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy