/**
* 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.datum.TimestampDatum;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
/**
* This class originated from src/backend/utils/adt/formatting.c of PostgreSQL
*/
public class DateTimeFormat {
/* ----------
* Full months_short
* ----------
*/
static final String[] months_full = {
"January", "February", "March", "April", "May", "June", "July",
"August", "September", "October", "November", "December", null
};
static String[] days_short = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", null
};
static String[] months_short = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec", null};
static String[] days_full = {"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday", null};
static int[][] ysum = {
{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
{0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}
};
/**
* AD / BC
* ----------
* There is no 0 AD. Years go from 1 BC to 1 AD, so we make it
* positive and map year == -1 to year zero, and shift all negative
* years up one. For interval years, we just return the year.
* @param year
* @param is_interval
* @return
*/
static int ADJUST_YEAR(int year, boolean is_interval) {
return ((is_interval) ? (year) : ((year) <= 0 ? -((year) - 1) : (year)));
}
static final String A_D_STR = "A.D.";
static final String a_d_STR = "a.d.";
static final String AD_STR = "AD";
static final String ad_STR = "ad";
static final String B_C_STR = "B.C.";
static final String b_c_STR = "b.c.";
static final String BC_STR = "BC";
static final String bc_STR = "bc";
/**
* AD / BC strings for seq_search.
*
* These are given in two variants, a long form with periods and a standard
* form without.
*
* The array is laid out such that matches for AD have an even index, and
* matches for BC have an odd index. So the boolean value for BC is given by
* taking the array index of the match, modulo 2.
*/
static final String[] adbc_strings = {ad_STR, bc_STR, AD_STR, BC_STR, null};
static final String[] adbc_strings_long = {a_d_STR, b_c_STR, A_D_STR, B_C_STR, null};
/**
* ----------
* AM / PM
* ----------
*/
static final String A_M_STR = "A.M.";
static final String a_m_STR = "a.m.";
static final String AM_STR = "AM";
static final String am_STR = "am";
static final String P_M_STR = "P.M.";
static final String p_m_STR = "p.m.";
static final String PM_STR = "PM";
static final String pm_STR = "pm";
/**
* AM / PM strings for seq_search.
*
* These are given in two variants, a long form with periods and a standard
* form without.
*
* The array is laid out such that matches for AM have an even index, and
* matches for PM have an odd index. So the boolean value for PM is given by
* taking the array index of the match, modulo 2.
*/
static final String[] ampm_strings = {am_STR, pm_STR, AM_STR, PM_STR, null};
static final String[] ampm_strings_long = {a_m_STR, p_m_STR, A_M_STR, P_M_STR, null};
/**
* ----------
* Months in roman-numeral
* (Must be in reverse order for seq_search (in FROM_CHAR), because
* 'VIII' must have higher precedence than 'V')
* ----------
*/
static final String[] rm_months_upper =
{"XII", "XI", "X", "IX", "VIII", "VII", "VI", "V", "IV", "III", "II", "I", null};
static final String[] rm_months_lower =
{"xii", "xi", "x", "ix", "viii", "vii", "vi", "v", "iv", "iii", "ii", "i", null};
/**
* ----------
* Roman numbers
* ----------
*/
static final String[] rm1 = {"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", null};
static final String[] rm10 = {"X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC", null};
static final String[] rm100 = {"C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM", null};
/**
* ----------
* Ordinal postfixes
* ----------
*/
static final String[] numTH = {"ST", "ND", "RD", "TH", null};
static final String[] numth = {"st", "nd", "rd", "th", null};
/**
* ----------
* Flags & Options:
* ----------
*/
static final int ONE_UPPER = 1; /* Name */
static final int ALL_UPPER = 2; /* NAME */
static final int ALL_LOWER = 3; /* name */
static final int MAX_MONTH_LEN = 9;
static final int MAX_MON_LEN = 3;
static final int MAX_DAY_LEN = 9;
static final int MAX_DY_LEN = 3;
static final int MAX_RM_LEN = 4;
static final int DCH_S_FM = 0x01;
static final int DCH_S_TH = 0x02;
static final int DCH_S_th = 0x04;
static final int DCH_S_SP = 0x08;
static final int DCH_S_TM = 0x10;
static final int NODE_TYPE_END = 1;
static final int NODE_TYPE_ACTION = 2;
static final int NODE_TYPE_CHAR = 3;
static final int SUFFTYPE_PREFIX = 1;
static final int SUFFTYPE_POSTFIX = 2;
static final int CLOCK_24_HOUR = 0;
static final int CLOCK_12_HOUR = 1;
static final int MONTHS_PER_YEAR = 12;
static final int HOURS_PER_DAY = 24;
/**
* ----------
* Maximal length of one node
* ----------
*/
static final int DCH_MAX_ITEM_SIZ = 9; /* max julian day */
static final int NUM_MAX_ITEM_SIZ = 8; /* roman number (RN has 15 chars) */
enum FORMAT_TYPE {
DCH_TYPE, NUM_TYPE
}
/**
* ----------
* Suffixes definition for DATE-TIME TO/FROM CHAR
* ----------
*/
static KeySuffix[] DCH_suff = {
new KeySuffix("FM", 2, DCH_S_FM, SUFFTYPE_PREFIX),
new KeySuffix("fm", 2, DCH_S_FM, SUFFTYPE_PREFIX),
new KeySuffix("TM", 2, DCH_S_TM, SUFFTYPE_PREFIX),
new KeySuffix("tm", 2, DCH_S_TM, SUFFTYPE_PREFIX),
new KeySuffix("TH", 2, DCH_S_TH, SUFFTYPE_POSTFIX),
new KeySuffix("th", 2, DCH_S_th, SUFFTYPE_POSTFIX),
new KeySuffix("SP", 2, DCH_S_SP, SUFFTYPE_POSTFIX),
};
/**
* ----------
* Format-pictures (KeyWord).
*
* The KeyWord field; alphabetic sorted, *BUT* strings alike is sorted
* complicated -to-> easy:
*
* (example: "DDD","DD","Day","D" )
*
* (this specific sort needs the algorithm for sequential search for strings,
* which not has exact end; -> How keyword is in "HH12blabla" ? - "HH"
* or "HH12"? You must first try "HH12", because "HH" is in string, but
* it is not good.
*
* (!)
* - Position for the keyword is similar as position in the enum DCH/NUM_poz.
* (!)
*
* For fast search is used the 'int index[]', index is ascii table from position
* 32 (' ') to 126 (~), in this index is DCH_ / NUM_ enums for each ASCII
* position or -1 if char is not used in the KeyWord. Search example for
* string "MM":
* 1) see in index to index['M' - 32],
* 2) take keywords position (enum DCH_MI) from index
* 3) run sequential search in keywords[] from this position
*
* ----------
*/
enum DCH_poz {
DCH_A_D(0),
DCH_A_M(1),
DCH_AD(2),
DCH_AM(3),
DCH_B_C(4),
DCH_BC(5),
DCH_CC(6),
DCH_DAY(7),
DCH_DDD(8),
DCH_DD(9),
DCH_DY(10),
DCH_Day(11),
DCH_Dy(12),
DCH_D(13),
DCH_FX(14), /* global suffix */
DCH_HH24(15),
DCH_HH12(16),
DCH_HH(17),
DCH_IDDD(18),
DCH_ID(19),
DCH_IW(20),
DCH_IYYY(21),
DCH_IYY(22),
DCH_IY(23),
DCH_I(24),
DCH_J(25),
DCH_MI(26),
DCH_MM(27),
DCH_MONTH(28),
DCH_MON(29),
DCH_MS(30),
DCH_Month(31),
DCH_Mon(32),
DCH_P_M(33),
DCH_PM(34),
DCH_Q(35),
DCH_RM(36),
DCH_SSSS(37),
DCH_SS(38),
DCH_TZ(39),
DCH_US(40),
DCH_WW(41),
DCH_W(42),
DCH_Y_YYY(43),
DCH_YYYY(44),
DCH_YYY(45),
DCH_YY(46),
DCH_Y(47),
DCH_a_d(48),
DCH_a_m(49),
DCH_ad(50),
DCH_am(51),
DCH_b_c(52),
DCH_bc(53),
DCH_cc(54),
DCH_day(55),
DCH_ddd(56),
DCH_dd(57),
DCH_dy(58),
DCH_d(59),
DCH_fx(60),
DCH_hh24(61),
DCH_hh12(62),
DCH_hh(63),
DCH_iddd(64),
DCH_id(65),
DCH_iw(66),
DCH_iyyy(67),
DCH_iyy(68),
DCH_iy(69),
DCH_i(70),
DCH_j(71),
DCH_mi(72),
DCH_mm(73),
DCH_month(74),
DCH_mon(75),
DCH_ms(76),
DCH_p_m(77),
DCH_pm(78),
DCH_q(79),
DCH_rm(80),
DCH_ssss(89),
DCH_ss(90),
DCH_tz(91),
DCH_us(92),
DCH_ww(93),
DCH_w(94),
DCH_y_yyy(95),
DCH_yyyy(96),
DCH_yyy(97),
DCH_yy(98),
DCH_y(99),
_DCH_last_(Integer.MAX_VALUE);
int value;
DCH_poz(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
/**
* ----------
* FromCharDateMode
* ----------
*
* This value is used to nominate one of several distinct (and mutually
* exclusive) date conventions that a keyword can belong to.
*/
enum FromCharDateMode
{
FROM_CHAR_DATE_NONE, /* Value does not affect date mode. */
FROM_CHAR_DATE_GREGORIAN, /* Gregorian (day, month, year) style date */
FROM_CHAR_DATE_ISOWEEK /* ISO 8601 week date */
}
/**
* ----------
* KeyWords for DATE-TIME version
* ----------
*/
static final Object[][] DCH_keywordValues = {
/* name, len, id, is_digit, date_mode */
{"A.D.", 4, DCH_poz.DCH_A_D, false, FromCharDateMode.FROM_CHAR_DATE_NONE}, /* A */
{"A.M.", 4, DCH_poz.DCH_A_M, false, FromCharDateMode.FROM_CHAR_DATE_NONE},
{"AD", 2, DCH_poz.DCH_AD, false, FromCharDateMode.FROM_CHAR_DATE_NONE},
{"AM", 2, DCH_poz.DCH_AM, false, FromCharDateMode.FROM_CHAR_DATE_NONE},
{"B.C.", 4, DCH_poz.DCH_B_C, false, FromCharDateMode.FROM_CHAR_DATE_NONE}, /* B */
{"BC", 2, DCH_poz.DCH_BC, false, FromCharDateMode.FROM_CHAR_DATE_NONE},
{"CC", 2, DCH_poz.DCH_CC, true, FromCharDateMode.FROM_CHAR_DATE_NONE}, /* C */
{"DAY", 3, DCH_poz.DCH_DAY, false, FromCharDateMode.FROM_CHAR_DATE_NONE}, /* D */
{"DDD", 3, DCH_poz.DCH_DDD, true, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN},
{"DD", 2, DCH_poz.DCH_DD, true, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN},
{"DY", 2, DCH_poz.DCH_DY, false, FromCharDateMode.FROM_CHAR_DATE_NONE},
{"Day", 3, DCH_poz.DCH_Day, false, FromCharDateMode.FROM_CHAR_DATE_NONE},
{"Dy", 2, DCH_poz.DCH_Dy, false, FromCharDateMode.FROM_CHAR_DATE_NONE},
{"D", 1, DCH_poz.DCH_D, true, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN},
{"FX", 2, DCH_poz.DCH_FX, false, FromCharDateMode.FROM_CHAR_DATE_NONE}, /* F */
{"HH24", 4, DCH_poz.DCH_HH24, true, FromCharDateMode.FROM_CHAR_DATE_NONE}, /* H */
{"HH12", 4, DCH_poz.DCH_HH12, true, FromCharDateMode.FROM_CHAR_DATE_NONE},
{"HH", 2, DCH_poz.DCH_HH, true, FromCharDateMode.FROM_CHAR_DATE_NONE},
{"IDDD", 4, DCH_poz.DCH_IDDD, true, FromCharDateMode.FROM_CHAR_DATE_ISOWEEK}, /* I */
{"ID", 2, DCH_poz.DCH_ID, true, FromCharDateMode.FROM_CHAR_DATE_ISOWEEK},
{"IW", 2, DCH_poz.DCH_IW, true, FromCharDateMode.FROM_CHAR_DATE_ISOWEEK},
{"IYYY", 4, DCH_poz.DCH_IYYY, true, FromCharDateMode.FROM_CHAR_DATE_ISOWEEK},
{"IYY", 3, DCH_poz.DCH_IYY, true, FromCharDateMode.FROM_CHAR_DATE_ISOWEEK},
{"IY", 2, DCH_poz.DCH_IY, true, FromCharDateMode.FROM_CHAR_DATE_ISOWEEK},
{"I", 1, DCH_poz.DCH_I, true, FromCharDateMode.FROM_CHAR_DATE_ISOWEEK},
{"J", 1, DCH_poz.DCH_J, true, FromCharDateMode.FROM_CHAR_DATE_NONE}, /* J */
{"MI", 2, DCH_poz.DCH_MI, true, FromCharDateMode.FROM_CHAR_DATE_NONE}, /* M */
{"MM", 2, DCH_poz.DCH_MM, true, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN},
{"MONTH", 5, DCH_poz.DCH_MONTH, false, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN},
{"MON", 3, DCH_poz.DCH_MON, false, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN},
{"MS", 2, DCH_poz.DCH_MS, true, FromCharDateMode.FROM_CHAR_DATE_NONE},
{"Month", 5, DCH_poz.DCH_Month, false, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN},
{"Mon", 3, DCH_poz.DCH_Mon, false, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN},
{"P.M.", 4, DCH_poz.DCH_P_M, false, FromCharDateMode.FROM_CHAR_DATE_NONE}, /* P */
{"PM", 2, DCH_poz.DCH_PM, false, FromCharDateMode.FROM_CHAR_DATE_NONE},
{"Q", 1, DCH_poz.DCH_Q, true, FromCharDateMode.FROM_CHAR_DATE_NONE}, /* Q */
{"RM", 2, DCH_poz.DCH_RM, false, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN}, /* R */
{"SSSS", 4, DCH_poz.DCH_SSSS, true, FromCharDateMode.FROM_CHAR_DATE_NONE}, /* S */
{"SS", 2, DCH_poz.DCH_SS, true, FromCharDateMode.FROM_CHAR_DATE_NONE},
{"TZ", 2, DCH_poz.DCH_TZ, false, FromCharDateMode.FROM_CHAR_DATE_NONE}, /* T */
{"US", 2, DCH_poz.DCH_US, true, FromCharDateMode.FROM_CHAR_DATE_NONE}, /* U */
{"WW", 2, DCH_poz.DCH_WW, true, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN}, /* W */
{"W", 1, DCH_poz.DCH_W, true, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN},
{"Y,YYY", 5, DCH_poz.DCH_Y_YYY, true, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN}, /* Y */
{"YYYY", 4, DCH_poz.DCH_YYYY, true, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN},
{"YYY", 3, DCH_poz.DCH_YYY, true, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN},
{"YY", 2, DCH_poz.DCH_YY, true, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN},
{"Y", 1, DCH_poz.DCH_Y, true, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN},
{"a.d.", 4, DCH_poz.DCH_a_d, false, FromCharDateMode.FROM_CHAR_DATE_NONE}, /* a */
{"a.m.", 4, DCH_poz.DCH_a_m, false, FromCharDateMode.FROM_CHAR_DATE_NONE},
{"ad", 2, DCH_poz.DCH_ad, false, FromCharDateMode.FROM_CHAR_DATE_NONE},
{"am", 2, DCH_poz.DCH_am, false, FromCharDateMode.FROM_CHAR_DATE_NONE},
{"b.c.", 4, DCH_poz.DCH_b_c, false, FromCharDateMode.FROM_CHAR_DATE_NONE}, /* b */
{"bc", 2, DCH_poz.DCH_bc, false, FromCharDateMode.FROM_CHAR_DATE_NONE},
{"cc", 2, DCH_poz.DCH_CC, true, FromCharDateMode.FROM_CHAR_DATE_NONE}, /* c */
{"day", 3, DCH_poz.DCH_day, false, FromCharDateMode.FROM_CHAR_DATE_NONE}, /* d */
{"ddd", 3, DCH_poz.DCH_DDD, true, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN},
{"dd", 2, DCH_poz.DCH_DD, true, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN},
{"dy", 2, DCH_poz.DCH_dy, false, FromCharDateMode.FROM_CHAR_DATE_NONE},
{"d", 1, DCH_poz.DCH_D, true, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN},
{"fx", 2, DCH_poz.DCH_FX, false, FromCharDateMode.FROM_CHAR_DATE_NONE}, /* f */
{"hh24", 4, DCH_poz.DCH_HH24, true, FromCharDateMode.FROM_CHAR_DATE_NONE}, /* h */
{"hh12", 4, DCH_poz.DCH_HH12, true, FromCharDateMode.FROM_CHAR_DATE_NONE},
{"hh", 2, DCH_poz.DCH_HH, true, FromCharDateMode.FROM_CHAR_DATE_NONE},
{"iddd", 4, DCH_poz.DCH_IDDD, true, FromCharDateMode.FROM_CHAR_DATE_ISOWEEK}, /* i */
{"id", 2, DCH_poz.DCH_ID, true, FromCharDateMode.FROM_CHAR_DATE_ISOWEEK},
{"iw", 2, DCH_poz.DCH_IW, true, FromCharDateMode.FROM_CHAR_DATE_ISOWEEK},
{"iyyy", 4, DCH_poz.DCH_IYYY, true, FromCharDateMode.FROM_CHAR_DATE_ISOWEEK},
{"iyy", 3, DCH_poz.DCH_IYY, true, FromCharDateMode.FROM_CHAR_DATE_ISOWEEK},
{"iy", 2, DCH_poz.DCH_IY, true, FromCharDateMode.FROM_CHAR_DATE_ISOWEEK},
{"i", 1, DCH_poz.DCH_I, true, FromCharDateMode.FROM_CHAR_DATE_ISOWEEK},
{"j", 1, DCH_poz.DCH_J, true, FromCharDateMode.FROM_CHAR_DATE_NONE}, /* j */
{"mi", 2, DCH_poz.DCH_MI, true, FromCharDateMode.FROM_CHAR_DATE_NONE}, /* m */
{"mm", 2, DCH_poz.DCH_MM, true, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN},
{"month", 5, DCH_poz.DCH_month, false, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN},
{"mon", 3, DCH_poz.DCH_mon, false, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN},
{"ms", 2, DCH_poz.DCH_MS, true, FromCharDateMode.FROM_CHAR_DATE_NONE},
{"p.m.", 4, DCH_poz.DCH_p_m, false, FromCharDateMode.FROM_CHAR_DATE_NONE}, /* p */
{"pm", 2, DCH_poz.DCH_pm, false, FromCharDateMode.FROM_CHAR_DATE_NONE},
{"q", 1, DCH_poz.DCH_Q, true, FromCharDateMode.FROM_CHAR_DATE_NONE}, /* q */
{"rm", 2, DCH_poz.DCH_rm, false, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN}, /* r */
{"ssss", 4, DCH_poz.DCH_SSSS, true, FromCharDateMode.FROM_CHAR_DATE_NONE}, /* s */
{"ss", 2, DCH_poz.DCH_SS, true, FromCharDateMode.FROM_CHAR_DATE_NONE},
{"tz", 2, DCH_poz.DCH_tz, false, FromCharDateMode.FROM_CHAR_DATE_NONE}, /* t */
{"us", 2, DCH_poz.DCH_US, true, FromCharDateMode.FROM_CHAR_DATE_NONE}, /* u */
{"ww", 2, DCH_poz.DCH_WW, true, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN}, /* w */
{"w", 1, DCH_poz.DCH_W, true, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN},
{"y,yyy", 5, DCH_poz.DCH_Y_YYY, true, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN}, /* y */
{"yyyy", 4, DCH_poz.DCH_YYYY, true, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN},
{"yyy", 3, DCH_poz.DCH_YYY, true, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN},
{"yy", 2, DCH_poz.DCH_YY, true, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN},
{"y", 1, DCH_poz.DCH_Y, true, FromCharDateMode.FROM_CHAR_DATE_GREGORIAN}
};
static final KeyWord[] DCH_keywords = new KeyWord[DCH_keywordValues.length];
static Map<Character, Integer> DCH_index = new HashMap<>();
static {
int index = 0;
for(Object[] eachKeywordValue: DCH_keywordValues) {
KeyWord keyword = new KeyWord();
keyword.name = (String)eachKeywordValue[0];
keyword.len = ((Integer)eachKeywordValue[1]).intValue();
keyword.idType = ((DCH_poz)eachKeywordValue[2]);
keyword.is_digit = ((Boolean)eachKeywordValue[3]).booleanValue();
keyword.date_mode = (FromCharDateMode)eachKeywordValue[4];
Character c = Character.valueOf(keyword.name.charAt(0));
Integer pos = DCH_index.get(c);
if (pos == null) {
DCH_index.put(c, index);
}
DCH_keywords[index++] = keyword;
}
}
/**
* ----------
* Format parser structs
* ----------
*/
static class KeySuffix {
String name; /* suffix string */
int len; /* suffix length */
int id; /* used in node->suffix */
int type; /* prefix / postfix */
public KeySuffix(String name, int len, int id, int type) {
this.name = name;
this.len = len;
this.id = id;
this.type = type;
}
}
static class KeyWord {
String name;
int len;
DCH_poz idType;
boolean is_digit;
FromCharDateMode date_mode;
}
static class FormatNode {
int type; /* node type */
KeyWord key; /* if node type is KEYWORD */
char character; /* if node type is CHAR */
int suffix; /* keyword suffix */
}
static class TmFromChar {
FromCharDateMode mode = FromCharDateMode.FROM_CHAR_DATE_NONE;
int hh;
int pm;
int mi;
int ss;
int ssss;
int d; /* stored as 1-7, Sunday = 1, 0 means missing */
int dd;
int ddd;
int mm;
int ms;
int year;
int bc;
int ww;
int w;
int cc;
int j;
int us;
int yysz; /* is it YY or YYYY ? */
int clock; /* 12 or 24 hour clock? */
}
static Map<String, FormatNode[]> formatNodeCache = new HashMap<>();
/**
* ----------
* Skip TM / th in FROM_CHAR
* ----------
*/
static int SKIP_THth(int suf) {
return (S_THth(suf) != 0 ? 2 : 0);
}
/**
* ----------
* Suffix tests
* ----------
*/
static int S_THth(int s) {
return ((((s) & DCH_S_TH) != 0 || ((s) & DCH_S_th) != 0) ? 1 : 0);
}
static int S_TH(int s) {
return (((s) & DCH_S_TH) != 0 ? 1 : 0);
}
static int S_th(int s) {
return (((s) & DCH_S_th) != 0 ? 1 : 0);
}
static int S_TH_TYPE(int s) {
return (((s) & DCH_S_TH) != 0 ? TH_UPPER : TH_LOWER);
}
static final int TH_UPPER = 1;
static final int TH_LOWER = 2;
/* Oracle toggles FM behavior, we don't; see docs. */
static int S_FM(int s) {
return (((s) & DCH_S_FM) != 0 ? 1 : 0);
}
static int S_SP(int s) {
return (((s) & DCH_S_SP) != 0 ? 1 : 0);
}
static int S_TM(int s) {
return (((s) & DCH_S_TM) != 0 ? 1 : 0);
}
public static TimeMeta parseDateTime(String dateText, String formatText) {
TimeMeta tm = new TimeMeta();
//TODO consider TimeZone
doToTimestamp(dateText, formatText, tm);
// when we parse some date without day like '2014-04', we should set day to 1.
if (tm.dayOfMonth == 0) {
tm.dayOfMonth = 1;
}
if (tm.dayOfYear > 0 && tm.dayOfMonth > 0) {
tm.dayOfYear = 0;
}
return tm;
}
/**
* Make Timestamp from date_str which is formatted at argument 'fmt'
* ( toTimestamp is reverse to_char() )
* @param dateText
* @param formatText
* @return
*/
public static TimestampDatum toTimestamp(String dateText, String formatText) {
TimeMeta tm = parseDateTime(dateText, formatText);
return new TimestampDatum(DateTimeUtil.toJulianTimestamp(tm));
}
/**
* Parse the 'dateText' according to 'formatText', return results as a TimeMeta tm
* and fractional seconds.
*
* We parse 'formatText' into a list of FormatNodes, which is then passed to
* DCH_from_char to populate a TmFromChar with the parsed contents of
* 'dateText'.
*
* The TmFromChar is then analysed and converted into the final results in struct 'tm'.
*
* This function does very little error checking, e.g.
* to_timestamp('20096040','YYYYMMDD') works
* @param dateText
* @param formatText
* @param tm
*/
static void doToTimestamp(String dateText, String formatText, TimeMeta tm) {
TmFromChar tmfc = new TmFromChar();
int formatLength = formatText.length();
if (formatLength > 0) {
FormatNode[] formatNodes;
synchronized(formatNodeCache) {
formatNodes = formatNodeCache.get(formatText);
}
if (formatNodes == null) {
formatNodes = new FormatNode[formatLength + 1];
for (int i = 0; i < formatNodes.length; i++) {
formatNodes[i] = new FormatNode();
}
parseFormat(formatNodes, formatText, FORMAT_TYPE.DCH_TYPE);
formatNodes[formatLength].type = NODE_TYPE_END; /* Paranoia? */
synchronized(formatNodeCache) {
formatNodeCache.put(formatText, formatNodes);
}
}
DCH_from_char(formatNodes, dateText, tmfc);
}
/*
* Convert values that user define for FROM_CHAR (to_date/to_timestamp) to
* standard 'tm'
*/
if (tmfc.ssss != 0) {
int x = tmfc.ssss;
tm.hours = x / DateTimeConstants.SECS_PER_HOUR;
x %= DateTimeConstants.SECS_PER_HOUR;
tm.minutes = x / DateTimeConstants.SECS_PER_MINUTE;
x %= DateTimeConstants.SECS_PER_MINUTE;
tm.secs = x;
}
if (tmfc.ss != 0) {
tm.secs = tmfc.ss;
}
if (tmfc.mi != 0) {
tm.minutes = tmfc.mi;
}
if (tmfc.hh != 0) {
tm.hours = tmfc.hh;
}
if (tmfc.clock == CLOCK_12_HOUR) {
if (tm.hours < 1 || tm.hours > HOURS_PER_DAY / 2) {
throw new IllegalArgumentException(
"hour \"" + tm.hours + "\" is invalid for the 12-hour clock, " +
"Use the 24-hour clock, or give an hour between 1 and 12.");
}
if (tmfc.pm != 0 && tm.hours < HOURS_PER_DAY / 2) {
tm.hours += HOURS_PER_DAY / 2;
} else if (tmfc.pm == 0 && tm.hours == HOURS_PER_DAY / 2) {
tm.hours = 0;
}
}
if (tmfc.year != 0) {
/*
* If CC and YY (or Y) are provided, use YY as 2 low-order digits for
* the year in the given century. Keep in mind that the 21st century
* AD runs from 2001-2100, not 2000-2099; 6th century BC runs from
* 600BC to 501BC.
*/
if (tmfc.cc != 0 && tmfc.yysz <= 2) {
if (tmfc.bc != 0) {
tmfc.cc = -tmfc.cc;
}
tm.years = tmfc.year % 100;
if (tm.years != 0) {
if (tmfc.cc >= 0) {
tm.years += (tmfc.cc - 1) * 100;
} else {
tm.years = (tmfc.cc + 1) * 100 - tm.years + 1;
}
} else {
/* find century year for dates ending in "00" */
tm.years = tmfc.cc * 100 + ((tmfc.cc >= 0) ? 0 : 1);
}
} else {
/* If a 4-digit year is provided, we use that and ignore CC. */
tm.years = tmfc.year;
if (tmfc.bc != 0 && tm.years > 0) {
tm.years = -(tm.years - 1);
}
}
}
else if (tmfc.cc != 0) { /* use first year of century */
if (tmfc.bc != 0) {
tmfc.cc = -tmfc.cc;
}
if (tmfc.cc >= 0) {
/* +1 because 21st century started in 2001 */
tm.years = (tmfc.cc - 1) * 100 + 1;
} else {
/* +1 because year == 599 is 600 BC */
tm.years = tmfc.cc * 100 + 1;
}
}
if (tmfc.j != 0) {
DateTimeUtil.j2date(tmfc.j, tm);
}
if (tmfc.ww != 0) {
if (tmfc.mode == FromCharDateMode.FROM_CHAR_DATE_ISOWEEK) {
/*
* If tmfc.d is not set, then the date is left at the beginning of
* the ISO week (Monday).
*/
if (tmfc.d != 0) {
DateTimeUtil.isoweekdate2date(tmfc.ww, tmfc.d, tm);
} else {
DateTimeUtil.isoweek2date(tmfc.ww, tm);
}
} else {
tmfc.ddd = (tmfc.ww - 1) * 7 + 1;
}
}
if (tmfc.w != 0) {
tmfc.dd = (tmfc.w - 1) * 7 + 1;
}
if (tmfc.d != 0) {
//tm.tm_wday = tmfc.d - 1; /* convert to native numbering */
}
if (tmfc.dd != 0) {
tm.dayOfMonth = tmfc.dd;
}
if (tmfc.ddd != 0) {
tm.dayOfYear = tmfc.ddd;
}
if (tmfc.mm != 0) {
tm.monthOfYear = tmfc.mm;
}
if (tmfc.ddd != 0 && (tm.monthOfYear <= 1 || tm.dayOfMonth <= 1)) {
/*
* The month and day field have not been set, so we use the
* day-of-year field to populate them. Depending on the date mode,
* this field may be interpreted as a Gregorian day-of-year, or an ISO
* week date day-of-year.
*/
if (tm.years == 0 && tmfc.bc == 0) {
throw new IllegalArgumentException("cannot calculate day of year without year information");
}
if (tmfc.mode == FromCharDateMode.FROM_CHAR_DATE_ISOWEEK) {
/* zeroth day of the ISO year, in Julian */
int j0 = DateTimeUtil.isoweek2j(tm.years, 1) - 1;
DateTimeUtil.j2date(j0 + tmfc.ddd, tm);
} else {
int i;
boolean leap = DateTimeUtil.isLeapYear(tm.years);
int[] y = ysum[leap ? 1 : 0];
for (i = 1; i <= MONTHS_PER_YEAR; i++) {
if (tmfc.ddd < y[i])
break;
}
if (tm.monthOfYear <= 1) {
tm.monthOfYear = i;
}
if (tm.dayOfMonth <= 1) {
tm.dayOfMonth = tmfc.ddd - y[i - 1];
}
tm.dayOfYear = 0;
}
}
if (tmfc.ms != 0) {
tm.fsecs += tmfc.ms * 1000;
}
if (tmfc.us != 0) {
tm.fsecs += tmfc.us;
}
}
/**
* Format parser, search small keywords and keyword's suffixes, and make
* format-node tree.
*
* for DATE-TIME & NUMBER version
* @param node
* @param str
* @param ver
*/
static void parseFormat(FormatNode[] node, String str, FORMAT_TYPE ver) {
KeySuffix s;
boolean node_set = false;
int suffix;
int last = 0;
int nodeIndex = 0;
int charIdx = 0;
char[] chars = str.toCharArray();
while (charIdx < chars.length) {
suffix = 0;
// Prefix
if (ver == FORMAT_TYPE.DCH_TYPE && (s = suff_search(chars, charIdx, SUFFTYPE_PREFIX)) != null) {
suffix |= s.id;
if (s.len > 0) {
charIdx += s.len;
}
}
// Keyword
if (charIdx < chars.length && (node[nodeIndex].key = index_seq_search(chars, charIdx)) != null) {
node[nodeIndex].type = NODE_TYPE_ACTION;
node[nodeIndex].suffix = 0;
node_set = true;
if (node[nodeIndex].key.len > 0) {
charIdx += node[nodeIndex].key.len;
}
// NUM version: Prepare global NUMDesc struct
if (ver == FORMAT_TYPE.NUM_TYPE) {
//NUMDesc_prepare(Num, node);
}
// Postfix
if (ver == FORMAT_TYPE.DCH_TYPE && charIdx < chars.length && (s = suff_search(chars, charIdx, SUFFTYPE_POSTFIX)) != null) {
suffix |= s.id;
if (s.len > 0) {
charIdx += s.len;
}
}
} else if (charIdx < chars.length) {
// Special characters '\' and '"'
if (chars[charIdx] == '"' && last != '\\') {
int x = 0;
while (charIdx < chars.length ) {
charIdx++;
if (chars[charIdx] == '"' && x != '\\') {
charIdx++;
break;
} else if (chars[charIdx] == '\\' && x != '\\') {
x = '\\';
continue;
}
node[nodeIndex].type = NODE_TYPE_CHAR;
node[nodeIndex].character = chars[charIdx];
node[nodeIndex].key = null;
node[nodeIndex].suffix = 0;
nodeIndex++;
x = chars[charIdx];
}
node_set = false;
suffix = 0;
last = 0;
} else if (charIdx < chars.length - 1 && chars[charIdx] == '\\' && last != '\\' && chars[charIdx + 1] == '"') {
last = chars[charIdx];
charIdx++;
} else if (charIdx < chars.length) {
node[nodeIndex].type = NODE_TYPE_CHAR;
node[nodeIndex].character = chars[charIdx];
node[nodeIndex].key = null;
node_set = true;
last = 0;
charIdx++;
}
}
// end
if (node_set) {
if (node[nodeIndex].type == NODE_TYPE_ACTION) {
node[nodeIndex].suffix = suffix;
}
nodeIndex++;
node[nodeIndex].suffix = 0;
node_set = false;
}
}
node[nodeIndex].type = NODE_TYPE_END;
node[nodeIndex].suffix = 0;
}
/**
* Process a string as denoted by a list of FormatNodes.
* The TmFromChar struct pointed to by 'out' is populated with the results.
*
* Note: we currently don't have any to_interval() function, so there
* is no need here for INVALID_FOR_INTERVAL checks.
* @param nodes
* @param dateText
* @param out
*/
static void DCH_from_char(FormatNode[] nodes, String dateText, TmFromChar out) {
int len;
AtomicInteger value = new AtomicInteger();
boolean fx_mode = false;
char[] chars = dateText.toCharArray();
int charIdx = 0;
int nodeIdx = 0;
for (; nodeIdx < nodes.length; nodeIdx++) {
FormatNode node = nodes[nodeIdx];
if (node.type == NODE_TYPE_END || charIdx >= chars.length) {
break;
}
if (node.type != NODE_TYPE_ACTION) {
charIdx++;
/* Ignore spaces when not in FX (fixed width) mode */
if (Character.isSpaceChar(node.character) && !fx_mode) {
while (charIdx < chars.length && Character.isSpaceChar(chars[charIdx])) {
charIdx++;
}
}
continue;
}
from_char_set_mode(out, node.key.date_mode);
switch (node.key.idType) {
case DCH_FX:
fx_mode = true;
break;
case DCH_A_M:
case DCH_P_M:
case DCH_a_m:
case DCH_p_m:
value.set(out.pm);
charIdx += from_char_seq_search(value, dateText, charIdx, ampm_strings_long, ALL_UPPER, node.key.len, node);
assertOutValue(out.pm, value.get() % 2, node);
out.pm = value.get() % 2;
out.clock = CLOCK_12_HOUR;
break;
case DCH_AM:
case DCH_PM:
case DCH_am:
case DCH_pm:
value.set(out.pm);
charIdx += from_char_seq_search(value, dateText, charIdx, ampm_strings, ALL_UPPER, node.key.len, node);
assertOutValue(out.pm, value.get() % 2, node);
out.pm = value.get() % 2;
out.clock = CLOCK_12_HOUR;
break;
case DCH_HH:
case DCH_HH12:
value.set(out.hh);
charIdx += from_char_parse_int_len(value, dateText, charIdx, 2, nodes, nodeIdx);
out.hh = value.get();
out.clock = CLOCK_12_HOUR;
charIdx += SKIP_THth(node.suffix);
break;
case DCH_HH24:
value.set(out.hh);
charIdx += from_char_parse_int_len(value, dateText, charIdx, 2, nodes, nodeIdx);
out.hh = value.get();
charIdx += SKIP_THth(node.suffix);
break;
case DCH_MI:
value.set(out.mi);
charIdx += from_char_parse_int(value, dateText, charIdx, nodes, nodeIdx);
out.mi = value.get();
charIdx += SKIP_THth(node.suffix);
break;
case DCH_SS:
value.set(out.ss);
charIdx += from_char_parse_int(value, dateText, charIdx, nodes, nodeIdx);
out.ss = value.get();
charIdx += SKIP_THth(node.suffix);
break;
case DCH_MS: /* millisecond */
value.set(out.ms);
len = from_char_parse_int_len(value, dateText, charIdx, 3, nodes, nodeIdx);
charIdx += len;
out.ms = value.get();
/*
* 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not 0.25
*/
out.ms *= len == 1 ? 100 :
len == 2 ? 10 : 1;
charIdx += SKIP_THth(node.suffix);
break;
case DCH_US: /* microsecond */
value.set(out.us);
len = from_char_parse_int_len(value, dateText, charIdx, 6, nodes, nodeIdx);
charIdx += len;
out.us = value.get();
out.us *= len == 1 ? 100000 :
len == 2 ? 10000 :
len == 3 ? 1000 :
len == 4 ? 100 :
len == 5 ? 10 : 1;
charIdx += SKIP_THth(node.suffix);
break;
case DCH_SSSS:
value.set(out.ssss);
charIdx += from_char_parse_int(value, dateText, charIdx, nodes, nodeIdx);
out.ssss = value.get();
charIdx += SKIP_THth(node.suffix);
break;
case DCH_tz:
case DCH_TZ:
throw new IllegalArgumentException("\"TZ\"/\"tz\" format patterns are not supported in to_date");
case DCH_A_D:
case DCH_B_C:
case DCH_a_d:
case DCH_b_c:
value.set(out.bc);
charIdx += from_char_seq_search(value, dateText, charIdx, adbc_strings_long, ALL_UPPER, node.key.len, node);
assertOutValue(out.bc, value.get() % 2, node);
out.bc = value.get() % 2;
break;
case DCH_AD:
case DCH_BC:
case DCH_ad:
case DCH_bc:
value.set(out.bc);
charIdx += from_char_seq_search(value, dateText, charIdx, adbc_strings, ALL_UPPER, node.key.len, node);
assertOutValue(out.bc, value.get() % 2, node);
out.bc = value.get() % 2;
break;
case DCH_MONTH:
case DCH_Month:
case DCH_month:
value.set(out.mm);
charIdx += from_char_seq_search(value, dateText, charIdx, months_full, ONE_UPPER, MAX_MONTH_LEN, node);
assertOutValue(out.mm, value.get() + 1, node);
out.mm = value.get() + 1;
break;
case DCH_MON:
case DCH_Mon:
case DCH_mon:
value.set(out.mm);
charIdx += from_char_seq_search(value, dateText, charIdx, months_short, ONE_UPPER, MAX_MON_LEN, node);
assertOutValue(out.mm, value.get() + 1, node);
out.mm = value.get() + 1;
break;
case DCH_MM:
value.set(out.mm);
charIdx += from_char_parse_int(value, dateText, charIdx, nodes, nodeIdx);
out.mm = value.get();
charIdx += SKIP_THth(node.suffix);
break;
case DCH_DAY:
case DCH_Day:
case DCH_day:
value.set(out.d);
charIdx += from_char_seq_search(value, dateText, charIdx, days_full, ONE_UPPER, MAX_DAY_LEN, node);
assertOutValue(out.d, value.get(), node);
out.d = value.get();
out.d++;
break;
case DCH_DY:
case DCH_Dy:
case DCH_dy:
value.set(out.d);
charIdx += from_char_seq_search(value, dateText, charIdx, days_full, ONE_UPPER, MAX_DY_LEN, node);
assertOutValue(out.d, value.get(), node);
out.d = value.get();
out.d++;
break;
case DCH_DDD:
value.set(out.ddd);
charIdx += from_char_parse_int(value, dateText, charIdx, nodes, nodeIdx);
out.ddd = value.get();
charIdx += SKIP_THth(node.suffix);
break;
case DCH_IDDD:
value.set(out.ddd);
charIdx += from_char_parse_int_len(value, dateText, charIdx, 3, nodes, nodeIdx);
out.ddd = value.get();
charIdx += SKIP_THth(node.suffix);
break;
case DCH_DD:
value.set(out.dd);
charIdx += from_char_parse_int(value, dateText, charIdx, nodes, nodeIdx);
out.dd = value.get();
charIdx += SKIP_THth(node.suffix);
break;
case DCH_D:
value.set(out.d);
charIdx += from_char_parse_int(value, dateText, charIdx, nodes, nodeIdx);
out.d = value.get();
charIdx += SKIP_THth(node.suffix);
break;
case DCH_ID:
value.set(out.d);
charIdx += from_char_parse_int_len(value, dateText, charIdx, 1, nodes, nodeIdx);
out.d = value.get();
/* Shift numbering to match Gregorian where Sunday = 1 */
if (++out.d > 7) {
out.d = 1;
}
charIdx += SKIP_THth(node.suffix);
break;
case DCH_WW:
case DCH_IW:
value.set(out.ww);
charIdx += from_char_parse_int(value, dateText, charIdx, nodes, nodeIdx);
out.ww = value.get();
charIdx += SKIP_THth(node.suffix);
break;
case DCH_Q:
/*
* We ignore 'Q' when converting to date because it is unclear
* which date in the quarter to use, and some people specify
* both quarter and month, so if it was honored it might
* conflict with the supplied month. That is also why we don't
* throw an error.
*
* We still parse the source string for an integer, but it
* isn't stored anywhere in 'out'.
*/
charIdx += from_char_parse_int(null, dateText, charIdx, nodes, nodeIdx);
charIdx += SKIP_THth(node.suffix);
break;
case DCH_CC:
value.set(out.cc);
charIdx += from_char_parse_int(value, dateText, charIdx, nodes, nodeIdx);
out.cc = value.get();
charIdx += SKIP_THth(node.suffix);
break;
case DCH_Y_YYY: {
int commaIndex = dateText.indexOf(",", charIdx);
if (commaIndex <= 0) {
throw new IllegalArgumentException("invalid input string for \"Y,YYY\"");
}
int millenia = Integer.parseInt(dateText.substring(charIdx, commaIndex));
int years = Integer.parseInt(dateText.substring(commaIndex + 1, commaIndex + 1 + 3));
years += (millenia * 1000);
assertOutValue(out.year, years, node);
out.year = years;
out.yysz = 4;
charIdx += strdigits_len(dateText, charIdx) + 4 + SKIP_THth(node.suffix);
}
break;
case DCH_YYYY:
case DCH_IYYY:
value.set(out.year);
charIdx += from_char_parse_int(value, dateText, charIdx, nodes, nodeIdx);
out.year = value.get();
out.yysz = 4;
charIdx += SKIP_THth(node.suffix);
break;
case DCH_YYY:
case DCH_IYY: {
int retVal = from_char_parse_int(value, dateText, charIdx, nodes, nodeIdx);
charIdx += retVal;
out.year = value.get();
if (retVal < 4) {
out.year = adjust_partial_year_to_2020(out.year);
}
out.yysz = 3;
charIdx += SKIP_THth(node.suffix);
}
break;
case DCH_YY:
case DCH_IY: {
value.set(out.year);
int retVal = from_char_parse_int(value, dateText, charIdx, nodes, nodeIdx);
charIdx += retVal;
out.year = value.get();
if (retVal < 4) {
out.year = adjust_partial_year_to_2020(out.year);
}
out.yysz = 2;
charIdx += SKIP_THth(node.suffix);
}
break;
case DCH_Y:
case DCH_I:
value.set(out.year);
int retVal = from_char_parse_int(value, dateText, charIdx, nodes, nodeIdx);
charIdx += retVal;
out.year = value.get();
if (retVal < 4) {
out.year = adjust_partial_year_to_2020(out.year);
}
out.yysz = 1;
charIdx += SKIP_THth(node.suffix);
break;
case DCH_RM:
value.set(out.mm);
charIdx += from_char_seq_search(value, dateText, charIdx, rm_months_upper, ALL_UPPER, MAX_RM_LEN, node);
assertOutValue(out.mm, MONTHS_PER_YEAR - value.get(), node);
out.mm = MONTHS_PER_YEAR - value.get();
break;
case DCH_rm:
value.set(out.mm);
charIdx += from_char_seq_search(value, dateText, charIdx, rm_months_lower, ALL_LOWER, MAX_RM_LEN, node);
assertOutValue(out.mm, MONTHS_PER_YEAR - value.get(), node);
out.mm = MONTHS_PER_YEAR - value.get();
break;
case DCH_W:
value.set(out.w);
charIdx += from_char_parse_int(value, dateText, charIdx, nodes, nodeIdx);
out.w = value.get();
charIdx += SKIP_THth(node.suffix);
break;
case DCH_J:
value.set(out.j);
charIdx += from_char_parse_int(value, dateText, charIdx, nodes, nodeIdx);
out.j = value.get();
charIdx += SKIP_THth(node.suffix);
break;
default:
break;
}
}
}
static KeySuffix suff_search(char[] chars, int startIdx, int type) {
for (KeySuffix eachSuffix: DCH_suff) {
if (eachSuffix.type != type) {
continue;
}
if (strncmp(chars, startIdx, eachSuffix.name, eachSuffix.len)) {
return eachSuffix;
}
}
return null;
}
/**
* Fast sequential search, use index for data selection which
* go to seq. cycle (it is very fast for unwanted strings)
* (can't be used binary search in format parsing)
* @param chars
* @param startIdx
* @return
*/
static KeyWord index_seq_search(char[] chars, int startIdx) {
if (KeyWord_INDEX_FILTER(chars[startIdx]) == 0) {
return null;
}
Integer pos = DCH_index.get(chars[startIdx]);
if (pos != null) {
KeyWord keyword = DCH_keywords[pos];
do {
if (strncmp(chars, startIdx, keyword.name, keyword.len)) {
return keyword;
}
pos++;
if (pos >= DCH_keywords.length) {
return null;
}
keyword = DCH_keywords[pos];
} while (chars[startIdx] == keyword.name.charAt(0));
}
return null;
}
static boolean strncmp(char[] chars, int startIdx, String str, int len) {
if (chars.length - startIdx < len) {
return false;
}
int index = startIdx;
for (int i = 0; i < len; i++, index++) {
if (chars[index] != str.charAt(i)) {
return false;
}
}
return true;
}
static int KeyWord_INDEX_FILTER(char c) {
return ((c) <= ' ' || (c) >= '~' ? 0 : 1);
}
/**
* Set the date mode of a from-char conversion.
*
* Puke if the date mode has already been set, and the caller attempts to set
* it to a conflicting mode.
* @param tmfc
* @param mode
*/
static void from_char_set_mode(TmFromChar tmfc, FromCharDateMode mode) {
if (mode != FromCharDateMode.FROM_CHAR_DATE_NONE) {
if (tmfc.mode == FromCharDateMode.FROM_CHAR_DATE_NONE) {
tmfc.mode = mode;
} else if (tmfc.mode != mode) {
throw new IllegalArgumentException("invalid combination of date conventions: " +
"Do not mix Gregorian and ISO week date " +
"conventions in a formatting template.");
}
}
}
/**
* Perform a sequential search in 'array' for text matching the first 'max'
* characters of the source string.
*
* If a match is found, copy the array index of the match into the integer
* pointed to by 'dest', advance 'src' to the end of the part of the string
* which matched, and return the number of characters consumed.
*
* If the string doesn't match, throw an error.
* @param dest
* @param src
* @param charIdx
* @param array
* @param type
* @param max
* @param node
* @return
*/
static int from_char_seq_search(AtomicInteger dest, String src, int charIdx, String[] array, int type, int max,
FormatNode node) {
AtomicInteger len = new AtomicInteger(0);
dest.set(seq_search(src, charIdx, array, type, max, len));
if (len.get() <= 0) {
String copy;
if (charIdx + node.key.len >= src.length()) {
copy = src.substring(charIdx, charIdx + node.key.len);
} else {
copy = src.substring(charIdx);
}
throw new IllegalArgumentException("Invalid value \"" + copy + "\" for \"" + node.key.name + "\". " +
"The given value did not match any of the allowed values for this field.");
}
return len.get();
}
/**
* Sequential search with to upper/lower conversion
* @param name
* @param charIdx
* @param array
* @param type
* @param max
* @param len
* @return
*/
static int seq_search(String name, int charIdx, String[] array, int type, int max, AtomicInteger len) {
if (name == null || name.length() <= charIdx) {
return -1;
}
char[] nameChars = name.toCharArray();
char nameChar = nameChars[charIdx];
/* set first char */
if (type == ONE_UPPER || type == ALL_UPPER) {
nameChar = Character.toUpperCase(nameChar);
} else if (type == ALL_LOWER) {
nameChar = Character.toLowerCase(nameChar);
}
int arrayIndex = 0;
for (int last = 0; array[arrayIndex] != null; arrayIndex++) {
String arrayStr = array[arrayIndex];
/* comperate first chars */
if (nameChar != arrayStr.charAt(0)) {
continue;
}
int arrayStrLen = arrayStr.length();
int arrayCharIdx = 1;
int nameCharIdx = charIdx + 1;
for (int idx = 1; ; nameCharIdx++, arrayCharIdx++, idx++) {
// search fragment (max) only
if (max != 0 && idx == max) {
len.set(idx + 1); // '\0'
return arrayIndex;
}
// full size
if (arrayCharIdx == arrayStrLen - 1) {
len.set(idx + 1); // '\0'
return arrayIndex;
}
// Not found in array 'a'
if (nameCharIdx == nameChars.length - 1) {
break;
}
/*
* Convert (but convert new chars only)
*/
nameChar = nameChars[nameCharIdx];
if (idx > last) {
if (type == ONE_UPPER || type == ALL_LOWER) {
nameChar = Character.toLowerCase(nameChar);
} else if (type == ALL_UPPER) {
nameChar = Character.toUpperCase(nameChar);
}
last = idx;
}
if (nameChar != arrayStr.charAt(arrayCharIdx)){
break;
}
}
}
return -1;
}
/**
* Read a single integer from the source string, into the int pointed to by
* 'dest'. If 'dest' is NULL, the result is discarded.
*
* In fixed-width mode (the node does not have the FM suffix), consume at most
* 'len' characters. However, any leading whitespace isn't counted in 'len'.
*
* We use strtol() to recover the integer value from the source string, in
* accordance with the given FormatNode.
*
* If the conversion completes successfully, src will have been advanced to
* point at the character immediately following the last character used in the
* conversion.
*
* Return the number of characters consumed.
*
* Note that from_char_parse_int() provides a more convenient wrapper where
* the length of the field is the same as the length of the format keyword (as
* with DD and MI).
* @param dest
* @param src
* @param charIdx
* @param len
* @param nodes
* @param nodeIndex
* @return
*/
static int from_char_parse_int_len(AtomicInteger dest, String src, int charIdx, int len, FormatNode[] nodes, int nodeIndex) {
long result;
int initCharIdx = charIdx;
StringBuilder tempSb = new StringBuilder();
/*
* Skip any whitespace before parsing the integer.
*/
charIdx = strspace_len(src, charIdx);
int used = src.length() <= charIdx + len ? src.length() - (charIdx + len) : len;
if (used <= 0) {
used = src.length() - charIdx;
}
String copy = src.substring(charIdx, charIdx + used);
if (S_FM(nodes[nodeIndex].suffix) != 0 || is_next_separator(nodes, nodeIndex)) {
/*
* This node is in Fill Mode, or the next node is known to be a
* non-digit value, so we just slurp as many characters as we can get.
*/
result = DateTimeUtil.strtol(src, charIdx, tempSb);
charIdx = src.length() - tempSb.length();
} else {
/*
* We need to pull exactly the number of characters given in 'len' out
* of the string, and convert those.
*/
if (used < len) {
throw new IllegalArgumentException("source string too short for \"" + nodes[nodeIndex].key.name + "\" + formatting field");
}
result = DateTimeUtil.strtol(copy, 0, tempSb);
used = copy.length() - tempSb.length();
if (used > 0 && used < len) {
throw new IllegalArgumentException("invalid value \"" + copy + "\" for \"" + nodes[nodeIndex].key.name + "\"." +
"Field requires " + len + " characters, but only " + used);
}
charIdx += used;
}
if (charIdx == initCharIdx) {
throw new IllegalArgumentException("invalid value \"" + copy + "\" for \"" + nodes[nodeIndex].key.name + "\"." +
"Value must be an integer.");
}
if (result < Integer.MIN_VALUE || result > Integer.MAX_VALUE) {
throw new IllegalArgumentException("value for \"" + nodes[nodeIndex].key.name + "\"" +
" in source string is out of range." +
"Value must be in the range " + Integer.MIN_VALUE + " to " + Integer.MAX_VALUE + ".");
}
if (dest != null) {
assertOutValue(dest.get(), (int)result, nodes[nodeIndex]);
dest.set((int)result);
}
return charIdx - initCharIdx;
}
/**
* Call from_char_parse_int_len(), using the length of the format keyword as
* the expected length of the field.
*
* Don't call this function if the field differs in length from the format
* keyword (as with HH24; the keyword length is 4, but the field length is 2).
* In such cases, call from_char_parse_int_len() instead to specify the
* required length explicitly.
* @param dest
* @param src
* @param charIdx
* @param nodes
* @param nodeIdx
* @return
*/
static int from_char_parse_int(AtomicInteger dest, String src, int charIdx, FormatNode[] nodes, int nodeIdx) {
return from_char_parse_int_len(dest, src, charIdx, nodes[nodeIdx].key.len, nodes, nodeIdx);
}
static int strspace_len(String str, int charIdx) {
int len = str.length();
while (charIdx < len && Character.isSpaceChar(str.charAt(charIdx))) {
charIdx++;
}
return charIdx;
}
static int strdigits_len(String str, int charIdx) {
int len = strspace_len(str, charIdx);
int index = charIdx + len;
int strLen = str.length();
while (index < strLen && Character.isDigit(str.charAt(index)) && len <= DCH_MAX_ITEM_SIZ) {
len++;
index++;
}
return len;
}
static void assertOutValue(int dest, int value, FormatNode node) {
if (dest != 0 && dest != value) {
throw new IllegalArgumentException(
"conflicting values for \"" + node.key.name + "\" field in formatting string," +
"This value contradicts a previous setting for the same field type(" + dest + "," + value + ")");
}
}
/**
* Return true if next format picture is not digit value
* @param nodes
* @param nodeIndex
* @return
*/
static boolean is_next_separator(FormatNode[] nodes, int nodeIndex) {
int index = nodeIndex;
if (nodes[index].type == NODE_TYPE_END) {
return false;
}
if (nodes[index].type == NODE_TYPE_ACTION && S_THth(nodes[index].suffix) != 0) {
return true;
}
/*
* Next node
*/
index++;
/* end of format string is treated like a non-digit separator */
if (nodes[index].type == NODE_TYPE_END) {
return true;
}
if (nodes[index].type == NODE_TYPE_ACTION) {
return nodes[index].key.is_digit ? false : true;
} else if (Character.isDigit(nodes[index].character)) {
return false;
}
return true; /* some non-digit input (separator) */
}
/**
* Adjust all dates toward 2020; this is effectively what happens when we
* assume '70' is 1970 and '69' is 2069.
* @param year
* @return
*/
static int adjust_partial_year_to_2020(int year) {
if (year < 70) {
/* Force 0-69 into the 2000's */
return year + 2000;
} else if (year < 100) {
/* Force 70-99 into the 1900's */
return year + 1900;
} else if (year < 520) {
/* Force 100-519 into the 2000's */
return year + 2000;
} else if (year < 1000) {
/* Force 520-999 into the 1000's */
return year + 1000;
} else {
return year;
}
}
/**
* Converts TimeMeta to a string using given the format pattern text.
* @param tm
* @param formatText
* @return
*/
public static String to_char(TimeMeta tm, String formatText) {
int fmt_len = formatText.length();
StringBuilder out = new StringBuilder();
if (fmt_len > 0) {
FormatNode[] formatNodes;
synchronized(formatNodeCache) {
formatNodes = formatNodeCache.get(formatText);
}
if (formatNodes == null) {
formatNodes = new FormatNode[fmt_len + 1];
for (int i = 0; i < formatNodes.length; i++) {
formatNodes[i] = new FormatNode();
}
parseFormat(formatNodes, formatText, FORMAT_TYPE.DCH_TYPE);
formatNodes[fmt_len].type = NODE_TYPE_END; /* Paranoia? */
synchronized(formatNodeCache) {
formatNodeCache.put(formatText, formatNodes);
}
}
DCH_to_char(formatNodes, false, tm, out);
return out.toString();
} else {
throw new IllegalArgumentException("No format text.");
}
}
private static final char[][] zeroStrings = {{}, {'0'}, {'0', '0'}, {'0', '0', '0'},
{'0', '0', '0', '0'}, {'0', '0', '0', '0', '0'}, {'0', '0', '0', '0', '0', '0'}};
/**
* Format integer value to strings.
* @param value - the value to format a string
* @param size - minimal width of string
*/
private static String formatInteger(int value, int size) {
char[] targetArray, tempArray;
final boolean isPositive = value>=0;
final int tempValue = isPositive?value:-value;
int targetArraySize;
tempArray = Integer.toString(tempValue).toCharArray();
targetArraySize = Math.max(tempArray.length, size + (isPositive?0:1));
targetArray = new char[targetArraySize];
if (size > tempArray.length) {
System.arraycopy(zeroStrings[size], 0, targetArray, (targetArraySize-size), size);
}
System.arraycopy(tempArray, 0, targetArray, (targetArraySize-tempArray.length), tempArray.length);
if (!isPositive) {
targetArray[0] = '-';
}
return new String(targetArray);
}
/**
* Format string value
* @param value - the value to format
* @param width - minimal width of string
* @return
*/
private static String formatString(String value, int width) {
char[] targetArray, tempArray;
final boolean isLeftJustified = width<0;
final int minimalWidth = isLeftJustified?-width:width;
String result = value;
int targetArraySize;
if (minimalWidth > 0 && value != null) {
tempArray = value.toCharArray();
targetArraySize = Math.max(tempArray.length, minimalWidth);
targetArray = new char[targetArraySize];
Arrays.fill(targetArray, ' ');
System.arraycopy(tempArray, 0, targetArray,
isLeftJustified?0:(targetArraySize-tempArray.length), tempArray.length);
result = new String(targetArray);
}
return result;
}
/**
* Process a TmToChar struct as denoted by a list of FormatNodes.
* The formatted data is written to the string pointed to by 'out'.
* @param nodes
* @param isInterval
* @param tm
* @param out
*/
private static void DCH_to_char(FormatNode[] nodes, boolean isInterval, TimeMeta tm, StringBuilder out) {
int i;
for (FormatNode node: nodes) {
if (node.type == NODE_TYPE_END) {
break;
}
if (node.type != NODE_TYPE_ACTION) {
out.append(node.character);
continue;
}
switch (node.key.idType) {
case DCH_A_M:
case DCH_P_M:
out.append((tm.hours % HOURS_PER_DAY >= HOURS_PER_DAY / 2) ? P_M_STR : A_M_STR);
break;
case DCH_AM:
case DCH_PM:
out.append((tm.hours % HOURS_PER_DAY >= HOURS_PER_DAY / 2) ? PM_STR : AM_STR);
break;
case DCH_a_m:
case DCH_p_m:
out.append((tm.hours % HOURS_PER_DAY >= HOURS_PER_DAY / 2) ? p_m_STR : a_m_STR);
break;
case DCH_am:
case DCH_pm:
out.append((tm.hours % HOURS_PER_DAY >= HOURS_PER_DAY / 2) ? pm_STR : am_STR);
break;
case DCH_HH:
case DCH_HH12: {
/*
* display time as shown on a 12-hour clock, even for
* intervals
*/
out.append(formatInteger(tm.hours % (HOURS_PER_DAY / 2) == 0 ? HOURS_PER_DAY / 2 : tm.hours
% (HOURS_PER_DAY / 2),
S_FM(node.suffix)!=0?0:2));
if (S_THth(node.suffix) != 0) {
str_numth(out, out, S_TH_TYPE(node.suffix));
}
break;
}
case DCH_HH24: {
out.append(formatInteger(tm.hours, S_FM(node.suffix)!=0?0:2));
if (S_THth(node.suffix) != 0) {
str_numth(out, out, S_TH_TYPE(node.suffix));
}
break;
}
case DCH_MI: {
out.append(formatInteger(tm.minutes, S_FM(node.suffix)!=0?0:2));
if (S_THth(node.suffix) != 0) {
str_numth(out, out, S_TH_TYPE(node.suffix));
}
break;
}
case DCH_SS: {
out.append(formatInteger(tm.secs, S_FM(node.suffix)!=0?0:2));
if (S_THth(node.suffix) != 0) {
str_numth(out, out, S_TH_TYPE(node.suffix));
}
break;
}
case DCH_MS: /* millisecond */
out.append(formatInteger((int)(tm.fsecs/1000.0), 3));
if (S_THth(node.suffix) != 0) {
str_numth(out, out, S_TH_TYPE(node.suffix));
}
break;
case DCH_US: /* microsecond */
out.append(formatInteger((int) tm.fsecs, 6));
if (S_THth(node.suffix) != 0) {
str_numth(out, out, S_TH_TYPE(node.suffix));
}
break;
case DCH_SSSS:
out.append(formatInteger((tm.hours * DateTimeConstants.SECS_PER_HOUR +
tm.minutes * DateTimeConstants.SECS_PER_MINUTE + tm.secs), 0));
if (S_THth(node.suffix) != 0) {
str_numth(out, out, S_TH_TYPE(node.suffix));
}
break;
case DCH_tz:
invalidForInterval(isInterval, node);
//TODO
break;
case DCH_A_D:
case DCH_B_C:
invalidForInterval(isInterval, node);
out.append((tm.years <= 0 ? B_C_STR : A_D_STR));
break;
case DCH_AD:
case DCH_BC:
invalidForInterval(isInterval, node);
out.append((tm.years <= 0 ? BC_STR : AD_STR));
break;
case DCH_a_d:
case DCH_b_c:
invalidForInterval(isInterval, node);
out.append((tm.years <= 0 ? b_c_STR : a_d_STR));
break;
case DCH_ad:
case DCH_bc:
invalidForInterval(isInterval, node);
out.append((tm.years <= 0 ? bc_STR : ad_STR));
break;
case DCH_MONTH:
invalidForInterval(isInterval, node);
if (tm.monthOfYear == 0) {
break;
}
if (S_TM(node.suffix) != 0) {
out.append(months_full[tm.monthOfYear - 1].toUpperCase());
} else {
out.append(formatString(months_full[tm.monthOfYear - 1].toUpperCase(),
S_FM(node.suffix)!=0?0:-9));
}
break;
case DCH_Month:
invalidForInterval(isInterval, node);
if (tm.monthOfYear == 0) {
break;
}
if (S_TM(node.suffix) != 0) {
out.append(months_full[tm.monthOfYear - 1]);
} else {
out.append(formatString(months_full[tm.monthOfYear - 1],
S_FM(node.suffix)!=0?0:-9));
}
break;
case DCH_month:
invalidForInterval(isInterval, node);
if (tm.monthOfYear == 0) {
break;
}
if (S_TM(node.suffix) != 0) {
out.append(months_full[tm.monthOfYear - 1].toLowerCase());
} else {
out.append(formatString(months_full[tm.monthOfYear - 1].toLowerCase(),
S_FM(node.suffix)!=0?0:-9));
}
break;
case DCH_MON:
invalidForInterval(isInterval, node);
if (tm.monthOfYear == 0) {
break;
}
if (S_TM(node.suffix) != 0) {
out.append(months_short[tm.monthOfYear - 1].toUpperCase());
} else {
out.append(months_short[tm.monthOfYear - 1].toUpperCase());
}
break;
case DCH_Mon:
invalidForInterval(isInterval, node);
if (tm.monthOfYear == 0) {
break;
}
if (S_TM(node.suffix) != 0) {
out.append(months_short[tm.monthOfYear - 1]);
} else {
out.append(months_short[tm.monthOfYear - 1]);
}
break;
case DCH_mon:
invalidForInterval(isInterval, node);
if (tm.monthOfYear == 0)
break;
if (S_TM(node.suffix) != 0) {
out.append(months_short[tm.monthOfYear - 1].toLowerCase());
} else {
out.append(months_short[tm.monthOfYear - 1].toLowerCase());
}
break;
case DCH_MM: {
out.append(formatInteger(tm.monthOfYear, S_FM(node.suffix)!=0?0:2));
if (S_THth(node.suffix) != 0) {
str_numth(out, out, S_TH_TYPE(node.suffix));
}
break;
}
case DCH_DAY: {
invalidForInterval(isInterval, node);
if (S_TM(node.suffix) != 0) {
out.append(days_full[tm.getDayOfWeek()].toUpperCase());
} else {
out.append(formatString(days_full[tm.getDayOfWeek()].toUpperCase(),
S_FM(node.suffix)!=0?0:-9));
}
break;
}
case DCH_Day:
invalidForInterval(isInterval, node);
if (S_TM(node.suffix) != 0) {
out.append(days_full[tm.getDayOfWeek()]);
} else {
out.append(formatString(days_full[tm.getDayOfWeek()],
S_FM(node.suffix)!=0?0:-9));
}
break;
case DCH_day:
invalidForInterval(isInterval, node);
if (S_TM(node.suffix) != 0) {
out.append(days_full[tm.getDayOfWeek()].toLowerCase());
} else {
out.append(formatString(days_full[tm.getDayOfWeek()].toLowerCase(),
S_FM(node.suffix)!=0?0:-9));
}
break;
case DCH_DY:
invalidForInterval(isInterval, node);
if (S_TM(node.suffix) != 0) {
out.append(days_short[tm.getDayOfWeek()]);
} else {
out.append(days_short[tm.getDayOfWeek()]);
}
break;
case DCH_Dy:
invalidForInterval(isInterval, node);
if (S_TM(node.suffix) != 0) {
out.append(days_short[tm.getDayOfWeek()]);
} else {
out.append(days_short[tm.getDayOfWeek()]);
}
break;
case DCH_dy:
invalidForInterval(isInterval, node);
if (S_TM(node.suffix) != 0) {
out.append(days_short[tm.getDayOfWeek()]);
} else {
out.append(days_short[tm.getDayOfWeek()]);
}
break;
case DCH_DDD:
case DCH_IDDD: {
out.append(formatInteger((node.key.idType == DCH_poz.DCH_DDD) ?
tm.getDayOfYear() : DateTimeUtil.date2isoyearday(tm.years, tm.monthOfYear, tm.dayOfMonth),
S_FM(node.suffix)!=0?0:3));
if (S_THth(node.suffix) != 0) {
str_numth(out, out, S_TH_TYPE(node.suffix));
}
break;
}
case DCH_DD: {
out.append(formatInteger(tm.dayOfMonth, S_FM(node.suffix)!=0?0:2));
if (S_THth(node.suffix) != 0) {
str_numth(out, out, S_TH_TYPE(node.suffix));
}
break;
}
case DCH_D:
invalidForInterval(isInterval, node);
out.append(formatInteger(tm.getDayOfWeek()+1, 0));
if (S_THth(node.suffix) != 0) {
str_numth(out, out, S_TH_TYPE(node.suffix));
}
break;
case DCH_ID:
invalidForInterval(isInterval, node);
out.append(formatInteger((tm.getDayOfWeek()==0)?7:tm.getDayOfWeek(), 0));
if (S_THth(node.suffix) != 0) {
str_numth(out, out, S_TH_TYPE(node.suffix));
}
break;
case DCH_WW: {
out.append(formatInteger((tm.getDayOfYear()-1)/7+1, S_FM(node.suffix)!=0?0:2));
if (S_THth(node.suffix) != 0) {
str_numth(out, out, S_TH_TYPE(node.suffix));
}
break;
}
case DCH_IW: {
out.append(formatInteger(DateTimeUtil.date2isoweek(tm.years, tm.monthOfYear, tm.dayOfMonth),
S_FM(node.suffix)!=0?0:2));
if (S_THth(node.suffix) != 0) {
str_numth(out, out, S_TH_TYPE(node.suffix));
}
break;
}
case DCH_Q:
if (tm.monthOfYear == 0) {
break;
}
out.append(formatInteger((tm.monthOfYear-1)/3+1, 0));
if (S_THth(node.suffix) != 0) {
str_numth(out, out, S_TH_TYPE(node.suffix));
}
break;
case DCH_CC: {
if (isInterval) { /* straight calculation */
i = tm.years / 100;
} else {
if (tm.years > 0) {
/* Century 20 == 1901 - 2000 */
i = (tm.years - 1) / 100 + 1;
} else {
/* Century 6BC == 600BC - 501BC */
i = tm.years / 100 - 1;
}
}
if (i <= 99 && i >= -99) {
out.append(formatInteger(i, S_FM(node.suffix)!=0?0:2));
} else {
out.append(formatInteger(i, 0));
}
if (S_THth(node.suffix) != 0) {
str_numth(out, out, S_TH_TYPE(node.suffix));
}
break;
}
case DCH_Y_YYY:
i = ADJUST_YEAR(tm.years, isInterval) / 1000;
out.append(formatInteger(i, 0))
.append(',')
.append(formatInteger(ADJUST_YEAR(tm.years, isInterval) - (i * 1000), 3));
if (S_THth(node.suffix) != 0) {
str_numth(out, out, S_TH_TYPE(node.suffix));
}
break;
case DCH_YYYY:
case DCH_IYYY: {
out.append(formatInteger((node.key.idType == DCH_poz.DCH_YYYY ? ADJUST_YEAR(tm.years, isInterval) :
ADJUST_YEAR(DateTimeUtil.date2isoyear(tm.years, tm.monthOfYear, tm.dayOfMonth), isInterval)),
S_FM(node.suffix)!=0?0:4));
if (S_THth(node.suffix) != 0) {
str_numth(out, out, S_TH_TYPE(node.suffix));
}
break;
}
case DCH_YYY:
case DCH_IYY: {
out.append(formatInteger((node.key.idType == DCH_poz.DCH_YYY ? ADJUST_YEAR(tm.years, isInterval) :
ADJUST_YEAR(DateTimeUtil.date2isoyear(tm.years, tm.monthOfYear, tm.dayOfMonth), isInterval)) % 1000,
S_FM(node.suffix)!=0?0:3));
if (S_THth(node.suffix) != 0) {
str_numth(out, out, S_TH_TYPE(node.suffix));
}
break;
}
case DCH_YY:
case DCH_IY: {
out.append(formatInteger((node.key.idType == DCH_poz.DCH_YY ? ADJUST_YEAR(tm.years, isInterval) :
ADJUST_YEAR(DateTimeUtil.date2isoyear(tm.years, tm.monthOfYear, tm.dayOfMonth), isInterval)) % 100,
S_FM(node.suffix)!=0?0:2));
if (S_THth(node.suffix) != 0)
str_numth(out, out, S_TH_TYPE(node.suffix));
break;
}
case DCH_Y:
case DCH_I:
out.append(formatInteger((node.key.idType == DCH_poz.DCH_Y ?
ADJUST_YEAR(tm.years, isInterval) :
ADJUST_YEAR(DateTimeUtil.date2isoyear(tm.years, tm.monthOfYear, tm.dayOfMonth),
isInterval)) % 10, 1));
if (S_THth(node.suffix) != 0) {
str_numth(out, out, S_TH_TYPE(node.suffix));
}
break;
case DCH_RM: {
if (tm.monthOfYear == 0) {
break;
}
out.append(formatString(rm_months_upper[MONTHS_PER_YEAR - tm.monthOfYear],
S_FM(node.suffix)!=0?0:-4));
break;
}
case DCH_rm: {
if (tm.monthOfYear == 0) {
break;
}
out.append(formatString(rm_months_lower[MONTHS_PER_YEAR - tm.monthOfYear],
S_FM(node.suffix)!=0?0:-4));
break;
}
case DCH_W:
out.append(formatInteger((tm.dayOfMonth-1)/7+1, 0));
if (S_THth(node.suffix) != 0) {
str_numth(out, out, S_TH_TYPE(node.suffix));
}
break;
case DCH_J:
out.append(formatInteger(DateTimeUtil.date2j(tm.years, tm.monthOfYear, tm.dayOfMonth), 0));
if (S_THth(node.suffix) != 0) {
str_numth(out, out, S_TH_TYPE(node.suffix));
}
break;
default:
break;
}
}
}
/**
* Return ST/ND/RD/TH for simple (1..9) numbers
* type --> 0 upper, 1 lower
* @param num
* @param type
* @return
*/
static String get_th(StringBuilder num, int type) {
int len = num.length();
char last = num.charAt(len - 1);
if (!Character.isDigit(last)) {
throw new IllegalArgumentException("\"" + num.toString() + "\" is not a number");
}
/*
* All "teens" (<x>1[0-9]) get 'TH/th', while <x>[02-9][123] still get
* 'ST/st', 'ND/nd', 'RD/rd', respectively
*/
if ((len > 1) && (num.charAt(len - 2) == '1')) {
last = 0;
}
switch (last) {
case '1':
if (type == TH_UPPER)
return numTH[0];
return numth[0];
case '2':
if (type == TH_UPPER)
return numTH[1];
return numth[1];
case '3':
if (type == TH_UPPER)
return numTH[2];
return numth[2];
default:
if (type == TH_UPPER)
return numTH[3];
return numth[3];
}
}
/**
* Convert string-number to ordinal string-number
* type --> 0 upper, 1 lower
* @param dest
* @param num
* @param type
*/
static void str_numth(StringBuilder dest, StringBuilder num, int type) {
if (!dest.equals(num)) {
dest.append(num);
}
dest.append(get_th(num, type));
}
private static void invalidForInterval(boolean isInterval, FormatNode node) {
if (isInterval) {
throw new IllegalArgumentException("\"" + node.key.name + "\" not support for interval");
}
}
}