📄 formatting.c
字号:
/* ----------------------------------------------------------------------- * formatting.c * * $PostgreSQL: pgsql/src/backend/utils/adt/formatting.c,v 1.101.2.2 2005/12/03 16:45:23 momjian Exp $ * * * Portions Copyright (c) 1999-2005, 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. * Module supports AD / BC / AM / PM. * * Supported types for to_char(): * * Timestamp, Numeric, int4, int8, float4, float8 * * Supported types for reverse conversion: * * Timestamp - to_timestamp() * Date - to_date() * Numeric - to_number() * * * Karel Zak * * TODO * - better number building (formatting) / parsing, now it isn't * ideal code * - use Assert() * - add support for abstime * - add support for roman number to standard number conversion * - add support for number spelling * - add support for string to string formatting (we must be better * than Oracle :-), * to_char('Hello', 'X X X X X') -> 'H e l l o' * * ----------------------------------------------------------------------- *//* ---------- * UnComment me for DEBUG * ---------- *//***#define DEBUG_TO_FROM_CHAR#define DEBUG_elog_output DEBUG3***/#include "postgres.h"#include <ctype.h>#include <unistd.h>#include <math.h>#include <float.h>#include "utils/builtins.h"#include "utils/date.h"#include "utils/datetime.h"#include "utils/formatting.h"#include "utils/int8.h"#include "utils/numeric.h"#include "utils/pg_locale.h"/* ---------- * Routines type * ---------- */#define DCH_TYPE 1 /* DATE-TIME version */#define NUM_TYPE 2 /* NUMBER version *//* ---------- * KeyWord Index (ascii from position 32 (' ') to 126 (~)) * ---------- */#define KeyWord_INDEX_SIZE ('~' - ' ')#define KeyWord_INDEX_FILTER(_c) ((_c) <= ' ' || (_c) >= '~' ? 0 : 1)/* ---------- * Maximal length of one node * ---------- */#define DCH_MAX_ITEM_SIZ 9 /* max julian day */#define NUM_MAX_ITEM_SIZ 8 /* roman number (RN has 15 chars) *//* ---------- * More is in float.c * ---------- */#define MAXFLOATWIDTH 64#define MAXDOUBLEWIDTH 128/* ---------- * External (defined in PgSQL datetime.c (timestamp utils)) * ---------- */extern char *months[], /* month abbreviation */ *days[]; /* full days *//* ---------- * Format parser structs * ---------- */typedef struct{ char *name; /* suffix string */ int len, /* suffix length */ id, /* used in node->suffix */ type; /* prefix / postfix */} KeySuffix;typedef struct FormatNode FormatNode;typedef struct{ const char *name; /* keyword */ int len; /* keyword length */ int (*action) (int arg, char *inout, /* action for keyword */ int suf, bool is_to_char, bool is_interval, FormatNode *node, void *data); int id; /* keyword id */ bool isitdigit; /* is expected output/input digit */} KeyWord;struct FormatNode{ int type; /* node type */ const KeyWord *key; /* if node type is KEYWORD */ int character, /* if node type is CHAR */ suffix; /* keyword suffix */};#define NODE_TYPE_END 1#define NODE_TYPE_ACTION 2#define NODE_TYPE_CHAR 3#define SUFFTYPE_PREFIX 1#define SUFFTYPE_POSTFIX 2/* ---------- * Full months * ---------- */static char *months_full[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", NULL};/* ---------- * AC / DC * ---------- *//* * There is no 0 AD. Years go from 1 BC to 1 AD, so we make it * positive and map year == -1 to year zero, and shift all negative * years up one. For interval years, we just return the year. */#define ADJUST_YEAR(year, is_interval) ((is_interval) ? (year) : ((year) <= 0 ? -((year) - 1) : (year)))#define BC_STR_ORIG " BC"#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};static char *rm_months_lower[] ={"xii", "xi", "x", "ix", "viii", "vii", "vi", "v", "iv", "iii", "ii", "i", NULL};/* ---------- * Roman numbers * ---------- */static char *rm1[] = {"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", NULL};static char *rm10[] = {"X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC", NULL};static char *rm100[] = {"C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM", NULL};/* ---------- * Ordinal postfixes * ---------- */static char *numTH[] = {"ST", "ND", "RD", "TH", NULL};static char *numth[] = {"st", "nd", "rd", "th", NULL};/* ---------- * Flags & Options: * ---------- */#define ONE_UPPER 1 /* Name */#define ALL_UPPER 2 /* NAME */#define ALL_LOWER 3 /* name */#define FULL_SIZ 0#define MAX_MON_LEN 3#define MAX_DY_LEN 3#define TH_UPPER 1#define TH_LOWER 2/* ---------- * Flags for DCH version * ---------- */static bool DCH_global_fx = false;/* ---------- * Number description struct * ---------- */typedef struct{ int pre, /* (count) numbers before decimal */ post, /* (count) numbers after decimal */ lsign, /* want locales sign */ flag, /* number parameters */ pre_lsign_num, /* tmp value for lsign */ multi, /* multiplier for 'V' */ zero_start, /* position of first zero */ zero_end, /* position of last zero */ need_locale; /* needs it locale */} NUMDesc;/* ---------- * Flags for NUMBER version * ---------- */#define NUM_F_DECIMAL (1 << 1)#define NUM_F_LDECIMAL (1 << 2)#define NUM_F_ZERO (1 << 3)#define NUM_F_BLANK (1 << 4)#define NUM_F_FILLMODE (1 << 5)#define NUM_F_LSIGN (1 << 6)#define NUM_F_BRACKET (1 << 7)#define NUM_F_MINUS (1 << 8)#define NUM_F_PLUS (1 << 9)#define NUM_F_ROMAN (1 << 10)#define NUM_F_MULTI (1 << 11)#define NUM_F_PLUS_POST (1 << 12)#define NUM_F_MINUS_POST (1 << 13)#define NUM_LSIGN_PRE (-1)#define NUM_LSIGN_POST 1#define NUM_LSIGN_NONE 0/* ---------- * Tests * ---------- */#define IS_DECIMAL(_f) ((_f)->flag & NUM_F_DECIMAL)#define IS_LDECIMAL(_f) ((_f)->flag & NUM_F_LDECIMAL)#define IS_ZERO(_f) ((_f)->flag & NUM_F_ZERO)#define IS_BLANK(_f) ((_f)->flag & NUM_F_BLANK)#define IS_FILLMODE(_f) ((_f)->flag & NUM_F_FILLMODE)#define IS_BRACKET(_f) ((_f)->flag & NUM_F_BRACKET)#define IS_MINUS(_f) ((_f)->flag & NUM_F_MINUS)#define IS_LSIGN(_f) ((_f)->flag & NUM_F_LSIGN)#define IS_PLUS(_f) ((_f)->flag & NUM_F_PLUS)#define IS_ROMAN(_f) ((_f)->flag & NUM_F_ROMAN)#define IS_MULTI(_f) ((_f)->flag & NUM_F_MULTI)/* ---------- * Format picture cache * (cache size: * Number part = NUM_CACHE_SIZE * NUM_CACHE_FIELDS * Date-time part = DCH_CACHE_SIZE * DCH_CACHE_FIELDS * ) * ---------- */#define NUM_CACHE_SIZE 64#define NUM_CACHE_FIELDS 16#define DCH_CACHE_SIZE 128#define DCH_CACHE_FIELDS 16typedef struct{ FormatNode format[DCH_CACHE_SIZE + 1]; char str[DCH_CACHE_SIZE + 1]; int age;} DCHCacheEntry;typedef struct{ FormatNode format[NUM_CACHE_SIZE + 1]; char str[NUM_CACHE_SIZE + 1]; int age; NUMDesc Num;} NUMCacheEntry;/* global cache for --- date/time part */static DCHCacheEntry DCHCache[DCH_CACHE_FIELDS + 1];static int n_DCHCache = 0; /* number of entries */static int DCHCounter = 0;/* global cache for --- number part */static NUMCacheEntry NUMCache[NUM_CACHE_FIELDS + 1];static NUMCacheEntry *last_NUMCacheEntry;static int n_NUMCache = 0; /* number of entries */static int NUMCounter = 0;#define MAX_INT32 (2147483600)/* ---------- * For char->date/time conversion * ---------- */typedef struct{ int hh, am, pm, mi, ss, ssss, d, dd, ddd, mm, ms, year, bc, iw, ww, w, cc, q, j, us, yysz; /* is it YY or YYYY ? */} 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\nyysz: %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, (_X)->yysz);#define DEBUG_TM(_X) \ elog(DEBUG_elog_output, "TM:\nsec %d\nyear %d\nmin %d\nwday %d\nhour %d\nyday %d\nmday %d\nnisdst %d\nmon %d\n",\ (_X)->tm_sec, (_X)->tm_year,\ (_X)->tm_min, (_X)->tm_wday, (_X)->tm_hour, (_X)->tm_yday,\ (_X)->tm_mday, (_X)->tm_isdst, (_X)->tm_mon)#else#define DEBUG_TMFC(_X)#define DEBUG_TM(_X)#endif/* ---------- * Datetime to char conversion * ---------- */typedef struct TmToChar{ struct pg_tm tm; /* classic 'tm' struct */ fsec_t fsec; /* fractional seconds */ char *tzn; /* timezone */} TmToChar;#define tmtcTm(_X) (&(_X)->tm)#define tmtcTzn(_X) ((_X)->tzn)#define tmtcFsec(_X) ((_X)->fsec)#define ZERO_tm(_X) \do { \ (_X)->tm_sec = (_X)->tm_year = (_X)->tm_min = (_X)->tm_wday = \ (_X)->tm_hour = (_X)->tm_yday = (_X)->tm_isdst = 0; \ (_X)->tm_mday = (_X)->tm_mon = 1; \} while(0)#define ZERO_tmtc(_X) \do { \ ZERO_tm( tmtcTm(_X) ); \ tmtcFsec(_X) = 0; \ tmtcTzn(_X) = NULL; \} while(0)/* * to_char(time) appears to to_char() as an interval, so this check * is really for interval and time data types. */#define INVALID_FOR_INTERVAL \do { \ if (is_interval) \ ereport(ERROR, \ (errcode(ERRCODE_INVALID_DATETIME_FORMAT), \ errmsg("invalid format specification for an interval value"), \ errhint("Intervals are not tied to specific calendar dates."))); \} while(0)/***************************************************************************** * KeyWords definition & action *****************************************************************************/static int dch_global(int arg, char *inout, int suf, bool is_to_char, bool is_interval, FormatNode *node, void *data);static int dch_time(int arg, char *inout, int suf, bool is_to_char, bool is_interval, FormatNode *node, void *data);static int dch_date(int arg, char *inout, int suf, bool is_to_char, bool is_interval, 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_IYYY, DCH_IYY, DCH_IY, DCH_I, DCH_J, DCH_MI, DCH_MM, DCH_MONTH, DCH_MON, DCH_MS, DCH_Month, DCH_Mon, DCH_P_M, DCH_PM, DCH_Q, DCH_RM, DCH_SSSS, DCH_SS, DCH_TZ, DCH_US, DCH_WW, DCH_W, DCH_Y_YYY, DCH_YYYY, DCH_YYY, DCH_YY, DCH_Y, DCH_a_d, DCH_a_m, DCH_ad, DCH_am, DCH_b_c, DCH_bc, DCH_cc, DCH_day, DCH_ddd, DCH_dd, DCH_dy, DCH_d, DCH_fx,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -