postgresql/src/backend/utils/adt/formatting.c

4796 lines
98 KiB
C
Raw Normal View History

/* -----------------------------------------------------------------------
* formatting.c
*
* $Header: /cvsroot/pgsql/src/backend/utils/adt/formatting.c,v 1.66 2003/08/04 23:59:38 tgl Exp $
*
*
* Portions Copyright (c) 1999-2003, 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 buffers and is persistent across transactions.
* If format-picture is bigger than cache buffer, 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.
2000-04-07 21:17:51 +02:00
* 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
2001-03-22 05:01:46 +01:00
* than Oracle :-),
* to_char('Hello', 'X X X X X') -> 'H e l l o'
*
* -----------------------------------------------------------------------
*/
/* ----------
* UnComment me for DEBUG
* ----------
*/
2000-04-07 21:17:51 +02:00
/***
#define DEBUG_TO_FROM_CHAR
#define DEBUG_elog_output DEBUG3
***/
#include "postgres.h"
#include <ctype.h>
#include <sys/time.h>
#include <unistd.h>
#include <math.h>
2000-01-26 07:33:49 +01:00
#include <float.h>
#include "utils/builtins.h"
2000-07-01 23:27:14 +02:00
#include "utils/date.h"
#include "utils/datetime.h"
#include "utils/formatting.h"
2000-07-01 23:27:14 +02:00
#include "utils/int8.h"
#include "utils/numeric.h"
2000-07-01 23:27:14 +02:00
#include "utils/pg_locale.h"
/* ----------
* Routines type
* ----------
*/
#define DCH_TYPE 1 /* DATE-TIME version */
#define NUM_TYPE 2 /* NUMBER version */
/* ----------
* KeyWord Index (ascii from position 32 (' ') to 126 (~))
* ----------
*/
2000-04-07 21:17:51 +02:00
#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 64
#define MAXDOUBLEWIDTH 128
/* ----------
* External (defined in PgSQL dt.c (timestamp utils))
* ----------
*/
2001-03-22 05:01:46 +01:00
extern char *months[], /* month abbreviation */
*days[]; /* full days */
/* ----------
* Format parser structs
* ----------
*/
typedef struct
{
2001-03-22 05:01:46 +01:00
char *name; /* suffix string */
int len, /* suffix length */
id, /* used in node->suffix */
type; /* prefix / postfix */
} KeySuffix;
typedef struct
{
2001-03-22 05:01:46 +01:00
char *name; /* keyword */
/* action for keyword */
int len, /* keyword length */
(*action) (),
id; /* keyword id */
bool isitdigit; /* is expected output/input digit */
} KeyWord;
typedef struct
{
2001-03-22 05:01:46 +01:00
int type; /* node type */
KeyWord *key; /* if node type is KEYWORD */
int character, /* if node type is CHAR */
suffix; /* keyword suffix */
} FormatNode;
#define NODE_TYPE_END 1
#define NODE_TYPE_ACTION 2
#define NODE_TYPE_CHAR 3
#define SUFFTYPE_PREFIX 1
#define SUFFTYPE_POSTFIX 2
/* ----------
* Full months
* ----------
*/
static char *months_full[] = {
"January", "February", "March", "April", "May", "June", "July",
"August", "September", "October", "November", "December", NULL
};
/* ----------
* AC / DC
* ----------
*/
#define YEAR_ABS(_y) (_y < 0 ? -(_y -1) : _y)
#define BC_STR_ORIG " BC"
2000-04-07 21:17:51 +02:00
#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"
/* ----------
* 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"
/* ----------
* Months in roman-numeral
* (Must be conversely for seq_search (in FROM_CHAR), because
* 'VIII' must be over 'V')
* ----------
*/
static char *rm_months_upper[] =
{"XII", "XI", "X", "IX", "VIII", "VII", "VI", "V", "IV", "III", "II", "I", NULL};
2000-04-07 21:17:51 +02:00
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 TO_CHAR 1
#define FROM_CHAR 2
#define ONE_UPPER 1 /* Name */
#define ALL_UPPER 2 /* NAME */
#define ALL_LOWER 3 /* name */
#define FULL_SIZ 0
#define MAX_MON_LEN 3
#define MAX_DY_LEN 3
#define TH_UPPER 1
#define TH_LOWER 2
/* ----------
* Flags for DCH version
* ----------
*/
2001-03-22 05:01:46 +01:00
static int DCH_global_flag = 0;
#define DCH_F_FX 0x01
#define IS_FX (DCH_global_flag & DCH_F_FX)
/* ----------
* Number description struct
* ----------
*/
typedef struct
{
2001-03-22 05:01:46 +01:00
int pre, /* (count) numbers before decimal */
post, /* (count) numbers after decimal */
lsign, /* want locales sign */
flag, /* number parameters */
2001-03-22 05:01:46 +01:00
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
* ----------
*/
2003-03-27 17:35:31 +01:00
#define NUM_F_DECIMAL (1 << 1)
#define NUM_F_LDECIMAL (1 << 2)
#define NUM_F_ZERO (1 << 3)
2003-08-04 02:43:34 +02:00
#define NUM_F_BLANK (1 << 4)
2003-03-27 17:35:31 +01:00
#define NUM_F_FILLMODE (1 << 5)
2003-08-04 02:43:34 +02:00
#define NUM_F_LSIGN (1 << 6)
2003-03-27 17:35:31 +01:00
#define NUM_F_BRACKET (1 << 7)
2003-08-04 02:43:34 +02:00
#define NUM_F_MINUS (1 << 8)
2003-03-27 17:35:31 +01:00
#define NUM_F_PLUS (1 << 9)
2003-08-04 02:43:34 +02:00
#define NUM_F_ROMAN (1 << 10)
2003-03-27 17:35:31 +01:00
#define NUM_F_MULTI (1 << 11)
2003-08-04 02:43:34 +02:00
#define NUM_F_PLUS_POST (1 << 12)
2003-03-27 17:35:31 +01:00
#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)
2001-03-22 05:01:46 +01:00
#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)
2001-03-22 05:01:46 +01:00
#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:
2001-03-22 05:01:46 +01:00
* 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];
2001-03-22 05:01:46 +01:00
int age;
} DCHCacheEntry;
typedef struct
{
FormatNode format[NUM_CACHE_SIZE + 1];
char str[NUM_CACHE_SIZE + 1];
2001-03-22 05:01:46 +01:00
int age;
NUMDesc Num;
} NUMCacheEntry;
/* global cache for --- date/time part */
2001-03-22 05:01:46 +01:00
static DCHCacheEntry DCHCache[DCH_CACHE_FIELDS + 1];
static int n_DCHCache = 0; /* number of entries */
static int DCHCounter = 0;
/* global cache for --- number part */
2001-03-22 05:01:46 +01:00
static NUMCacheEntry NUMCache[NUM_CACHE_FIELDS + 1];
static NUMCacheEntry *last_NUMCacheEntry;
static int n_NUMCache = 0; /* number of entries */
static int NUMCounter = 0;
#define MAX_INT32 (2147483600)
/* ----------
* For char->date/time conversion
* ----------
*/
2001-03-22 05:01:46 +01:00
typedef struct
{
int hh,
am,
pm,
mi,
ss,
ssss,
d,
dd,
ddd,
mm,
ms,
year,
2001-03-22 05:01:46 +01:00
bc,
iw,
ww,
w,
2001-03-22 05:01:46 +01:00
cc,
q,
j,
us;
} 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:\nhh %d\nam %d\npm %d\nmi %d\nss %d\nssss %d\nd %d\ndd %d\nddd %d\nmm %d\nms: %d\nyear %d\nbc %d\niw %d\nww %d\nw %d\ncc %d\nq %d\nj %d\nus: %d", \
(_X)->hh, (_X)->am, (_X)->pm, (_X)->mi, (_X)->ss, \
(_X)->ssss, (_X)->d, (_X)->dd, (_X)->ddd, (_X)->mm, (_X)->ms, \
(_X)->year, (_X)->bc, (_X)->iw, (_X)->ww, (_X)->w, \
(_X)->cc, (_X)->q, (_X)->j, (_X)->us);
#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 tm tm; /* classic 'tm' struct */
Support alternate storage scheme of 64-bit integer for date/time types. Use "--enable-integer-datetimes" in configuration to use this rather than the original float8 storage. I would recommend the integer-based storage for any platform on which it is available. We perhaps should make this the default for the production release. Change timezone(timestamptz) results to return timestamp rather than a character string. Formerly, we didn't have a way to represent timestamps with an explicit time zone other than freezing the info into a string. Now, we can reasonably omit the explicit time zone from the result and return a timestamp with values appropriate for the specified time zone. Much cleaner, and if you need the time zone in the result you can put it into a character string pretty easily anyway. Allow fractional seconds in date/time types even for dates prior to 1BC. Limit timestamp data types to 6 decimal places of precision. Just right for a micro-second storage of int8 date/time types, and reduces the number of places ad-hoc rounding was occuring for the float8-based types. Use lookup tables for precision/rounding calculations for timestamp and interval types. Formerly used pow() to calculate the desired value but with a more limited range there is no reason to not type in a lookup table. Should be *much* better performance, though formerly there were some optimizations to help minimize the number of times pow() was called. Define a HAVE_INT64_TIMESTAMP variable. Based on the configure option "--enable-integer-datetimes" and the existing internal INT64_IS_BUSTED. Add explicit date/interval operators and functions for addition and subtraction. Formerly relied on implicit type promotion from date to timestamp with time zone. Change timezone conversion functions for the timetz type from "timetz()" to "timezone()". This is consistant with other time zone coersion functions for other types. Bump the catalog version to 200204201. Fix up regression tests to reflect changes in fractional seconds representation for date/times in BC eras. All regression tests pass on my Linux box.
2002-04-21 21:52:18 +02:00
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)
/*****************************************************************************
* KeyWords definition & action
*****************************************************************************/
static int dch_global(int arg, char *inout, int suf, int flag, FormatNode *node, void *data);
static int dch_time(int arg, char *inout, int suf, int flag, FormatNode *node, void *data);
static int dch_date(int arg, char *inout, int suf, int flag, FormatNode *node, void *data);
/* ----------
* Suffixes:
* ----------
*/
#define DCH_S_FM 0x01
#define DCH_S_TH 0x02
#define DCH_S_th 0x04
#define DCH_S_SP 0x08
/* ----------
* 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)
/* ----------
* 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},
{"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_MM) 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_IW,
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_iw,
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,
2000-04-07 21:17:51 +02:00
/* 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 KeyWord DCH_keywords[] = {
/* keyword, len, func, type, isitdigit is in Index */
{"A.D.", 4, dch_date, DCH_A_D, FALSE}, /* A */
{"A.M.", 4, dch_time, DCH_A_M, FALSE},
{"AD", 2, dch_date, DCH_AD, FALSE},
{"AM", 2, dch_time, DCH_AM, FALSE},
{"B.C.", 4, dch_date, DCH_B_C, FALSE}, /* B */
{"BC", 2, dch_date, DCH_BC, FALSE},
2001-03-22 05:01:46 +01:00
{"CC", 2, dch_date, DCH_CC, TRUE}, /* C */
{"DAY", 3, dch_date, DCH_DAY, FALSE}, /* D */
{"DDD", 3, dch_date, DCH_DDD, TRUE},
{"DD", 2, dch_date, DCH_DD, TRUE},
{"DY", 2, dch_date, DCH_DY, FALSE},
{"Day", 3, dch_date, DCH_Day, FALSE},
{"Dy", 2, dch_date, DCH_Dy, FALSE},
{"D", 1, dch_date, DCH_D, TRUE},
{"FX", 2, dch_global, DCH_FX, FALSE}, /* F */
{"HH24", 4, dch_time, DCH_HH24, TRUE}, /* H */
{"HH12", 4, dch_time, DCH_HH12, TRUE},
{"HH", 2, dch_time, DCH_HH, TRUE},
2001-03-22 05:01:46 +01:00
{"IW", 2, dch_date, DCH_IW, TRUE}, /* I */
{"J", 1, dch_date, DCH_J, TRUE}, /* J */
{"MI", 2, dch_time, DCH_MI, TRUE},
{"MM", 2, dch_date, DCH_MM, TRUE},
{"MONTH", 5, dch_date, DCH_MONTH, FALSE},
{"MON", 3, dch_date, DCH_MON, FALSE},
{"MS", 2, dch_time, DCH_MS, TRUE},
{"Month", 5, dch_date, DCH_Month, FALSE},
{"Mon", 3, dch_date, DCH_Mon, FALSE},
{"P.M.", 4, dch_time, DCH_P_M, FALSE}, /* P */
{"PM", 2, dch_time, DCH_PM, FALSE},
2001-03-22 05:01:46 +01:00
{"Q", 1, dch_date, DCH_Q, TRUE}, /* Q */
{"RM", 2, dch_date, DCH_RM, FALSE}, /* R */
{"SSSS", 4, dch_time, DCH_SSSS, TRUE}, /* S */
{"SS", 2, dch_time, DCH_SS, TRUE},
2001-03-22 05:01:46 +01:00
{"TZ", 2, dch_time, DCH_TZ, FALSE}, /* T */
{"US", 2, dch_time, DCH_US, TRUE}, /* U */
2001-03-22 05:01:46 +01:00
{"WW", 2, dch_date, DCH_WW, TRUE}, /* W */
{"W", 1, dch_date, DCH_W, TRUE},
{"Y,YYY", 5, dch_date, DCH_Y_YYY, TRUE}, /* Y */
{"YYYY", 4, dch_date, DCH_YYYY, TRUE},
{"YYY", 3, dch_date, DCH_YYY, TRUE},
{"YY", 2, dch_date, DCH_YY, TRUE},
{"Y", 1, dch_date, DCH_Y, TRUE},
{"a.d.", 4, dch_date, DCH_a_d, FALSE}, /* a */
{"a.m.", 4, dch_time, DCH_a_m, FALSE},
{"ad", 2, dch_date, DCH_ad, FALSE},
{"am", 2, dch_time, DCH_am, FALSE},
{"b.c.", 4, dch_date, DCH_b_c, FALSE}, /* b */
{"bc", 2, dch_date, DCH_bc, FALSE},
2001-03-22 05:01:46 +01:00
{"cc", 2, dch_date, DCH_CC, TRUE}, /* c */
{"day", 3, dch_date, DCH_day, FALSE}, /* d */
{"ddd", 3, dch_date, DCH_DDD, TRUE},
{"dd", 2, dch_date, DCH_DD, TRUE},
{"dy", 2, dch_date, DCH_dy, FALSE},
{"d", 1, dch_date, DCH_D, TRUE},
{"fx", 2, dch_global, DCH_FX, FALSE}, /* f */
{"hh24", 4, dch_time, DCH_HH24, TRUE}, /* h */
{"hh12", 4, dch_time, DCH_HH12, TRUE},
{"hh", 2, dch_time, DCH_HH, TRUE},
2001-03-22 05:01:46 +01:00
{"iw", 2, dch_date, DCH_IW, TRUE}, /* i */
{"j", 1, dch_time, DCH_J, TRUE}, /* j */
{"mi", 2, dch_time, DCH_MI, TRUE}, /* m */
{"mm", 2, dch_date, DCH_MM, TRUE},
{"month", 5, dch_date, DCH_month, FALSE},
{"mon", 3, dch_date, DCH_mon, FALSE},
{"ms", 2, dch_time, DCH_MS, TRUE},
{"p.m.", 4, dch_time, DCH_p_m, FALSE}, /* p */
{"pm", 2, dch_time, DCH_pm, FALSE},
2001-03-22 05:01:46 +01:00
{"q", 1, dch_date, DCH_Q, TRUE}, /* q */
{"rm", 2, dch_date, DCH_rm, FALSE}, /* r */
{"ssss", 4, dch_time, DCH_SSSS, TRUE}, /* s */
{"ss", 2, dch_time, DCH_SS, TRUE},
2001-03-22 05:01:46 +01:00
{"tz", 2, dch_time, DCH_tz, FALSE}, /* t */
{"us", 2, dch_time, DCH_US, TRUE}, /* u */
2001-03-22 05:01:46 +01:00
{"ww", 2, dch_date, DCH_WW, TRUE}, /* w */
{"w", 1, dch_date, DCH_W, TRUE},
{"y,yyy", 5, dch_date, DCH_Y_YYY, TRUE}, /* y */
{"yyyy", 4, dch_date, DCH_YYYY, TRUE},
{"yyy", 3, dch_date, DCH_YYY, TRUE},
{"yy", 2, dch_date, DCH_YY, TRUE},
{"y", 1, dch_date, DCH_Y, TRUE},
/* last */
{NULL, 0, NULL, 0}};
/* ----------
* KeyWords for NUMBER version (now, isitdigit info is not needful here..)
* ----------
*/
static KeyWord NUM_keywords[] = {
/* keyword, len, func. type is in Index */
{",", 1, NULL, NUM_COMMA}, /* , */
{".", 1, NULL, NUM_DEC}, /* . */
{"0", 1, NULL, NUM_0}, /* 0 */
{"9", 1, NULL, NUM_9}, /* 9 */
{"B", 1, NULL, NUM_B}, /* B */
{"C", 1, NULL, NUM_C}, /* C */
{"D", 1, NULL, NUM_D}, /* D */
{"E", 1, NULL, NUM_E}, /* E */
{"FM", 2, NULL, NUM_FM}, /* F */
{"G", 1, NULL, NUM_G}, /* G */
{"L", 1, NULL, NUM_L}, /* L */
{"MI", 2, NULL, NUM_MI}, /* M */
{"PL", 2, NULL, NUM_PL}, /* P */
{"PR", 2, NULL, NUM_PR},
{"RN", 2, NULL, NUM_RN}, /* R */
{"SG", 2, NULL, NUM_SG}, /* S */
{"SP", 2, NULL, NUM_SP},
{"S", 1, NULL, NUM_S},
{"TH", 2, NULL, NUM_TH}, /* T */
{"V", 1, NULL, NUM_V}, /* V */
{"b", 1, NULL, NUM_B}, /* b */
{"c", 1, NULL, NUM_C}, /* c */
{"d", 1, NULL, NUM_D}, /* d */
{"e", 1, NULL, NUM_E}, /* e */
{"fm", 2, NULL, NUM_FM}, /* f */
{"g", 1, NULL, NUM_G}, /* g */
{"l", 1, NULL, NUM_L}, /* l */
{"mi", 2, NULL, NUM_MI}, /* m */
{"pl", 2, NULL, NUM_PL}, /* p */
{"pr", 2, NULL, NUM_PR},
{"rn", 2, NULL, NUM_rn}, /* r */
{"sg", 2, NULL, NUM_SG}, /* s */
{"sp", 2, NULL, NUM_SP},
{"s", 1, NULL, NUM_S},
{"th", 2, NULL, NUM_th}, /* t */
{"v", 1, NULL, NUM_V}, /* v */
/* last */
{NULL, 0, NULL, 0}};
/* ----------
* KeyWords index for DATE-TIME version
* ----------
*/
static 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_IW, 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_iw, 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 skiped ----*/
};
/* ----------
* KeyWords index for NUMBER version
* ----------
*/
static int NUM_index[KeyWord_INDEX_SIZE] = {
/*
0 1 2 3 4 5 6 7 8 9
*/
/*---- first 0..31 chars are skiped ----*/
-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 skiped ----*/
};
/* ----------
* Number processor struct
* ----------
*/
typedef struct NUMProc
{
2001-03-22 05:01:46 +01:00
int type; /* FROM_CHAR (TO_NUMBER) or 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 */
char *number, /* string with number */
*number_p, /* pointer to current number position */
2001-03-22 05:01:46 +01:00
*inout, /* in / out buffer */
*inout_p, /* pointer to current inout position */
2001-03-22 05:01:46 +01:00
*last_relevant, /* last relevant number after decimal
* point */
*L_negative_sign, /* Locale */
2001-03-22 05:01:46 +01:00
*L_positive_sign,
*decimal,
*L_thousands_sep,
*L_currency_symbol;
} NUMProc;
/* ----------
* Functions
* ----------
*/
static KeyWord *index_seq_search(char *str, KeyWord *kw, int *index);
static KeySuffix *suff_search(char *str, KeySuffix *suf, int type);
static void NUMDesc_prepare(NUMDesc *num, FormatNode *n);
static void parse_format(FormatNode *node, char *str, KeyWord *kw,
KeySuffix *suf, int *index, int ver, NUMDesc *Num);
static char *DCH_processor(FormatNode *node, char *inout, int flag, void *data);
#ifdef DEBUG_TO_FROM_CHAR
static void dump_index(KeyWord *k, 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 strdigits_len(char *str);
static char *str_toupper(char *buff);
static char *str_tolower(char *buff);
2000-04-07 21:17:51 +02:00
/* static int is_acdc(char *str, int *len); */
static int seq_search(char *name, char **array, int type, int max, int *len);
static int dch_global(int arg, char *inout, int suf, int flag, FormatNode *node, void *data);
static int dch_time(int arg, char *inout, int suf, int flag, FormatNode *node, void *data);
static int dch_date(int arg, char *inout, int suf, int flag, FormatNode *node, void *data);
static char *fill_str(char *str, int c, int max);
static FormatNode *NUM_cache(int len, NUMDesc *Num, char *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, int type);
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 KeyWord *
index_seq_search(char *str, KeyWord *kw, int *index)
{
int poz;
if (!KeyWord_INDEX_FILTER(*str))
return (KeyWord *) NULL;
if ((poz = *(index + (*str - ' '))) > -1)
{
2000-07-01 23:27:14 +02:00
KeyWord *k = kw + poz;
do
{
if (!strncmp(str, k->name, k->len))
return k;
k++;
if (!k->name)
return (KeyWord *) NULL;
} while (*str == *k->name);
}
return (KeyWord *) 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 (KeySuffix *) NULL;
}
/* ----------
* Prepare NUMDesc (number description struct) via FormatNode struct
* ----------
*/
static void
NUMDesc_prepare(NUMDesc *num, FormatNode *n)
{
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\"")));
}
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\"")));
}
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")));
}
if (IS_MULTI(num))
{
NUM_cache_remove(last_NUMCacheEntry);
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
2003-08-04 02:43:34 +02:00
errmsg("cannot use \"V\" and decimal point together")));
}
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("not unique \"S\"")));
}
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")));
}
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")));
}
num->flag |= NUM_F_MINUS;
2003-03-27 17:35:31 +01:00
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")));
}
num->flag |= NUM_F_PLUS;
2003-03-27 17:35:31 +01:00
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")));
}
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")));
}
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),
2003-08-04 02:43:34 +02:00
errmsg("cannot use \"V\" and decimal point together")));
}
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")));
}
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, KeyWord *kw,
KeySuffix *suf, int *index, int ver, NUMDesc *Num)
{
2001-03-22 05:01:46 +01:00
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);
/*
* 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 = (KeyWord *) 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 = (KeyWord *) 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;
}
/* ----------
* Call keyword's function for each of (action) node in format-node tree
* ----------
*/
static char *
DCH_processor(FormatNode *node, char *inout, int flag, void *data)
{
FormatNode *n;
char *s;
/*
* Zeroing global flags
*/
DCH_global_flag = 0;
for (n = node, s = inout; n->type != NODE_TYPE_END; n++)
{
if (n->type == NODE_TYPE_ACTION)
{
2001-03-22 05:01:46 +01:00
int len;
/*
* Call node action function
*/
len = n->key->action(n->key->id, s, n->suffix, flag, n, data);
if (len > 0)
s += len;
else if (len == -1)
continue;
2000-07-01 23:27:14 +02:00
}
else
{
/*
* Remove to output char from input in TO_CHAR
*/
if (flag == TO_CHAR)
*s = n->character;
2000-07-01 23:27:14 +02:00
else
{
/*
* Skip blank space in FROM_CHAR's input
*/
2001-03-22 05:01:46 +01:00
if (isspace((unsigned char) n->character) && IS_FX == 0)
{
while (*s != '\0' && isspace((unsigned char) *(s + 1)))
++s;
}
}
}
2000-07-01 23:27:14 +02:00
++s; /* ! */
2000-07-01 23:27:14 +02:00
}
if (flag == TO_CHAR)
*s = '\0';
return inout;
}
/* ----------
* 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)
{
2001-03-22 05:01:46 +01:00
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)));
2000-07-01 23:27:14 +02:00
/*
2001-03-22 05:01:46 +01:00
* All "teens" (<x>1[0-9]) get 'TH/th', while <x>[02-9][123] still get
* 'ST/st', 'ND/nd', 'RD/rd', respectively
2000-07-01 23:27:14 +02:00
*/
2001-03-22 05:01:46 +01:00
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)
{
sprintf(dest, "%s%s", num, get_th(num, type));
return dest;
}
/* ----------
* Convert string to upper-string. Input string is modified in place.
* ----------
*/
static char *
str_toupper(char *buff)
{
char *p_buff = buff;
if (!buff)
return NULL;
while (*p_buff)
{
*p_buff = toupper((unsigned char) *p_buff);
++p_buff;
}
return buff;
}
/* ----------
* Convert string to lower-string. Input string is modified in place.
* ----------
*/
static char *
str_tolower(char *buff)
{
char *p_buff = buff;
if (!buff)
return NULL;
while (*p_buff)
{
*p_buff = tolower((unsigned char) *p_buff);
++p_buff;
}
return buff;
}
/* ----------
* Sequential search with to upper/lower conversion
* ----------
*/
static int
seq_search(char *name, char **array, int type, int max, int *len)
{
2001-03-22 05:01:46 +01:00
char *p,
*n,
**a;
int last,
i;
*len = 0;
if (!*name)
return -1;
/* set first char */
if (type == ONE_UPPER || type == ALL_UPPER)
*name = toupper((unsigned char) *name);
else if (type == ALL_LOWER)
*name = 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 = tolower((unsigned char) *n);
else if (type == ALL_UPPER)
*n = 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;
}
#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(KeyWord *k, int *index)
{
2001-03-22 05:01:46 +01:00
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 */
/* ----------
* Skip TM / th in FROM_CHAR
* ----------
*/
#define SKIP_THth(_suf) (S_THth(_suf) ? 2 : 0)
/* ----------
* Global format option for DCH version
* ----------
*/
static int
dch_global(int arg, char *inout, int suf, int flag, FormatNode *node, void *data)
{
if (arg == DCH_FX)
DCH_global_flag |= DCH_F_FX;
return -1;
}
/* ----------
* 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;
2001-03-22 05:01:46 +01:00
if (n->type == NODE_TYPE_ACTION && S_THth(n->suffix))
return TRUE;
2001-03-22 05:01:46 +01:00
/*
* Next node
*/
2001-03-22 05:01:46 +01:00
n++;
if (n->type == NODE_TYPE_END)
return FALSE;
2001-03-22 05:01:46 +01:00
if (n->type == NODE_TYPE_ACTION)
{
if (n->key->isitdigit)
return FALSE;
2001-03-22 05:01:46 +01:00
return TRUE;
}
else if (isdigit((unsigned char) n->character))
return FALSE;
2001-03-22 05:01:46 +01:00
return TRUE; /* some non-digit input (separator) */
}
static int
strdigits_len(char *str)
{
char *p = str;
int len = 0;
while (*p && isdigit((unsigned char) *p) && len <= DCH_MAX_ITEM_SIZ)
{
len++;
p++;
}
return len;
}
#define AMPM_ERROR ereport(ERROR, \
(errcode(ERRCODE_INVALID_DATETIME_FORMAT), \
errmsg("invalid AM/PM string")));
/* ----------
* Master function of TIME for:
* TO_CHAR - write (inout) formated string
* FROM_CHAR - scan (inout) string by course of FormatNode
* ----------
*/
static int
dch_time(int arg, char *inout, int suf, int flag, FormatNode *node, void *data)
{
char *p_inout = inout;
struct tm *tm = NULL;
TmFromChar *tmfc = NULL;
TmToChar *tmtc = NULL;
if (flag == TO_CHAR)
{
tmtc = (TmToChar *) data;
tm = tmtcTm(tmtc);
}
else
tmfc = (TmFromChar *) data;
switch (arg)
{
2001-03-22 05:01:46 +01:00
case DCH_A_M:
case DCH_P_M:
if (flag == TO_CHAR)
{
2001-03-22 05:01:46 +01:00
strcpy(inout, ((tm->tm_hour > 11
&& tm->tm_hour < 24) ? P_M_STR : A_M_STR));
return 3;
}
else if (flag == FROM_CHAR)
{
if (strncmp(inout, P_M_STR, 4) == 0)
tmfc->pm = TRUE;
else if (strncmp(inout, A_M_STR, 4) == 0)
2001-03-22 05:01:46 +01:00
tmfc->am = TRUE;
else
2001-03-22 05:01:46 +01:00
AMPM_ERROR;
return 3;
}
break;
case DCH_AM:
case DCH_PM:
if (flag == TO_CHAR)
{
2001-03-22 05:01:46 +01:00
strcpy(inout, ((tm->tm_hour > 11
&& tm->tm_hour < 24) ? PM_STR : AM_STR));
return 1;
}
else if (flag == FROM_CHAR)
{
if (strncmp(inout, PM_STR, 2) == 0)
tmfc->pm = TRUE;
else if (strncmp(inout, AM_STR, 2) == 0)
tmfc->am = TRUE;
else
2001-03-22 05:01:46 +01:00
AMPM_ERROR;
return 1;
}
break;
case DCH_a_m:
case DCH_p_m:
if (flag == TO_CHAR)
{
2001-03-22 05:01:46 +01:00
strcpy(inout, ((tm->tm_hour > 11
&& tm->tm_hour < 24) ? p_m_STR : a_m_STR));
return 3;
}
else if (flag == FROM_CHAR)
{
if (strncmp(inout, p_m_STR, 4) == 0)
tmfc->pm = TRUE;
else if (strncmp(inout, a_m_STR, 4) == 0)
tmfc->am = TRUE;
else
2001-03-22 05:01:46 +01:00
AMPM_ERROR;
return 3;
}
break;
case DCH_am:
case DCH_pm:
if (flag == TO_CHAR)
{
strcpy(inout, ((tm->tm_hour > 11
2001-03-22 05:01:46 +01:00
&& tm->tm_hour < 24) ? pm_STR : am_STR));
return 1;
}
else if (flag == FROM_CHAR)
{
if (strncmp(inout, pm_STR, 2) == 0)
tmfc->pm = TRUE;
else if (strncmp(inout, am_STR, 2) == 0)
tmfc->am = TRUE;
else
2001-03-22 05:01:46 +01:00
AMPM_ERROR;
return 1;
}
break;
case DCH_HH:
case DCH_HH12:
if (flag == TO_CHAR)
{
sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2,
tm->tm_hour == 0 ? 12 :
tm->tm_hour < 13 ? tm->tm_hour : tm->tm_hour - 12);
if (S_THth(suf))
str_numth(p_inout, inout, 0);
if (S_FM(suf) || S_THth(suf))
return strlen(p_inout) - 1;
else
return 1;
2000-07-01 23:27:14 +02:00
}
else if (flag == FROM_CHAR)
{
if (S_FM(suf) || is_next_separator(node))
{
sscanf(inout, "%d", &tmfc->hh);
return strdigits_len(inout) - 1 + SKIP_THth(suf);
}
else
{
sscanf(inout, "%02d", &tmfc->hh);
return 1 + SKIP_THth(suf);
}
}
break;
case DCH_HH24:
if (flag == TO_CHAR)
{
sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2, tm->tm_hour);
if (S_THth(suf))
str_numth(p_inout, inout, S_TH_TYPE(suf));
if (S_FM(suf) || S_THth(suf))
return strlen(p_inout) - 1;
else
return 1;
}
else if (flag == FROM_CHAR)
{
if (S_FM(suf) || is_next_separator(node))
{
sscanf(inout, "%d", &tmfc->hh);
return strdigits_len(inout) - 1 + SKIP_THth(suf);
}
else
{
sscanf(inout, "%02d", &tmfc->hh);
return 1 + SKIP_THth(suf);
}
}
break;
case DCH_MI:
if (flag == TO_CHAR)
{
sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2, tm->tm_min);
if (S_THth(suf))
str_numth(p_inout, inout, S_TH_TYPE(suf));
if (S_FM(suf) || S_THth(suf))
return strlen(p_inout) - 1;
else
return 1;
}
else if (flag == FROM_CHAR)
{
if (S_FM(suf) || is_next_separator(node))
{
sscanf(inout, "%d", &tmfc->mi);
return strdigits_len(inout) - 1 + SKIP_THth(suf);
}
else
{
sscanf(inout, "%02d", &tmfc->mi);
return 1 + SKIP_THth(suf);
}
}
break;
case DCH_SS:
if (flag == TO_CHAR)
{
sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2, tm->tm_sec);
if (S_THth(suf))
str_numth(p_inout, inout, S_TH_TYPE(suf));
if (S_FM(suf) || S_THth(suf))
return strlen(p_inout) - 1;
else
return 1;
2000-07-01 23:27:14 +02:00
}
else if (flag == FROM_CHAR)
{
if (S_FM(suf) || is_next_separator(node))
{
sscanf(inout, "%d", &tmfc->ss);
return strdigits_len(inout) - 1 + SKIP_THth(suf);
}
else
{
sscanf(inout, "%02d", &tmfc->ss);
return 1 + SKIP_THth(suf);
}
}
break;
case DCH_MS: /* millisecond */
if (flag == TO_CHAR)
{
Support alternate storage scheme of 64-bit integer for date/time types. Use "--enable-integer-datetimes" in configuration to use this rather than the original float8 storage. I would recommend the integer-based storage for any platform on which it is available. We perhaps should make this the default for the production release. Change timezone(timestamptz) results to return timestamp rather than a character string. Formerly, we didn't have a way to represent timestamps with an explicit time zone other than freezing the info into a string. Now, we can reasonably omit the explicit time zone from the result and return a timestamp with values appropriate for the specified time zone. Much cleaner, and if you need the time zone in the result you can put it into a character string pretty easily anyway. Allow fractional seconds in date/time types even for dates prior to 1BC. Limit timestamp data types to 6 decimal places of precision. Just right for a micro-second storage of int8 date/time types, and reduces the number of places ad-hoc rounding was occuring for the float8-based types. Use lookup tables for precision/rounding calculations for timestamp and interval types. Formerly used pow() to calculate the desired value but with a more limited range there is no reason to not type in a lookup table. Should be *much* better performance, though formerly there were some optimizations to help minimize the number of times pow() was called. Define a HAVE_INT64_TIMESTAMP variable. Based on the configure option "--enable-integer-datetimes" and the existing internal INT64_IS_BUSTED. Add explicit date/interval operators and functions for addition and subtraction. Formerly relied on implicit type promotion from date to timestamp with time zone. Change timezone conversion functions for the timetz type from "timetz()" to "timezone()". This is consistant with other time zone coersion functions for other types. Bump the catalog version to 200204201. Fix up regression tests to reflect changes in fractional seconds representation for date/times in BC eras. All regression tests pass on my Linux box.
2002-04-21 21:52:18 +02:00
#ifdef HAVE_INT64_TIMESTAMP
sprintf(inout, "%03d", (int) (tmtc->fsec / INT64CONST(1000)));
#else
sprintf(inout, "%03d", (int) rint(tmtc->fsec * 1000));
Support alternate storage scheme of 64-bit integer for date/time types. Use "--enable-integer-datetimes" in configuration to use this rather than the original float8 storage. I would recommend the integer-based storage for any platform on which it is available. We perhaps should make this the default for the production release. Change timezone(timestamptz) results to return timestamp rather than a character string. Formerly, we didn't have a way to represent timestamps with an explicit time zone other than freezing the info into a string. Now, we can reasonably omit the explicit time zone from the result and return a timestamp with values appropriate for the specified time zone. Much cleaner, and if you need the time zone in the result you can put it into a character string pretty easily anyway. Allow fractional seconds in date/time types even for dates prior to 1BC. Limit timestamp data types to 6 decimal places of precision. Just right for a micro-second storage of int8 date/time types, and reduces the number of places ad-hoc rounding was occuring for the float8-based types. Use lookup tables for precision/rounding calculations for timestamp and interval types. Formerly used pow() to calculate the desired value but with a more limited range there is no reason to not type in a lookup table. Should be *much* better performance, though formerly there were some optimizations to help minimize the number of times pow() was called. Define a HAVE_INT64_TIMESTAMP variable. Based on the configure option "--enable-integer-datetimes" and the existing internal INT64_IS_BUSTED. Add explicit date/interval operators and functions for addition and subtraction. Formerly relied on implicit type promotion from date to timestamp with time zone. Change timezone conversion functions for the timetz type from "timetz()" to "timezone()". This is consistant with other time zone coersion functions for other types. Bump the catalog version to 200204201. Fix up regression tests to reflect changes in fractional seconds representation for date/times in BC eras. All regression tests pass on my Linux box.
2002-04-21 21:52:18 +02:00
#endif
if (S_THth(suf))
str_numth(p_inout, inout, S_TH_TYPE(suf));
if (S_THth(suf))
return strlen(p_inout) - 1;
else
return 2;
}
else if (flag == FROM_CHAR)
{
int len,
x;
if (is_next_separator(node))
{
sscanf(inout, "%d", &tmfc->ms);
len = x = strdigits_len(inout);
}
else
{
sscanf(inout, "%03d", &tmfc->ms);
x = strdigits_len(inout);
len = x = x > 3 ? 3 : x;
}
/*
* 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not
* 0.25
*/
tmfc->ms *= x == 1 ? 100 :
x == 2 ? 10 : 1;
/*
* elog(DEBUG3, "X: %d, MS: %d, LEN: %d", x, tmfc->ms,
* len);
*/
return len - 1 + SKIP_THth(suf);
}
break;
case DCH_US: /* microsecond */
if (flag == TO_CHAR)
{
Support alternate storage scheme of 64-bit integer for date/time types. Use "--enable-integer-datetimes" in configuration to use this rather than the original float8 storage. I would recommend the integer-based storage for any platform on which it is available. We perhaps should make this the default for the production release. Change timezone(timestamptz) results to return timestamp rather than a character string. Formerly, we didn't have a way to represent timestamps with an explicit time zone other than freezing the info into a string. Now, we can reasonably omit the explicit time zone from the result and return a timestamp with values appropriate for the specified time zone. Much cleaner, and if you need the time zone in the result you can put it into a character string pretty easily anyway. Allow fractional seconds in date/time types even for dates prior to 1BC. Limit timestamp data types to 6 decimal places of precision. Just right for a micro-second storage of int8 date/time types, and reduces the number of places ad-hoc rounding was occuring for the float8-based types. Use lookup tables for precision/rounding calculations for timestamp and interval types. Formerly used pow() to calculate the desired value but with a more limited range there is no reason to not type in a lookup table. Should be *much* better performance, though formerly there were some optimizations to help minimize the number of times pow() was called. Define a HAVE_INT64_TIMESTAMP variable. Based on the configure option "--enable-integer-datetimes" and the existing internal INT64_IS_BUSTED. Add explicit date/interval operators and functions for addition and subtraction. Formerly relied on implicit type promotion from date to timestamp with time zone. Change timezone conversion functions for the timetz type from "timetz()" to "timezone()". This is consistant with other time zone coersion functions for other types. Bump the catalog version to 200204201. Fix up regression tests to reflect changes in fractional seconds representation for date/times in BC eras. All regression tests pass on my Linux box.
2002-04-21 21:52:18 +02:00
#ifdef HAVE_INT64_TIMESTAMP
sprintf(inout, "%06d", (int) tmtc->fsec);
#else
sprintf(inout, "%06d", (int) rint(tmtc->fsec * 1000000));
Support alternate storage scheme of 64-bit integer for date/time types. Use "--enable-integer-datetimes" in configuration to use this rather than the original float8 storage. I would recommend the integer-based storage for any platform on which it is available. We perhaps should make this the default for the production release. Change timezone(timestamptz) results to return timestamp rather than a character string. Formerly, we didn't have a way to represent timestamps with an explicit time zone other than freezing the info into a string. Now, we can reasonably omit the explicit time zone from the result and return a timestamp with values appropriate for the specified time zone. Much cleaner, and if you need the time zone in the result you can put it into a character string pretty easily anyway. Allow fractional seconds in date/time types even for dates prior to 1BC. Limit timestamp data types to 6 decimal places of precision. Just right for a micro-second storage of int8 date/time types, and reduces the number of places ad-hoc rounding was occuring for the float8-based types. Use lookup tables for precision/rounding calculations for timestamp and interval types. Formerly used pow() to calculate the desired value but with a more limited range there is no reason to not type in a lookup table. Should be *much* better performance, though formerly there were some optimizations to help minimize the number of times pow() was called. Define a HAVE_INT64_TIMESTAMP variable. Based on the configure option "--enable-integer-datetimes" and the existing internal INT64_IS_BUSTED. Add explicit date/interval operators and functions for addition and subtraction. Formerly relied on implicit type promotion from date to timestamp with time zone. Change timezone conversion functions for the timetz type from "timetz()" to "timezone()". This is consistant with other time zone coersion functions for other types. Bump the catalog version to 200204201. Fix up regression tests to reflect changes in fractional seconds representation for date/times in BC eras. All regression tests pass on my Linux box.
2002-04-21 21:52:18 +02:00
#endif
if (S_THth(suf))
str_numth(p_inout, inout, S_TH_TYPE(suf));
if (S_THth(suf))
return strlen(p_inout) - 1;
else
return 5;
}
else if (flag == FROM_CHAR)
{
int len,
x;
if (is_next_separator(node))
{
sscanf(inout, "%d", &tmfc->us);
len = x = strdigits_len(inout);
}
else
{
sscanf(inout, "%06d", &tmfc->us);
x = strdigits_len(inout);
len = x = x > 6 ? 6 : x;
}
tmfc->us *= x == 1 ? 100000 :
x == 2 ? 10000 :
x == 3 ? 1000 :
x == 4 ? 100 :
x == 5 ? 10 : 1;
/*
* elog(DEBUG3, "X: %d, US: %d, LEN: %d", x, tmfc->us,
* len);
*/
return len - 1 + SKIP_THth(suf);
}
break;
case DCH_SSSS:
if (flag == TO_CHAR)
{
sprintf(inout, "%d", tm->tm_hour * 3600 +
tm->tm_min * 60 +
tm->tm_sec);
if (S_THth(suf))
str_numth(p_inout, inout, S_TH_TYPE(suf));
return strlen(p_inout) - 1;
}
2001-03-22 05:01:46 +01:00
else if (flag == FROM_CHAR)
{
if (S_FM(suf) || is_next_separator(node))
{
sscanf(inout, "%d", &tmfc->ssss);
return strdigits_len(inout) - 1 + SKIP_THth(suf);
}
else
{
2001-03-22 05:01:46 +01:00
sscanf(inout, "%05d", &tmfc->ssss);
return 4 + SKIP_THth(suf);
}
}
break;
case DCH_tz:
case DCH_TZ:
if (flag == TO_CHAR && tmtcTzn(tmtc))
{
int siz = strlen(tmtcTzn(tmtc));
2001-03-22 05:01:46 +01:00
if (arg == DCH_TZ)
strcpy(inout, tmtcTzn(tmtc));
2001-03-22 05:01:46 +01:00
else
{
2001-03-22 05:01:46 +01:00
char *p = palloc(siz);
strcpy(p, tmtcTzn(tmtc));
strcpy(inout, str_tolower(p));
pfree(p);
}
return siz - 1;
2001-03-22 05:01:46 +01:00
}
else if (flag == FROM_CHAR)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("\"TZ\"/\"tz\" not supported")));
}
return -1;
}
#define CHECK_SEQ_SEARCH(_l, _s) \
do { \
if ((_l) <= 0) { \
ereport(ERROR, \
(errcode(ERRCODE_INVALID_DATETIME_FORMAT), \
errmsg("invalid value for %s", (_s)))); \
} \
} while (0)
/* ----------
* Master of DATE for:
* TO_CHAR - write (inout) formated string
* FROM_CHAR - scan (inout) string by course of FormatNode
* ----------
*/
static int
dch_date(int arg, char *inout, int suf, int flag, FormatNode *node, void *data)
{
char buff[DCH_CACHE_SIZE],
*p_inout;
int i,
len;
struct tm *tm = NULL;
TmFromChar *tmfc = NULL;
TmToChar *tmtc = NULL;
if (flag == TO_CHAR)
{
tmtc = (TmToChar *) data;
tm = tmtcTm(tmtc);
}
else
tmfc = (TmFromChar *) data;
p_inout = inout;
/*
* In the FROM-char is not difference between "January" or "JANUARY"
* or "january", all is before search convert to "first-upper". This
* convention is used for MONTH, MON, DAY, DY
*/
if (flag == FROM_CHAR)
{
if (arg == DCH_MONTH || arg == DCH_Month || arg == DCH_month)
{
2001-03-22 05:01:46 +01:00
tmfc->mm = seq_search(inout, months_full, ONE_UPPER, FULL_SIZ, &len) + 1;
CHECK_SEQ_SEARCH(len, "MONTH/Month/month");
if (S_FM(suf))
return len - 1;
else
return 8;
2000-07-01 23:27:14 +02:00
}
else if (arg == DCH_MON || arg == DCH_Mon || arg == DCH_mon)
{
2001-03-22 05:01:46 +01:00
tmfc->mm = seq_search(inout, months, ONE_UPPER, MAX_MON_LEN, &len) + 1;
CHECK_SEQ_SEARCH(len, "MON/Mon/mon");
return 2;
}
else if (arg == DCH_DAY || arg == DCH_Day || arg == DCH_day)
{
tmfc->d = seq_search(inout, days, ONE_UPPER, FULL_SIZ, &len);
CHECK_SEQ_SEARCH(len, "DAY/Day/day");
if (S_FM(suf))
return len - 1;
else
return 8;
2000-07-01 23:27:14 +02:00
}
else if (arg == DCH_DY || arg == DCH_Dy || arg == DCH_dy)
{
tmfc->d = seq_search(inout, days, ONE_UPPER, MAX_DY_LEN, &len);
CHECK_SEQ_SEARCH(len, "DY/Dy/dy");
return 2;
}
}
switch (arg)
{
2000-07-01 23:27:14 +02:00
case DCH_A_D:
case DCH_B_C:
if (flag == TO_CHAR)
{
strcpy(inout, (tm->tm_year < 0 ? B_C_STR : A_D_STR));
return 3;
2000-07-01 23:27:14 +02:00
}
else if (flag == FROM_CHAR)
{
if (strncmp(inout, B_C_STR, 4) == 0)
tmfc->bc = TRUE;
return 3;
}
break;
case DCH_AD:
case DCH_BC:
if (flag == TO_CHAR)
{
strcpy(inout, (tm->tm_year < 0 ? BC_STR : AD_STR));
return 1;
2000-07-01 23:27:14 +02:00
}
else if (flag == FROM_CHAR)
{
if (strncmp(inout, BC_STR, 2) == 0)
tmfc->bc = TRUE;
return 1;
}
break;
case DCH_a_d:
case DCH_b_c:
if (flag == TO_CHAR)
{
strcpy(inout, (tm->tm_year < 0 ? b_c_STR : a_d_STR));
2000-04-07 21:17:51 +02:00
return 3;
2000-07-01 23:27:14 +02:00
}
else if (flag == FROM_CHAR)
{
if (strncmp(inout, b_c_STR, 4) == 0)
tmfc->bc = TRUE;
2000-04-07 21:17:51 +02:00
return 3;
}
break;
case DCH_ad:
case DCH_bc:
if (flag == TO_CHAR)
{
strcpy(inout, (tm->tm_year < 0 ? bc_STR : ad_STR));
return 1;
2000-07-01 23:27:14 +02:00
}
else if (flag == FROM_CHAR)
{
if (strncmp(inout, bc_STR, 2) == 0)
tmfc->bc = TRUE;
return 1;
}
break;
case DCH_MONTH:
strcpy(inout, months_full[tm->tm_mon - 1]);
sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, str_toupper(inout));
if (S_FM(suf))
return strlen(p_inout) - 1;
else
return 8;
2001-03-22 05:01:46 +01:00
case DCH_Month:
sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, months_full[tm->tm_mon - 1]);
if (S_FM(suf))
return strlen(p_inout) - 1;
else
return 8;
2001-03-22 05:01:46 +01:00
case DCH_month:
sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, months_full[tm->tm_mon - 1]);
*inout = tolower((unsigned char) *inout);
if (S_FM(suf))
return strlen(p_inout) - 1;
else
return 8;
2001-03-22 05:01:46 +01:00
case DCH_MON:
strcpy(inout, months[tm->tm_mon - 1]);
inout = str_toupper(inout);
return 2;
2001-03-22 05:01:46 +01:00
case DCH_Mon:
strcpy(inout, months[tm->tm_mon - 1]);
return 2;
case DCH_mon:
strcpy(inout, months[tm->tm_mon - 1]);
*inout = tolower((unsigned char) *inout);
return 2;
case DCH_MM:
if (flag == TO_CHAR)
{
sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2, tm->tm_mon);
if (S_THth(suf))
str_numth(p_inout, inout, S_TH_TYPE(suf));
if (S_FM(suf) || S_THth(suf))
return strlen(p_inout) - 1;
else
return 1;
2000-07-01 23:27:14 +02:00
}
else if (flag == FROM_CHAR)
{
if (S_FM(suf) || is_next_separator(node))
{
sscanf(inout, "%d", &tmfc->mm);
return strdigits_len(inout) - 1 + SKIP_THth(suf);
}
else
{
sscanf(inout, "%02d", &tmfc->mm);
return 1 + SKIP_THth(suf);
}
}
break;
case DCH_DAY:
strcpy(inout, days[tm->tm_wday]);
sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, str_toupper(inout));
if (S_FM(suf))
return strlen(p_inout) - 1;
else
return 8;
case DCH_Day:
sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, days[tm->tm_wday]);
if (S_FM(suf))
return strlen(p_inout) - 1;
else
return 8;
case DCH_day:
sprintf(inout, "%*s", S_FM(suf) ? 0 : -9, days[tm->tm_wday]);
*inout = tolower((unsigned char) *inout);
if (S_FM(suf))
return strlen(p_inout) - 1;
else
return 8;
case DCH_DY:
strcpy(inout, days[tm->tm_wday]);
inout = str_toupper(inout);
return 2;
case DCH_Dy:
strcpy(inout, days[tm->tm_wday]);
return 2;
case DCH_dy:
strcpy(inout, days[tm->tm_wday]);
*inout = tolower((unsigned char) *inout);
return 2;
case DCH_DDD:
if (flag == TO_CHAR)
{
sprintf(inout, "%0*d", S_FM(suf) ? 0 : 3, tm->tm_yday);
if (S_THth(suf))
str_numth(p_inout, inout, S_TH_TYPE(suf));
if (S_FM(suf) || S_THth(suf))
return strlen(p_inout) - 1;
else
return 2;
2000-07-01 23:27:14 +02:00
}
else if (flag == FROM_CHAR)
{
if (S_FM(suf) || is_next_separator(node))
{
sscanf(inout, "%d", &tmfc->ddd);
return strdigits_len(inout) - 1 + SKIP_THth(suf);
}
else
{
sscanf(inout, "%03d", &tmfc->ddd);
return 2 + SKIP_THth(suf);
}
}
break;
case DCH_DD:
if (flag == TO_CHAR)
{
sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2, tm->tm_mday);
if (S_THth(suf))
str_numth(p_inout, inout, S_TH_TYPE(suf));
if (S_FM(suf) || S_THth(suf))
return strlen(p_inout) - 1;
else
return 1;
2000-07-01 23:27:14 +02:00
}
else if (flag == FROM_CHAR)
{
if (S_FM(suf) || is_next_separator(node))
{
sscanf(inout, "%d", &tmfc->dd);
return strdigits_len(inout) - 1 + SKIP_THth(suf);
}
else
{
sscanf(inout, "%02d", &tmfc->dd);
return 1 + SKIP_THth(suf);
}
}
break;
case DCH_D:
if (flag == TO_CHAR)
{
sprintf(inout, "%d", tm->tm_wday + 1);
if (S_THth(suf))
{
str_numth(p_inout, inout, S_TH_TYPE(suf));
return 2;
}
return 0;
}
else if (flag == FROM_CHAR)
{
sscanf(inout, "%1d", &tmfc->d);
return 0 + SKIP_THth(suf);
}
break;
case DCH_WW:
if (flag == TO_CHAR)
{
sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2,
2001-03-22 05:01:46 +01:00
(tm->tm_yday - 1) / 7 + 1);
if (S_THth(suf))
str_numth(p_inout, inout, S_TH_TYPE(suf));
if (S_FM(suf) || S_THth(suf))
return strlen(p_inout) - 1;
else
return 1;
}
2001-03-22 05:01:46 +01:00
else if (flag == FROM_CHAR)
{
if (S_FM(suf) || is_next_separator(node))
{
sscanf(inout, "%d", &tmfc->ww);
return strdigits_len(inout) - 1 + SKIP_THth(suf);
}
else
{
sscanf(inout, "%02d", &tmfc->ww);
return 1 + SKIP_THth(suf);
}
}
break;
case DCH_IW:
if (flag == TO_CHAR)
{
sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2,
2001-03-22 05:01:46 +01:00
date2isoweek(tm->tm_year, tm->tm_mon, tm->tm_mday));
if (S_THth(suf))
str_numth(p_inout, inout, S_TH_TYPE(suf));
if (S_FM(suf) || S_THth(suf))
return strlen(p_inout) - 1;
else
return 1;
}
2001-03-22 05:01:46 +01:00
else if (flag == FROM_CHAR)
{
if (S_FM(suf) || is_next_separator(node))
{
sscanf(inout, "%d", &tmfc->iw);
return strdigits_len(inout) - 1 + SKIP_THth(suf);
}
else
{
sscanf(inout, "%02d", &tmfc->iw);
return 1 + SKIP_THth(suf);
}
}
2001-03-22 05:01:46 +01:00
break;
case DCH_Q:
if (flag == TO_CHAR)
{
sprintf(inout, "%d", (tm->tm_mon - 1) / 3 + 1);
if (S_THth(suf))
{
str_numth(p_inout, inout, S_TH_TYPE(suf));
return 2;
}
return 0;
}
else if (flag == FROM_CHAR)
{
sscanf(inout, "%1d", &tmfc->q);
return 0 + SKIP_THth(suf);
}
break;
case DCH_CC:
if (flag == TO_CHAR)
{
i = tm->tm_year / 100 + 1;
if (i <= 99 && i >= -99)
sprintf(inout, "%0*d", S_FM(suf) ? 0 : 2, i);
else
sprintf(inout, "%d", i);
if (S_THth(suf))
str_numth(p_inout, inout, S_TH_TYPE(suf));
return strlen(p_inout) - 1;
}
else if (flag == FROM_CHAR)
{
if (S_FM(suf) || is_next_separator(node))
{
sscanf(inout, "%d", &tmfc->cc);
return strdigits_len(inout) - 1 + SKIP_THth(suf);
}
else
{
sscanf(inout, "%02d", &tmfc->cc);
return 1 + SKIP_THth(suf);
}
}
break;
case DCH_Y_YYY:
if (flag == TO_CHAR)
{
i = YEAR_ABS(tm->tm_year) / 1000;
sprintf(inout, "%d,%03d", i, YEAR_ABS(tm->tm_year) - (i * 1000));
if (S_THth(suf))
str_numth(p_inout, inout, S_TH_TYPE(suf));
return strlen(p_inout) - 1;
}
else if (flag == FROM_CHAR)
{
int cc;
sscanf(inout, "%d,%03d", &cc, &tmfc->year);
tmfc->year += (cc * 1000);
return strdigits_len(inout) + 3 + SKIP_THth(suf);
}
break;
case DCH_YYYY:
if (flag == TO_CHAR)
{
if (tm->tm_year <= 9999 && tm->tm_year >= -9998)
sprintf(inout, "%0*d", S_FM(suf) ? 0 : 4, YEAR_ABS(tm->tm_year));
else
sprintf(inout, "%d", YEAR_ABS(tm->tm_year));
if (S_THth(suf))
str_numth(p_inout, inout, S_TH_TYPE(suf));
return strlen(p_inout) - 1;
}
else if (flag == FROM_CHAR)
{
if (S_FM(suf) || is_next_separator(node))
{
sscanf(inout, "%d", &tmfc->year);
return strdigits_len(inout) - 1 + SKIP_THth(suf);
}
else
{
sscanf(inout, "%04d", &tmfc->year);
return 3 + SKIP_THth(suf);
}
}
break;
case DCH_YYY:
if (flag == TO_CHAR)
{
snprintf(buff, sizeof(buff), "%03d", YEAR_ABS(tm->tm_year));
i = strlen(buff);
strcpy(inout, buff + (i - 3));
if (S_THth(suf))
{
str_numth(p_inout, inout, S_TH_TYPE(suf));
return 4;
}
return 2;
2000-07-01 23:27:14 +02:00
}
else if (flag == FROM_CHAR)
{
sscanf(inout, "%03d", &tmfc->year);
/*
2002-09-04 22:31:48 +02:00
* 3-digit year: '100' ... '999' = 1100 ... 1999 '000' ...
* '099' = 2000 ... 2099
*/
if (tmfc->year >= 100)
tmfc->year += 1000;
else
tmfc->year += 2000;
return 2 + SKIP_THth(suf);
}
break;
case DCH_YY:
if (flag == TO_CHAR)
{
snprintf(buff, sizeof(buff), "%02d", YEAR_ABS(tm->tm_year));
i = strlen(buff);
strcpy(inout, buff + (i - 2));
if (S_THth(suf))
{
str_numth(p_inout, inout, S_TH_TYPE(suf));
return 3;
}
return 1;
2000-07-01 23:27:14 +02:00
}
else if (flag == FROM_CHAR)
{
sscanf(inout, "%02d", &tmfc->year);
/*
2002-09-04 22:31:48 +02:00
* 2-digit year: '00' ... '69' = 2000 ... 2069 '70' ...
* '99' = 1970 ... 1999
*/
if (tmfc->year < 70)
tmfc->year += 2000;
else
tmfc->year += 1900;
return 1 + SKIP_THth(suf);
}
break;
case DCH_Y:
if (flag == TO_CHAR)
{
snprintf(buff, sizeof(buff), "%1d", YEAR_ABS(tm->tm_year));
i = strlen(buff);
strcpy(inout, buff + (i - 1));
if (S_THth(suf))
{
str_numth(p_inout, inout, S_TH_TYPE(suf));
return 2;
}
return 0;
}
else if (flag == FROM_CHAR)
{
sscanf(inout, "%1d", &tmfc->year);
/*
* 1-digit year: always +2000
*/
tmfc->year += 2000;
return 0 + SKIP_THth(suf);
}
break;
case DCH_RM:
if (flag == TO_CHAR)
{
sprintf(inout, "%*s", S_FM(suf) ? 0 : -4,
rm_months_upper[12 - tm->tm_mon]);
if (S_FM(suf))
return strlen(p_inout) - 1;
else
return 3;
2000-07-01 23:27:14 +02:00
}
else if (flag == FROM_CHAR)
{
tmfc->mm = 12 - seq_search(inout, rm_months_upper, ALL_UPPER, FULL_SIZ, &len);
CHECK_SEQ_SEARCH(len, "RM");
if (S_FM(suf))
return len - 1;
else
return 3;
}
break;
case DCH_rm:
if (flag == TO_CHAR)
{
sprintf(inout, "%*s", S_FM(suf) ? 0 : -4,
rm_months_lower[12 - tm->tm_mon]);
if (S_FM(suf))
return strlen(p_inout) - 1;
else
return 3;
}
else if (flag == FROM_CHAR)
{
tmfc->mm = 12 - seq_search(inout, rm_months_lower, ALL_LOWER, FULL_SIZ, &len);
CHECK_SEQ_SEARCH(len, "rm");
if (S_FM(suf))
return len - 1;
else
return 3;
}
break;
case DCH_W:
if (flag == TO_CHAR)
{
2001-03-22 05:01:46 +01:00
sprintf(inout, "%d", (tm->tm_mday - 1) / 7 + 1);
if (S_THth(suf))
{
str_numth(p_inout, inout, S_TH_TYPE(suf));
return 2;
}
return 0;
}
else if (flag == FROM_CHAR)
{
sscanf(inout, "%1d", &tmfc->w);
return 0 + SKIP_THth(suf);
}
break;
case DCH_J:
if (flag == TO_CHAR)
{
sprintf(inout, "%d", date2j(tm->tm_year, tm->tm_mon, tm->tm_mday));
if (S_THth(suf))
str_numth(p_inout, inout, S_TH_TYPE(suf));
return strlen(p_inout) - 1;
}
else if (flag == FROM_CHAR)
{
sscanf(inout, "%d", &tmfc->j);
return strdigits_len(inout) - 1 + SKIP_THth(suf);
}
break;
}
return -1;
}
static DCHCacheEntry *
DCH_cache_getnew(char *str)
{
DCHCacheEntry *ent = NULL;
/* counter overload check - paranoia? */
if (DCHCounter + DCH_CACHE_FIELDS >= MAX_INT32)
{
DCHCounter = 0;
for (ent = DCHCache; ent <= (DCHCache + DCH_CACHE_FIELDS); ent++)
ent->age = (++DCHCounter);
}
/*
* Cache is full - needs remove any older 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; 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;
}
return (DCHCacheEntry *) NULL; /* never */
}
static DCHCacheEntry *
DCH_cache_search(char *str)
{
int i = 0;
DCHCacheEntry *ent;
/* counter overload check - paranoia? */
if (DCHCounter + DCH_CACHE_FIELDS >= MAX_INT32)
{
DCHCounter = 0;
for (ent = DCHCache; ent <= (DCHCache + DCH_CACHE_FIELDS); ent++)
ent->age = (++DCHCounter);
}
for (ent = DCHCache; ent <= (DCHCache + DCH_CACHE_FIELDS); ent++)
{
if (i == n_DCHCache)
break;
if (strcmp(ent->str, str) == 0)
{
ent->age = (++DCHCounter);
return ent;
}
i++;
}
return (DCHCacheEntry *) NULL;
}
static text *
datetime_to_char_body(TmToChar *tmtc, text *fmt)
{
2001-03-22 05:01:46 +01:00
FormatNode *format;
struct tm *tm = NULL;
char *str_fmt,
*result;
bool incache;
int len = VARSIZE(fmt) - VARHDRSZ;
tm = tmtcTm(tmtc);
tm->tm_wday = (date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) + 1) % 7;
tm->tm_yday = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - date2j(tm->tm_year, 1, 1) + 1;
/*
* Convert fmt to C string
*/
str_fmt = (char *) palloc(len + 1);
memcpy(str_fmt, VARDATA(fmt), len);
*(str_fmt + len) = '\0';
/*
* Allocate result
*/
result = palloc((len * DCH_MAX_ITEM_SIZ) + 1);
/*
* Allocate new memory if format picture is bigger than static cache
* and not use cache (call parser always) - incache=FALSE show this
* variant
*/
if (len > DCH_CACHE_SIZE)
{
format = (FormatNode *) palloc((len + 1) * sizeof(FormatNode));
incache = FALSE;
parse_format(format, str_fmt, DCH_keywords,
DCH_suff, DCH_index, DCH_TYPE, NULL);
(format + len)->type = NODE_TYPE_END; /* Paranoia? */
2000-07-01 23:27:14 +02:00
}
else
{
/*
* Use cache buffers
*/
DCHCacheEntry *ent;
incache = TRUE;
if ((ent = DCH_cache_search(str_fmt)) == NULL)
{
ent = DCH_cache_getnew(str_fmt);
/*
* Not in the cache, must run parser and save a new
* format-picture to the cache.
*/
parse_format(ent->format, str_fmt, DCH_keywords,
2001-03-22 05:01:46 +01:00
DCH_suff, DCH_index, DCH_TYPE, NULL);
(ent->format + len)->type = NODE_TYPE_END; /* Paranoia? */
#ifdef DEBUG_TO_FROM_CHAR
/* dump_node(ent->format, len); */
2000-04-07 21:17:51 +02:00
/* dump_index(DCH_keywords, DCH_index); */
#endif
}
format = ent->format;
}
DCH_processor(format, result, TO_CHAR, (void *) tmtc);
if (!incache)
pfree(format);
pfree(str_fmt);
/*
* for result is allocated max memory, which current format-picture
* needs, now it allocate result with real size
*/
if (!(len = strlen(result)))
pfree(result);
else
{
text *res = (text *) palloc(len + 1 + VARHDRSZ);
memcpy(VARDATA(res), result, len);
VARATT_SIZEP(res) = len + VARHDRSZ;
return res;
}
return NULL;
}
2001-03-22 05:01:46 +01:00
/****************************************************************************
* 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;
if ((VARSIZE(fmt) - VARHDRSZ) <= 0 || TIMESTAMP_NOT_FINITE(dt))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
if (timestamp2tm(dt, NULL, tmtcTm(&tmtc), &tmtcFsec(&tmtc), NULL) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
if (!(res = datetime_to_char_body(&tmtc, fmt)))
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;
if ((VARSIZE(fmt) - VARHDRSZ) <= 0 || TIMESTAMP_NOT_FINITE(dt))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
if (timestamp2tm(dt, &tz, tmtcTm(&tmtc), &tmtcFsec(&tmtc), &tmtcTzn(&tmtc)) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
if (!(res = datetime_to_char_body(&tmtc, fmt)))
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;
if ((VARSIZE(fmt) - VARHDRSZ) <= 0)
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
if (interval2tm(*it, tmtcTm(&tmtc), &tmtcFsec(&tmtc)) != 0)
PG_RETURN_NULL();
if (!(res = datetime_to_char_body(&tmtc, fmt)))
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() )
* ---------------------
*/
2000-07-01 23:27:14 +02:00
Datum
to_timestamp(PG_FUNCTION_ARGS)
{
text *date_txt = PG_GETARG_TEXT_P(0);
text *fmt = PG_GETARG_TEXT_P(1);
Timestamp result;
2001-03-22 05:01:46 +01:00
FormatNode *format;
TmFromChar tmfc;
bool incache;
char *str;
char *date_str;
int len,
date_len,
tz = 0;
struct tm tm;
Support alternate storage scheme of 64-bit integer for date/time types. Use "--enable-integer-datetimes" in configuration to use this rather than the original float8 storage. I would recommend the integer-based storage for any platform on which it is available. We perhaps should make this the default for the production release. Change timezone(timestamptz) results to return timestamp rather than a character string. Formerly, we didn't have a way to represent timestamps with an explicit time zone other than freezing the info into a string. Now, we can reasonably omit the explicit time zone from the result and return a timestamp with values appropriate for the specified time zone. Much cleaner, and if you need the time zone in the result you can put it into a character string pretty easily anyway. Allow fractional seconds in date/time types even for dates prior to 1BC. Limit timestamp data types to 6 decimal places of precision. Just right for a micro-second storage of int8 date/time types, and reduces the number of places ad-hoc rounding was occuring for the float8-based types. Use lookup tables for precision/rounding calculations for timestamp and interval types. Formerly used pow() to calculate the desired value but with a more limited range there is no reason to not type in a lookup table. Should be *much* better performance, though formerly there were some optimizations to help minimize the number of times pow() was called. Define a HAVE_INT64_TIMESTAMP variable. Based on the configure option "--enable-integer-datetimes" and the existing internal INT64_IS_BUSTED. Add explicit date/interval operators and functions for addition and subtraction. Formerly relied on implicit type promotion from date to timestamp with time zone. Change timezone conversion functions for the timetz type from "timetz()" to "timezone()". This is consistant with other time zone coersion functions for other types. Bump the catalog version to 200204201. Fix up regression tests to reflect changes in fractional seconds representation for date/times in BC eras. All regression tests pass on my Linux box.
2002-04-21 21:52:18 +02:00
fsec_t fsec = 0;
ZERO_tm(&tm);
ZERO_tmfc(&tmfc);
2000-07-01 23:27:14 +02:00
len = VARSIZE(fmt) - VARHDRSZ;
if (len)
{
str = (char *) palloc(len + 1);
memcpy(str, VARDATA(fmt), len);
*(str + len) = '\0';
/*
* Allocate new memory if format picture is bigger than static
* cache and not use cache (call parser always) - incache=FALSE
* show this variant
*/
if (len > DCH_CACHE_SIZE)
{
format = (FormatNode *) palloc((len + 1) * sizeof(FormatNode));
incache = FALSE;
parse_format(format, str, DCH_keywords,
DCH_suff, DCH_index, DCH_TYPE, NULL);
(format + len)->type = NODE_TYPE_END; /* Paranoia? */
}
else
{
/*
* Use cache buffers
*/
DCHCacheEntry *ent;
incache = 0;
if ((ent = DCH_cache_search(str)) == NULL)
{
2000-07-01 23:27:14 +02:00
ent = DCH_cache_getnew(str);
/*
* Not in the cache, must run parser and save a new
* format-picture to the cache.
*/
parse_format(ent->format, str, DCH_keywords,
DCH_suff, DCH_index, DCH_TYPE, NULL);
(ent->format + len)->type = NODE_TYPE_END; /* Paranoia? */
#ifdef DEBUG_TO_FROM_CHAR
/* dump_node(ent->format, len); */
/* dump_index(DCH_keywords, DCH_index); */
#endif
}
format = ent->format;
}
/*
* Call action for each node in FormatNode tree
*/
#ifdef DEBUG_TO_FROM_CHAR
/* dump_node(format, len); */
#endif
/*
* Convert date to C string
*/
date_len = VARSIZE(date_txt) - VARHDRSZ;
date_str = (char *) palloc(date_len + 1);
memcpy(date_str, VARDATA(date_txt), date_len);
*(date_str + date_len) = '\0';
DCH_processor(format, date_str, FROM_CHAR, (void *) &tmfc);
pfree(date_str);
pfree(str);
if (incache)
pfree(format);
}
DEBUG_TMFC(&tmfc);
/*
* Convert values that user define for FROM_CHAR
* (to_date/to_timestamp) to standard 'tm'
2001-03-22 05:01:46 +01:00
*/
if (tmfc.ssss)
{
int x = tmfc.ssss;
tm.tm_hour = x / 3600;
x %= 3600;
tm.tm_min = x / 60;
x %= 60;
tm.tm_sec = x;
}
if (tmfc.cc)
tm.tm_year = (tmfc.cc - 1) * 100;
if (tmfc.ww)
tmfc.ddd = (tmfc.ww - 1) * 7 + 1;
if (tmfc.w)
tmfc.dd = (tmfc.w - 1) * 7 + 1;
if (tmfc.ss)
tm.tm_sec = tmfc.ss;
if (tmfc.mi)
tm.tm_min = tmfc.mi;
if (tmfc.hh)
tm.tm_hour = tmfc.hh;
2001-03-22 05:01:46 +01:00
if (tmfc.pm || tmfc.am)
2001-03-22 05:01:46 +01:00
{
if (tm.tm_hour < 1 || tm.tm_hour > 12)
ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("AM/PM hour must be between 1 and 12")));
2001-03-22 05:01:46 +01:00
if (tmfc.pm && tm.tm_hour < 12)
tm.tm_hour += 12;
2001-03-22 05:01:46 +01:00
else if (tmfc.am && tm.tm_hour == 12)
tm.tm_hour = 0;
2001-03-22 05:01:46 +01:00
}
switch (tmfc.q)
{
2001-03-22 05:01:46 +01:00
case 1:
tm.tm_mday = 1;
tm.tm_mon = 1;
2001-03-22 05:01:46 +01:00
break;
case 2:
tm.tm_mday = 1;
tm.tm_mon = 4;
2001-03-22 05:01:46 +01:00
break;
case 3:
tm.tm_mday = 1;
tm.tm_mon = 7;
2001-03-22 05:01:46 +01:00
break;
case 4:
tm.tm_mday = 1;
tm.tm_mon = 10;
2001-03-22 05:01:46 +01:00
break;
}
2001-03-22 05:01:46 +01:00
if (tmfc.year)
tm.tm_year = tmfc.year;
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\"",
2003-08-04 02:43:34 +02:00
tm.tm_year)));
2001-03-22 05:01:46 +01:00
}
if (tmfc.j)
j2date(tmfc.j, &tm.tm_year, &tm.tm_mon, &tm.tm_mday);
2001-03-22 05:01:46 +01:00
if (tmfc.iw)
isoweek2date(tmfc.iw, &tm.tm_year, &tm.tm_mon, &tm.tm_mday);
2001-03-22 05:01:46 +01:00
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;
/*
* we don't ignore DDD
*/
if (tmfc.ddd && (tm.tm_mon <= 1 || tm.tm_mday <= 1))
{
/* count mday and mon from yday */
2001-03-22 05:01:46 +01:00
int *y,
i;
int ysum[2][13] = {
{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, 0}};
if (!tm.tm_year)
ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
2003-08-04 02:43:34 +02:00
errmsg("cannot convert yday without year information")));
2001-03-22 05:01:46 +01:00
y = ysum[isleap(tm.tm_year)];
2001-03-22 05:01:46 +01:00
for (i = 0; i <= 11; i++)
{
if (tm.tm_yday < y[i])
break;
2001-03-22 05:01:46 +01:00
}
if (tm.tm_mon <= 1)
tm.tm_mon = i + 1;
2001-03-22 05:01:46 +01:00
if (tm.tm_mday <= 1)
tm.tm_mday = i == 0 ? tm.tm_yday :
tm.tm_yday - y[i - 1];
}
2001-03-22 05:01:46 +01:00
Support alternate storage scheme of 64-bit integer for date/time types. Use "--enable-integer-datetimes" in configuration to use this rather than the original float8 storage. I would recommend the integer-based storage for any platform on which it is available. We perhaps should make this the default for the production release. Change timezone(timestamptz) results to return timestamp rather than a character string. Formerly, we didn't have a way to represent timestamps with an explicit time zone other than freezing the info into a string. Now, we can reasonably omit the explicit time zone from the result and return a timestamp with values appropriate for the specified time zone. Much cleaner, and if you need the time zone in the result you can put it into a character string pretty easily anyway. Allow fractional seconds in date/time types even for dates prior to 1BC. Limit timestamp data types to 6 decimal places of precision. Just right for a micro-second storage of int8 date/time types, and reduces the number of places ad-hoc rounding was occuring for the float8-based types. Use lookup tables for precision/rounding calculations for timestamp and interval types. Formerly used pow() to calculate the desired value but with a more limited range there is no reason to not type in a lookup table. Should be *much* better performance, though formerly there were some optimizations to help minimize the number of times pow() was called. Define a HAVE_INT64_TIMESTAMP variable. Based on the configure option "--enable-integer-datetimes" and the existing internal INT64_IS_BUSTED. Add explicit date/interval operators and functions for addition and subtraction. Formerly relied on implicit type promotion from date to timestamp with time zone. Change timezone conversion functions for the timetz type from "timetz()" to "timezone()". This is consistant with other time zone coersion functions for other types. Bump the catalog version to 200204201. Fix up regression tests to reflect changes in fractional seconds representation for date/times in BC eras. All regression tests pass on my Linux box.
2002-04-21 21:52:18 +02:00
#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;
Support alternate storage scheme of 64-bit integer for date/time types. Use "--enable-integer-datetimes" in configuration to use this rather than the original float8 storage. I would recommend the integer-based storage for any platform on which it is available. We perhaps should make this the default for the production release. Change timezone(timestamptz) results to return timestamp rather than a character string. Formerly, we didn't have a way to represent timestamps with an explicit time zone other than freezing the info into a string. Now, we can reasonably omit the explicit time zone from the result and return a timestamp with values appropriate for the specified time zone. Much cleaner, and if you need the time zone in the result you can put it into a character string pretty easily anyway. Allow fractional seconds in date/time types even for dates prior to 1BC. Limit timestamp data types to 6 decimal places of precision. Just right for a micro-second storage of int8 date/time types, and reduces the number of places ad-hoc rounding was occuring for the float8-based types. Use lookup tables for precision/rounding calculations for timestamp and interval types. Formerly used pow() to calculate the desired value but with a more limited range there is no reason to not type in a lookup table. Should be *much* better performance, though formerly there were some optimizations to help minimize the number of times pow() was called. Define a HAVE_INT64_TIMESTAMP variable. Based on the configure option "--enable-integer-datetimes" and the existing internal INT64_IS_BUSTED. Add explicit date/interval operators and functions for addition and subtraction. Formerly relied on implicit type promotion from date to timestamp with time zone. Change timezone conversion functions for the timetz type from "timetz()" to "timezone()". This is consistant with other time zone coersion functions for other types. Bump the catalog version to 200204201. Fix up regression tests to reflect changes in fractional seconds representation for date/times in BC eras. All regression tests pass on my Linux box.
2002-04-21 21:52:18 +02:00
#endif
/* -------------------------------------------------------------- */
DEBUG_TM(&tm);
tz = DetermineLocalTimeZone(&tm);
if (tm2timestamp(&tm, fsec, &tz, &result) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("timestamp out of range")));
2000-07-01 23:27:14 +02:00
PG_RETURN_TIMESTAMP(result);
}
/* ----------
* TO_DATE
* Make Date from date_str which is formated at argument 'fmt'
* ----------
*/
2000-07-01 23:27:14 +02:00
Datum
to_date(PG_FUNCTION_ARGS)
{
2001-03-22 05:01:46 +01:00
/*
* Quick hack: since our inputs are just like to_timestamp, hand over
* the whole input info struct...
2000-07-01 23:27:14 +02:00
*/
return DirectFunctionCall1(timestamptz_date, to_timestamp(fcinfo));
}
/**********************************************************************
* the NUMBER version part
*********************************************************************/
static char *
fill_str(char *str, int c, int max)
{
memset(str, c, max);
*(str + max + 1) = '\0';
return str;
}
#define zeroize_NUM(_n) \
do { \
(_n)->flag = 0; \
(_n)->lsign = 0; \
(_n)->pre = 0; \
(_n)->post = 0; \
2001-03-22 05:01:46 +01:00
(_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 = NULL;
/* counter overload check - paranoia? */
if (NUMCounter + NUM_CACHE_FIELDS >= MAX_INT32)
{
NUMCounter = 0;
for (ent = NUMCache; ent <= (NUMCache + NUM_CACHE_FIELDS); ent++)
ent->age = (++NUMCounter);
}
/*
* Cache is full - needs remove any older 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++)
{
2001-03-22 05:01:46 +01:00
/*
* entry removed via NUM_cache_remove() can be used here
*/
2001-03-22 05:01:46 +01:00
if (*ent->str == '\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; /* never */
}
static NUMCacheEntry *
NUM_cache_search(char *str)
{
2001-03-22 05:01:46 +01:00
int i = 0;
NUMCacheEntry *ent;
/* counter overload check - paranoia? */
if (NUMCounter + NUM_CACHE_FIELDS >= MAX_INT32)
{
NUMCounter = 0;
for (ent = NUMCache; ent <= (NUMCache + NUM_CACHE_FIELDS); ent++)
ent->age = (++NUMCounter);
}
for (ent = NUMCache; ent <= (NUMCache + NUM_CACHE_FIELDS); ent++)
{
if (i == n_NUMCache)
break;
if (strcmp(ent->str, str) == 0)
{
ent->age = (++NUMCounter);
last_NUMCacheEntry = ent;
return ent;
}
i++;
}
return (NUMCacheEntry *) 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';
ent->age = 0;
}
/* ----------
* Cache routine for NUM to_char version
* ----------
*/
static FormatNode *
NUM_cache(int len, NUMDesc *Num, char *pars_str, bool *shouldFree)
{
FormatNode *format = NULL;
char *str;
/*
* Convert VARDATA() to string
*/
str = (char *) palloc(len + 1);
memcpy(str, pars_str, len);
*(str + len) = '\0';
/*
* 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);
(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);
(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
2000-04-07 21:17:51 +02:00
/* dump_node(format, len); */
dump_index(NUM_keywords, NUM_index);
#endif
2000-04-07 21:17:51 +02:00
pfree(str);
return format;
}
static char *
int_to_roman(int number)
{
2001-03-22 05:01:46 +01:00
int len = 0,
num = 0,
set = 0;
char *p = NULL,
*result,
numstr[5];
result = (char *) palloc(16);
*result = '\0';
if (number > 3999 || number < 1)
{
fill_str(result, '#', 15);
return result;
}
2001-02-27 09:13:31 +01:00
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 (num == -1 && set == 0)
continue;
set = 1;
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 thousands separator
*/
if (lconv->thousands_sep && *lconv->thousands_sep)
Np->L_thousands_sep = lconv->thousands_sep;
else
Np->L_thousands_sep = ",";
/*
* Number decimal point
*/
if (lconv->decimal_point && *lconv->decimal_point)
Np->decimal = lconv->decimal_point;
else
Np->decimal = ".";
/*
* Currency symbol
*/
if (lconv->currency_symbol && *lconv->currency_symbol)
Np->L_currency_symbol = lconv->currency_symbol;
else
Np->L_currency_symbol = " ";
if (!IS_LDECIMAL(Np->Num))
Np->decimal = ".";
}
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,
2001-03-22 05:01:46 +01:00
*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)
{
#ifdef DEBUG_TO_FROM_CHAR
elog(DEBUG_elog_output, " --- scan start --- ");
#endif
if (*Np->inout_p == ' ')
Np->inout_p++;
#define OVERLOAD_TEST (Np->inout_p >= Np->inout + plen)
if (*Np->inout_p == ' ')
Np->inout_p++;
if (OVERLOAD_TEST)
return;
/*
* read sign
*/
if (*Np->number == ' ' && (id == NUM_0 || id == NUM_9 || NUM_S))
{
#ifdef DEBUG_TO_FROM_CHAR
elog(DEBUG_elog_output, "Try read sign (%c)", *Np->inout_p);
#endif
/*
* locale sign
*/
if (IS_LSIGN(Np->Num))
{
int x = strlen(Np->L_negative_sign);
#ifdef DEBUG_TO_FROM_CHAR
elog(DEBUG_elog_output, "Try read locale sign (%c)", *Np->inout_p);
#endif
if (!strncmp(Np->inout_p, Np->L_negative_sign, x))
{
Np->inout_p += x - 1;
*Np->number = '-';
return;
}
x = strlen(Np->L_positive_sign);
if (!strncmp(Np->inout_p, Np->L_positive_sign, x))
{
Np->inout_p += x - 1;
*Np->number = '+';
return;
}
}
#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) &&
2003-08-04 02:43:34 +02:00
*Np->inout_p == '<'))
{
*Np->number = '-'; /* set - */
Np->inout_p++;
}
else if (*Np->inout_p == '+')
{
*Np->number = '+'; /* set + */
Np->inout_p++;
}
}
if (OVERLOAD_TEST)
return;
/*
* 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++;
#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))
{
#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;
}
else
{
2001-03-22 05:01:46 +01:00
int x = strlen(Np->decimal);
#ifdef DEBUG_TO_FROM_CHAR
elog(DEBUG_elog_output, "Try read locale point (%c)",
*Np->inout_p);
#endif
if (!strncmp(Np->inout_p, Np->decimal, x))
{
Np->inout_p += x - 1;
*Np->number_p = '.';
Np->number_p++;
Np->read_dec = TRUE;
}
}
}
}
2003-03-27 17:35:31 +01:00
#define IS_PREDEC_SPACE(_n) \
(IS_ZERO((_n)->Num)==FALSE && \
(_n)->number == (_n)->number_p && \
*(_n)->number == '0' && \
2003-08-04 02:43:34 +02:00
(_n)->Num->post != 0)
2003-03-27 17:35:31 +01:00
/* ----------
* Add digit or sign to number-string
* ----------
*/
static void
NUM_numpart_to_char(NUMProc *Np, int id)
{
2003-08-04 02:43:34 +02:00
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;
/*
2003-08-04 02:43:34 +02:00
* Write sign if real number will write to output Note:
* IS_PREDEC_SPACE() handle "9.9" --> " .1"
*/
2003-08-04 02:43:34 +02:00
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))
{
2003-03-27 17:35:31 +01:00
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))
{
2003-03-27 17:35:31 +01:00
*Np->inout_p = Np->sign == '+' ? ' ' : '<';
++Np->inout_p;
2003-03-27 17:35:31 +01:00
Np->sign_wrote = TRUE;
}
else if (Np->sign == '+')
{
2003-03-27 17:35:31 +01:00
if (!IS_FILLMODE(Np->Num))
{
2003-08-04 02:43:34 +02:00
*Np->inout_p = ' '; /* Write + */
2003-03-27 17:35:31 +01:00
++Np->inout_p;
}
Np->sign_wrote = TRUE;
}
else if (Np->sign == '-')
{ /* Write - */
*Np->inout_p = '-';
++Np->inout_p;
2003-03-27 17:35:31 +01:00
Np->sign_wrote = TRUE;
}
}
2003-08-04 02:43:34 +02:00
/*
* digits / FM / Zero / Dec. point
*/
2003-03-27 17:35:31 +01:00
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);
}
2003-08-04 02:43:34 +02:00
/*
* Ora 'n' -- FM9.9 --> 'n.'
*/
2003-08-04 02:43:34 +02:00
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)
;
2003-08-04 02:43:34 +02:00
/*
2003-03-27 17:35:31 +01:00
* '0.1' -- 9.9 --> ' .1'
*/
2003-03-27 17:35:31 +01:00
else if (IS_PREDEC_SPACE(Np))
{
if (!IS_FILLMODE(Np->Num))
{
*Np->inout_p = ' ';
++Np->inout_p;
}
2003-08-04 02:43:34 +02:00
/*
2003-03-27 17:35:31 +01:00
* '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;
}
2003-03-27 17:35:31 +01:00
end = Np->num_count + (Np->num_pre ? 1 : 0) + (IS_DECIMAL(Np->Num) ? 1 : 0);
2003-08-04 02:43:34 +02:00
2003-03-27 17:35:31 +01:00
if (Np->last_relevant && Np->last_relevant == Np->number_p)
end = Np->num_curr;
2003-08-04 02:43:34 +02:00
if (Np->num_curr + 1 == end)
2003-03-27 17:35:31 +01:00
{
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, int type)
{
FormatNode *n;
2001-03-22 05:01:46 +01:00
NUMProc _Np,
*Np = &_Np;
MemSet(Np, 0, sizeof(NUMProc));
Np->Num = Num;
Np->type = type;
Np->number = number;
Np->inout = inout;
Np->last_relevant = NULL;
Np->read_post = 0;
Np->read_dec = FALSE;
if (Np->Num->zero_start)
--Np->Num->zero_start;
2003-08-04 02:43:34 +02:00
/*
* Roman correction
*/
if (IS_ROMAN(Np->Num))
{
if (Np->type == FROM_CHAR)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("\"RN\" not supported")));
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 (type == FROM_CHAR)
Np->sign = FALSE;
else
{
Np->sign = sign;
2003-08-04 02:43:34 +02:00
2003-03-27 17:35:31 +01:00
/* MI/PL/SG - write sign itself and not in number */
if (IS_PLUS(Np->Num) || IS_MINUS(Np->Num))
{
2003-08-04 02:43:34 +02:00
if (IS_PLUS(Np->Num) && IS_MINUS(Np->Num) == FALSE)
Np->sign_wrote = FALSE; /* need sign */
else
2003-08-04 02:43:34 +02:00
Np->sign_wrote = TRUE; /* needn't sign */
}
else
2003-03-27 17:35:31 +01:00
{
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;
2003-08-04 02:43:34 +02:00
if (Np->sign == '+' && IS_FILLMODE(Np->Num) && IS_LSIGN(Np->Num) == FALSE)
Np->sign_wrote = TRUE; /* needn't sign */
2003-03-27 17:35:31 +01:00
else
2003-08-04 02:43:34 +02:00
Np->sign_wrote = FALSE; /* need sign */
2003-03-27 17:35:31 +01:00
if (Np->Num->lsign == NUM_LSIGN_PRE && Np->Num->pre == Np->Num->pre_lsign_num)
Np->Num->lsign = NUM_LSIGN_POST;
}
}
/*
* Count
*/
Np->num_count = Np->Num->post + Np->Num->pre - 1;
if (type == TO_CHAR)
{
Np->num_pre = plen;
if (IS_FILLMODE(Np->Num))
{
if (IS_DECIMAL(Np->Num))
Np->last_relevant = get_last_relevant_decnum(
2003-08-04 02:43:34 +02:00
Np->number +
2001-03-22 05:01:46 +01:00
((Np->Num->zero_end - Np->num_pre > 0) ?
Np->Num->zero_end - Np->num_pre : 0));
}
2003-08-04 02:43:34 +02:00
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,
2003-03-27 17:35:31 +01:00
"\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,
2003-03-27 17:35:31 +01:00
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"
2003-08-04 02:43:34 +02:00
);
#endif
/*
* Locale
*/
NUM_prepare_locale(Np);
/*
* Processor direct cycle
*/
if (Np->type == FROM_CHAR)
Np->number_p = Np->number + 1; /* first char is space for sign */
else if (Np->type == TO_CHAR)
Np->number_p = Np->number;
for (n = node, Np->inout_p = Np->inout; n->type != NODE_TYPE_END; n++)
{
if (Np->type == FROM_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
*/
switch (n->key->id)
{
case NUM_9:
case NUM_0:
case NUM_DEC:
case NUM_D:
if (Np->type == 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->type == TO_CHAR)
{
if (!Np->num_in)
{
if (IS_FILLMODE(Np->Num))
continue;
else
*Np->inout_p = ' ';
}
else
*Np->inout_p = ',';
}
else if (Np->type == FROM_CHAR)
{
if (!Np->num_in)
{
if (IS_FILLMODE(Np->Num))
continue;
}
}
break;
case NUM_G:
if (Np->type == 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->type == FROM_CHAR)
{
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->type == TO_CHAR)
{
strcpy(Np->inout_p, Np->L_currency_symbol);
Np->inout_p += strlen(Np->inout_p) - 1;
}
else if (Np->type == FROM_CHAR)
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(Np->number_p));
Np->inout_p += strlen(Np->inout_p) - 1;
}
else
{
sprintf(Np->inout_p, "%15s", str_tolower(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->type == 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->type == TO_CHAR)
strcpy(Np->inout_p, get_th(Np->number, TH_UPPER));
Np->inout_p += 1;
break;
case NUM_MI:
if (Np->type == TO_CHAR)
{
if (Np->sign == '-')
*Np->inout_p = '-';
2003-03-27 17:35:31 +01:00
else if (IS_FILLMODE(Np->Num))
continue;
else
*Np->inout_p = ' ';
}
else if (Np->type == FROM_CHAR)
{
if (*Np->inout_p == '-')
*Np->number = '-';
}
break;
case NUM_PL:
if (Np->type == TO_CHAR)
{
if (Np->sign == '+')
*Np->inout_p = '+';
2003-03-27 17:35:31 +01:00
else if (IS_FILLMODE(Np->Num))
continue;
else
*Np->inout_p = ' ';
}
else if (Np->type == FROM_CHAR)
{
if (*Np->inout_p == '+')
*Np->number = '+';
}
break;
case NUM_SG:
if (Np->type == TO_CHAR)
*Np->inout_p = Np->sign;
else if (Np->type == FROM_CHAR)
{
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->type == TO_CHAR)
*Np->inout_p = n->character;
}
Np->inout_p++;
}
if (Np->type == TO_CHAR)
{
*Np->inout_p = '\0';
return Np->inout;
}
else if (Np->type == FROM_CHAR)
{
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;
}
else
return NULL;
return NULL;
}
/* ----------
* 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; \
2000-07-01 23:27:14 +02:00
if (len <= 0) \
return DirectFunctionCall1(textin, CStringGetDatum("")); \
result = (text *) palloc( (len * NUM_MAX_ITEM_SIZ) + 1 + VARHDRSZ); \
2003-03-27 17:35:31 +01:00
memset(result, 0, (len * NUM_MAX_ITEM_SIZ) + 1 + VARHDRSZ ); \
format = NUM_cache(len, &Num, VARDATA(fmt), &shouldFree); \
} while (0)
/* ----------
* MACRO: Finish part of NUM
* ----------
*/
#define NUM_TOCHAR_finish \
do { \
NUM_processor(format, &Num, VARDATA(result), \
numstr, plen, sign, TO_CHAR); \
pfree(orgnum); \
\
if (shouldFree) \
pfree(format); \
\
/*
* for result is allocated max memory, which current format-picture\
* needs, now it must be re-allocate to result real size \
*/ \
if (!(len = strlen(VARDATA(result)))) \
{ \
pfree(result); \
PG_RETURN_NULL(); \
} \
\
result_tmp = result; \
result = (text *) palloc( len + 1 + VARHDRSZ); \
\
strcpy( VARDATA(result), VARDATA(result_tmp)); \
VARATT_SIZEP(result) = len + VARHDRSZ; \
pfree(result_tmp); \
} while(0)
/* -------------------
* NUMERIC to_number() (convert string to numeric)
* -------------------
*/
2000-07-01 23:27:14 +02:00
Datum
numeric_to_number(PG_FUNCTION_ARGS)
{
2001-03-22 05:01:46 +01:00
text *value = PG_GETARG_TEXT_P(0);
text *fmt = PG_GETARG_TEXT_P(1);
NUMDesc Num;
Datum result;
FormatNode *format;
2001-03-22 05:01:46 +01:00
char *numstr;
bool shouldFree;
2001-03-22 05:01:46 +01:00
int len = 0;
int scale,
precision;
len = VARSIZE(fmt) - VARHDRSZ;
2000-07-01 23:27:14 +02:00
if (len <= 0)
PG_RETURN_NULL();
format = NUM_cache(len, &Num, VARDATA(fmt), &shouldFree);
numstr = (char *) palloc((len * NUM_MAX_ITEM_SIZ) + 1);
NUM_processor(format, &Num, VARDATA(value), numstr,
VARSIZE(value) - VARHDRSZ, 0, FROM_CHAR);
scale = Num.post;
precision = Max(0, Num.pre) + scale;
if (shouldFree)
pfree(format);
2000-07-01 23:27:14 +02:00
result = DirectFunctionCall3(numeric_in,
2001-03-22 05:01:46 +01:00
CStringGetDatum(numstr),
ObjectIdGetDatum(InvalidOid),
Int32GetDatum(((precision << 16) | scale) + VARHDRSZ));
pfree(numstr);
return result;
}
/* ------------------
* NUMERIC to_char()
* ------------------
*/
2000-07-01 23:27:14 +02:00
Datum
numeric_to_char(PG_FUNCTION_ARGS)
{
2001-03-22 05:01:46 +01:00
Numeric value = PG_GETARG_NUMERIC(0);
text *fmt = PG_GETARG_TEXT_P(1);
NUMDesc Num;
FormatNode *format;
2001-03-22 05:01:46 +01:00
text *result,
*result_tmp;
bool shouldFree;
2001-03-22 05:01:46 +01:00
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))
{
2000-07-01 23:27:14 +02:00
x = DatumGetNumeric(DirectFunctionCall2(numeric_round,
2001-03-22 05:01:46 +01:00
NumericGetDatum(value),
Int32GetDatum(0)));
numstr = orgnum =
int_to_roman(DatumGetInt32(DirectFunctionCall1(numeric_int4,
2001-03-22 05:01:46 +01:00
NumericGetDatum(x))));
pfree(x);
}
else
{
Numeric val = value;
if (IS_MULTI(&Num))
{
2000-07-01 23:27:14 +02:00
Numeric a = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
2001-03-22 05:01:46 +01:00
Int32GetDatum(10)));
2000-07-01 23:27:14 +02:00
Numeric b = DatumGetNumeric(DirectFunctionCall1(int4_numeric,
2001-03-22 05:01:46 +01:00
Int32GetDatum(Num.multi)));
x = DatumGetNumeric(DirectFunctionCall2(numeric_power,
NumericGetDatum(a),
NumericGetDatum(b)));
val = DatumGetNumeric(DirectFunctionCall2(numeric_mul,
2001-03-22 05:01:46 +01:00
NumericGetDatum(value),
NumericGetDatum(x)));
pfree(x);
pfree(a);
pfree(b);
Num.pre += Num.multi;
}
2000-07-01 23:27:14 +02:00
x = DatumGetNumeric(DirectFunctionCall2(numeric_round,
2001-03-22 05:01:46 +01:00
NumericGetDatum(val),
Int32GetDatum(Num.post)));
2000-07-01 23:27:14 +02:00
orgnum = DatumGetCString(DirectFunctionCall1(numeric_out,
2001-03-22 05:01:46 +01:00
NumericGetDatum(x)));
pfree(x);
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)
{
fill_str(numstr, '#', Num.pre);
*(numstr + Num.pre) = '.';
fill_str(numstr + 1 + Num.pre, '#', Num.post);
}
if (IS_MULTI(&Num))
pfree(val);
}
NUM_TOCHAR_finish;
2000-07-01 23:27:14 +02:00
PG_RETURN_TEXT_P(result);
}
/* ---------------
* INT4 to_char()
* ---------------
*/
2000-07-01 23:27:14 +02:00
Datum
int4_to_char(PG_FUNCTION_ARGS)
{
2001-03-22 05:01:46 +01:00
int32 value = PG_GETARG_INT32(0);
text *fmt = PG_GETARG_TEXT_P(1);
NUMDesc Num;
FormatNode *format;
2001-03-22 05:01:46 +01:00
text *result,
*result_tmp;
bool shouldFree;
2001-03-22 05:01:46 +01:00
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,
2001-03-22 05:01:46 +01:00
Int32GetDatum(value * ((int32) pow((double) 10, (double) Num.multi)))));
Num.pre += Num.multi;
}
else
{
orgnum = DatumGetCString(DirectFunctionCall1(int4out,
2001-03-22 05:01:46 +01:00
Int32GetDatum(value)));
}
len = strlen(orgnum);
if (*orgnum == '-')
{ /* < 0 */
sign = '-';
--len;
}
else
sign = '+';
if (Num.post)
{
2001-03-22 05:01:46 +01:00
int i;
numstr = (char *) palloc(len + Num.post + 2);
strcpy(numstr, orgnum + (*orgnum == '-' ? 1 : 0));
*(numstr + len) = '.';
for (i = len + 1; i <= len + Num.post; i++)
*(numstr + i) = '0';
*(numstr + len + Num.post + 1) = '\0';
pfree(orgnum);
orgnum = numstr;
}
else
numstr = orgnum + (*orgnum == '-' ? 1 : 0);
if (Num.pre > len)
plen = Num.pre - len;
else if (len > Num.pre)
{
fill_str(numstr, '#', Num.pre);
*(numstr + Num.pre) = '.';
fill_str(numstr + 1 + Num.pre, '#', Num.post);
}
}
NUM_TOCHAR_finish;
2000-07-01 23:27:14 +02:00
PG_RETURN_TEXT_P(result);
}
/* ---------------
* INT8 to_char()
* ---------------
*/
2000-07-01 23:27:14 +02:00
Datum
int8_to_char(PG_FUNCTION_ARGS)
{
2001-03-22 05:01:46 +01:00
int64 value = PG_GETARG_INT64(0);
text *fmt = PG_GETARG_TEXT_P(1);
NUMDesc Num;
FormatNode *format;
2001-03-22 05:01:46 +01:00
text *result,
*result_tmp;
bool shouldFree;
2001-03-22 05:01:46 +01:00
int len = 0,
plen = 0,
sign = 0;
char *numstr,
*orgnum;
NUM_TOCHAR_prepare;
/*
* On DateType depend part (int32)
*/
if (IS_ROMAN(&Num))
{
2000-07-01 23:27:14 +02:00
/* Currently don't support int8 conversion to roman... */
numstr = orgnum = int_to_roman(DatumGetInt32(
2001-03-22 05:01:46 +01:00
DirectFunctionCall1(int84, Int64GetDatum(value))));
}
else
{
if (IS_MULTI(&Num))
{
double multi = pow((double) 10, (double) Num.multi);
2000-07-01 23:27:14 +02:00
value = DatumGetInt64(DirectFunctionCall2(int8mul,
2001-03-22 05:01:46 +01:00
Int64GetDatum(value),
DirectFunctionCall1(dtoi8,
Float8GetDatum(multi))));
Num.pre += Num.multi;
}
2000-07-01 23:27:14 +02:00
orgnum = DatumGetCString(DirectFunctionCall1(int8out,
2001-03-22 05:01:46 +01:00
Int64GetDatum(value)));
len = strlen(orgnum);
if (*orgnum == '-')
{ /* < 0 */
sign = '-';
--len;
}
else
sign = '+';
if (Num.post)
{
2001-03-22 05:01:46 +01:00
int i;
numstr = (char *) palloc(len + Num.post + 2);
strcpy(numstr, orgnum + (*orgnum == '-' ? 1 : 0));
*(numstr + len) = '.';
for (i = len + 1; i <= len + Num.post; i++)
*(numstr + i) = '0';
*(numstr + len + Num.post + 1) = '\0';
pfree(orgnum);
orgnum = numstr;
}
else
numstr = orgnum + (*orgnum == '-' ? 1 : 0);
if (Num.pre > len)
plen = Num.pre - len;
else if (len > Num.pre)
{
fill_str(numstr, '#', Num.pre);
*(numstr + Num.pre) = '.';
fill_str(numstr + 1 + Num.pre, '#', Num.post);
}
}
NUM_TOCHAR_finish;
2000-07-01 23:27:14 +02:00
PG_RETURN_TEXT_P(result);
}
/* -----------------
* FLOAT4 to_char()
* -----------------
*/
2000-07-01 23:27:14 +02:00
Datum
float4_to_char(PG_FUNCTION_ARGS)
{
2001-03-22 05:01:46 +01:00
float4 value = PG_GETARG_FLOAT4(0);
text *fmt = PG_GETARG_TEXT_P(1);
NUMDesc Num;
FormatNode *format;
2001-03-22 05:01:46 +01:00
text *result,
*result_tmp;
bool shouldFree;
2001-03-22 05:01:46 +01:00
int len = 0,
plen = 0,
sign = 0;
char *numstr,
*orgnum,
*p;
NUM_TOCHAR_prepare;
if (IS_ROMAN(&Num))
{
2000-07-01 23:27:14 +02:00
numstr = orgnum = int_to_roman((int) rint(value));
}
else
{
2000-07-01 23:27:14 +02:00
float4 val = value;
if (IS_MULTI(&Num))
{
2001-03-22 05:01:46 +01:00
float multi = pow((double) 10, (double) Num.multi);
2000-07-01 23:27:14 +02:00
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)
{
fill_str(numstr, '#', Num.pre);
*(numstr + Num.pre) = '.';
fill_str(numstr + 1 + Num.pre, '#', Num.post);
}
}
NUM_TOCHAR_finish;
2000-07-01 23:27:14 +02:00
PG_RETURN_TEXT_P(result);
}
/* -----------------
* FLOAT8 to_char()
* -----------------
*/
2000-07-01 23:27:14 +02:00
Datum
float8_to_char(PG_FUNCTION_ARGS)
{
2001-03-22 05:01:46 +01:00
float8 value = PG_GETARG_FLOAT8(0);
text *fmt = PG_GETARG_TEXT_P(1);
NUMDesc Num;
FormatNode *format;
2001-03-22 05:01:46 +01:00
text *result,
*result_tmp;
bool shouldFree;
2001-03-22 05:01:46 +01:00
int len = 0,
plen = 0,
sign = 0;
char *numstr,
*orgnum,
*p;
NUM_TOCHAR_prepare;
if (IS_ROMAN(&Num))
{
2000-07-01 23:27:14 +02:00
numstr = orgnum = int_to_roman((int) rint(value));
}
else
{
2000-07-01 23:27:14 +02:00
float8 val = value;
if (IS_MULTI(&Num))
{
2001-03-22 05:01:46 +01:00
double multi = pow((double) 10, (double) Num.multi);
2000-07-01 23:27:14 +02:00
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)
{
fill_str(numstr, '#', Num.pre);
*(numstr + Num.pre) = '.';
fill_str(numstr + 1 + Num.pre, '#', Num.post);
}
}
NUM_TOCHAR_finish;
2000-07-01 23:27:14 +02:00
PG_RETURN_TEXT_P(result);
}