| /* ----------------------------------------------------------------------- |
| * formatting.c |
| * |
| * $PostgreSQL: pgsql/src/backend/utils/adt/formatting.c,v 1.116.2.4 2009/03/12 00:53:41 tgl Exp $ |
| * |
| * |
| * Portions Copyright (c) 1999-2008, PostgreSQL Global Development Group |
| * |
| * |
| * TO_CHAR(); TO_TIMESTAMP(); TO_DATE(); TO_NUMBER(); |
| * |
| * The PostgreSQL routines for a timestamp/int/float/numeric formatting, |
| * inspired by the Oracle TO_CHAR() / TO_DATE() / TO_NUMBER() routines. |
| * |
| * |
| * Cache & Memory: |
| * Routines use (itself) internal cache for format pictures. |
| * |
| * The cache uses a static buffer and is persistent across transactions. If |
| * the format-picture is bigger than the cache buffer, the parser is called |
| * always. |
| * |
| * NOTE for Number version: |
| * All in this version is implemented as keywords ( => not used |
| * suffixes), because a format picture is for *one* item (number) |
| * only. It not is as a timestamp version, where each keyword (can) |
| * has suffix. |
| * |
| * NOTE for Timestamp routines: |
| * In this module the POSIX 'struct tm' type is *not* used, but rather |
| * PgSQL type, which has tm_mon based on one (*non* zero) and |
| * year *not* based on 1900, but is used full year number. |
| * Module supports AD / BC / AM / PM. |
| * |
| * Supported types for to_char(): |
| * |
| * Timestamp, Numeric, int4, int8, float4, float8 |
| * |
| * Supported types for reverse conversion: |
| * |
| * Timestamp - to_timestamp() |
| * Date - to_date() |
| * Numeric - to_number() |
| * |
| * |
| * Karel Zak |
| * |
| * TODO |
| * - better number building (formatting) / parsing, now it isn't |
| * ideal code |
| * - use Assert() |
| * - add support for abstime |
| * - add support for roman number to standard number conversion |
| * - add support for number spelling |
| * - add support for string to string formatting (we must be better |
| * than Oracle :-), |
| * to_char('Hello', 'X X X X X') -> 'H e l l o' |
| * |
| * ----------------------------------------------------------------------- |
| */ |
| |
| /* ---------- |
| * UnComment me for DEBUG |
| * ---------- |
| */ |
| /*** |
| #define DEBUG_TO_FROM_CHAR |
| #define DEBUG_elog_output DEBUG3 |
| ***/ |
| |
| #include "postgres.h" |
| |
| #include <ctype.h> |
| #include <unistd.h> |
| #include <math.h> |
| #include <float.h> |
| #include <limits.h> |
| #include <locale.h> |
| /* |
| * towlower() and friends should be in <wctype.h>, but some pre-C99 systems |
| * declare them in <wchar.h>. |
| */ |
| #ifdef HAVE_WCHAR_H |
| #include <wchar.h> |
| #endif |
| #ifdef HAVE_WCTYPE_H |
| #include <wctype.h> |
| #endif |
| |
| #include "utils/builtins.h" |
| #include "utils/date.h" |
| #include "utils/datetime.h" |
| #include "utils/formatting.h" |
| #include "utils/int8.h" |
| #include "utils/numeric.h" |
| #include "utils/pg_locale.h" |
| #include "mb/pg_wchar.h" |
| |
| #ifndef _ |
| #define _(x) gettext(x) |
| #endif |
| |
| /* ---------- |
| * Routines type |
| * ---------- |
| */ |
| #define DCH_TYPE 1 /* DATE-TIME version */ |
| #define NUM_TYPE 2 /* NUMBER version */ |
| |
| /* ---------- |
| * KeyWord Index (ascii from position 32 (' ') to 126 (~)) |
| * ---------- |
| */ |
| #define KeyWord_INDEX_SIZE ('~' - ' ') |
| #define KeyWord_INDEX_FILTER(_c) ((_c) <= ' ' || (_c) >= '~' ? 0 : 1) |
| |
| /* ---------- |
| * Maximal length of one node |
| * ---------- |
| */ |
| #define DCH_MAX_ITEM_SIZ 9 /* max julian day */ |
| #define NUM_MAX_ITEM_SIZ 8 /* roman number (RN has 15 chars) */ |
| |
| /* ---------- |
| * More is in float.c |
| * ---------- |
| */ |
| #define MAXFLOATWIDTH 60 |
| #define MAXDOUBLEWIDTH 500 |
| |
| |
| /* ---------- |
| * External (defined in PgSQL datetime.c (timestamp utils)) |
| * ---------- |
| */ |
| extern char *months[], /* month abbreviation */ |
| *days[]; /* full days */ |
| |
| /* ---------- |
| * Format parser structs |
| * ---------- |
| */ |
| typedef struct |
| { |
| char *name; /* suffix string */ |
| int len, /* suffix length */ |
| id, /* used in node->suffix */ |
| type; /* prefix / postfix */ |
| } KeySuffix; |
| |
| /* ---------- |
| * FromCharDateMode |
| * ---------- |
| * |
| * This value is used to nominate one of several distinct (and mutually |
| * exclusive) date conventions that a keyword can belong to. |
| */ |
| typedef enum |
| { |
| FROM_CHAR_DATE_NONE = 0, /* Value does not affect date mode. */ |
| FROM_CHAR_DATE_GREGORIAN, /* Gregorian (day, month, year) style date */ |
| FROM_CHAR_DATE_ISOWEEK /* ISO 8601 week date */ |
| } FromCharDateMode; |
| |
| typedef struct FormatNode FormatNode; |
| |
| typedef struct |
| { |
| const char *name; |
| int len; |
| int id; |
| bool is_digit; |
| FromCharDateMode date_mode; |
| } KeyWord; |
| |
| typedef enum |
| { |
| FORMAT_TOCHAR, |
| FORMAT_TONUMBER, |
| FORMAT_TOTIMESTAMP |
| } FormatFunction; |
| |
| struct FormatNode |
| { |
| int type; /* node type */ |
| const KeyWord *key; /* if node type is KEYWORD */ |
| char character; /* if node type is CHAR */ |
| int suffix; /* keyword suffix */ |
| }; |
| |
| #define NODE_TYPE_END 1 |
| #define NODE_TYPE_ACTION 2 |
| #define NODE_TYPE_CHAR 3 |
| |
| #define SUFFTYPE_PREFIX 1 |
| #define SUFFTYPE_POSTFIX 2 |
| |
| #define CLOCK_24_HOUR 0 |
| #define CLOCK_12_HOUR 1 |
| |
| /* ---------- |
| * Full months |
| * ---------- |
| */ |
| static char *months_full[] = { |
| "January", "February", "March", "April", "May", "June", "July", |
| "August", "September", "October", "November", "December", NULL |
| }; |
| |
| static char *days_short[] = { |
| "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL |
| }; |
| |
| /* ---------- |
| * 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. |
| */ |
| #define ADJUST_YEAR(year, is_interval) ((is_interval) ? (year) : ((year) <= 0 ? -((year) - 1) : (year))) |
| |
| #define A_D_STR "A.D." |
| #define a_d_STR "a.d." |
| #define AD_STR "AD" |
| #define ad_STR "ad" |
| |
| #define B_C_STR "B.C." |
| #define b_c_STR "b.c." |
| #define BC_STR "BC" |
| #define 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 char *adbc_strings[] = {ad_STR, bc_STR, AD_STR, BC_STR, NULL}; |
| static char *adbc_strings_long[] = {a_d_STR, b_c_STR, A_D_STR, B_C_STR, NULL}; |
| |
| /* ---------- |
| * AM / PM |
| * ---------- |
| */ |
| #define A_M_STR "A.M." |
| #define a_m_STR "a.m." |
| #define AM_STR "AM" |
| #define am_STR "am" |
| |
| #define P_M_STR "P.M." |
| #define p_m_STR "p.m." |
| #define PM_STR "PM" |
| #define 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 char *ampm_strings[] = {am_STR, pm_STR, AM_STR, PM_STR, NULL}; |
| static char *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 char *rm_months_upper[] = |
| {"XII", "XI", "X", "IX", "VIII", "VII", "VI", "V", "IV", "III", "II", "I", NULL}; |
| |
| static char *rm_months_lower[] = |
| {"xii", "xi", "x", "ix", "viii", "vii", "vi", "v", "iv", "iii", "ii", "i", NULL}; |
| |
| /* ---------- |
| * Roman numbers |
| * ---------- |
| */ |
| static char *rm1[] = {"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", NULL}; |
| static char *rm10[] = {"X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC", NULL}; |
| static char *rm100[] = {"C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM", NULL}; |
| |
| /* ---------- |
| * Ordinal postfixes |
| * ---------- |
| */ |
| static char *numTH[] = {"ST", "ND", "RD", "TH", NULL}; |
| static char *numth[] = {"st", "nd", "rd", "th", NULL}; |
| |
| /* ---------- |
| * Flags & Options: |
| * ---------- |
| */ |
| #define ONE_UPPER 1 /* Name */ |
| #define ALL_UPPER 2 /* NAME */ |
| #define ALL_LOWER 3 /* name */ |
| |
| #define FULL_SIZ 0 |
| |
| #define MAX_MONTH_LEN 9 |
| #define MAX_MON_LEN 3 |
| #define MAX_DAY_LEN 9 |
| #define MAX_DY_LEN 3 |
| #define MAX_RM_LEN 4 |
| |
| #define TH_UPPER 1 |
| #define TH_LOWER 2 |
| |
| |
| /* ---------- |
| * Number description struct |
| * ---------- |
| */ |
| typedef struct |
| { |
| int pre, /* (count) numbers before decimal */ |
| post, /* (count) numbers after decimal */ |
| lsign, /* want locales sign */ |
| flag, /* number parameters */ |
| pre_lsign_num, /* tmp value for lsign */ |
| multi, /* multiplier for 'V' */ |
| zero_start, /* position of first zero */ |
| zero_end, /* position of last zero */ |
| need_locale; /* needs it locale */ |
| } NUMDesc; |
| |
| /* ---------- |
| * Flags for NUMBER version |
| * ---------- |
| */ |
| #define NUM_F_DECIMAL (1 << 1) |
| #define NUM_F_LDECIMAL (1 << 2) |
| #define NUM_F_ZERO (1 << 3) |
| #define NUM_F_BLANK (1 << 4) |
| #define NUM_F_FILLMODE (1 << 5) |
| #define NUM_F_LSIGN (1 << 6) |
| #define NUM_F_BRACKET (1 << 7) |
| #define NUM_F_MINUS (1 << 8) |
| #define NUM_F_PLUS (1 << 9) |
| #define NUM_F_ROMAN (1 << 10) |
| #define NUM_F_MULTI (1 << 11) |
| #define NUM_F_PLUS_POST (1 << 12) |
| #define NUM_F_MINUS_POST (1 << 13) |
| |
| #define NUM_LSIGN_PRE (-1) |
| #define NUM_LSIGN_POST 1 |
| #define NUM_LSIGN_NONE 0 |
| |
| /* ---------- |
| * Tests |
| * ---------- |
| */ |
| #define IS_DECIMAL(_f) ((_f)->flag & NUM_F_DECIMAL) |
| #define IS_LDECIMAL(_f) ((_f)->flag & NUM_F_LDECIMAL) |
| #define IS_ZERO(_f) ((_f)->flag & NUM_F_ZERO) |
| #define IS_BLANK(_f) ((_f)->flag & NUM_F_BLANK) |
| #define IS_FILLMODE(_f) ((_f)->flag & NUM_F_FILLMODE) |
| #define IS_BRACKET(_f) ((_f)->flag & NUM_F_BRACKET) |
| #define IS_MINUS(_f) ((_f)->flag & NUM_F_MINUS) |
| #define IS_LSIGN(_f) ((_f)->flag & NUM_F_LSIGN) |
| #define IS_PLUS(_f) ((_f)->flag & NUM_F_PLUS) |
| #define IS_ROMAN(_f) ((_f)->flag & NUM_F_ROMAN) |
| #define IS_MULTI(_f) ((_f)->flag & NUM_F_MULTI) |
| |
| /* ---------- |
| * Format picture cache |
| * (cache size: |
| * Number part = NUM_CACHE_SIZE * NUM_CACHE_FIELDS |
| * Date-time part = DCH_CACHE_SIZE * DCH_CACHE_FIELDS |
| * ) |
| * ---------- |
| */ |
| #define NUM_CACHE_SIZE 64 |
| #define NUM_CACHE_FIELDS 16 |
| #define DCH_CACHE_SIZE 128 |
| #define DCH_CACHE_FIELDS 16 |
| |
| typedef struct |
| { |
| FormatNode format[DCH_CACHE_SIZE + 1]; |
| char str[DCH_CACHE_SIZE + 1]; |
| int age; |
| } DCHCacheEntry; |
| |
| typedef struct |
| { |
| FormatNode format[NUM_CACHE_SIZE + 1]; |
| char str[NUM_CACHE_SIZE + 1]; |
| int age; |
| NUMDesc Num; |
| } NUMCacheEntry; |
| |
| /* global cache for --- date/time part */ |
| static DCHCacheEntry DCHCache[DCH_CACHE_FIELDS + 1]; |
| |
| static int n_DCHCache = 0; /* number of entries */ |
| static int DCHCounter = 0; |
| |
| /* global cache for --- number part */ |
| static NUMCacheEntry NUMCache[NUM_CACHE_FIELDS + 1]; |
| |
| static int n_NUMCache = 0; /* number of entries */ |
| static int NUMCounter = 0; |
| static NUMCacheEntry *last_NUMCacheEntry = NUMCache + 0; |
| |
| #define MAX_INT32 (2147483600) |
| |
| /* ---------- |
| * For char->date/time conversion |
| * ---------- |
| */ |
| typedef struct |
| { |
| FromCharDateMode mode; |
| int hh, |
| pm, |
| mi, |
| ss, |
| ssss, |
| d, |
| dd, |
| ddd, |
| mm, |
| ms, |
| year, |
| bc, |
| ww, |
| w, |
| cc, |
| j, |
| us, |
| yysz, /* is it YY or YYYY ? */ |
| clock; /* 12 or 24 hour clock? */ |
| } TmFromChar; |
| |
| #define ZERO_tmfc(_X) memset(_X, 0, sizeof(TmFromChar)) |
| |
| /* ---------- |
| * Debug |
| * ---------- |
| */ |
| #ifdef DEBUG_TO_FROM_CHAR |
| #define DEBUG_TMFC(_X) \ |
| elog(DEBUG_elog_output, "TMFC:\nmode %d\nhh %d\npm %d\nmi %d\nss %d\nssss %d\nd %d\ndd %d\nddd %d\nmm %d\nms: %d\nyear %d\nbc %d\nww %d\nw %d\ncc %d\nj %d\nus: %d\nyysz: %d\nclock: %d", \ |
| (_X)->mode, (_X)->hh, (_X)->pm, (_X)->mi, (_X)->ss, (_X)->ssss, \ |
| (_X)->d, (_X)->dd, (_X)->ddd, (_X)->mm, (_X)->ms, (_X)->year, \ |
| (_X)->bc, (_X)->ww, (_X)->w, (_X)->cc, (_X)->j, (_X)->us, \ |
| (_X)->yysz, (_X)->clock); |
| #define DEBUG_TM(_X) \ |
| elog(DEBUG_elog_output, "TM:\nsec %d\nyear %d\nmin %d\nwday %d\nhour %d\nyday %d\nmday %d\nnisdst %d\nmon %d\n",\ |
| (_X)->tm_sec, (_X)->tm_year,\ |
| (_X)->tm_min, (_X)->tm_wday, (_X)->tm_hour, (_X)->tm_yday,\ |
| (_X)->tm_mday, (_X)->tm_isdst, (_X)->tm_mon) |
| #else |
| #define DEBUG_TMFC(_X) |
| #define DEBUG_TM(_X) |
| #endif |
| |
| /* ---------- |
| * Datetime to char conversion |
| * ---------- |
| */ |
| typedef struct TmToChar |
| { |
| struct pg_tm tm; /* classic 'tm' struct */ |
| fsec_t fsec; /* fractional seconds */ |
| char *tzn; /* timezone */ |
| } TmToChar; |
| |
| #define tmtcTm(_X) (&(_X)->tm) |
| #define tmtcTzn(_X) ((_X)->tzn) |
| #define tmtcFsec(_X) ((_X)->fsec) |
| |
| #define ZERO_tm(_X) \ |
| do { \ |
| (_X)->tm_sec = (_X)->tm_year = (_X)->tm_min = (_X)->tm_wday = \ |
| (_X)->tm_hour = (_X)->tm_yday = (_X)->tm_isdst = 0; \ |
| (_X)->tm_mday = (_X)->tm_mon = 1; \ |
| } while(0) |
| |
| #define ZERO_tmtc(_X) \ |
| do { \ |
| ZERO_tm( tmtcTm(_X) ); \ |
| tmtcFsec(_X) = 0; \ |
| tmtcTzn(_X) = NULL; \ |
| } while(0) |
| |
| /* |
| * to_char(time) appears to to_char() as an interval, so this check |
| * is really for interval and time data types. |
| */ |
| #define INVALID_FOR_INTERVAL \ |
| do { \ |
| if (is_interval) \ |
| ereport(ERROR, \ |
| (errcode(ERRCODE_INVALID_DATETIME_FORMAT), \ |
| errmsg("invalid format specification for an interval value"), \ |
| errhint("Intervals are not tied to specific calendar dates."))); \ |
| } while(0) |
| |
| /***************************************************************************** |
| * KeyWord definitions |
| *****************************************************************************/ |
| |
| /* ---------- |
| * Suffixes: |
| * ---------- |
| */ |
| #define DCH_S_FM 0x01 |
| #define DCH_S_TH 0x02 |
| #define DCH_S_th 0x04 |
| #define DCH_S_SP 0x08 |
| #define DCH_S_TM 0x10 |
| |
| /* ---------- |
| * Suffix tests |
| * ---------- |
| */ |
| #define S_THth(_s) ((((_s) & DCH_S_TH) || ((_s) & DCH_S_th)) ? 1 : 0) |
| #define S_TH(_s) (((_s) & DCH_S_TH) ? 1 : 0) |
| #define S_th(_s) (((_s) & DCH_S_th) ? 1 : 0) |
| #define S_TH_TYPE(_s) (((_s) & DCH_S_TH) ? TH_UPPER : TH_LOWER) |
| |
| #define S_FM(_s) (((_s) & DCH_S_FM) ? 1 : 0) |
| #define S_SP(_s) (((_s) & DCH_S_SP) ? 1 : 0) |
| #define S_TM(_s) (((_s) & DCH_S_TM) ? 1 : 0) |
| |
| /* ---------- |
| * Suffixes definition for DATE-TIME TO/FROM CHAR |
| * ---------- |
| */ |
| static KeySuffix DCH_suff[] = { |
| {"FM", 2, DCH_S_FM, SUFFTYPE_PREFIX}, |
| {"fm", 2, DCH_S_FM, SUFFTYPE_PREFIX}, |
| {"TM", 2, DCH_S_TM, SUFFTYPE_PREFIX}, |
| {"tm", 2, DCH_S_TM, SUFFTYPE_PREFIX}, |
| {"TH", 2, DCH_S_TH, SUFFTYPE_POSTFIX}, |
| {"th", 2, DCH_S_th, SUFFTYPE_POSTFIX}, |
| {"SP", 2, DCH_S_SP, SUFFTYPE_POSTFIX}, |
| /* last */ |
| {NULL, 0, 0, 0} |
| }; |
| |
| /* ---------- |
| * 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 |
| * |
| * ---------- |
| */ |
| |
| typedef enum |
| { |
| DCH_A_D, |
| DCH_A_M, |
| DCH_AD, |
| DCH_AM, |
| DCH_B_C, |
| DCH_BC, |
| DCH_CC, |
| DCH_DAY, |
| DCH_DDD, |
| DCH_DD, |
| DCH_DY, |
| DCH_Day, |
| DCH_Dy, |
| DCH_D, |
| DCH_FX, /* global suffix */ |
| DCH_HH24, |
| DCH_HH12, |
| DCH_HH, |
| DCH_IDDD, |
| DCH_ID, |
| DCH_IW, |
| DCH_IYYY, |
| DCH_IYY, |
| DCH_IY, |
| DCH_I, |
| DCH_J, |
| DCH_MI, |
| DCH_MM, |
| DCH_MONTH, |
| DCH_MON, |
| DCH_MS, |
| DCH_Month, |
| DCH_Mon, |
| DCH_P_M, |
| DCH_PM, |
| DCH_Q, |
| DCH_RM, |
| DCH_SSSS, |
| DCH_SS, |
| DCH_TZ, |
| DCH_US, |
| DCH_WW, |
| DCH_W, |
| DCH_Y_YYY, |
| DCH_YYYY, |
| DCH_YYY, |
| DCH_YY, |
| DCH_Y, |
| DCH_a_d, |
| DCH_a_m, |
| DCH_ad, |
| DCH_am, |
| DCH_b_c, |
| DCH_bc, |
| DCH_cc, |
| DCH_day, |
| DCH_ddd, |
| DCH_dd, |
| DCH_dy, |
| DCH_d, |
| DCH_fx, |
| DCH_hh24, |
| DCH_hh12, |
| DCH_hh, |
| DCH_iddd, |
| DCH_id, |
| DCH_iw, |
| DCH_iyyy, |
| DCH_iyy, |
| DCH_iy, |
| DCH_i, |
| DCH_j, |
| DCH_mi, |
| DCH_mm, |
| DCH_month, |
| DCH_mon, |
| DCH_ms, |
| DCH_p_m, |
| DCH_pm, |
| DCH_q, |
| DCH_rm, |
| DCH_ssss, |
| DCH_ss, |
| DCH_tz, |
| DCH_us, |
| DCH_ww, |
| DCH_w, |
| DCH_y_yyy, |
| DCH_yyyy, |
| DCH_yyy, |
| DCH_yy, |
| DCH_y, |
| |
| /* last */ |
| _DCH_last_ |
| } DCH_poz; |
| |
| typedef enum |
| { |
| NUM_COMMA, |
| NUM_DEC, |
| NUM_0, |
| NUM_9, |
| NUM_B, |
| NUM_C, |
| NUM_D, |
| NUM_E, |
| NUM_FM, |
| NUM_G, |
| NUM_L, |
| NUM_MI, |
| NUM_PL, |
| NUM_PR, |
| NUM_RN, |
| NUM_SG, |
| NUM_SP, |
| NUM_S, |
| NUM_TH, |
| NUM_V, |
| NUM_b, |
| NUM_c, |
| NUM_d, |
| NUM_e, |
| NUM_fm, |
| NUM_g, |
| NUM_l, |
| NUM_mi, |
| NUM_pl, |
| NUM_pr, |
| NUM_rn, |
| NUM_sg, |
| NUM_sp, |
| NUM_s, |
| NUM_th, |
| NUM_v, |
| |
| /* last */ |
| _NUM_last_ |
| } NUM_poz; |
| |
| /* ---------- |
| * KeyWords for DATE-TIME version |
| * ---------- |
| */ |
| static const KeyWord DCH_keywords[] = { |
| /* name, len, id, is_digit, date_mode */ |
| {"A.D.", 4, DCH_A_D, FALSE, FROM_CHAR_DATE_NONE}, /* A */ |
| {"A.M.", 4, DCH_A_M, FALSE, FROM_CHAR_DATE_NONE}, |
| {"AD", 2, DCH_AD, FALSE, FROM_CHAR_DATE_NONE}, |
| {"AM", 2, DCH_AM, FALSE, FROM_CHAR_DATE_NONE}, |
| {"B.C.", 4, DCH_B_C, FALSE, FROM_CHAR_DATE_NONE}, /* B */ |
| {"BC", 2, DCH_BC, FALSE, FROM_CHAR_DATE_NONE}, |
| {"CC", 2, DCH_CC, TRUE, FROM_CHAR_DATE_NONE}, /* C */ |
| {"DAY", 3, DCH_DAY, FALSE, FROM_CHAR_DATE_NONE}, /* D */ |
| {"DDD", 3, DCH_DDD, TRUE, FROM_CHAR_DATE_GREGORIAN}, |
| {"DD", 2, DCH_DD, TRUE, FROM_CHAR_DATE_GREGORIAN}, |
| {"DY", 2, DCH_DY, FALSE, FROM_CHAR_DATE_NONE}, |
| {"Day", 3, DCH_Day, FALSE, FROM_CHAR_DATE_NONE}, |
| {"Dy", 2, DCH_Dy, FALSE, FROM_CHAR_DATE_NONE}, |
| {"D", 1, DCH_D, TRUE, FROM_CHAR_DATE_GREGORIAN}, |
| {"FX", 2, DCH_FX, FALSE, FROM_CHAR_DATE_NONE}, /* F */ |
| {"HH24", 4, DCH_HH24, TRUE, FROM_CHAR_DATE_NONE}, /* H */ |
| {"HH12", 4, DCH_HH12, TRUE, FROM_CHAR_DATE_NONE}, |
| {"HH", 2, DCH_HH, TRUE, FROM_CHAR_DATE_NONE}, |
| {"IDDD", 4, DCH_IDDD, TRUE, FROM_CHAR_DATE_ISOWEEK}, /* I */ |
| {"ID", 2, DCH_ID, TRUE, FROM_CHAR_DATE_ISOWEEK}, |
| {"IW", 2, DCH_IW, TRUE, FROM_CHAR_DATE_ISOWEEK}, |
| {"IYYY", 4, DCH_IYYY, TRUE, FROM_CHAR_DATE_ISOWEEK}, |
| {"IYY", 3, DCH_IYY, TRUE, FROM_CHAR_DATE_ISOWEEK}, |
| {"IY", 2, DCH_IY, TRUE, FROM_CHAR_DATE_ISOWEEK}, |
| {"I", 1, DCH_I, TRUE, FROM_CHAR_DATE_ISOWEEK}, |
| {"J", 1, DCH_J, TRUE, FROM_CHAR_DATE_NONE}, /* J */ |
| {"MI", 2, DCH_MI, TRUE, FROM_CHAR_DATE_NONE}, /* M */ |
| {"MM", 2, DCH_MM, TRUE, FROM_CHAR_DATE_GREGORIAN}, |
| {"MONTH", 5, DCH_MONTH, FALSE, FROM_CHAR_DATE_GREGORIAN}, |
| {"MON", 3, DCH_MON, FALSE, FROM_CHAR_DATE_GREGORIAN}, |
| {"MS", 2, DCH_MS, TRUE, FROM_CHAR_DATE_NONE}, |
| {"Month", 5, DCH_Month, FALSE, FROM_CHAR_DATE_GREGORIAN}, |
| {"Mon", 3, DCH_Mon, FALSE, FROM_CHAR_DATE_GREGORIAN}, |
| {"P.M.", 4, DCH_P_M, FALSE, FROM_CHAR_DATE_NONE}, /* P */ |
| {"PM", 2, DCH_PM, FALSE, FROM_CHAR_DATE_NONE}, |
| {"Q", 1, DCH_Q, TRUE, FROM_CHAR_DATE_NONE}, /* Q */ |
| {"RM", 2, DCH_RM, FALSE, FROM_CHAR_DATE_GREGORIAN}, /* R */ |
| {"SSSS", 4, DCH_SSSS, TRUE, FROM_CHAR_DATE_NONE}, /* S */ |
| {"SS", 2, DCH_SS, TRUE, FROM_CHAR_DATE_NONE}, |
| {"TZ", 2, DCH_TZ, FALSE, FROM_CHAR_DATE_NONE}, /* T */ |
| {"US", 2, DCH_US, TRUE, FROM_CHAR_DATE_NONE}, /* U */ |
| {"WW", 2, DCH_WW, TRUE, FROM_CHAR_DATE_GREGORIAN}, /* W */ |
| {"W", 1, DCH_W, TRUE, FROM_CHAR_DATE_GREGORIAN}, |
| {"Y,YYY", 5, DCH_Y_YYY, TRUE, FROM_CHAR_DATE_GREGORIAN},/* Y */ |
| {"YYYY", 4, DCH_YYYY, TRUE, FROM_CHAR_DATE_GREGORIAN}, |
| {"YYY", 3, DCH_YYY, TRUE, FROM_CHAR_DATE_GREGORIAN}, |
| {"YY", 2, DCH_YY, TRUE, FROM_CHAR_DATE_GREGORIAN}, |
| {"Y", 1, DCH_Y, TRUE, FROM_CHAR_DATE_GREGORIAN}, |
| {"a.d.", 4, DCH_a_d, FALSE, FROM_CHAR_DATE_NONE}, /* a */ |
| {"a.m.", 4, DCH_a_m, FALSE, FROM_CHAR_DATE_NONE}, |
| {"ad", 2, DCH_ad, FALSE, FROM_CHAR_DATE_NONE}, |
| {"am", 2, DCH_am, FALSE, FROM_CHAR_DATE_NONE}, |
| {"b.c.", 4, DCH_b_c, FALSE, FROM_CHAR_DATE_NONE}, /* b */ |
| {"bc", 2, DCH_bc, FALSE, FROM_CHAR_DATE_NONE}, |
| {"cc", 2, DCH_CC, TRUE, FROM_CHAR_DATE_NONE}, /* c */ |
| {"day", 3, DCH_day, FALSE, FROM_CHAR_DATE_NONE}, /* d */ |
| {"ddd", 3, DCH_DDD, TRUE, FROM_CHAR_DATE_GREGORIAN}, |
| {"dd", 2, DCH_DD, TRUE, FROM_CHAR_DATE_GREGORIAN}, |
| {"dy", 2, DCH_dy, FALSE, FROM_CHAR_DATE_NONE}, |
| {"d", 1, DCH_D, TRUE, FROM_CHAR_DATE_GREGORIAN}, |
| {"fx", 2, DCH_FX, FALSE, FROM_CHAR_DATE_NONE}, /* f */ |
| {"hh24", 4, DCH_HH24, TRUE, FROM_CHAR_DATE_NONE}, /* h */ |
| {"hh12", 4, DCH_HH12, TRUE, FROM_CHAR_DATE_NONE}, |
| {"hh", 2, DCH_HH, TRUE, FROM_CHAR_DATE_NONE}, |
| {"iddd", 4, DCH_IDDD, TRUE, FROM_CHAR_DATE_ISOWEEK}, /* i */ |
| {"id", 2, DCH_ID, TRUE, FROM_CHAR_DATE_ISOWEEK}, |
| {"iw", 2, DCH_IW, TRUE, FROM_CHAR_DATE_ISOWEEK}, |
| {"iyyy", 4, DCH_IYYY, TRUE, FROM_CHAR_DATE_ISOWEEK}, |
| {"iyy", 3, DCH_IYY, TRUE, FROM_CHAR_DATE_ISOWEEK}, |
| {"iy", 2, DCH_IY, TRUE, FROM_CHAR_DATE_ISOWEEK}, |
| {"i", 1, DCH_I, TRUE, FROM_CHAR_DATE_ISOWEEK}, |
| {"j", 1, DCH_J, TRUE, FROM_CHAR_DATE_NONE}, /* j */ |
| {"mi", 2, DCH_MI, TRUE, FROM_CHAR_DATE_NONE}, /* m */ |
| {"mm", 2, DCH_MM, TRUE, FROM_CHAR_DATE_GREGORIAN}, |
| {"month", 5, DCH_month, FALSE, FROM_CHAR_DATE_GREGORIAN}, |
| {"mon", 3, DCH_mon, FALSE, FROM_CHAR_DATE_GREGORIAN}, |
| {"ms", 2, DCH_MS, TRUE, FROM_CHAR_DATE_NONE}, |
| {"p.m.", 4, DCH_p_m, FALSE, FROM_CHAR_DATE_NONE}, /* p */ |
| {"pm", 2, DCH_pm, FALSE, FROM_CHAR_DATE_NONE}, |
| {"q", 1, DCH_Q, TRUE, FROM_CHAR_DATE_NONE}, /* q */ |
| {"rm", 2, DCH_rm, FALSE, FROM_CHAR_DATE_GREGORIAN}, /* r */ |
| {"ssss", 4, DCH_SSSS, TRUE, FROM_CHAR_DATE_NONE}, /* s */ |
| {"ss", 2, DCH_SS, TRUE, FROM_CHAR_DATE_NONE}, |
| {"tz", 2, DCH_tz, FALSE, FROM_CHAR_DATE_NONE}, /* t */ |
| {"us", 2, DCH_US, TRUE, FROM_CHAR_DATE_NONE}, /* u */ |
| {"ww", 2, DCH_WW, TRUE, FROM_CHAR_DATE_GREGORIAN}, /* w */ |
| {"w", 1, DCH_W, TRUE, FROM_CHAR_DATE_GREGORIAN}, |
| {"y,yyy", 5, DCH_Y_YYY, TRUE, FROM_CHAR_DATE_GREGORIAN},/* y */ |
| {"yyyy", 4, DCH_YYYY, TRUE, FROM_CHAR_DATE_GREGORIAN}, |
| {"yyy", 3, DCH_YYY, TRUE, FROM_CHAR_DATE_GREGORIAN}, |
| {"yy", 2, DCH_YY, TRUE, FROM_CHAR_DATE_GREGORIAN}, |
| {"y", 1, DCH_Y, TRUE, FROM_CHAR_DATE_GREGORIAN}, |
| |
| /* last */ |
| {NULL, 0, 0, 0, 0} |
| }; |
| |
| /* ---------- |
| * KeyWords for NUMBER version |
| * |
| * The is_digit and date_mode fields are not relevant here. |
| * ---------- |
| */ |
| static const KeyWord NUM_keywords[] = { |
| /* name, len, id is in Index */ |
| {",", 1, NUM_COMMA}, /* , */ |
| {".", 1, NUM_DEC}, /* . */ |
| {"0", 1, NUM_0}, /* 0 */ |
| {"9", 1, NUM_9}, /* 9 */ |
| {"B", 1, NUM_B}, /* B */ |
| {"C", 1, NUM_C}, /* C */ |
| {"D", 1, NUM_D}, /* D */ |
| {"E", 1, NUM_E}, /* E */ |
| {"FM", 2, NUM_FM}, /* F */ |
| {"G", 1, NUM_G}, /* G */ |
| {"L", 1, NUM_L}, /* L */ |
| {"MI", 2, NUM_MI}, /* M */ |
| {"PL", 2, NUM_PL}, /* P */ |
| {"PR", 2, NUM_PR}, |
| {"RN", 2, NUM_RN}, /* R */ |
| {"SG", 2, NUM_SG}, /* S */ |
| {"SP", 2, NUM_SP}, |
| {"S", 1, NUM_S}, |
| {"TH", 2, NUM_TH}, /* T */ |
| {"V", 1, NUM_V}, /* V */ |
| {"b", 1, NUM_B}, /* b */ |
| {"c", 1, NUM_C}, /* c */ |
| {"d", 1, NUM_D}, /* d */ |
| {"e", 1, NUM_E}, /* e */ |
| {"fm", 2, NUM_FM}, /* f */ |
| {"g", 1, NUM_G}, /* g */ |
| {"l", 1, NUM_L}, /* l */ |
| {"mi", 2, NUM_MI}, /* m */ |
| {"pl", 2, NUM_PL}, /* p */ |
| {"pr", 2, NUM_PR}, |
| {"rn", 2, NUM_rn}, /* r */ |
| {"sg", 2, NUM_SG}, /* s */ |
| {"sp", 2, NUM_SP}, |
| {"s", 1, NUM_S}, |
| {"th", 2, NUM_th}, /* t */ |
| {"v", 1, NUM_V}, /* v */ |
| |
| /* last */ |
| {NULL, 0, 0} |
| }; |
| |
| |
| /* ---------- |
| * KeyWords index for DATE-TIME version |
| * ---------- |
| */ |
| static const int DCH_index[KeyWord_INDEX_SIZE] = { |
| /* |
| 0 1 2 3 4 5 6 7 8 9 |
| */ |
| /*---- first 0..31 chars are skipped ----*/ |
| |
| -1, -1, -1, -1, -1, -1, -1, -1, |
| -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
| -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
| -1, -1, -1, -1, -1, DCH_A_D, DCH_B_C, DCH_CC, DCH_DAY, -1, |
| DCH_FX, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, -1, |
| DCH_P_M, DCH_Q, DCH_RM, DCH_SSSS, DCH_TZ, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY, |
| -1, -1, -1, -1, -1, -1, -1, DCH_a_d, DCH_b_c, DCH_cc, |
| DCH_day, -1, DCH_fx, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi, |
| -1, -1, DCH_p_m, DCH_q, DCH_rm, DCH_ssss, DCH_tz, DCH_us, -1, DCH_ww, |
| -1, DCH_y_yyy, -1, -1, -1, -1 |
| |
| /*---- chars over 126 are skipped ----*/ |
| }; |
| |
| /* ---------- |
| * KeyWords index for NUMBER version |
| * ---------- |
| */ |
| static const int NUM_index[KeyWord_INDEX_SIZE] = { |
| /* |
| 0 1 2 3 4 5 6 7 8 9 |
| */ |
| /*---- first 0..31 chars are skipped ----*/ |
| |
| -1, -1, -1, -1, -1, -1, -1, -1, |
| -1, -1, -1, -1, NUM_COMMA, -1, NUM_DEC, -1, NUM_0, -1, |
| -1, -1, -1, -1, -1, -1, -1, NUM_9, -1, -1, |
| -1, -1, -1, -1, -1, -1, NUM_B, NUM_C, NUM_D, NUM_E, |
| NUM_FM, NUM_G, -1, -1, -1, -1, NUM_L, NUM_MI, -1, -1, |
| NUM_PL, -1, NUM_RN, NUM_SG, NUM_TH, -1, NUM_V, -1, -1, -1, |
| -1, -1, -1, -1, -1, -1, -1, -1, NUM_b, NUM_c, |
| NUM_d, NUM_e, NUM_fm, NUM_g, -1, -1, -1, -1, NUM_l, NUM_mi, |
| -1, -1, NUM_pl, -1, NUM_rn, NUM_sg, NUM_th, -1, NUM_v, -1, |
| -1, -1, -1, -1, -1, -1 |
| |
| /*---- chars over 126 are skipped ----*/ |
| }; |
| |
| /* ---------- |
| * Number processor struct |
| * ---------- |
| */ |
| typedef struct NUMProc |
| { |
| bool is_to_char; |
| NUMDesc *Num; /* number description */ |
| |
| int sign, /* '-' or '+' */ |
| sign_wrote, /* was sign write */ |
| num_count, /* number of write digits */ |
| num_in, /* is inside number */ |
| num_curr, /* current position in number */ |
| num_pre, /* space before first number */ |
| |
| read_dec, /* to_number - was read dec. point */ |
| read_post, /* to_number - number of dec. digit */ |
| read_pre; /* to_number - number non-dec. digit */ |
| |
| char *number, /* string with number */ |
| *number_p, /* pointer to current number position */ |
| *inout, /* in / out buffer */ |
| *inout_p, /* pointer to current inout position */ |
| *last_relevant, /* last relevant number after decimal point */ |
| |
| *L_negative_sign, /* Locale */ |
| *L_positive_sign, |
| *decimal, |
| *L_thousands_sep, |
| *L_currency_symbol; |
| } NUMProc; |
| |
| |
| /* ---------- |
| * Functions |
| * ---------- |
| */ |
| static const KeyWord *index_seq_search(char *str, const KeyWord *kw, |
| const int *index); |
| static KeySuffix *suff_search(char *str, KeySuffix *suf, int type); |
| static void NUMDesc_prepare(NUMDesc *num, FormatNode *n, char *func); |
| static void parse_format(FormatNode *node, char *str, const KeyWord *kw, |
| KeySuffix *suf, const int *index, int ver, NUMDesc *Num, |
| char *func); |
| |
| static void DCH_to_char(FormatNode *node, bool is_interval, |
| TmToChar *in, char *out); |
| static void DCH_from_char(FormatNode *node, char *in, TmFromChar *out); |
| |
| #ifdef DEBUG_TO_FROM_CHAR |
| static void dump_index(const KeyWord *k, const int *index); |
| static void dump_node(FormatNode *node, int max); |
| #endif |
| |
| static char *get_th(char *num, int type); |
| static char *str_numth(char *dest, char *num, int type); |
| static int strspace_len(char *str); |
| static int strdigits_len(char *str); |
| static void from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode); |
| static void from_char_set_int(int *dest, const int value, const FormatNode *node); |
| static int from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node); |
| static int from_char_parse_int(int *dest, char **src, FormatNode *node); |
| static int seq_search(char *name, char **array, int type, int max, int *len); |
| static int from_char_seq_search(int *dest, char **src, char **array, int type, int max, FormatNode *node); |
| static void do_to_timestamp(text *date_txt, text *fmt, |
| struct pg_tm * tm, fsec_t *fsec); |
| static char *fill_str(char *str, int c, int max); |
| static FormatNode *NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree); |
| static char *int_to_roman(int number); |
| static void NUM_prepare_locale(NUMProc *Np); |
| static char *get_last_relevant_decnum(char *num); |
| static void NUM_numpart_from_char(NUMProc *Np, int id, int plen); |
| static void NUM_numpart_to_char(NUMProc *Np, int id); |
| static char *NUM_processor(FormatNode *node, NUMDesc *Num, char *inout, char *number, |
| int plen, int sign, bool is_to_char); |
| static DCHCacheEntry *DCH_cache_search(char *str); |
| static DCHCacheEntry *DCH_cache_getnew(char *str); |
| |
| static NUMCacheEntry *NUM_cache_search(char *str); |
| static NUMCacheEntry *NUM_cache_getnew(char *str); |
| static void NUM_cache_remove(NUMCacheEntry *ent); |
| |
| |
| /* ---------- |
| * 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) |
| * ---------- |
| */ |
| static const KeyWord * |
| index_seq_search(char *str, const KeyWord *kw, const int *index) |
| { |
| int poz; |
| |
| if (!KeyWord_INDEX_FILTER(*str)) |
| return NULL; |
| |
| if ((poz = *(index + (*str - ' '))) > -1) |
| { |
| const KeyWord *k = kw + poz; |
| |
| do |
| { |
| if (!strncmp(str, k->name, k->len)) |
| return k; |
| k++; |
| if (!k->name) |
| return NULL; |
| } while (*str == *k->name); |
| } |
| return NULL; |
| } |
| |
| static KeySuffix * |
| suff_search(char *str, KeySuffix *suf, int type) |
| { |
| KeySuffix *s; |
| |
| for (s = suf; s->name != NULL; s++) |
| { |
| if (s->type != type) |
| continue; |
| |
| if (!strncmp(str, s->name, s->len)) |
| return s; |
| } |
| return NULL; |
| } |
| |
| /* ---------- |
| * Prepare NUMDesc (number description struct) via FormatNode struct |
| * ---------- |
| */ |
| static void |
| NUMDesc_prepare(NUMDesc *num, FormatNode *n, char *func) |
| { |
| if (n->type != NODE_TYPE_ACTION) |
| return; |
| |
| switch (n->key->id) |
| { |
| case NUM_9: |
| if (IS_BRACKET(num)) |
| { |
| NUM_cache_remove(last_NUMCacheEntry); |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("\"9\" must be ahead of \"PR\" for function \"%s\"", func))); |
| } |
| if (IS_MULTI(num)) |
| { |
| ++num->multi; |
| break; |
| } |
| if (IS_DECIMAL(num)) |
| ++num->post; |
| else |
| ++num->pre; |
| break; |
| |
| case NUM_0: |
| if (IS_BRACKET(num)) |
| { |
| NUM_cache_remove(last_NUMCacheEntry); |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("\"0\" must be ahead of \"PR\" for function \"%s\"", func))); |
| } |
| if (!IS_ZERO(num) && !IS_DECIMAL(num)) |
| { |
| num->flag |= NUM_F_ZERO; |
| num->zero_start = num->pre + 1; |
| } |
| if (!IS_DECIMAL(num)) |
| ++num->pre; |
| else |
| ++num->post; |
| |
| num->zero_end = num->pre + num->post; |
| break; |
| |
| case NUM_B: |
| if (num->pre == 0 && num->post == 0 && (!IS_ZERO(num))) |
| num->flag |= NUM_F_BLANK; |
| break; |
| |
| case NUM_D: |
| num->flag |= NUM_F_LDECIMAL; |
| num->need_locale = TRUE; |
| case NUM_DEC: |
| if (IS_DECIMAL(num)) |
| { |
| NUM_cache_remove(last_NUMCacheEntry); |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("multiple decimal points for function \"%s\"", func))); |
| } |
| if (IS_MULTI(num)) |
| { |
| NUM_cache_remove(last_NUMCacheEntry); |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("cannot use \"V\" and decimal point together for function \"%s\"", func))); |
| } |
| num->flag |= NUM_F_DECIMAL; |
| break; |
| |
| case NUM_FM: |
| num->flag |= NUM_F_FILLMODE; |
| break; |
| |
| case NUM_S: |
| if (IS_LSIGN(num)) |
| { |
| NUM_cache_remove(last_NUMCacheEntry); |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("cannot use \"S\" twice for function \"%s\"", func))); |
| } |
| if (IS_PLUS(num) || IS_MINUS(num) || IS_BRACKET(num)) |
| { |
| NUM_cache_remove(last_NUMCacheEntry); |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("cannot use \"S\" and \"PL\"/\"MI\"/\"SG\"/\"PR\" together for function \"%s\"", func))); |
| } |
| if (!IS_DECIMAL(num)) |
| { |
| num->lsign = NUM_LSIGN_PRE; |
| num->pre_lsign_num = num->pre; |
| num->need_locale = TRUE; |
| num->flag |= NUM_F_LSIGN; |
| } |
| else if (num->lsign == NUM_LSIGN_NONE) |
| { |
| num->lsign = NUM_LSIGN_POST; |
| num->need_locale = TRUE; |
| num->flag |= NUM_F_LSIGN; |
| } |
| break; |
| |
| case NUM_MI: |
| if (IS_LSIGN(num)) |
| { |
| NUM_cache_remove(last_NUMCacheEntry); |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("cannot use \"S\" and \"MI\" together for function \"%s\"", func))); |
| } |
| num->flag |= NUM_F_MINUS; |
| if (IS_DECIMAL(num)) |
| num->flag |= NUM_F_MINUS_POST; |
| break; |
| |
| case NUM_PL: |
| if (IS_LSIGN(num)) |
| { |
| NUM_cache_remove(last_NUMCacheEntry); |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("cannot use \"S\" and \"PL\" together for function \"%s\"", func))); |
| } |
| num->flag |= NUM_F_PLUS; |
| if (IS_DECIMAL(num)) |
| num->flag |= NUM_F_PLUS_POST; |
| break; |
| |
| case NUM_SG: |
| if (IS_LSIGN(num)) |
| { |
| NUM_cache_remove(last_NUMCacheEntry); |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("cannot use \"S\" and \"SG\" together for function \"%s\"", func))); |
| } |
| num->flag |= NUM_F_MINUS; |
| num->flag |= NUM_F_PLUS; |
| break; |
| |
| case NUM_PR: |
| if (IS_LSIGN(num) || IS_PLUS(num) || IS_MINUS(num)) |
| { |
| NUM_cache_remove(last_NUMCacheEntry); |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("cannot use \"PR\" and \"S\"/\"PL\"/\"MI\"/\"SG\" together for function \"%s\"", func))); |
| } |
| num->flag |= NUM_F_BRACKET; |
| break; |
| |
| case NUM_rn: |
| case NUM_RN: |
| num->flag |= NUM_F_ROMAN; |
| break; |
| |
| case NUM_L: |
| case NUM_G: |
| num->need_locale = TRUE; |
| break; |
| |
| case NUM_V: |
| if (IS_DECIMAL(num)) |
| { |
| NUM_cache_remove(last_NUMCacheEntry); |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("cannot use \"V\" and decimal point together for function \"%s\"", func))); |
| } |
| num->flag |= NUM_F_MULTI; |
| break; |
| |
| case NUM_E: |
| NUM_cache_remove(last_NUMCacheEntry); |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("\"E\" is not supported for function \"%s\"", func))); |
| } |
| |
| return; |
| } |
| |
| /* ---------- |
| * Format parser, search small keywords and keyword's suffixes, and make |
| * format-node tree. |
| * |
| * for DATE-TIME & NUMBER version |
| * ---------- |
| */ |
| static void |
| parse_format(FormatNode *node, char *str, const KeyWord *kw, |
| KeySuffix *suf, const int *index, int ver, NUMDesc *Num, |
| char *func) |
| { |
| KeySuffix *s; |
| FormatNode *n; |
| int node_set = 0, |
| suffix, |
| last = 0; |
| |
| #ifdef DEBUG_TO_FROM_CHAR |
| elog(DEBUG_elog_output, "to_char/number(): run parser"); |
| #endif |
| |
| n = node; |
| |
| while (*str) |
| { |
| suffix = 0; |
| |
| /* |
| * Prefix |
| */ |
| if (ver == DCH_TYPE && (s = suff_search(str, suf, SUFFTYPE_PREFIX)) != NULL) |
| { |
| suffix |= s->id; |
| if (s->len) |
| str += s->len; |
| } |
| |
| /* |
| * Keyword |
| */ |
| if (*str && (n->key = index_seq_search(str, kw, index)) != NULL) |
| { |
| n->type = NODE_TYPE_ACTION; |
| n->suffix = 0; |
| node_set = 1; |
| if (n->key->len) |
| str += n->key->len; |
| |
| /* |
| * NUM version: Prepare global NUMDesc struct |
| */ |
| if (ver == NUM_TYPE) |
| NUMDesc_prepare(Num, n, func); |
| |
| /* |
| * Postfix |
| */ |
| if (ver == DCH_TYPE && *str && (s = suff_search(str, suf, SUFFTYPE_POSTFIX)) != NULL) |
| { |
| suffix |= s->id; |
| if (s->len) |
| str += s->len; |
| } |
| } |
| else if (*str) |
| { |
| /* |
| * Special characters '\' and '"' |
| */ |
| if (*str == '"' && last != '\\') |
| { |
| int x = 0; |
| |
| while (*(++str)) |
| { |
| if (*str == '"' && x != '\\') |
| { |
| str++; |
| break; |
| } |
| else if (*str == '\\' && x != '\\') |
| { |
| x = '\\'; |
| continue; |
| } |
| n->type = NODE_TYPE_CHAR; |
| n->character = *str; |
| n->key = NULL; |
| n->suffix = 0; |
| ++n; |
| x = *str; |
| } |
| node_set = 0; |
| suffix = 0; |
| last = 0; |
| } |
| else if (*str && *str == '\\' && last != '\\' && *(str + 1) == '"') |
| { |
| last = *str; |
| str++; |
| } |
| else if (*str) |
| { |
| n->type = NODE_TYPE_CHAR; |
| n->character = *str; |
| n->key = NULL; |
| node_set = 1; |
| last = 0; |
| str++; |
| } |
| } |
| |
| /* end */ |
| if (node_set) |
| { |
| if (n->type == NODE_TYPE_ACTION) |
| n->suffix = suffix; |
| ++n; |
| |
| n->suffix = 0; |
| node_set = 0; |
| } |
| } |
| |
| n->type = NODE_TYPE_END; |
| n->suffix = 0; |
| return; |
| } |
| |
| /* ---------- |
| * DEBUG: Dump the FormatNode Tree (debug) |
| * ---------- |
| */ |
| #ifdef DEBUG_TO_FROM_CHAR |
| |
| #define DUMP_THth(_suf) (S_TH(_suf) ? "TH" : (S_th(_suf) ? "th" : " ")) |
| #define DUMP_FM(_suf) (S_FM(_suf) ? "FM" : " ") |
| |
| static void |
| dump_node(FormatNode *node, int max) |
| { |
| FormatNode *n; |
| int a; |
| |
| elog(DEBUG_elog_output, "to_from-char(): DUMP FORMAT"); |
| |
| for (a = 0, n = node; a <= max; n++, a++) |
| { |
| if (n->type == NODE_TYPE_ACTION) |
| elog(DEBUG_elog_output, "%d:\t NODE_TYPE_ACTION '%s'\t(%s,%s)", |
| a, n->key->name, DUMP_THth(n->suffix), DUMP_FM(n->suffix)); |
| else if (n->type == NODE_TYPE_CHAR) |
| elog(DEBUG_elog_output, "%d:\t NODE_TYPE_CHAR '%c'", a, n->character); |
| else if (n->type == NODE_TYPE_END) |
| { |
| elog(DEBUG_elog_output, "%d:\t NODE_TYPE_END", a); |
| return; |
| } |
| else |
| elog(DEBUG_elog_output, "%d:\t unknown NODE!", a); |
| } |
| } |
| #endif /* DEBUG */ |
| |
| /***************************************************************************** |
| * Private utils |
| *****************************************************************************/ |
| |
| /* ---------- |
| * Return ST/ND/RD/TH for simple (1..9) numbers |
| * type --> 0 upper, 1 lower |
| * ---------- |
| */ |
| static char * |
| get_th(char *num, int type) |
| { |
| int len = strlen(num), |
| last, |
| seclast; |
| |
| last = *(num + (len - 1)); |
| if (!isdigit((unsigned char) last)) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
| errmsg("\"%s\" is not a number", num))); |
| |
| /* |
| * 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) && ((seclast = num[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]; |
| } |
| return NULL; |
| } |
| |
| /* ---------- |
| * Convert string-number to ordinal string-number |
| * type --> 0 upper, 1 lower |
| * ---------- |
| */ |
| static char * |
| str_numth(char *dest, char *num, int type) |
| { |
| if (dest != num) |
| strcpy(dest, num); |
| strcat(dest, get_th(num, type)); |
| return dest; |
| } |
| |
| /* |
| * If the system provides the needed functions for wide-character manipulation |
| * (which are all standardized by C99), then we implement upper/lower/initcap |
| * using wide-character functions, if necessary. Otherwise we use the |
| * traditional <ctype.h> functions, which of course will not work as desired |
| * in multibyte character sets. Note that in either case we are effectively |
| * assuming that the database character encoding matches the encoding implied |
| * by LC_CTYPE. |
| */ |
| |
| /* |
| * wide-character-aware lower function |
| * |
| * We pass the number of bytes so we can pass varlena and char* |
| * to this function. The result is a palloc'd, null-terminated string. |
| */ |
| char * |
| str_tolower(const char *buff, size_t nbytes) |
| { |
| char *result; |
| |
| if (!buff) |
| return NULL; |
| |
| #ifdef USE_WIDE_UPPER_LOWER |
| if (pg_database_encoding_max_length() > 1 && !lc_ctype_is_c()) |
| { |
| wchar_t *workspace; |
| size_t curr_char; |
| size_t result_size; |
| |
| /* Overflow paranoia */ |
| if ((nbytes + 1) > (INT_MAX / sizeof(wchar_t))) |
| ereport(ERROR, |
| (errcode(ERRCODE_OUT_OF_MEMORY), |
| errmsg("out of memory"))); |
| |
| /* Output workspace cannot have more codes than input bytes */ |
| workspace = (wchar_t *) palloc((nbytes + 1) * sizeof(wchar_t)); |
| |
| char2wchar(workspace, nbytes + 1, buff, nbytes); |
| |
| for (curr_char = 0; workspace[curr_char] != 0; curr_char++) |
| workspace[curr_char] = towlower(workspace[curr_char]); |
| |
| /* Make result large enough; case change might change number of bytes */ |
| result_size = curr_char * pg_database_encoding_max_length() + 1; |
| result = palloc(result_size); |
| |
| wchar2char(result, workspace, result_size); |
| pfree(workspace); |
| } |
| else |
| #endif /* USE_WIDE_UPPER_LOWER */ |
| { |
| char *p; |
| |
| result = pnstrdup(buff, nbytes); |
| |
| for (p = result; *p; p++) |
| *p = pg_tolower((unsigned char) *p); |
| } |
| |
| return result; |
| } |
| |
| /* |
| * wide-character-aware upper function |
| * |
| * We pass the number of bytes so we can pass varlena and char* |
| * to this function. The result is a palloc'd, null-terminated string. |
| */ |
| char * |
| str_toupper(const char *buff, size_t nbytes) |
| { |
| char *result; |
| |
| if (!buff) |
| return NULL; |
| |
| #ifdef USE_WIDE_UPPER_LOWER |
| if (pg_database_encoding_max_length() > 1 && !lc_ctype_is_c()) |
| { |
| wchar_t *workspace; |
| size_t curr_char; |
| size_t result_size; |
| |
| /* Overflow paranoia */ |
| if ((nbytes + 1) > (INT_MAX / sizeof(wchar_t))) |
| ereport(ERROR, |
| (errcode(ERRCODE_OUT_OF_MEMORY), |
| errmsg("out of memory"))); |
| |
| /* Output workspace cannot have more codes than input bytes */ |
| workspace = (wchar_t *) palloc((nbytes + 1) * sizeof(wchar_t)); |
| |
| char2wchar(workspace, nbytes + 1, buff, nbytes); |
| |
| for (curr_char = 0; workspace[curr_char] != 0; curr_char++) |
| workspace[curr_char] = towupper(workspace[curr_char]); |
| |
| /* Make result large enough; case change might change number of bytes */ |
| result_size = curr_char * pg_database_encoding_max_length() + 1; |
| result = palloc(result_size); |
| |
| wchar2char(result, workspace, result_size); |
| pfree(workspace); |
| } |
| else |
| #endif /* USE_WIDE_UPPER_LOWER */ |
| { |
| char *p; |
| |
| result = pnstrdup(buff, nbytes); |
| |
| for (p = result; *p; p++) |
| *p = pg_toupper((unsigned char) *p); |
| } |
| |
| return result; |
| } |
| |
| /* |
| * wide-character-aware initcap function |
| * |
| * We pass the number of bytes so we can pass varlena and char* |
| * to this function. The result is a palloc'd, null-terminated string. |
| */ |
| char * |
| str_initcap(const char *buff, size_t nbytes) |
| { |
| char *result; |
| int wasalnum = false; |
| |
| if (!buff) |
| return NULL; |
| |
| #ifdef USE_WIDE_UPPER_LOWER |
| if (pg_database_encoding_max_length() > 1 && !lc_ctype_is_c()) |
| { |
| wchar_t *workspace; |
| size_t curr_char; |
| size_t result_size; |
| |
| /* Overflow paranoia */ |
| if ((nbytes + 1) > (INT_MAX / sizeof(wchar_t))) |
| ereport(ERROR, |
| (errcode(ERRCODE_OUT_OF_MEMORY), |
| errmsg("out of memory"))); |
| |
| /* Output workspace cannot have more codes than input bytes */ |
| workspace = (wchar_t *) palloc((nbytes + 1) * sizeof(wchar_t)); |
| |
| char2wchar(workspace, nbytes + 1, buff, nbytes); |
| |
| for (curr_char = 0; workspace[curr_char] != 0; curr_char++) |
| { |
| if (wasalnum) |
| workspace[curr_char] = towlower(workspace[curr_char]); |
| else |
| workspace[curr_char] = towupper(workspace[curr_char]); |
| wasalnum = iswalnum(workspace[curr_char]); |
| } |
| |
| /* Make result large enough; case change might change number of bytes */ |
| result_size = curr_char * pg_database_encoding_max_length() + 1; |
| result = palloc(result_size); |
| |
| wchar2char(result, workspace, result_size); |
| pfree(workspace); |
| } |
| else |
| #endif /* USE_WIDE_UPPER_LOWER */ |
| { |
| char *p; |
| |
| result = pnstrdup(buff, nbytes); |
| |
| for (p = result; *p; p++) |
| { |
| if (wasalnum) |
| *p = pg_tolower((unsigned char) *p); |
| else |
| *p = pg_toupper((unsigned char) *p); |
| wasalnum = isalnum((unsigned char) *p); |
| } |
| } |
| |
| return result; |
| } |
| |
| /* convenience routines for when the input is null-terminated */ |
| |
| static char * |
| str_tolower_z(const char *buff) |
| { |
| return str_tolower(buff, strlen(buff)); |
| } |
| |
| static char * |
| str_toupper_z(const char *buff) |
| { |
| return str_toupper(buff, strlen(buff)); |
| } |
| |
| static char * |
| str_initcap_z(const char *buff) |
| { |
| return str_initcap(buff, strlen(buff)); |
| } |
| |
| |
| /* ---------- |
| * Skip TM / th in FROM_CHAR |
| * ---------- |
| */ |
| #define SKIP_THth(_suf) (S_THth(_suf) ? 2 : 0) |
| |
| #ifdef DEBUG_TO_FROM_CHAR |
| /* ----------- |
| * DEBUG: Call for debug and for index checking; (Show ASCII char |
| * and defined keyword for each used position |
| * ---------- |
| */ |
| static void |
| dump_index(const KeyWord *k, const int *index) |
| { |
| int i, |
| count = 0, |
| free_i = 0; |
| |
| elog(DEBUG_elog_output, "TO-FROM_CHAR: Dump KeyWord Index:"); |
| |
| for (i = 0; i < KeyWord_INDEX_SIZE; i++) |
| { |
| if (index[i] != -1) |
| { |
| elog(DEBUG_elog_output, "\t%c: %s, ", i + 32, k[index[i]].name); |
| count++; |
| } |
| else |
| { |
| free_i++; |
| elog(DEBUG_elog_output, "\t(%d) %c %d", i, i + 32, index[i]); |
| } |
| } |
| elog(DEBUG_elog_output, "\n\t\tUsed positions: %d,\n\t\tFree positions: %d", |
| count, free_i); |
| } |
| #endif /* DEBUG */ |
| |
| /* ---------- |
| * Return TRUE if next format picture is not digit value |
| * ---------- |
| */ |
| static bool |
| is_next_separator(FormatNode *n) |
| { |
| if (n->type == NODE_TYPE_END) |
| return FALSE; |
| |
| if (n->type == NODE_TYPE_ACTION && S_THth(n->suffix)) |
| return TRUE; |
| |
| /* |
| * Next node |
| */ |
| n++; |
| |
| /* end of format string is treated like a non-digit separator */ |
| if (n->type == NODE_TYPE_END) |
| return TRUE; |
| |
| if (n->type == NODE_TYPE_ACTION) |
| { |
| if (n->key->is_digit) |
| return FALSE; |
| |
| return TRUE; |
| } |
| else if (isdigit((unsigned char) n->character)) |
| return FALSE; |
| |
| return TRUE; /* some non-digit input (separator) */ |
| } |
| |
| static int |
| strspace_len(char *str) |
| { |
| int len = 0; |
| |
| while (*str && isspace((unsigned char) *str)) |
| { |
| str++; |
| len++; |
| } |
| return len; |
| } |
| |
| static int |
| strdigits_len(char *str) |
| { |
| char *p = str; |
| int len; |
| |
| len = strspace_len(str); |
| p += len; |
| |
| while (*p && isdigit((unsigned char) *p) && len <= DCH_MAX_ITEM_SIZ) |
| { |
| len++; |
| p++; |
| } |
| return len; |
| } |
| |
| /* |
| * 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. |
| */ |
| static void |
| from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode) |
| { |
| if (mode != FROM_CHAR_DATE_NONE) |
| { |
| if (tmfc->mode == FROM_CHAR_DATE_NONE) |
| tmfc->mode = mode; |
| else if (tmfc->mode != mode) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_DATETIME_FORMAT), |
| errmsg("invalid combination of date conventions"), |
| errhint("Do not mix Gregorian and ISO week date " |
| "conventions in a formatting template."))); |
| } |
| } |
| |
| /* |
| * Set the integer pointed to by 'dest' to the given value. |
| * |
| * Puke if the destination integer has previously been set to some other |
| * non-zero value. |
| */ |
| static void |
| from_char_set_int(int *dest, const int value, const FormatNode *node) |
| { |
| if (*dest != 0 && *dest != value) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_DATETIME_FORMAT), |
| errmsg("conflicting values for \"%s\" field in formatting string", |
| node->key->name), |
| errdetail("This value contradicts a previous setting for " |
| "the same field type."))); |
| *dest = value; |
| } |
| |
| /* |
| * 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). |
| */ |
| static int |
| from_char_parse_int_len(int *dest, char **src, const int len, FormatNode *node) |
| { |
| long result; |
| char copy[DCH_MAX_ITEM_SIZ + 1]; |
| char *init = *src; |
| int used; |
| |
| /* |
| * Skip any whitespace before parsing the integer. |
| */ |
| *src += strspace_len(*src); |
| |
| Assert(len <= DCH_MAX_ITEM_SIZ); |
| used = (int) strlcpy(copy, *src, len + 1); |
| |
| if (S_FM(node->suffix) || is_next_separator(node)) |
| { |
| /* |
| * 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. |
| */ |
| errno = 0; |
| result = strtol(init, src, 10); |
| } |
| else |
| { |
| /* |
| * We need to pull exactly the number of characters given in 'len' out |
| * of the string, and convert those. |
| */ |
| char *last; |
| |
| if (used < len) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_DATETIME_FORMAT), |
| errmsg("source string too short for \"%s\" formatting field", |
| node->key->name), |
| errdetail("Field requires %d characters, but only %d " |
| "remain.", |
| len, used), |
| errhint("If your source string is not fixed-width, try " |
| "using the \"FM\" modifier."))); |
| |
| errno = 0; |
| result = strtol(copy, &last, 10); |
| used = last - copy; |
| |
| if (used > 0 && used < len) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_DATETIME_FORMAT), |
| errmsg("invalid value \"%s\" for \"%s\"", |
| copy, node->key->name), |
| errdetail("Field requires %d characters, but only %d " |
| "could be parsed.", len, used), |
| errhint("If your source string is not fixed-width, try " |
| "using the \"FM\" modifier."))); |
| |
| *src += used; |
| } |
| |
| if (*src == init) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_DATETIME_FORMAT), |
| errmsg("invalid value \"%s\" for \"%s\"", |
| copy, node->key->name), |
| errdetail("Value must be an integer."))); |
| |
| if (errno == ERANGE || result < INT_MIN || result > INT_MAX) |
| ereport(ERROR, |
| (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), |
| errmsg("value for \"%s\" in source string is out of range", |
| node->key->name), |
| errdetail("Value must be in the range %d to %d.", |
| INT_MIN, INT_MAX))); |
| |
| if (dest != NULL) |
| from_char_set_int(dest, (int) result, node); |
| return *src - init; |
| } |
| |
| /* |
| * 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 explictly. |
| */ |
| static int |
| from_char_parse_int(int *dest, char **src, FormatNode *node) |
| { |
| return from_char_parse_int_len(dest, src, node->key->len, node); |
| } |
| |
| /* ---------- |
| * Sequential search with to upper/lower conversion |
| * ---------- |
| */ |
| static int |
| seq_search(char *name, char **array, int type, int max, int *len) |
| { |
| char *p, |
| *n, |
| **a; |
| int last, |
| i; |
| |
| *len = 0; |
| |
| if (!*name) |
| return -1; |
| |
| /* set first char */ |
| if (type == ONE_UPPER || type == ALL_UPPER) |
| *name = pg_toupper((unsigned char) *name); |
| else if (type == ALL_LOWER) |
| *name = pg_tolower((unsigned char) *name); |
| |
| for (last = 0, a = array; *a != NULL; a++) |
| { |
| /* comperate first chars */ |
| if (*name != **a) |
| continue; |
| |
| for (i = 1, p = *a + 1, n = name + 1;; n++, p++, i++) |
| { |
| /* search fragment (max) only */ |
| if (max && i == max) |
| { |
| *len = i; |
| return a - array; |
| } |
| /* full size */ |
| if (*p == '\0') |
| { |
| *len = i; |
| return a - array; |
| } |
| /* Not found in array 'a' */ |
| if (*n == '\0') |
| break; |
| |
| /* |
| * Convert (but convert new chars only) |
| */ |
| if (i > last) |
| { |
| if (type == ONE_UPPER || type == ALL_LOWER) |
| *n = pg_tolower((unsigned char) *n); |
| else if (type == ALL_UPPER) |
| *n = pg_toupper((unsigned char) *n); |
| last = i; |
| } |
| |
| #ifdef DEBUG_TO_FROM_CHAR |
| elog(DEBUG_elog_output, "N: %c, P: %c, A: %s (%s)", |
| *n, *p, *a, name); |
| #endif |
| if (*n != *p) |
| break; |
| } |
| } |
| |
| return -1; |
| } |
| |
| /* |
| * 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. |
| */ |
| static int |
| from_char_seq_search(int *dest, char **src, char **array, int type, int max, |
| FormatNode *node) |
| { |
| int len; |
| |
| *dest = seq_search(*src, array, type, max, &len); |
| if (len <= 0) |
| { |
| char copy[DCH_MAX_ITEM_SIZ + 1]; |
| |
| Assert(max <= DCH_MAX_ITEM_SIZ); |
| strlcpy(copy, *src, max + 1); |
| |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_DATETIME_FORMAT), |
| errmsg("invalid value \"%s\" for \"%s\"", |
| copy, node->key->name), |
| errdetail("The given value did not match any of the allowed " |
| "values for this field."), |
| errOmitLocation(true))); |
| } |
| *src += len; |
| return len; |
| } |
| |
| /* ---------- |
| * Process a TmToChar struct as denoted by a list of FormatNodes. |
| * The formatted data is written to the string pointed to by 'out'. |
| * ---------- |
| */ |
| static void |
| DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out) |
| { |
| FormatNode *n; |
| char *s; |
| struct pg_tm *tm = &in->tm; |
| char buff[DCH_CACHE_SIZE]; |
| int i; |
| |
| /* cache localized days and months */ |
| cache_locale_time(); |
| |
| s = out; |
| for (n = node; n->type != NODE_TYPE_END; n++) |
| { |
| if (n->type != NODE_TYPE_ACTION) |
| { |
| *s = n->character; |
| s++; |
| continue; |
| } |
| |
| switch (n->key->id) |
| { |
| case DCH_A_M: |
| case DCH_P_M: |
| strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2) |
| ? P_M_STR : A_M_STR); |
| s += strlen(s); |
| break; |
| case DCH_AM: |
| case DCH_PM: |
| strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2) |
| ? PM_STR : AM_STR); |
| s += strlen(s); |
| break; |
| case DCH_a_m: |
| case DCH_p_m: |
| strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2) |
| ? p_m_STR : a_m_STR); |
| s += strlen(s); |
| break; |
| case DCH_am: |
| case DCH_pm: |
| strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2) |
| ? pm_STR : am_STR); |
| s += strlen(s); |
| break; |
| case DCH_HH: |
| case DCH_HH12: |
| sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2, |
| tm->tm_hour % (HOURS_PER_DAY / 2) == 0 ? 12 : |
| tm->tm_hour % (HOURS_PER_DAY / 2)); |
| if (S_THth(n->suffix)) |
| str_numth(s, s, 0); |
| s += strlen(s); |
| break; |
| case DCH_HH24: |
| sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2, tm->tm_hour); |
| if (S_THth(n->suffix)) |
| str_numth(s, s, S_TH_TYPE(n->suffix)); |
| s += strlen(s); |
| break; |
| case DCH_MI: |
| sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2, tm->tm_min); |
| if (S_THth(n->suffix)) |
| str_numth(s, s, S_TH_TYPE(n->suffix)); |
| s += strlen(s); |
| break; |
| case DCH_SS: |
| sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2, tm->tm_sec); |
| if (S_THth(n->suffix)) |
| str_numth(s, s, S_TH_TYPE(n->suffix)); |
| s += strlen(s); |
| break; |
| case DCH_MS: /* millisecond */ |
| #ifdef HAVE_INT64_TIMESTAMP |
| sprintf(s, "%03d", (int) (in->fsec / INT64CONST(1000))); |
| #else |
| /* No rint() because we can't overflow and we might print US */ |
| sprintf(s, "%03d", (int) (in->fsec * 1000)); |
| #endif |
| if (S_THth(n->suffix)) |
| str_numth(s, s, S_TH_TYPE(n->suffix)); |
| s += strlen(s); |
| break; |
| case DCH_US: /* microsecond */ |
| #ifdef HAVE_INT64_TIMESTAMP |
| sprintf(s, "%06d", (int) in->fsec); |
| #else |
| /* don't use rint() because we can't overflow 1000 */ |
| sprintf(s, "%06d", (int) (in->fsec * 1000000)); |
| #endif |
| if (S_THth(n->suffix)) |
| str_numth(s, s, S_TH_TYPE(n->suffix)); |
| s += strlen(s); |
| break; |
| case DCH_SSSS: |
| sprintf(s, "%d", tm->tm_hour * SECS_PER_HOUR + |
| tm->tm_min * SECS_PER_MINUTE + |
| tm->tm_sec); |
| if (S_THth(n->suffix)) |
| str_numth(s, s, S_TH_TYPE(n->suffix)); |
| s += strlen(s); |
| break; |
| case DCH_tz: |
| INVALID_FOR_INTERVAL; |
| if (tmtcTzn(in)) |
| { |
| char *p = str_tolower_z(tmtcTzn(in)); |
| |
| strcpy(s, p); |
| pfree(p); |
| s += strlen(s); |
| } |
| break; |
| case DCH_TZ: |
| INVALID_FOR_INTERVAL; |
| if (tmtcTzn(in)) |
| { |
| strcpy(s, tmtcTzn(in)); |
| s += strlen(s); |
| } |
| break; |
| case DCH_A_D: |
| case DCH_B_C: |
| INVALID_FOR_INTERVAL; |
| strcpy(s, (tm->tm_year <= 0 ? B_C_STR : A_D_STR)); |
| s += strlen(s); |
| break; |
| case DCH_AD: |
| case DCH_BC: |
| INVALID_FOR_INTERVAL; |
| strcpy(s, (tm->tm_year <= 0 ? BC_STR : AD_STR)); |
| s += strlen(s); |
| break; |
| case DCH_a_d: |
| case DCH_b_c: |
| INVALID_FOR_INTERVAL; |
| strcpy(s, (tm->tm_year <= 0 ? b_c_STR : a_d_STR)); |
| s += strlen(s); |
| break; |
| case DCH_ad: |
| case DCH_bc: |
| INVALID_FOR_INTERVAL; |
| strcpy(s, (tm->tm_year <= 0 ? bc_STR : ad_STR)); |
| s += strlen(s); |
| break; |
| case DCH_MONTH: |
| INVALID_FOR_INTERVAL; |
| if (!tm->tm_mon) |
| break; |
| if (S_TM(n->suffix)) |
| strcpy(s, str_toupper_z(localized_full_months[tm->tm_mon - 1])); |
| else |
| sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, |
| str_toupper_z(months_full[tm->tm_mon - 1])); |
| s += strlen(s); |
| break; |
| case DCH_Month: |
| INVALID_FOR_INTERVAL; |
| if (!tm->tm_mon) |
| break; |
| if (S_TM(n->suffix)) |
| strcpy(s, str_initcap_z(localized_full_months[tm->tm_mon - 1])); |
| else |
| sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, months_full[tm->tm_mon - 1]); |
| s += strlen(s); |
| break; |
| case DCH_month: |
| INVALID_FOR_INTERVAL; |
| if (!tm->tm_mon) |
| break; |
| if (S_TM(n->suffix)) |
| strcpy(s, str_tolower_z(localized_full_months[tm->tm_mon - 1])); |
| else |
| { |
| sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, months_full[tm->tm_mon - 1]); |
| *s = pg_tolower((unsigned char) *s); |
| } |
| s += strlen(s); |
| break; |
| case DCH_MON: |
| INVALID_FOR_INTERVAL; |
| if (!tm->tm_mon) |
| break; |
| if (S_TM(n->suffix)) |
| strcpy(s, str_toupper_z(localized_abbrev_months[tm->tm_mon - 1])); |
| else |
| strcpy(s, str_toupper_z(months[tm->tm_mon - 1])); |
| s += strlen(s); |
| break; |
| case DCH_Mon: |
| INVALID_FOR_INTERVAL; |
| if (!tm->tm_mon) |
| break; |
| if (S_TM(n->suffix)) |
| strcpy(s, str_initcap_z(localized_abbrev_months[tm->tm_mon - 1])); |
| else |
| strcpy(s, months[tm->tm_mon - 1]); |
| s += strlen(s); |
| break; |
| case DCH_mon: |
| INVALID_FOR_INTERVAL; |
| if (!tm->tm_mon) |
| break; |
| if (S_TM(n->suffix)) |
| strcpy(s, str_tolower_z(localized_abbrev_months[tm->tm_mon - 1])); |
| else |
| { |
| strcpy(s, months[tm->tm_mon - 1]); |
| *s = pg_tolower((unsigned char) *s); |
| } |
| s += strlen(s); |
| break; |
| case DCH_MM: |
| sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2, tm->tm_mon); |
| if (S_THth(n->suffix)) |
| str_numth(s, s, S_TH_TYPE(n->suffix)); |
| s += strlen(s); |
| break; |
| case DCH_DAY: |
| INVALID_FOR_INTERVAL; |
| if (S_TM(n->suffix)) |
| strcpy(s, str_toupper_z(localized_full_days[tm->tm_wday])); |
| else |
| sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, |
| str_toupper_z(days[tm->tm_wday])); |
| s += strlen(s); |
| break; |
| case DCH_Day: |
| INVALID_FOR_INTERVAL; |
| if (S_TM(n->suffix)) |
| strcpy(s, str_initcap_z(localized_full_days[tm->tm_wday])); |
| else |
| sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, days[tm->tm_wday]); |
| s += strlen(s); |
| break; |
| case DCH_day: |
| INVALID_FOR_INTERVAL; |
| if (S_TM(n->suffix)) |
| strcpy(s, str_tolower_z(localized_full_days[tm->tm_wday])); |
| else |
| { |
| sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -9, days[tm->tm_wday]); |
| *s = pg_tolower((unsigned char) *s); |
| } |
| s += strlen(s); |
| break; |
| case DCH_DY: |
| INVALID_FOR_INTERVAL; |
| if (S_TM(n->suffix)) |
| strcpy(s, str_toupper_z(localized_abbrev_days[tm->tm_wday])); |
| else |
| strcpy(s, str_toupper_z(days_short[tm->tm_wday])); |
| s += strlen(s); |
| break; |
| case DCH_Dy: |
| INVALID_FOR_INTERVAL; |
| if (S_TM(n->suffix)) |
| strcpy(s, str_initcap_z(localized_abbrev_days[tm->tm_wday])); |
| else |
| strcpy(s, days_short[tm->tm_wday]); |
| s += strlen(s); |
| break; |
| case DCH_dy: |
| INVALID_FOR_INTERVAL; |
| if (S_TM(n->suffix)) |
| strcpy(s, str_tolower_z(localized_abbrev_days[tm->tm_wday])); |
| else |
| { |
| strcpy(s, days_short[tm->tm_wday]); |
| *s = pg_tolower((unsigned char) *s); |
| } |
| s += strlen(s); |
| break; |
| case DCH_DDD: |
| case DCH_IDDD: |
| sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 3, |
| (n->key->id == DCH_DDD) ? |
| tm->tm_yday : |
| date2isoyearday(tm->tm_year, tm->tm_mon, tm->tm_mday)); |
| if (S_THth(n->suffix)) |
| str_numth(s, s, S_TH_TYPE(n->suffix)); |
| s += strlen(s); |
| break; |
| case DCH_DD: |
| sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2, tm->tm_mday); |
| if (S_THth(n->suffix)) |
| str_numth(s, s, S_TH_TYPE(n->suffix)); |
| s += strlen(s); |
| break; |
| case DCH_D: |
| INVALID_FOR_INTERVAL; |
| sprintf(s, "%d", tm->tm_wday + 1); |
| if (S_THth(n->suffix)) |
| str_numth(s, s, S_TH_TYPE(n->suffix)); |
| s += strlen(s); |
| break; |
| case DCH_ID: |
| INVALID_FOR_INTERVAL; |
| sprintf(s, "%d", (tm->tm_wday == 0) ? 7 : tm->tm_wday); |
| if (S_THth(n->suffix)) |
| str_numth(s, s, S_TH_TYPE(n->suffix)); |
| s += strlen(s); |
| break; |
| case DCH_WW: |
| sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2, |
| (tm->tm_yday - 1) / 7 + 1); |
| if (S_THth(n->suffix)) |
| str_numth(s, s, S_TH_TYPE(n->suffix)); |
| s += strlen(s); |
| break; |
| case DCH_IW: |
| sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2, |
| date2isoweek(tm->tm_year, tm->tm_mon, tm->tm_mday)); |
| if (S_THth(n->suffix)) |
| str_numth(s, s, S_TH_TYPE(n->suffix)); |
| s += strlen(s); |
| break; |
| case DCH_Q: |
| if (!tm->tm_mon) |
| break; |
| sprintf(s, "%d", (tm->tm_mon - 1) / 3 + 1); |
| if (S_THth(n->suffix)) |
| str_numth(s, s, S_TH_TYPE(n->suffix)); |
| s += strlen(s); |
| break; |
| case DCH_CC: |
| if (is_interval) /* straight calculation */ |
| i = tm->tm_year / 100; |
| else /* century 21 starts in 2001 */ |
| i = (tm->tm_year - 1) / 100 + 1; |
| if (i <= 99 && i >= -99) |
| sprintf(s, "%0*d", S_FM(n->suffix) ? 0 : 2, i); |
| else |
| sprintf(s, "%d", i); |
| if (S_THth(n->suffix)) |
| str_numth(s, s, S_TH_TYPE(n->suffix)); |
| s += strlen(s); |
| break; |
| case DCH_Y_YYY: |
| i = ADJUST_YEAR(tm->tm_year, is_interval) / 1000; |
| sprintf(s, "%d,%03d", i, |
| ADJUST_YEAR(tm->tm_year, is_interval) - (i * 1000)); |
| if (S_THth(n->suffix)) |
| str_numth(s, s, S_TH_TYPE(n->suffix)); |
| s += strlen(s); |
| break; |
| case DCH_YYYY: |
| case DCH_IYYY: |
| if (tm->tm_year <= 9999 && tm->tm_year >= -9998) |
| sprintf(s, "%0*d", |
| S_FM(n->suffix) ? 0 : 4, |
| n->key->id == DCH_YYYY ? |
| ADJUST_YEAR(tm->tm_year, is_interval) : |
| ADJUST_YEAR(date2isoyear( |
| tm->tm_year, |
| tm->tm_mon, |
| tm->tm_mday), is_interval)); |
| else |
| sprintf(s, "%d", |
| n->key->id == DCH_YYYY ? |
| ADJUST_YEAR(tm->tm_year, is_interval) : |
| ADJUST_YEAR(date2isoyear( |
| tm->tm_year, |
| tm->tm_mon, |
| tm->tm_mday), is_interval)); |
| if (S_THth(n->suffix)) |
| str_numth(s, s, S_TH_TYPE(n->suffix)); |
| s += strlen(s); |
| break; |
| case DCH_YYY: |
| case DCH_IYY: |
| snprintf(buff, sizeof(buff), "%03d", |
| n->key->id == DCH_YYY ? |
| ADJUST_YEAR(tm->tm_year, is_interval) : |
| ADJUST_YEAR(date2isoyear(tm->tm_year, |
| tm->tm_mon, tm->tm_mday), |
| is_interval)); |
| i = strlen(buff); |
| strcpy(s, buff + (i - 3)); |
| if (S_THth(n->suffix)) |
| str_numth(s, s, S_TH_TYPE(n->suffix)); |
| s += strlen(s); |
| break; |
| case DCH_YY: |
| case DCH_IY: |
| snprintf(buff, sizeof(buff), "%02d", |
| n->key->id == DCH_YY ? |
| ADJUST_YEAR(tm->tm_year, is_interval) : |
| ADJUST_YEAR(date2isoyear(tm->tm_year, |
| tm->tm_mon, tm->tm_mday), |
| is_interval)); |
| i = strlen(buff); |
| strcpy(s, buff + (i - 2)); |
| if (S_THth(n->suffix)) |
| str_numth(s, s, S_TH_TYPE(n->suffix)); |
| s += strlen(s); |
| break; |
| case DCH_Y: |
| case DCH_I: |
| snprintf(buff, sizeof(buff), "%1d", |
| n->key->id == DCH_Y ? |
| ADJUST_YEAR(tm->tm_year, is_interval) : |
| ADJUST_YEAR(date2isoyear(tm->tm_year, |
| tm->tm_mon, tm->tm_mday), |
| is_interval)); |
| i = strlen(buff); |
| strcpy(s, buff + (i - 1)); |
| if (S_THth(n->suffix)) |
| str_numth(s, s, S_TH_TYPE(n->suffix)); |
| s += strlen(s); |
| break; |
| case DCH_RM: |
| if (!tm->tm_mon) |
| break; |
| sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -4, |
| rm_months_upper[12 - tm->tm_mon]); |
| s += strlen(s); |
| break; |
| case DCH_rm: |
| if (!tm->tm_mon) |
| break; |
| sprintf(s, "%*s", S_FM(n->suffix) ? 0 : -4, |
| rm_months_lower[12 - tm->tm_mon]); |
| s += strlen(s); |
| break; |
| case DCH_W: |
| sprintf(s, "%d", (tm->tm_mday - 1) / 7 + 1); |
| if (S_THth(n->suffix)) |
| str_numth(s, s, S_TH_TYPE(n->suffix)); |
| s += strlen(s); |
| break; |
| case DCH_J: |
| sprintf(s, "%d", date2j(tm->tm_year, tm->tm_mon, tm->tm_mday)); |
| if (S_THth(n->suffix)) |
| str_numth(s, s, S_TH_TYPE(n->suffix)); |
| s += strlen(s); |
| break; |
| } |
| } |
| |
| *s = '\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. |
| * ---------- |
| */ |
| static void |
| DCH_from_char(FormatNode *node, char *in, TmFromChar *out) |
| { |
| FormatNode *n; |
| char *s; |
| int len, |
| value; |
| bool fx_mode = false; |
| |
| for (n = node, s = in; n->type != NODE_TYPE_END && *s != '\0'; n++) |
| { |
| if (n->type != NODE_TYPE_ACTION) |
| { |
| s++; |
| /* Ignore spaces when not in FX (fixed width) mode */ |
| if (isspace((unsigned char) n->character) && !fx_mode) |
| { |
| while (*s != '\0' && isspace((unsigned char) *s)) |
| s++; |
| } |
| continue; |
| } |
| |
| from_char_set_mode(out, n->key->date_mode); |
| |
| switch (n->key->id) |
| { |
| case DCH_FX: |
| fx_mode = true; |
| break; |
| case DCH_A_M: |
| case DCH_P_M: |
| case DCH_a_m: |
| case DCH_p_m: |
| from_char_seq_search(&value, &s, ampm_strings_long, |
| ALL_UPPER, n->key->len, n); |
| from_char_set_int(&out->pm, value % 2, n); |
| out->clock = CLOCK_12_HOUR; |
| break; |
| case DCH_AM: |
| case DCH_PM: |
| case DCH_am: |
| case DCH_pm: |
| from_char_seq_search(&value, &s, ampm_strings, |
| ALL_UPPER, n->key->len, n); |
| from_char_set_int(&out->pm, value % 2, n); |
| out->clock = CLOCK_12_HOUR; |
| break; |
| case DCH_HH: |
| case DCH_HH12: |
| from_char_parse_int_len(&out->hh, &s, 2, n); |
| out->clock = CLOCK_12_HOUR; |
| s += SKIP_THth(n->suffix); |
| break; |
| case DCH_HH24: |
| from_char_parse_int_len(&out->hh, &s, 2, n); |
| s += SKIP_THth(n->suffix); |
| break; |
| case DCH_MI: |
| from_char_parse_int(&out->mi, &s, n); |
| s += SKIP_THth(n->suffix); |
| break; |
| case DCH_SS: |
| from_char_parse_int(&out->ss, &s, n); |
| s += SKIP_THth(n->suffix); |
| break; |
| case DCH_MS: /* millisecond */ |
| len = from_char_parse_int_len(&out->ms, &s, 3, n); |
| |
| /* |
| * 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; |
| |
| s += SKIP_THth(n->suffix); |
| break; |
| case DCH_US: /* microsecond */ |
| len = from_char_parse_int_len(&out->us, &s, 6, n); |
| |
| out->us *= len == 1 ? 100000 : |
| len == 2 ? 10000 : |
| len == 3 ? 1000 : |
| len == 4 ? 100 : |
| len == 5 ? 10 : 1; |
| |
| s += SKIP_THth(n->suffix); |
| break; |
| case DCH_SSSS: |
| from_char_parse_int(&out->ssss, &s, n); |
| s += SKIP_THth(n->suffix); |
| break; |
| case DCH_tz: |
| case DCH_TZ: |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("\"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: |
| from_char_seq_search(&value, &s, adbc_strings_long, |
| ALL_UPPER, n->key->len, n); |
| from_char_set_int(&out->bc, value % 2, n); |
| break; |
| case DCH_AD: |
| case DCH_BC: |
| case DCH_ad: |
| case DCH_bc: |
| from_char_seq_search(&value, &s, adbc_strings, |
| ALL_UPPER, n->key->len, n); |
| from_char_set_int(&out->bc, value % 2, n); |
| break; |
| case DCH_MONTH: |
| case DCH_Month: |
| case DCH_month: |
| from_char_seq_search(&value, &s, months_full, ONE_UPPER, |
| MAX_MONTH_LEN, n); |
| from_char_set_int(&out->mm, value + 1, n); |
| break; |
| case DCH_MON: |
| case DCH_Mon: |
| case DCH_mon: |
| from_char_seq_search(&value, &s, months, ONE_UPPER, |
| MAX_MON_LEN, n); |
| from_char_set_int(&out->mm, value + 1, n); |
| break; |
| case DCH_MM: |
| from_char_parse_int(&out->mm, &s, n); |
| s += SKIP_THth(n->suffix); |
| break; |
| case DCH_DAY: |
| case DCH_Day: |
| case DCH_day: |
| from_char_seq_search(&value, &s, days, ONE_UPPER, |
| MAX_DAY_LEN, n); |
| from_char_set_int(&out->d, value, n); |
| break; |
| case DCH_DY: |
| case DCH_Dy: |
| case DCH_dy: |
| from_char_seq_search(&value, &s, days, ONE_UPPER, |
| MAX_DY_LEN, n); |
| from_char_set_int(&out->d, value, n); |
| break; |
| case DCH_DDD: |
| from_char_parse_int(&out->ddd, &s, n); |
| s += SKIP_THth(n->suffix); |
| break; |
| case DCH_IDDD: |
| from_char_parse_int_len(&out->ddd, &s, 3, n); |
| s += SKIP_THth(n->suffix); |
| break; |
| case DCH_DD: |
| from_char_parse_int(&out->dd, &s, n); |
| s += SKIP_THth(n->suffix); |
| break; |
| case DCH_D: |
| from_char_parse_int(&out->d, &s, n); |
| out->d--; |
| s += SKIP_THth(n->suffix); |
| break; |
| case DCH_ID: |
| from_char_parse_int_len(&out->d, &s, 1, n); |
| s += SKIP_THth(n->suffix); |
| break; |
| case DCH_WW: |
| case DCH_IW: |
| from_char_parse_int(&out->ww, &s, n); |
| s += SKIP_THth(n->suffix); |
| break; |
| case DCH_Q: |
| /* |
| * We ignore Q when converting to date because it is not |
| * normative. |
| * |
| * We still parse the source string for an integer, but it |
| * isn't stored anywhere in 'out'. |
| */ |
| from_char_parse_int((int *) NULL, &s, n); |
| s += SKIP_THth(n->suffix); |
| break; |
| case DCH_CC: |
| from_char_parse_int(&out->cc, &s, n); |
| s += SKIP_THth(n->suffix); |
| break; |
| case DCH_Y_YYY: |
| { |
| int matched, years, millenia; |
| |
| matched = sscanf(s, "%d,%03d", &millenia, &years); |
| if (matched != 2) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_DATETIME_FORMAT), |
| errmsg("invalid input string for \"Y,YYY\" in function \"to_date\""))); |
| years += (millenia * 1000); |
| from_char_set_int(&out->year, years, n); |
| out->yysz = 4; |
| s += strdigits_len(s) + 4 + SKIP_THth(n->suffix); |
| } |
| break; |
| case DCH_YYYY: |
| case DCH_IYYY: |
| from_char_parse_int(&out->year, &s, n); |
| out->yysz = 4; |
| s += SKIP_THth(n->suffix); |
| break; |
| case DCH_YYY: |
| case DCH_IYY: |
| from_char_parse_int(&out->year, &s, n); |
| out->yysz = 3; |
| |
| /* |
| * 3-digit year: '100' ... '999' = 1100 ... 1999 '000' ... |
| * '099' = 2000 ... 2099 |
| */ |
| if (out->year >= 100) |
| out->year += 1000; |
| else |
| out->year += 2000; |
| s += SKIP_THth(n->suffix); |
| break; |
| case DCH_YY: |
| case DCH_IY: |
| from_char_parse_int(&out->year, &s, n); |
| out->yysz = 2; |
| |
| /* |
| * 2-digit year: '00' ... '69' = 2000 ... 2069 '70' ... '99' |
| * = 1970 ... 1999 |
| */ |
| if (out->year < 70) |
| out->year += 2000; |
| else |
| out->year += 1900; |
| s += SKIP_THth(n->suffix); |
| break; |
| case DCH_Y: |
| case DCH_I: |
| from_char_parse_int(&out->year, &s, n); |
| out->yysz = 1; |
| |
| /* |
| * 1-digit year: always +2000 |
| */ |
| out->year += 2000; |
| s += SKIP_THth(n->suffix); |
| break; |
| case DCH_RM: |
| from_char_seq_search(&value, &s, rm_months_upper, |
| ALL_UPPER, MAX_RM_LEN, n); |
| from_char_set_int(&out->mm, 12 - value, n); |
| break; |
| case DCH_rm: |
| from_char_seq_search(&value, &s, rm_months_lower, |
| ALL_LOWER, MAX_RM_LEN, n); |
| from_char_set_int(&out->mm, 12 - value, n); |
| break; |
| case DCH_W: |
| from_char_parse_int(&out->w, &s, n); |
| s += SKIP_THth(n->suffix); |
| break; |
| case DCH_J: |
| from_char_parse_int(&out->j, &s, n); |
| s += SKIP_THth(n->suffix); |
| break; |
| } |
| } |
| } |
| |
| static DCHCacheEntry * |
| DCH_cache_getnew(char *str) |
| { |
| DCHCacheEntry *ent; |
| |
| /* counter overflow check - paranoia? */ |
| if (DCHCounter >= (INT_MAX - DCH_CACHE_FIELDS - 1)) |
| { |
| DCHCounter = 0; |
| |
| for (ent = DCHCache; ent <= (DCHCache + DCH_CACHE_FIELDS); ent++) |
| ent->age = (++DCHCounter); |
| } |
| |
| /* |
| * If cache is full, remove oldest entry |
| */ |
| if (n_DCHCache > DCH_CACHE_FIELDS) |
| { |
| DCHCacheEntry *old = DCHCache + 0; |
| |
| #ifdef DEBUG_TO_FROM_CHAR |
| elog(DEBUG_elog_output, "cache is full (%d)", n_DCHCache); |
| #endif |
| for (ent = DCHCache + 1; ent <= (DCHCache + DCH_CACHE_FIELDS); ent++) |
| { |
| if (ent->age < old->age) |
| old = ent; |
| } |
| #ifdef DEBUG_TO_FROM_CHAR |
| elog(DEBUG_elog_output, "OLD: '%s' AGE: %d", old->str, old->age); |
| #endif |
| StrNCpy(old->str, str, DCH_CACHE_SIZE + 1); |
| /* old->format fill parser */ |
| old->age = (++DCHCounter); |
| return old; |
| } |
| else |
| { |
| #ifdef DEBUG_TO_FROM_CHAR |
| elog(DEBUG_elog_output, "NEW (%d)", n_DCHCache); |
| #endif |
| ent = DCHCache + n_DCHCache; |
| StrNCpy(ent->str, str, DCH_CACHE_SIZE + 1); |
| /* ent->format fill parser */ |
| ent->age = (++DCHCounter); |
| ++n_DCHCache; |
| return ent; |
| } |
| } |
| |
| static DCHCacheEntry * |
| DCH_cache_search(char *str) |
| { |
| int i; |
| DCHCacheEntry *ent; |
| |
| /* counter overflow check - paranoia? */ |
| if (DCHCounter >= (INT_MAX - DCH_CACHE_FIELDS - 1)) |
| { |
| DCHCounter = 0; |
| |
| for (ent = DCHCache; ent <= (DCHCache + DCH_CACHE_FIELDS); ent++) |
| ent->age = (++DCHCounter); |
| } |
| |
| for (i = 0, ent = DCHCache; i < n_DCHCache; i++, ent++) |
| { |
| if (strcmp(ent->str, str) == 0) |
| { |
| ent->age = (++DCHCounter); |
| return ent; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /* |
| * Format a date/time or interval into a string according to fmt. |
| * We parse fmt into a list of FormatNodes. This is then passed to DCH_to_char |
| * for formatting. |
| */ |
| static text * |
| datetime_to_char_body(TmToChar *tmtc, text *fmt, bool is_interval) |
| { |
| FormatNode *format; |
| char *fmt_str, |
| *result; |
| bool incache; |
| int fmt_len; |
| text *res; |
| |
| /* |
| * Convert fmt to C string |
| */ |
| fmt_str = text_to_cstring(fmt); |
| fmt_len = strlen(fmt_str); |
| |
| /* |
| * Allocate workspace for result as C string |
| */ |
| result = palloc((fmt_len * DCH_MAX_ITEM_SIZ) + 1); |
| *result = '\0'; |
| |
| /* |
| * Allocate new memory if format picture is bigger than static cache and |
| * not use cache (call parser always) |
| */ |
| if (fmt_len > DCH_CACHE_SIZE) |
| { |
| format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode)); |
| incache = FALSE; |
| |
| parse_format(format, fmt_str, DCH_keywords, |
| DCH_suff, DCH_index, DCH_TYPE, NULL, |
| "to_char"); |
| |
| (format + fmt_len)->type = NODE_TYPE_END; /* Paranoia? */ |
| } |
| else |
| { |
| /* |
| * Use cache buffers |
| */ |
| DCHCacheEntry *ent; |
| |
| incache = TRUE; |
| |
| if ((ent = DCH_cache_search(fmt_str)) == NULL) |
| { |
| ent = DCH_cache_getnew(fmt_str); |
| |
| /* |
| * Not in the cache, must run parser and save a new format-picture |
| * to the cache. |
| */ |
| parse_format(ent->format, fmt_str, DCH_keywords, |
| DCH_suff, DCH_index, DCH_TYPE, NULL, |
| "to_char"); |
| |
| (ent->format + fmt_len)->type = NODE_TYPE_END; /* Paranoia? */ |
| |
| #ifdef DEBUG_TO_FROM_CHAR |
| /* dump_node(ent->format, fmt_len); */ |
| /* dump_index(DCH_keywords, DCH_index); */ |
| #endif |
| } |
| format = ent->format; |
| } |
| |
| /* The real work is here */ |
| DCH_to_char(format, is_interval, tmtc, result); |
| |
| if (!incache) |
| pfree(format); |
| |
| pfree(fmt_str); |
| |
| /* convert C-string result to TEXT format */ |
| res = cstring_to_text(result); |
| |
| pfree(result); |
| return res; |
| } |
| |
| /**************************************************************************** |
| * Public routines |
| ***************************************************************************/ |
| |
| /* ------------------- |
| * TIMESTAMP to_char() |
| * ------------------- |
| */ |
| Datum |
| timestamp_to_char(PG_FUNCTION_ARGS) |
| { |
| Timestamp dt = PG_GETARG_TIMESTAMP(0); |
| text *fmt = PG_GETARG_TEXT_P(1), |
| *res; |
| TmToChar tmtc; |
| struct pg_tm *tm; |
| int thisdate; |
| |
| if ((VARSIZE(fmt) - VARHDRSZ) <= 0 || TIMESTAMP_NOT_FINITE(dt)) |
| PG_RETURN_NULL(); |
| |
| ZERO_tmtc(&tmtc); |
| tm = tmtcTm(&tmtc); |
| |
| if (timestamp2tm(dt, NULL, tm, &tmtcFsec(&tmtc), NULL, NULL) != 0) |
| ereport(ERROR, |
| (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), |
| errmsg("timestamp out of range for function \"to_char\""))); |
| |
| thisdate = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday); |
| tm->tm_wday = (thisdate + 1) % 7; |
| tm->tm_yday = thisdate - date2j(tm->tm_year, 1, 1) + 1; |
| |
| if (!(res = datetime_to_char_body(&tmtc, fmt, false))) |
| PG_RETURN_NULL(); |
| |
| PG_RETURN_TEXT_P(res); |
| } |
| |
| Datum |
| timestamptz_to_char(PG_FUNCTION_ARGS) |
| { |
| TimestampTz dt = PG_GETARG_TIMESTAMP(0); |
| text *fmt = PG_GETARG_TEXT_P(1), |
| *res; |
| TmToChar tmtc; |
| int tz; |
| struct pg_tm *tm; |
| int thisdate; |
| |
| if ((VARSIZE(fmt) - VARHDRSZ) <= 0 || TIMESTAMP_NOT_FINITE(dt)) |
| PG_RETURN_NULL(); |
| |
| ZERO_tmtc(&tmtc); |
| tm = tmtcTm(&tmtc); |
| |
| if (timestamp2tm(dt, &tz, tm, &tmtcFsec(&tmtc), &tmtcTzn(&tmtc), NULL) != 0) |
| ereport(ERROR, |
| (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), |
| errmsg("timestamp out of range for function \"to_char\""))); |
| |
| thisdate = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday); |
| tm->tm_wday = (thisdate + 1) % 7; |
| tm->tm_yday = thisdate - date2j(tm->tm_year, 1, 1) + 1; |
| |
| if (!(res = datetime_to_char_body(&tmtc, fmt, false))) |
| PG_RETURN_NULL(); |
| |
| PG_RETURN_TEXT_P(res); |
| } |
| |
| |
| /* ------------------- |
| * INTERVAL to_char() |
| * ------------------- |
| */ |
| Datum |
| interval_to_char(PG_FUNCTION_ARGS) |
| { |
| Interval *it = PG_GETARG_INTERVAL_P(0); |
| text *fmt = PG_GETARG_TEXT_P(1), |
| *res; |
| TmToChar tmtc; |
| struct pg_tm *tm; |
| |
| if ((VARSIZE(fmt) - VARHDRSZ) <= 0) |
| PG_RETURN_NULL(); |
| |
| ZERO_tmtc(&tmtc); |
| tm = tmtcTm(&tmtc); |
| |
| if (interval2tm(*it, tm, &tmtcFsec(&tmtc)) != 0) |
| PG_RETURN_NULL(); |
| |
| /* wday is meaningless, yday approximates the total span in days */ |
| tm->tm_yday = (tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon) * DAYS_PER_MONTH + tm->tm_mday; |
| |
| if (!(res = datetime_to_char_body(&tmtc, fmt, true))) |
| PG_RETURN_NULL(); |
| |
| PG_RETURN_TEXT_P(res); |
| } |
| |
| /* --------------------- |
| * TO_TIMESTAMP() |
| * |
| * Make Timestamp from date_str which is formatted at argument 'fmt' |
| * ( to_timestamp is reverse to_char() ) |
| * --------------------- |
| */ |
| Datum |
| to_timestamp(PG_FUNCTION_ARGS) |
| { |
| text *date_txt = PG_GETARG_TEXT_P(0); |
| text *fmt = PG_GETARG_TEXT_P(1); |
| Timestamp result; |
| int tz; |
| struct pg_tm tm; |
| fsec_t fsec; |
| |
| do_to_timestamp(date_txt, fmt, &tm, &fsec); |
| |
| tz = DetermineTimeZoneOffset(&tm, session_timezone); |
| |
| if (tm2timestamp(&tm, fsec, &tz, &result) != 0) |
| ereport(ERROR, |
| (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), |
| errmsg("timestamp out of range for function \"to_timestamp\""))); |
| |
| PG_RETURN_TIMESTAMP(result); |
| } |
| |
| /* ---------- |
| * TO_DATE |
| * Make Date from date_str which is formated at argument 'fmt' |
| * ---------- |
| */ |
| Datum |
| to_date(PG_FUNCTION_ARGS) |
| { |
| text *date_txt = PG_GETARG_TEXT_P(0); |
| text *fmt = PG_GETARG_TEXT_P(1); |
| DateADT result; |
| struct pg_tm tm; |
| fsec_t fsec; |
| |
| do_to_timestamp(date_txt, fmt, &tm, &fsec); |
| |
| result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - POSTGRES_EPOCH_JDATE; |
| |
| PG_RETURN_DATEADT(result); |
| } |
| |
| /* |
| * do_to_timestamp: shared code for to_timestamp and to_date |
| * |
| * Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm |
| * and fractional seconds. |
| * |
| * We parse 'fmt' into a list of FormatNodes, which is then passed to |
| * DCH_from_char to populate a TmFromChar with the parsed contents of |
| * 'date_txt'. |
| * |
| * The TmFromChar is then analysed and converted into the final results in |
| * struct 'tm' and 'fsec'. |
| */ |
| static void |
| do_to_timestamp(text *date_txt, text *fmt, |
| struct pg_tm * tm, fsec_t *fsec) |
| { |
| FormatNode *format; |
| TmFromChar tmfc; |
| int fmt_len; |
| |
| ZERO_tmfc(&tmfc); |
| ZERO_tm(tm); |
| *fsec = 0; |
| |
| fmt_len = VARSIZE(fmt) - VARHDRSZ; |
| |
| if (fmt_len) |
| { |
| char *fmt_str; |
| char *date_str; |
| bool incache; |
| |
| fmt_str = text_to_cstring(fmt); |
| |
| /* |
| * Allocate new memory if format picture is bigger than static cache |
| * and not use cache (call parser always) |
| */ |
| if (fmt_len > DCH_CACHE_SIZE) |
| { |
| format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode)); |
| incache = FALSE; |
| |
| parse_format(format, fmt_str, DCH_keywords, |
| DCH_suff, DCH_index, DCH_TYPE, NULL, |
| "to_timestamp"); |
| |
| (format + fmt_len)->type = NODE_TYPE_END; /* Paranoia? */ |
| } |
| else |
| { |
| /* |
| * Use cache buffers |
| */ |
| DCHCacheEntry *ent; |
| |
| incache = TRUE; |
| |
| if ((ent = DCH_cache_search(fmt_str)) == NULL) |
| { |
| ent = DCH_cache_getnew(fmt_str); |
| |
| /* |
| * Not in the cache, must run parser and save a new |
| * format-picture to the cache. |
| */ |
| parse_format(ent->format, fmt_str, DCH_keywords, |
| DCH_suff, DCH_index, DCH_TYPE, NULL, |
| "to_timestamp"); |
| |
| (ent->format + fmt_len)->type = NODE_TYPE_END; /* Paranoia? */ |
| #ifdef DEBUG_TO_FROM_CHAR |
| /* dump_node(ent->format, fmt_len); */ |
| /* dump_index(DCH_keywords, DCH_index); */ |
| #endif |
| } |
| format = ent->format; |
| } |
| |
| #ifdef DEBUG_TO_FROM_CHAR |
| /* dump_node(format, fmt_len); */ |
| #endif |
| |
| date_str = text_to_cstring(date_txt); |
| |
| DCH_from_char(format, date_str, &tmfc); |
| |
| pfree(date_str); |
| pfree(fmt_str); |
| if (!incache) |
| pfree(format); |
| } |
| |
| DEBUG_TMFC(&tmfc); |
| |
| /* |
| * Convert values that user define for FROM_CHAR (to_date/to_timestamp) to |
| * standard 'tm' |
| */ |
| if (tmfc.ssss) |
| { |
| int x = tmfc.ssss; |
| |
| tm->tm_hour = x / SECS_PER_HOUR; |
| x %= SECS_PER_HOUR; |
| tm->tm_min = x / SECS_PER_MINUTE; |
| x %= SECS_PER_MINUTE; |
| tm->tm_sec = x; |
| } |
| |
| if (tmfc.ss) |
| tm->tm_sec = tmfc.ss; |
| if (tmfc.mi) |
| tm->tm_min = tmfc.mi; |
| if (tmfc.hh) |
| tm->tm_hour = tmfc.hh; |
| |
| if (tmfc.clock == CLOCK_12_HOUR) |
| { |
| if (tm->tm_hour < 1 || tm->tm_hour > 12) |
| { |
| if (tm->tm_hour > 12 && !tmfc.pm) |
| { |
| ereport(WARNING, |
| (errcode(ERRCODE_INVALID_DATETIME_FORMAT), |
| errmsg("hour \"%d\" is invalid for the 12-hour clock", |
| tm->tm_hour), |
| errhint("Use the 24-hour clock, or give an hour between 1 and 12."))); |
| tmfc.pm = TRUE; |
| tm->tm_hour = tm->tm_hour - 12; |
| } |
| else |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_DATETIME_FORMAT), |
| errmsg("hour \"%d\" is invalid for the 12-hour clock", |
| tm->tm_hour), |
| errhint("Use the 24-hour clock, or give an hour between 1 and 12."))); |
| } |
| |
| if (tmfc.pm && tm->tm_hour < 12) |
| tm->tm_hour += 12; |
| else if (!tmfc.pm && tm->tm_hour == 12) |
| tm->tm_hour = 0; |
| } |
| |
| if (tmfc.year) |
| { |
| /* |
| * 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 |
| * runs from 2001-2100, not 2000-2099. |
| * |
| * If a 4-digit year is provided, we use that and ignore CC. |
| */ |
| if (tmfc.cc && tmfc.yysz <= 2) |
| { |
| tm->tm_year = tmfc.year % 100; |
| if (tm->tm_year) |
| tm->tm_year += (tmfc.cc - 1) * 100; |
| else |
| tm->tm_year = tmfc.cc * 100; |
| } |
| else |
| tm->tm_year = tmfc.year; |
| } |
| else if (tmfc.cc) /* use first year of century */ |
| tm->tm_year = (tmfc.cc - 1) * 100 + 1; |
| |
| if (tmfc.bc) |
| { |
| if (tm->tm_year > 0) |
| tm->tm_year = -(tm->tm_year - 1); |
| else |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_DATETIME_FORMAT), |
| errmsg("inconsistent use of year %04d and \"BC\"", |
| tm->tm_year))); |
| } |
| |
| if (tmfc.j) |
| j2date(tmfc.j, &tm->tm_year, &tm->tm_mon, &tm->tm_mday); |
| |
| if (tmfc.ww) |
| { |
| if (tmfc.mode == 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) |
| isoweekdate2date(tmfc.ww, tmfc.d, &tm->tm_year, &tm->tm_mon, &tm->tm_mday); |
| else |
| isoweek2date(tmfc.ww, &tm->tm_year, &tm->tm_mon, &tm->tm_mday); |
| } |
| else |
| tmfc.ddd = (tmfc.ww - 1) * 7 + 1; |
| } |
| |
| if (tmfc.w) |
| tmfc.dd = (tmfc.w - 1) * 7 + 1; |
| if (tmfc.d) |
| tm->tm_wday = tmfc.d; |
| if (tmfc.dd) |
| tm->tm_mday = tmfc.dd; |
| if (tmfc.ddd) |
| tm->tm_yday = tmfc.ddd; |
| if (tmfc.mm) |
| tm->tm_mon = tmfc.mm; |
| |
| if (tmfc.ddd && (tm->tm_mon <= 1 || tm->tm_mday <= 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->tm_year && !tmfc.bc) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_DATETIME_FORMAT), |
| errmsg("cannot calculate day of year without year information"))); |
| |
| if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK) |
| { |
| int j0; /* zeroth day of the ISO year, in Julian */ |
| |
| j0 = isoweek2j(tm->tm_year, 1) - 1; |
| |
| j2date(j0 + tmfc.ddd, &tm->tm_year, &tm->tm_mon, &tm->tm_mday); |
| } |
| else |
| { |
| const int *y; |
| int i; |
| |
| static const int ysum[2][13] = { |
| {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}}; |
| |
| y = ysum[isleap(tm->tm_year)]; |
| |
| for (i = 1; i <= 12; i++) |
| { |
| if (tmfc.ddd < y[i]) |
| break; |
| } |
| if (tm->tm_mon <= 1) |
| tm->tm_mon = i; |
| |
| if (tm->tm_mday <= 1) |
| tm->tm_mday = tmfc.ddd - y[i - 1]; |
| } |
| } |
| |
| #ifdef HAVE_INT64_TIMESTAMP |
| if (tmfc.ms) |
| *fsec += tmfc.ms * 1000; |
| if (tmfc.us) |
| *fsec += tmfc.us; |
| #else |
| if (tmfc.ms) |
| *fsec += (double) tmfc.ms / 1000; |
| if (tmfc.us) |
| *fsec += (double) tmfc.us / 1000000; |
| #endif |
| |
| DEBUG_TM(tm); |
| } |
| |
| |
| /********************************************************************** |
| * the NUMBER version part |
| *********************************************************************/ |
| |
| |
| static char * |
| fill_str(char *str, int c, int max) |
| { |
| memset(str, c, max); |
| *(str + max) = '\0'; |
| return str; |
| } |
| |
| #define zeroize_NUM(_n) \ |
| do { \ |
| (_n)->flag = 0; \ |
| (_n)->lsign = 0; \ |
| (_n)->pre = 0; \ |
| (_n)->post = 0; \ |
| (_n)->pre_lsign_num = 0; \ |
| (_n)->need_locale = 0; \ |
| (_n)->multi = 0; \ |
| (_n)->zero_start = 0; \ |
| (_n)->zero_end = 0; \ |
| } while(0) |
| |
| static NUMCacheEntry * |
| NUM_cache_getnew(char *str) |
| { |
| NUMCacheEntry *ent; |
| |
| /* counter overflow check - paranoia? */ |
| if (NUMCounter >= (INT_MAX - NUM_CACHE_FIELDS - 1)) |
| { |
| NUMCounter = 0; |
| |
| for (ent = NUMCache; ent <= (NUMCache + NUM_CACHE_FIELDS); ent++) |
| ent->age = (++NUMCounter); |
| } |
| |
| /* |
| * If cache is full, remove oldest entry |
| */ |
| if (n_NUMCache > NUM_CACHE_FIELDS) |
| { |
| NUMCacheEntry *old = NUMCache + 0; |
| |
| #ifdef DEBUG_TO_FROM_CHAR |
| elog(DEBUG_elog_output, "Cache is full (%d)", n_NUMCache); |
| #endif |
| |
| for (ent = NUMCache; ent <= (NUMCache + NUM_CACHE_FIELDS); ent++) |
| { |
| /* |
| * entry removed via NUM_cache_remove() can be used here, |
| * which is why it's worth scanning first entry again |
| */ |
| if (ent->str[0] == '\0') |
| { |
| old = ent; |
| break; |
| } |
| if (ent->age < old->age) |
| old = ent; |
| } |
| #ifdef DEBUG_TO_FROM_CHAR |
| elog(DEBUG_elog_output, "OLD: \"%s\" AGE: %d", old->str, old->age); |
| #endif |
| StrNCpy(old->str, str, NUM_CACHE_SIZE + 1); |
| /* old->format fill parser */ |
| old->age = (++NUMCounter); |
| |
| ent = old; |
| } |
| else |
| { |
| #ifdef DEBUG_TO_FROM_CHAR |
| elog(DEBUG_elog_output, "NEW (%d)", n_NUMCache); |
| #endif |
| ent = NUMCache + n_NUMCache; |
| StrNCpy(ent->str, str, NUM_CACHE_SIZE + 1); |
| /* ent->format fill parser */ |
| ent->age = (++NUMCounter); |
| ++n_NUMCache; |
| } |
| |
| zeroize_NUM(&ent->Num); |
| |
| last_NUMCacheEntry = ent; |
| return ent; |
| } |
| |
| static NUMCacheEntry * |
| NUM_cache_search(char *str) |
| { |
| int i = 0; |
| NUMCacheEntry *ent; |
| |
| /* counter overflow check - paranoia? */ |
| if (NUMCounter >= (INT_MAX - NUM_CACHE_FIELDS - 1)) |
| { |
| NUMCounter = 0; |
| |
| for (ent = NUMCache; ent <= (NUMCache + NUM_CACHE_FIELDS); ent++) |
| ent->age = (++NUMCounter); |
| } |
| |
| for (i = 0, ent = NUMCache; i < n_NUMCache; i++, ent++) |
| { |
| if (strcmp(ent->str, str) == 0) |
| { |
| ent->age = (++NUMCounter); |
| last_NUMCacheEntry = ent; |
| return ent; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static void |
| NUM_cache_remove(NUMCacheEntry *ent) |
| { |
| #ifdef DEBUG_TO_FROM_CHAR |
| elog(DEBUG_elog_output, "REMOVING ENTRY (%s)", ent->str); |
| #endif |
| ent->str[0] = '\0'; |
| ent->age = 0; |
| } |
| |
| /* ---------- |
| * Cache routine for NUM to_char version |
| * ---------- |
| */ |
| static FormatNode * |
| NUM_cache(int len, NUMDesc *Num, text *pars_str, bool *shouldFree) |
| { |
| FormatNode *format = NULL; |
| char *str; |
| |
| str = text_to_cstring(pars_str); |
| |
| /* |
| * Allocate new memory if format picture is bigger than static cache and |
| * not use cache (call parser always). This branches sets shouldFree to |
| * true, accordingly. |
| */ |
| if (len > NUM_CACHE_SIZE) |
| { |
| format = (FormatNode *) palloc((len + 1) * sizeof(FormatNode)); |
| |
| *shouldFree = true; |
| |
| zeroize_NUM(Num); |
| |
| parse_format(format, str, NUM_keywords, |
| NULL, NUM_index, NUM_TYPE, Num, |
| "to_char"); |
| |
| (format + len)->type = NODE_TYPE_END; /* Paranoia? */ |
| } |
| else |
| { |
| /* |
| * Use cache buffers |
| */ |
| NUMCacheEntry *ent; |
| |
| *shouldFree = false; |
| |
| if ((ent = NUM_cache_search(str)) == NULL) |
| { |
| ent = NUM_cache_getnew(str); |
| |
| /* |
| * Not in the cache, must run parser and save a new format-picture |
| * to the cache. |
| */ |
| parse_format(ent->format, str, NUM_keywords, |
| NULL, NUM_index, NUM_TYPE, &ent->Num, |
| "to_char"); |
| |
| (ent->format + len)->type = NODE_TYPE_END; /* Paranoia? */ |
| } |
| |
| format = ent->format; |
| |
| /* |
| * Copy cache to used struct |
| */ |
| Num->flag = ent->Num.flag; |
| Num->lsign = ent->Num.lsign; |
| Num->pre = ent->Num.pre; |
| Num->post = ent->Num.post; |
| Num->pre_lsign_num = ent->Num.pre_lsign_num; |
| Num->need_locale = ent->Num.need_locale; |
| Num->multi = ent->Num.multi; |
| Num->zero_start = ent->Num.zero_start; |
| Num->zero_end = ent->Num.zero_end; |
| } |
| |
| #ifdef DEBUG_TO_FROM_CHAR |
| /* dump_node(format, len); */ |
| dump_index(NUM_keywords, NUM_index); |
| #endif |
| |
| pfree(str); |
| return format; |
| } |
| |
| |
| static char * |
| int_to_roman(int number) |
| { |
| int len = 0, |
| num = 0; |
| char *p = NULL, |
| *result, |
| numstr[5]; |
| |
| result = (char *) palloc(16); |
| *result = '\0'; |
| |
| if (number > 3999 || number < 1) |
| { |
| fill_str(result, '#', 15); |
| return result; |
| } |
| len = snprintf(numstr, sizeof(numstr), "%d", number); |
| |
| for (p = numstr; *p != '\0'; p++, --len) |
| { |
| num = *p - 49; /* 48 ascii + 1 */ |
| if (num < 0) |
| continue; |
| |
| if (len > 3) |
| { |
| while (num-- != -1) |
| strcat(result, "M"); |
| } |
| else |
| { |
| if (len == 3) |
| strcat(result, rm100[num]); |
| else if (len == 2) |
| strcat(result, rm10[num]); |
| else if (len == 1) |
| strcat(result, rm1[num]); |
| } |
| } |
| return result; |
| } |
| |
| |
| |
| /* ---------- |
| * Locale |
| * ---------- |
| */ |
| static void |
| NUM_prepare_locale(NUMProc *Np) |
| { |
| if (Np->Num->need_locale) |
| { |
| struct lconv *lconv; |
| |
| /* |
| * Get locales |
| */ |
| lconv = PGLC_localeconv(); |
| |
| /* |
| * Positive / Negative number sign |
| */ |
| if (lconv->negative_sign && *lconv->negative_sign) |
| Np->L_negative_sign = lconv->negative_sign; |
| else |
| Np->L_negative_sign = "-"; |
| |
| if (lconv->positive_sign && *lconv->positive_sign) |
| Np->L_positive_sign = lconv->positive_sign; |
| else |
| Np->L_positive_sign = "+"; |
| |
| /* |
| * Number decimal point |
| */ |
| if (lconv->decimal_point && *lconv->decimal_point) |
| Np->decimal = lconv->decimal_point; |
| else |
| Np->decimal = "."; |
| |
| if (!IS_LDECIMAL(Np->Num)) |
| Np->decimal = "."; |
| |
| /* |
| * Number thousands separator |
| * |
| * Some locales (e.g. broken glibc pt_BR), have a comma for decimal, |
| * but "" for thousands_sep, so we set the thousands_sep too. |
| * http://archives.postgresql.org/pgsql-hackers/2007-11/msg00772.php |
| */ |
| if (lconv->thousands_sep && *lconv->thousands_sep) |
| Np->L_thousands_sep = lconv->thousands_sep; |
| /* Make sure thousands separator doesn't match decimal point symbol. */ |
| else if (strcmp(Np->decimal, ",") != 0) |
| Np->L_thousands_sep = ","; |
| else |
| Np->L_thousands_sep = "."; |
| |
| /* |
| * Currency symbol |
| */ |
| if (lconv->currency_symbol && *lconv->currency_symbol) |
| Np->L_currency_symbol = lconv->currency_symbol; |
| else |
| Np->L_currency_symbol = " "; |
| } |
| else |
| { |
| /* |
| * Default values |
| */ |
| Np->L_negative_sign = "-"; |
| Np->L_positive_sign = "+"; |
| Np->decimal = "."; |
| |
| Np->L_thousands_sep = ","; |
| Np->L_currency_symbol = " "; |
| } |
| } |
| |
| /* ---------- |
| * Return pointer of last relevant number after decimal point |
| * 12.0500 --> last relevant is '5' |
| * ---------- |
| */ |
| static char * |
| get_last_relevant_decnum(char *num) |
| { |
| char *result, |
| *p = strchr(num, '.'); |
| |
| #ifdef DEBUG_TO_FROM_CHAR |
| elog(DEBUG_elog_output, "get_last_relevant_decnum()"); |
| #endif |
| |
| if (!p) |
| p = num; |
| result = p; |
| |
| while (*(++p)) |
| { |
| if (*p != '0') |
| result = p; |
| } |
| |
| return result; |
| } |
| |
| /* ---------- |
| * Number extraction for TO_NUMBER() |
| * ---------- |
| */ |
| static void |
| NUM_numpart_from_char(NUMProc *Np, int id, int plen) |
| { |
| bool isread = FALSE; |
| |
| #ifdef DEBUG_TO_FROM_CHAR |
| elog(DEBUG_elog_output, " --- scan start --- id=%s", |
| (id == NUM_0 || id == NUM_9) ? "NUM_0/9" : id == NUM_DEC ? "NUM_DEC" : "???"); |
| #endif |
| |
| if (*Np->inout_p == ' ') |
| Np->inout_p++; |
| |
| #define OVERLOAD_TEST (Np->inout_p >= Np->inout + plen) |
| #define AMOUNT_TEST(_s) (plen-(Np->inout_p-Np->inout) >= _s) |
| |
| if (*Np->inout_p == ' ') |
| Np->inout_p++; |
| |
| if (OVERLOAD_TEST) |
| return; |
| |
| /* |
| * read sign before number |
| */ |
| if (*Np->number == ' ' && (id == NUM_0 || id == NUM_9) && |
| (Np->read_pre + Np->read_post) == 0) |
| { |
| #ifdef DEBUG_TO_FROM_CHAR |
| elog(DEBUG_elog_output, "Try read sign (%c), locale positive: %s, negative: %s", |
| *Np->inout_p, Np->L_positive_sign, Np->L_negative_sign); |
| #endif |
| |
| /* |
| * locale sign |
| */ |
| if (IS_LSIGN(Np->Num) && Np->Num->lsign == NUM_LSIGN_PRE) |
| { |
| int x = 0; |
| |
| #ifdef DEBUG_TO_FROM_CHAR |
| elog(DEBUG_elog_output, "Try read locale pre-sign (%c)", *Np->inout_p); |
| #endif |
| if ((x = strlen(Np->L_negative_sign)) && |
| AMOUNT_TEST(x) && |
| strncmp(Np->inout_p, Np->L_negative_sign, x) == 0) |
| { |
| Np->inout_p += x; |
| *Np->number = '-'; |
| } |
| else if ((x = strlen(Np->L_positive_sign)) && |
| AMOUNT_TEST(x) && |
| strncmp(Np->inout_p, Np->L_positive_sign, x) == 0) |
| { |
| Np->inout_p += x; |
| *Np->number = '+'; |
| } |
| } |
| else |
| { |
| #ifdef DEBUG_TO_FROM_CHAR |
| elog(DEBUG_elog_output, "Try read simple sign (%c)", *Np->inout_p); |
| #endif |
| |
| /* |
| * simple + - < > |
| */ |
| if (*Np->inout_p == '-' || (IS_BRACKET(Np->Num) && |
| *Np->inout_p == '<')) |
| { |
| *Np->number = '-'; /* set - */ |
| Np->inout_p++; |
| } |
| else if (*Np->inout_p == '+') |
| { |
| *Np->number = '+'; /* set + */ |
| Np->inout_p++; |
| } |
| } |
| } |
| |
| if (OVERLOAD_TEST) |
| return; |
| |
| #ifdef DEBUG_TO_FROM_CHAR |
| elog(DEBUG_elog_output, "Scan for numbers (%c), current number: '%s'", *Np->inout_p, Np->number); |
| #endif |
| |
| /* |
| * read digit |
| */ |
| if (isdigit((unsigned char) *Np->inout_p)) |
| { |
| if (Np->read_dec && Np->read_post == Np->Num->post) |
| return; |
| |
| *Np->number_p = *Np->inout_p; |
| Np->number_p++; |
| |
| if (Np->read_dec) |
| Np->read_post++; |
| else |
| Np->read_pre++; |
| |
| isread = TRUE; |
| |
| #ifdef DEBUG_TO_FROM_CHAR |
| elog(DEBUG_elog_output, "Read digit (%c)", *Np->inout_p); |
| #endif |
| |
| /* |
| * read decimal point |
| */ |
| } |
| else if (IS_DECIMAL(Np->Num) && Np->read_dec == FALSE) |
| { |
| #ifdef DEBUG_TO_FROM_CHAR |
| elog(DEBUG_elog_output, "Try read decimal point (%c)", *Np->inout_p); |
| #endif |
| if (*Np->inout_p == '.') |
| { |
| *Np->number_p = '.'; |
| Np->number_p++; |
| Np->read_dec = TRUE; |
| isread = TRUE; |
| } |
| else |
| { |
| int x = strlen(Np->decimal); |
| |
| #ifdef DEBUG_TO_FROM_CHAR |
| elog(DEBUG_elog_output, "Try read locale point (%c)", |
| *Np->inout_p); |
| #endif |
| if (x && AMOUNT_TEST(x) && strncmp(Np->inout_p, Np->decimal, x) == 0) |
| { |
| Np->inout_p += x - 1; |
| *Np->number_p = '.'; |
| Np->number_p++; |
| Np->read_dec = TRUE; |
| isread = TRUE; |
| } |
| } |
| } |
| |
| if (OVERLOAD_TEST) |
| return; |
| |
| /* |
| * Read sign behind "last" number |
| * |
| * We need sign detection because determine exact position of post-sign is |
| * difficult: |
| * |
| * FM9999.9999999S -> 123.001- 9.9S -> .5- FM9.999999MI -> |
| * 5.01- |
| */ |
| if (*Np->number == ' ' && Np->read_pre + Np->read_post > 0) |
| { |
| /* |
| * locale sign (NUM_S) is always anchored behind a last number, if: - |
| * locale sign expected - last read char was NUM_0/9 or NUM_DEC - and |
| * next char is not digit |
| */ |
| if (IS_LSIGN(Np->Num) && isread && |
| (Np->inout_p + 1) <= Np->inout + plen && |
| !isdigit((unsigned char) *(Np->inout_p + 1))) |
| { |
| int x; |
| char *tmp = Np->inout_p++; |
| |
| #ifdef DEBUG_TO_FROM_CHAR |
| elog(DEBUG_elog_output, "Try read locale post-sign (%c)", *Np->inout_p); |
| #endif |
| if ((x = strlen(Np->L_negative_sign)) && |
| AMOUNT_TEST(x) && |
| strncmp(Np->inout_p, Np->L_negative_sign, x) == 0) |
| { |
| Np->inout_p += x - 1; /* -1 .. NUM_processor() do inout_p++ */ |
| *Np->number = '-'; |
| } |
| else if ((x = strlen(Np->L_positive_sign)) && |
| AMOUNT_TEST(x) && |
| strncmp(Np->inout_p, Np->L_positive_sign, x) == 0) |
| { |
| Np->inout_p += x - 1; /* -1 .. NUM_processor() do inout_p++ */ |
| *Np->number = '+'; |
| } |
| if (*Np->number == ' ') |
| /* no sign read */ |
| Np->inout_p = tmp; |
| } |
| |
| /* |
| * try read non-locale sign, it's happen only if format is not exact |
| * and we cannot determine sign position of MI/PL/SG, an example: |
| * |
| * FM9.999999MI -> 5.01- |
| * |
| * if (.... && IS_LSIGN(Np->Num)==FALSE) prevents read wrong formats |
| * like to_number('1 -', '9S') where sign is not anchored to last |
| * number. |
| */ |
| else if (isread == FALSE && IS_LSIGN(Np->Num) == FALSE && |
| (IS_PLUS(Np->Num) || IS_MINUS(Np->Num))) |
| { |
| #ifdef DEBUG_TO_FROM_CHAR |
| elog(DEBUG_elog_output, "Try read simple post-sign (%c)", *Np->inout_p); |
| #endif |
| |
| /* |
| * simple + - |
| */ |
| if (*Np->inout_p == '-' || *Np->inout_p == '+') |
| /* NUM_processor() do inout_p++ */ |
| *Np->number = *Np->inout_p; |
| } |
| } |
| } |
| |
| #define IS_PREDEC_SPACE(_n) \ |
| (IS_ZERO((_n)->Num)==FALSE && \ |
| (_n)->number == (_n)->number_p && \ |
| *(_n)->number == '0' && \ |
| (_n)->Num->post != 0) |
| |
| /* ---------- |
| * Add digit or sign to number-string |
| * ---------- |
| */ |
| static void |
| NUM_numpart_to_char(NUMProc *Np, int id) |
| { |
| int end; |
| |
| if (IS_ROMAN(Np->Num)) |
| return; |
| |
| /* Note: in this elog() output not set '\0' in 'inout' */ |
| |
| #ifdef DEBUG_TO_FROM_CHAR |
| |
| /* |
| * Np->num_curr is number of current item in format-picture, it is not |
| * current position in inout! |
| */ |
| elog(DEBUG_elog_output, |
| "SIGN_WROTE: %d, CURRENT: %d, NUMBER_P: \"%s\", INOUT: \"%s\"", |
| Np->sign_wrote, |
| Np->num_curr, |
| Np->number_p, |
| Np->inout); |
| #endif |
| Np->num_in = FALSE; |
| |
| /* |
| * Write sign if real number will write to output Note: IS_PREDEC_SPACE() |
| * handle "9.9" --> " .1" |
| */ |
| if (Np->sign_wrote == FALSE && |
| (Np->num_curr >= Np->num_pre || (IS_ZERO(Np->Num) && Np->Num->zero_start == Np->num_curr)) && |
| (IS_PREDEC_SPACE(Np) == FALSE || (Np->last_relevant && *Np->last_relevant == '.'))) |
| { |
| if (IS_LSIGN(Np->Num)) |
| { |
| if (Np->Num->lsign == NUM_LSIGN_PRE) |
| { |
| if (Np->sign == '-') |
| strcpy(Np->inout_p, Np->L_negative_sign); |
| else |
| strcpy(Np->inout_p, Np->L_positive_sign); |
| Np->inout_p += strlen(Np->inout_p); |
| Np->sign_wrote = TRUE; |
| } |
| } |
| else if (IS_BRACKET(Np->Num)) |
| { |
| *Np->inout_p = Np->sign == '+' ? ' ' : '<'; |
| ++Np->inout_p; |
| Np->sign_wrote = TRUE; |
| } |
| else if (Np->sign == '+') |
| { |
| if (!IS_FILLMODE(Np->Num)) |
| { |
| *Np->inout_p = ' '; /* Write + */ |
| ++Np->inout_p; |
| } |
| Np->sign_wrote = TRUE; |
| } |
| else if (Np->sign == '-') |
| { /* Write - */ |
| *Np->inout_p = '-'; |
| ++Np->inout_p; |
| Np->sign_wrote = TRUE; |
| } |
| } |
| |
| |
| /* |
| * digits / FM / Zero / Dec. point |
| */ |
| if (id == NUM_9 || id == NUM_0 || id == NUM_D || id == NUM_DEC) |
| { |
| if (Np->num_curr < Np->num_pre && |
| (Np->Num->zero_start > Np->num_curr || !IS_ZERO(Np->Num))) |
| { |
| /* |
| * Write blank space |
| */ |
| if (!IS_FILLMODE(Np->Num)) |
| { |
| *Np->inout_p = ' '; /* Write ' ' */ |
| ++Np->inout_p; |
| } |
| } |
| else if (IS_ZERO(Np->Num) && |
| Np->num_curr < Np->num_pre && |
| Np->Num->zero_start <= Np->num_curr) |
| { |
| /* |
| * Write ZERO |
| */ |
| *Np->inout_p = '0'; /* Write '0' */ |
| ++Np->inout_p; |
| Np->num_in = TRUE; |
| } |
| else |
| { |
| /* |
| * Write Decimal point |
| */ |
| if (*Np->number_p == '.') |
| { |
| if (!Np->last_relevant || *Np->last_relevant != '.') |
| { |
| strcpy(Np->inout_p, Np->decimal); /* Write DEC/D */ |
| Np->inout_p += strlen(Np->inout_p); |
| } |
| |
| /* |
| * Ora 'n' -- FM9.9 --> 'n.' |
| */ |
| else if (IS_FILLMODE(Np->Num) && |
| Np->last_relevant && *Np->last_relevant == '.') |
| { |
| strcpy(Np->inout_p, Np->decimal); /* Write DEC/D */ |
| Np->inout_p += strlen(Np->inout_p); |
| } |
| } |
| else |
| { |
| /* |
| * Write Digits |
| */ |
| if (Np->last_relevant && Np->number_p > Np->last_relevant && |
| id != NUM_0) |
| ; |
| |
| /* |
| * '0.1' -- 9.9 --> ' .1' |
| */ |
| else if (IS_PREDEC_SPACE(Np)) |
| { |
| if (!IS_FILLMODE(Np->Num)) |
| { |
| *Np->inout_p = ' '; |
| ++Np->inout_p; |
| } |
| |
| /* |
| * '0' -- FM9.9 --> '0.' |
| */ |
| else if (Np->last_relevant && *Np->last_relevant == '.') |
| { |
| *Np->inout_p = '0'; |
| ++Np->inout_p; |
| } |
| } |
| else |
| { |
| *Np->inout_p = *Np->number_p; /* Write DIGIT */ |
| ++Np->inout_p; |
| Np->num_in = TRUE; |
| } |
| } |
| ++Np->number_p; |
| } |
| |
| end = Np->num_count + (Np->num_pre ? 1 : 0) + (IS_DECIMAL(Np->Num) ? 1 : 0); |
| |
| if (Np->last_relevant && Np->last_relevant == Np->number_p) |
| end = Np->num_curr; |
| |
| if (Np->num_curr + 1 == end) |
| { |
| if (Np->sign_wrote == TRUE && IS_BRACKET(Np->Num)) |
| { |
| *Np->inout_p = Np->sign == '+' ? ' ' : '>'; |
| ++Np->inout_p; |
| } |
| else if (IS_LSIGN(Np->Num) && Np->Num->lsign == NUM_LSIGN_POST) |
| { |
| if (Np->sign == '-') |
| strcpy(Np->inout_p, Np->L_negative_sign); |
| else |
| strcpy(Np->inout_p, Np->L_positive_sign); |
| Np->inout_p += strlen(Np->inout_p); |
| } |
| } |
| } |
| |
| ++Np->num_curr; |
| } |
| |
| /* |
| * Note: 'plen' is used in FROM_CHAR conversion and it's length of |
| * input (inout). In TO_CHAR conversion it's space before first number. |
| */ |
| static char * |
| NUM_processor(FormatNode *node, NUMDesc *Num, char *inout, char *number, |
| int plen, int sign, bool is_to_char) |
| { |
| FormatNode *n; |
| NUMProc _Np, |
| *Np = &_Np; |
| |
| MemSet(Np, 0, sizeof(NUMProc)); |
| |
| Np->Num = Num; |
| Np->is_to_char = is_to_char; |
| Np->number = number; |
| Np->inout = inout; |
| Np->last_relevant = NULL; |
| Np->read_post = 0; |
| Np->read_pre = 0; |
| Np->read_dec = FALSE; |
| |
| if (Np->Num->zero_start) |
| --Np->Num->zero_start; |
| |
| /* |
| * Roman correction |
| */ |
| if (IS_ROMAN(Np->Num)) |
| { |
| if (!Np->is_to_char) |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("\"RN\" not supported with function \"to_number\""))); |
| |
| Np->Num->lsign = Np->Num->pre_lsign_num = Np->Num->post = |
| Np->Num->pre = Np->num_pre = Np->sign = 0; |
| |
| if (IS_FILLMODE(Np->Num)) |
| { |
| Np->Num->flag = 0; |
| Np->Num->flag |= NUM_F_FILLMODE; |
| } |
| else |
| Np->Num->flag = 0; |
| Np->Num->flag |= NUM_F_ROMAN; |
| } |
| |
| /* |
| * Sign |
| */ |
| if (is_to_char) |
| { |
| Np->sign = sign; |
| |
| /* MI/PL/SG - write sign itself and not in number */ |
| if (IS_PLUS(Np->Num) || IS_MINUS(Np->Num)) |
| { |
| if (IS_PLUS(Np->Num) && IS_MINUS(Np->Num) == FALSE) |
| Np->sign_wrote = FALSE; /* need sign */ |
| else |
| Np->sign_wrote = TRUE; /* needn't sign */ |
| } |
| else |
| { |
| if (Np->sign != '-') |
| { |
| if (IS_BRACKET(Np->Num) && IS_FILLMODE(Np->Num)) |
| Np->Num->flag &= ~NUM_F_BRACKET; |
| if (IS_MINUS(Np->Num)) |
| Np->Num->flag &= ~NUM_F_MINUS; |
| } |
| else if (Np->sign != '+' && IS_PLUS(Np->Num)) |
| Np->Num->flag &= ~NUM_F_PLUS; |
| |
| if (Np->sign == '+' && IS_FILLMODE(Np->Num) && IS_LSIGN(Np->Num) == FALSE) |
| Np->sign_wrote = TRUE; /* needn't sign */ |
| else |
| Np->sign_wrote = FALSE; /* need sign */ |
| |
| if (Np->Num->lsign == NUM_LSIGN_PRE && Np->Num->pre == Np->Num->pre_lsign_num) |
| Np->Num->lsign = NUM_LSIGN_POST; |
| } |
| } |
| else |
| Np->sign = FALSE; |
| |
| /* |
| * Count |
| */ |
| Np->num_count = Np->Num->post + Np->Num->pre - 1; |
| |
| if (is_to_char) |
| { |
| Np->num_pre = plen; |
| |
| if (IS_FILLMODE(Np->Num)) |
| { |
| if (IS_DECIMAL(Np->Num)) |
| Np->last_relevant = get_last_relevant_decnum( |
| Np->number + |
| ((Np->Num->zero_end - Np->num_pre > 0) ? |
| Np->Num->zero_end - Np->num_pre : 0)); |
| } |
| |
| if (Np->sign_wrote == FALSE && Np->num_pre == 0) |
| ++Np->num_count; |
| } |
| else |
| { |
| Np->num_pre = 0; |
| *Np->number = ' '; /* sign space */ |
| *(Np->number + 1) = '\0'; |
| } |
| |
| Np->num_in = 0; |
| Np->num_curr = 0; |
| |
| #ifdef DEBUG_TO_FROM_CHAR |
| elog(DEBUG_elog_output, |
| "\n\tSIGN: '%c'\n\tNUM: '%s'\n\tPRE: %d\n\tPOST: %d\n\tNUM_COUNT: %d\n\tNUM_PRE: %d\n\tSIGN_WROTE: %s\n\tZERO: %s\n\tZERO_START: %d\n\tZERO_END: %d\n\tLAST_RELEVANT: %s\n\tBRACKET: %s\n\tPLUS: %s\n\tMINUS: %s\n\tFILLMODE: %s\n\tROMAN: %s", |
| Np->sign, |
| Np->number, |
| Np->Num->pre, |
| Np->Num->post, |
| Np->num_count, |
| Np->num_pre, |
| Np->sign_wrote ? "Yes" : "No", |
| IS_ZERO(Np->Num) ? "Yes" : "No", |
| Np->Num->zero_start, |
| Np->Num->zero_end, |
| Np->last_relevant ? Np->last_relevant : "<not set>", |
| IS_BRACKET(Np->Num) ? "Yes" : "No", |
| IS_PLUS(Np->Num) ? "Yes" : "No", |
| IS_MINUS(Np->Num) ? "Yes" : "No", |
| IS_FILLMODE(Np->Num) ? "Yes" : "No", |
| IS_ROMAN(Np->Num) ? "Yes" : "No" |
| ); |
| #endif |
| |
| /* |
| * Locale |
| */ |
| NUM_prepare_locale(Np); |
| |
| /* |
| * Processor direct cycle |
| */ |
| if (Np->is_to_char) |
| Np->number_p = Np->number; |
| else |
| Np->number_p = Np->number + 1; /* first char is space for sign */ |
| |
| for (n = node, Np->inout_p = Np->inout; n->type != NODE_TYPE_END; n++) |
| { |
| if (!Np->is_to_char) |
| { |
| /* |
| * Check non-string inout end |
| */ |
| if (Np->inout_p >= Np->inout + plen) |
| break; |
| } |
| |
| /* |
| * Format pictures actions |
| */ |
| if (n->type == NODE_TYPE_ACTION) |
| { |
| /* |
| * Create/reading digit/zero/blank/sing |
| * |
| * 'NUM_S' note: The locale sign is anchored to number and we |
| * read/write it when we work with first or last number |
| * (NUM_0/NUM_9). This is reason why NUM_S missing in follow |
| * switch(). |
| */ |
| switch (n->key->id) |
| { |
| case NUM_9: |
| case NUM_0: |
| case NUM_DEC: |
| case NUM_D: |
| if (Np->is_to_char) |
| { |
| NUM_numpart_to_char(Np, n->key->id); |
| continue; /* for() */ |
| } |
| else |
| { |
| NUM_numpart_from_char(Np, n->key->id, plen); |
| break; /* switch() case: */ |
| } |
| |
| case NUM_COMMA: |
| if (Np->is_to_char) |
| { |
| if (!Np->num_in) |
| { |
| if (IS_FILLMODE(Np->Num)) |
| continue; |
| else |
| *Np->inout_p = ' '; |
| } |
| else |
| *Np->inout_p = ','; |
| } |
| else |
| { |
| if (!Np->num_in) |
| { |
| if (IS_FILLMODE(Np->Num)) |
| continue; |
| } |
| } |
| break; |
| |
| case NUM_G: |
| if (Np->is_to_char) |
| { |
| if (!Np->num_in) |
| { |
| if (IS_FILLMODE(Np->Num)) |
| continue; |
| else |
| { |
| int x = strlen(Np->L_thousands_sep); |
| |
| memset(Np->inout_p, ' ', x); |
| Np->inout_p += x - 1; |
| } |
| } |
| else |
| { |
| strcpy(Np->inout_p, Np->L_thousands_sep); |
| Np->inout_p += strlen(Np->inout_p) - 1; |
| } |
| } |
| else |
| { |
| if (!Np->num_in) |
| { |
| if (IS_FILLMODE(Np->Num)) |
| continue; |
| } |
| Np->inout_p += strlen(Np->L_thousands_sep) - 1; |
| } |
| break; |
| |
| case NUM_L: |
| if (Np->is_to_char) |
| { |
| strcpy(Np->inout_p, Np->L_currency_symbol); |
| Np->inout_p += strlen(Np->inout_p) - 1; |
| } |
| else |
| Np->inout_p += strlen(Np->L_currency_symbol) - 1; |
| break; |
| |
| case NUM_RN: |
| if (IS_FILLMODE(Np->Num)) |
| { |
| strcpy(Np->inout_p, Np->number_p); |
| Np->inout_p += strlen(Np->inout_p) - 1; |
| } |
| else |
| { |
| sprintf(Np->inout_p, "%15s", Np->number_p); |
| Np->inout_p += strlen(Np->inout_p) - 1; |
| } |
| break; |
| |
| case NUM_rn: |
| if (IS_FILLMODE(Np->Num)) |
| { |
| strcpy(Np->inout_p, str_tolower_z(Np->number_p)); |
| Np->inout_p += strlen(Np->inout_p) - 1; |
| } |
| else |
| { |
| sprintf(Np->inout_p, "%15s", str_tolower_z(Np->number_p)); |
| Np->inout_p += strlen(Np->inout_p) - 1; |
| } |
| break; |
| |
| case NUM_th: |
| if (IS_ROMAN(Np->Num) || *Np->number == '#' || |
| Np->sign == '-' || IS_DECIMAL(Np->Num)) |
| continue; |
| |
| if (Np->is_to_char) |
| strcpy(Np->inout_p, get_th(Np->number, TH_LOWER)); |
| Np->inout_p += 1; |
| break; |
| |
| case NUM_TH: |
| if (IS_ROMAN(Np->Num) || *Np->number == '#' || |
| Np->sign == '-' || IS_DECIMAL(Np->Num)) |
| continue; |
| |
| if (Np->is_to_char) |
| strcpy(Np->inout_p, get_th(Np->number, TH_UPPER)); |
| Np->inout_p += 1; |
| break; |
| |
| case NUM_MI: |
| if (Np->is_to_char) |
| { |
| if (Np->sign == '-') |
| *Np->inout_p = '-'; |
| else if (IS_FILLMODE(Np->Num)) |
| continue; |
| else |
| *Np->inout_p = ' '; |
| } |
| else |
| { |
| if (*Np->inout_p == '-') |
| *Np->number = '-'; |
| } |
| break; |
| |
| case NUM_PL: |
| if (Np->is_to_char) |
| { |
| if (Np->sign == '+') |
| *Np->inout_p = '+'; |
| else if (IS_FILLMODE(Np->Num)) |
| continue; |
| else |
| *Np->inout_p = ' '; |
| } |
| else |
| { |
| if (*Np->inout_p == '+') |
| *Np->number = '+'; |
| } |
| break; |
| |
| case NUM_SG: |
| if (Np->is_to_char) |
| *Np->inout_p = Np->sign; |
| |
| else |
| { |
| if (*Np->inout_p == '-') |
| *Np->number = '-'; |
| else if (*Np->inout_p == '+') |
| *Np->number = '+'; |
| } |
| break; |
| |
| |
| default: |
| continue; |
| break; |
| } |
| } |
| else |
| { |
| /* |
| * Remove to output char from input in TO_CHAR |
| */ |
| if (Np->is_to_char) |
| *Np->inout_p = n->character; |
| } |
| Np->inout_p++; |
| } |
| |
| if (Np->is_to_char) |
| { |
| *Np->inout_p = '\0'; |
| return Np->inout; |
| } |
| else |
| { |
| if (*(Np->number_p - 1) == '.') |
| *(Np->number_p - 1) = '\0'; |
| else |
| *Np->number_p = '\0'; |
| |
| /* |
| * Correction - precision of dec. number |
| */ |
| Np->Num->post = Np->read_post; |
| |
| #ifdef DEBUG_TO_FROM_CHAR |
| elog(DEBUG_elog_output, "TO_NUMBER (number): '%s'", Np->number); |
| #endif |
| return Np->number; |
| } |
| } |
| |
| /* ---------- |
| * MACRO: Start part of NUM - for all NUM's to_char variants |
| * (sorry, but I hate copy same code - macro is better..) |
| * ---------- |
| */ |
| #define NUM_TOCHAR_prepare \ |
| do { \ |
| len = VARSIZE(fmt) - VARHDRSZ; \ |
| if (len <= 0 || len >= (INT_MAX-VARHDRSZ)/NUM_MAX_ITEM_SIZ) \ |
| PG_RETURN_TEXT_P(cstring_to_text("")); \ |
| result = (text *) palloc0((len * NUM_MAX_ITEM_SIZ) + 1 + VARHDRSZ); \ |
| format = NUM_cache(len, &Num, fmt, &shouldFree); \ |
| } while (0) |
| |
| /* ---------- |
| * MACRO: Finish part of NUM |
| * ---------- |
| */ |
| #define NUM_TOCHAR_finish \ |
| do { \ |
| NUM_processor(format, &Num, VARDATA(result), numstr, plen, sign, true); \ |
| \ |
| if (shouldFree) \ |
| pfree(format); \ |
| \ |
| /* \ |
| * Convert null-terminated representation of result to standard text. \ |
| * The result is usually much bigger than it needs to be, but there \ |
| * seems little point in realloc'ing it smaller. \ |
| */ \ |
| len = strlen(VARDATA(result)); \ |
| SET_VARSIZE(result, len + VARHDRSZ); \ |
| } while(0) |
| |
| /* ------------------- |
| * NUMERIC to_number() (convert string to numeric) |
| * ------------------- |
| */ |
| Datum |
| numeric_to_number(PG_FUNCTION_ARGS) |
| { |
| text *value = PG_GETARG_TEXT_P(0); |
| text *fmt = PG_GETARG_TEXT_P(1); |
| NUMDesc Num; |
| Datum result; |
| FormatNode *format; |
| char *numstr; |
| bool shouldFree; |
| int len = 0; |
| int scale, |
| precision; |
| |
| len = VARSIZE(fmt) - VARHDRSZ; |
| |
| if (len <= 0 || len >= INT_MAX/NUM_MAX_ITEM_SIZ) |
| PG_RETURN_NULL(); |
| |
| format = NUM_cache(len, &Num, fmt, &shouldFree); |
| |
| numstr = (char *) palloc((len * NUM_MAX_ITEM_SIZ) + 1); |
| |
| NUM_processor(format, &Num, VARDATA(value), numstr, |
| VARSIZE(value) - VARHDRSZ, 0, false); |
| |
| scale = Num.post; |
| precision = Max(0, Num.pre) + scale; |
| |
| if (shouldFree) |
| pfree(format); |
| |
| result = DirectFunctionCall3(numeric_in, |
| CStringGetDatum(numstr), |
| ObjectIdGetDatum(InvalidOid), |
| Int32GetDatum(((precision << 16) | scale) + VARHDRSZ)); |
| pfree(numstr); |
| return result; |
| } |
| |
| /* ------------------ |
| * NUMERIC to_char() |
| * ------------------ |
| */ |
| Datum |
| numeric_to_char(PG_FUNCTION_ARGS) |
| { |
| Numeric value = PG_GETARG_NUMERIC(0); |
| text *fmt = PG_GETARG_TEXT_P(1); |
| NUMDesc Num; |
| FormatNode *format; |
| text *result; |
| bool shouldFree; |
| int len = 0, |
| plen = 0, |
| sign = 0; |
| char *numstr, |
| *orgnum, |
| *p; |
| Numeric x; |
| |
| NUM_TOCHAR_prepare; |
| |
| /* |
| * On DateType depend part (numeric) |
| */ |
| if (IS_ROMAN(&Num)) |
| { |
| x = DatumGetNumeric(DirectFunctionCall2(numeric_round, |
| NumericGetDatum(value), |
| Int32GetDatum(0))); |
| numstr = orgnum = |
| int_to_roman(DatumGetInt32(DirectFunctionCall1(numeric_int4, |
| NumericGetDatum(x)))); |
| } |
| else |
| { |
| Numeric val = value; |
| |
| if (IS_MULTI(&Num)) |
| { |
| Numeric a = DatumGetNumeric(DirectFunctionCall1(int4_numeric, |
| Int32GetDatum(10))); |
| Numeric b = DatumGetNumeric(DirectFunctionCall1(int4_numeric, |
| Int32GetDatum(Num.multi))); |
| |
| x = DatumGetNumeric(DirectFunctionCall2(numeric_power, |
| NumericGetDatum(a), |
| NumericGetDatum(b))); |
| val = DatumGetNumeric(DirectFunctionCall2(numeric_mul, |
| NumericGetDatum(value), |
| NumericGetDatum(x))); |
| Num.pre += Num.multi; |
| } |
| |
| x = DatumGetNumeric(DirectFunctionCall2(numeric_round, |
| NumericGetDatum(val), |
| Int32GetDatum(Num.post))); |
| orgnum = DatumGetCString(DirectFunctionCall1(numeric_out, |
| NumericGetDatum(x))); |
| |
| if (*orgnum == '-') |
| { |
| sign = '-'; |
| numstr = orgnum + 1; |
| } |
| else |
| { |
| sign = '+'; |
| numstr = orgnum; |
| } |
| if ((p = strchr(numstr, '.'))) |
| len = p - numstr; |
| else |
| len = strlen(numstr); |
| |
| if (Num.pre > len) |
| plen = Num.pre - len; |
| else if (len > Num.pre) |
| { |
| numstr = (char *) palloc(Num.pre + Num.post + 2); |
| fill_str(numstr, '#', Num.pre + Num.post + 1); |
| *(numstr + Num.pre) = '.'; |
| } |
| } |
| |
| NUM_TOCHAR_finish; |
| PG_RETURN_TEXT_P(result); |
| } |
| |
| /* --------------- |
| * INT4 to_char() |
| * --------------- |
| */ |
| Datum |
| int4_to_char(PG_FUNCTION_ARGS) |
| { |
| int32 value = PG_GETARG_INT32(0); |
| text *fmt = PG_GETARG_TEXT_P(1); |
| NUMDesc Num; |
| FormatNode *format; |
| text *result; |
| bool shouldFree; |
| int len = 0, |
| plen = 0, |
| sign = 0; |
| char *numstr, |
| *orgnum; |
| |
| NUM_TOCHAR_prepare; |
| |
| /* |
| * On DateType depend part (int32) |
| */ |
| if (IS_ROMAN(&Num)) |
| numstr = orgnum = int_to_roman(value); |
| else |
| { |
| if (IS_MULTI(&Num)) |
| { |
| orgnum = DatumGetCString(DirectFunctionCall1(int4out, |
| Int32GetDatum(value * ((int32) pow((double) 10, (double) Num.multi))))); |
| Num.pre += Num.multi; |
| } |
| else |
| { |
| orgnum = DatumGetCString(DirectFunctionCall1(int4out, |
| Int32GetDatum(value))); |
| } |
| |
| if (*orgnum == '-') |
| { |
| sign = '-'; |
| orgnum++; |
| } |
| else |
| sign = '+'; |
| len = strlen(orgnum); |
| |
| if (Num.post) |
| { |
| numstr = (char *) palloc(len + Num.post + 2); |
| strcpy(numstr, orgnum); |
| *(numstr + len) = '.'; |
| memset(numstr + len + 1, '0', Num.post); |
| *(numstr + len + Num.post + 1) = '\0'; |
| } |
| else |
| numstr = orgnum; |
| |
| if (Num.pre > len) |
| plen = Num.pre - len; |
| else if (len > Num.pre) |
| { |
| numstr = (char *) palloc(Num.pre + Num.post + 2); |
| fill_str(numstr, '#', Num.pre + Num.post + 1); |
| *(numstr + Num.pre) = '.'; |
| } |
| } |
| |
| NUM_TOCHAR_finish; |
| PG_RETURN_TEXT_P(result); |
| } |
| |
| /* --------------- |
| * INT8 to_char() |
| * --------------- |
| */ |
| Datum |
| int8_to_char(PG_FUNCTION_ARGS) |
| { |
| int64 value = PG_GETARG_INT64(0); |
| text *fmt = PG_GETARG_TEXT_P(1); |
| NUMDesc Num; |
| FormatNode *format; |
| text *result; |
| bool shouldFree; |
| int len = 0, |
| plen = 0, |
| sign = 0; |
| char *numstr, |
| *orgnum; |
| |
| NUM_TOCHAR_prepare; |
| |
| /* |
| * On DateType depend part (int32) |
| */ |
| if (IS_ROMAN(&Num)) |
| { |
| /* Currently don't support int8 conversion to roman... */ |
| numstr = orgnum = int_to_roman(DatumGetInt32( |
| DirectFunctionCall1(int84, Int64GetDatum(value)))); |
| } |
| else |
| { |
| if (IS_MULTI(&Num)) |
| { |
| double multi = pow((double) 10, (double) Num.multi); |
| |
| value = DatumGetInt64(DirectFunctionCall2(int8mul, |
| Int64GetDatum(value), |
| DirectFunctionCall1(dtoi8, |
| Float8GetDatum(multi)))); |
| Num.pre += Num.multi; |
| } |
| |
| orgnum = DatumGetCString(DirectFunctionCall1(int8out, |
| Int64GetDatum(value))); |
| |
| if (*orgnum == '-') |
| { |
| sign = '-'; |
| orgnum++; |
| } |
| else |
| sign = '+'; |
| len = strlen(orgnum); |
| |
| if (Num.post) |
| { |
| numstr = (char *) palloc(len + Num.post + 2); |
| strcpy(numstr, orgnum); |
| *(numstr + len) = '.'; |
| memset(numstr + len + 1, '0', Num.post); |
| *(numstr + len + Num.post + 1) = '\0'; |
| } |
| else |
| numstr = orgnum; |
| |
| if (Num.pre > len) |
| plen = Num.pre - len; |
| else if (len > Num.pre) |
| { |
| numstr = (char *) palloc(Num.pre + Num.post + 2); |
| fill_str(numstr, '#', Num.pre + Num.post + 1); |
| *(numstr + Num.pre) = '.'; |
| } |
| } |
| |
| NUM_TOCHAR_finish; |
| PG_RETURN_TEXT_P(result); |
| } |
| |
| /* ----------------- |
| * FLOAT4 to_char() |
| * ----------------- |
| */ |
| Datum |
| float4_to_char(PG_FUNCTION_ARGS) |
| { |
| float4 value = PG_GETARG_FLOAT4(0); |
| text *fmt = PG_GETARG_TEXT_P(1); |
| NUMDesc Num; |
| FormatNode *format; |
| text *result; |
| bool shouldFree; |
| int len = 0, |
| plen = 0, |
| sign = 0; |
| char *numstr, |
| *orgnum, |
| *p; |
| |
| NUM_TOCHAR_prepare; |
| |
| if (IS_ROMAN(&Num)) |
| numstr = orgnum = int_to_roman((int) rint(value)); |
| else |
| { |
| float4 val = value; |
| |
| if (IS_MULTI(&Num)) |
| { |
| float multi = pow((double) 10, (double) Num.multi); |
| |
| val = value * multi; |
| Num.pre += Num.multi; |
| } |
| |
| orgnum = (char *) palloc(MAXFLOATWIDTH + 1); |
| snprintf(orgnum, MAXFLOATWIDTH + 1, "%.0f", fabs(val)); |
| len = strlen(orgnum); |
| if (Num.pre > len) |
| plen = Num.pre - len; |
| if (len >= FLT_DIG) |
| Num.post = 0; |
| else if (Num.post + len > FLT_DIG) |
| Num.post = FLT_DIG - len; |
| snprintf(orgnum, MAXFLOATWIDTH + 1, "%.*f", Num.post, val); |
| |
| if (*orgnum == '-') |
| { /* < 0 */ |
| sign = '-'; |
| numstr = orgnum + 1; |
| } |
| else |
| { |
| sign = '+'; |
| numstr = orgnum; |
| } |
| if ((p = strchr(numstr, '.'))) |
| len = p - numstr; |
| else |
| len = strlen(numstr); |
| |
| if (Num.pre > len) |
| plen = Num.pre - len; |
| else if (len > Num.pre) |
| { |
| numstr = (char *) palloc(Num.pre + Num.post + 2); |
| fill_str(numstr, '#', Num.pre + Num.post + 1); |
| *(numstr + Num.pre) = '.'; |
| } |
| } |
| |
| NUM_TOCHAR_finish; |
| PG_RETURN_TEXT_P(result); |
| } |
| |
| /* ----------------- |
| * FLOAT8 to_char() |
| * ----------------- |
| */ |
| Datum |
| float8_to_char(PG_FUNCTION_ARGS) |
| { |
| float8 value = PG_GETARG_FLOAT8(0); |
| text *fmt = PG_GETARG_TEXT_P(1); |
| NUMDesc Num; |
| FormatNode *format; |
| text *result; |
| bool shouldFree; |
| int len = 0, |
| plen = 0, |
| sign = 0; |
| char *numstr, |
| *orgnum, |
| *p; |
| |
| NUM_TOCHAR_prepare; |
| |
| if (IS_ROMAN(&Num)) |
| numstr = orgnum = int_to_roman((int) rint(value)); |
| else |
| { |
| float8 val = value; |
| |
| if (IS_MULTI(&Num)) |
| { |
| double multi = pow((double) 10, (double) Num.multi); |
| |
| val = value * multi; |
| Num.pre += Num.multi; |
| } |
| orgnum = (char *) palloc(MAXDOUBLEWIDTH + 1); |
| len = snprintf(orgnum, MAXDOUBLEWIDTH + 1, "%.0f", fabs(val)); |
| if (Num.pre > len) |
| plen = Num.pre - len; |
| if (len >= DBL_DIG) |
| Num.post = 0; |
| else if (Num.post + len > DBL_DIG) |
| Num.post = DBL_DIG - len; |
| snprintf(orgnum, MAXDOUBLEWIDTH + 1, "%.*f", Num.post, val); |
| |
| if (*orgnum == '-') |
| { /* < 0 */ |
| sign = '-'; |
| numstr = orgnum + 1; |
| } |
| else |
| { |
| sign = '+'; |
| numstr = orgnum; |
| } |
| if ((p = strchr(numstr, '.'))) |
| len = p - numstr; |
| else |
| len = strlen(numstr); |
| |
| if (Num.pre > len) |
| plen = Num.pre - len; |
| else if (len > Num.pre) |
| { |
| numstr = (char *) palloc(Num.pre + Num.post + 2); |
| fill_str(numstr, '#', Num.pre + Num.post + 1); |
| *(numstr + Num.pre) = '.'; |
| } |
| } |
| |
| NUM_TOCHAR_finish; |
| PG_RETURN_TEXT_P(result); |
| } |