You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tajo.apache.org by ji...@apache.org on 2014/05/22 03:33:39 UTC

[3/6] TAJO-825: Datetime type refactoring. (Hyoungjun Kim via jihoon)

http://git-wip-us.apache.org/repos/asf/tajo/blob/526dca28/tajo-common/src/main/java/org/apache/tajo/util/datetime/DateTimeUtil.java
----------------------------------------------------------------------
diff --git a/tajo-common/src/main/java/org/apache/tajo/util/datetime/DateTimeUtil.java b/tajo-common/src/main/java/org/apache/tajo/util/datetime/DateTimeUtil.java
new file mode 100644
index 0000000..187e25d
--- /dev/null
+++ b/tajo-common/src/main/java/org/apache/tajo/util/datetime/DateTimeUtil.java
@@ -0,0 +1,2102 @@
+/**
+ * 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.tajo.util.datetime;
+
+import org.apache.tajo.conf.TajoConf;
+import org.apache.tajo.datum.Int8Datum;
+import org.apache.tajo.exception.ValueOutOfRangeException;
+import org.apache.tajo.util.datetime.DateTimeConstants.DateStyle;
+import org.apache.tajo.util.datetime.DateTimeConstants.DateToken;
+import org.apache.tajo.util.datetime.DateTimeConstants.TokenField;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import java.util.TimeZone;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * This Class is originated from j2date in datetime.c of PostgreSQL.
+ */
+public class DateTimeUtil {
+
+  private static int MAX_FRACTION_LENGTH = 6;
+
+  /** maximum possible number of fields in a date * string */
+  private static int MAXDATEFIELDS = 25;
+
+  public static boolean isJulianCalendar(int year, int month, int day) {
+    return year <= 1752 && month <= 9 && day < 14;
+  }
+
+  public static int getCenturyOfEra(int year) {
+    if (year > 0) {
+      return (year - 1) / 100 + 1;
+    } else if (year < 0) {
+      //600BC to 501BC -> -6
+      int pYear = -year;
+      return -((pYear - 1) / 100 + 1);
+    } else {
+      return 0;
+    }
+  }
+
+  public static boolean isLeapYear(int year) {
+    return ((year & 3) == 0) && ((year % 100) != 0 || (year % 400) == 0);
+  }
+
+  public static int getDaysInYearMonth(int year, int month) {
+    if (isLeapYear(year)) {
+      return DateTimeConstants.DAY_OF_MONTH[1][month - 1];
+    } else {
+      return DateTimeConstants.DAY_OF_MONTH[0][month - 1];
+    }
+  }
+
+  /**
+   * Julian date support.
+   *
+   * isValidJulianDate checks the minimum date exactly, but is a bit sloppy
+   * about the maximum, since it's far enough out to not be especially
+   * interesting.
+   * @param years
+   * @param months
+   * @param days
+   * @return
+   */
+  public static boolean isValidJulianDate(int years, int months, int days) {
+    return years > DateTimeConstants.JULIAN_MINYEAR || years == DateTimeConstants.JULIAN_MINYEAR &&
+        months > DateTimeConstants.JULIAN_MINMONTH || months == DateTimeConstants.JULIAN_MINMONTH &&
+        days >= DateTimeConstants.JULIAN_MINDAY && years < DateTimeConstants.JULIAN_MAXYEAR;
+  }
+
+  /**
+   * Calendar time to Julian date conversions.
+   * Julian date is commonly used in astronomical applications,
+   *	since it is numerically accurate and computationally simple.
+   * The algorithms here will accurately convert between Julian day
+   *	and calendar date for all non-negative Julian days_full
+   *	(i.e. from Nov 24, -4713 on).
+   *
+   * These routines will be used by other date/time packages
+   * - thomas 97/02/25
+   *
+   * Rewritten to eliminate overflow problems. This now allows the
+   * routines to work correctly for all Julian day counts from
+   * 0 to 2147483647	(Nov 24, -4713 to Jun 3, 5874898) assuming
+   * a 32-bit integer. Longer types should also work to the limits
+   * of their precision.
+   * @param year
+   * @param month
+   * @param day
+   * @return
+   */
+  public static int date2j(int year, int month, int day) {
+    int julian;
+    int century;
+
+    if (month > 2) {
+      month += 1;
+      year += 4800;
+    } else {
+      month += 13;
+      year += 4799;
+    }
+
+    century = year / 100;
+    julian = (year * 365) - 32167;
+    julian += (((year / 4) - century) + (century / 4));
+    julian += ((7834 * month) / 256) + day;
+
+    return julian;
+  }
+
+  /**
+   * Set TimeMeta's date fields.
+   * @param julianDate
+   * @param tm
+   */
+  public static void j2date(int julianDate, TimeMeta tm) {
+    long julian;
+    long quad;
+    long extra;
+    long y;
+
+    julian = julianDate;
+    julian += 32044;
+    quad = julian / 146097;
+    extra = (julian - quad * 146097) * 4 + 3;
+    julian += 60 + quad * 3 + extra / 146097;
+    quad = julian / 1461;
+    julian -= quad * 1461;
+    y = julian * 4 / 1461;
+    julian = ((y != 0) ? ((julian + 305) % 365) : ((julian + 306) % 366))
+        + 123;
+    y += quad * 4;
+
+
+    tm.years = (int)(y - 4800);
+    quad = julian * 2141 / 65536;
+    tm.dayOfMonth = (int)(julian - 7834 * quad / 256);
+    tm.monthOfYear = (int) ((quad + 10) % DateTimeConstants.MONTHS_PER_YEAR + 1);
+  }
+
+  /**
+   * This method is originated from j2date in datetime.c of PostgreSQL.
+   *
+   * julianToDay - convert Julian date to day-of-week (0..6 == Sun..Sat)
+   *
+   * Note: various places use the locution julianToDay(date - 1) to produce a
+   * result according to the convention 0..6 = Mon..Sun.	This is a bit of
+   * a crock, but will work as long as the computation here is just a modulo.
+   * @param julianDate
+   * @return
+   */
+  public static int j2day(int julianDate) {
+    long day;
+
+    day = julianDate;
+
+    day += 1;
+    day %= 7;
+
+    return (int) day;
+  }
+
+  /**
+   * This method is originated from date2isoweek in timestamp.c of PostgreSQL.
+   * Returns ISO week number of year.
+   * @param year
+   * @param mon
+   * @param mday
+   * @return
+   */
+  public static int date2isoweek(int year, int mon, int mday) {
+    double result;
+    int day0;
+    int day4;
+    int dayn;
+
+    /* current day */
+    dayn = date2j(year, mon, mday);
+
+    /* fourth day of current year */
+    day4 = date2j(year, 1, 4);
+
+    /* day0 == offset to first day of week (Monday) */
+    day0 = j2day(day4 - 1);
+
+    /*
+     * We need the first week containing a Thursday, otherwise this day falls
+     * into the previous year for purposes of counting weeks
+     */
+    if (dayn < day4 - day0) {
+      day4 = date2j(year - 1, 1, 4);
+
+      /* day0 == offset to first day of week (Monday) */
+      day0 = j2day(day4 - 1);
+    }
+
+    result = (dayn - (day4 - day0)) / 7 + 1;
+
+      /*
+       * Sometimes the last few days_full in a year will fall into the first week of
+       * the next year, so check for this.
+       */
+    if (result >= 52) {
+      day4 = date2j(year + 1, 1, 4);
+
+      /* day0 == offset to first day of week (Monday) */
+      day0 = j2day(day4 - 1);
+
+      if (dayn >= day4 - day0) {
+        result = (dayn - (day4 - day0)) / 7 + 1;
+      }
+    }
+
+    return (int) result;
+  }
+
+  /**
+   * date2isoyear()
+   *
+   * Returns ISO 8601 year number.
+   * @param year
+   * @param mon
+   * @param mday
+   * @return
+   */
+  public static int date2isoyear(int year, int mon, int mday) {
+    /* current day */
+    int dayn = date2j(year, mon, mday);
+
+	  /* fourth day of current year */
+    int day4 = date2j(year, 1, 4);
+
+	  /* day0 == offset to first day of week (Monday) */
+    int day0 = j2day(day4 - 1);
+
+    /*
+     * We need the first week containing a Thursday, otherwise this day falls
+     * into the previous year for purposes of counting weeks
+     */
+    if (dayn < day4 - day0) {
+      day4 = date2j(year - 1, 1, 4);
+
+		/* day0 == offset to first day of week (Monday) */
+      day0 = j2day(day4 - 1);
+
+      year--;
+    }
+
+    double result = (dayn - (day4 - day0)) / 7 + 1;
+
+    /*
+     * Sometimes the last few days in a year will fall into the first week of
+     * the next year, so check for this.
+     */
+    if (result >= 52) {
+      day4 = date2j(year + 1, 1, 4);
+
+		/* day0 == offset to first day of week (Monday) */
+      day0 = j2day(day4 - 1);
+
+      if (dayn >= day4 - day0) {
+        year++;
+      }
+    }
+
+    return year;
+  }
+
+  /**
+   * Converts julian timestamp to epoch.
+   * @param timestamp
+   * @return
+   */
+  public static int julianTimeToEpoch(long timestamp) {
+    long totalSecs = timestamp / DateTimeConstants.USECS_PER_SEC;
+    return (int)(totalSecs + DateTimeConstants.SECS_DIFFERENCE_BETWEEN_JULIAN_AND_UNIXTIME);
+  }
+
+  /**
+   * Converts julian timestamp to java timestamp.
+   * @param timestamp
+   * @return
+   */
+  public static long julianTimeToJavaTime(long timestamp) {
+    double totalSecs = (double)timestamp / (double)DateTimeConstants.MSECS_PER_SEC;
+    return (long)(Math.round(totalSecs + DateTimeConstants.SECS_DIFFERENCE_BETWEEN_JULIAN_AND_UNIXTIME * 1000.0));
+  }
+
+  /**
+   * Converts java timestamp to julian timestamp.
+   * @param javaTimestamp
+   * @return
+   */
+  public static long javaTimeToJulianTime(long javaTimestamp) {
+    double totalSecs = javaTimestamp / 1000.0;
+    return (long)((totalSecs -
+        DateTimeConstants.SECS_DIFFERENCE_BETWEEN_JULIAN_AND_UNIXTIME) * DateTimeConstants.USECS_PER_SEC);
+  }
+
+  /**
+   * Calculate the time value(hour, minute, sec, fsec)
+   * If tm.TomeZone is set, the result value is adjusted.
+   * @param tm
+   * @return
+   */
+  public static long toTime(TimeMeta tm) {
+    if (tm.timeZone != 0 && tm.timeZone != Integer.MAX_VALUE) {
+      int timeZoneSecs = tm.timeZone;
+      tm.timeZone = Integer.MAX_VALUE;
+      tm.plusMillis(0 - timeZoneSecs * 1000);
+    }
+    return toTime(tm.hours, tm.minutes, tm.secs, tm.fsecs);
+  }
+
+  /**
+   * Calculate the time value(hour, minute, sec, fsec)
+   * @param hour
+   * @param min
+   * @param sec
+   * @param fsec
+   * @return
+   */
+  public static long toTime(int hour, int min, int sec, int fsec) {
+    return (((((hour * DateTimeConstants.MINS_PER_HOUR) + min) *
+        DateTimeConstants.SECS_PER_MINUTE) + sec) *
+        DateTimeConstants.USECS_PER_SEC) + fsec;
+  }
+
+  /**
+   * Calculate julian timestamp.
+   * @param years
+   * @param months
+   * @param days
+   * @param hours
+   * @param minutes
+   * @param seconds
+   * @param fsec
+   * @return
+   */
+  public static long toJulianTimestamp(
+      int years, int months, int days, int hours, int minutes, int seconds, int fsec) {
+    /* Julian day routines are not correct for negative Julian days_full */
+    if (!isValidJulianDate(years, months, days)) {
+      throw new ValueOutOfRangeException("Out of Range Julian days_full");
+    }
+
+    long numJulianDays = date2j(years, months, days) - DateTimeConstants.POSTGRES_EPOCH_JDATE;
+
+    return toJulianTimestamp(numJulianDays, hours, minutes, seconds, fsec);
+  }
+
+  /**
+   * Calculate julian timestamp.
+   * @param numJulianDays
+   * @param hours
+   * @param minutes
+   * @param seconds
+   * @param fsec
+   * @return
+   */
+  private static long toJulianTimestamp(long numJulianDays, int hours, int minutes, int seconds, int fsec) {
+    long time = toTime(hours, minutes, seconds, fsec);
+
+    long timestamp = numJulianDays * DateTimeConstants.USECS_PER_DAY + time;
+      /* check for major overflow */
+    if ((timestamp - time) / DateTimeConstants.USECS_PER_DAY != numJulianDays) {
+      throw new RuntimeException("Out of Range of Time");
+    }
+      /* check for just-barely overflow (okay except time-of-day wraps) */
+      /* caution: we want to allow 1999-12-31 24:00:00 */
+    if ((timestamp < 0 && numJulianDays > 0) || (timestamp > 0 && numJulianDays < -1)) {
+      throw new RuntimeException("Out of Range of Date");
+    }
+
+    return timestamp;
+  }
+
+  /**
+   * Calculate julian timestamp.
+   * If tm.TomeZone is set, the result value is adjusted.
+   * @param tm
+   * @return
+   */
+  public static long toJulianTimestamp(TimeMeta tm) {
+    if (tm.timeZone != 0 && tm.timeZone != Integer.MAX_VALUE) {
+      int timeZoneSecs = tm.timeZone;
+      tm.timeZone = Integer.MAX_VALUE;
+      tm.plusMillis(0 - timeZoneSecs * 1000);
+    }
+    if (tm.dayOfYear > 0) {
+      return toJulianTimestamp(date2j(tm.years, 1, 1) + tm.dayOfYear - 1, tm.hours, tm.minutes, tm.secs, tm.fsecs);
+    } else {
+      return toJulianTimestamp(tm.years, tm.monthOfYear, tm.dayOfMonth, tm.hours, tm.minutes, tm.secs, tm.fsecs);
+    }
+  }
+
+  /**
+   * Set TimeMeta's field value using given julian timestamp.
+   * Note that year is _not_ 1900-based, but is an explicit full value.
+   * Also, month is one-based, _not_ zero-based.
+   * Returns:
+   *	 0 on success
+   *	-1 on out of range
+   *
+   * If attimezone is NULL, the global timezone (including possibly brute forced
+   * timezone) will be used.
+   */
+  public static void toJulianTimeMeta(long julianTimestamp, TimeMeta tm) {
+    long date;
+    long time;
+
+    // TODO - If timezone is set, timestamp value should be adjusted here.
+    time = julianTimestamp;
+
+    // TMODULO
+    date = time / DateTimeConstants.USECS_PER_DAY;
+    if (date != 0) {
+      time -= date * DateTimeConstants.USECS_PER_DAY;
+    }
+    if (time < 0) {
+      time += DateTimeConstants.USECS_PER_DAY;
+      date -= 1;
+    }
+
+    /* add offset to go from J2000 back to standard Julian date */
+    date += DateTimeConstants.POSTGRES_EPOCH_JDATE;
+
+    /* Julian day routine does not work for negative Julian days_full */
+    if (date < 0 || date > Integer.MAX_VALUE) {
+      throw new RuntimeException("Timestamp Out Of Scope");
+    }
+
+    j2date((int) date, tm);
+    date2j(time, tm);
+  }
+
+  /**
+   * This method is originated from dt2time in timestamp.c of PostgreSQL.
+   *
+   * @param julianDate
+   * @return hour, min, sec, fsec
+   */
+  public static void date2j(long julianDate, TimeMeta tm) {
+    long time = julianDate;
+
+    tm.hours = (int) (time / DateTimeConstants.USECS_PER_HOUR);
+    time -= tm.hours * DateTimeConstants.USECS_PER_HOUR;
+    tm.minutes = (int) (time / DateTimeConstants.USECS_PER_MINUTE);
+    time -= tm.minutes * DateTimeConstants.USECS_PER_MINUTE;
+    tm.secs = (int) (time / DateTimeConstants.USECS_PER_SEC);
+    tm.fsecs = (int) (time - (tm.secs * DateTimeConstants.USECS_PER_SEC));
+  }
+
+  /**
+   * Decode date string which includes delimiters.
+   *
+   * This method is originated from DecodeDate() in datetime.c of PostgreSQL.
+   * @param str The date string like '2013-12-25'.
+   * @param fmask
+   * @param tmaskValue
+   * @param is2digits
+   * @param tm
+   */
+  private static void decodeDate(String str, int fmask, AtomicInteger tmaskValue,  AtomicBoolean is2digits, TimeMeta tm) {
+
+    int idx = 0;
+    int nf = 0;
+    TokenField type = null;
+    int val = 0;
+
+    AtomicInteger dmask = new AtomicInteger(0);
+    int tmask = tmaskValue.get();
+    boolean haveTextMonth = false;
+
+    int length = str.length();
+    char[] dateStr = str.toCharArray();
+    String[] fields = new String[MAXDATEFIELDS];
+
+    while(idx < length && nf < MAXDATEFIELDS) {
+
+      /* skip field separators */
+      while (idx < length && !Character.isLetterOrDigit(dateStr[idx])) {
+        idx++;
+      }
+
+      if (idx == length) {
+        throw new IllegalArgumentException("BAD Format: " + str);
+      }
+
+      int fieldStartIdx = idx;
+      int fieldLength = idx;
+      if (Character.isDigit(dateStr[idx])) {
+        while (idx < length && Character.isDigit(dateStr[idx])) {
+          idx++;
+        }
+        fieldLength = idx;
+      } else if (Character.isLetterOrDigit(dateStr[idx])) {
+        while (idx < length && Character.isLetterOrDigit(dateStr[idx])) {
+          idx++;
+        }
+        fieldLength = idx;
+      }
+
+      fields[nf] = str.substring(fieldStartIdx, fieldLength);
+      nf++;
+    }
+
+    /* look first for text fields, since that will be unambiguous month */
+    for (int i = 0; i < nf; i++) {
+      if (Character.isLetter(fields[i].charAt(0))) {
+        DateToken dateToken =  DateTimeConstants.dateTokenMap.get(fields[i].toLowerCase());
+        type = dateToken.getType();
+
+        if (type == TokenField.IGNORE_DTF) {
+          continue;
+        }
+
+        dmask.set(DateTimeConstants.DTK_M(type));
+        switch (type) {
+          case MONTH:
+            tm.monthOfYear = type.getValue();
+            haveTextMonth = true;
+            break;
+
+          default:
+            throw new IllegalArgumentException("BAD Format: " + str);
+        }
+        if ((fmask & dmask.get()) != 0) {
+          throw new IllegalArgumentException("BAD Format: " + str);
+        }
+
+        fmask |= dmask.get();
+        tmask |= dmask.get();
+
+			/* mark this field as being completed */
+        fields[i] = null;
+      }
+    }
+
+    /* now pick up remaining numeric fields */
+    for (int i = 0; i < nf; i++) {
+      if (fields[i] == null) {
+        continue;
+      }
+
+      length = fields[i].length();
+      if (length  <= 0) {
+        throw new IllegalArgumentException("BAD Format: " + str);
+      }
+
+      decodeNumber(length, fields[i], haveTextMonth, fmask, dmask, tm, new AtomicLong(0), is2digits);
+
+      if ( (fmask & dmask.get()) != 0 ) {
+        throw new IllegalArgumentException("BAD Format: " + str);
+      }
+      fmask |= dmask.get();
+      tmask |= dmask.get();
+    }
+
+    tmaskValue.set(tmask);
+
+    if ((fmask & ~(DateTimeConstants.DTK_M(TokenField.DOY) | DateTimeConstants.DTK_M(TokenField.TZ))) != DateTimeConstants.DTK_DATE_M) {
+      throw new IllegalArgumentException("BAD Format: " + str);
+    }
+  }
+
+  /**
+   * Decode time string which includes delimiters.
+   * Return 0 if okay, a DTERR code if not.
+   *
+   * Only check the lower limit on hours, since this same code can be
+   * used to represent time spans.
+   * @param str
+   * @param fmask
+   * @param range
+   * @param tmask
+   * @param tm
+   * @param fsec
+   */
+  private static void decodeTime(String str, int fmask, int range,
+             AtomicInteger tmask, TimeMeta tm, AtomicLong fsec) {
+    StringBuilder cp = new StringBuilder();
+
+    tmask.set(DateTimeConstants.DTK_TIME_M);
+
+    tm.hours = strtoi(str, 0, cp);
+    if (cp.charAt(0) != ':') {
+      throw new IllegalArgumentException("BAD Format: " + str);
+    }
+
+    tm.minutes = strtoi(cp.toString(), 1, cp);
+
+    if (cp.length() == 0) {
+      tm.secs = 0;
+      fsec.set(0);
+		  /* If it's a MINUTE TO SECOND interval, take 2 fields as being mm:ss */
+      if (range == (DateTimeConstants.INTERVAL_MASK(TokenField.MINUTE) | DateTimeConstants.INTERVAL_MASK(TokenField.SECOND))) {
+        tm.secs = tm.minutes;
+        tm.minutes = tm.hours;
+        tm.hours = 0;
+      }
+    } else if (cp.charAt(0) == '.') {
+		  /* always assume mm:ss.sss is MINUTE TO SECOND */
+      ParseFractionalSecond(cp, fsec);
+      tm.secs = tm.minutes;
+      tm.minutes = tm.hours;
+      tm.hours = 0;
+    }
+    else if (cp.charAt(0) == ':') {
+      tm.secs = strtoi(cp.toString(), 1, cp);
+      if (cp.length() == 0){
+        fsec.set(0);
+      } else if (cp.charAt(0) == '.') {
+        ParseFractionalSecond(cp, fsec);
+      } else{
+        throw new IllegalArgumentException("BAD Format: " + str);
+      }
+    } else {
+      throw new IllegalArgumentException("BAD Format: " + str);
+    }
+
+	/* do a sanity check */
+    if (tm.hours < 0 || tm.minutes < 0 || tm.minutes > DateTimeConstants.MINS_PER_HOUR - 1 ||
+        tm.secs < 0 || tm.secs > DateTimeConstants.SECS_PER_MINUTE ||
+            fsec.get() < 0 ||
+            fsec.get() > DateTimeConstants.USECS_PER_SEC) {
+      throw new IllegalArgumentException("BAD Format: FIELD_OVERFLOW: " + str);
+    }
+  }
+
+  /**
+   * Parse datetime string to julian time.
+   * The result is the UTC time basis.
+   * @param str
+   * @return
+   */
+  public static long toJulianTimestamp(String str) {
+    TimeMeta tm = decodeDateTime(str, MAXDATEFIELDS);
+    return toJulianTimestamp(tm);
+  }
+
+  public static TimeMeta decodeDateTime(String str) {
+    return decodeDateTime(str, MAXDATEFIELDS);
+  }
+
+  /**
+   * Break string into tokens based on a date/time context.
+   *
+   * This method is originated form ParseDateTime() in datetime.c of PostgreSQL.
+   *
+   * @param str The input string
+   * @param maxFields
+   */
+  public static TimeMeta decodeDateTime(String str, int maxFields) {
+    int idx = 0;
+    int nf = 0;
+    int length = str.length();
+    char [] timeStr = str.toCharArray();
+    String [] fields = new String[maxFields];
+    TokenField[] fieldTypes = new TokenField[maxFields];
+
+    while (idx < length) {
+
+      /* Ignore spaces between fields */
+      if (Character.isSpaceChar(timeStr[idx])) {
+        idx++;
+        continue;
+      }
+
+      /* Record start of current field */
+      if (nf >= maxFields) {
+        throw new IllegalArgumentException("Too many fields");
+      }
+
+      int startIdx = idx;
+
+      //January 8, 1999
+      /* leading digit? then date or time */
+      if (Character.isDigit(timeStr[idx])) {
+        idx++;
+        while (idx < length && Character.isDigit(timeStr[idx])) {
+          idx++;
+        }
+
+        if (idx < length && timeStr[idx] == ':') {
+          fieldTypes[nf] = TokenField.DTK_TIME;
+
+          while (idx <length && (Character.isDigit(timeStr[idx]) || timeStr[idx] == ':' || timeStr[idx] == '.')) {
+            idx++;
+          }
+        }
+
+        /* date field? allow embedded text month */
+        else if (idx < length && (timeStr[idx] == '-' || timeStr[idx] == '/' || timeStr[idx] == '.')) {
+
+          /* save delimiting character to use later */
+          char delim = timeStr[idx];
+          idx++;
+
+          /* second field is all digits? then no embedded text month */
+          if (Character.isDigit(timeStr[idx])) {
+            fieldTypes[nf] = delim == '.' ? TokenField.DTK_NUMBER : TokenField.DTK_DATE;
+
+            while (idx < length && Character.isDigit(timeStr[idx])) {
+              idx++;
+            }
+
+            /*
+					   * insist that the delimiters match to get a three-field
+					   * date.
+					   */
+            if (idx < length && timeStr[idx] == delim) {
+              fieldTypes[nf] = TokenField.DTK_DATE;
+              idx++;
+              while (idx < length && (Character.isDigit(timeStr[idx]) || timeStr[idx] == delim)) {
+                idx++;
+              }
+            }
+          } else {
+            fieldTypes[nf] = TokenField.DTK_DATE;
+            while (idx < length && Character.isLetterOrDigit(timeStr[idx]) || timeStr[idx] == delim) {
+              idx++;
+            }
+          }
+        } else {
+          /*
+			     * otherwise, number only and will determine year, month, day, or
+			     * concatenated fields later...
+			    */
+          fieldTypes[nf] = TokenField.DTK_NUMBER;
+        }
+      }
+
+      /* Leading decimal point? Then fractional seconds... */
+      else if (timeStr[idx] == '.') {
+        idx++;
+        while (idx < length && Character.isDigit(timeStr[idx])) {
+          idx++;
+          continue;
+        }
+        fieldTypes[nf] = TokenField.DTK_NUMBER;
+      }
+
+      // text? then date string, month, day of week, special, or timezone
+      else if (Character.isLetter(timeStr[idx])) {
+        boolean isDate;
+        idx++;
+        while (idx < length && Character.isLetter(timeStr[idx])) {
+          idx++;
+        }
+
+        // Dates can have embedded '-', '/', or '.' separators.  It could
+        // also be a timezone name containing embedded '/', '+', '-', '_',
+        // or ':' (but '_' or ':' can't be the first punctuation). If the
+        // next character is a digit or '+', we need to check whether what
+        // we have so far is a recognized non-timezone keyword --- if so,
+        // don't believe that this is the start of a timezone.
+
+        isDate = false;
+        if (idx < length && (timeStr[idx] == '-' || timeStr[idx] == '/' || timeStr[idx] == '.')) {
+          isDate = true;
+        } else if (idx < length && (timeStr[idx] == '+' || Character.isDigit(timeStr[idx]))) {
+          // The original ParseDateTime handles this case. But, we currently omit this case.
+          throw new IllegalArgumentException("Cannot parse this datetime field " + str.substring(startIdx, idx));
+        }
+
+        if (isDate) {
+          fieldTypes[nf] = TokenField.DTK_DATE;
+
+          do {
+            idx++;
+          } while (idx <length && (timeStr[idx] == '+' || timeStr[idx] == '-' || timeStr[idx] == '/' ||
+              timeStr[idx] == '_' || timeStr[idx] == '.' || timeStr[idx] == ':' ||
+              Character.isLetterOrDigit(timeStr[idx])));
+        } else {
+          fieldTypes[nf] = TokenField.DTK_STRING;
+        }
+      }
+
+      // sign? then special or numeric timezone
+      else if (timeStr[idx] == '+' || timeStr[idx] == '-') {
+        idx++;
+
+        // soak up leading whitespace
+        while (idx < length && Character.isSpaceChar(timeStr[idx])) {
+          idx++;
+        }
+
+        // numeric timezone?
+        // note that "DTK_TZ" could also be a signed float or yyyy-mm */
+        if (idx < length && Character.isDigit(timeStr[idx])) {
+          fieldTypes[nf] = TokenField.DTK_TZ;
+          idx++;
+
+          while (idx < length && (Character.isDigit(timeStr[idx]) || timeStr[idx] == ':' || timeStr[idx] == '.' ||
+              timeStr[idx] == '-')) {
+            idx++;
+          }
+        }
+        /* special? */
+        else if (idx < length && Character.isLetter(timeStr[idx])) {
+          fieldTypes[nf] = TokenField.DTK_SPECIAL;
+          idx++;
+
+          while (idx < length && Character.isLetter(timeStr[idx])) {
+            idx++;
+          }
+        } else {
+          throw new IllegalArgumentException("BAD Format: " + str.substring(startIdx, idx));
+        }
+      }
+      /* ignore other punctuation but use as delimiter */
+      else if (isPunctuation(timeStr[idx])) {
+        idx++;
+        continue;
+      } else {  // otherwise, something is not right...
+        throw new IllegalArgumentException("BAD datetime format: " + str.substring(startIdx, idx));
+      }
+
+      fields[nf] = str.substring(startIdx, idx);
+      nf++;
+    }
+    return decodeDateTime(fields, fieldTypes, nf);
+  }
+
+  /**
+   * Fetch a fractional-second value with suitable error checking
+   * @param cp
+   * @param fsec
+   */
+  public static void ParseFractionalSecond(StringBuilder cp, AtomicLong fsec) {
+	  /* Caller should always pass the start of the fraction part */
+    double frac = strtod(cp.toString(), 1, cp);
+    fsec.set(Math.round(frac * 1000000));
+  }
+
+  /**
+   * Interpret string as a numeric timezone.
+   *
+   * Return 0 if okay (and set *tzp), a DTERR code if not okay.
+   *
+   * NB: this must *not* ereport on failure; see commands/variable.c.
+   * @param str
+   * @param tz
+   */
+  public static void decodeTimezone(String str, AtomicInteger tz) {
+    int min = 0;
+    int sec = 0;
+    StringBuilder sb = new StringBuilder();
+
+    int strIndex = 0;
+	  /* leading character must be "+" or "-" */
+    if (str.charAt(strIndex) != '+' && str.charAt(strIndex) != '-') {
+      throw new IllegalArgumentException("BAD Format: " + str);
+    }
+    int hr = strtoi(str, 1, sb);
+
+	  /* explicit delimiter? */
+    if (sb.length() > 0 && sb.charAt(0) == ':') {
+      min = strtoi(sb.toString(), 1, sb);
+      if (sb.charAt(0) == ':') {
+        sec = strtoi(sb.toString(), 1, sb);
+      }
+    }
+	  /* otherwise, might have run things together... */
+    else if (sb.length() == 0 && str.length() > 3) {
+      min = hr % 100;
+      hr = hr / 100;
+		/* we could, but don't, support a run-together hhmmss format */
+    } else {
+      min = 0;
+    }
+	  /* Range-check the values; see notes in datatype/timestamp.h */
+    if (hr < 0 || hr > DateTimeConstants.MAX_TZDISP_HOUR) {
+      throw new IllegalArgumentException("BAD Format: TZDISP_OVERFLOW: " + str);
+    }
+    if (min < 0 || min >= DateTimeConstants.MINS_PER_HOUR) {
+      throw new IllegalArgumentException("BAD Format: TZDISP_OVERFLOW: " + str);
+    }
+    if (sec < 0 || sec >= DateTimeConstants.SECS_PER_MINUTE) {
+      throw new IllegalArgumentException("BAD Format: TZDISP_OVERFLOW: " + str);
+    }
+
+    int tzValue = (hr * DateTimeConstants.MINS_PER_HOUR + min) * DateTimeConstants.SECS_PER_MINUTE + sec;
+    if (str.charAt(strIndex) == '-') {
+      tzValue = -tzValue;
+    }
+    tz.set(tzValue);
+  }
+
+  /**
+   * Interpret plain numeric field as a date value in context.
+   * @param flen
+   * @param str
+   * @param haveTextMonth
+   * @param fmask
+   * @param tmaskValue
+   * @param tm
+   * @param fsec
+   * @param is2digits
+   */
+  private static void decodeNumber(int flen, String str, boolean haveTextMonth, int fmask,
+               AtomicInteger tmaskValue, TimeMeta tm, AtomicLong fsec, AtomicBoolean is2digits) {
+    int	val;
+    StringBuilder cp = new StringBuilder();
+
+    int tmask = 0;
+    tmaskValue.set(tmask);
+
+    val = strtoi(str, 0, cp);
+    if (cp.toString().equals(str)) {
+      throw new IllegalArgumentException("BAD Format: " + str);
+    }
+
+    if (cp.length() > 0 && cp.charAt(0) == '.') {
+		/*
+		 * More than two digits before decimal point? Then could be a date or
+		 * a run-together time: 2001.360 20011225 040506.789
+		 */
+      if (cp.length() - str.length() > 2) {
+        decodeNumberField(flen, str,
+            (fmask | DateTimeConstants.DTK_DATE_M),
+            tmaskValue, tm,
+            fsec, is2digits);
+        return;
+      }
+      ParseFractionalSecond(cp, fsec);
+    }
+
+  	// Special case for day of year
+    if (flen == 3 && (fmask & DateTimeConstants.DTK_DATE_M) == DateTimeConstants.DTK_M(TokenField.YEAR) &&
+        val >= 1 && val <= 366) {
+      tmaskValue.set((DateTimeConstants.DTK_M(TokenField.DOY) |
+          DateTimeConstants.DTK_M(TokenField.MONTH) |
+          DateTimeConstants.DTK_M(TokenField.DAY)));
+      tm.dayOfYear = val;
+		  // tm_mon and tm_mday can't actually be set yet ...
+      return;
+    }
+
+	  /* Switch based on what we have so far */
+    int checkValue = fmask & DateTimeConstants.DTK_DATE_M;
+    if (checkValue == 0) {
+			/*
+			 * Nothing so far; make a decision about what we think the input
+			 * is.	There used to be lots of heuristics here, but the
+			 * consensus now is to be paranoid.  It *must* be either
+			 * YYYY-MM-DD (with a more-than-two-digit year field), or the
+			 * field order defined by DateOrder.
+			 */
+      if (flen >= 3 || TajoConf.getDateOrder() == DateTimeConstants.DATEORDER_YMD) {
+        tmaskValue.set(DateTimeConstants.DTK_M(TokenField.YEAR));
+        tm.years = val;
+      } else if (TajoConf.getDateOrder() == DateTimeConstants.DATEORDER_DMY) {
+        tmaskValue.set(DateTimeConstants.DTK_M(TokenField.DAY));
+        tm.dayOfMonth = val;
+      } else {
+        tmaskValue.set(DateTimeConstants.DTK_M(TokenField.MONTH));
+        tm.monthOfYear = val;
+      }
+    } else if (checkValue == (DateTimeConstants.DTK_M(TokenField.YEAR))) {
+			/* Must be at second field of YY-MM-DD */
+      tmaskValue.set(DateTimeConstants.DTK_M(TokenField.MONTH));
+      tm.monthOfYear = val;
+    } else if (checkValue == (DateTimeConstants.DTK_M(TokenField.MONTH))) {
+      if (haveTextMonth) {
+				/*
+				 * We are at the first numeric field of a date that included a
+				 * textual month name.	We want to support the variants
+				 * MON-DD-YYYY, DD-MON-YYYY, and YYYY-MON-DD as unambiguous
+				 * inputs.	We will also accept MON-DD-YY or DD-MON-YY in
+				 * either DMY or MDY modes, as well as YY-MON-DD in YMD mode.
+				 */
+        if (flen >= 3 || TajoConf.getDateOrder() == DateTimeConstants.DATEORDER_YMD) {
+          tmaskValue.set(DateTimeConstants.DTK_M(TokenField.YEAR));
+          tm.years = val;
+        } else {
+          tmaskValue.set(DateTimeConstants.DTK_M(TokenField.DAY));
+          tm.dayOfMonth = val;
+        }
+      } else {
+				/* Must be at second field of MM-DD-YY */
+        tmaskValue.set(DateTimeConstants.DTK_M(TokenField.DAY));
+        tm.dayOfMonth = val;
+      }
+    } else if (checkValue == (DateTimeConstants.DTK_M(TokenField.YEAR) | DateTimeConstants.DTK_M(TokenField.MONTH))) {
+      if (haveTextMonth) {
+				/* Need to accept DD-MON-YYYY even in YMD mode */
+        if (flen >= 3 && is2digits.get()) {
+					/* Guess that first numeric field is day was wrong */
+          tmaskValue.set(DateTimeConstants.DTK_M(TokenField.DAY));		/* YEAR is already set */
+          tm.dayOfMonth = tm.years;
+          tm.years = val;
+          is2digits.set(false);
+        } else {
+          tmaskValue.set(DateTimeConstants.DTK_M(TokenField.DAY));
+          tm.dayOfMonth = val;
+        }
+      } else {
+				/* Must be at third field of YY-MM-DD */
+        tmaskValue.set(DateTimeConstants.DTK_M(TokenField.DAY));
+        tm.dayOfMonth = val;
+      }
+    } else if (checkValue == DateTimeConstants.DTK_M(TokenField.DAY)) {
+			/* Must be at second field of DD-MM-YY */
+      tmaskValue.set(DateTimeConstants.DTK_M(TokenField.MONTH));
+      tm.monthOfYear = val;
+    } else if (checkValue == (DateTimeConstants.DTK_M(TokenField.MONTH) | DateTimeConstants.DTK_M(TokenField.DAY))) {
+			/* Must be at third field of DD-MM-YY or MM-DD-YY */
+      tmaskValue.set(DateTimeConstants.DTK_M(TokenField.YEAR));
+      tm.years = val;
+    } else if (checkValue == (DateTimeConstants.DTK_M(TokenField.YEAR) | DateTimeConstants.DTK_M(TokenField.MONTH) | DateTimeConstants.DTK_M(TokenField.DAY))) {
+			/* we have all the date, so it must be a time field */
+      decodeNumberField(flen, str, fmask,
+          tmaskValue, tm,
+          fsec, is2digits);
+      return;
+
+    } else {
+      throw new IllegalArgumentException("BAD Format: " + str);
+    }
+
+	/*
+	 * When processing a year field, mark it for adjustment if it's only one
+	 * or two digits.
+	 */
+    if (tmaskValue.get() == DateTimeConstants.DTK_M(TokenField.YEAR)) {
+      is2digits.set(flen <= 2);
+    }
+  }
+
+  /**
+   * Interpret numeric string as a concatenated date or time field.
+   *
+   * Use the context of previously decoded fields to help with
+   * the interpretation.
+   * @param len
+   * @param str
+   * @param fmask
+   * @param tmaskValue
+   * @param tm
+   * @param fsec
+   * @param is2digits
+   * @return
+   */
+  static TokenField decodeNumberField(int len, String str, int fmask,
+                    AtomicInteger tmaskValue, TimeMeta tm, AtomicLong fsec, AtomicBoolean is2digits) {
+    /*
+     * Have a decimal point? Then this is a date or something with a seconds
+     * field...
+     */
+    int index = str.indexOf('.');
+
+    if (index >= 0) {
+      String cp = str.substring(index + 1);
+		/*
+		 * Can we use ParseFractionalSecond here?  Not clear whether trailing
+		 * junk should be rejected ...
+		 */
+      double frac = strtod(cp, 0, null);
+      fsec.set(Math.round(frac * 1000000));
+		  /* Now truncate off the fraction for further processing */
+      len = str.length();
+    }
+	  /* No decimal point and no complete date yet? */
+    else if ((fmask & DateTimeConstants.DTK_DATE_M) != DateTimeConstants.DTK_DATE_M) {
+		  /* yyyymmdd? */
+      if (len == 8) {
+        tmaskValue.set(DateTimeConstants.DTK_DATE_M);
+
+        tm.dayOfMonth = Integer.parseInt(str.substring(6));
+        tm.monthOfYear = Integer.parseInt(str.substring(4, 6));
+        tm.years = Integer.parseInt(str.substring(0, 4));
+
+        return TokenField.DTK_DATE;
+      }
+		  /* yymmdd? */
+      else if (len == 6) {
+        tmaskValue.set(DateTimeConstants.DTK_DATE_M);
+        tm.dayOfMonth = Integer.parseInt(str.substring(4));
+        tm.monthOfYear = Integer.parseInt(str.substring(2, 4));
+        tm.years = Integer.parseInt(str.substring(0, 2));
+        is2digits.set(true);
+
+        return TokenField.DTK_DATE;
+      }
+    }
+
+	  /* not all time fields are specified? */
+    if ((fmask & DateTimeConstants.DTK_TIME_M) != DateTimeConstants.DTK_TIME_M) {
+		  /* hhmmss */
+      if (len == 6) {
+        tmaskValue.set(DateTimeConstants.DTK_TIME_M);
+        tm.secs = Integer.parseInt(str.substring(4));
+        tm.minutes = Integer.parseInt(str.substring(2, 4));
+        tm.hours = Integer.parseInt(str.substring(0, 2));
+
+        return TokenField.DTK_TIME;
+      }
+		  /* hhmm? */
+      else if (len == 4) {
+        tmaskValue.set(DateTimeConstants.DTK_TIME_M);
+        tm.secs = 0;
+        tm.minutes = Integer.parseInt(str.substring(2, 4));
+        tm.hours = Integer.parseInt(str.substring(0, 2));
+
+        return TokenField.DTK_TIME;
+      }
+    }
+
+    throw new IllegalArgumentException("BAD Format: " + str);
+  }
+
+  private static TimeMeta decodeDateTime(String[] fields, TokenField[] fieldTypes, int nf) {
+    int	fmask = 0;
+    AtomicInteger tmask = new AtomicInteger(0);
+    int type;
+
+    /* "prefix type" for ISO y2001m02d04 format */
+    TokenField ptype = null;
+
+    boolean		haveTextMonth = false;
+    boolean		isjulian = false;
+    AtomicBoolean is2digits = new AtomicBoolean(false);
+    boolean		bc = false;
+
+    int tzp = Integer.MAX_VALUE;
+    String namedTimeZone = null;
+
+    StringBuilder sb = new StringBuilder();
+    // We'll insist on at least all of the date fields, but initialize the
+    // remaining fields in case they are not set later...
+    TokenField dtype = TokenField.DTK_DATE;
+    TokenField mer = null;
+
+    TimeMeta tm = new TimeMeta();
+    TimeMeta cur_tm = new TimeMeta();
+
+    AtomicLong fsec = new AtomicLong();
+    AtomicInteger tz = new AtomicInteger(Integer.MAX_VALUE);
+
+    // don't know daylight savings time status apriori */
+    tm.isDST = false;
+
+    for (int i = 0; i < nf; i++) {
+      if (fieldTypes[i] == null) {
+        continue;
+      }
+      switch (fieldTypes[i]) {
+        case DTK_DATE:
+          /***
+           * Integral julian day with attached time zone?
+           * All other forms with JD will be separated into
+           * distinct fields, so we handle just this case here.
+           ***/
+          if (ptype == TokenField.DTK_JULIAN) {
+            int			val;
+
+            if (tzp == Integer.MAX_VALUE) {
+              throw new IllegalArgumentException("BAD Format: " + fields[i]);
+            }
+
+            val = strtoi(fields[i], 0, sb);
+
+            date2j(val, tm);
+            isjulian = true;
+
+					  /* Get the time zone from the end of the string */
+            decodeTimezone(sb.toString(), tz);
+
+            tmask.set(DateTimeConstants.DTK_DATE_M | DateTimeConstants.DTK_TIME_M | DateTimeConstants.DTK_M(TokenField.TZ));
+            ptype = null;
+            break;
+          }
+          /***
+           * Already have a date? Then this might be a time zone name
+           * with embedded punctuation (e.g. "America/New_York") or a
+           * run-together time with trailing time zone (e.g. hhmmss-zz).
+           * - thomas 2001-12-25
+           *
+           * We consider it a time zone if we already have month & day.
+           * This is to allow the form "mmm dd hhmmss tz year", which
+           * we've historically accepted.
+           ***/
+          else if (ptype != null ||
+              ((fmask & (DateTimeConstants.DTK_M(TokenField.MONTH) | DateTimeConstants.DTK_M(TokenField.DAY))) ==
+                  (DateTimeConstants.DTK_M(TokenField.MONTH) | DateTimeConstants.DTK_M(TokenField.DAY))))
+          {
+					/* No time zone accepted? Then quit... */
+            if (tzp == Integer.MAX_VALUE) {
+              throw new IllegalArgumentException("BAD Format: " + fields[i]);
+            }
+
+            if (Character.isDigit(fields[i].charAt(0)) || ptype != null) {
+              if (ptype != null) {
+							  /* Sanity check; should not fail this test */
+                if (ptype != TokenField.DTK_TIME) {
+                  throw new IllegalArgumentException("BAD Format: " + fields[i]);
+                }
+                ptype = null;
+              }
+
+						/*
+						 * Starts with a digit but we already have a time
+						 * field? Then we are in trouble with a date and time
+						 * already...
+						 */
+              if ((fmask & DateTimeConstants.DTK_TIME_M) == DateTimeConstants.DTK_TIME_M) {
+                throw new IllegalArgumentException("BAD Format: " + fields[i]);
+              }
+
+              int index = fields[i].indexOf("-");
+              if (index < 0) {
+                throw new IllegalArgumentException("BAD Format: " + fields[i]);
+              }
+
+						  /* Get the time zone from the end of the string */
+              decodeTimezone(fields[i].substring(index + 1), tz);
+
+              /*
+               * Then read the rest of the field as a concatenated
+               * time
+               */
+              decodeNumberField(fields[i].length(), fields[i],
+                  fmask,
+                  tmask, tm,
+                  fsec, is2digits);
+
+              /*
+               * modify tmask after returning from
+               * DecodeNumberField()
+               */
+              tmask.set(tmask.get() | DateTimeConstants.DTK_M(TokenField.TZ));
+            }
+            else {
+              namedTimeZone = pg_tzset(fields[i]);
+              if (namedTimeZone == null) {
+							/*
+							 * We should return an error code instead of
+							 * ereport'ing directly, but then there is no way
+							 * to report the bad time zone name.
+							 */
+                throw new IllegalArgumentException("BAD Format: time zone \"%s\" not recognized: " + fields[i]);
+              }
+						  /* we'll apply the zone setting below */
+              tmask.set(DateTimeConstants.DTK_M(TokenField.TZ));
+            }
+          } else {
+            decodeDate(fields[i], fmask, tmask, is2digits, tm);
+          }
+          break;
+
+        case DTK_TIME:
+          decodeTime(fields[i], (fmask | DateTimeConstants.DTK_DATE_M),
+              DateTimeConstants.INTERVAL_FULL_RANGE,
+              tmask, tm, fsec);
+          break;
+
+        case DTK_TZ: {
+          decodeTimezone(fields[i], tz);
+          tmask.set(DateTimeConstants.DTK_M(TokenField.TZ));
+          break;
+        }
+
+        case DTK_NUMBER:
+
+          /*
+           * Was this an "ISO date" with embedded field labels? An
+           * example is "y2001m02d04" - thomas 2001-02-04
+           */
+          if (ptype != null) {
+            int val = strtoi(fields[i], 0, sb);
+
+            /*
+             * only a few kinds are allowed to have an embedded
+             * decimal
+             */
+            if (sb.length() == 0) {
+              continue;
+            }
+            if (sb.charAt(0) == '.') {
+              switch (ptype) {
+                case DTK_JULIAN:
+                case DTK_TIME:
+                case DTK_SECOND:
+                  break;
+                default:
+                  throw new IllegalArgumentException("BAD Format: " + fields[i]);
+              }
+            } else {
+              throw new IllegalArgumentException("BAD Format: " + fields[i]);
+            }
+            switch (ptype) {
+              case DTK_YEAR:
+                tm.years = val;
+                tmask.set(DateTimeConstants.DTK_M(TokenField.YEAR));
+                break;
+
+              case DTK_MONTH:
+
+                /*
+                 * already have a month and hour? then assume
+                 * minutes
+                 */
+                if ((fmask & DateTimeConstants.DTK_M(TokenField.MONTH)) != 0 &&
+                    (fmask & DateTimeConstants.DTK_M(TokenField.HOUR)) != 0) {
+                  tm.minutes = val;
+                  tmask.set(DateTimeConstants.DTK_M(TokenField.MINUTE));
+                }
+                else {
+                  tm.monthOfYear = val;
+                  tmask.set(DateTimeConstants.DTK_M(TokenField.MONTH));
+                }
+                break;
+
+              case DTK_DAY:
+                tm.dayOfMonth = val;
+                tmask.set(DateTimeConstants.DTK_M(TokenField.DAY));
+                break;
+
+              case DTK_HOUR:
+                tm.hours = val;
+                tmask.set(DateTimeConstants.DTK_M(TokenField.HOUR));
+                break;
+
+              case DTK_MINUTE:
+                tm.minutes = val;
+                tmask.set(DateTimeConstants.DTK_M(TokenField.MINUTE));
+                break;
+
+              case DTK_SECOND:
+                tm.secs = val;
+                tmask.set(DateTimeConstants.DTK_M(TokenField.SECOND));
+                if (sb.charAt(0) == '.') {
+                  ParseFractionalSecond(sb, fsec);
+                  tmask.set(DateTimeConstants.DTK_ALL_SECS_M);
+                }
+                break;
+              case DTK_TZ:
+                tmask.set(DateTimeConstants.DTK_M(TokenField.TZ));
+                decodeTimezone(fields[i], tz);
+                break;
+
+              case DTK_JULIAN:
+							  /* previous field was a label for "julian date" */
+                if (val < 0) {
+                  throw new IllegalArgumentException("BAD Format: FIELD_OVERFLOW: " + fields[i]);
+                }
+                tmask.set(DateTimeConstants.DTK_DATE_M);
+                date2j(val, tm);
+                isjulian = true;
+
+							  /* fractional Julian Day? */
+                if (sb.charAt(0) == '.') {
+                  double time = strtod(sb.toString(), 0, sb);
+
+                  time *= DateTimeConstants.USECS_PER_DAY;
+                  date2j((long)time, tm);
+                  tmask.set(tmask.get() | DateTimeConstants.DTK_TIME_M);
+                }
+                break;
+
+              case DTK_TIME:
+							/* previous field was "t" for ISO time */
+                decodeNumberField(fields[i].length(), fields[i],
+                    (fmask | DateTimeConstants.DTK_DATE_M),
+                    tmask, tm,
+                    fsec, is2digits);
+                if (tmask.get() != DateTimeConstants.DTK_TIME_M) {
+                  throw new IllegalArgumentException("BAD Format: FIELD_OVERFLOW: " + fields[i]);
+                }
+                break;
+
+              default:
+                throw new IllegalArgumentException("BAD Format: " + fields[i]);
+            }
+
+            ptype = null;
+            dtype = TokenField.DTK_DATE;
+          } else {
+            int flen = fields[i].length();
+            int index = fields[i].indexOf(".");
+            String cp = null;
+            if (index > 0) {
+              cp = fields[i].substring(index + 1);
+            }
+
+					  /* Embedded decimal and no date yet? */
+            if (cp != null && ((fmask & DateTimeConstants.DTK_DATE_M) == 0 )) {
+              decodeDate(fields[i], fmask,
+                  tmask, is2digits, tm);
+            }
+					  /* embedded decimal and several digits before? */
+            else if (cp != null && flen - cp.length() > 2) {
+						/*
+						 * Interpret as a concatenated date or time Set the
+						 * type field to allow decoding other fields later.
+						 * Example: 20011223 or 040506
+						 */
+              decodeNumberField(flen, fields[i], fmask,
+                  tmask, tm,
+                  fsec, is2digits);
+            }
+            else if (flen > 4) {
+              decodeNumberField(flen, fields[i], fmask,
+                  tmask, tm,
+                  fsec, is2digits);
+            }
+					  /* otherwise it is a single date/time field... */
+            else {
+              decodeNumber(flen, fields[i],
+                  haveTextMonth, fmask,
+                  tmask, tm,
+                  fsec, is2digits);
+            }
+          }
+          break;
+        case DTK_STRING:
+        case DTK_SPECIAL:
+          DateToken dateToken =  DateTimeConstants.dateTokenMap.get(fields[i].toLowerCase());
+          if (dateToken == null) {
+            throw new IllegalArgumentException("BAD Format: " + fields[i]);
+          }
+          tmask.set(DateTimeConstants.DTK_M(dateToken.getType()));
+          switch (dateToken.getType()) {
+            case RESERV:
+              switch(dateToken.getValueType()) {
+                case DTK_CURRENT:
+                  throw new IllegalArgumentException("BAD Format: date/time value \"current\" is no longer supported" + fields[i]);
+
+                case DTK_NOW:
+                  tmask.set(DateTimeConstants.DTK_DATE_M | DateTimeConstants.DTK_TIME_M | DateTimeConstants.DTK_M(TokenField.TZ));
+                  dtype = TokenField.DTK_DATE;
+                  date2j(javaTimeToJulianTime(System.currentTimeMillis()), tm);
+                  break;
+
+                case DTK_YESTERDAY:
+                  tmask.set(DateTimeConstants.DTK_DATE_M);
+                  dtype = TokenField.DTK_DATE;
+                  date2j(javaTimeToJulianTime(System.currentTimeMillis()), tm);
+                  tm.plusDays(-1);
+                  break;
+
+                case DTK_TODAY:
+                  tmask.set(DateTimeConstants.DTK_DATE_M);
+                  dtype = TokenField.DTK_DATE;
+                  date2j(javaTimeToJulianTime(System.currentTimeMillis()), cur_tm);
+                  tm.years = cur_tm.years;
+                  tm.monthOfYear = cur_tm.monthOfYear;
+                  tm.dayOfMonth = cur_tm.dayOfMonth;
+                  break;
+
+                case DTK_TOMORROW:
+                  tmask.set(DateTimeConstants.DTK_DATE_M);
+                  dtype = TokenField.DTK_DATE;
+                  date2j(javaTimeToJulianTime(System.currentTimeMillis()), tm);
+                  tm.plusDays(1);
+                  break;
+
+                case DTK_ZULU:
+                  tmask.set(DateTimeConstants.DTK_TIME_M | DateTimeConstants.DTK_M(TokenField.TZ));
+                  dtype = TokenField.DTK_DATE;
+                  tm.hours = 0;
+                  tm.minutes = 0;
+                  tm.secs = 0;
+                  break;
+
+                default:
+                  dtype = dateToken.getValueType();
+              }
+              break;
+
+            case MONTH:
+              /*
+               * already have a (numeric) month? then see if we can
+               * substitute...
+               */
+              if ((fmask & DateTimeConstants.DTK_M(TokenField.MONTH)) != 0 && !haveTextMonth &&
+                  (fmask & DateTimeConstants.DTK_M(TokenField.DAY)) == 0 &&
+                  tm.monthOfYear >= 1 && tm.monthOfYear <= 31) {
+                tm.dayOfMonth = tm.monthOfYear;
+                tmask.set(DateTimeConstants.DTK_M(TokenField.DAY));
+              }
+              haveTextMonth = true;
+              tm.monthOfYear = dateToken.getValue();
+              break;
+
+            case DTZMOD:
+
+						/*
+						 * daylight savings time modifier (solves "MET DST"
+						 * syntax)
+						 */
+              tmask.set(tmask.get() | DateTimeConstants.DTK_M(TokenField.DTZ));
+              tm.isDST = true;
+              if (tzp == Integer.MAX_VALUE) {
+                throw new IllegalArgumentException("BAD Format: " + fields[i]);
+              }
+              tzp += dateToken.getValue() * DateTimeConstants.MINS_PER_HOUR;
+              break;
+
+            case DTZ:
+
+						/*
+						 * set mask for TZ here _or_ check for DTZ later when
+						 * getting default timezone
+						 */
+              tmask.set(tmask.get() | DateTimeConstants.DTK_M(TokenField.TZ));
+              tm.isDST = true;
+              if (tzp == Integer.MAX_VALUE) {
+                throw new IllegalArgumentException("BAD Format: " + fields[i]);
+              }
+              tzp = dateToken.getValue() * DateTimeConstants.MINS_PER_HOUR;
+              break;
+
+            case TZ:
+              tm.isDST = false;
+              if (tzp == Integer.MAX_VALUE) {
+                throw new IllegalArgumentException("BAD Format: " + fields[i]);
+              }
+              tzp = dateToken.getValue() * DateTimeConstants.MINS_PER_HOUR;
+              break;
+
+            case IGNORE_DTF:
+              break;
+
+            case AMPM:
+              mer = dateToken.getValueType();
+              break;
+
+            case ADBC:
+              bc = (dateToken.getValueType() == TokenField.BC);
+              break;
+
+            case DOW:
+              tm.dayOfWeek = dateToken.getValue();
+              break;
+
+            case UNITS:
+              tmask.set(0);
+              ptype = dateToken.getValueType();
+              break;
+
+            case ISOTIME:
+
+						/*
+						 * This is a filler field "t" indicating that the next
+						 * field is time. Try to verify that this is sensible.
+						 */
+              tmask.set(0);
+
+						/* No preceding date? Then quit... */
+              if ((fmask & DateTimeConstants.DTK_DATE_M) != DateTimeConstants.DTK_DATE_M) {
+                throw new IllegalArgumentException("BAD Format: " + fields[i]);
+              }
+
+              /***
+               * We will need one of the following fields:
+               *	DTK_NUMBER should be hhmmss.fff
+               *	DTK_TIME should be hh:mm:ss.fff
+               *	DTK_DATE should be hhmmss-zz
+               ***/
+              if (i >= nf - 1 ||
+                  (fieldTypes[i + 1] != TokenField.DTK_NUMBER &&
+                      fieldTypes[i + 1] != TokenField.DTK_TIME &&
+                      fieldTypes[i + 1] != TokenField.DTK_DATE)) {
+                throw new IllegalArgumentException("BAD Format: " + fields[i]);
+              }
+
+              ptype = dateToken.getValueType();
+              break;
+
+            case UNKNOWN_FIELD:
+
+						/*
+						 * Before giving up and declaring error, check to see
+						 * if it is an all-alpha timezone name.
+						 */
+              namedTimeZone = pg_tzset(fields[i]);
+              if (namedTimeZone == null) {
+                throw new IllegalArgumentException("BAD Format: " + fields[i]);
+              }
+						/* we'll apply the zone setting below */
+              tmask.set(DateTimeConstants.DTK_M(TokenField.TZ));
+              break;
+
+            default:
+              throw new IllegalArgumentException("BAD Format: " + fields[i]);
+          }
+          break;
+      }
+      if ((tmask.get() & fmask) != 0) {
+        throw new IllegalArgumentException("BAD Format: " + fields[i]);
+      }
+      fmask |= tmask.get();
+    }   /* end loop over fields */
+
+    tm.fsecs = fsec.intValue();
+    tm.timeZone = tz.get();
+    /* do final checking/adjustment of Y/M/D fields */
+    validateDate(fmask, isjulian, is2digits.get(), bc, tm);
+
+	  /* handle AM/PM */
+    if (mer != null && mer != TokenField.HR24 && tm.hours > DateTimeConstants.HOURS_PER_DAY / 2) {
+      throw new IllegalArgumentException("BAD Format: overflow hour: " + tm.hours);
+    }
+    if (mer != null && mer == TokenField.AM && tm.hours == DateTimeConstants.HOURS_PER_DAY / 2) {
+      tm.hours = 0;
+    } else if (mer != null && mer == TokenField.PM && tm.hours != DateTimeConstants.HOURS_PER_DAY / 2) {
+      tm.hours += DateTimeConstants.HOURS_PER_DAY / 2;
+    }
+	  /* do additional checking for full date specs... */
+    if (dtype == TokenField.DTK_DATE) {
+      if ((fmask & DateTimeConstants.DTK_DATE_M) != DateTimeConstants.DTK_DATE_M) {
+        if ((fmask & DateTimeConstants.DTK_TIME_M) == DateTimeConstants.DTK_TIME_M) {
+          return tm;
+        }
+        throw new IllegalArgumentException("BAD Format: " + tm);
+      }
+
+		/*
+		 * If we had a full timezone spec, compute the offset (we could not do
+		 * it before, because we need the date to resolve DST status).
+		 */
+      if (namedTimeZone != null) {
+			/* daylight savings time modifier disallowed with full TZ */
+        if ( (fmask & DateTimeConstants.DTK_M(TokenField.DTZMOD)) != 0 ) {
+          throw new IllegalArgumentException("BAD Format: " + tm);
+        }
+      }
+    }
+
+    return tm;
+  }
+
+  private static String pg_tzset(String str) {
+    //TODO implements logic
+    return null;
+  }
+
+  /**
+   * Check valid year/month/day values, handle BC and DOY cases
+   * Return 0 if okay, a DTERR code if not.
+   * @param fmask
+   * @param isjulian
+   * @param is2digits
+   * @param bc
+   * @param tm
+   * @return
+   */
+  private static int validateDate(int fmask, boolean isjulian, boolean is2digits, boolean bc, TimeMeta tm) {
+    if ( (fmask & DateTimeConstants.DTK_M(TokenField.YEAR)) != 0 ) {
+      if (isjulian) {
+			/* tm_year is correct and should not be touched */
+      } else if (bc) {
+			  /* there is no year zero in AD/BC notation */
+        if (tm.years <= 0) {
+          throw new IllegalArgumentException("BAD Format: year overflow:" + tm.years);
+        }
+			  /* internally, we represent 1 BC as year zero, 2 BC as -1, etc */
+        tm.years = -(tm.years - 1);
+      }
+      else if (is2digits) {
+			  /* process 1 or 2-digit input as 1970-2069 AD, allow '0' and '00' */
+        if (tm.years < 0) { /* just paranoia */
+          throw new IllegalArgumentException("BAD Format: year overflow:" + tm.years);
+        }
+        if (tm.years < 70) {
+          tm.years += 2000;
+        } else if (tm.years < 100) {
+          tm.years += 1900;
+        }
+      }
+      else {
+			  /* there is no year zero in AD/BC notation */
+        if (tm.years <= 0) {
+          throw new IllegalArgumentException("BAD Format: year overflow:" + tm.years);
+        }
+      }
+    }
+
+	  /* now that we have correct year, decode DOY */
+    if ( (fmask & DateTimeConstants.DTK_M(TokenField.DOY)) != 0 ) {
+      j2date(date2j(tm.years, 1, 1) + tm.dayOfYear - 1, tm);
+    }
+
+	  /* check for valid month */
+    if ( (fmask & DateTimeConstants.DTK_M(TokenField.MONTH)) != 0 ) {
+      if (tm.monthOfYear < 1 || tm.monthOfYear > DateTimeConstants.MONTHS_PER_YEAR) {
+        throw new IllegalArgumentException("BAD Format: month overflow:" + tm.monthOfYear);
+      }
+    }
+
+	  /* minimal check for valid day */
+    if ( (fmask & DateTimeConstants.DTK_M(TokenField.DAY)) != 0 ) {
+      if (tm.dayOfMonth < 1 || tm.dayOfMonth > 31) {
+        throw new IllegalArgumentException("BAD Format: day overflow:" + tm.dayOfMonth);
+      }
+    }
+
+    if ((fmask & DateTimeConstants.DTK_DATE_M) == DateTimeConstants.DTK_DATE_M) {
+		/*
+		 * Check for valid day of month, now that we know for sure the month
+		 * and year.  Note we don't use MD_FIELD_OVERFLOW here, since it seems
+		 * unlikely that "Feb 29" is a YMD-order error.
+		 */
+      boolean leapYear = isLeapYear(tm.years);
+      if (tm.dayOfMonth > DateTimeConstants.DAY_OF_MONTH[leapYear ? 1: 0][tm.monthOfYear - 1])
+        throw new IllegalArgumentException("BAD Format: day overflow:" + tm.dayOfMonth);
+    }
+
+    return 0;
+  }
+
+  public static int strtoi(String str, int startIndex, StringBuilder sb) {
+    sb.setLength(0);
+    char[] chars = str.toCharArray();
+
+    int index = startIndex;
+    for (; index < chars.length; index++) {
+      if (!Character.isDigit(chars[index])) {
+        break;
+      }
+    }
+
+    int val = index == startIndex ? 0 : Integer.parseInt(str.substring(startIndex, index));
+    sb.append(chars, index, chars.length - index);
+
+    return val;
+  }
+
+  public static long strtol(String str, int startIndex, StringBuilder sb) {
+    sb.setLength(0);
+    char[] chars = str.toCharArray();
+
+    int index = startIndex;
+    for (; index < chars.length; index++) {
+      if (!Character.isDigit(chars[index])) {
+        break;
+      }
+    }
+
+    long val = index == startIndex ? 0 : Long.parseLong(str.substring(startIndex, index));
+    sb.append(chars, index, chars.length - index);
+
+    return val;
+  }
+
+  public static double strtod(String str, int strIndex, StringBuilder sb) {
+    if (sb != null) {
+      sb.setLength(0);
+    }
+    char[] chars = str.toCharArray();
+
+    int index = strIndex;
+    for (; index < chars.length; index++) {
+      if (!Character.isDigit(chars[index])) {
+        break;
+      }
+    }
+
+    double val = Double.parseDouble(str.substring(0, index));
+    if (sb != null) {
+      sb.append(chars, index, chars.length - index);
+    }
+    return val;
+  }
+
+  /**
+   * Check whether it is a punctuation character or not.
+   * @param c The character to be checked
+   * @return True if it is a punctuation character. Otherwise, false.
+   */
+  public static boolean isPunctuation(char c) {
+    return ((c >= '!' && c <= '/') ||
+        (c >= ':' && c <= '@') ||
+        (c >= '[' && c <= '`') ||
+        (c >= '{' && c <= '~'));
+  }
+
+  public static String toString(TimeMeta tm) {
+    return encodeDateTime(tm, DateStyle.ISO_DATES);
+  }
+
+  /**
+   * Encode date and time interpreted as local time.
+   *
+   * tm and fsec are the value to encode, print_tz determines whether to include
+   * a time zone (the difference between timestamp and timestamptz types), tz is
+   * the numeric time zone offset, tzn is the textual time zone, which if
+   * specified will be used instead of tz by some styles, style is the date
+   * style, str is where to write the output.
+   *
+   * Supported date styles:
+   *	Postgres - day mon hh:mm:ss yyyy tz
+   *	SQL - mm/dd/yyyy hh:mm:ss.ss tz
+   *	ISO - yyyy-mm-dd hh:mm:ss+/-tz
+   *	German - dd.mm.yyyy hh:mm:ss tz
+   *	XSD - yyyy-mm-ddThh:mm:ss.ss+/-tz
+   *
+   * This method is originated from EncodeDateTime of datetime.c of PostgreSQL.
+   * @param tm
+   * @param style
+   * @return
+   */
+  public static String encodeDateTime(TimeMeta tm, DateStyle style) {
+
+    StringBuilder sb = new StringBuilder();
+    switch (style) {
+
+      case ISO_DATES:
+      case XSO_DATES:
+        if (style == DateTimeConstants.DateStyle.ISO_DATES) {
+          sb.append(String.format("%04d-%02d-%02d %02d:%02d:",
+              (tm.years > 0) ? tm.years : -(tm.years - 1),
+              tm.monthOfYear, tm.dayOfMonth, tm.hours, tm.minutes));
+        } else {
+          sb.append(String.format("%04d-%02d-%02dT%02d:%02d:",
+              (tm.years > 0) ? tm.years : -(tm.years - 1),
+              tm.monthOfYear, tm.dayOfMonth, tm.hours, tm.minutes));
+        }
+
+        appendSecondsToEncodeOutput(sb, tm.secs, tm.fsecs, 6, true);
+        if (tm.timeZone != 0 && tm.timeZone != Integer.MAX_VALUE) {
+          sb.append(getTimeZoneDisplayTime(tm.timeZone));
+        }
+        if (tm.years <= 0) {
+          sb.append(" BC");
+        }
+        break;
+
+      case SQL_DATES:
+        // Compatible with Oracle/Ingres date formats
+
+    }
+
+    return sb.toString();
+  }
+
+  public static String encodeDate(TimeMeta tm, DateStyle style) {
+    StringBuilder sb = new StringBuilder();
+    switch (style) {
+      case ISO_DATES:
+      case XSO_DATES:
+      case SQL_DATES:
+        // Compatible with Oracle/Ingres date formats
+      default:
+        sb.append(String.format("%04d-%02d-%02d",
+            (tm.years > 0) ? tm.years : -(tm.years - 1),
+            tm.monthOfYear, tm.dayOfMonth));
+    }
+
+    return sb.toString();
+  }
+
+  public static String encodeTime(TimeMeta tm, DateStyle style) {
+    StringBuilder sb = new StringBuilder();
+    switch (style) {
+
+      case ISO_DATES:
+      case XSO_DATES:
+      case SQL_DATES:
+        // Compatible with Oracle/Ingres date formats
+      default :
+        sb.append(String.format("%02d:%02d:", tm.hours, tm.minutes));
+        appendSecondsToEncodeOutput(sb, tm.secs, tm.fsecs, 6, true);
+        if (tm.timeZone != 0 && tm.timeZone != Integer.MAX_VALUE) {
+          sb.append(getTimeZoneDisplayTime(tm.timeZone));
+        }
+        break;
+    }
+
+    return sb.toString();
+  }
+
+  /**
+   * Append sections and fractional seconds (if any) at *cp.
+   * precision is the max number of fraction digits, fillzeros says to
+   * pad to two integral-seconds digits.
+   * Note that any sign is stripped from the input seconds values.
+   *
+   * This method is originated form AppendSeconds in datetime.c of PostgreSQL.
+   */
+  public static void appendSecondsToEncodeOutput(
+      StringBuilder sb, int sec, int fsec, int precision, boolean fillzeros) {
+    if (fsec == 0) {
+      if (fillzeros)
+        sb.append(String.format("%02d", Math.abs(sec)));
+      else
+        sb.append(String.format("%d", Math.abs(sec)));
+    } else {
+      if (fillzeros) {
+        sb.append(String.format("%02d", Math.abs(sec)));
+      } else {
+        sb.append(String.format("%d", Math.abs(sec)));
+      }
+
+      if (precision > MAX_FRACTION_LENGTH) {
+        precision = MAX_FRACTION_LENGTH;
+      }
+
+      if (precision > 0) {
+        char[] fracChars = String.valueOf(fsec).toCharArray();
+        char[] resultChars = new char[MAX_FRACTION_LENGTH];
+
+        int numFillZero = MAX_FRACTION_LENGTH - fracChars.length;
+        for (int i = 0, fracIdx = 0; i < MAX_FRACTION_LENGTH; i++) {
+          if (i < numFillZero) {
+            resultChars[i] = '0';
+          } else {
+            resultChars[i] = fracChars[fracIdx];
+            fracIdx++;
+          }
+        }
+        sb.append(".").append(resultChars, 0, precision);
+      }
+      trimTrailingZeros(sb);
+    }
+  }
+
+  /**
+   * ... resulting from printing numbers with full precision.
+   *
+   * Before Postgres 8.4, this always left at least 2 fractional digits,
+   * but conversations on the lists suggest this isn't desired
+   * since showing '0.10' is misleading with values of precision(1).
+   *
+   * This method is originated form AppendSeconds in datetime.c of PostgreSQL.
+   * @param sb
+   */
+  public static void trimTrailingZeros(StringBuilder sb) {
+    int len = sb.length();
+    while (len > 1 && sb.charAt(len - 1) == '0' && sb.charAt(len - 2) != '.') {
+      len--;
+      sb.setLength(len);
+    }
+  }
+
+  /**
+   * Return the Julian day which corresponds to the first day (Monday) of the given ISO 8601 year and week.
+   * Julian days_full are used to convert between ISO week dates and Gregorian dates.
+   *
+   * This method is originated form AppendSeconds in timestamp.c of PostgreSQL.
+   * @param year
+   * @param week
+   * @return
+   */
+  public static int isoweek2j(int year, int week) {
+	  /* fourth day of current year */
+    int day4 = date2j(year, 1, 4);
+
+	  /* day0 == offset to first day of week (Monday) */
+    int day0 = j2day(day4 - 1);
+
+    return ((week - 1) * 7) + (day4 - day0);
+  }
+
+  /**
+   * Convert ISO week of year number to date.
+   * The year field must be specified with the ISO year!
+   * karel 2000/08/07
+   *
+   * This method is originated form AppendSeconds in timestamp.c of PostgreSQL.
+   * @param woy
+   * @param tm
+   */
+  public static void isoweek2date(int woy, TimeMeta tm) {
+    j2date(isoweek2j(tm.years, woy), tm);
+  }
+
+  /**
+   * Convert an ISO 8601 week date (ISO year, ISO week) into a Gregorian date.
+   * Gregorian day of week sent so weekday strings can be supplied.
+   * Populates year, mon, and mday with the correct Gregorian values.
+   * year must be passed in as the ISO year.
+   *
+   * This method is originated form AppendSeconds in timestamp.c of PostgreSQL.
+   * @param isoweek
+   * @param wday
+   * @param tm
+   */
+  public static void isoweekdate2date(int isoweek, int wday, TimeMeta tm) {
+    int jday;
+    jday = isoweek2j(tm.years, isoweek);
+	  /* convert Gregorian week start (Sunday=1) to ISO week start (Monday=1) */
+    if (wday > 1) {
+      jday += wday - 2;
+    } else {
+      jday += 6;
+    }
+    j2date(jday, tm);
+  }
+
+  /**
+   * Returns the ISO 8601 day-of-year, given a Gregorian year, month and day.
+   * Possible return values are 1 through 371 (364 in non-leap years).
+   * @param year
+   * @param mon
+   * @param mday
+   * @return
+   */
+  public static int date2isoyearday(int year, int mon, int mday) {
+    return date2j(year, mon, mday) - isoweek2j(date2isoyear(year, mon, mday), 1) + 1;
+  }
+
+  public static void toUserTimezone(TimeMeta tm) {
+    toUserTimezone(tm, TajoConf.getCurrentTimeZone());
+  }
+
+  public static void toUserTimezone(TimeMeta tm, TimeZone timeZone) {
+    tm.plusMillis(timeZone.getRawOffset());
+  }
+
+  public static void toUTCTimezone(TimeMeta tm) {
+    TimeZone timeZone = TajoConf.getCurrentTimeZone();
+    tm.plusMillis(0 - timeZone.getRawOffset());
+  }
+
+  public static String getTimeZoneDisplayTime(TimeZone timeZone) {
+    return getTimeZoneDisplayTime(timeZone.getRawOffset() / 1000);
+  }
+
+  public static String getTimeZoneDisplayTime(int totalSecs) {
+    if (totalSecs == 0) {
+      return "";
+    }
+    int minutes = Math.abs(totalSecs) / DateTimeConstants.SECS_PER_MINUTE;
+    int hours = minutes / DateTimeConstants.MINS_PER_HOUR;
+    minutes = minutes - hours * DateTimeConstants.MINS_PER_HOUR;
+
+    StringBuilder sb = new StringBuilder();
+    String prefix = "";
+
+    sb.append(totalSecs > 0 ? "+" : "-").append(String.format("%02d", hours));
+
+    if (minutes > 0) {
+      sb.append(":").append(String.format("%02d", minutes));
+      prefix = ":";
+    }
+
+    return sb.toString();
+  }
+
+  public static long getDay(DateTime dateTime) {
+    return convertToMicroSeconds(dateTime.withTimeAtStartOfDay());
+  }
+
+  public static long getHour(DateTime dateTime) {
+    return convertToMicroSeconds(dateTime.withTime(dateTime.get(org.joda.time.DateTimeFieldType.hourOfDay()), 0, 0, 0));
+  }
+
+  public static long getMinute(DateTime dateTime) {
+    return convertToMicroSeconds(dateTime.withTime(dateTime.get(org.joda.time.DateTimeFieldType.hourOfDay()),
+        dateTime.get(org.joda.time.DateTimeFieldType.minuteOfHour()), 0, 0));
+  }
+
+  public static long getSecond(DateTime dateTime) {
+    return convertToMicroSeconds(dateTime.withTime(dateTime.get(org.joda.time.DateTimeFieldType.hourOfDay()),
+        dateTime.get(org.joda.time.DateTimeFieldType.minuteOfHour()), dateTime.get(org.joda.time.DateTimeFieldType.secondOfMinute()), 0));
+  }
+
+  public static long getMonth(DateTime dateTime) {
+    return convertToMicroSeconds(dateTime.withTimeAtStartOfDay().withDate(dateTime.getYear(),
+        dateTime.getMonthOfYear(),1));
+  }
+
+  public static long getDayOfWeek(DateTime dateTime,int week) {
+    return convertToMicroSeconds(dateTime.withTimeAtStartOfDay().withDayOfWeek(week));
+  }
+
+  public static long getYear (DateTime dateTime) {
+    return convertToMicroSeconds(dateTime.withTimeAtStartOfDay().withDate(dateTime.getYear(), 1, 1));
+  }
+
+  public static DateTime getUTCDateTime(Int8Datum int8Datum){
+    return new DateTime(int8Datum.asInt8()/1000, DateTimeZone.UTC);
+  }
+
+  public static long convertToMicroSeconds(DateTime dateTime) {
+    return  dateTime.getMillis() * 1000;
+  }
+}

http://git-wip-us.apache.org/repos/asf/tajo/blob/526dca28/tajo-common/src/main/java/org/apache/tajo/util/datetime/TimeMeta.java
----------------------------------------------------------------------
diff --git a/tajo-common/src/main/java/org/apache/tajo/util/datetime/TimeMeta.java b/tajo-common/src/main/java/org/apache/tajo/util/datetime/TimeMeta.java
new file mode 100644
index 0000000..25fe64a
--- /dev/null
+++ b/tajo-common/src/main/java/org/apache/tajo/util/datetime/TimeMeta.java
@@ -0,0 +1,154 @@
+/**
+ * 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.tajo.util.datetime;
+
+import org.apache.tajo.util.datetime.DateTimeConstants.DateStyle;
+
+public class TimeMeta {
+  public int      fsecs;    // 1/1,000,000 secs
+  public int			secs;
+  public int			minutes;
+  public int			hours;
+  public int			dayOfMonth;
+  public int      dayOfYear;   //used for only DateTimeFormat
+  public int			monthOfYear; // origin 0, not 1
+  public int			years;		   // relative to 1900
+  public boolean	isDST;       // daylight savings time
+  public int      dayOfWeek;
+  public int      timeZone = Integer.MAX_VALUE;   //sec, used for only Text -> Timestamp
+
+  @Override
+  public String toString() {
+    return DateTimeUtil.encodeDateTime(this, DateStyle.ISO_DATES);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof TimeMeta)) {
+      return false;
+    }
+    TimeMeta other = (TimeMeta)o;
+
+    return fsecs == other.fsecs &&
+        secs == other.secs &&
+        minutes == other.minutes &&
+        hours == other.hours &&
+        dayOfMonth == other.dayOfMonth &&
+        dayOfYear == other.dayOfYear &&
+        monthOfYear == other.monthOfYear &&
+        years == other.years &&
+        isDST == other.isDST
+        ;
+  }
+
+  public void plusMonths(int months) {
+    if (months == 0) {
+      return;
+    }
+    int thisYear = years;
+    int thisMonth = monthOfYear;
+
+    int yearToUse;
+    // Initially, monthToUse is zero-based
+    int monthToUse = thisMonth - 1 + months;
+    if (monthToUse >= 0) {
+      yearToUse = thisYear + (monthToUse / DateTimeConstants.MONTHS_PER_YEAR);
+      monthToUse = (monthToUse % DateTimeConstants.MONTHS_PER_YEAR) + 1;
+    } else {
+      yearToUse = thisYear + (monthToUse / DateTimeConstants.MONTHS_PER_YEAR) - 1;
+      monthToUse = Math.abs(monthToUse);
+      int remMonthToUse = monthToUse % DateTimeConstants.MONTHS_PER_YEAR;
+      // Take care of the boundary condition
+      if (remMonthToUse == 0) {
+        remMonthToUse = DateTimeConstants.MONTHS_PER_YEAR;
+      }
+      monthToUse = DateTimeConstants.MONTHS_PER_YEAR - remMonthToUse + 1;
+      // Take care MONTHS_PER_YEAR the boundary condition
+      if (monthToUse == 1) {
+        yearToUse += 1;
+      }
+    }
+    // End of do not refactor.
+    // ----------------------------------------------------------
+
+    //
+    // Quietly force DOM to nearest sane value.
+    //
+    int dayToUse = dayOfMonth;
+    int maxDay = DateTimeUtil.getDaysInYearMonth(yearToUse, monthToUse);
+    if (dayToUse > maxDay) {
+      dayToUse = maxDay;
+    }
+
+    this.years = yearToUse;
+    this.monthOfYear = monthToUse;
+    this.dayOfMonth = dayToUse;
+  }
+
+  public void plusDays(int days) {
+    long timestamp = DateTimeUtil.toJulianTimestamp(this);
+    timestamp += days * DateTimeConstants.USECS_PER_DAY;
+
+    DateTimeUtil.toJulianTimeMeta(timestamp, this);
+  }
+
+  public void plusMillis(long millis) {
+    plusTime(millis * 1000);
+  }
+
+  public void plusTime(long time) {
+    long timestamp = DateTimeUtil.toJulianTimestamp(this);
+    timestamp += time;
+    DateTimeUtil.toJulianTimeMeta(timestamp, this);
+  }
+
+  public int getCenturyOfEra() {
+    return DateTimeUtil.getCenturyOfEra(years);
+  }
+
+  public int getDayOfYear() {
+    int dayOfYear = 0;
+    for (int i = 0; i < monthOfYear - 1; i++) {
+      dayOfYear += DateTimeUtil.getDaysInYearMonth(years, i + 1);
+    }
+
+    return dayOfYear + dayOfMonth;
+  }
+
+  public int getWeekOfYear() {
+    return DateTimeUtil.date2isoweek(years, monthOfYear, dayOfMonth);
+  }
+
+  public int getWeekyear() {
+    return DateTimeUtil.date2isoyear(years, monthOfYear, dayOfMonth);
+  }
+
+  public int getISODayOfWeek() {
+    int dow = getDayOfWeek();
+    if (dow == 0) {   //Sunday
+      return 7;
+    } else {
+      return dow;
+    }
+  }
+
+  public int getDayOfWeek() {
+    return (DateTimeUtil.date2j(years, monthOfYear, dayOfMonth) + 1) % 7;
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tajo/blob/526dca28/tajo-common/src/test/java/org/apache/tajo/datum/TestDateDatum.java
----------------------------------------------------------------------
diff --git a/tajo-common/src/test/java/org/apache/tajo/datum/TestDateDatum.java b/tajo-common/src/test/java/org/apache/tajo/datum/TestDateDatum.java
index 2123dc5..f2ad261 100644
--- a/tajo-common/src/test/java/org/apache/tajo/datum/TestDateDatum.java
+++ b/tajo-common/src/test/java/org/apache/tajo/datum/TestDateDatum.java
@@ -68,13 +68,6 @@ public class TestDateDatum {
     assertEquals(d, copy);
 	}
 
-  @Test
-  public final void testAsByteArray() {
-    DateDatum d = DatumFactory.createDate(DATE);
-    DateDatum copy = new DateDatum(d.asByteArray());
-    assertEquals(d.asInt8(), copy.asInt8());
-  }
-
 	@Test
   public final void testSize() {
     Datum d = DatumFactory.createDate(DATE);

http://git-wip-us.apache.org/repos/asf/tajo/blob/526dca28/tajo-common/src/test/java/org/apache/tajo/datum/TestIntervalDatum.java
----------------------------------------------------------------------
diff --git a/tajo-common/src/test/java/org/apache/tajo/datum/TestIntervalDatum.java b/tajo-common/src/test/java/org/apache/tajo/datum/TestIntervalDatum.java
index 3bce64c..511b356 100644
--- a/tajo-common/src/test/java/org/apache/tajo/datum/TestIntervalDatum.java
+++ b/tajo-common/src/test/java/org/apache/tajo/datum/TestIntervalDatum.java
@@ -19,9 +19,9 @@
 package org.apache.tajo.datum;
 
 import org.apache.tajo.common.TajoDataTypes;
+import org.apache.tajo.conf.TajoConf;
 import org.apache.tajo.exception.InvalidOperationException;
-import org.joda.time.DateTime;
-import org.joda.time.LocalTime;
+import org.apache.tajo.util.datetime.DateTimeUtil;
 import org.junit.Test;
 
 import static org.junit.Assert.assertEquals;
@@ -64,27 +64,35 @@ public class TestIntervalDatum {
     // http://www.postgresql.org/docs/8.2/static/functions-datetime.html
 
     // date '2001-09-28' + integer '7'	==> date '2001-10-05'
-    Datum datum = new DateDatum(2001, 9, 28);
+    Datum datum = DatumFactory.createDate(2001, 9, 28);
     Datum[] datums = new Datum[]{new Int2Datum((short) 7), new Int4Datum(7), new Int8Datum(7),
           new Float4Datum(7.0f), new Float8Datum(7.0f)};
 
     for (int i = 0; i < datums.length; i++) {
       Datum result = datum.plus(datums[i]);
       assertEquals(TajoDataTypes.Type.DATE, result.type());
-      assertEquals(new DateDatum(2001, 10, 5), result);
+      assertEquals("date '2001-09-28' + " + datums[i].asChars() + "(" + i + " th test)", "2001-10-05", result.asChars());
     }
 
+    //TimestampDatum and TimeDatum should be TimeZone when convert to string
     // date '2001-09-28' + interval '1 hour'	==> timestamp '2001-09-28 01:00:00'
-    datum = new DateDatum(2001, 9, 28);
+    datum = DatumFactory.createDate(2001, 9, 28);
     Datum result = datum.plus(new IntervalDatum(60 * 60 * 1000));
     assertEquals(TajoDataTypes.Type.TIMESTAMP, result.type());
-    assertEquals(new TimestampDatum(new DateTime(2001, 9, 28, 1, 0, 0, 0)), result);
+    assertEquals("2001-09-28 01:00:00", ((TimestampDatum)result).asChars(TajoConf.getCurrentTimeZone(), false));
+
+    // interval '1 hour' +  date '2001-09-28'	==> timestamp '2001-09-28 01:00:00'
+    datum = new IntervalDatum(60 * 60 * 1000);
+    result = datum.plus(DatumFactory.createDate(2001, 9, 28));
+    assertEquals(TajoDataTypes.Type.TIMESTAMP, result.type());
+    assertEquals("2001-09-28 01:00:00", ((TimestampDatum)result).asChars(TajoConf.getCurrentTimeZone(), false));
 
     // date '2001-09-28' + time '03:00' ==> timestamp '2001-09-28 03:00:00'
-    datum = new DateDatum(2001, 9, 28);
-    result = datum.plus(new TimeDatum(new LocalTime(3, 0)));
+    datum = DatumFactory.createDate(2001, 9, 28);
+    TimeDatum time = new TimeDatum(DateTimeUtil.toTime(3, 0, 0, 0));
+    result = datum.plus(time);
     assertEquals(TajoDataTypes.Type.TIMESTAMP, result.type());
-    assertEquals(new TimestampDatum(new DateTime(2001, 9, 28, 3, 0, 0, 0)), result);
+    assertEquals("2001-09-28 03:00:00", result.asChars());
 
     // interval '1 day' + interval '1 hour'	interval '1 day 01:00:00'
     datum = new IntervalDatum(IntervalDatum.DAY_MILLIS);
@@ -93,63 +101,65 @@ public class TestIntervalDatum {
     assertEquals("1 day 01:00:00", result.asChars());
 
     // timestamp '2001-09-28 01:00' + interval '23 hours'	==> timestamp '2001-09-29 00:00:00'
-    datum = new TimestampDatum(new DateTime(2001, 9, 28, 1, 0, 0, 0));
+    datum = new TimestampDatum(DateTimeUtil.toJulianTimestamp(2001, 9, 28, 1, 0, 0, 0));
     result = datum.plus(new IntervalDatum(23 * 60 * 60 * 1000));
     assertEquals(TajoDataTypes.Type.TIMESTAMP, result.type());
-    assertEquals(new TimestampDatum(new DateTime(2001, 9, 29, 0, 0, 0, 0)), result);
+    assertEquals("2001-09-29 00:00:00", result.asChars());
 
     // time '01:00' + interval '3 hours' ==> time '04:00:00'
-    datum = new TimeDatum(new LocalTime(1, 0, 0, 0));
+    datum = new TimeDatum(DateTimeUtil.toTime(1, 0, 0, 0));
     result = datum.plus(new IntervalDatum(3 * 60 * 60 * 1000));
     assertEquals(TajoDataTypes.Type.TIME, result.type());
-    assertEquals(new TimeDatum(new LocalTime(4, 0, 0, 0)), result);
+    assertEquals(new TimeDatum(DateTimeUtil.toTime(4, 0, 0, 0)), result);
 
-//    // - interval '23 hours' ==> interval '-23:00:00'
-//    // TODO Currently Interval's inverseSign() not supported
+    // - interval '23 hours' ==> interval '-23:00:00'
+    // TODO Currently Interval's inverseSign() not supported
 
     // date '2001-10-01' - date '2001-09-28' ==>	integer '3'
-    datum = new DateDatum(2001, 10, 01);
-    result = datum.minus(new DateDatum(2001, 9, 28));
+    datum = DatumFactory.createDate(2001, 10, 1);
+    result = datum.minus(DatumFactory.createDate(2001, 9, 28));
     assertEquals(TajoDataTypes.Type.INT4, result.type());
     assertEquals(new Int4Datum(3), result);
 
     // date '2001-10-01' - integer '7' ==>	date '2001-09-24'
-    datum = new DateDatum(2001, 10, 01);
+    datum = DatumFactory.createDate(2001, 10, 1);
     for (int i = 0; i < datums.length; i++) {
       Datum result2 = datum.minus(datums[i]);
       assertEquals(TajoDataTypes.Type.DATE, result2.type());
-      assertEquals(new DateDatum(2001, 9, 24), result2);
+      assertEquals(DatumFactory.createDate(2001, 9, 24), result2);
     }
 
     // date '2001-09-28' - interval '1 hour' ==> timestamp '2001-09-27 23:00:00'
-    datum = new DateDatum(2001, 9, 28);
+    datum = DatumFactory.createDate(2001, 9, 28);
     result = datum.minus(new IntervalDatum(1 * 60 * 60 * 1000));
     assertEquals(TajoDataTypes.Type.TIMESTAMP, result.type());
-    assertEquals(new TimestampDatum(new DateTime(2001, 9, 27, 23, 0, 0, 0)), result);
+    assertEquals("2001-09-27 23:00:00", ((TimestampDatum)result).asChars(TajoConf.getCurrentTimeZone(), false));
 
     // date '2001-09-28' - interval '1 day 1 hour' ==> timestamp '2001-09-26 23:00:00'
-    datum = new DateDatum(2001, 9, 28);
+    // In this case all datums are UTC
+    datum = DatumFactory.createDate(2001, 9, 28);
     result = datum.minus(new IntervalDatum(IntervalDatum.DAY_MILLIS + 1 * 60 * 60 * 1000));
     assertEquals(TajoDataTypes.Type.TIMESTAMP, result.type());
-    assertEquals(new TimestampDatum(new DateTime(2001, 9, 26, 23, 0, 0, 0)), result);
+    assertEquals("2001-09-26 23:00:00",  ((TimestampDatum)result).asChars(TajoConf.getCurrentTimeZone(), false));
 
     // time '05:00' - time '03:00' ==>	interval '02:00:00'
-    datum = new TimeDatum(new LocalTime(5, 0, 0, 0));
-    result = datum.minus(new TimeDatum(new LocalTime(3, 0, 0, 0)));
+    datum = new TimeDatum(DateTimeUtil.toTime(5, 0, 0, 0));
+    result = datum.minus(new TimeDatum(DateTimeUtil.toTime(3, 0, 0, 0)));
     assertEquals(TajoDataTypes.Type.INTERVAL, result.type());
     assertEquals(new IntervalDatum(2 * 60 * 60 * 1000), result);
 
     // time '05:00' - interval '2 hours' ==>	time '03:00:00'
-    datum = new TimeDatum(new LocalTime(5, 0, 0, 0));
+    datum = new TimeDatum(DateTimeUtil.toTime(5, 0, 0, 0));
     result = datum.minus(new IntervalDatum(2 * 60 * 60 * 1000));
     assertEquals(TajoDataTypes.Type.TIME, result.type());
-    assertEquals(new TimeDatum(3, 0, 0, 0), result);
+    assertEquals(new TimeDatum(DateTimeUtil.toTime(3, 0, 0, 0)), result);
 
     // timestamp '2001-09-28 23:00' - interval '23 hours' ==>	timestamp '2001-09-28 00:00:00'
-    datum = new TimestampDatum(new DateTime(2001, 9, 28, 23, 0, 0, 0));
+    // In this case all datums are UTC
+    datum = new TimestampDatum(DateTimeUtil.toJulianTimestamp(2001, 9, 28, 23, 0, 0, 0));
     result = datum.minus(new IntervalDatum(23 * 60 * 60 * 1000));
     assertEquals(TajoDataTypes.Type.TIMESTAMP, result.type());
-    assertEquals(new TimestampDatum(new DateTime(2001, 9, 28, 0, 0, 0, 0)), result);
+    assertEquals("2001-09-28 00:00:00", result.asChars());
 
     // interval '1 day' - interval '1 hour'	==> interval '1 day -01:00:00'
     datum = new IntervalDatum(IntervalDatum.DAY_MILLIS);
@@ -158,8 +168,8 @@ public class TestIntervalDatum {
     assertEquals(new IntervalDatum(23 * 60 * 60 * 1000), result);
 
     // timestamp '2001-09-29 03:00' - timestamp '2001-09-27 12:00' ==>	interval '1 day 15:00:00'
-    datum = new TimestampDatum(new DateTime(2001, 9, 29, 3, 0, 0, 0));
-    result = datum.minus(new TimestampDatum(new DateTime(2001, 9, 27, 12, 0, 0, 0)));
+    datum = new TimestampDatum(DateTimeUtil.toJulianTimestamp(2001, 9, 29, 3, 0, 0, 0));
+    result = datum.minus(new TimestampDatum(DateTimeUtil.toJulianTimestamp(2001, 9, 27, 12, 0, 0, 0)));
     assertEquals(TajoDataTypes.Type.INTERVAL, result.type());
     assertEquals(new IntervalDatum(IntervalDatum.DAY_MILLIS + 15 * 60 * 60 * 1000), result);
 
@@ -191,9 +201,10 @@ public class TestIntervalDatum {
     assertEquals(new IntervalDatum(40 * 60 * 1000), result);
 
     // timestamp '2001-08-31 01:00:00' + interval '1 mons' ==> timestamp 2001-09-30 01:00:00
-    datum = new TimestampDatum(new DateTime(2001, 8, 31, 1, 0, 0, 0));
+    // In this case all datums are UTC
+    datum = new TimestampDatum(DateTimeUtil.toJulianTimestamp(2001, 8, 31, 1, 0, 0, 0));
     result = datum.plus(new IntervalDatum(1, 0));
     assertEquals(TajoDataTypes.Type.TIMESTAMP, result.type());
-    assertEquals(new TimestampDatum(new DateTime(2001, 9, 30, 1, 0, 0, 0)), result);
+    assertEquals("2001-09-30 01:00:00", result.asChars());
   }
 }

http://git-wip-us.apache.org/repos/asf/tajo/blob/526dca28/tajo-common/src/test/java/org/apache/tajo/datum/TestTimeDatum.java
----------------------------------------------------------------------
diff --git a/tajo-common/src/test/java/org/apache/tajo/datum/TestTimeDatum.java b/tajo-common/src/test/java/org/apache/tajo/datum/TestTimeDatum.java
index c405d68..ea641ec 100644
--- a/tajo-common/src/test/java/org/apache/tajo/datum/TestTimeDatum.java
+++ b/tajo-common/src/test/java/org/apache/tajo/datum/TestTimeDatum.java
@@ -70,13 +70,6 @@ public class TestTimeDatum {
   }
 
   @Test
-  public final void testAsByteArray() {
-    TimeDatum d = DatumFactory.createTime(TIME);
-    TimeDatum copy = new TimeDatum(d.asByteArray());
-    assertEquals(d.asInt8(), copy.asInt8());
-  }
-
-  @Test
   public final void testSize() {
     Datum d = DatumFactory.createTime(TIME);
     assertEquals(TimeDatum.SIZE, d.asByteArray().length);