Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : * formatting.c
3 : : *
4 : : * src/backend/utils/adt/formatting.c
5 : : *
6 : : *
7 : : * Portions Copyright (c) 1999-2025, PostgreSQL Global Development Group
8 : : *
9 : : *
10 : : * TO_CHAR(); TO_TIMESTAMP(); TO_DATE(); TO_NUMBER();
11 : : *
12 : : * The PostgreSQL routines for a timestamp/int/float/numeric formatting,
13 : : * inspired by the Oracle TO_CHAR() / TO_DATE() / TO_NUMBER() routines.
14 : : *
15 : : *
16 : : * Cache & Memory:
17 : : * Routines use (itself) internal cache for format pictures.
18 : : *
19 : : * The cache uses a static buffer and is persistent across transactions. If
20 : : * the format-picture is bigger than the cache buffer, the parser is called
21 : : * always.
22 : : *
23 : : * NOTE for Number version:
24 : : * All in this version is implemented as keywords ( => not used
25 : : * suffixes), because a format picture is for *one* item (number)
26 : : * only. It not is as a timestamp version, where each keyword (can)
27 : : * has suffix.
28 : : *
29 : : * NOTE for Timestamp routines:
30 : : * In this module the POSIX 'struct tm' type is *not* used, but rather
31 : : * PgSQL type, which has tm_mon based on one (*non* zero) and
32 : : * year *not* based on 1900, but is used full year number.
33 : : * Module supports AD / BC / AM / PM.
34 : : *
35 : : * Supported types for to_char():
36 : : *
37 : : * Timestamp, Numeric, int4, int8, float4, float8
38 : : *
39 : : * Supported types for reverse conversion:
40 : : *
41 : : * Timestamp - to_timestamp()
42 : : * Date - to_date()
43 : : * Numeric - to_number()
44 : : *
45 : : *
46 : : * Karel Zak
47 : : *
48 : : * TODO
49 : : * - better number building (formatting) / parsing, now it isn't
50 : : * ideal code
51 : : * - use Assert()
52 : : * - add support for number spelling
53 : : * - add support for string to string formatting (we must be better
54 : : * than Oracle :-),
55 : : * to_char('Hello', 'X X X X X') -> 'H e l l o'
56 : : *
57 : : *-------------------------------------------------------------------------
58 : : */
59 : :
60 : : #ifdef DEBUG_TO_FROM_CHAR
61 : : #define DEBUG_elog_output DEBUG3
62 : : #endif
63 : :
64 : : #include "postgres.h"
65 : :
66 : : #include <ctype.h>
67 : : #include <unistd.h>
68 : : #include <math.h>
69 : : #include <float.h>
70 : : #include <limits.h>
71 : : #include <wctype.h>
72 : :
73 : : #ifdef USE_ICU
74 : : #include <unicode/ustring.h>
75 : : #endif
76 : :
77 : : #include "catalog/pg_collation.h"
78 : : #include "catalog/pg_type.h"
79 : : #include "common/int.h"
80 : : #include "common/unicode_case.h"
81 : : #include "common/unicode_category.h"
82 : : #include "mb/pg_wchar.h"
83 : : #include "nodes/miscnodes.h"
84 : : #include "parser/scansup.h"
85 : : #include "utils/builtins.h"
86 : : #include "utils/date.h"
87 : : #include "utils/datetime.h"
88 : : #include "utils/formatting.h"
89 : : #include "utils/memutils.h"
90 : : #include "utils/numeric.h"
91 : : #include "utils/pg_locale.h"
92 : : #include "varatt.h"
93 : :
94 : :
95 : : /*
96 : : * Routines flags
97 : : */
98 : : #define DCH_FLAG 0x1 /* DATE-TIME flag */
99 : : #define NUM_FLAG 0x2 /* NUMBER flag */
100 : : #define STD_FLAG 0x4 /* STANDARD flag */
101 : :
102 : : /*
103 : : * KeyWord Index (ascii from position 32 (' ') to 126 (~))
104 : : */
105 : : #define KeyWord_INDEX_SIZE ('~' - ' ')
106 : : #define KeyWord_INDEX_FILTER(_c) ((_c) <= ' ' || (_c) >= '~' ? 0 : 1)
107 : :
108 : : /*
109 : : * Maximal length of one node
110 : : */
111 : : #define DCH_MAX_ITEM_SIZ 12 /* max localized day name */
112 : : #define NUM_MAX_ITEM_SIZ 8 /* roman number (RN has 15 chars) */
113 : :
114 : :
115 : : /*
116 : : * Format parser structs
117 : : */
118 : :
119 : : enum KeySuffixType
120 : : {
121 : : SUFFTYPE_PREFIX = 1,
122 : : SUFFTYPE_POSTFIX = 2,
123 : : };
124 : :
125 : : typedef struct
126 : : {
127 : : const char *name; /* suffix string */
128 : : size_t len; /* suffix length */
129 : : int id; /* used in node->suffix */
130 : : enum KeySuffixType type; /* prefix / postfix */
131 : : } KeySuffix;
132 : :
133 : : /*
134 : : * FromCharDateMode
135 : : *
136 : : * This value is used to nominate one of several distinct (and mutually
137 : : * exclusive) date conventions that a keyword can belong to.
138 : : */
139 : : typedef enum
140 : : {
141 : : FROM_CHAR_DATE_NONE = 0, /* Value does not affect date mode. */
142 : : FROM_CHAR_DATE_GREGORIAN, /* Gregorian (day, month, year) style date */
143 : : FROM_CHAR_DATE_ISOWEEK, /* ISO 8601 week date */
144 : : } FromCharDateMode;
145 : :
146 : : typedef struct
147 : : {
148 : : const char *name;
149 : : size_t len;
150 : : int id;
151 : : bool is_digit;
152 : : FromCharDateMode date_mode;
153 : : } KeyWord;
154 : :
155 : : enum FormatNodeType
156 : : {
157 : : NODE_TYPE_END = 1,
158 : : NODE_TYPE_ACTION = 2,
159 : : NODE_TYPE_CHAR = 3,
160 : : NODE_TYPE_SEPARATOR = 4,
161 : : NODE_TYPE_SPACE = 5,
162 : : };
163 : :
164 : : typedef struct
165 : : {
166 : : enum FormatNodeType type;
167 : : char character[MAX_MULTIBYTE_CHAR_LEN + 1]; /* if type is CHAR */
168 : : uint8 suffix; /* keyword prefix/suffix code, if any
169 : : * (DCH_SUFFIX_*) */
170 : : const KeyWord *key; /* if type is ACTION */
171 : : } FormatNode;
172 : :
173 : :
174 : : /*
175 : : * Full months
176 : : */
177 : : static const char *const months_full[] = {
178 : : "January", "February", "March", "April", "May", "June", "July",
179 : : "August", "September", "October", "November", "December", NULL
180 : : };
181 : :
182 : : static const char *const days_short[] = {
183 : : "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL
184 : : };
185 : :
186 : : /*
187 : : * AD / BC
188 : : *
189 : : * There is no 0 AD. Years go from 1 BC to 1 AD, so we make it
190 : : * positive and map year == -1 to year zero, and shift all negative
191 : : * years up one. For interval years, we just return the year.
192 : : */
193 : : #define ADJUST_YEAR(year, is_interval) ((is_interval) ? (year) : ((year) <= 0 ? -((year) - 1) : (year)))
194 : :
195 : : #define A_D_STR "A.D."
196 : : #define a_d_STR "a.d."
197 : : #define AD_STR "AD"
198 : : #define ad_STR "ad"
199 : :
200 : : #define B_C_STR "B.C."
201 : : #define b_c_STR "b.c."
202 : : #define BC_STR "BC"
203 : : #define bc_STR "bc"
204 : :
205 : : /*
206 : : * AD / BC strings for seq_search.
207 : : *
208 : : * These are given in two variants, a long form with periods and a standard
209 : : * form without.
210 : : *
211 : : * The array is laid out such that matches for AD have an even index, and
212 : : * matches for BC have an odd index. So the boolean value for BC is given by
213 : : * taking the array index of the match, modulo 2.
214 : : */
215 : : static const char *const adbc_strings[] = {ad_STR, bc_STR, AD_STR, BC_STR, NULL};
216 : : static const char *const adbc_strings_long[] = {a_d_STR, b_c_STR, A_D_STR, B_C_STR, NULL};
217 : :
218 : : /*
219 : : * AM / PM
220 : : */
221 : : #define A_M_STR "A.M."
222 : : #define a_m_STR "a.m."
223 : : #define AM_STR "AM"
224 : : #define am_STR "am"
225 : :
226 : : #define P_M_STR "P.M."
227 : : #define p_m_STR "p.m."
228 : : #define PM_STR "PM"
229 : : #define pm_STR "pm"
230 : :
231 : : /*
232 : : * AM / PM strings for seq_search.
233 : : *
234 : : * These are given in two variants, a long form with periods and a standard
235 : : * form without.
236 : : *
237 : : * The array is laid out such that matches for AM have an even index, and
238 : : * matches for PM have an odd index. So the boolean value for PM is given by
239 : : * taking the array index of the match, modulo 2.
240 : : */
241 : : static const char *const ampm_strings[] = {am_STR, pm_STR, AM_STR, PM_STR, NULL};
242 : : static const char *const ampm_strings_long[] = {a_m_STR, p_m_STR, A_M_STR, P_M_STR, NULL};
243 : :
244 : : /*
245 : : * Months in roman-numeral
246 : : * (Must be in reverse order for seq_search (in FROM_CHAR), because
247 : : * 'VIII' must have higher precedence than 'V')
248 : : */
249 : : static const char *const rm_months_upper[] =
250 : : {"XII", "XI", "X", "IX", "VIII", "VII", "VI", "V", "IV", "III", "II", "I", NULL};
251 : :
252 : : static const char *const rm_months_lower[] =
253 : : {"xii", "xi", "x", "ix", "viii", "vii", "vi", "v", "iv", "iii", "ii", "i", NULL};
254 : :
255 : : /*
256 : : * Roman numerals
257 : : */
258 : : static const char *const rm1[] = {"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", NULL};
259 : : static const char *const rm10[] = {"X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC", NULL};
260 : : static const char *const rm100[] = {"C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM", NULL};
261 : :
262 : : /*
263 : : * MACRO: Check if the current and next characters form a valid subtraction
264 : : * combination for roman numerals.
265 : : */
266 : : #define IS_VALID_SUB_COMB(curr, next) \
267 : : (((curr) == 'I' && ((next) == 'V' || (next) == 'X')) || \
268 : : ((curr) == 'X' && ((next) == 'L' || (next) == 'C')) || \
269 : : ((curr) == 'C' && ((next) == 'D' || (next) == 'M')))
270 : :
271 : : /*
272 : : * MACRO: Roman numeral value, or 0 if character isn't a roman numeral.
273 : : */
274 : : #define ROMAN_VAL(r) \
275 : : ((r) == 'I' ? 1 : \
276 : : (r) == 'V' ? 5 : \
277 : : (r) == 'X' ? 10 : \
278 : : (r) == 'L' ? 50 : \
279 : : (r) == 'C' ? 100 : \
280 : : (r) == 'D' ? 500 : \
281 : : (r) == 'M' ? 1000 : 0)
282 : :
283 : : /*
284 : : * 'MMMDCCCLXXXVIII' (3888) is the longest valid roman numeral (15 characters).
285 : : */
286 : : #define MAX_ROMAN_LEN 15
287 : :
288 : : /*
289 : : * Ordinal postfixes
290 : : */
291 : : static const char *const numTH[] = {"ST", "ND", "RD", "TH", NULL};
292 : : static const char *const numth[] = {"st", "nd", "rd", "th", NULL};
293 : :
294 : : /*
295 : : * Flags & Options:
296 : : */
297 : : enum TH_Case
298 : : {
299 : : TH_UPPER = 1,
300 : : TH_LOWER = 2,
301 : : };
302 : :
303 : : enum NUMDesc_lsign
304 : : {
305 : : NUM_LSIGN_PRE = -1,
306 : : NUM_LSIGN_POST = 1,
307 : : NUM_LSIGN_NONE = 0,
308 : : };
309 : :
310 : : /*
311 : : * Number description struct
312 : : */
313 : : typedef struct
314 : : {
315 : : int pre; /* (count) numbers before decimal */
316 : : int post; /* (count) numbers after decimal */
317 : : enum NUMDesc_lsign lsign; /* want locales sign */
318 : : int flag; /* number parameters (NUM_F_*) */
319 : : int pre_lsign_num; /* tmp value for lsign */
320 : : int multi; /* multiplier for 'V' */
321 : : int zero_start; /* position of first zero */
322 : : int zero_end; /* position of last zero */
323 : : bool need_locale; /* needs it locale */
324 : : } NUMDesc;
325 : :
326 : : /*
327 : : * Flags for NUMBER version
328 : : */
329 : : #define NUM_F_DECIMAL (1 << 1)
330 : : #define NUM_F_LDECIMAL (1 << 2)
331 : : #define NUM_F_ZERO (1 << 3)
332 : : #define NUM_F_BLANK (1 << 4)
333 : : #define NUM_F_FILLMODE (1 << 5)
334 : : #define NUM_F_LSIGN (1 << 6)
335 : : #define NUM_F_BRACKET (1 << 7)
336 : : #define NUM_F_MINUS (1 << 8)
337 : : #define NUM_F_PLUS (1 << 9)
338 : : #define NUM_F_ROMAN (1 << 10)
339 : : #define NUM_F_MULTI (1 << 11)
340 : : #define NUM_F_PLUS_POST (1 << 12)
341 : : #define NUM_F_MINUS_POST (1 << 13)
342 : : #define NUM_F_EEEE (1 << 14)
343 : :
344 : : /*
345 : : * Tests
346 : : */
347 : : #define IS_DECIMAL(_f) ((_f)->flag & NUM_F_DECIMAL)
348 : : #define IS_LDECIMAL(_f) ((_f)->flag & NUM_F_LDECIMAL)
349 : : #define IS_ZERO(_f) ((_f)->flag & NUM_F_ZERO)
350 : : #define IS_BLANK(_f) ((_f)->flag & NUM_F_BLANK)
351 : : #define IS_FILLMODE(_f) ((_f)->flag & NUM_F_FILLMODE)
352 : : #define IS_BRACKET(_f) ((_f)->flag & NUM_F_BRACKET)
353 : : #define IS_MINUS(_f) ((_f)->flag & NUM_F_MINUS)
354 : : #define IS_LSIGN(_f) ((_f)->flag & NUM_F_LSIGN)
355 : : #define IS_PLUS(_f) ((_f)->flag & NUM_F_PLUS)
356 : : #define IS_ROMAN(_f) ((_f)->flag & NUM_F_ROMAN)
357 : : #define IS_MULTI(_f) ((_f)->flag & NUM_F_MULTI)
358 : : #define IS_EEEE(_f) ((_f)->flag & NUM_F_EEEE)
359 : :
360 : : /*
361 : : * Format picture cache
362 : : *
363 : : * We will cache datetime format pictures up to DCH_CACHE_SIZE bytes long;
364 : : * likewise number format pictures up to NUM_CACHE_SIZE bytes long.
365 : : *
366 : : * For simplicity, the cache entries are fixed-size, so they allow for the
367 : : * worst case of a FormatNode for each byte in the picture string.
368 : : *
369 : : * The CACHE_SIZE constants are computed to make sizeof(DCHCacheEntry) and
370 : : * sizeof(NUMCacheEntry) be powers of 2, or just less than that, so that
371 : : * we don't waste too much space by palloc'ing them individually. Be sure
372 : : * to adjust those macros if you add fields to those structs.
373 : : *
374 : : * The max number of entries in each cache is DCH_CACHE_ENTRIES
375 : : * resp. NUM_CACHE_ENTRIES.
376 : : */
377 : : #define DCH_CACHE_OVERHEAD \
378 : : MAXALIGN(sizeof(bool) + sizeof(int))
379 : : #define NUM_CACHE_OVERHEAD \
380 : : MAXALIGN(sizeof(bool) + sizeof(int) + sizeof(NUMDesc))
381 : :
382 : : #define DCH_CACHE_SIZE \
383 : : ((2048 - DCH_CACHE_OVERHEAD) / (sizeof(FormatNode) + sizeof(char)) - 1)
384 : : #define NUM_CACHE_SIZE \
385 : : ((1024 - NUM_CACHE_OVERHEAD) / (sizeof(FormatNode) + sizeof(char)) - 1)
386 : :
387 : : #define DCH_CACHE_ENTRIES 20
388 : : #define NUM_CACHE_ENTRIES 20
389 : :
390 : : typedef struct
391 : : {
392 : : FormatNode format[DCH_CACHE_SIZE + 1];
393 : : char str[DCH_CACHE_SIZE + 1];
394 : : bool std;
395 : : bool valid;
396 : : int age;
397 : : } DCHCacheEntry;
398 : :
399 : : typedef struct
400 : : {
401 : : FormatNode format[NUM_CACHE_SIZE + 1];
402 : : char str[NUM_CACHE_SIZE + 1];
403 : : bool valid;
404 : : int age;
405 : : NUMDesc Num;
406 : : } NUMCacheEntry;
407 : :
408 : : /* global cache for date/time format pictures */
409 : : static DCHCacheEntry *DCHCache[DCH_CACHE_ENTRIES];
410 : : static int n_DCHCache = 0; /* current number of entries */
411 : : static int DCHCounter = 0; /* aging-event counter */
412 : :
413 : : /* global cache for number format pictures */
414 : : static NUMCacheEntry *NUMCache[NUM_CACHE_ENTRIES];
415 : : static int n_NUMCache = 0; /* current number of entries */
416 : : static int NUMCounter = 0; /* aging-event counter */
417 : :
418 : : /*
419 : : * For char->date/time conversion
420 : : */
421 : : typedef struct
422 : : {
423 : : FromCharDateMode mode;
424 : : int hh;
425 : : int pm;
426 : : int mi;
427 : : int ss;
428 : : int ssss;
429 : : int d; /* stored as 1-7, Sunday = 1, 0 means missing */
430 : : int dd;
431 : : int ddd;
432 : : int mm;
433 : : int ms;
434 : : int year;
435 : : int bc;
436 : : int ww;
437 : : int w;
438 : : int cc;
439 : : int j;
440 : : int us;
441 : : int yysz; /* is it YY or YYYY ? */
442 : : bool clock_12_hour; /* 12 or 24 hour clock? */
443 : : int tzsign; /* +1, -1, or 0 if no TZH/TZM fields */
444 : : int tzh;
445 : : int tzm;
446 : : int ff; /* fractional precision */
447 : : bool has_tz; /* was there a TZ field? */
448 : : int gmtoffset; /* GMT offset of fixed-offset zone abbrev */
449 : : pg_tz *tzp; /* pg_tz for dynamic abbrev */
450 : : const char *abbrev; /* dynamic abbrev */
451 : : } TmFromChar;
452 : :
453 : : struct fmt_tz /* do_to_timestamp's timezone info output */
454 : : {
455 : : bool has_tz; /* was there any TZ/TZH/TZM field? */
456 : : int gmtoffset; /* GMT offset in seconds */
457 : : };
458 : :
459 : : /*
460 : : * Debug
461 : : */
462 : : #ifdef DEBUG_TO_FROM_CHAR
463 : : #define DEBUG_TMFC(_X) \
464 : : elog(DEBUG_elog_output, "TMFC:\nmode %d\nhh %d\npm %d\nmi %d\nss %d\nssss %d\nd %d\ndd %d\nddd %d\nmm %d\nms: %d\nyear %d\nbc %d\nww %d\nw %d\ncc %d\nj %d\nus: %d\nyysz: %d\nclock: %d", \
465 : : (_X)->mode, (_X)->hh, (_X)->pm, (_X)->mi, (_X)->ss, (_X)->ssss, \
466 : : (_X)->d, (_X)->dd, (_X)->ddd, (_X)->mm, (_X)->ms, (_X)->year, \
467 : : (_X)->bc, (_X)->ww, (_X)->w, (_X)->cc, (_X)->j, (_X)->us, \
468 : : (_X)->yysz, (_X)->clock_12_hour)
469 : : #define DEBUG_TM(_X) \
470 : : elog(DEBUG_elog_output, "TM:\nsec %d\nyear %d\nmin %d\nwday %d\nhour %d\nyday %d\nmday %d\nnisdst %d\nmon %d\n",\
471 : : (_X)->tm_sec, (_X)->tm_year,\
472 : : (_X)->tm_min, (_X)->tm_wday, (_X)->tm_hour, (_X)->tm_yday,\
473 : : (_X)->tm_mday, (_X)->tm_isdst, (_X)->tm_mon)
474 : : #else
475 : : #define DEBUG_TMFC(_X)
476 : : #define DEBUG_TM(_X)
477 : : #endif
478 : :
479 : : /*
480 : : * Datetime to char conversion
481 : : *
482 : : * To support intervals as well as timestamps, we use a custom "tm" struct
483 : : * that is almost like struct pg_tm, but has a 64-bit tm_hour field.
484 : : * We omit the tm_isdst and tm_zone fields, which are not used here.
485 : : */
486 : : struct fmt_tm
487 : : {
488 : : int tm_sec;
489 : : int tm_min;
490 : : int64 tm_hour;
491 : : int tm_mday;
492 : : int tm_mon;
493 : : int tm_year;
494 : : int tm_wday;
495 : : int tm_yday;
496 : : long int tm_gmtoff;
497 : : };
498 : :
499 : : typedef struct TmToChar
500 : : {
501 : : struct fmt_tm tm; /* almost the classic 'tm' struct */
502 : : fsec_t fsec; /* fractional seconds */
503 : : const char *tzn; /* timezone */
504 : : } TmToChar;
505 : :
506 : : #define tmtcTm(_X) (&(_X)->tm)
507 : : #define tmtcTzn(_X) ((_X)->tzn)
508 : : #define tmtcFsec(_X) ((_X)->fsec)
509 : :
510 : : /* Note: this is used to copy pg_tm to fmt_tm, so not quite a bitwise copy */
511 : : #define COPY_tm(_DST, _SRC) \
512 : : do { \
513 : : (_DST)->tm_sec = (_SRC)->tm_sec; \
514 : : (_DST)->tm_min = (_SRC)->tm_min; \
515 : : (_DST)->tm_hour = (_SRC)->tm_hour; \
516 : : (_DST)->tm_mday = (_SRC)->tm_mday; \
517 : : (_DST)->tm_mon = (_SRC)->tm_mon; \
518 : : (_DST)->tm_year = (_SRC)->tm_year; \
519 : : (_DST)->tm_wday = (_SRC)->tm_wday; \
520 : : (_DST)->tm_yday = (_SRC)->tm_yday; \
521 : : (_DST)->tm_gmtoff = (_SRC)->tm_gmtoff; \
522 : : } while(0)
523 : :
524 : : /* Caution: this is used to zero both pg_tm and fmt_tm structs */
525 : : #define ZERO_tm(_X) \
526 : : do { \
527 : : memset(_X, 0, sizeof(*(_X))); \
528 : : (_X)->tm_mday = (_X)->tm_mon = 1; \
529 : : } while(0)
530 : :
531 : : #define ZERO_tmtc(_X) \
532 : : do { \
533 : : ZERO_tm( tmtcTm(_X) ); \
534 : : tmtcFsec(_X) = 0; \
535 : : tmtcTzn(_X) = NULL; \
536 : : } while(0)
537 : :
538 : : /*
539 : : * to_char(time) appears to to_char() as an interval, so this check
540 : : * is really for interval and time data types.
541 : : */
542 : : #define INVALID_FOR_INTERVAL \
543 : : do { \
544 : : if (is_interval) \
545 : : ereport(ERROR, \
546 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT), \
547 : : errmsg("invalid format specification for an interval value"), \
548 : : errhint("Intervals are not tied to specific calendar dates."))); \
549 : : } while(0)
550 : :
551 : : /*****************************************************************************
552 : : * KeyWord definitions
553 : : *****************************************************************************/
554 : :
555 : : /*
556 : : * Suffixes (FormatNode.suffix is an OR of these codes)
557 : : */
558 : : #define DCH_SUFFIX_FM 0x01
559 : : #define DCH_SUFFIX_TH 0x02
560 : : #define DCH_SUFFIX_th 0x04
561 : : #define DCH_SUFFIX_SP 0x08
562 : : #define DCH_SUFFIX_TM 0x10
563 : :
564 : : /*
565 : : * Suffix tests
566 : : */
567 : : static inline bool
47 peter@eisentraut.org 568 :GNC 147594 : IS_SUFFIX_TH(uint8 _s)
569 : : {
570 : 147594 : return (_s & DCH_SUFFIX_TH);
571 : : }
572 : :
573 : : static inline bool
574 : 147213 : IS_SUFFIX_th(uint8 _s)
575 : : {
576 : 147213 : return (_s & DCH_SUFFIX_th);
577 : : }
578 : :
579 : : static inline bool
580 : 147594 : IS_SUFFIX_THth(uint8 _s)
581 : : {
582 [ + + + + ]: 147594 : return IS_SUFFIX_TH(_s) || IS_SUFFIX_th(_s);
583 : : }
584 : :
585 : : static inline enum TH_Case
586 : 1143 : SUFFIX_TH_TYPE(uint8 _s)
587 : : {
588 [ + + ]: 1143 : return _s & DCH_SUFFIX_TH ? TH_UPPER : TH_LOWER;
589 : : }
590 : :
591 : : /* Oracle toggles FM behavior, we don't; see docs. */
592 : : static inline bool
593 : 87450 : IS_SUFFIX_FM(uint8 _s)
594 : : {
595 : 87450 : return (_s & DCH_SUFFIX_FM);
596 : : }
597 : :
598 : : static inline bool
599 : 6981 : IS_SUFFIX_TM(uint8 _s)
600 : : {
601 : 6981 : return (_s & DCH_SUFFIX_TM);
602 : : }
603 : :
604 : : /*
605 : : * Suffixes definition for DATE-TIME TO/FROM CHAR
606 : : */
607 : : #define TM_SUFFIX_LEN 2
608 : :
609 : : static const KeySuffix DCH_suff[] = {
610 : : {"FM", 2, DCH_SUFFIX_FM, SUFFTYPE_PREFIX},
611 : : {"fm", 2, DCH_SUFFIX_FM, SUFFTYPE_PREFIX},
612 : : {"TM", TM_SUFFIX_LEN, DCH_SUFFIX_TM, SUFFTYPE_PREFIX},
613 : : {"tm", 2, DCH_SUFFIX_TM, SUFFTYPE_PREFIX},
614 : : {"TH", 2, DCH_SUFFIX_TH, SUFFTYPE_POSTFIX},
615 : : {"th", 2, DCH_SUFFIX_th, SUFFTYPE_POSTFIX},
616 : : {"SP", 2, DCH_SUFFIX_SP, SUFFTYPE_POSTFIX},
617 : : /* last */
618 : : {NULL, 0, 0, 0}
619 : : };
620 : :
621 : :
622 : : /*
623 : : * Format-pictures (KeyWord).
624 : : *
625 : : * The KeyWord field; alphabetic sorted, *BUT* strings alike is sorted
626 : : * complicated -to-> easy:
627 : : *
628 : : * (example: "DDD","DD","Day","D" )
629 : : *
630 : : * (this specific sort needs the algorithm for sequential search for strings,
631 : : * which not has exact end; -> How keyword is in "HH12blabla" ? - "HH"
632 : : * or "HH12"? You must first try "HH12", because "HH" is in string, but
633 : : * it is not good.
634 : : *
635 : : * (!)
636 : : * - Position for the keyword is similar as position in the enum DCH/NUM_poz.
637 : : * (!)
638 : : *
639 : : * For fast search is used the 'int index[]', index is ascii table from position
640 : : * 32 (' ') to 126 (~), in this index is DCH_ / NUM_ enums for each ASCII
641 : : * position or -1 if char is not used in the KeyWord. Search example for
642 : : * string "MM":
643 : : * 1) see in index to index['M' - 32],
644 : : * 2) take keywords position (enum DCH_MI) from index
645 : : * 3) run sequential search in keywords[] from this position
646 : : */
647 : :
648 : : typedef enum
649 : : {
650 : : DCH_A_D,
651 : : DCH_A_M,
652 : : DCH_AD,
653 : : DCH_AM,
654 : : DCH_B_C,
655 : : DCH_BC,
656 : : DCH_CC,
657 : : DCH_DAY,
658 : : DCH_DDD,
659 : : DCH_DD,
660 : : DCH_DY,
661 : : DCH_Day,
662 : : DCH_Dy,
663 : : DCH_D,
664 : : DCH_FF1, /* FFn codes must be consecutive */
665 : : DCH_FF2,
666 : : DCH_FF3,
667 : : DCH_FF4,
668 : : DCH_FF5,
669 : : DCH_FF6,
670 : : DCH_FX, /* global suffix */
671 : : DCH_HH24,
672 : : DCH_HH12,
673 : : DCH_HH,
674 : : DCH_IDDD,
675 : : DCH_ID,
676 : : DCH_IW,
677 : : DCH_IYYY,
678 : : DCH_IYY,
679 : : DCH_IY,
680 : : DCH_I,
681 : : DCH_J,
682 : : DCH_MI,
683 : : DCH_MM,
684 : : DCH_MONTH,
685 : : DCH_MON,
686 : : DCH_MS,
687 : : DCH_Month,
688 : : DCH_Mon,
689 : : DCH_OF,
690 : : DCH_P_M,
691 : : DCH_PM,
692 : : DCH_Q,
693 : : DCH_RM,
694 : : DCH_SSSSS,
695 : : DCH_SSSS,
696 : : DCH_SS,
697 : : DCH_TZH,
698 : : DCH_TZM,
699 : : DCH_TZ,
700 : : DCH_US,
701 : : DCH_WW,
702 : : DCH_W,
703 : : DCH_Y_YYY,
704 : : DCH_YYYY,
705 : : DCH_YYY,
706 : : DCH_YY,
707 : : DCH_Y,
708 : : DCH_a_d,
709 : : DCH_a_m,
710 : : DCH_ad,
711 : : DCH_am,
712 : : DCH_b_c,
713 : : DCH_bc,
714 : : DCH_cc,
715 : : DCH_day,
716 : : DCH_ddd,
717 : : DCH_dd,
718 : : DCH_dy,
719 : : DCH_d,
720 : : DCH_ff1,
721 : : DCH_ff2,
722 : : DCH_ff3,
723 : : DCH_ff4,
724 : : DCH_ff5,
725 : : DCH_ff6,
726 : : DCH_fx,
727 : : DCH_hh24,
728 : : DCH_hh12,
729 : : DCH_hh,
730 : : DCH_iddd,
731 : : DCH_id,
732 : : DCH_iw,
733 : : DCH_iyyy,
734 : : DCH_iyy,
735 : : DCH_iy,
736 : : DCH_i,
737 : : DCH_j,
738 : : DCH_mi,
739 : : DCH_mm,
740 : : DCH_month,
741 : : DCH_mon,
742 : : DCH_ms,
743 : : DCH_of,
744 : : DCH_p_m,
745 : : DCH_pm,
746 : : DCH_q,
747 : : DCH_rm,
748 : : DCH_sssss,
749 : : DCH_ssss,
750 : : DCH_ss,
751 : : DCH_tzh,
752 : : DCH_tzm,
753 : : DCH_tz,
754 : : DCH_us,
755 : : DCH_ww,
756 : : DCH_w,
757 : : DCH_y_yyy,
758 : : DCH_yyyy,
759 : : DCH_yyy,
760 : : DCH_yy,
761 : : DCH_y,
762 : :
763 : : /* last */
764 : : _DCH_last_
765 : : } DCH_poz;
766 : :
767 : : typedef enum
768 : : {
769 : : NUM_COMMA,
770 : : NUM_DEC,
771 : : NUM_0,
772 : : NUM_9,
773 : : NUM_B,
774 : : NUM_C,
775 : : NUM_D,
776 : : NUM_E,
777 : : NUM_FM,
778 : : NUM_G,
779 : : NUM_L,
780 : : NUM_MI,
781 : : NUM_PL,
782 : : NUM_PR,
783 : : NUM_RN,
784 : : NUM_SG,
785 : : NUM_SP,
786 : : NUM_S,
787 : : NUM_TH,
788 : : NUM_V,
789 : : NUM_b,
790 : : NUM_c,
791 : : NUM_d,
792 : : NUM_e,
793 : : NUM_fm,
794 : : NUM_g,
795 : : NUM_l,
796 : : NUM_mi,
797 : : NUM_pl,
798 : : NUM_pr,
799 : : NUM_rn,
800 : : NUM_sg,
801 : : NUM_sp,
802 : : NUM_s,
803 : : NUM_th,
804 : : NUM_v,
805 : :
806 : : /* last */
807 : : _NUM_last_
808 : : } NUM_poz;
809 : :
810 : : /*
811 : : * KeyWords for DATE-TIME version
812 : : */
813 : : static const KeyWord DCH_keywords[] = {
814 : : /* name, len, id, is_digit, date_mode */
815 : : {"A.D.", 4, DCH_A_D, false, FROM_CHAR_DATE_NONE}, /* A */
816 : : {"A.M.", 4, DCH_A_M, false, FROM_CHAR_DATE_NONE},
817 : : {"AD", 2, DCH_AD, false, FROM_CHAR_DATE_NONE},
818 : : {"AM", 2, DCH_AM, false, FROM_CHAR_DATE_NONE},
819 : : {"B.C.", 4, DCH_B_C, false, FROM_CHAR_DATE_NONE}, /* B */
820 : : {"BC", 2, DCH_BC, false, FROM_CHAR_DATE_NONE},
821 : : {"CC", 2, DCH_CC, true, FROM_CHAR_DATE_NONE}, /* C */
822 : : {"DAY", 3, DCH_DAY, false, FROM_CHAR_DATE_NONE}, /* D */
823 : : {"DDD", 3, DCH_DDD, true, FROM_CHAR_DATE_GREGORIAN},
824 : : {"DD", 2, DCH_DD, true, FROM_CHAR_DATE_GREGORIAN},
825 : : {"DY", 2, DCH_DY, false, FROM_CHAR_DATE_NONE},
826 : : {"Day", 3, DCH_Day, false, FROM_CHAR_DATE_NONE},
827 : : {"Dy", 2, DCH_Dy, false, FROM_CHAR_DATE_NONE},
828 : : {"D", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
829 : : {"FF1", 3, DCH_FF1, true, FROM_CHAR_DATE_NONE}, /* F */
830 : : {"FF2", 3, DCH_FF2, true, FROM_CHAR_DATE_NONE},
831 : : {"FF3", 3, DCH_FF3, true, FROM_CHAR_DATE_NONE},
832 : : {"FF4", 3, DCH_FF4, true, FROM_CHAR_DATE_NONE},
833 : : {"FF5", 3, DCH_FF5, true, FROM_CHAR_DATE_NONE},
834 : : {"FF6", 3, DCH_FF6, true, FROM_CHAR_DATE_NONE},
835 : : {"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
836 : : {"HH24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE}, /* H */
837 : : {"HH12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
838 : : {"HH", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
839 : : {"IDDD", 4, DCH_IDDD, true, FROM_CHAR_DATE_ISOWEEK}, /* I */
840 : : {"ID", 2, DCH_ID, true, FROM_CHAR_DATE_ISOWEEK},
841 : : {"IW", 2, DCH_IW, true, FROM_CHAR_DATE_ISOWEEK},
842 : : {"IYYY", 4, DCH_IYYY, true, FROM_CHAR_DATE_ISOWEEK},
843 : : {"IYY", 3, DCH_IYY, true, FROM_CHAR_DATE_ISOWEEK},
844 : : {"IY", 2, DCH_IY, true, FROM_CHAR_DATE_ISOWEEK},
845 : : {"I", 1, DCH_I, true, FROM_CHAR_DATE_ISOWEEK},
846 : : {"J", 1, DCH_J, true, FROM_CHAR_DATE_NONE}, /* J */
847 : : {"MI", 2, DCH_MI, true, FROM_CHAR_DATE_NONE}, /* M */
848 : : {"MM", 2, DCH_MM, true, FROM_CHAR_DATE_GREGORIAN},
849 : : {"MONTH", 5, DCH_MONTH, false, FROM_CHAR_DATE_GREGORIAN},
850 : : {"MON", 3, DCH_MON, false, FROM_CHAR_DATE_GREGORIAN},
851 : : {"MS", 2, DCH_MS, true, FROM_CHAR_DATE_NONE},
852 : : {"Month", 5, DCH_Month, false, FROM_CHAR_DATE_GREGORIAN},
853 : : {"Mon", 3, DCH_Mon, false, FROM_CHAR_DATE_GREGORIAN},
854 : : {"OF", 2, DCH_OF, false, FROM_CHAR_DATE_NONE}, /* O */
855 : : {"P.M.", 4, DCH_P_M, false, FROM_CHAR_DATE_NONE}, /* P */
856 : : {"PM", 2, DCH_PM, false, FROM_CHAR_DATE_NONE},
857 : : {"Q", 1, DCH_Q, true, FROM_CHAR_DATE_NONE}, /* Q */
858 : : {"RM", 2, DCH_RM, false, FROM_CHAR_DATE_GREGORIAN}, /* R */
859 : : {"SSSSS", 5, DCH_SSSS, true, FROM_CHAR_DATE_NONE}, /* S */
860 : : {"SSSS", 4, DCH_SSSS, true, FROM_CHAR_DATE_NONE},
861 : : {"SS", 2, DCH_SS, true, FROM_CHAR_DATE_NONE},
862 : : {"TZH", 3, DCH_TZH, false, FROM_CHAR_DATE_NONE}, /* T */
863 : : {"TZM", 3, DCH_TZM, true, FROM_CHAR_DATE_NONE},
864 : : {"TZ", 2, DCH_TZ, false, FROM_CHAR_DATE_NONE},
865 : : {"US", 2, DCH_US, true, FROM_CHAR_DATE_NONE}, /* U */
866 : : {"WW", 2, DCH_WW, true, FROM_CHAR_DATE_GREGORIAN}, /* W */
867 : : {"W", 1, DCH_W, true, FROM_CHAR_DATE_GREGORIAN},
868 : : {"Y,YYY", 5, DCH_Y_YYY, true, FROM_CHAR_DATE_GREGORIAN}, /* Y */
869 : : {"YYYY", 4, DCH_YYYY, true, FROM_CHAR_DATE_GREGORIAN},
870 : : {"YYY", 3, DCH_YYY, true, FROM_CHAR_DATE_GREGORIAN},
871 : : {"YY", 2, DCH_YY, true, FROM_CHAR_DATE_GREGORIAN},
872 : : {"Y", 1, DCH_Y, true, FROM_CHAR_DATE_GREGORIAN},
873 : : {"a.d.", 4, DCH_a_d, false, FROM_CHAR_DATE_NONE}, /* a */
874 : : {"a.m.", 4, DCH_a_m, false, FROM_CHAR_DATE_NONE},
875 : : {"ad", 2, DCH_ad, false, FROM_CHAR_DATE_NONE},
876 : : {"am", 2, DCH_am, false, FROM_CHAR_DATE_NONE},
877 : : {"b.c.", 4, DCH_b_c, false, FROM_CHAR_DATE_NONE}, /* b */
878 : : {"bc", 2, DCH_bc, false, FROM_CHAR_DATE_NONE},
879 : : {"cc", 2, DCH_CC, true, FROM_CHAR_DATE_NONE}, /* c */
880 : : {"day", 3, DCH_day, false, FROM_CHAR_DATE_NONE}, /* d */
881 : : {"ddd", 3, DCH_DDD, true, FROM_CHAR_DATE_GREGORIAN},
882 : : {"dd", 2, DCH_DD, true, FROM_CHAR_DATE_GREGORIAN},
883 : : {"dy", 2, DCH_dy, false, FROM_CHAR_DATE_NONE},
884 : : {"d", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
885 : : {"ff1", 3, DCH_FF1, true, FROM_CHAR_DATE_NONE}, /* f */
886 : : {"ff2", 3, DCH_FF2, true, FROM_CHAR_DATE_NONE},
887 : : {"ff3", 3, DCH_FF3, true, FROM_CHAR_DATE_NONE},
888 : : {"ff4", 3, DCH_FF4, true, FROM_CHAR_DATE_NONE},
889 : : {"ff5", 3, DCH_FF5, true, FROM_CHAR_DATE_NONE},
890 : : {"ff6", 3, DCH_FF6, true, FROM_CHAR_DATE_NONE},
891 : : {"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
892 : : {"hh24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE}, /* h */
893 : : {"hh12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
894 : : {"hh", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
895 : : {"iddd", 4, DCH_IDDD, true, FROM_CHAR_DATE_ISOWEEK}, /* i */
896 : : {"id", 2, DCH_ID, true, FROM_CHAR_DATE_ISOWEEK},
897 : : {"iw", 2, DCH_IW, true, FROM_CHAR_DATE_ISOWEEK},
898 : : {"iyyy", 4, DCH_IYYY, true, FROM_CHAR_DATE_ISOWEEK},
899 : : {"iyy", 3, DCH_IYY, true, FROM_CHAR_DATE_ISOWEEK},
900 : : {"iy", 2, DCH_IY, true, FROM_CHAR_DATE_ISOWEEK},
901 : : {"i", 1, DCH_I, true, FROM_CHAR_DATE_ISOWEEK},
902 : : {"j", 1, DCH_J, true, FROM_CHAR_DATE_NONE}, /* j */
903 : : {"mi", 2, DCH_MI, true, FROM_CHAR_DATE_NONE}, /* m */
904 : : {"mm", 2, DCH_MM, true, FROM_CHAR_DATE_GREGORIAN},
905 : : {"month", 5, DCH_month, false, FROM_CHAR_DATE_GREGORIAN},
906 : : {"mon", 3, DCH_mon, false, FROM_CHAR_DATE_GREGORIAN},
907 : : {"ms", 2, DCH_MS, true, FROM_CHAR_DATE_NONE},
908 : : {"of", 2, DCH_OF, false, FROM_CHAR_DATE_NONE}, /* o */
909 : : {"p.m.", 4, DCH_p_m, false, FROM_CHAR_DATE_NONE}, /* p */
910 : : {"pm", 2, DCH_pm, false, FROM_CHAR_DATE_NONE},
911 : : {"q", 1, DCH_Q, true, FROM_CHAR_DATE_NONE}, /* q */
912 : : {"rm", 2, DCH_rm, false, FROM_CHAR_DATE_GREGORIAN}, /* r */
913 : : {"sssss", 5, DCH_SSSS, true, FROM_CHAR_DATE_NONE}, /* s */
914 : : {"ssss", 4, DCH_SSSS, true, FROM_CHAR_DATE_NONE},
915 : : {"ss", 2, DCH_SS, true, FROM_CHAR_DATE_NONE},
916 : : {"tzh", 3, DCH_TZH, false, FROM_CHAR_DATE_NONE}, /* t */
917 : : {"tzm", 3, DCH_TZM, true, FROM_CHAR_DATE_NONE},
918 : : {"tz", 2, DCH_tz, false, FROM_CHAR_DATE_NONE},
919 : : {"us", 2, DCH_US, true, FROM_CHAR_DATE_NONE}, /* u */
920 : : {"ww", 2, DCH_WW, true, FROM_CHAR_DATE_GREGORIAN}, /* w */
921 : : {"w", 1, DCH_W, true, FROM_CHAR_DATE_GREGORIAN},
922 : : {"y,yyy", 5, DCH_Y_YYY, true, FROM_CHAR_DATE_GREGORIAN}, /* y */
923 : : {"yyyy", 4, DCH_YYYY, true, FROM_CHAR_DATE_GREGORIAN},
924 : : {"yyy", 3, DCH_YYY, true, FROM_CHAR_DATE_GREGORIAN},
925 : : {"yy", 2, DCH_YY, true, FROM_CHAR_DATE_GREGORIAN},
926 : : {"y", 1, DCH_Y, true, FROM_CHAR_DATE_GREGORIAN},
927 : :
928 : : /* last */
929 : : {NULL, 0, 0, 0, 0}
930 : : };
931 : :
932 : : /*
933 : : * KeyWords for NUMBER version
934 : : *
935 : : * The is_digit and date_mode fields are not relevant here.
936 : : */
937 : : static const KeyWord NUM_keywords[] = {
938 : : /* name, len, id is in Index */
939 : : {",", 1, NUM_COMMA}, /* , */
940 : : {".", 1, NUM_DEC}, /* . */
941 : : {"0", 1, NUM_0}, /* 0 */
942 : : {"9", 1, NUM_9}, /* 9 */
943 : : {"B", 1, NUM_B}, /* B */
944 : : {"C", 1, NUM_C}, /* C */
945 : : {"D", 1, NUM_D}, /* D */
946 : : {"EEEE", 4, NUM_E}, /* E */
947 : : {"FM", 2, NUM_FM}, /* F */
948 : : {"G", 1, NUM_G}, /* G */
949 : : {"L", 1, NUM_L}, /* L */
950 : : {"MI", 2, NUM_MI}, /* M */
951 : : {"PL", 2, NUM_PL}, /* P */
952 : : {"PR", 2, NUM_PR},
953 : : {"RN", 2, NUM_RN}, /* R */
954 : : {"SG", 2, NUM_SG}, /* S */
955 : : {"SP", 2, NUM_SP},
956 : : {"S", 1, NUM_S},
957 : : {"TH", 2, NUM_TH}, /* T */
958 : : {"V", 1, NUM_V}, /* V */
959 : : {"b", 1, NUM_B}, /* b */
960 : : {"c", 1, NUM_C}, /* c */
961 : : {"d", 1, NUM_D}, /* d */
962 : : {"eeee", 4, NUM_E}, /* e */
963 : : {"fm", 2, NUM_FM}, /* f */
964 : : {"g", 1, NUM_G}, /* g */
965 : : {"l", 1, NUM_L}, /* l */
966 : : {"mi", 2, NUM_MI}, /* m */
967 : : {"pl", 2, NUM_PL}, /* p */
968 : : {"pr", 2, NUM_PR},
969 : : {"rn", 2, NUM_rn}, /* r */
970 : : {"sg", 2, NUM_SG}, /* s */
971 : : {"sp", 2, NUM_SP},
972 : : {"s", 1, NUM_S},
973 : : {"th", 2, NUM_th}, /* t */
974 : : {"v", 1, NUM_V}, /* v */
975 : :
976 : : /* last */
977 : : {NULL, 0, 0}
978 : : };
979 : :
980 : :
981 : : /*
982 : : * KeyWords index for DATE-TIME version
983 : : */
984 : : static const int DCH_index[KeyWord_INDEX_SIZE] = {
985 : : /*
986 : : 0 1 2 3 4 5 6 7 8 9
987 : : */
988 : : /*---- first 0..31 chars are skipped ----*/
989 : :
990 : : -1, -1, -1, -1, -1, -1, -1, -1,
991 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
992 : : -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
993 : : -1, -1, -1, -1, -1, DCH_A_D, DCH_B_C, DCH_CC, DCH_DAY, -1,
994 : : DCH_FF1, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
995 : : DCH_P_M, DCH_Q, DCH_RM, DCH_SSSSS, DCH_TZH, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY,
996 : : -1, -1, -1, -1, -1, -1, -1, DCH_a_d, DCH_b_c, DCH_cc,
997 : : DCH_day, -1, DCH_ff1, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
998 : : -1, DCH_of, DCH_p_m, DCH_q, DCH_rm, DCH_sssss, DCH_tzh, DCH_us, -1, DCH_ww,
999 : : -1, DCH_y_yyy, -1, -1, -1, -1
1000 : :
1001 : : /*---- chars over 126 are skipped ----*/
1002 : : };
1003 : :
1004 : : /*
1005 : : * KeyWords index for NUMBER version
1006 : : */
1007 : : static const int NUM_index[KeyWord_INDEX_SIZE] = {
1008 : : /*
1009 : : 0 1 2 3 4 5 6 7 8 9
1010 : : */
1011 : : /*---- first 0..31 chars are skipped ----*/
1012 : :
1013 : : -1, -1, -1, -1, -1, -1, -1, -1,
1014 : : -1, -1, -1, -1, NUM_COMMA, -1, NUM_DEC, -1, NUM_0, -1,
1015 : : -1, -1, -1, -1, -1, -1, -1, NUM_9, -1, -1,
1016 : : -1, -1, -1, -1, -1, -1, NUM_B, NUM_C, NUM_D, NUM_E,
1017 : : NUM_FM, NUM_G, -1, -1, -1, -1, NUM_L, NUM_MI, -1, -1,
1018 : : NUM_PL, -1, NUM_RN, NUM_SG, NUM_TH, -1, NUM_V, -1, -1, -1,
1019 : : -1, -1, -1, -1, -1, -1, -1, -1, NUM_b, NUM_c,
1020 : : NUM_d, NUM_e, NUM_fm, NUM_g, -1, -1, -1, -1, NUM_l, NUM_mi,
1021 : : -1, -1, NUM_pl, -1, NUM_rn, NUM_sg, NUM_th, -1, NUM_v, -1,
1022 : : -1, -1, -1, -1, -1, -1
1023 : :
1024 : : /*---- chars over 126 are skipped ----*/
1025 : : };
1026 : :
1027 : : /*
1028 : : * Number processor struct
1029 : : */
1030 : : typedef struct NUMProc
1031 : : {
1032 : : bool is_to_char;
1033 : : NUMDesc *Num; /* number description */
1034 : :
1035 : : int sign, /* '-' or '+' */
1036 : : sign_wrote, /* was sign write */
1037 : : num_count, /* number of write digits */
1038 : : num_in, /* is inside number */
1039 : : num_curr, /* current position in number */
1040 : : out_pre_spaces, /* spaces before first digit */
1041 : :
1042 : : read_dec, /* to_number - was read dec. point */
1043 : : read_post, /* to_number - number of dec. digit */
1044 : : read_pre; /* to_number - number non-dec. digit */
1045 : :
1046 : : char *number, /* string with number */
1047 : : *number_p, /* pointer to current number position */
1048 : : *inout, /* in / out buffer */
1049 : : *inout_p; /* pointer to current inout position */
1050 : :
1051 : : const char *last_relevant, /* last relevant number after decimal point */
1052 : :
1053 : : *L_negative_sign, /* Locale */
1054 : : *L_positive_sign,
1055 : : *decimal,
1056 : : *L_thousands_sep,
1057 : : *L_currency_symbol;
1058 : : } NUMProc;
1059 : :
1060 : : /* Return flags for DCH_from_char() */
1061 : : #define DCH_DATED 0x01
1062 : : #define DCH_TIMED 0x02
1063 : : #define DCH_ZONED 0x04
1064 : :
1065 : : /*
1066 : : * These macros are used in NUM_processor() and its subsidiary routines.
1067 : : * OVERLOAD_TEST: true if we've reached end of input string
1068 : : * AMOUNT_TEST(s): true if at least s bytes remain in string
1069 : : */
1070 : : #define OVERLOAD_TEST (Np->inout_p >= Np->inout + input_len)
1071 : : #define AMOUNT_TEST(s) (Np->inout_p <= Np->inout + (input_len - (s)))
1072 : :
1073 : :
1074 : : /*
1075 : : * Functions
1076 : : */
1077 : : static const KeyWord *index_seq_search(const char *str, const KeyWord *kw,
1078 : : const int *index);
1079 : : static const KeySuffix *suff_search(const char *str, const KeySuffix *suf, enum KeySuffixType type);
1080 : : static bool is_separator_char(const char *str);
1081 : : static void NUMDesc_prepare(NUMDesc *num, FormatNode *n);
1082 : : static void parse_format(FormatNode *node, const char *str, const KeyWord *kw,
1083 : : const KeySuffix *suf, const int *index, uint32 flags, NUMDesc *Num);
1084 : :
1085 : : static void DCH_to_char(FormatNode *node, bool is_interval,
1086 : : TmToChar *in, char *out, Oid collid);
1087 : : static void DCH_from_char(FormatNode *node, const char *in, TmFromChar *out,
1088 : : Oid collid, bool std, Node *escontext);
1089 : :
1090 : : #ifdef DEBUG_TO_FROM_CHAR
1091 : : static void dump_index(const KeyWord *k, const int *index);
1092 : : static void dump_node(FormatNode *node, int max);
1093 : : #endif
1094 : :
1095 : : static const char *get_th(const char *num, enum TH_Case type);
1096 : : static char *str_numth(char *dest, const char *num, enum TH_Case type);
1097 : : static int adjust_partial_year_to_2020(int year);
1098 : : static size_t strspace_len(const char *str);
1099 : : static bool from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode,
1100 : : Node *escontext);
1101 : : static bool from_char_set_int(int *dest, const int value, const FormatNode *node,
1102 : : Node *escontext);
1103 : : static int from_char_parse_int_len(int *dest, const char **src, const size_t len,
1104 : : FormatNode *node, Node *escontext);
1105 : : static int from_char_parse_int(int *dest, const char **src, FormatNode *node,
1106 : : Node *escontext);
1107 : : static int seq_search_ascii(const char *name, const char *const *array, size_t *len);
1108 : : static int seq_search_localized(const char *name, char **array, size_t *len,
1109 : : Oid collid);
1110 : : static bool from_char_seq_search(int *dest, const char **src,
1111 : : const char *const *array,
1112 : : char **localized_array, Oid collid,
1113 : : FormatNode *node, Node *escontext);
1114 : : static bool do_to_timestamp(const text *date_txt, const text *fmt, Oid collid, bool std,
1115 : : struct pg_tm *tm, fsec_t *fsec, struct fmt_tz *tz,
1116 : : int *fprec, uint32 *flags, Node *escontext);
1117 : : static void fill_str(char *str, int c, int max);
1118 : : static FormatNode *NUM_cache(int len, NUMDesc *Num, const text *pars_str, bool *shouldFree);
1119 : : static char *int_to_roman(int number);
1120 : : static int roman_to_int(NUMProc *Np, size_t input_len);
1121 : : static void NUM_prepare_locale(NUMProc *Np);
1122 : : static const char *get_last_relevant_decnum(const char *num);
1123 : : static void NUM_numpart_from_char(NUMProc *Np, int id, size_t input_len);
1124 : : static void NUM_numpart_to_char(NUMProc *Np, int id);
1125 : : static char *NUM_processor(FormatNode *node, NUMDesc *Num, char *inout,
1126 : : char *number, size_t input_len, int to_char_out_pre_spaces,
1127 : : int sign, bool is_to_char, Oid collid);
1128 : : static DCHCacheEntry *DCH_cache_getnew(const char *str, bool std);
1129 : : static DCHCacheEntry *DCH_cache_search(const char *str, bool std);
1130 : : static DCHCacheEntry *DCH_cache_fetch(const char *str, bool std);
1131 : : static NUMCacheEntry *NUM_cache_getnew(const char *str);
1132 : : static NUMCacheEntry *NUM_cache_search(const char *str);
1133 : : static NUMCacheEntry *NUM_cache_fetch(const char *str);
1134 : :
1135 : :
1136 : : /*
1137 : : * Fast sequential search, use index for data selection which
1138 : : * go to seq. cycle (it is very fast for unwanted strings)
1139 : : * (can't be used binary search in format parsing)
1140 : : */
1141 : : static const KeyWord *
3367 tgl@sss.pgh.pa.us 1142 :CBC 15429 : index_seq_search(const char *str, const KeyWord *kw, const int *index)
1143 : : {
1144 : : int poz;
1145 : :
9380 bruce@momjian.us 1146 [ + + - + ]: 15429 : if (!KeyWord_INDEX_FILTER(*str))
8015 neilc@samurai.com 1147 : 3545 : return NULL;
1148 : :
49 peter@eisentraut.org 1149 [ + + ]:GNC 11884 : if ((poz = index[*str - ' ']) > -1)
1150 : : {
7571 tgl@sss.pgh.pa.us 1151 :CBC 10836 : const KeyWord *k = kw + poz;
1152 : :
1153 : : do
1154 : : {
5104 peter_e@gmx.net 1155 [ + + ]: 14421 : if (strncmp(str, k->name, k->len) == 0)
9458 bruce@momjian.us 1156 : 10770 : return k;
1157 : 3651 : k++;
1158 [ - + ]: 3651 : if (!k->name)
8015 neilc@samurai.com 1159 :UBC 0 : return NULL;
9380 bruce@momjian.us 1160 [ + + ]:CBC 3651 : } while (*str == *k->name);
1161 : : }
8015 neilc@samurai.com 1162 : 1114 : return NULL;
1163 : : }
1164 : :
1165 : : static const KeySuffix *
47 peter@eisentraut.org 1166 :GNC 5836 : suff_search(const char *str, const KeySuffix *suf, enum KeySuffixType type)
1167 : : {
50 1168 [ + + ]: 45254 : for (const KeySuffix *s = suf; s->name != NULL; s++)
1169 : : {
9458 bruce@momjian.us 1170 [ + + ]:CBC 39637 : if (s->type != type)
1171 : 18719 : continue;
1172 : :
5104 peter_e@gmx.net 1173 [ + + ]: 20918 : if (strncmp(str, s->name, s->len) == 0)
9458 bruce@momjian.us 1174 : 219 : return s;
1175 : : }
8015 neilc@samurai.com 1176 : 5617 : return NULL;
1177 : : }
1178 : :
1179 : : static bool
2656 akorotkov@postgresql 1180 : 3276 : is_separator_char(const char *str)
1181 : : {
1182 : : /* ASCII printable character, but not letter or digit */
1183 [ + - ]: 2434 : return (*str > 0x20 && *str < 0x7F &&
1184 [ + + + + ]: 2434 : !(*str >= 'A' && *str <= 'Z') &&
1185 [ + + + + : 8000 : !(*str >= 'a' && *str <= 'z') &&
- + ]
1186 [ + + + + ]: 2290 : !(*str >= '0' && *str <= '9'));
1187 : : }
1188 : :
1189 : : /*
1190 : : * Prepare NUMDesc (number description struct) via FormatNode struct
1191 : : */
1192 : : static void
4114 bruce@momjian.us 1193 : 8512 : NUMDesc_prepare(NUMDesc *num, FormatNode *n)
1194 : : {
9458 1195 [ - + ]: 8512 : if (n->type != NODE_TYPE_ACTION)
9458 bruce@momjian.us 1196 :UBC 0 : return;
1197 : :
3367 tgl@sss.pgh.pa.us 1198 [ - + - - ]:CBC 8512 : if (IS_EEEE(num) && n->key->id != NUM_E)
3367 tgl@sss.pgh.pa.us 1199 [ # # ]:UBC 0 : ereport(ERROR,
1200 : : (errcode(ERRCODE_SYNTAX_ERROR),
1201 : : errmsg("\"EEEE\" must be the last pattern used")));
1202 : :
3367 tgl@sss.pgh.pa.us 1203 [ + + - + :CBC 8512 : switch (n->key->id)
+ + + + +
+ + + + +
+ + ]
1204 : : {
1205 : 7245 : case NUM_9:
1206 [ - + ]: 7245 : if (IS_BRACKET(num))
3367 tgl@sss.pgh.pa.us 1207 [ # # ]:UBC 0 : ereport(ERROR,
1208 : : (errcode(ERRCODE_SYNTAX_ERROR),
1209 : : errmsg("\"9\" must be ahead of \"PR\"")));
3367 tgl@sss.pgh.pa.us 1210 [ + + ]:CBC 7245 : if (IS_MULTI(num))
1211 : : {
1212 : 18 : ++num->multi;
5773 bruce@momjian.us 1213 : 18 : break;
1214 : : }
3367 tgl@sss.pgh.pa.us 1215 [ + + ]: 7227 : if (IS_DECIMAL(num))
1216 : 2491 : ++num->post;
1217 : : else
1218 : 4736 : ++num->pre;
1219 : 7227 : break;
1220 : :
1221 : 268 : case NUM_0:
1222 [ - + ]: 268 : if (IS_BRACKET(num))
3367 tgl@sss.pgh.pa.us 1223 [ # # ]:UBC 0 : ereport(ERROR,
1224 : : (errcode(ERRCODE_SYNTAX_ERROR),
1225 : : errmsg("\"0\" must be ahead of \"PR\"")));
3367 tgl@sss.pgh.pa.us 1226 [ + + + + ]:CBC 268 : if (!IS_ZERO(num) && !IS_DECIMAL(num))
1227 : : {
1228 : 57 : num->flag |= NUM_F_ZERO;
1229 : 57 : num->zero_start = num->pre + 1;
1230 : : }
1231 [ + + ]: 268 : if (!IS_DECIMAL(num))
1232 : 184 : ++num->pre;
1233 : : else
1234 : 84 : ++num->post;
1235 : :
1236 : 268 : num->zero_end = num->pre + num->post;
1237 : 268 : break;
1238 : :
3367 tgl@sss.pgh.pa.us 1239 :UBC 0 : case NUM_B:
49 peter@eisentraut.org 1240 [ # # # # :UNC 0 : if (num->pre == 0 && num->post == 0 && !IS_ZERO(num))
# # ]
3367 tgl@sss.pgh.pa.us 1241 :UBC 0 : num->flag |= NUM_F_BLANK;
1242 : 0 : break;
1243 : :
3367 tgl@sss.pgh.pa.us 1244 :CBC 45 : case NUM_D:
1245 : 45 : num->flag |= NUM_F_LDECIMAL;
3045 peter_e@gmx.net 1246 : 45 : num->need_locale = true;
1247 : : /* FALLTHROUGH */
3367 tgl@sss.pgh.pa.us 1248 : 243 : case NUM_DEC:
1249 [ - + ]: 243 : if (IS_DECIMAL(num))
3367 tgl@sss.pgh.pa.us 1250 [ # # ]:UBC 0 : ereport(ERROR,
1251 : : (errcode(ERRCODE_SYNTAX_ERROR),
1252 : : errmsg("multiple decimal points")));
3367 tgl@sss.pgh.pa.us 1253 [ - + ]:CBC 243 : if (IS_MULTI(num))
3367 tgl@sss.pgh.pa.us 1254 [ # # ]:UBC 0 : ereport(ERROR,
1255 : : (errcode(ERRCODE_SYNTAX_ERROR),
1256 : : errmsg("cannot use \"V\" and decimal point together")));
3367 tgl@sss.pgh.pa.us 1257 :CBC 243 : num->flag |= NUM_F_DECIMAL;
1258 : 243 : break;
1259 : :
1260 : 132 : case NUM_FM:
1261 : 132 : num->flag |= NUM_F_FILLMODE;
1262 : 132 : break;
1263 : :
1264 : 113 : case NUM_S:
1265 [ - + ]: 113 : if (IS_LSIGN(num))
3367 tgl@sss.pgh.pa.us 1266 [ # # ]:UBC 0 : ereport(ERROR,
1267 : : (errcode(ERRCODE_SYNTAX_ERROR),
1268 : : errmsg("cannot use \"S\" twice")));
3367 tgl@sss.pgh.pa.us 1269 [ + - + - :CBC 113 : if (IS_PLUS(num) || IS_MINUS(num) || IS_BRACKET(num))
- + ]
3367 tgl@sss.pgh.pa.us 1270 [ # # ]:UBC 0 : ereport(ERROR,
1271 : : (errcode(ERRCODE_SYNTAX_ERROR),
1272 : : errmsg("cannot use \"S\" and \"PL\"/\"MI\"/\"SG\"/\"PR\" together")));
3367 tgl@sss.pgh.pa.us 1273 [ + + ]:CBC 113 : if (!IS_DECIMAL(num))
1274 : : {
1275 : 96 : num->lsign = NUM_LSIGN_PRE;
1276 : 96 : num->pre_lsign_num = num->pre;
3045 peter_e@gmx.net 1277 : 96 : num->need_locale = true;
3367 tgl@sss.pgh.pa.us 1278 : 96 : num->flag |= NUM_F_LSIGN;
1279 : : }
1280 [ + - ]: 17 : else if (num->lsign == NUM_LSIGN_NONE)
1281 : : {
1282 : 17 : num->lsign = NUM_LSIGN_POST;
3045 peter_e@gmx.net 1283 : 17 : num->need_locale = true;
3367 tgl@sss.pgh.pa.us 1284 : 17 : num->flag |= NUM_F_LSIGN;
1285 : : }
1286 : 113 : break;
1287 : :
1288 : 18 : case NUM_MI:
1289 [ - + ]: 18 : if (IS_LSIGN(num))
3367 tgl@sss.pgh.pa.us 1290 [ # # ]:UBC 0 : ereport(ERROR,
1291 : : (errcode(ERRCODE_SYNTAX_ERROR),
1292 : : errmsg("cannot use \"S\" and \"MI\" together")));
3367 tgl@sss.pgh.pa.us 1293 :CBC 18 : num->flag |= NUM_F_MINUS;
1294 [ + + ]: 18 : if (IS_DECIMAL(num))
1295 : 3 : num->flag |= NUM_F_MINUS_POST;
1296 : 18 : break;
1297 : :
1298 : 3 : case NUM_PL:
1299 [ - + ]: 3 : if (IS_LSIGN(num))
3367 tgl@sss.pgh.pa.us 1300 [ # # ]:UBC 0 : ereport(ERROR,
1301 : : (errcode(ERRCODE_SYNTAX_ERROR),
1302 : : errmsg("cannot use \"S\" and \"PL\" together")));
3367 tgl@sss.pgh.pa.us 1303 :CBC 3 : num->flag |= NUM_F_PLUS;
1304 [ - + ]: 3 : if (IS_DECIMAL(num))
3367 tgl@sss.pgh.pa.us 1305 :UBC 0 : num->flag |= NUM_F_PLUS_POST;
3367 tgl@sss.pgh.pa.us 1306 :CBC 3 : break;
1307 : :
1308 : 12 : case NUM_SG:
1309 [ - + ]: 12 : if (IS_LSIGN(num))
3367 tgl@sss.pgh.pa.us 1310 [ # # ]:UBC 0 : ereport(ERROR,
1311 : : (errcode(ERRCODE_SYNTAX_ERROR),
1312 : : errmsg("cannot use \"S\" and \"SG\" together")));
3367 tgl@sss.pgh.pa.us 1313 :CBC 12 : num->flag |= NUM_F_MINUS;
1314 : 12 : num->flag |= NUM_F_PLUS;
1315 : 12 : break;
1316 : :
1317 : 18 : case NUM_PR:
1318 [ + - + - : 18 : if (IS_LSIGN(num) || IS_PLUS(num) || IS_MINUS(num))
- + ]
3367 tgl@sss.pgh.pa.us 1319 [ # # ]:UBC 0 : ereport(ERROR,
1320 : : (errcode(ERRCODE_SYNTAX_ERROR),
1321 : : errmsg("cannot use \"PR\" and \"S\"/\"PL\"/\"MI\"/\"SG\" together")));
3367 tgl@sss.pgh.pa.us 1322 :CBC 18 : num->flag |= NUM_F_BRACKET;
1323 : 18 : break;
1324 : :
1325 : 33 : case NUM_rn:
1326 : : case NUM_RN:
329 1327 [ + + ]: 33 : if (IS_ROMAN(num))
1328 [ + - ]: 3 : ereport(ERROR,
1329 : : (errcode(ERRCODE_SYNTAX_ERROR),
1330 : : errmsg("cannot use \"RN\" twice")));
3367 1331 : 30 : num->flag |= NUM_F_ROMAN;
1332 : 30 : break;
1333 : :
1334 : 352 : case NUM_L:
1335 : : case NUM_G:
3045 peter_e@gmx.net 1336 : 352 : num->need_locale = true;
3367 tgl@sss.pgh.pa.us 1337 : 352 : break;
1338 : :
1339 : 9 : case NUM_V:
1340 [ - + ]: 9 : if (IS_DECIMAL(num))
3367 tgl@sss.pgh.pa.us 1341 [ # # ]:UBC 0 : ereport(ERROR,
1342 : : (errcode(ERRCODE_SYNTAX_ERROR),
1343 : : errmsg("cannot use \"V\" and decimal point together")));
3367 tgl@sss.pgh.pa.us 1344 :CBC 9 : num->flag |= NUM_F_MULTI;
1345 : 9 : break;
1346 : :
1347 : 9 : case NUM_E:
1348 [ - + ]: 9 : if (IS_EEEE(num))
3367 tgl@sss.pgh.pa.us 1349 [ # # ]:UBC 0 : ereport(ERROR,
1350 : : (errcode(ERRCODE_SYNTAX_ERROR),
1351 : : errmsg("cannot use \"EEEE\" twice")));
3367 tgl@sss.pgh.pa.us 1352 [ + - + - :CBC 9 : if (IS_BLANK(num) || IS_FILLMODE(num) || IS_LSIGN(num) ||
+ - ]
1353 [ + - + - : 9 : IS_BRACKET(num) || IS_MINUS(num) || IS_PLUS(num) ||
+ - ]
1354 [ + - - + ]: 9 : IS_ROMAN(num) || IS_MULTI(num))
3367 tgl@sss.pgh.pa.us 1355 [ # # ]:UBC 0 : ereport(ERROR,
1356 : : (errcode(ERRCODE_SYNTAX_ERROR),
1357 : : errmsg("\"EEEE\" is incompatible with other formats"),
1358 : : errdetail("\"EEEE\" may only be used together with digit and decimal point patterns.")));
3367 tgl@sss.pgh.pa.us 1359 :CBC 9 : num->flag |= NUM_F_EEEE;
1360 : 9 : break;
1361 : : }
1362 : :
329 1363 [ + + ]: 8509 : if (IS_ROMAN(num) &&
1364 [ + + ]: 30 : (num->flag & ~(NUM_F_ROMAN | NUM_F_FILLMODE)) != 0)
1365 [ + - ]: 3 : ereport(ERROR,
1366 : : (errcode(ERRCODE_SYNTAX_ERROR),
1367 : : errmsg("\"RN\" is incompatible with other formats"),
1368 : : errdetail("\"RN\" may only be used together with \"FM\".")));
1369 : : }
1370 : :
1371 : : /*
1372 : : * Format parser, search small keywords and keyword's suffixes, and make
1373 : : * format-node tree.
1374 : : *
1375 : : * for DATE-TIME & NUMBER version
1376 : : */
1377 : : static void
3367 1378 : 929 : parse_format(FormatNode *node, const char *str, const KeyWord *kw,
1379 : : const KeySuffix *suf, const int *index, uint32 flags, NUMDesc *Num)
1380 : : {
1381 : : FormatNode *n;
1382 : :
1383 : : #ifdef DEBUG_TO_FROM_CHAR
1384 : : elog(DEBUG_elog_output, "to_char/number(): run parser");
1385 : : #endif
1386 : :
9458 bruce@momjian.us 1387 : 929 : n = node;
1388 : :
9380 1389 [ + + ]: 16349 : while (*str)
1390 : : {
2951 tgl@sss.pgh.pa.us 1391 : 15429 : int suffix = 0;
1392 : : const KeySuffix *s;
1393 : :
1394 : : /*
1395 : : * Prefix
1396 : : */
2275 akorotkov@postgresql 1397 [ + + + + ]: 19460 : if ((flags & DCH_FLAG) &&
2951 tgl@sss.pgh.pa.us 1398 : 4031 : (s = suff_search(str, suf, SUFFTYPE_PREFIX)) != NULL)
1399 : : {
9458 bruce@momjian.us 1400 : 198 : suffix |= s->id;
1401 [ + - ]: 198 : if (s->len)
1402 : 198 : str += s->len;
1403 : : }
1404 : :
1405 : : /*
1406 : : * Keyword
1407 : : */
9380 1408 [ + - + + ]: 15429 : if (*str && (n->key = index_seq_search(str, kw, index)) != NULL)
1409 : : {
9458 1410 : 10770 : n->type = NODE_TYPE_ACTION;
2951 tgl@sss.pgh.pa.us 1411 : 10770 : n->suffix = suffix;
9458 bruce@momjian.us 1412 [ + - ]: 10770 : if (n->key->len)
1413 : 10770 : str += n->key->len;
1414 : :
1415 : : /*
1416 : : * NUM version: Prepare global NUMDesc struct
1417 : : */
2275 akorotkov@postgresql 1418 [ + + ]: 10770 : if (flags & NUM_FLAG)
4114 bruce@momjian.us 1419 : 8512 : NUMDesc_prepare(Num, n);
1420 : :
1421 : : /*
1422 : : * Postfix
1423 : : */
2275 akorotkov@postgresql 1424 [ + + + + : 12569 : if ((flags & DCH_FLAG) && *str &&
+ + ]
2951 tgl@sss.pgh.pa.us 1425 : 1805 : (s = suff_search(str, suf, SUFFTYPE_POSTFIX)) != NULL)
1426 : : {
1427 : 21 : n->suffix |= s->id;
9458 bruce@momjian.us 1428 [ + - ]: 21 : if (s->len)
1429 : 21 : str += s->len;
1430 : : }
1431 : :
2951 tgl@sss.pgh.pa.us 1432 : 10764 : n++;
1433 : : }
9380 bruce@momjian.us 1434 [ + - ]: 4659 : else if (*str)
1435 : : {
1436 : : int chlen;
1437 : :
1905 akorotkov@postgresql 1438 [ + + + + ]: 4659 : if ((flags & STD_FLAG) && *str != '"')
1439 : : {
1440 : : /*
1441 : : * Standard mode, allow only following separators: "-./,':; ".
1442 : : * However, we support double quotes even in standard mode
1443 : : * (see below). This is our extension of standard mode.
1444 : : */
2275 1445 [ + + ]: 285 : if (strchr("-./,':; ", *str) == NULL)
1446 [ + - ]: 3 : ereport(ERROR,
1447 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
1448 : : errmsg("invalid datetime format separator: \"%s\"",
1449 : : pnstrdup(str, pg_mblen(str)))));
1450 : :
1451 [ + + ]: 282 : if (*str == ' ')
1452 : 45 : n->type = NODE_TYPE_SPACE;
1453 : : else
1454 : 237 : n->type = NODE_TYPE_SEPARATOR;
1455 : :
1456 : 282 : n->character[0] = *str;
1457 : 282 : n->character[1] = '\0';
1458 : 282 : n->key = NULL;
1459 : 282 : n->suffix = 0;
1460 : 282 : n++;
1461 : 282 : str++;
1462 : : }
1463 [ + + ]: 4374 : else if (*str == '"')
1464 : : {
1465 : : /*
1466 : : * Process double-quoted literal string, if any
1467 : : */
2951 tgl@sss.pgh.pa.us 1468 : 192 : str++;
1469 [ + + ]: 2208 : while (*str)
1470 : : {
1471 [ + + ]: 2205 : if (*str == '"')
1472 : : {
9458 bruce@momjian.us 1473 : 189 : str++;
1474 : 189 : break;
1475 : : }
1476 : : /* backslash quotes the next character, if any */
2951 tgl@sss.pgh.pa.us 1477 [ + + + - ]: 2016 : if (*str == '\\' && *(str + 1))
1478 : 120 : str++;
1479 : 2016 : chlen = pg_mblen(str);
9458 bruce@momjian.us 1480 : 2016 : n->type = NODE_TYPE_CHAR;
2951 tgl@sss.pgh.pa.us 1481 : 2016 : memcpy(n->character, str, chlen);
1482 : 2016 : n->character[chlen] = '\0';
8015 neilc@samurai.com 1483 : 2016 : n->key = NULL;
9458 bruce@momjian.us 1484 : 2016 : n->suffix = 0;
2951 tgl@sss.pgh.pa.us 1485 : 2016 : n++;
1486 : 2016 : str += chlen;
1487 : : }
1488 : : }
1489 : : else
1490 : : {
1491 : : /*
1492 : : * Outside double-quoted strings, backslash is only special if
1493 : : * it immediately precedes a double quote.
1494 : : */
1495 [ + + + + ]: 4182 : if (*str == '\\' && *(str + 1) == '"')
1496 : 6 : str++;
1497 : 4182 : chlen = pg_mblen(str);
1498 : :
2275 akorotkov@postgresql 1499 [ + + + + ]: 4182 : if ((flags & DCH_FLAG) && is_separator_char(str))
2656 1500 : 538 : n->type = NODE_TYPE_SEPARATOR;
1501 [ + + ]: 3644 : else if (isspace((unsigned char) *str))
1502 : 3500 : n->type = NODE_TYPE_SPACE;
1503 : : else
1504 : 144 : n->type = NODE_TYPE_CHAR;
1505 : :
2951 tgl@sss.pgh.pa.us 1506 : 4182 : memcpy(n->character, str, chlen);
1507 : 4182 : n->character[chlen] = '\0';
8015 neilc@samurai.com 1508 : 4182 : n->key = NULL;
2951 tgl@sss.pgh.pa.us 1509 : 4182 : n->suffix = 0;
1510 : 4182 : n++;
1511 : 4182 : str += chlen;
1512 : : }
1513 : : }
1514 : : }
1515 : :
9458 bruce@momjian.us 1516 : 920 : n->type = NODE_TYPE_END;
1517 : 920 : n->suffix = 0;
1518 : 920 : }
1519 : :
1520 : : /*
1521 : : * DEBUG: Dump the FormatNode Tree (debug)
1522 : : */
1523 : : #ifdef DEBUG_TO_FROM_CHAR
1524 : :
1525 : : #define DUMP_THth(_suf) (IS_SUFFIX_TH(_suf) ? "TH" : (IS_SUFFIX_th(_suf) ? "th" : " "))
1526 : : #define DUMP_FM(_suf) (IS_SUFFIX_FM(_suf) ? "FM" : " ")
1527 : :
1528 : : static void
1529 : : dump_node(FormatNode *node, int max)
1530 : : {
1531 : : FormatNode *n;
1532 : : int a;
1533 : :
1534 : : elog(DEBUG_elog_output, "to_from-char(): DUMP FORMAT");
1535 : :
1536 : : for (a = 0, n = node; a <= max; n++, a++)
1537 : : {
1538 : : if (n->type == NODE_TYPE_ACTION)
1539 : : elog(DEBUG_elog_output, "%d:\t NODE_TYPE_ACTION '%s'\t(%s,%s)",
1540 : : a, n->key->name, DUMP_THth(n->suffix), DUMP_FM(n->suffix));
1541 : : else if (n->type == NODE_TYPE_CHAR)
1542 : : elog(DEBUG_elog_output, "%d:\t NODE_TYPE_CHAR '%s'",
1543 : : a, n->character);
1544 : : else if (n->type == NODE_TYPE_END)
1545 : : {
1546 : : elog(DEBUG_elog_output, "%d:\t NODE_TYPE_END", a);
1547 : : return;
1548 : : }
1549 : : else
1550 : : elog(DEBUG_elog_output, "%d:\t unknown NODE!", a);
1551 : : }
1552 : : }
1553 : : #endif /* DEBUG */
1554 : :
1555 : : /*****************************************************************************
1556 : : * Private utils
1557 : : *****************************************************************************/
1558 : :
1559 : : /*
1560 : : * Return ST/ND/RD/TH for simple (1..9) numbers
1561 : : */
1562 : : static const char *
47 peter@eisentraut.org 1563 :GNC 1167 : get_th(const char *num, enum TH_Case type)
1564 : : {
49 1565 : 1167 : size_t len = strlen(num);
1566 : : char last;
1567 : :
121 1568 [ - + ]: 1167 : Assert(len > 0);
1569 : :
49 1570 : 1167 : last = num[len - 1];
9458 bruce@momjian.us 1571 [ - + ]:CBC 1167 : if (!isdigit((unsigned char) last))
8179 tgl@sss.pgh.pa.us 1572 [ # # ]:UBC 0 : ereport(ERROR,
1573 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
1574 : : errmsg("\"%s\" is not a number", num)));
1575 : :
1576 : : /*
1577 : : * All "teens" (<x>1[0-9]) get 'TH/th', while <x>[02-9][123] still get
1578 : : * 'ST/st', 'ND/nd', 'RD/rd', respectively
1579 : : */
49 peter@eisentraut.org 1580 [ + - + + ]:GNC 1167 : if (len > 1 && num[len - 2] == '1')
9380 bruce@momjian.us 1581 :CBC 66 : last = 0;
1582 : :
1583 [ + + + + ]: 1167 : switch (last)
1584 : : {
9458 1585 : 48 : case '1':
9380 1586 [ + + ]: 48 : if (type == TH_UPPER)
1587 : 12 : return numTH[0];
9458 1588 : 36 : return numth[0];
1589 : 24 : case '2':
9380 1590 [ - + ]: 24 : if (type == TH_UPPER)
9380 bruce@momjian.us 1591 :UBC 0 : return numTH[1];
9458 bruce@momjian.us 1592 :CBC 24 : return numth[1];
1593 : 18 : case '3':
9380 1594 [ + + ]: 18 : if (type == TH_UPPER)
1595 : 3 : return numTH[2];
1596 : 15 : return numth[2];
9458 1597 : 1077 : default:
9380 1598 [ + + ]: 1077 : if (type == TH_UPPER)
1599 : 378 : return numTH[3];
9458 1600 : 699 : return numth[3];
1601 : : }
1602 : : }
1603 : :
1604 : : /*
1605 : : * Convert string-number to ordinal string-number
1606 : : */
1607 : : static char *
47 peter@eisentraut.org 1608 :GNC 1143 : str_numth(char *dest, const char *num, enum TH_Case type)
1609 : : {
7643 tgl@sss.pgh.pa.us 1610 [ - + ]:CBC 1143 : if (dest != num)
7643 tgl@sss.pgh.pa.us 1611 :UBC 0 : strcpy(dest, num);
7643 tgl@sss.pgh.pa.us 1612 :CBC 1143 : strcat(dest, get_th(num, type));
9380 bruce@momjian.us 1613 : 1143 : return dest;
1614 : : }
1615 : :
1616 : : /*****************************************************************************
1617 : : * upper/lower/initcap functions
1618 : : *****************************************************************************/
1619 : :
1620 : : /*
1621 : : * collation-aware, wide-character-aware lower function
1622 : : *
1623 : : * We pass the number of bytes so we can pass varlena and char*
1624 : : * to this function. The result is a palloc'd, null-terminated string.
1625 : : */
1626 : : char *
5426 peter_e@gmx.net 1627 : 218867 : str_tolower(const char *buff, size_t nbytes, Oid collid)
1628 : : {
1629 : : char *result;
1630 : : pg_locale_t mylocale;
1631 : :
8489 bruce@momjian.us 1632 [ - + ]: 218867 : if (!buff)
8489 bruce@momjian.us 1633 :UBC 0 : return NULL;
1634 : :
1427 peter@eisentraut.org 1635 [ - + ]:CBC 218867 : if (!OidIsValid(collid))
1636 : : {
1637 : : /*
1638 : : * This typically means that the parser could not resolve a conflict
1639 : : * of implicit collations, so report it that way.
1640 : : */
1427 peter@eisentraut.org 1641 [ # # ]:UBC 0 : ereport(ERROR,
1642 : : (errcode(ERRCODE_INDETERMINATE_COLLATION),
1643 : : errmsg("could not determine which collation to use for %s function",
1644 : : "lower()"),
1645 : : errhint("Use the COLLATE clause to set the collation explicitly.")));
1646 : : }
1647 : :
467 jdavis@postgresql.or 1648 :CBC 218867 : mylocale = pg_newlocale_from_collation(collid);
1649 : :
1650 : : /* C/POSIX collations use this path regardless of database encoding */
1651 [ + + ]: 218867 : if (mylocale->ctype_is_c)
1652 : : {
4670 tgl@sss.pgh.pa.us 1653 : 138 : result = asc_tolower(buff, nbytes);
1654 : : }
1655 : : else
1656 : : {
366 jdavis@postgresql.or 1657 : 218729 : const char *src = buff;
1658 : 218729 : size_t srclen = nbytes;
1659 : : size_t dstsize;
1660 : : char *dst;
1661 : : size_t needed;
1662 : :
1663 : : /* first try buffer of equal size plus terminating NUL */
1664 : 218729 : dstsize = srclen + 1;
1665 : 218729 : dst = palloc(dstsize);
1666 : :
1667 : 218729 : needed = pg_strlower(dst, dstsize, src, srclen, mylocale);
1668 [ + + ]: 218729 : if (needed + 1 > dstsize)
1669 : : {
1670 : : /* grow buffer if needed and retry */
1671 : 48 : dstsize = needed + 1;
1672 : 48 : dst = repalloc(dst, dstsize);
1673 : 48 : needed = pg_strlower(dst, dstsize, src, srclen, mylocale);
1674 [ - + ]: 48 : Assert(needed + 1 <= dstsize);
1675 : : }
1676 : :
1677 [ - + ]: 218729 : Assert(dst[needed] == '\0');
1678 : 218729 : result = dst;
1679 : : }
1680 : :
6421 tgl@sss.pgh.pa.us 1681 : 218867 : return result;
1682 : : }
1683 : :
1684 : : /*
1685 : : * collation-aware, wide-character-aware upper function
1686 : : *
1687 : : * We pass the number of bytes so we can pass varlena and char*
1688 : : * to this function. The result is a palloc'd, null-terminated string.
1689 : : */
1690 : : char *
5426 peter_e@gmx.net 1691 : 526633 : str_toupper(const char *buff, size_t nbytes, Oid collid)
1692 : : {
1693 : : char *result;
1694 : : pg_locale_t mylocale;
1695 : :
8489 bruce@momjian.us 1696 [ - + ]: 526633 : if (!buff)
8489 bruce@momjian.us 1697 :UBC 0 : return NULL;
1698 : :
1427 peter@eisentraut.org 1699 [ - + ]:CBC 526633 : if (!OidIsValid(collid))
1700 : : {
1701 : : /*
1702 : : * This typically means that the parser could not resolve a conflict
1703 : : * of implicit collations, so report it that way.
1704 : : */
1427 peter@eisentraut.org 1705 [ # # ]:UBC 0 : ereport(ERROR,
1706 : : (errcode(ERRCODE_INDETERMINATE_COLLATION),
1707 : : errmsg("could not determine which collation to use for %s function",
1708 : : "upper()"),
1709 : : errhint("Use the COLLATE clause to set the collation explicitly.")));
1710 : : }
1711 : :
467 jdavis@postgresql.or 1712 :CBC 526633 : mylocale = pg_newlocale_from_collation(collid);
1713 : :
1714 : : /* C/POSIX collations use this path regardless of database encoding */
1715 [ + + ]: 526633 : if (mylocale->ctype_is_c)
1716 : : {
4670 tgl@sss.pgh.pa.us 1717 : 7581 : result = asc_toupper(buff, nbytes);
1718 : : }
1719 : : else
1720 : : {
366 jdavis@postgresql.or 1721 : 519052 : const char *src = buff;
1722 : 519052 : size_t srclen = nbytes;
1723 : : size_t dstsize;
1724 : : char *dst;
1725 : : size_t needed;
1726 : :
1727 : : /* first try buffer of equal size plus terminating NUL */
1728 : 519052 : dstsize = srclen + 1;
1729 : 519052 : dst = palloc(dstsize);
1730 : :
1731 : 519052 : needed = pg_strupper(dst, dstsize, src, srclen, mylocale);
1732 [ + + ]: 519052 : if (needed + 1 > dstsize)
1733 : : {
1734 : : /* grow buffer if needed and retry */
1735 : 3 : dstsize = needed + 1;
1736 : 3 : dst = repalloc(dst, dstsize);
1737 : 3 : needed = pg_strupper(dst, dstsize, src, srclen, mylocale);
1738 [ - + ]: 3 : Assert(needed + 1 <= dstsize);
1739 : : }
1740 : :
1741 [ - + ]: 519052 : Assert(dst[needed] == '\0');
1742 : 519052 : result = dst;
1743 : : }
1744 : :
6421 tgl@sss.pgh.pa.us 1745 : 526633 : return result;
1746 : : }
1747 : :
1748 : : /*
1749 : : * collation-aware, wide-character-aware initcap function
1750 : : *
1751 : : * We pass the number of bytes so we can pass varlena and char*
1752 : : * to this function. The result is a palloc'd, null-terminated string.
1753 : : */
1754 : : char *
5426 peter_e@gmx.net 1755 : 113 : str_initcap(const char *buff, size_t nbytes, Oid collid)
1756 : : {
1757 : : char *result;
1758 : : pg_locale_t mylocale;
1759 : :
6887 bruce@momjian.us 1760 [ - + ]: 113 : if (!buff)
6887 bruce@momjian.us 1761 :UBC 0 : return NULL;
1762 : :
1427 peter@eisentraut.org 1763 [ - + ]:CBC 113 : if (!OidIsValid(collid))
1764 : : {
1765 : : /*
1766 : : * This typically means that the parser could not resolve a conflict
1767 : : * of implicit collations, so report it that way.
1768 : : */
1427 peter@eisentraut.org 1769 [ # # ]:UBC 0 : ereport(ERROR,
1770 : : (errcode(ERRCODE_INDETERMINATE_COLLATION),
1771 : : errmsg("could not determine which collation to use for %s function",
1772 : : "initcap()"),
1773 : : errhint("Use the COLLATE clause to set the collation explicitly.")));
1774 : : }
1775 : :
467 jdavis@postgresql.or 1776 :CBC 113 : mylocale = pg_newlocale_from_collation(collid);
1777 : :
1778 : : /* C/POSIX collations use this path regardless of database encoding */
1779 [ + + ]: 113 : if (mylocale->ctype_is_c)
1780 : : {
4670 tgl@sss.pgh.pa.us 1781 : 12 : result = asc_initcap(buff, nbytes);
1782 : : }
1783 : : else
1784 : : {
366 jdavis@postgresql.or 1785 : 101 : const char *src = buff;
1786 : 101 : size_t srclen = nbytes;
1787 : : size_t dstsize;
1788 : : char *dst;
1789 : : size_t needed;
1790 : :
1791 : : /* first try buffer of equal size plus terminating NUL */
1792 : 101 : dstsize = srclen + 1;
1793 : 101 : dst = palloc(dstsize);
1794 : :
1795 : 101 : needed = pg_strtitle(dst, dstsize, src, srclen, mylocale);
1796 [ + + ]: 101 : if (needed + 1 > dstsize)
1797 : : {
1798 : : /* grow buffer if needed and retry */
1799 : 15 : dstsize = needed + 1;
1800 : 15 : dst = repalloc(dst, dstsize);
1801 : 15 : needed = pg_strtitle(dst, dstsize, src, srclen, mylocale);
1802 [ - + ]: 15 : Assert(needed + 1 <= dstsize);
1803 : : }
1804 : :
1805 [ - + ]: 101 : Assert(dst[needed] == '\0');
1806 : 101 : result = dst;
1807 : : }
1808 : :
6421 tgl@sss.pgh.pa.us 1809 : 113 : return result;
1810 : : }
1811 : :
1812 : : /*
1813 : : * collation-aware, wide-character-aware case folding
1814 : : *
1815 : : * We pass the number of bytes so we can pass varlena and char*
1816 : : * to this function. The result is a palloc'd, null-terminated string.
1817 : : */
1818 : : char *
327 jdavis@postgresql.or 1819 : 12 : str_casefold(const char *buff, size_t nbytes, Oid collid)
1820 : : {
1821 : : char *result;
1822 : : pg_locale_t mylocale;
1823 : :
1824 [ - + ]: 12 : if (!buff)
327 jdavis@postgresql.or 1825 :UBC 0 : return NULL;
1826 : :
327 jdavis@postgresql.or 1827 [ - + ]:CBC 12 : if (!OidIsValid(collid))
1828 : : {
1829 : : /*
1830 : : * This typically means that the parser could not resolve a conflict
1831 : : * of implicit collations, so report it that way.
1832 : : */
327 jdavis@postgresql.or 1833 [ # # ]:UBC 0 : ereport(ERROR,
1834 : : (errcode(ERRCODE_INDETERMINATE_COLLATION),
1835 : : errmsg("could not determine which collation to use for %s function",
1836 : : "lower()"),
1837 : : errhint("Use the COLLATE clause to set the collation explicitly.")));
1838 : : }
1839 : :
327 jdavis@postgresql.or 1840 [ - + ]:CBC 12 : if (GetDatabaseEncoding() != PG_UTF8)
327 jdavis@postgresql.or 1841 [ # # ]:UBC 0 : ereport(ERROR,
1842 : : (errcode(ERRCODE_SYNTAX_ERROR),
1843 : : errmsg("Unicode case folding can only be performed if server encoding is UTF8")));
1844 : :
327 jdavis@postgresql.or 1845 :CBC 12 : mylocale = pg_newlocale_from_collation(collid);
1846 : :
1847 : : /* C/POSIX collations use this path regardless of database encoding */
1848 [ - + ]: 12 : if (mylocale->ctype_is_c)
1849 : : {
327 jdavis@postgresql.or 1850 :UBC 0 : result = asc_tolower(buff, nbytes);
1851 : : }
1852 : : else
1853 : : {
327 jdavis@postgresql.or 1854 :CBC 12 : const char *src = buff;
1855 : 12 : size_t srclen = nbytes;
1856 : : size_t dstsize;
1857 : : char *dst;
1858 : : size_t needed;
1859 : :
1860 : : /* first try buffer of equal size plus terminating NUL */
1861 : 12 : dstsize = srclen + 1;
1862 : 12 : dst = palloc(dstsize);
1863 : :
1864 : 12 : needed = pg_strfold(dst, dstsize, src, srclen, mylocale);
1865 [ - + ]: 12 : if (needed + 1 > dstsize)
1866 : : {
1867 : : /* grow buffer if needed and retry */
327 jdavis@postgresql.or 1868 :UBC 0 : dstsize = needed + 1;
1869 : 0 : dst = repalloc(dst, dstsize);
1870 : 0 : needed = pg_strfold(dst, dstsize, src, srclen, mylocale);
1871 [ # # ]: 0 : Assert(needed + 1 <= dstsize);
1872 : : }
1873 : :
327 jdavis@postgresql.or 1874 [ - + ]:CBC 12 : Assert(dst[needed] == '\0');
1875 : 12 : result = dst;
1876 : : }
1877 : :
1878 : 12 : return result;
1879 : : }
1880 : :
1881 : : /*
1882 : : * ASCII-only lower function
1883 : : *
1884 : : * We pass the number of bytes so we can pass varlena and char*
1885 : : * to this function. The result is a palloc'd, null-terminated string.
1886 : : */
1887 : : char *
4670 tgl@sss.pgh.pa.us 1888 : 2442 : asc_tolower(const char *buff, size_t nbytes)
1889 : : {
1890 : : char *result;
1891 : :
1892 [ - + ]: 2442 : if (!buff)
4670 tgl@sss.pgh.pa.us 1893 :UBC 0 : return NULL;
1894 : :
4670 tgl@sss.pgh.pa.us 1895 :CBC 2442 : result = pnstrdup(buff, nbytes);
1896 : :
50 peter@eisentraut.org 1897 [ + + ]:GNC 16800 : for (char *p = result; *p; p++)
4670 tgl@sss.pgh.pa.us 1898 :CBC 14358 : *p = pg_ascii_tolower((unsigned char) *p);
1899 : :
1900 : 2442 : return result;
1901 : : }
1902 : :
1903 : : /*
1904 : : * ASCII-only upper function
1905 : : *
1906 : : * We pass the number of bytes so we can pass varlena and char*
1907 : : * to this function. The result is a palloc'd, null-terminated string.
1908 : : */
1909 : : char *
1910 : 9867 : asc_toupper(const char *buff, size_t nbytes)
1911 : : {
1912 : : char *result;
1913 : :
1914 [ - + ]: 9867 : if (!buff)
4670 tgl@sss.pgh.pa.us 1915 :UBC 0 : return NULL;
1916 : :
4670 tgl@sss.pgh.pa.us 1917 :CBC 9867 : result = pnstrdup(buff, nbytes);
1918 : :
50 peter@eisentraut.org 1919 [ + + ]:GNC 85068 : for (char *p = result; *p; p++)
4670 tgl@sss.pgh.pa.us 1920 :CBC 75201 : *p = pg_ascii_toupper((unsigned char) *p);
1921 : :
1922 : 9867 : return result;
1923 : : }
1924 : :
1925 : : /*
1926 : : * ASCII-only initcap function
1927 : : *
1928 : : * We pass the number of bytes so we can pass varlena and char*
1929 : : * to this function. The result is a palloc'd, null-terminated string.
1930 : : */
1931 : : char *
1932 : 12 : asc_initcap(const char *buff, size_t nbytes)
1933 : : {
1934 : : char *result;
1935 : 12 : int wasalnum = false;
1936 : :
1937 [ - + ]: 12 : if (!buff)
4670 tgl@sss.pgh.pa.us 1938 :UBC 0 : return NULL;
1939 : :
4670 tgl@sss.pgh.pa.us 1940 :CBC 12 : result = pnstrdup(buff, nbytes);
1941 : :
50 peter@eisentraut.org 1942 [ + + ]:GNC 48 : for (char *p = result; *p; p++)
1943 : : {
1944 : : char c;
1945 : :
4670 tgl@sss.pgh.pa.us 1946 [ + + ]:CBC 36 : if (wasalnum)
1947 : 24 : *p = c = pg_ascii_tolower((unsigned char) *p);
1948 : : else
1949 : 12 : *p = c = pg_ascii_toupper((unsigned char) *p);
1950 : : /* we don't trust isalnum() here */
1951 [ + + + - ]: 72 : wasalnum = ((c >= 'A' && c <= 'Z') ||
1952 [ + - - + : 72 : (c >= 'a' && c <= 'z') ||
- - ]
4670 tgl@sss.pgh.pa.us 1953 [ # # ]:UBC 0 : (c >= '0' && c <= '9'));
1954 : : }
1955 : :
4670 tgl@sss.pgh.pa.us 1956 :CBC 12 : return result;
1957 : : }
1958 : :
1959 : : /* convenience routines for when the input is null-terminated */
1960 : :
1961 : : static char *
5426 peter_e@gmx.net 1962 :UBC 0 : str_tolower_z(const char *buff, Oid collid)
1963 : : {
1964 : 0 : return str_tolower(buff, strlen(buff), collid);
1965 : : }
1966 : :
1967 : : static char *
1968 : 0 : str_toupper_z(const char *buff, Oid collid)
1969 : : {
1970 : 0 : return str_toupper(buff, strlen(buff), collid);
1971 : : }
1972 : :
1973 : : static char *
1974 : 0 : str_initcap_z(const char *buff, Oid collid)
1975 : : {
1976 : 0 : return str_initcap(buff, strlen(buff), collid);
1977 : : }
1978 : :
1979 : : static char *
4670 tgl@sss.pgh.pa.us 1980 :CBC 2304 : asc_tolower_z(const char *buff)
1981 : : {
1982 : 2304 : return asc_tolower(buff, strlen(buff));
1983 : : }
1984 : :
1985 : : static char *
1986 : 2286 : asc_toupper_z(const char *buff)
1987 : : {
1988 : 2286 : return asc_toupper(buff, strlen(buff));
1989 : : }
1990 : :
1991 : : /* asc_initcap_z is not currently needed */
1992 : :
1993 : :
1994 : : /*
1995 : : * Skip TM / th in FROM_CHAR
1996 : : *
1997 : : * If IS_SUFFIX_THth is on, skip two chars, assuming there are two available
1998 : : */
1999 : : #define SKIP_THth(ptr, _suf) \
2000 : : do { \
2001 : : if (IS_SUFFIX_THth(_suf)) \
2002 : : { \
2003 : : if (*(ptr)) (ptr) += pg_mblen(ptr); \
2004 : : if (*(ptr)) (ptr) += pg_mblen(ptr); \
2005 : : } \
2006 : : } while (0)
2007 : :
2008 : :
2009 : : #ifdef DEBUG_TO_FROM_CHAR
2010 : : /*
2011 : : * DEBUG: Call for debug and for index checking; (Show ASCII char
2012 : : * and defined keyword for each used position
2013 : : */
2014 : : static void
2015 : : dump_index(const KeyWord *k, const int *index)
2016 : : {
2017 : : int count = 0,
2018 : : free_i = 0;
2019 : :
2020 : : elog(DEBUG_elog_output, "TO-FROM_CHAR: Dump KeyWord Index:");
2021 : :
2022 : : for (int i = 0; i < KeyWord_INDEX_SIZE; i++)
2023 : : {
2024 : : if (index[i] != -1)
2025 : : {
2026 : : elog(DEBUG_elog_output, "\t%c: %s, ", i + 32, k[index[i]].name);
2027 : : count++;
2028 : : }
2029 : : else
2030 : : {
2031 : : free_i++;
2032 : : elog(DEBUG_elog_output, "\t(%d) %c %d", i, i + 32, index[i]);
2033 : : }
2034 : : }
2035 : : elog(DEBUG_elog_output, "\n\t\tUsed positions: %d,\n\t\tFree positions: %d",
2036 : : count, free_i);
2037 : : }
2038 : : #endif /* DEBUG */
2039 : :
2040 : : /*
2041 : : * Return true if next format picture is not digit value
2042 : : */
2043 : : static bool
9153 bruce@momjian.us 2044 : 61867 : is_next_separator(FormatNode *n)
2045 : : {
2046 [ - + ]: 61867 : if (n->type == NODE_TYPE_END)
3045 peter_e@gmx.net 2047 :UBC 0 : return false;
2048 : :
47 peter@eisentraut.org 2049 [ + - - + ]:GNC 61867 : if (n->type == NODE_TYPE_ACTION && IS_SUFFIX_THth(n->suffix))
3045 peter_e@gmx.net 2050 :UBC 0 : return true;
2051 : :
2052 : : /*
2053 : : * Next node
2054 : : */
9036 bruce@momjian.us 2055 :CBC 61867 : n++;
2056 : :
2057 : : /* end of format string is treated like a non-digit separator */
9153 2058 [ + + ]: 61867 : if (n->type == NODE_TYPE_END)
3045 peter_e@gmx.net 2059 : 6424 : return true;
2060 : :
9153 bruce@momjian.us 2061 [ + + ]: 55443 : if (n->type == NODE_TYPE_ACTION)
2062 : : {
6479 tgl@sss.pgh.pa.us 2063 [ + + ]: 3225 : if (n->key->is_digit)
3045 peter_e@gmx.net 2064 : 240 : return false;
2065 : :
2066 : 2985 : return true;
2067 : : }
2951 tgl@sss.pgh.pa.us 2068 [ + - ]: 52218 : else if (n->character[1] == '\0' &&
2069 [ - + ]: 52218 : isdigit((unsigned char) n->character[0]))
3045 peter_e@gmx.net 2070 :UBC 0 : return false;
2071 : :
3045 peter_e@gmx.net 2072 :CBC 52218 : return true; /* some non-digit input (separator) */
2073 : : }
2074 : :
2075 : :
2076 : : static int
5215 bruce@momjian.us 2077 : 42 : adjust_partial_year_to_2020(int year)
2078 : : {
2079 : : /*
2080 : : * Adjust all dates toward 2020; this is effectively what happens when we
2081 : : * assume '70' is 1970 and '69' is 2069.
2082 : : */
2083 : : /* Force 0-69 into the 2000's */
2084 [ + + ]: 42 : if (year < 70)
2085 : 21 : return year + 2000;
2086 : : /* Force 70-99 into the 1900's */
4916 tgl@sss.pgh.pa.us 2087 [ + + ]: 21 : else if (year < 100)
5215 bruce@momjian.us 2088 : 18 : return year + 1900;
2089 : : /* Force 100-519 into the 2000's */
4916 tgl@sss.pgh.pa.us 2090 [ - + ]: 3 : else if (year < 520)
5215 bruce@momjian.us 2091 :UBC 0 : return year + 2000;
2092 : : /* Force 520-999 into the 1000's */
4916 tgl@sss.pgh.pa.us 2093 [ + - ]:CBC 3 : else if (year < 1000)
5215 bruce@momjian.us 2094 : 3 : return year + 1000;
2095 : : else
5215 bruce@momjian.us 2096 :UBC 0 : return year;
2097 : : }
2098 : :
2099 : :
2100 : : static size_t
2155 tgl@sss.pgh.pa.us 2101 :CBC 61888 : strspace_len(const char *str)
2102 : : {
49 peter@eisentraut.org 2103 :GNC 61888 : size_t len = 0;
2104 : :
7182 bruce@momjian.us 2105 [ + - - + ]:CBC 61888 : while (*str && isspace((unsigned char) *str))
2106 : : {
7182 bruce@momjian.us 2107 :UBC 0 : str++;
2108 : 0 : len++;
2109 : : }
7182 bruce@momjian.us 2110 :CBC 61888 : return len;
2111 : : }
2112 : :
2113 : : /*
2114 : : * Set the date mode of a from-char conversion.
2115 : : *
2116 : : * Puke if the date mode has already been set, and the caller attempts to set
2117 : : * it to a conflicting mode.
2118 : : *
2119 : : * Returns true on success, false on failure (if escontext points to an
2120 : : * ErrorSaveContext; otherwise errors are thrown).
2121 : : */
2122 : : static bool
1104 tgl@sss.pgh.pa.us 2123 : 61879 : from_char_set_mode(TmFromChar *tmfc, const FromCharDateMode mode,
2124 : : Node *escontext)
2125 : : {
6306 2126 [ + + ]: 61879 : if (mode != FROM_CHAR_DATE_NONE)
2127 : : {
2128 [ + + ]: 27463 : if (tmfc->mode == FROM_CHAR_DATE_NONE)
2129 : 10144 : tmfc->mode = mode;
2130 [ + + ]: 17319 : else if (tmfc->mode != mode)
1104 2131 [ + - ]: 3 : ereturn(escontext, false,
2132 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
2133 : : errmsg("invalid combination of date conventions"),
2134 : : errhint("Do not mix Gregorian and ISO week date conventions in a formatting template.")));
2135 : : }
2136 : 61876 : return true;
2137 : : }
2138 : :
2139 : : /*
2140 : : * Set the integer pointed to by 'dest' to the given value.
2141 : : *
2142 : : * Puke if the destination integer has previously been set to some other
2143 : : * non-zero value.
2144 : : *
2145 : : * Returns true on success, false on failure (if escontext points to an
2146 : : * ErrorSaveContext; otherwise errors are thrown).
2147 : : */
2148 : : static bool
2275 akorotkov@postgresql 2149 : 61575 : from_char_set_int(int *dest, const int value, const FormatNode *node,
2150 : : Node *escontext)
2151 : : {
6306 tgl@sss.pgh.pa.us 2152 [ + + + - ]: 61575 : if (*dest != 0 && *dest != value)
1104 2153 [ + - ]: 3 : ereturn(escontext, false,
2154 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
2155 : : errmsg("conflicting values for \"%s\" field in formatting string",
2156 : : node->key->name),
2157 : : errdetail("This value contradicts a previous setting for the same field type.")));
6306 2158 : 61572 : *dest = value;
1104 2159 : 61572 : return true;
2160 : : }
2161 : :
2162 : : /*
2163 : : * Read a single integer from the source string, into the int pointed to by
2164 : : * 'dest'. If 'dest' is NULL, the result is discarded.
2165 : : *
2166 : : * In fixed-width mode (the node does not have the FM suffix), consume at most
2167 : : * 'len' characters. However, any leading whitespace isn't counted in 'len'.
2168 : : *
2169 : : * We use strtol() to recover the integer value from the source string, in
2170 : : * accordance with the given FormatNode.
2171 : : *
2172 : : * If the conversion completes successfully, src will have been advanced to
2173 : : * point at the character immediately following the last character used in the
2174 : : * conversion.
2175 : : *
2176 : : * Returns the number of characters consumed, or -1 on error (if escontext
2177 : : * points to an ErrorSaveContext; otherwise errors are thrown).
2178 : : *
2179 : : * Note that from_char_parse_int() provides a more convenient wrapper where
2180 : : * the length of the field is the same as the length of the format keyword (as
2181 : : * with DD and MI).
2182 : : */
2183 : : static int
49 peter@eisentraut.org 2184 :GNC 61888 : from_char_parse_int_len(int *dest, const char **src, const size_t len, FormatNode *node,
2185 : : Node *escontext)
2186 : : {
2187 : : long result;
2188 : : char copy[DCH_MAX_ITEM_SIZ + 1];
2155 tgl@sss.pgh.pa.us 2189 :CBC 61888 : const char *init = *src;
2190 : : size_t used;
2191 : :
2192 : : /*
2193 : : * Skip any whitespace before parsing the integer.
2194 : : */
6022 2195 : 61888 : *src += strspace_len(*src);
2196 : :
6157 bruce@momjian.us 2197 [ - + ]: 61888 : Assert(len <= DCH_MAX_ITEM_SIZ);
49 peter@eisentraut.org 2198 :GNC 61888 : used = strlcpy(copy, *src, len + 1);
2199 : :
47 2200 [ + + + + ]: 61888 : if (IS_SUFFIX_FM(node->suffix) || is_next_separator(node))
6306 tgl@sss.pgh.pa.us 2201 :CBC 61648 : {
2202 : : /*
2203 : : * This node is in Fill Mode, or the next node is known to be a
2204 : : * non-digit value, so we just slurp as many characters as we can get.
2205 : : */
2206 : : char *endptr;
2207 : :
2208 : 61648 : errno = 0;
2155 2209 : 61648 : result = strtol(init, &endptr, 10);
2210 : 61648 : *src = endptr;
2211 : : }
2212 : : else
2213 : : {
2214 : : /*
2215 : : * We need to pull exactly the number of characters given in 'len' out
2216 : : * of the string, and convert those.
2217 : : */
2218 : : char *last;
2219 : :
6157 bruce@momjian.us 2220 [ + + ]: 240 : if (used < len)
1104 tgl@sss.pgh.pa.us 2221 [ + - ]: 3 : ereturn(escontext, -1,
2222 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
2223 : : errmsg("source string too short for \"%s\" formatting field",
2224 : : node->key->name),
2225 : : errdetail("Field requires %zu characters, but only %zu remain.",
2226 : : len, used),
2227 : : errhint("If your source string is not fixed-width, try using the \"FM\" modifier.")));
2228 : :
6306 2229 : 237 : errno = 0;
6157 bruce@momjian.us 2230 : 237 : result = strtol(copy, &last, 10);
2231 : 237 : used = last - copy;
2232 : :
6306 tgl@sss.pgh.pa.us 2233 [ + - + + ]: 237 : if (used > 0 && used < len)
1104 2234 [ + - ]: 3 : ereturn(escontext, -1,
2235 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
2236 : : errmsg("invalid value \"%s\" for \"%s\"",
2237 : : copy, node->key->name),
2238 : : errdetail("Field requires %zu characters, but only %zu could be parsed.",
2239 : : len, used),
2240 : : errhint("If your source string is not fixed-width, try using the \"FM\" modifier.")));
2241 : :
6306 2242 : 234 : *src += used;
2243 : : }
2244 : :
2245 [ + + ]: 61882 : if (*src == init)
1104 2246 [ + + ]: 418 : ereturn(escontext, -1,
2247 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
2248 : : errmsg("invalid value \"%s\" for \"%s\"",
2249 : : copy, node->key->name),
2250 : : errdetail("Value must be an integer.")));
2251 : :
6306 2252 [ + - + - : 61464 : if (errno == ERANGE || result < INT_MIN || result > INT_MAX)
+ + ]
1104 2253 [ + - ]: 3 : ereturn(escontext, -1,
2254 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
2255 : : errmsg("value for \"%s\" in source string is out of range",
2256 : : node->key->name),
2257 : : errdetail("Value must be in the range %d to %d.",
2258 : : INT_MIN, INT_MAX)));
2259 : :
6246 heikki.linnakangas@i 2260 [ + + ]: 61461 : if (dest != NULL)
2261 : : {
1104 tgl@sss.pgh.pa.us 2262 [ - + ]: 61458 : if (!from_char_set_int(dest, (int) result, node, escontext))
1104 tgl@sss.pgh.pa.us 2263 :UBC 0 : return -1;
2264 : : }
2265 : :
6306 tgl@sss.pgh.pa.us 2266 :CBC 61461 : return *src - init;
2267 : : }
2268 : :
2269 : : /*
2270 : : * Call from_char_parse_int_len(), using the length of the format keyword as
2271 : : * the expected length of the field.
2272 : : *
2273 : : * Don't call this function if the field differs in length from the format
2274 : : * keyword (as with HH24; the keyword length is 4, but the field length is 2).
2275 : : * In such cases, call from_char_parse_int_len() instead to specify the
2276 : : * required length explicitly.
2277 : : */
2278 : : static int
1104 2279 : 43927 : from_char_parse_int(int *dest, const char **src, FormatNode *node,
2280 : : Node *escontext)
2281 : : {
2282 : 43927 : return from_char_parse_int_len(dest, src, node->key->len, node, escontext);
2283 : : }
2284 : :
2285 : : /*
2286 : : * Sequentially search null-terminated "array" for a case-insensitive match
2287 : : * to the initial character(s) of "name".
2288 : : *
2289 : : * Returns array index of match, or -1 for no match.
2290 : : *
2291 : : * *len is set to the length of the match, or 0 for no match.
2292 : : *
2293 : : * Case-insensitivity is defined per pg_ascii_tolower, so this is only
2294 : : * suitable for comparisons to ASCII strings.
2295 : : */
2296 : : static int
49 peter@eisentraut.org 2297 :GNC 120 : seq_search_ascii(const char *name, const char *const *array, size_t *len)
2298 : : {
2299 : : unsigned char firstc;
2300 : :
6306 tgl@sss.pgh.pa.us 2301 :CBC 120 : *len = 0;
2302 : :
2303 : : /* empty string can't match anything */
2304 [ - + ]: 120 : if (!*name)
6306 tgl@sss.pgh.pa.us 2305 :UBC 0 : return -1;
2306 : :
2307 : : /* we handle first char specially to gain some speed */
2155 tgl@sss.pgh.pa.us 2308 :CBC 120 : firstc = pg_ascii_tolower((unsigned char) *name);
2309 : :
50 peter@eisentraut.org 2310 [ + + ]:GNC 534 : for (const char *const *a = array; *a != NULL; a++)
2311 : : {
2312 : : /* compare first chars */
2155 tgl@sss.pgh.pa.us 2313 [ + + ]:CBC 528 : if (pg_ascii_tolower((unsigned char) **a) != firstc)
6306 2314 : 390 : continue;
2315 : :
2316 : : /* compare rest of string */
50 peter@eisentraut.org 2317 :GNC 393 : for (const char *p = *a + 1, *n = name + 1;; p++, n++)
2318 : : {
2319 : : /* return success if we matched whole array entry */
6306 tgl@sss.pgh.pa.us 2320 [ + + ]:CBC 393 : if (*p == '\0')
2321 : : {
2155 2322 : 114 : *len = n - name;
6306 2323 : 114 : return a - array;
2324 : : }
2325 : : /* else, must have another character in "name" ... */
2326 [ - + ]: 279 : if (*n == '\0')
6306 tgl@sss.pgh.pa.us 2327 :UBC 0 : break;
2328 : : /* ... and it must match */
2155 tgl@sss.pgh.pa.us 2329 [ + + ]:CBC 558 : if (pg_ascii_tolower((unsigned char) *p) !=
2330 : 279 : pg_ascii_tolower((unsigned char) *n))
6306 2331 : 24 : break;
2332 : : }
2333 : : }
2334 : :
2335 : 6 : return -1;
2336 : : }
2337 : :
2338 : : /*
2339 : : * Sequentially search an array of possibly non-English words for
2340 : : * a case-insensitive match to the initial character(s) of "name".
2341 : : *
2342 : : * This has the same API as seq_search_ascii(), but we use a more general
2343 : : * case-folding transformation to achieve case-insensitivity. Case folding
2344 : : * is done per the rules of the collation identified by "collid".
2345 : : *
2346 : : * The array is treated as const, but we don't declare it that way because
2347 : : * the arrays exported by pg_locale.c aren't const.
2348 : : */
2349 : : static int
49 peter@eisentraut.org 2350 :UNC 0 : seq_search_localized(const char *name, char **array, size_t *len, Oid collid)
2351 : : {
2352 : : char *upper_name;
2353 : : char *lower_name;
2354 : :
2115 tgl@sss.pgh.pa.us 2355 :UBC 0 : *len = 0;
2356 : :
2357 : : /* empty string can't match anything */
2358 [ # # ]: 0 : if (!*name)
2359 : 0 : return -1;
2360 : :
2361 : : /*
2362 : : * The case-folding processing done below is fairly expensive, so before
2363 : : * doing that, make a quick pass to see if there is an exact match.
2364 : : */
50 peter@eisentraut.org 2365 [ # # ]:UNC 0 : for (char **a = array; *a != NULL; a++)
2366 : : {
49 2367 : 0 : size_t element_len = strlen(*a);
2368 : :
2115 tgl@sss.pgh.pa.us 2369 [ # # ]:UBC 0 : if (strncmp(name, *a, element_len) == 0)
2370 : : {
2371 : 0 : *len = element_len;
2372 : 0 : return a - array;
2373 : : }
2374 : : }
2375 : :
2376 : : /*
2377 : : * Fold to upper case, then to lower case, so that we can match reliably
2378 : : * even in languages in which case conversions are not injective.
2379 : : */
467 peter@eisentraut.org 2380 : 0 : upper_name = str_toupper(name, strlen(name), collid);
2115 tgl@sss.pgh.pa.us 2381 : 0 : lower_name = str_tolower(upper_name, strlen(upper_name), collid);
2382 : 0 : pfree(upper_name);
2383 : :
50 peter@eisentraut.org 2384 [ # # ]:UNC 0 : for (char **a = array; *a != NULL; a++)
2385 : : {
2386 : : char *upper_element;
2387 : : char *lower_element;
2388 : : size_t element_len;
2389 : :
2390 : : /* Likewise upper/lower-case array element */
2115 tgl@sss.pgh.pa.us 2391 :UBC 0 : upper_element = str_toupper(*a, strlen(*a), collid);
2392 : 0 : lower_element = str_tolower(upper_element, strlen(upper_element),
2393 : : collid);
2394 : 0 : pfree(upper_element);
2395 : 0 : element_len = strlen(lower_element);
2396 : :
2397 : : /* Match? */
2398 [ # # ]: 0 : if (strncmp(lower_name, lower_element, element_len) == 0)
2399 : : {
2400 : 0 : *len = element_len;
2401 : 0 : pfree(lower_element);
2402 : 0 : pfree(lower_name);
2403 : 0 : return a - array;
2404 : : }
2405 : 0 : pfree(lower_element);
2406 : : }
2407 : :
2408 : 0 : pfree(lower_name);
2409 : 0 : return -1;
2410 : : }
2411 : :
2412 : : /*
2413 : : * Perform a sequential search in 'array' (or 'localized_array', if that's
2414 : : * not NULL) for an entry matching the first character(s) of the 'src'
2415 : : * string case-insensitively.
2416 : : *
2417 : : * The 'array' is presumed to be English words (all-ASCII), but
2418 : : * if 'localized_array' is supplied, that might be non-English
2419 : : * so we need a more expensive case-folding transformation
2420 : : * (which will follow the rules of the collation 'collid').
2421 : : *
2422 : : * If a match is found, copy the array index of the match into the integer
2423 : : * pointed to by 'dest' and advance 'src' to the end of the part of the string
2424 : : * which matched.
2425 : : *
2426 : : * Returns true on match, false on failure (if escontext points to an
2427 : : * ErrorSaveContext; otherwise errors are thrown).
2428 : : *
2429 : : * 'node' is used only for error reports: node->key->name identifies the
2430 : : * field type we were searching for.
2431 : : */
2432 : : static bool
2155 tgl@sss.pgh.pa.us 2433 :CBC 120 : from_char_seq_search(int *dest, const char **src, const char *const *array,
2434 : : char **localized_array, Oid collid,
2435 : : FormatNode *node, Node *escontext)
2436 : : {
2437 : : size_t len;
2438 : :
2115 2439 [ + - ]: 120 : if (localized_array == NULL)
2440 : 120 : *dest = seq_search_ascii(*src, array, &len);
2441 : : else
2115 tgl@sss.pgh.pa.us 2442 :UBC 0 : *dest = seq_search_localized(*src, localized_array, &len, collid);
2443 : :
6306 tgl@sss.pgh.pa.us 2444 [ + + ]:CBC 120 : if (len <= 0)
2445 : : {
2446 : : /*
2447 : : * In the error report, truncate the string at the next whitespace (if
2448 : : * any) to avoid including irrelevant data.
2449 : : */
2155 2450 : 6 : char *copy = pstrdup(*src);
2451 : :
50 peter@eisentraut.org 2452 [ + + ]:GNC 30 : for (char *c = copy; *c; c++)
2453 : : {
2155 tgl@sss.pgh.pa.us 2454 [ + + ]:CBC 27 : if (scanner_isspace(*c))
2455 : : {
2456 : 3 : *c = '\0';
2457 : 3 : break;
2458 : : }
2459 : : }
2460 : :
1104 2461 [ + - ]: 6 : ereturn(escontext, false,
2462 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
2463 : : errmsg("invalid value \"%s\" for \"%s\"",
2464 : : copy, node->key->name),
2465 : : errdetail("The given value did not match any of the allowed values for this field.")));
2466 : : }
6306 2467 : 114 : *src += len;
1104 2468 : 114 : return true;
2469 : : }
2470 : :
2471 : : /*
2472 : : * Process a TmToChar struct as denoted by a list of FormatNodes.
2473 : : * The formatted data is written to the string pointed to by 'out'.
2474 : : */
2475 : : static void
5426 peter_e@gmx.net 2476 : 4793 : DCH_to_char(FormatNode *node, bool is_interval, TmToChar *in, char *out, Oid collid)
2477 : : {
2478 : : char *s;
1355 tgl@sss.pgh.pa.us 2479 : 4793 : struct fmt_tm *tm = &in->tm;
2480 : : int i;
2481 : :
2482 : : /* cache localized days and months */
6421 2483 : 4793 : cache_locale_time();
2484 : :
6479 2485 : 4793 : s = out;
50 peter@eisentraut.org 2486 [ + + ]:GNC 93481 : for (FormatNode *n = node; n->type != NODE_TYPE_END; n++)
2487 : : {
6479 tgl@sss.pgh.pa.us 2488 [ + + ]:CBC 88688 : if (n->type != NODE_TYPE_ACTION)
2489 : : {
2951 2490 : 51948 : strcpy(s, n->character);
2491 : 51948 : s += strlen(s);
6479 2492 : 51948 : continue;
2493 : : }
2494 : :
2495 [ + - + + : 36740 : switch (n->key->id)
+ + + + +
+ + + + +
+ + + + +
+ + - + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + - +
- ]
2496 : : {
2497 : 381 : case DCH_A_M:
2498 : : case DCH_P_M:
2499 [ + + ]: 381 : strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
2500 : : ? P_M_STR : A_M_STR);
2501 : 381 : s += strlen(s);
2502 : 381 : break;
6479 tgl@sss.pgh.pa.us 2503 :UBC 0 : case DCH_AM:
2504 : : case DCH_PM:
2505 [ # # ]: 0 : strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
2506 : : ? PM_STR : AM_STR);
2507 : 0 : s += strlen(s);
2508 : 0 : break;
6479 tgl@sss.pgh.pa.us 2509 :CBC 381 : case DCH_a_m:
2510 : : case DCH_p_m:
2511 [ + + ]: 381 : strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
2512 : : ? p_m_STR : a_m_STR);
2513 : 381 : s += strlen(s);
2514 : 381 : break;
2515 : 381 : case DCH_am:
2516 : : case DCH_pm:
2517 [ + + ]: 381 : strcpy(s, (tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
2518 : : ? pm_STR : am_STR);
2519 : 381 : s += strlen(s);
2520 : 381 : break;
2521 : 2300 : case DCH_HH:
2522 : : case DCH_HH12:
2523 : :
2524 : : /*
2525 : : * display time as shown on a 12-hour clock, even for
2526 : : * intervals
2527 : : */
47 peter@eisentraut.org 2528 [ + - + - ]:GNC 2300 : sprintf(s, "%0*lld", IS_SUFFIX_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
1355 tgl@sss.pgh.pa.us 2529 [ + + ]:CBC 2300 : tm->tm_hour % (HOURS_PER_DAY / 2) == 0 ?
2530 : : (long long) (HOURS_PER_DAY / 2) :
2531 : 2216 : (long long) (tm->tm_hour % (HOURS_PER_DAY / 2)));
47 peter@eisentraut.org 2532 [ - + ]:GNC 2300 : if (IS_SUFFIX_THth(n->suffix))
47 peter@eisentraut.org 2533 :UNC 0 : str_numth(s, s, SUFFIX_TH_TYPE(n->suffix));
6479 tgl@sss.pgh.pa.us 2534 :CBC 2300 : s += strlen(s);
2535 : 2300 : break;
2536 : 763 : case DCH_HH24:
47 peter@eisentraut.org 2537 [ + - + - ]:GNC 763 : sprintf(s, "%0*lld", IS_SUFFIX_FM(n->suffix) ? 0 : (tm->tm_hour >= 0) ? 2 : 3,
1355 tgl@sss.pgh.pa.us 2538 [ - + ]:CBC 763 : (long long) tm->tm_hour);
47 peter@eisentraut.org 2539 [ - + ]:GNC 763 : if (IS_SUFFIX_THth(n->suffix))
47 peter@eisentraut.org 2540 :UNC 0 : str_numth(s, s, SUFFIX_TH_TYPE(n->suffix));
6479 tgl@sss.pgh.pa.us 2541 :CBC 763 : s += strlen(s);
2542 : 763 : break;
2543 : 2301 : case DCH_MI:
47 peter@eisentraut.org 2544 [ + - + - ]:GNC 2301 : sprintf(s, "%0*d", IS_SUFFIX_FM(n->suffix) ? 0 : (tm->tm_min >= 0) ? 2 : 3,
2545 : : tm->tm_min);
2546 [ - + ]: 2301 : if (IS_SUFFIX_THth(n->suffix))
47 peter@eisentraut.org 2547 :UNC 0 : str_numth(s, s, SUFFIX_TH_TYPE(n->suffix));
6479 tgl@sss.pgh.pa.us 2548 :CBC 2301 : s += strlen(s);
2549 : 2301 : break;
2550 : 2301 : case DCH_SS:
47 peter@eisentraut.org 2551 [ + - + - ]:GNC 2301 : sprintf(s, "%0*d", IS_SUFFIX_FM(n->suffix) ? 0 : (tm->tm_sec >= 0) ? 2 : 3,
2552 : : tm->tm_sec);
2553 [ - + ]: 2301 : if (IS_SUFFIX_THth(n->suffix))
47 peter@eisentraut.org 2554 :UNC 0 : str_numth(s, s, SUFFIX_TH_TYPE(n->suffix));
6479 tgl@sss.pgh.pa.us 2555 :CBC 2301 : s += strlen(s);
2556 : 2301 : break;
2557 : :
2558 : : #define DCH_to_char_fsec(frac_fmt, frac_val) \
2559 : : sprintf(s, frac_fmt, (int) (frac_val)); \
2560 : : if (IS_SUFFIX_THth(n->suffix)) \
2561 : : str_numth(s, s, SUFFIX_TH_TYPE(n->suffix)); \
2562 : : s += strlen(s)
2563 : :
2284 akorotkov@postgresql 2564 : 48 : case DCH_FF1: /* tenth of second */
2565 [ - + - + : 48 : DCH_to_char_fsec("%01d", in->fsec / 100000);
- - ]
2566 : 48 : break;
2567 : 48 : case DCH_FF2: /* hundredth of second */
2568 [ - + - + : 48 : DCH_to_char_fsec("%02d", in->fsec / 10000);
- - ]
2569 : 48 : break;
2570 : 72 : case DCH_FF3:
2571 : : case DCH_MS: /* millisecond */
2572 [ - + - + : 72 : DCH_to_char_fsec("%03d", in->fsec / 1000);
- - ]
6479 tgl@sss.pgh.pa.us 2573 : 72 : break;
2284 akorotkov@postgresql 2574 : 48 : case DCH_FF4: /* tenth of a millisecond */
2575 [ - + - + : 48 : DCH_to_char_fsec("%04d", in->fsec / 100);
- - ]
2576 : 48 : break;
2577 : 48 : case DCH_FF5: /* hundredth of a millisecond */
2578 [ - + - + : 48 : DCH_to_char_fsec("%05d", in->fsec / 10);
- - ]
2579 : 48 : break;
2580 : 72 : case DCH_FF6:
2581 : : case DCH_US: /* microsecond */
2582 [ - + - + : 72 : DCH_to_char_fsec("%06d", in->fsec);
- - ]
6479 tgl@sss.pgh.pa.us 2583 : 72 : break;
2584 : : #undef DCH_to_char_fsec
2585 : 387 : case DCH_SSSS:
1355 2586 : 387 : sprintf(s, "%lld",
2587 : 387 : (long long) (tm->tm_hour * SECS_PER_HOUR +
2588 : 387 : tm->tm_min * SECS_PER_MINUTE +
2589 : 387 : tm->tm_sec));
47 peter@eisentraut.org 2590 [ - + ]:GNC 387 : if (IS_SUFFIX_THth(n->suffix))
47 peter@eisentraut.org 2591 :UNC 0 : str_numth(s, s, SUFFIX_TH_TYPE(n->suffix));
6479 tgl@sss.pgh.pa.us 2592 :CBC 387 : s += strlen(s);
2593 : 387 : break;
2594 : 3 : case DCH_tz:
2595 [ - + - - ]: 3 : INVALID_FOR_INTERVAL;
2596 [ + - ]: 3 : if (tmtcTzn(in))
2597 : : {
2598 : : /* We assume here that timezone names aren't localized */
4670 2599 : 3 : char *p = asc_tolower_z(tmtcTzn(in));
2600 : :
6367 2601 : 3 : strcpy(s, p);
6479 2602 : 3 : pfree(p);
2603 : 3 : s += strlen(s);
2604 : : }
2605 : 3 : break;
2606 : 9 : case DCH_TZ:
2607 [ - + - - ]: 9 : INVALID_FOR_INTERVAL;
2608 [ + - ]: 9 : if (tmtcTzn(in))
2609 : : {
2610 : 9 : strcpy(s, tmtcTzn(in));
2611 : 9 : s += strlen(s);
2612 : : }
2613 : 9 : break;
2899 andrew@dunslane.net 2614 : 54 : case DCH_TZH:
2615 [ - + - - ]: 54 : INVALID_FOR_INTERVAL;
2616 : 54 : sprintf(s, "%c%02d",
2617 : 54 : (tm->tm_gmtoff >= 0) ? '+' : '-',
2618 [ + + ]: 54 : abs((int) tm->tm_gmtoff) / SECS_PER_HOUR);
2619 : 54 : s += strlen(s);
2620 : 54 : break;
2621 : 54 : case DCH_TZM:
2622 [ - + - - ]: 54 : INVALID_FOR_INTERVAL;
2623 : 54 : sprintf(s, "%02d",
2624 : 54 : (abs((int) tm->tm_gmtoff) % SECS_PER_HOUR) / SECS_PER_MINUTE);
2625 : 54 : s += strlen(s);
2626 : 54 : break;
4552 bruce@momjian.us 2627 : 54 : case DCH_OF:
2628 [ - + - - ]: 54 : INVALID_FOR_INTERVAL;
3562 tgl@sss.pgh.pa.us 2629 [ - + ]: 108 : sprintf(s, "%c%0*d",
2630 [ + + ]: 54 : (tm->tm_gmtoff >= 0) ? '+' : '-',
47 peter@eisentraut.org 2631 :GNC 54 : IS_SUFFIX_FM(n->suffix) ? 0 : 2,
3562 tgl@sss.pgh.pa.us 2632 [ - + ]:CBC 54 : abs((int) tm->tm_gmtoff) / SECS_PER_HOUR);
4552 bruce@momjian.us 2633 : 54 : s += strlen(s);
3562 tgl@sss.pgh.pa.us 2634 [ + + ]: 54 : if (abs((int) tm->tm_gmtoff) % SECS_PER_HOUR != 0)
2635 : : {
2636 : 36 : sprintf(s, ":%02d",
2637 : 36 : (abs((int) tm->tm_gmtoff) % SECS_PER_HOUR) / SECS_PER_MINUTE);
4552 bruce@momjian.us 2638 : 36 : s += strlen(s);
2639 : : }
2640 : 54 : break;
6479 tgl@sss.pgh.pa.us 2641 : 381 : case DCH_A_D:
2642 : : case DCH_B_C:
2643 [ - + - - ]: 381 : INVALID_FOR_INTERVAL;
2644 [ + + ]: 381 : strcpy(s, (tm->tm_year <= 0 ? B_C_STR : A_D_STR));
2645 : 381 : s += strlen(s);
2646 : 381 : break;
6479 tgl@sss.pgh.pa.us 2647 :UBC 0 : case DCH_AD:
2648 : : case DCH_BC:
2649 [ # # # # ]: 0 : INVALID_FOR_INTERVAL;
2650 [ # # ]: 0 : strcpy(s, (tm->tm_year <= 0 ? BC_STR : AD_STR));
2651 : 0 : s += strlen(s);
2652 : 0 : break;
6479 tgl@sss.pgh.pa.us 2653 :CBC 381 : case DCH_a_d:
2654 : : case DCH_b_c:
2655 [ - + - - ]: 381 : INVALID_FOR_INTERVAL;
2656 [ + + ]: 381 : strcpy(s, (tm->tm_year <= 0 ? b_c_STR : a_d_STR));
2657 : 381 : s += strlen(s);
2658 : 381 : break;
2659 : 381 : case DCH_ad:
2660 : : case DCH_bc:
2661 [ - + - - ]: 381 : INVALID_FOR_INTERVAL;
2662 [ + + ]: 381 : strcpy(s, (tm->tm_year <= 0 ? bc_STR : ad_STR));
2663 : 381 : s += strlen(s);
2664 : 381 : break;
2665 : 762 : case DCH_MONTH:
2666 [ - + - - ]: 762 : INVALID_FOR_INTERVAL;
2667 [ - + ]: 762 : if (!tm->tm_mon)
6479 tgl@sss.pgh.pa.us 2668 :UBC 0 : break;
47 peter@eisentraut.org 2669 [ - + ]:GNC 762 : if (IS_SUFFIX_TM(n->suffix))
2670 : : {
3861 bruce@momjian.us 2671 :UBC 0 : char *str = str_toupper_z(localized_full_months[tm->tm_mon - 1], collid);
2672 : :
3967 noah@leadboat.com 2673 [ # # ]: 0 : if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
3971 bruce@momjian.us 2674 : 0 : strcpy(s, str);
2675 : : else
2676 [ # # ]: 0 : ereport(ERROR,
2677 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
2678 : : errmsg("localized string format value too long")));
2679 : : }
2680 : : else
47 peter@eisentraut.org 2681 [ + + ]:GNC 762 : sprintf(s, "%*s", IS_SUFFIX_FM(n->suffix) ? 0 : -9,
4585 bruce@momjian.us 2682 :CBC 762 : asc_toupper_z(months_full[tm->tm_mon - 1]));
6479 tgl@sss.pgh.pa.us 2683 : 762 : s += strlen(s);
2684 : 762 : break;
2685 : 762 : case DCH_Month:
2686 [ - + - - ]: 762 : INVALID_FOR_INTERVAL;
2687 [ - + ]: 762 : if (!tm->tm_mon)
6479 tgl@sss.pgh.pa.us 2688 :UBC 0 : break;
47 peter@eisentraut.org 2689 [ - + ]:GNC 762 : if (IS_SUFFIX_TM(n->suffix))
2690 : : {
3861 bruce@momjian.us 2691 :UBC 0 : char *str = str_initcap_z(localized_full_months[tm->tm_mon - 1], collid);
2692 : :
3967 noah@leadboat.com 2693 [ # # ]: 0 : if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
3971 bruce@momjian.us 2694 : 0 : strcpy(s, str);
2695 : : else
2696 [ # # ]: 0 : ereport(ERROR,
2697 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
2698 : : errmsg("localized string format value too long")));
2699 : : }
2700 : : else
47 peter@eisentraut.org 2701 [ + + ]:GNC 762 : sprintf(s, "%*s", IS_SUFFIX_FM(n->suffix) ? 0 : -9,
4670 tgl@sss.pgh.pa.us 2702 [ + + ]:CBC 762 : months_full[tm->tm_mon - 1]);
6479 2703 : 762 : s += strlen(s);
2704 : 762 : break;
2705 : 762 : case DCH_month:
2706 [ - + - - ]: 762 : INVALID_FOR_INTERVAL;
2707 [ - + ]: 762 : if (!tm->tm_mon)
6479 tgl@sss.pgh.pa.us 2708 :UBC 0 : break;
47 peter@eisentraut.org 2709 [ - + ]:GNC 762 : if (IS_SUFFIX_TM(n->suffix))
2710 : : {
3861 bruce@momjian.us 2711 :UBC 0 : char *str = str_tolower_z(localized_full_months[tm->tm_mon - 1], collid);
2712 : :
3967 noah@leadboat.com 2713 [ # # ]: 0 : if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
3971 bruce@momjian.us 2714 : 0 : strcpy(s, str);
2715 : : else
2716 [ # # ]: 0 : ereport(ERROR,
2717 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
2718 : : errmsg("localized string format value too long")));
2719 : : }
2720 : : else
47 peter@eisentraut.org 2721 [ + + ]:GNC 762 : sprintf(s, "%*s", IS_SUFFIX_FM(n->suffix) ? 0 : -9,
4670 tgl@sss.pgh.pa.us 2722 :CBC 762 : asc_tolower_z(months_full[tm->tm_mon - 1]));
6479 2723 : 762 : s += strlen(s);
2724 : 762 : break;
2725 : 381 : case DCH_MON:
2726 [ - + - - ]: 381 : INVALID_FOR_INTERVAL;
2727 [ - + ]: 381 : if (!tm->tm_mon)
6479 tgl@sss.pgh.pa.us 2728 :UBC 0 : break;
47 peter@eisentraut.org 2729 [ - + ]:GNC 381 : if (IS_SUFFIX_TM(n->suffix))
2730 : : {
3861 bruce@momjian.us 2731 :UBC 0 : char *str = str_toupper_z(localized_abbrev_months[tm->tm_mon - 1], collid);
2732 : :
3967 noah@leadboat.com 2733 [ # # ]: 0 : if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
3971 bruce@momjian.us 2734 : 0 : strcpy(s, str);
2735 : : else
2736 [ # # ]: 0 : ereport(ERROR,
2737 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
2738 : : errmsg("localized string format value too long")));
2739 : : }
2740 : : else
4670 tgl@sss.pgh.pa.us 2741 :CBC 381 : strcpy(s, asc_toupper_z(months[tm->tm_mon - 1]));
6479 2742 : 381 : s += strlen(s);
2743 : 381 : break;
2744 : 423 : case DCH_Mon:
2745 [ - + - - ]: 423 : INVALID_FOR_INTERVAL;
2746 [ - + ]: 423 : if (!tm->tm_mon)
6479 tgl@sss.pgh.pa.us 2747 :UBC 0 : break;
47 peter@eisentraut.org 2748 [ - + ]:GNC 423 : if (IS_SUFFIX_TM(n->suffix))
2749 : : {
3861 bruce@momjian.us 2750 :UBC 0 : char *str = str_initcap_z(localized_abbrev_months[tm->tm_mon - 1], collid);
2751 : :
3967 noah@leadboat.com 2752 [ # # ]: 0 : if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
3971 bruce@momjian.us 2753 : 0 : strcpy(s, str);
2754 : : else
2755 [ # # ]: 0 : ereport(ERROR,
2756 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
2757 : : errmsg("localized string format value too long")));
2758 : : }
2759 : : else
6479 tgl@sss.pgh.pa.us 2760 :CBC 423 : strcpy(s, months[tm->tm_mon - 1]);
2761 : 423 : s += strlen(s);
2762 : 423 : break;
2763 : 381 : case DCH_mon:
2764 [ - + - - ]: 381 : INVALID_FOR_INTERVAL;
2765 [ - + ]: 381 : if (!tm->tm_mon)
6479 tgl@sss.pgh.pa.us 2766 :UBC 0 : break;
47 peter@eisentraut.org 2767 [ - + ]:GNC 381 : if (IS_SUFFIX_TM(n->suffix))
2768 : : {
3861 bruce@momjian.us 2769 :UBC 0 : char *str = str_tolower_z(localized_abbrev_months[tm->tm_mon - 1], collid);
2770 : :
3967 noah@leadboat.com 2771 [ # # ]: 0 : if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
3971 bruce@momjian.us 2772 : 0 : strcpy(s, str);
2773 : : else
2774 [ # # ]: 0 : ereport(ERROR,
2775 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
2776 : : errmsg("localized string format value too long")));
2777 : : }
2778 : : else
4670 tgl@sss.pgh.pa.us 2779 :CBC 381 : strcpy(s, asc_tolower_z(months[tm->tm_mon - 1]));
6479 2780 : 381 : s += strlen(s);
2781 : 381 : break;
2782 : 1024 : case DCH_MM:
47 peter@eisentraut.org 2783 [ + + + - ]:GNC 1024 : sprintf(s, "%0*d", IS_SUFFIX_FM(n->suffix) ? 0 : (tm->tm_mon >= 0) ? 2 : 3,
2784 : : tm->tm_mon);
2785 [ - + ]: 1024 : if (IS_SUFFIX_THth(n->suffix))
47 peter@eisentraut.org 2786 :UNC 0 : str_numth(s, s, SUFFIX_TH_TYPE(n->suffix));
6479 tgl@sss.pgh.pa.us 2787 :CBC 1024 : s += strlen(s);
2788 : 1024 : break;
2789 : 762 : case DCH_DAY:
2790 [ - + - - ]: 762 : INVALID_FOR_INTERVAL;
47 peter@eisentraut.org 2791 [ - + ]:GNC 762 : if (IS_SUFFIX_TM(n->suffix))
2792 : : {
3861 bruce@momjian.us 2793 :UBC 0 : char *str = str_toupper_z(localized_full_days[tm->tm_wday], collid);
2794 : :
3967 noah@leadboat.com 2795 [ # # ]: 0 : if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
3971 bruce@momjian.us 2796 : 0 : strcpy(s, str);
2797 : : else
2798 [ # # ]: 0 : ereport(ERROR,
2799 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
2800 : : errmsg("localized string format value too long")));
2801 : : }
2802 : : else
47 peter@eisentraut.org 2803 [ + + ]:GNC 762 : sprintf(s, "%*s", IS_SUFFIX_FM(n->suffix) ? 0 : -9,
4670 tgl@sss.pgh.pa.us 2804 :CBC 762 : asc_toupper_z(days[tm->tm_wday]));
6479 2805 : 762 : s += strlen(s);
2806 : 762 : break;
2807 : 762 : case DCH_Day:
2808 [ - + - - ]: 762 : INVALID_FOR_INTERVAL;
47 peter@eisentraut.org 2809 [ - + ]:GNC 762 : if (IS_SUFFIX_TM(n->suffix))
2810 : : {
3861 bruce@momjian.us 2811 :UBC 0 : char *str = str_initcap_z(localized_full_days[tm->tm_wday], collid);
2812 : :
3967 noah@leadboat.com 2813 [ # # ]: 0 : if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
3971 bruce@momjian.us 2814 : 0 : strcpy(s, str);
2815 : : else
2816 [ # # ]: 0 : ereport(ERROR,
2817 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
2818 : : errmsg("localized string format value too long")));
2819 : : }
2820 : : else
47 peter@eisentraut.org 2821 [ + + ]:GNC 762 : sprintf(s, "%*s", IS_SUFFIX_FM(n->suffix) ? 0 : -9,
4670 tgl@sss.pgh.pa.us 2822 [ + + ]:CBC 762 : days[tm->tm_wday]);
6479 2823 : 762 : s += strlen(s);
2824 : 762 : break;
2825 : 762 : case DCH_day:
2826 [ - + - - ]: 762 : INVALID_FOR_INTERVAL;
47 peter@eisentraut.org 2827 [ - + ]:GNC 762 : if (IS_SUFFIX_TM(n->suffix))
2828 : : {
3861 bruce@momjian.us 2829 :UBC 0 : char *str = str_tolower_z(localized_full_days[tm->tm_wday], collid);
2830 : :
3967 noah@leadboat.com 2831 [ # # ]: 0 : if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
3971 bruce@momjian.us 2832 : 0 : strcpy(s, str);
2833 : : else
2834 [ # # ]: 0 : ereport(ERROR,
2835 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
2836 : : errmsg("localized string format value too long")));
2837 : : }
2838 : : else
47 peter@eisentraut.org 2839 [ + + ]:GNC 762 : sprintf(s, "%*s", IS_SUFFIX_FM(n->suffix) ? 0 : -9,
4670 tgl@sss.pgh.pa.us 2840 :CBC 762 : asc_tolower_z(days[tm->tm_wday]));
6479 2841 : 762 : s += strlen(s);
2842 : 762 : break;
2843 : 381 : case DCH_DY:
2844 [ - + - - ]: 381 : INVALID_FOR_INTERVAL;
47 peter@eisentraut.org 2845 [ - + ]:GNC 381 : if (IS_SUFFIX_TM(n->suffix))
2846 : : {
3861 bruce@momjian.us 2847 :UBC 0 : char *str = str_toupper_z(localized_abbrev_days[tm->tm_wday], collid);
2848 : :
3967 noah@leadboat.com 2849 [ # # ]: 0 : if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
3971 bruce@momjian.us 2850 : 0 : strcpy(s, str);
2851 : : else
2852 [ # # ]: 0 : ereport(ERROR,
2853 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
2854 : : errmsg("localized string format value too long")));
2855 : : }
2856 : : else
4670 tgl@sss.pgh.pa.us 2857 :CBC 381 : strcpy(s, asc_toupper_z(days_short[tm->tm_wday]));
6479 2858 : 381 : s += strlen(s);
2859 : 381 : break;
2860 : 381 : case DCH_Dy:
2861 [ - + - - ]: 381 : INVALID_FOR_INTERVAL;
47 peter@eisentraut.org 2862 [ - + ]:GNC 381 : if (IS_SUFFIX_TM(n->suffix))
2863 : : {
3861 bruce@momjian.us 2864 :UBC 0 : char *str = str_initcap_z(localized_abbrev_days[tm->tm_wday], collid);
2865 : :
3967 noah@leadboat.com 2866 [ # # ]: 0 : if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
3971 bruce@momjian.us 2867 : 0 : strcpy(s, str);
2868 : : else
2869 [ # # ]: 0 : ereport(ERROR,
2870 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
2871 : : errmsg("localized string format value too long")));
2872 : : }
2873 : : else
6479 tgl@sss.pgh.pa.us 2874 :CBC 381 : strcpy(s, days_short[tm->tm_wday]);
2875 : 381 : s += strlen(s);
2876 : 381 : break;
2877 : 381 : case DCH_dy:
2878 [ - + - - ]: 381 : INVALID_FOR_INTERVAL;
47 peter@eisentraut.org 2879 [ - + ]:GNC 381 : if (IS_SUFFIX_TM(n->suffix))
2880 : : {
3861 bruce@momjian.us 2881 :UBC 0 : char *str = str_tolower_z(localized_abbrev_days[tm->tm_wday], collid);
2882 : :
3967 noah@leadboat.com 2883 [ # # ]: 0 : if (strlen(str) <= (n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
3971 bruce@momjian.us 2884 : 0 : strcpy(s, str);
2885 : : else
2886 [ # # ]: 0 : ereport(ERROR,
2887 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
2888 : : errmsg("localized string format value too long")));
2889 : : }
2890 : : else
4670 tgl@sss.pgh.pa.us 2891 :CBC 381 : strcpy(s, asc_tolower_z(days_short[tm->tm_wday]));
6479 2892 : 381 : s += strlen(s);
2893 : 381 : break;
2894 : 1524 : case DCH_DDD:
2895 : : case DCH_IDDD:
47 peter@eisentraut.org 2896 [ + + ]:GNC 1524 : sprintf(s, "%0*d", IS_SUFFIX_FM(n->suffix) ? 0 : 3,
6479 tgl@sss.pgh.pa.us 2897 [ + + ]:CBC 1524 : (n->key->id == DCH_DDD) ?
2898 : : tm->tm_yday :
3101 2899 : 762 : date2isoyearday(tm->tm_year, tm->tm_mon, tm->tm_mday));
47 peter@eisentraut.org 2900 [ - + ]:GNC 1524 : if (IS_SUFFIX_THth(n->suffix))
47 peter@eisentraut.org 2901 :UNC 0 : str_numth(s, s, SUFFIX_TH_TYPE(n->suffix));
6479 tgl@sss.pgh.pa.us 2902 :CBC 1524 : s += strlen(s);
2903 : 1524 : break;
2904 : 780 : case DCH_DD:
47 peter@eisentraut.org 2905 [ + + ]:GNC 780 : sprintf(s, "%0*d", IS_SUFFIX_FM(n->suffix) ? 0 : 2, tm->tm_mday);
2906 [ - + ]: 780 : if (IS_SUFFIX_THth(n->suffix))
47 peter@eisentraut.org 2907 :UNC 0 : str_numth(s, s, SUFFIX_TH_TYPE(n->suffix));
6479 tgl@sss.pgh.pa.us 2908 :CBC 780 : s += strlen(s);
2909 : 780 : break;
2910 : 762 : case DCH_D:
2911 [ - + - - ]: 762 : INVALID_FOR_INTERVAL;
2912 : 762 : sprintf(s, "%d", tm->tm_wday + 1);
47 peter@eisentraut.org 2913 [ - + ]:GNC 762 : if (IS_SUFFIX_THth(n->suffix))
47 peter@eisentraut.org 2914 :UNC 0 : str_numth(s, s, SUFFIX_TH_TYPE(n->suffix));
6479 tgl@sss.pgh.pa.us 2915 :CBC 762 : s += strlen(s);
2916 : 762 : break;
2917 : 762 : case DCH_ID:
2918 [ - + - - ]: 762 : INVALID_FOR_INTERVAL;
2919 [ + + ]: 762 : sprintf(s, "%d", (tm->tm_wday == 0) ? 7 : tm->tm_wday);
47 peter@eisentraut.org 2920 [ - + ]:GNC 762 : if (IS_SUFFIX_THth(n->suffix))
47 peter@eisentraut.org 2921 :UNC 0 : str_numth(s, s, SUFFIX_TH_TYPE(n->suffix));
6479 tgl@sss.pgh.pa.us 2922 :CBC 762 : s += strlen(s);
2923 : 762 : break;
2924 : 762 : case DCH_WW:
47 peter@eisentraut.org 2925 [ + + ]:GNC 762 : sprintf(s, "%0*d", IS_SUFFIX_FM(n->suffix) ? 0 : 2,
6479 tgl@sss.pgh.pa.us 2926 [ + + ]:CBC 762 : (tm->tm_yday - 1) / 7 + 1);
47 peter@eisentraut.org 2927 [ - + ]:GNC 762 : if (IS_SUFFIX_THth(n->suffix))
47 peter@eisentraut.org 2928 :UNC 0 : str_numth(s, s, SUFFIX_TH_TYPE(n->suffix));
6479 tgl@sss.pgh.pa.us 2929 :CBC 762 : s += strlen(s);
2930 : 762 : break;
2931 : 762 : case DCH_IW:
47 peter@eisentraut.org 2932 [ + + ]:GNC 762 : sprintf(s, "%0*d", IS_SUFFIX_FM(n->suffix) ? 0 : 2,
2933 : : date2isoweek(tm->tm_year, tm->tm_mon, tm->tm_mday));
2934 [ - + ]: 762 : if (IS_SUFFIX_THth(n->suffix))
47 peter@eisentraut.org 2935 :UNC 0 : str_numth(s, s, SUFFIX_TH_TYPE(n->suffix));
6479 tgl@sss.pgh.pa.us 2936 :CBC 762 : s += strlen(s);
2937 : 762 : break;
2938 : 762 : case DCH_Q:
2939 [ - + ]: 762 : if (!tm->tm_mon)
6479 tgl@sss.pgh.pa.us 2940 :UBC 0 : break;
6479 tgl@sss.pgh.pa.us 2941 :CBC 762 : sprintf(s, "%d", (tm->tm_mon - 1) / 3 + 1);
47 peter@eisentraut.org 2942 [ - + ]:GNC 762 : if (IS_SUFFIX_THth(n->suffix))
47 peter@eisentraut.org 2943 :UNC 0 : str_numth(s, s, SUFFIX_TH_TYPE(n->suffix));
6479 tgl@sss.pgh.pa.us 2944 :CBC 762 : s += strlen(s);
2945 : 762 : break;
2946 : 762 : case DCH_CC:
6033 bruce@momjian.us 2947 [ - + ]: 762 : if (is_interval) /* straight calculation */
6479 tgl@sss.pgh.pa.us 2948 :UBC 0 : i = tm->tm_year / 100;
2949 : : else
2950 : : {
4880 bruce@momjian.us 2951 [ + + ]:CBC 762 : if (tm->tm_year > 0)
2952 : : /* Century 20 == 1901 - 2000 */
2953 : 750 : i = (tm->tm_year - 1) / 100 + 1;
2954 : : else
2955 : : /* Century 6BC == 600BC - 501BC */
2956 : 12 : i = tm->tm_year / 100 - 1;
2957 : : }
6479 tgl@sss.pgh.pa.us 2958 [ + - + - ]: 762 : if (i <= 99 && i >= -99)
47 peter@eisentraut.org 2959 [ + + + + ]:GNC 762 : sprintf(s, "%0*d", IS_SUFFIX_FM(n->suffix) ? 0 : (i >= 0) ? 2 : 3, i);
2960 : : else
6479 tgl@sss.pgh.pa.us 2961 :UBC 0 : sprintf(s, "%d", i);
47 peter@eisentraut.org 2962 [ - + ]:GNC 762 : if (IS_SUFFIX_THth(n->suffix))
47 peter@eisentraut.org 2963 :UNC 0 : str_numth(s, s, SUFFIX_TH_TYPE(n->suffix));
6479 tgl@sss.pgh.pa.us 2964 :CBC 762 : s += strlen(s);
2965 : 762 : break;
2966 : 762 : case DCH_Y_YYY:
2967 [ - + + + ]: 762 : i = ADJUST_YEAR(tm->tm_year, is_interval) / 1000;
2968 : 762 : sprintf(s, "%d,%03d", i,
2969 [ - + + + ]: 762 : ADJUST_YEAR(tm->tm_year, is_interval) - (i * 1000));
47 peter@eisentraut.org 2970 [ - + ]:GNC 762 : if (IS_SUFFIX_THth(n->suffix))
47 peter@eisentraut.org 2971 :UNC 0 : str_numth(s, s, SUFFIX_TH_TYPE(n->suffix));
6479 tgl@sss.pgh.pa.us 2972 :CBC 762 : s += strlen(s);
2973 : 762 : break;
2974 : 3691 : case DCH_YYYY:
2975 : : case DCH_IYYY:
5733 2976 [ + + ]: 10311 : sprintf(s, "%0*d",
47 peter@eisentraut.org 2977 :GNC 3691 : IS_SUFFIX_FM(n->suffix) ? 0 :
3726 bruce@momjian.us 2978 [ - + + + :CBC 2929 : (ADJUST_YEAR(tm->tm_year, is_interval) >= 0) ? 4 : 5,
+ - ]
5733 tgl@sss.pgh.pa.us 2979 [ + + ]: 3691 : (n->key->id == DCH_YYYY ?
2980 [ - + + + ]: 2929 : ADJUST_YEAR(tm->tm_year, is_interval) :
2981 [ - + + + ]: 762 : ADJUST_YEAR(date2isoyear(tm->tm_year,
2982 : : tm->tm_mon,
2983 : : tm->tm_mday),
2984 : : is_interval)));
47 peter@eisentraut.org 2985 [ + + ]:GNC 3691 : if (IS_SUFFIX_THth(n->suffix))
2986 : 762 : str_numth(s, s, SUFFIX_TH_TYPE(n->suffix));
6479 tgl@sss.pgh.pa.us 2987 :CBC 3691 : s += strlen(s);
2988 : 3691 : break;
2989 : 1524 : case DCH_YYY:
2990 : : case DCH_IYY:
5733 2991 [ + + ]: 3810 : sprintf(s, "%0*d",
47 peter@eisentraut.org 2992 :GNC 1524 : IS_SUFFIX_FM(n->suffix) ? 0 :
3726 bruce@momjian.us 2993 [ - + + + :CBC 762 : (ADJUST_YEAR(tm->tm_year, is_interval) >= 0) ? 3 : 4,
+ - ]
5733 tgl@sss.pgh.pa.us 2994 [ + + ]: 1524 : (n->key->id == DCH_YYY ?
6479 2995 : 1524 : ADJUST_YEAR(tm->tm_year, is_interval) :
2996 : 1524 : ADJUST_YEAR(date2isoyear(tm->tm_year,
2997 : : tm->tm_mon,
2998 : : tm->tm_mday),
5733 2999 [ - + + + : 3048 : is_interval)) % 1000);
- + + + ]
47 peter@eisentraut.org 3000 [ - + ]:GNC 1524 : if (IS_SUFFIX_THth(n->suffix))
47 peter@eisentraut.org 3001 :UNC 0 : str_numth(s, s, SUFFIX_TH_TYPE(n->suffix));
6479 tgl@sss.pgh.pa.us 3002 :CBC 1524 : s += strlen(s);
3003 : 1524 : break;
3004 : 1524 : case DCH_YY:
3005 : : case DCH_IY:
5733 3006 [ + + ]: 3810 : sprintf(s, "%0*d",
47 peter@eisentraut.org 3007 :GNC 1524 : IS_SUFFIX_FM(n->suffix) ? 0 :
3726 bruce@momjian.us 3008 [ - + + + :CBC 762 : (ADJUST_YEAR(tm->tm_year, is_interval) >= 0) ? 2 : 3,
+ - ]
5733 tgl@sss.pgh.pa.us 3009 [ + + ]: 1524 : (n->key->id == DCH_YY ?
6479 3010 : 1524 : ADJUST_YEAR(tm->tm_year, is_interval) :
3011 : 1524 : ADJUST_YEAR(date2isoyear(tm->tm_year,
3012 : : tm->tm_mon,
3013 : : tm->tm_mday),
5733 3014 [ - + + + : 3048 : is_interval)) % 100);
- + + + ]
47 peter@eisentraut.org 3015 [ - + ]:GNC 1524 : if (IS_SUFFIX_THth(n->suffix))
47 peter@eisentraut.org 3016 :UNC 0 : str_numth(s, s, SUFFIX_TH_TYPE(n->suffix));
6479 tgl@sss.pgh.pa.us 3017 :CBC 1524 : s += strlen(s);
3018 : 1524 : break;
3019 : 1524 : case DCH_Y:
3020 : : case DCH_I:
5733 3021 : 1524 : sprintf(s, "%1d",
3022 [ + + ]: 1524 : (n->key->id == DCH_Y ?
6479 3023 : 1524 : ADJUST_YEAR(tm->tm_year, is_interval) :
3024 : 1524 : ADJUST_YEAR(date2isoyear(tm->tm_year,
3025 : : tm->tm_mon,
3026 : : tm->tm_mday),
5733 3027 [ - + + + : 3048 : is_interval)) % 10);
- + + + ]
47 peter@eisentraut.org 3028 [ - + ]:GNC 1524 : if (IS_SUFFIX_THth(n->suffix))
47 peter@eisentraut.org 3029 :UNC 0 : str_numth(s, s, SUFFIX_TH_TYPE(n->suffix));
6479 tgl@sss.pgh.pa.us 3030 :CBC 1524 : s += strlen(s);
3031 : 1524 : break;
3032 : 924 : case DCH_RM:
3033 : : /* FALLTHROUGH */
3034 : : case DCH_rm:
3035 : :
3036 : : /*
3037 : : * For intervals, values like '12 month' will be reduced to 0
3038 : : * month and some years. These should be processed.
3039 : : */
1710 michael@paquier.xyz 3040 [ + + + + ]: 924 : if (!tm->tm_mon && !tm->tm_year)
3041 : : break;
3042 : : else
3043 : : {
3044 : 918 : int mon = 0;
3045 : : const char *const *months;
3046 : :
3047 [ + + ]: 918 : if (n->key->id == DCH_RM)
3048 : 840 : months = rm_months_upper;
3049 : : else
3050 : 78 : months = rm_months_lower;
3051 : :
3052 : : /*
3053 : : * Compute the position in the roman-numeral array. Note
3054 : : * that the contents of the array are reversed, December
3055 : : * being first and January last.
3056 : : */
3057 [ + + ]: 918 : if (tm->tm_mon == 0)
3058 : : {
3059 : : /*
3060 : : * This case is special, and tracks the case of full
3061 : : * interval years.
3062 : : */
3063 [ + + ]: 12 : mon = tm->tm_year >= 0 ? 0 : MONTHS_PER_YEAR - 1;
3064 : : }
3065 [ + + ]: 906 : else if (tm->tm_mon < 0)
3066 : : {
3067 : : /*
3068 : : * Negative case. In this case, the calculation is
3069 : : * reversed, where -1 means December, -2 November,
3070 : : * etc.
3071 : : */
3072 : 72 : mon = -1 * (tm->tm_mon + 1);
3073 : : }
3074 : : else
3075 : : {
3076 : : /*
3077 : : * Common case, with a strictly positive value. The
3078 : : * position in the array matches with the value of
3079 : : * tm_mon.
3080 : : */
3081 : 834 : mon = MONTHS_PER_YEAR - tm->tm_mon;
3082 : : }
3083 : :
47 peter@eisentraut.org 3084 [ + + ]:GNC 918 : sprintf(s, "%*s", IS_SUFFIX_FM(n->suffix) ? 0 : -4,
1710 michael@paquier.xyz 3085 [ + + ]:CBC 918 : months[mon]);
3086 : 918 : s += strlen(s);
3087 : : }
6479 tgl@sss.pgh.pa.us 3088 : 918 : break;
6479 tgl@sss.pgh.pa.us 3089 :UBC 0 : case DCH_W:
3090 : 0 : sprintf(s, "%d", (tm->tm_mday - 1) / 7 + 1);
47 peter@eisentraut.org 3091 [ # # ]:UNC 0 : if (IS_SUFFIX_THth(n->suffix))
3092 : 0 : str_numth(s, s, SUFFIX_TH_TYPE(n->suffix));
6479 tgl@sss.pgh.pa.us 3093 :UBC 0 : s += strlen(s);
3094 : 0 : break;
6479 tgl@sss.pgh.pa.us 3095 :CBC 1143 : case DCH_J:
3096 : 1143 : sprintf(s, "%d", date2j(tm->tm_year, tm->tm_mon, tm->tm_mday));
47 peter@eisentraut.org 3097 [ + + ]:GNC 1143 : if (IS_SUFFIX_THth(n->suffix))
3098 : 381 : str_numth(s, s, SUFFIX_TH_TYPE(n->suffix));
6479 tgl@sss.pgh.pa.us 3099 :CBC 1143 : s += strlen(s);
3100 : 1143 : break;
3101 : : }
3102 : : }
3103 : :
3104 : 4793 : *s = '\0';
3105 : 4793 : }
3106 : :
3107 : : /*
3108 : : * Process the string 'in' as denoted by the array of FormatNodes 'node[]'.
3109 : : * The TmFromChar struct pointed to by 'out' is populated with the results.
3110 : : *
3111 : : * 'collid' identifies the collation to use, if needed.
3112 : : * 'std' specifies standard parsing mode.
3113 : : *
3114 : : * If escontext points to an ErrorSaveContext, data errors will be reported
3115 : : * by filling that struct; the caller must test SOFT_ERROR_OCCURRED() to see
3116 : : * whether an error occurred. Otherwise, errors are thrown.
3117 : : *
3118 : : * Note: we currently don't have any to_interval() function, so there
3119 : : * is no need here for INVALID_FOR_INTERVAL checks.
3120 : : */
3121 : : static void
2115 3122 : 20224 : DCH_from_char(FormatNode *node, const char *in, TmFromChar *out,
3123 : : Oid collid, bool std, Node *escontext)
3124 : : {
3125 : : FormatNode *n;
3126 : : const char *s;
3127 : : int len,
3128 : : value;
2275 akorotkov@postgresql 3129 : 20224 : bool fx_mode = std;
3130 : :
3131 : : /* number of extra skipped characters (more than given in format string) */
2656 3132 : 20224 : int extra_skip = 0;
3133 : :
3134 : : /* cache localized days and months */
2115 tgl@sss.pgh.pa.us 3135 : 20224 : cache_locale_time();
3136 : :
6479 3137 [ + + + + ]: 121600 : for (n = node, s = in; n->type != NODE_TYPE_END && *s != '\0'; n++)
3138 : : {
3139 : : /*
3140 : : * Ignore spaces at the beginning of the string and before fields when
3141 : : * not in FX (fixed width) mode.
3142 : : */
2656 akorotkov@postgresql 3143 [ + + + + : 112087 : if (!fx_mode && (n->type != NODE_TYPE_ACTION || n->key->id != DCH_FX) &&
+ + ]
3144 [ + + + + ]: 4678 : (n->type == NODE_TYPE_ACTION || n == node))
3145 : : {
3146 [ + - + + ]: 2641 : while (*s != '\0' && isspace((unsigned char) *s))
3147 : : {
3148 : 42 : s++;
3149 : 42 : extra_skip++;
3150 : : }
3151 : : }
3152 : :
3153 [ + + + + ]: 112087 : if (n->type == NODE_TYPE_SPACE || n->type == NODE_TYPE_SEPARATOR)
3154 : : {
2275 3155 [ + + ]: 48615 : if (std)
3156 : : {
3157 : : /*
3158 : : * Standard mode requires strict matching between format
3159 : : * string separators/spaces and input string.
3160 : : */
3161 [ + - - + ]: 46740 : Assert(n->character[0] && !n->character[1]);
3162 : :
3163 [ + + ]: 46740 : if (*s == n->character[0])
3164 : 37830 : s++;
3165 : : else
1104 tgl@sss.pgh.pa.us 3166 [ - + ]: 15546 : ereturn(escontext,,
3167 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
3168 : : errmsg("unmatched format separator \"%c\"",
3169 : : n->character[0])));
3170 : : }
2275 akorotkov@postgresql 3171 [ + + ]: 1875 : else if (!fx_mode)
3172 : : {
3173 : : /*
3174 : : * In non FX (fixed format) mode one format string space or
3175 : : * separator match to one space or separator in input string.
3176 : : * Or match nothing if there is no space or separator in the
3177 : : * current position of input string.
3178 : : */
2656 3179 : 1863 : extra_skip--;
3180 [ + + + + ]: 1863 : if (isspace((unsigned char) *s) || is_separator_char(s))
3181 : : {
3182 : 1329 : s++;
3183 : 1329 : extra_skip++;
3184 : : }
3185 : : }
3186 : : else
3187 : : {
3188 : : /*
3189 : : * In FX mode, on format string space or separator we consume
3190 : : * exactly one character from input string. Notice we don't
3191 : : * insist that the consumed character match the format's
3192 : : * character.
3193 : : */
3194 : 12 : s += pg_mblen(s);
3195 : : }
3196 : 39705 : continue;
3197 : : }
3198 [ + + ]: 63472 : else if (n->type != NODE_TYPE_ACTION)
3199 : : {
3200 : : /*
3201 : : * Text character, so consume one character from input string.
3202 : : * Notice we don't insist that the consumed character match the
3203 : : * format's character.
3204 : : */
2645 3205 [ + + ]: 1593 : if (!fx_mode)
3206 : : {
3207 : : /*
3208 : : * In non FX mode we might have skipped some extra characters
3209 : : * (more than specified in format string) before. In this
3210 : : * case we don't skip input string character, because it might
3211 : : * be part of field.
3212 : : */
3213 [ + + ]: 219 : if (extra_skip > 0)
3214 : 12 : extra_skip--;
3215 : : else
3216 : 207 : s += pg_mblen(s);
3217 : : }
3218 : : else
3219 : : {
1905 3220 : 1374 : int chlen = pg_mblen(s);
3221 : :
3222 : : /*
3223 : : * Standard mode requires strict match of format characters.
3224 : : */
3225 [ + - + - ]: 1374 : if (std && n->type == NODE_TYPE_CHAR &&
3226 [ + + ]: 1374 : strncmp(s, n->character, chlen) != 0)
1104 tgl@sss.pgh.pa.us 3227 [ + + ]: 1356 : ereturn(escontext,,
3228 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
3229 : : errmsg("unmatched format character \"%s\"",
3230 : : n->character)));
3231 : :
1905 akorotkov@postgresql 3232 : 18 : s += chlen;
3233 : : }
6479 tgl@sss.pgh.pa.us 3234 : 237 : continue;
3235 : : }
3236 : :
1104 3237 [ - + ]: 61879 : if (!from_char_set_mode(out, n->key->date_mode, escontext))
1104 tgl@sss.pgh.pa.us 3238 :UBC 0 : return;
3239 : :
6479 tgl@sss.pgh.pa.us 3240 [ + + + + :CBC 61876 : switch (n->key->id)
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + +
- ]
3241 : : {
3242 : 6 : case DCH_FX:
3243 : 6 : fx_mode = true;
3244 : 6 : break;
3245 : 6 : case DCH_A_M:
3246 : : case DCH_P_M:
3247 : : case DCH_a_m:
3248 : : case DCH_p_m:
1104 3249 [ - + ]: 6 : if (!from_char_seq_search(&value, &s, ampm_strings_long,
3250 : : NULL, InvalidOid,
3251 : : n, escontext))
1104 tgl@sss.pgh.pa.us 3252 :UBC 0 : return;
1104 tgl@sss.pgh.pa.us 3253 [ - + ]:CBC 6 : if (!from_char_set_int(&out->pm, value % 2, n, escontext))
1104 tgl@sss.pgh.pa.us 3254 :UBC 0 : return;
47 peter@eisentraut.org 3255 :GNC 6 : out->clock_12_hour = true;
6479 tgl@sss.pgh.pa.us 3256 :CBC 6 : break;
6157 bruce@momjian.us 3257 : 6 : case DCH_AM:
3258 : : case DCH_PM:
3259 : : case DCH_am:
3260 : : case DCH_pm:
1104 tgl@sss.pgh.pa.us 3261 [ - + ]: 6 : if (!from_char_seq_search(&value, &s, ampm_strings,
3262 : : NULL, InvalidOid,
3263 : : n, escontext))
1104 tgl@sss.pgh.pa.us 3264 :UBC 0 : return;
1104 tgl@sss.pgh.pa.us 3265 [ - + ]:CBC 6 : if (!from_char_set_int(&out->pm, value % 2, n, escontext))
1104 tgl@sss.pgh.pa.us 3266 :UBC 0 : return;
47 peter@eisentraut.org 3267 :GNC 6 : out->clock_12_hour = true;
6479 tgl@sss.pgh.pa.us 3268 :CBC 6 : break;
3269 : 72 : case DCH_HH:
3270 : : case DCH_HH12:
1104 3271 [ - + ]: 72 : if (from_char_parse_int_len(&out->hh, &s, 2, n, escontext) < 0)
1104 tgl@sss.pgh.pa.us 3272 :UBC 0 : return;
47 peter@eisentraut.org 3273 :GNC 72 : out->clock_12_hour = true;
3512 tgl@sss.pgh.pa.us 3274 [ - + - - :CBC 72 : SKIP_THth(s, n->suffix);
- - - - ]
6157 bruce@momjian.us 3275 : 72 : break;
6479 tgl@sss.pgh.pa.us 3276 : 14841 : case DCH_HH24:
1104 3277 [ + + ]: 14841 : if (from_char_parse_int_len(&out->hh, &s, 2, n, escontext) < 0)
3278 : 72 : return;
3512 3279 [ - + - - : 14766 : SKIP_THth(s, n->suffix);
- - - - ]
6479 3280 : 14766 : break;
3281 : 8610 : case DCH_MI:
1104 3282 [ - + ]: 8610 : if (from_char_parse_int(&out->mi, &s, n, escontext) < 0)
1104 tgl@sss.pgh.pa.us 3283 :UBC 0 : return;
3512 tgl@sss.pgh.pa.us 3284 [ - + - - :CBC 8610 : SKIP_THth(s, n->suffix);
- - - - ]
6479 3285 : 8610 : break;
3286 : 7917 : case DCH_SS:
1104 3287 [ - + ]: 7917 : if (from_char_parse_int(&out->ss, &s, n, escontext) < 0)
1104 tgl@sss.pgh.pa.us 3288 :UBC 0 : return;
3512 tgl@sss.pgh.pa.us 3289 [ - + - - :CBC 7917 : SKIP_THth(s, n->suffix);
- - - - ]
6479 3290 : 7917 : break;
6033 bruce@momjian.us 3291 : 6 : case DCH_MS: /* millisecond */
1104 tgl@sss.pgh.pa.us 3292 : 6 : len = from_char_parse_int_len(&out->ms, &s, 3, n, escontext);
3293 [ - + ]: 6 : if (len < 0)
1104 tgl@sss.pgh.pa.us 3294 :UBC 0 : return;
3295 : :
3296 : : /*
3297 : : * 25 is 0.25 and 250 is 0.25 too; 025 is 0.025 and not 0.25
3298 : : */
6306 tgl@sss.pgh.pa.us 3299 [ + - ]:CBC 12 : out->ms *= len == 1 ? 100 :
3300 [ - + ]: 6 : len == 2 ? 10 : 1;
3301 : :
3512 3302 [ - + - - : 6 : SKIP_THth(s, n->suffix);
- - - - ]
6479 3303 : 6 : break;
2284 akorotkov@postgresql 3304 : 129 : case DCH_FF1:
3305 : : case DCH_FF2:
3306 : : case DCH_FF3:
3307 : : case DCH_FF4:
3308 : : case DCH_FF5:
3309 : : case DCH_FF6:
3310 : 129 : out->ff = n->key->id - DCH_FF1 + 1;
3311 : : /* FALLTHROUGH */
6033 bruce@momjian.us 3312 : 666 : case DCH_US: /* microsecond */
2284 akorotkov@postgresql 3313 : 666 : len = from_char_parse_int_len(&out->us, &s,
3314 [ + + ]: 666 : n->key->id == DCH_US ? 6 :
1104 tgl@sss.pgh.pa.us 3315 :GIC 129 : out->ff, n, escontext);
1104 tgl@sss.pgh.pa.us 3316 [ - + ]:CBC 666 : if (len < 0)
1104 tgl@sss.pgh.pa.us 3317 :UBC 0 : return;
3318 : :
6306 tgl@sss.pgh.pa.us 3319 [ + + ]:CBC 1293 : out->us *= len == 1 ? 100000 :
3320 [ + + ]: 1236 : len == 2 ? 10000 :
3321 [ + + ]: 762 : len == 3 ? 1000 :
3322 [ + + ]: 228 : len == 4 ? 100 :
3323 [ + + ]: 75 : len == 5 ? 10 : 1;
3324 : :
3512 3325 [ - + - - : 666 : SKIP_THth(s, n->suffix);
- - - - ]
6479 3326 : 666 : break;
3327 : 12 : case DCH_SSSS:
1104 3328 [ - + ]: 12 : if (from_char_parse_int(&out->ssss, &s, n, escontext) < 0)
1104 tgl@sss.pgh.pa.us 3329 :UBC 0 : return;
3512 tgl@sss.pgh.pa.us 3330 [ - + - - :CBC 12 : SKIP_THth(s, n->suffix);
- - - - ]
6479 3331 : 12 : break;
3332 : 1779 : case DCH_tz:
3333 : : case DCH_TZ:
3334 : : {
3335 : : int tzlen;
3336 : :
692 3337 : 1779 : tzlen = DecodeTimezoneAbbrevPrefix(s,
3338 : : &out->gmtoffset,
3339 : : &out->tzp);
3340 [ + + ]: 1779 : if (tzlen > 0)
3341 : : {
3342 : 18 : out->has_tz = true;
3343 : : /* we only need the zone abbrev for DYNTZ case */
3344 [ + + ]: 18 : if (out->tzp)
3345 : 3 : out->abbrev = pnstrdup(s, tzlen);
3346 : 18 : out->tzsign = 0; /* drop any earlier TZH/TZM info */
3347 : 18 : s += tzlen;
3348 : 18 : break;
3349 : : }
3350 [ + + ]: 1761 : else if (isalpha((unsigned char) *s))
3351 : : {
3352 : : /*
3353 : : * It doesn't match any abbreviation, but it starts
3354 : : * with a letter. OF format certainly won't succeed;
3355 : : * assume it's a misspelled abbreviation and complain
3356 : : * accordingly.
3357 : : */
3358 [ + - ]: 3 : ereturn(escontext,,
3359 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
3360 : : errmsg("invalid value \"%s\" for \"%s\"", s, n->key->name),
3361 : : errdetail("Time zone abbreviation is not recognized.")));
3362 : : }
3363 : : /* otherwise parse it like OF */
3364 : : }
3365 : : /* FALLTHROUGH */
3366 : : case DCH_OF:
3367 : : /* OF is equivalent to TZH or TZH:TZM */
3368 : : /* see TZH comments below */
3369 [ + + + + : 1770 : if (*s == '+' || *s == '-' || *s == ' ')
+ + ]
3370 : : {
3371 [ + + ]: 1590 : out->tzsign = *s == '-' ? -1 : +1;
3372 : 1590 : s++;
3373 : : }
3374 : : else
3375 : : {
3376 [ + + + + ]: 180 : if (extra_skip > 0 && *(s - 1) == '-')
3377 : 6 : out->tzsign = -1;
3378 : : else
3379 : 174 : out->tzsign = +1;
3380 : : }
3381 [ + + ]: 1770 : if (from_char_parse_int_len(&out->tzh, &s, 2, n, escontext) < 0)
3382 : 159 : return;
3383 [ + + ]: 1605 : if (*s == ':')
3384 : : {
3385 : 165 : s++;
3386 [ - + ]: 165 : if (from_char_parse_int_len(&out->tzm, &s, 2, n,
3387 : : escontext) < 0)
692 tgl@sss.pgh.pa.us 3388 :UBC 0 : return;
3389 : : }
3222 tgl@sss.pgh.pa.us 3390 :CBC 1602 : break;
2899 andrew@dunslane.net 3391 : 387 : case DCH_TZH:
3392 : :
3393 : : /*
3394 : : * Value of TZH might be negative. And the issue is that we
3395 : : * might swallow minus sign as the separator. So, if we have
3396 : : * skipped more characters than specified in the format
3397 : : * string, then we consider prepending last skipped minus to
3398 : : * TZH.
3399 : : */
3400 [ + + + + : 387 : if (*s == '+' || *s == '-' || *s == ' ')
- + ]
3401 : : {
2656 akorotkov@postgresql 3402 [ + + ]: 369 : out->tzsign = *s == '-' ? -1 : +1;
2899 andrew@dunslane.net 3403 : 369 : s++;
3404 : : }
3405 : : else
3406 : : {
2656 akorotkov@postgresql 3407 [ + + + + ]: 18 : if (extra_skip > 0 && *(s - 1) == '-')
3408 : 9 : out->tzsign = -1;
3409 : : else
3410 : 9 : out->tzsign = +1;
3411 : : }
3412 : :
1104 tgl@sss.pgh.pa.us 3413 [ - + ]: 387 : if (from_char_parse_int_len(&out->tzh, &s, 2, n, escontext) < 0)
1104 tgl@sss.pgh.pa.us 3414 :UBC 0 : return;
2899 andrew@dunslane.net 3415 :CBC 387 : break;
3416 : 39 : case DCH_TZM:
3417 : : /* assign positive timezone sign if TZH was not seen before */
3418 [ + + ]: 39 : if (!out->tzsign)
3419 : 3 : out->tzsign = +1;
1104 tgl@sss.pgh.pa.us 3420 [ - + ]: 39 : if (from_char_parse_int_len(&out->tzm, &s, 2, n, escontext) < 0)
1104 tgl@sss.pgh.pa.us 3421 :UBC 0 : return;
2899 andrew@dunslane.net 3422 :CBC 39 : break;
6479 tgl@sss.pgh.pa.us 3423 : 6 : case DCH_A_D:
3424 : : case DCH_B_C:
3425 : : case DCH_a_d:
3426 : : case DCH_b_c:
1104 3427 [ - + ]: 6 : if (!from_char_seq_search(&value, &s, adbc_strings_long,
3428 : : NULL, InvalidOid,
3429 : : n, escontext))
1104 tgl@sss.pgh.pa.us 3430 :UBC 0 : return;
1104 tgl@sss.pgh.pa.us 3431 [ - + ]:CBC 6 : if (!from_char_set_int(&out->bc, value % 2, n, escontext))
1104 tgl@sss.pgh.pa.us 3432 :UBC 0 : return;
6479 tgl@sss.pgh.pa.us 3433 :CBC 6 : break;
6157 bruce@momjian.us 3434 : 18 : case DCH_AD:
3435 : : case DCH_BC:
3436 : : case DCH_ad:
3437 : : case DCH_bc:
1104 tgl@sss.pgh.pa.us 3438 [ - + ]: 18 : if (!from_char_seq_search(&value, &s, adbc_strings,
3439 : : NULL, InvalidOid,
3440 : : n, escontext))
1104 tgl@sss.pgh.pa.us 3441 :UBC 0 : return;
1104 tgl@sss.pgh.pa.us 3442 [ - + ]:CBC 18 : if (!from_char_set_int(&out->bc, value % 2, n, escontext))
1104 tgl@sss.pgh.pa.us 3443 :UBC 0 : return;
6479 tgl@sss.pgh.pa.us 3444 :CBC 18 : break;
3445 : 9 : case DCH_MONTH:
3446 : : case DCH_Month:
3447 : : case DCH_month:
1104 3448 [ - + - + ]: 9 : if (!from_char_seq_search(&value, &s, months_full,
47 peter@eisentraut.org 3449 :GNC 9 : IS_SUFFIX_TM(n->suffix) ? localized_full_months : NULL,
3450 : : collid,
3451 : : n, escontext))
1104 tgl@sss.pgh.pa.us 3452 :UBC 0 : return;
1104 tgl@sss.pgh.pa.us 3453 [ - + ]:CBC 9 : if (!from_char_set_int(&out->mm, value + 1, n, escontext))
1104 tgl@sss.pgh.pa.us 3454 :UBC 0 : return;
6479 tgl@sss.pgh.pa.us 3455 :CBC 9 : break;
3456 : 60 : case DCH_MON:
3457 : : case DCH_Mon:
3458 : : case DCH_mon:
1104 3459 [ - + - + ]: 60 : if (!from_char_seq_search(&value, &s, months,
47 peter@eisentraut.org 3460 :GNC 60 : IS_SUFFIX_TM(n->suffix) ? localized_abbrev_months : NULL,
3461 : : collid,
3462 : : n, escontext))
1104 tgl@sss.pgh.pa.us 3463 :UBC 0 : return;
1104 tgl@sss.pgh.pa.us 3464 [ - + ]:CBC 54 : if (!from_char_set_int(&out->mm, value + 1, n, escontext))
1104 tgl@sss.pgh.pa.us 3465 :UBC 0 : return;
6479 tgl@sss.pgh.pa.us 3466 :CBC 51 : break;
3467 : 8577 : case DCH_MM:
1104 3468 [ - + ]: 8577 : if (from_char_parse_int(&out->mm, &s, n, escontext) < 0)
1104 tgl@sss.pgh.pa.us 3469 :UBC 0 : return;
3512 tgl@sss.pgh.pa.us 3470 [ - + - - :CBC 8568 : SKIP_THth(s, n->suffix);
- - - - ]
6479 3471 : 8568 : break;
3472 : 3 : case DCH_DAY:
3473 : : case DCH_Day:
3474 : : case DCH_day:
1104 3475 [ - + - + ]: 3 : if (!from_char_seq_search(&value, &s, days,
47 peter@eisentraut.org 3476 :GNC 3 : IS_SUFFIX_TM(n->suffix) ? localized_full_days : NULL,
3477 : : collid,
3478 : : n, escontext))
1104 tgl@sss.pgh.pa.us 3479 :UBC 0 : return;
1104 tgl@sss.pgh.pa.us 3480 [ - + ]:CBC 3 : if (!from_char_set_int(&out->d, value, n, escontext))
1104 tgl@sss.pgh.pa.us 3481 :UBC 0 : return;
4853 bruce@momjian.us 3482 :CBC 3 : out->d++;
6479 tgl@sss.pgh.pa.us 3483 : 3 : break;
3484 : 9 : case DCH_DY:
3485 : : case DCH_Dy:
3486 : : case DCH_dy:
1104 3487 [ - + - + ]: 9 : if (!from_char_seq_search(&value, &s, days_short,
47 peter@eisentraut.org 3488 :GNC 9 : IS_SUFFIX_TM(n->suffix) ? localized_abbrev_days : NULL,
3489 : : collid,
3490 : : n, escontext))
1104 tgl@sss.pgh.pa.us 3491 :UBC 0 : return;
1104 tgl@sss.pgh.pa.us 3492 [ - + ]:CBC 9 : if (!from_char_set_int(&out->d, value, n, escontext))
1104 tgl@sss.pgh.pa.us 3493 :UBC 0 : return;
4853 bruce@momjian.us 3494 :CBC 9 : out->d++;
6479 tgl@sss.pgh.pa.us 3495 : 9 : break;
3496 : 18 : case DCH_DDD:
1104 3497 [ - + ]: 18 : if (from_char_parse_int(&out->ddd, &s, n, escontext) < 0)
1104 tgl@sss.pgh.pa.us 3498 :UBC 0 : return;
3512 tgl@sss.pgh.pa.us 3499 [ - + - - :CBC 18 : SKIP_THth(s, n->suffix);
- - - - ]
6306 3500 : 18 : break;
6479 3501 : 3 : case DCH_IDDD:
1104 3502 [ - + ]: 3 : if (from_char_parse_int_len(&out->ddd, &s, 3, n, escontext) < 0)
1104 tgl@sss.pgh.pa.us 3503 :UBC 0 : return;
3512 tgl@sss.pgh.pa.us 3504 [ - + - - :CBC 3 : SKIP_THth(s, n->suffix);
- - - - ]
6479 3505 : 3 : break;
3506 : 8611 : case DCH_DD:
1104 3507 [ - + ]: 8611 : if (from_char_parse_int(&out->dd, &s, n, escontext) < 0)
1104 tgl@sss.pgh.pa.us 3508 :UBC 0 : return;
3512 tgl@sss.pgh.pa.us 3509 [ - + - - :CBC 8604 : SKIP_THth(s, n->suffix);
- - - - ]
6479 3510 : 8604 : break;
3511 : 6 : case DCH_D:
1104 3512 [ - + ]: 6 : if (from_char_parse_int(&out->d, &s, n, escontext) < 0)
1104 tgl@sss.pgh.pa.us 3513 :UBC 0 : return;
3512 tgl@sss.pgh.pa.us 3514 [ - + - - :CBC 6 : SKIP_THth(s, n->suffix);
- - - - ]
6306 3515 : 6 : break;
6479 3516 : 12 : case DCH_ID:
1104 3517 [ - + ]: 12 : if (from_char_parse_int_len(&out->d, &s, 1, n, escontext) < 0)
1104 tgl@sss.pgh.pa.us 3518 :UBC 0 : return;
3519 : : /* Shift numbering to match Gregorian where Sunday = 1 */
4853 bruce@momjian.us 3520 [ + - ]:CBC 12 : if (++out->d > 7)
3521 : 12 : out->d = 1;
3512 tgl@sss.pgh.pa.us 3522 [ - + - - : 12 : SKIP_THth(s, n->suffix);
- - - - ]
6479 3523 : 12 : break;
3524 : 18 : case DCH_WW:
3525 : : case DCH_IW:
1104 3526 [ - + ]: 18 : if (from_char_parse_int(&out->ww, &s, n, escontext) < 0)
1104 tgl@sss.pgh.pa.us 3527 :UBC 0 : return;
3512 tgl@sss.pgh.pa.us 3528 [ - + - - :CBC 18 : SKIP_THth(s, n->suffix);
- - - - ]
6479 3529 : 18 : break;
3530 : 3 : case DCH_Q:
3531 : :
3532 : : /*
3533 : : * We ignore 'Q' when converting to date because it is unclear
3534 : : * which date in the quarter to use, and some people specify
3535 : : * both quarter and month, so if it was honored it might
3536 : : * conflict with the supplied month. That is also why we don't
3537 : : * throw an error.
3538 : : *
3539 : : * We still parse the source string for an integer, but it
3540 : : * isn't stored anywhere in 'out'.
3541 : : */
1104 3542 [ - + ]: 3 : if (from_char_parse_int((int *) NULL, &s, n, escontext) < 0)
1104 tgl@sss.pgh.pa.us 3543 :UBC 0 : return;
3512 tgl@sss.pgh.pa.us 3544 [ - + - - :CBC 3 : SKIP_THth(s, n->suffix);
- - - - ]
6479 3545 : 3 : break;
3546 : 15 : case DCH_CC:
1104 3547 [ - + ]: 15 : if (from_char_parse_int(&out->cc, &s, n, escontext) < 0)
1104 tgl@sss.pgh.pa.us 3548 :UBC 0 : return;
3512 tgl@sss.pgh.pa.us 3549 [ - + - - :CBC 15 : SKIP_THth(s, n->suffix);
- - - - ]
6479 3550 : 15 : break;
3551 : 6 : case DCH_Y_YYY:
3552 : : {
3553 : : int matched,
3554 : : years,
3555 : : millennia,
3556 : : nch;
3557 : :
3484 stark@mit.edu 3558 : 6 : matched = sscanf(s, "%d,%03d%n", &millennia, &years, &nch);
3512 tgl@sss.pgh.pa.us 3559 [ - + ]: 6 : if (matched < 2)
1104 tgl@sss.pgh.pa.us 3560 [ # # ]:UBC 0 : ereturn(escontext,,
3561 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
3562 : : errmsg("invalid value \"%s\" for \"%s\"", s, "Y,YYY")));
3563 : :
3564 : : /* years += (millennia * 1000); */
373 nathan@postgresql.or 3565 [ + + - + ]:CBC 9 : if (pg_mul_s32_overflow(millennia, 1000, &millennia) ||
3566 : 3 : pg_add_s32_overflow(years, millennia, &years))
3567 [ + - ]: 3 : ereturn(escontext,,
3568 : : (errcode(ERRCODE_DATETIME_FIELD_OVERFLOW),
3569 : : errmsg("value for \"%s\" in source string is out of range", "Y,YYY")));
3570 : :
1104 tgl@sss.pgh.pa.us 3571 [ - + ]: 3 : if (!from_char_set_int(&out->year, years, n, escontext))
1104 tgl@sss.pgh.pa.us 3572 :UBC 0 : return;
6479 tgl@sss.pgh.pa.us 3573 :CBC 3 : out->yysz = 4;
3512 3574 : 3 : s += nch;
3575 [ + - + - : 3 : SKIP_THth(s, n->suffix);
+ - + - ]
3576 : : }
6479 3577 : 3 : break;
6306 3578 : 10089 : case DCH_YYYY:
3579 : : case DCH_IYYY:
1104 3580 [ + + ]: 10089 : if (from_char_parse_int(&out->year, &s, n, escontext) < 0)
3581 : 162 : return;
6306 3582 : 9921 : out->yysz = 4;
3512 3583 [ - + - - : 9921 : SKIP_THth(s, n->suffix);
- - - - ]
6306 3584 : 9921 : break;
6479 3585 : 6 : case DCH_YYY:
3586 : : case DCH_IYY:
1104 3587 : 6 : len = from_char_parse_int(&out->year, &s, n, escontext);
3588 [ - + ]: 6 : if (len < 0)
1104 tgl@sss.pgh.pa.us 3589 :UBC 0 : return;
2275 akorotkov@postgresql 3590 [ + - ]:CBC 6 : if (len < 4)
5215 bruce@momjian.us 3591 : 6 : out->year = adjust_partial_year_to_2020(out->year);
6306 tgl@sss.pgh.pa.us 3592 : 6 : out->yysz = 3;
3512 3593 [ - + - - : 6 : SKIP_THth(s, n->suffix);
- - - - ]
6479 3594 : 6 : break;
3595 : 30 : case DCH_YY:
3596 : : case DCH_IY:
1104 3597 : 30 : len = from_char_parse_int(&out->year, &s, n, escontext);
3598 [ - + ]: 30 : if (len < 0)
1104 tgl@sss.pgh.pa.us 3599 :UBC 0 : return;
2275 akorotkov@postgresql 3600 [ + - ]:CBC 30 : if (len < 4)
5215 bruce@momjian.us 3601 : 30 : out->year = adjust_partial_year_to_2020(out->year);
6306 tgl@sss.pgh.pa.us 3602 : 30 : out->yysz = 2;
3512 3603 [ - + - - : 30 : SKIP_THth(s, n->suffix);
- - - - ]
6479 3604 : 30 : break;
3605 : 6 : case DCH_Y:
3606 : : case DCH_I:
1104 3607 : 6 : len = from_char_parse_int(&out->year, &s, n, escontext);
3608 [ - + ]: 6 : if (len < 0)
1104 tgl@sss.pgh.pa.us 3609 :UBC 0 : return;
2275 akorotkov@postgresql 3610 [ + - ]:CBC 6 : if (len < 4)
5215 bruce@momjian.us 3611 : 6 : out->year = adjust_partial_year_to_2020(out->year);
6306 tgl@sss.pgh.pa.us 3612 : 6 : out->yysz = 1;
3512 3613 [ - + - - : 6 : SKIP_THth(s, n->suffix);
- - - - ]
6479 3614 : 6 : break;
3615 : 3 : case DCH_RM:
3616 : : case DCH_rm:
1104 3617 [ - + ]: 3 : if (!from_char_seq_search(&value, &s, rm_months_lower,
3618 : : NULL, InvalidOid,
3619 : : n, escontext))
1104 tgl@sss.pgh.pa.us 3620 :UBC 0 : return;
1104 tgl@sss.pgh.pa.us 3621 [ - + ]:CBC 3 : if (!from_char_set_int(&out->mm, MONTHS_PER_YEAR - value, n,
3622 : : escontext))
1104 tgl@sss.pgh.pa.us 3623 :UBC 0 : return;
6479 tgl@sss.pgh.pa.us 3624 :CBC 3 : break;
3625 : 6 : case DCH_W:
1104 3626 [ - + ]: 6 : if (from_char_parse_int(&out->w, &s, n, escontext) < 0)
1104 tgl@sss.pgh.pa.us 3627 :UBC 0 : return;
3512 tgl@sss.pgh.pa.us 3628 [ - + - - :CBC 6 : SKIP_THth(s, n->suffix);
- - - - ]
6479 3629 : 6 : break;
3630 : 3 : case DCH_J:
1104 3631 [ - + ]: 3 : if (from_char_parse_int(&out->j, &s, n, escontext) < 0)
1104 tgl@sss.pgh.pa.us 3632 :UBC 0 : return;
3512 tgl@sss.pgh.pa.us 3633 [ - + - - :CBC 3 : SKIP_THth(s, n->suffix);
- - - - ]
6479 3634 : 3 : break;
3635 : : }
3636 : :
3637 : : /* Ignore all spaces after fields */
2656 akorotkov@postgresql 3638 [ + + ]: 61434 : if (!fx_mode)
3639 : : {
3640 : 2547 : extra_skip = 0;
3641 [ + + + + ]: 3150 : while (*s != '\0' && isspace((unsigned char) *s))
3642 : : {
3643 : 603 : s++;
3644 : 603 : extra_skip++;
3645 : : }
3646 : : }
3647 : : }
3648 : :
3649 : : /*
3650 : : * Standard parsing mode doesn't allow unmatched format patterns or
3651 : : * trailing characters in the input string.
3652 : : */
2275 3653 [ + + ]: 9513 : if (std)
3654 : : {
3655 [ + + ]: 8997 : if (n->type != NODE_TYPE_END)
1104 tgl@sss.pgh.pa.us 3656 [ + + ]: 3348 : ereturn(escontext,,
3657 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
3658 : : errmsg("input string is too short for datetime format")));
3659 : :
2275 akorotkov@postgresql 3660 [ + + + + ]: 7194 : while (*s != '\0' && isspace((unsigned char) *s))
3661 : 1545 : s++;
3662 : :
3663 [ + + ]: 5649 : if (*s != '\0')
1104 tgl@sss.pgh.pa.us 3664 [ + + ]: 1563 : ereturn(escontext,,
3665 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
3666 : : errmsg("trailing characters remain in input string after datetime format")));
3667 : : }
3668 : : }
3669 : :
3670 : : /*
3671 : : * The invariant for DCH cache entry management is that DCHCounter is equal
3672 : : * to the maximum age value among the existing entries, and we increment it
3673 : : * whenever an access occurs. If we approach overflow, deal with that by
3674 : : * halving all the age values, so that we retain a fairly accurate idea of
3675 : : * which entries are oldest.
3676 : : */
3677 : : static inline void
2619 3678 : 25522 : DCH_prevent_counter_overflow(void)
3679 : : {
3680 [ - + ]: 25522 : if (DCHCounter >= (INT_MAX - 1))
3681 : : {
2619 tgl@sss.pgh.pa.us 3682 [ # # ]:UBC 0 : for (int i = 0; i < n_DCHCache; i++)
3683 : 0 : DCHCache[i]->age >>= 1;
3684 : 0 : DCHCounter >>= 1;
3685 : : }
2619 tgl@sss.pgh.pa.us 3686 :CBC 25522 : }
3687 : :
3688 : : /*
3689 : : * Get mask of date/time/zone components present in format nodes.
3690 : : */
3691 : : static int
1104 3692 : 4119 : DCH_datetime_type(FormatNode *node)
3693 : : {
2275 akorotkov@postgresql 3694 : 4119 : int flags = 0;
3695 : :
50 peter@eisentraut.org 3696 [ + + ]:GNC 37893 : for (FormatNode *n = node; n->type != NODE_TYPE_END; n++)
3697 : : {
2275 akorotkov@postgresql 3698 [ + + ]:CBC 33774 : if (n->type != NODE_TYPE_ACTION)
3699 : 14031 : continue;
3700 : :
3701 [ - + + + : 19743 : switch (n->key->id)
- ]
3702 : : {
2275 akorotkov@postgresql 3703 :UBC 0 : case DCH_FX:
3704 : 0 : break;
2275 akorotkov@postgresql 3705 :CBC 10146 : case DCH_A_M:
3706 : : case DCH_P_M:
3707 : : case DCH_a_m:
3708 : : case DCH_p_m:
3709 : : case DCH_AM:
3710 : : case DCH_PM:
3711 : : case DCH_am:
3712 : : case DCH_pm:
3713 : : case DCH_HH:
3714 : : case DCH_HH12:
3715 : : case DCH_HH24:
3716 : : case DCH_MI:
3717 : : case DCH_SS:
3718 : : case DCH_MS: /* millisecond */
3719 : : case DCH_US: /* microsecond */
3720 : : case DCH_FF1:
3721 : : case DCH_FF2:
3722 : : case DCH_FF3:
3723 : : case DCH_FF4:
3724 : : case DCH_FF5:
3725 : : case DCH_FF6:
3726 : : case DCH_SSSS:
3727 : 10146 : flags |= DCH_TIMED;
3728 : 10146 : break;
3729 : 2010 : case DCH_tz:
3730 : : case DCH_TZ:
3731 : : case DCH_OF:
3732 : : case DCH_TZH:
3733 : : case DCH_TZM:
3734 : 2010 : flags |= DCH_ZONED;
3735 : 2010 : break;
3736 : 7587 : case DCH_A_D:
3737 : : case DCH_B_C:
3738 : : case DCH_a_d:
3739 : : case DCH_b_c:
3740 : : case DCH_AD:
3741 : : case DCH_BC:
3742 : : case DCH_ad:
3743 : : case DCH_bc:
3744 : : case DCH_MONTH:
3745 : : case DCH_Month:
3746 : : case DCH_month:
3747 : : case DCH_MON:
3748 : : case DCH_Mon:
3749 : : case DCH_mon:
3750 : : case DCH_MM:
3751 : : case DCH_DAY:
3752 : : case DCH_Day:
3753 : : case DCH_day:
3754 : : case DCH_DY:
3755 : : case DCH_Dy:
3756 : : case DCH_dy:
3757 : : case DCH_DDD:
3758 : : case DCH_IDDD:
3759 : : case DCH_DD:
3760 : : case DCH_D:
3761 : : case DCH_ID:
3762 : : case DCH_WW:
3763 : : case DCH_Q:
3764 : : case DCH_CC:
3765 : : case DCH_Y_YYY:
3766 : : case DCH_YYYY:
3767 : : case DCH_IYYY:
3768 : : case DCH_YYY:
3769 : : case DCH_IYY:
3770 : : case DCH_YY:
3771 : : case DCH_IY:
3772 : : case DCH_Y:
3773 : : case DCH_I:
3774 : : case DCH_RM:
3775 : : case DCH_rm:
3776 : : case DCH_W:
3777 : : case DCH_J:
3778 : 7587 : flags |= DCH_DATED;
3779 : 7587 : break;
3780 : : }
3781 : : }
3782 : :
3783 : 4119 : return flags;
3784 : : }
3785 : :
3786 : : /* select a DCHCacheEntry to hold the given format picture */
3787 : : static DCHCacheEntry *
3788 : 469 : DCH_cache_getnew(const char *str, bool std)
3789 : : {
3790 : : DCHCacheEntry *ent;
3791 : :
3792 : : /* Ensure we can advance DCHCounter below */
2619 tgl@sss.pgh.pa.us 3793 : 469 : DCH_prevent_counter_overflow();
3794 : :
3795 : : /*
3796 : : * If cache is full, remove oldest entry (or recycle first not-valid one)
3797 : : */
3367 3798 [ + + ]: 469 : if (n_DCHCache >= DCH_CACHE_ENTRIES)
3799 : : {
2619 3800 : 231 : DCHCacheEntry *old = DCHCache[0];
3801 : :
3802 : : #ifdef DEBUG_TO_FROM_CHAR
3803 : : elog(DEBUG_elog_output, "cache is full (%d)", n_DCHCache);
3804 : : #endif
3367 3805 [ + - ]: 231 : if (old->valid)
3806 : : {
2619 3807 [ + + ]: 4599 : for (int i = 1; i < DCH_CACHE_ENTRIES; i++)
3808 : : {
3809 : 4371 : ent = DCHCache[i];
3367 3810 [ + + ]: 4371 : if (!ent->valid)
3811 : : {
3812 : 3 : old = ent;
3813 : 3 : break;
3814 : : }
3815 [ + + ]: 4368 : if (ent->age < old->age)
3816 : 384 : old = ent;
3817 : : }
3818 : : }
3819 : : #ifdef DEBUG_TO_FROM_CHAR
3820 : : elog(DEBUG_elog_output, "OLD: '%s' AGE: %d", old->str, old->age);
3821 : : #endif
3822 : 231 : old->valid = false;
1955 peter@eisentraut.org 3823 : 231 : strlcpy(old->str, str, DCH_CACHE_SIZE + 1);
9407 bruce@momjian.us 3824 : 231 : old->age = (++DCHCounter);
3825 : : /* caller is expected to fill format, then set valid */
3826 : 231 : return old;
3827 : : }
3828 : : else
3829 : : {
3830 : : #ifdef DEBUG_TO_FROM_CHAR
3831 : : elog(DEBUG_elog_output, "NEW (%d)", n_DCHCache);
3832 : : #endif
2619 tgl@sss.pgh.pa.us 3833 [ - + ]: 238 : Assert(DCHCache[n_DCHCache] == NULL);
3834 : 238 : DCHCache[n_DCHCache] = ent = (DCHCacheEntry *)
3835 : 238 : MemoryContextAllocZero(TopMemoryContext, sizeof(DCHCacheEntry));
3367 3836 : 238 : ent->valid = false;
1955 peter@eisentraut.org 3837 : 238 : strlcpy(ent->str, str, DCH_CACHE_SIZE + 1);
2275 akorotkov@postgresql 3838 : 238 : ent->std = std;
9407 bruce@momjian.us 3839 : 238 : ent->age = (++DCHCounter);
3840 : : /* caller is expected to fill format, then set valid */
3841 : 238 : ++n_DCHCache;
3842 : 238 : return ent;
3843 : : }
3844 : : }
3845 : :
3846 : : /* look for an existing DCHCacheEntry matching the given format picture */
3847 : : static DCHCacheEntry *
2275 akorotkov@postgresql 3848 : 25053 : DCH_cache_search(const char *str, bool std)
3849 : : {
3850 : : /* Ensure we can advance DCHCounter below */
2619 tgl@sss.pgh.pa.us 3851 : 25053 : DCH_prevent_counter_overflow();
3852 : :
3853 [ + + ]: 132619 : for (int i = 0; i < n_DCHCache; i++)
3854 : : {
3855 : 132150 : DCHCacheEntry *ent = DCHCache[i];
3856 : :
2275 akorotkov@postgresql 3857 [ + + + + : 132150 : if (ent->valid && strcmp(ent->str, str) == 0 && ent->std == std)
+ - ]
3858 : : {
9407 bruce@momjian.us 3859 : 24584 : ent->age = (++DCHCounter);
3860 : 24584 : return ent;
3861 : : }
3862 : : }
3863 : :
8015 neilc@samurai.com 3864 : 469 : return NULL;
3865 : : }
3866 : :
3867 : : /* Find or create a DCHCacheEntry for the given format picture */
3868 : : static DCHCacheEntry *
2275 akorotkov@postgresql 3869 : 25053 : DCH_cache_fetch(const char *str, bool std)
3870 : : {
3871 : : DCHCacheEntry *ent;
3872 : :
3873 [ + + ]: 25053 : if ((ent = DCH_cache_search(str, std)) == NULL)
3874 : : {
3875 : : /*
3876 : : * Not in the cache, must run parser and save a new format-picture to
3877 : : * the cache. Do not mark the cache entry valid until parsing
3878 : : * succeeds.
3879 : : */
3880 : 469 : ent = DCH_cache_getnew(str, std);
3881 : :
3882 [ + + ]: 469 : parse_format(ent->format, str, DCH_keywords, DCH_suff, DCH_index,
3883 : : DCH_FLAG | (std ? STD_FLAG : 0), NULL);
3884 : :
3367 tgl@sss.pgh.pa.us 3885 : 466 : ent->valid = true;
3886 : : }
3887 : 25050 : return ent;
3888 : : }
3889 : :
3890 : : /*
3891 : : * Format a date/time or interval into a string according to fmt.
3892 : : * We parse fmt into a list of FormatNodes. This is then passed to DCH_to_char
3893 : : * for formatting.
3894 : : */
3895 : : static text *
49 peter@eisentraut.org 3896 :GNC 4793 : datetime_to_char_body(TmToChar *tmtc, const text *fmt, bool is_interval, Oid collid)
3897 : : {
3898 : : FormatNode *format;
3899 : : char *fmt_str,
3900 : : *result;
3901 : : bool incache;
3902 : : size_t fmt_len;
3903 : : text *res;
3904 : :
3905 : : /*
3906 : : * Convert fmt to C string
3907 : : */
6476 tgl@sss.pgh.pa.us 3908 :CBC 4793 : fmt_str = text_to_cstring(fmt);
3909 : 4793 : fmt_len = strlen(fmt_str);
3910 : :
3911 : : /*
3912 : : * Allocate workspace for result as C string
3913 : : */
8141 3914 : 4793 : result = palloc((fmt_len * DCH_MAX_ITEM_SIZ) + 1);
7363 3915 : 4793 : *result = '\0';
3916 : :
8141 3917 [ - + ]: 4793 : if (fmt_len > DCH_CACHE_SIZE)
3918 : : {
3919 : : /*
3920 : : * Allocate new memory if format picture is bigger than static cache
3921 : : * and do not use cache (call parser always)
3922 : : */
3045 peter_e@gmx.net 3923 :UBC 0 : incache = false;
3924 : :
3367 tgl@sss.pgh.pa.us 3925 : 0 : format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
3926 : :
8141 3927 : 0 : parse_format(format, fmt_str, DCH_keywords,
3928 : : DCH_suff, DCH_index, DCH_FLAG, NULL);
3929 : : }
3930 : : else
3931 : : {
3932 : : /*
3933 : : * Use cache buffers
3934 : : */
2275 akorotkov@postgresql 3935 :CBC 4793 : DCHCacheEntry *ent = DCH_cache_fetch(fmt_str, false);
3936 : :
3045 peter_e@gmx.net 3937 : 4793 : incache = true;
9380 bruce@momjian.us 3938 : 4793 : format = ent->format;
3939 : : }
3940 : :
3941 : : /* The real work is here */
5426 peter_e@gmx.net 3942 : 4793 : DCH_to_char(format, is_interval, tmtc, result, collid);
3943 : :
8868 bruce@momjian.us 3944 [ - + ]: 4793 : if (!incache)
9458 bruce@momjian.us 3945 :UBC 0 : pfree(format);
3946 : :
8141 tgl@sss.pgh.pa.us 3947 :CBC 4793 : pfree(fmt_str);
3948 : :
3949 : : /* convert C-string result to TEXT format */
6476 3950 : 4793 : res = cstring_to_text(result);
3951 : :
8141 3952 : 4793 : pfree(result);
7363 3953 : 4793 : return res;
3954 : : }
3955 : :
3956 : : /****************************************************************************
3957 : : * Public routines
3958 : : ***************************************************************************/
3959 : :
3960 : : /*
3961 : : * TIMESTAMP to_char()
3962 : : */
3963 : : Datum
8868 bruce@momjian.us 3964 : 2402 : timestamp_to_char(PG_FUNCTION_ARGS)
3965 : : {
8819 3966 : 2402 : Timestamp dt = PG_GETARG_TIMESTAMP(0);
3202 noah@leadboat.com 3967 : 2402 : text *fmt = PG_GETARG_TEXT_PP(1),
3968 : : *res;
3969 : : TmToChar tmtc;
3970 : : struct pg_tm tt;
3971 : : struct fmt_tm *tm;
3972 : : int thisdate;
3973 : :
3974 [ + - + + : 2402 : if (VARSIZE_ANY_EXHDR(fmt) <= 0 || TIMESTAMP_NOT_FINITE(dt))
+ + - - -
+ - - + -
+ + + + ]
8846 lockhart@fourpalms.o 3975 : 66 : PG_RETURN_NULL();
3976 : :
3977 : 2336 : ZERO_tmtc(&tmtc);
7363 tgl@sss.pgh.pa.us 3978 : 2336 : tm = tmtcTm(&tmtc);
3979 : :
1355 3980 [ - + ]: 2336 : if (timestamp2tm(dt, NULL, &tt, &tmtcFsec(&tmtc), NULL, NULL) != 0)
8179 tgl@sss.pgh.pa.us 3981 [ # # ]:UBC 0 : ereport(ERROR,
3982 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
3983 : : errmsg("timestamp out of range")));
3984 : :
3985 : : /* calculate wday and yday, because timestamp2tm doesn't */
1354 tgl@sss.pgh.pa.us 3986 :CBC 2336 : thisdate = date2j(tt.tm_year, tt.tm_mon, tt.tm_mday);
3987 : 2336 : tt.tm_wday = (thisdate + 1) % 7;
3988 : 2336 : tt.tm_yday = thisdate - date2j(tt.tm_year, 1, 1) + 1;
3989 : :
3990 : 2336 : COPY_tm(tm, &tt);
3991 : :
5426 peter_e@gmx.net 3992 [ - + ]: 2336 : if (!(res = datetime_to_char_body(&tmtc, fmt, false, PG_GET_COLLATION())))
8846 lockhart@fourpalms.o 3993 :UBC 0 : PG_RETURN_NULL();
3994 : :
8846 lockhart@fourpalms.o 3995 :CBC 2336 : PG_RETURN_TEXT_P(res);
3996 : : }
3997 : :
3998 : : Datum
3999 : 2360 : timestamptz_to_char(PG_FUNCTION_ARGS)
4000 : : {
4001 : 2360 : TimestampTz dt = PG_GETARG_TIMESTAMP(0);
3202 noah@leadboat.com 4002 : 2360 : text *fmt = PG_GETARG_TEXT_PP(1),
4003 : : *res;
4004 : : TmToChar tmtc;
4005 : : int tz;
4006 : : struct pg_tm tt;
4007 : : struct fmt_tm *tm;
4008 : : int thisdate;
4009 : :
4010 [ + - + + : 2360 : if (VARSIZE_ANY_EXHDR(fmt) <= 0 || TIMESTAMP_NOT_FINITE(dt))
+ + - - -
+ - - + -
+ + + + ]
8868 bruce@momjian.us 4011 : 66 : PG_RETURN_NULL();
4012 : :
4013 : 2294 : ZERO_tmtc(&tmtc);
7363 tgl@sss.pgh.pa.us 4014 : 2294 : tm = tmtcTm(&tmtc);
4015 : :
1355 4016 [ - + ]: 2294 : if (timestamp2tm(dt, &tz, &tt, &tmtcFsec(&tmtc), &tmtcTzn(&tmtc), NULL) != 0)
8179 tgl@sss.pgh.pa.us 4017 [ # # ]:UBC 0 : ereport(ERROR,
4018 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
4019 : : errmsg("timestamp out of range")));
4020 : :
4021 : : /* calculate wday and yday, because timestamp2tm doesn't */
1354 tgl@sss.pgh.pa.us 4022 :CBC 2294 : thisdate = date2j(tt.tm_year, tt.tm_mon, tt.tm_mday);
4023 : 2294 : tt.tm_wday = (thisdate + 1) % 7;
4024 : 2294 : tt.tm_yday = thisdate - date2j(tt.tm_year, 1, 1) + 1;
4025 : :
4026 : 2294 : COPY_tm(tm, &tt);
4027 : :
5426 peter_e@gmx.net 4028 [ - + ]: 2294 : if (!(res = datetime_to_char_body(&tmtc, fmt, false, PG_GET_COLLATION())))
8868 bruce@momjian.us 4029 :UBC 0 : PG_RETURN_NULL();
4030 : :
8868 bruce@momjian.us 4031 :CBC 2294 : PG_RETURN_TEXT_P(res);
4032 : : }
4033 : :
4034 : :
4035 : : /*
4036 : : * INTERVAL to_char()
4037 : : */
4038 : : Datum
4039 : 169 : interval_to_char(PG_FUNCTION_ARGS)
4040 : : {
8819 4041 : 169 : Interval *it = PG_GETARG_INTERVAL_P(0);
3202 noah@leadboat.com 4042 : 169 : text *fmt = PG_GETARG_TEXT_PP(1),
4043 : : *res;
4044 : : TmToChar tmtc;
4045 : : struct fmt_tm *tm;
4046 : : struct pg_itm tt,
1355 tgl@sss.pgh.pa.us 4047 : 169 : *itm = &tt;
4048 : :
764 dean.a.rasheed@gmail 4049 [ + - + + : 169 : if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
+ - - + +
+ + - + -
+ + + - -
+ + + + -
+ - ]
8868 bruce@momjian.us 4050 : 6 : PG_RETURN_NULL();
4051 : :
4052 : 163 : ZERO_tmtc(&tmtc);
7363 tgl@sss.pgh.pa.us 4053 : 163 : tm = tmtcTm(&tmtc);
4054 : :
1355 4055 : 163 : interval2itm(*it, itm);
4056 : 163 : tmtc.fsec = itm->tm_usec;
4057 : 163 : tm->tm_sec = itm->tm_sec;
4058 : 163 : tm->tm_min = itm->tm_min;
4059 : 163 : tm->tm_hour = itm->tm_hour;
4060 : 163 : tm->tm_mday = itm->tm_mday;
4061 : 163 : tm->tm_mon = itm->tm_mon;
4062 : 163 : tm->tm_year = itm->tm_year;
4063 : :
4064 : : /* wday is meaningless, yday approximates the total span in days */
7363 4065 : 163 : tm->tm_yday = (tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon) * DAYS_PER_MONTH + tm->tm_mday;
4066 : :
5426 peter_e@gmx.net 4067 [ - + ]: 163 : if (!(res = datetime_to_char_body(&tmtc, fmt, true, PG_GET_COLLATION())))
8868 bruce@momjian.us 4068 :UBC 0 : PG_RETURN_NULL();
4069 : :
8868 bruce@momjian.us 4070 :CBC 163 : PG_RETURN_TEXT_P(res);
4071 : : }
4072 : :
4073 : : /*
4074 : : * TO_TIMESTAMP()
4075 : : *
4076 : : * Make Timestamp from date_str which is formatted at argument 'fmt'
4077 : : * ( to_timestamp is reverse to_char() )
4078 : : */
4079 : : Datum
9300 4080 : 462 : to_timestamp(PG_FUNCTION_ARGS)
4081 : : {
3202 noah@leadboat.com 4082 : 462 : text *date_txt = PG_GETARG_TEXT_PP(0);
4083 : 462 : text *fmt = PG_GETARG_TEXT_PP(1);
2115 tgl@sss.pgh.pa.us 4084 : 462 : Oid collid = PG_GET_COLLATION();
4085 : : Timestamp result;
4086 : : int tz;
4087 : : struct pg_tm tm;
4088 : : struct fmt_tz ftz;
4089 : : fsec_t fsec;
4090 : : int fprec;
4091 : :
4092 : 462 : do_to_timestamp(date_txt, fmt, collid, false,
4093 : : &tm, &fsec, &ftz, &fprec, NULL, NULL);
4094 : :
4095 : : /* Use the specified time zone, if any. */
692 4096 [ + + ]: 378 : if (ftz.has_tz)
4097 : 48 : tz = ftz.gmtoffset;
4098 : : else
2899 andrew@dunslane.net 4099 : 330 : tz = DetermineTimeZoneOffset(&tm, session_timezone);
4100 : :
8150 tgl@sss.pgh.pa.us 4101 [ - + ]: 378 : if (tm2timestamp(&tm, fsec, &tz, &result) != 0)
8150 tgl@sss.pgh.pa.us 4102 [ # # ]:UBC 0 : ereport(ERROR,
4103 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
4104 : : errmsg("timestamp out of range")));
4105 : :
4106 : : /* Use the specified fractional precision, if any. */
2284 akorotkov@postgresql 4107 [ + + ]:CBC 378 : if (fprec)
1104 tgl@sss.pgh.pa.us 4108 : 126 : AdjustTimestampForTypmod(&result, fprec, NULL);
4109 : :
8150 4110 : 378 : PG_RETURN_TIMESTAMP(result);
4111 : : }
4112 : :
4113 : : /*
4114 : : * TO_DATE
4115 : : * Make Date from date_str which is formatted at argument 'fmt'
4116 : : */
4117 : : Datum
4118 : 103 : to_date(PG_FUNCTION_ARGS)
4119 : : {
3202 noah@leadboat.com 4120 : 103 : text *date_txt = PG_GETARG_TEXT_PP(0);
4121 : 103 : text *fmt = PG_GETARG_TEXT_PP(1);
2115 tgl@sss.pgh.pa.us 4122 : 103 : Oid collid = PG_GET_COLLATION();
4123 : : DateADT result;
4124 : : struct pg_tm tm;
4125 : : struct fmt_tz ftz;
4126 : : fsec_t fsec;
4127 : :
4128 : 103 : do_to_timestamp(date_txt, fmt, collid, false,
4129 : : &tm, &fsec, &ftz, NULL, NULL, NULL);
4130 : :
4131 : : /* Prevent overflow in Julian-day routines */
4720 4132 [ - + - - : 72 : if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
- - - + -
- - - ]
4720 tgl@sss.pgh.pa.us 4133 [ # # ]:UBC 0 : ereport(ERROR,
4134 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
4135 : : errmsg("date out of range: \"%s\"", text_to_cstring(date_txt))));
4136 : :
8150 tgl@sss.pgh.pa.us 4137 :CBC 72 : result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - POSTGRES_EPOCH_JDATE;
4138 : :
4139 : : /* Now check for just-out-of-range dates */
3563 4140 [ + - - + ]: 72 : if (!IS_VALID_DATE(result))
3563 tgl@sss.pgh.pa.us 4141 [ # # ]:UBC 0 : ereport(ERROR,
4142 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
4143 : : errmsg("date out of range: \"%s\"", text_to_cstring(date_txt))));
4144 : :
8150 tgl@sss.pgh.pa.us 4145 :CBC 72 : PG_RETURN_DATEADT(result);
4146 : : }
4147 : :
4148 : : /*
4149 : : * Convert the 'date_txt' input to a datetime type using argument 'fmt'
4150 : : * as a format string. The collation 'collid' may be used for case-folding
4151 : : * rules in some cases. 'strict' specifies standard parsing mode.
4152 : : *
4153 : : * The actual data type (returned in 'typid', 'typmod') is determined by
4154 : : * the presence of date/time/zone components in the format string.
4155 : : *
4156 : : * When a timezone component is present, the corresponding offset is
4157 : : * returned in '*tz'.
4158 : : *
4159 : : * If escontext points to an ErrorSaveContext, data errors will be reported
4160 : : * by filling that struct; the caller must test SOFT_ERROR_OCCURRED() to see
4161 : : * whether an error occurred. Otherwise, errors are thrown.
4162 : : */
4163 : : Datum
2115 4164 : 19662 : parse_datetime(text *date_txt, text *fmt, Oid collid, bool strict,
4165 : : Oid *typid, int32 *typmod, int *tz,
4166 : : Node *escontext)
4167 : : {
4168 : : struct pg_tm tm;
4169 : : struct fmt_tz ftz;
4170 : : fsec_t fsec;
4171 : : int fprec;
4172 : : uint32 flags;
4173 : :
1104 4174 [ + + ]: 19662 : if (!do_to_timestamp(date_txt, fmt, collid, strict,
4175 : : &tm, &fsec, &ftz, &fprec, &flags, escontext))
4176 : 15546 : return (Datum) 0;
4177 : :
2275 akorotkov@postgresql 4178 [ - + ]: 4086 : *typmod = fprec ? fprec : -1; /* fractional part precision */
4179 : :
4180 [ + + ]: 4086 : if (flags & DCH_DATED)
4181 : : {
4182 [ + + ]: 2520 : if (flags & DCH_TIMED)
4183 : : {
4184 [ + + ]: 1878 : if (flags & DCH_ZONED)
4185 : : {
4186 : : TimestampTz result;
4187 : :
692 tgl@sss.pgh.pa.us 4188 [ + - ]: 1077 : if (ftz.has_tz)
4189 : : {
4190 : 1077 : *tz = ftz.gmtoffset;
4191 : : }
4192 : : else
4193 : : {
4194 : : /*
4195 : : * Time zone is present in format string, but not in input
4196 : : * string. Assuming do_to_timestamp() triggers no error
4197 : : * this should be possible only in non-strict case.
4198 : : */
2275 akorotkov@postgresql 4199 [ # # ]:UBC 0 : Assert(!strict);
4200 : :
1104 tgl@sss.pgh.pa.us 4201 [ # # ]: 0 : ereturn(escontext, (Datum) 0,
4202 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
4203 : : errmsg("missing time zone in input string for type timestamptz")));
4204 : : }
4205 : :
2275 akorotkov@postgresql 4206 [ - + ]:CBC 1077 : if (tm2timestamp(&tm, fsec, tz, &result) != 0)
1104 tgl@sss.pgh.pa.us 4207 [ # # ]:UBC 0 : ereturn(escontext, (Datum) 0,
4208 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
4209 : : errmsg("timestamptz out of range")));
4210 : :
1104 tgl@sss.pgh.pa.us 4211 :CBC 1077 : AdjustTimestampForTypmod(&result, *typmod, escontext);
4212 : :
2275 akorotkov@postgresql 4213 : 1077 : *typid = TIMESTAMPTZOID;
4214 : 1077 : return TimestampTzGetDatum(result);
4215 : : }
4216 : : else
4217 : : {
4218 : : Timestamp result;
4219 : :
4220 [ - + ]: 801 : if (tm2timestamp(&tm, fsec, NULL, &result) != 0)
1104 tgl@sss.pgh.pa.us 4221 [ # # ]:UBC 0 : ereturn(escontext, (Datum) 0,
4222 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
4223 : : errmsg("timestamp out of range")));
4224 : :
1104 tgl@sss.pgh.pa.us 4225 :CBC 801 : AdjustTimestampForTypmod(&result, *typmod, escontext);
4226 : :
2275 akorotkov@postgresql 4227 : 801 : *typid = TIMESTAMPOID;
4228 : 801 : return TimestampGetDatum(result);
4229 : : }
4230 : : }
4231 : : else
4232 : : {
4233 [ - + ]: 642 : if (flags & DCH_ZONED)
4234 : : {
1104 tgl@sss.pgh.pa.us 4235 [ # # ]:UBC 0 : ereturn(escontext, (Datum) 0,
4236 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
4237 : : errmsg("datetime format is zoned but not timed")));
4238 : : }
4239 : : else
4240 : : {
4241 : : DateADT result;
4242 : :
4243 : : /* Prevent overflow in Julian-day routines */
2275 akorotkov@postgresql 4244 [ - + - - :CBC 642 : if (!IS_VALID_JULIAN(tm.tm_year, tm.tm_mon, tm.tm_mday))
- - - + -
- - - ]
1104 tgl@sss.pgh.pa.us 4245 [ # # ]:UBC 0 : ereturn(escontext, (Datum) 0,
4246 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
4247 : : errmsg("date out of range: \"%s\"", text_to_cstring(date_txt))));
4248 : :
2275 akorotkov@postgresql 4249 :CBC 642 : result = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) -
4250 : : POSTGRES_EPOCH_JDATE;
4251 : :
4252 : : /* Now check for just-out-of-range dates */
4253 [ + - - + ]: 642 : if (!IS_VALID_DATE(result))
1104 tgl@sss.pgh.pa.us 4254 [ # # ]:UBC 0 : ereturn(escontext, (Datum) 0,
4255 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
4256 : : errmsg("date out of range: \"%s\"", text_to_cstring(date_txt))));
4257 : :
2275 akorotkov@postgresql 4258 :CBC 642 : *typid = DATEOID;
4259 : 642 : return DateADTGetDatum(result);
4260 : : }
4261 : : }
4262 : : }
4263 [ + - ]: 1566 : else if (flags & DCH_TIMED)
4264 : : {
4265 [ + + ]: 1566 : if (flags & DCH_ZONED)
4266 : : {
7 michael@paquier.xyz 4267 :GNC 885 : TimeTzADT *result = palloc_object(TimeTzADT);
4268 : :
692 tgl@sss.pgh.pa.us 4269 [ + - ]:CBC 885 : if (ftz.has_tz)
4270 : : {
4271 : 885 : *tz = ftz.gmtoffset;
4272 : : }
4273 : : else
4274 : : {
4275 : : /*
4276 : : * Time zone is present in format string, but not in input
4277 : : * string. Assuming do_to_timestamp() triggers no error this
4278 : : * should be possible only in non-strict case.
4279 : : */
2275 akorotkov@postgresql 4280 [ # # ]:UBC 0 : Assert(!strict);
4281 : :
1104 tgl@sss.pgh.pa.us 4282 [ # # ]: 0 : ereturn(escontext, (Datum) 0,
4283 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
4284 : : errmsg("missing time zone in input string for type timetz")));
4285 : : }
4286 : :
2275 akorotkov@postgresql 4287 [ - + ]:CBC 885 : if (tm2timetz(&tm, fsec, *tz, result) != 0)
1104 tgl@sss.pgh.pa.us 4288 [ # # ]:UBC 0 : ereturn(escontext, (Datum) 0,
4289 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
4290 : : errmsg("timetz out of range")));
4291 : :
2275 akorotkov@postgresql 4292 :CBC 885 : AdjustTimeForTypmod(&result->time, *typmod);
4293 : :
4294 : 885 : *typid = TIMETZOID;
4295 : 885 : return TimeTzADTPGetDatum(result);
4296 : : }
4297 : : else
4298 : : {
4299 : : TimeADT result;
4300 : :
4301 [ - + ]: 681 : if (tm2time(&tm, fsec, &result) != 0)
1104 tgl@sss.pgh.pa.us 4302 [ # # ]:UBC 0 : ereturn(escontext, (Datum) 0,
4303 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
4304 : : errmsg("time out of range")));
4305 : :
2275 akorotkov@postgresql 4306 :CBC 681 : AdjustTimeForTypmod(&result, *typmod);
4307 : :
4308 : 681 : *typid = TIMEOID;
4309 : 681 : return TimeADTGetDatum(result);
4310 : : }
4311 : : }
4312 : : else
4313 : : {
1104 tgl@sss.pgh.pa.us 4314 [ # # ]:UBC 0 : ereturn(escontext, (Datum) 0,
4315 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
4316 : : errmsg("datetime format is not dated and not timed")));
4317 : : }
4318 : : }
4319 : :
4320 : : /*
4321 : : * Parses the datetime format string in 'fmt_str' and returns true if it
4322 : : * contains a timezone specifier, false if not.
4323 : : */
4324 : : bool
636 amitlan@postgresql.o 4325 :CBC 33 : datetime_format_has_tz(const char *fmt_str)
4326 : : {
4327 : : bool incache;
49 peter@eisentraut.org 4328 :GNC 33 : size_t fmt_len = strlen(fmt_str);
4329 : : int result;
4330 : : FormatNode *format;
4331 : :
636 amitlan@postgresql.o 4332 [ - + ]:CBC 33 : if (fmt_len > DCH_CACHE_SIZE)
4333 : : {
4334 : : /*
4335 : : * Allocate new memory if format picture is bigger than static cache
4336 : : * and do not use cache (call parser always)
4337 : : */
636 amitlan@postgresql.o 4338 :UBC 0 : incache = false;
4339 : :
4340 : 0 : format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
4341 : :
4342 : 0 : parse_format(format, fmt_str, DCH_keywords,
4343 : : DCH_suff, DCH_index, DCH_FLAG, NULL);
4344 : : }
4345 : : else
4346 : : {
4347 : : /*
4348 : : * Use cache buffers
4349 : : */
636 amitlan@postgresql.o 4350 :CBC 33 : DCHCacheEntry *ent = DCH_cache_fetch(fmt_str, false);
4351 : :
4352 : 33 : incache = true;
4353 : 33 : format = ent->format;
4354 : : }
4355 : :
4356 : 33 : result = DCH_datetime_type(format);
4357 : :
4358 [ - + ]: 33 : if (!incache)
636 amitlan@postgresql.o 4359 :UBC 0 : pfree(format);
4360 : :
636 amitlan@postgresql.o 4361 :CBC 33 : return result & DCH_ZONED;
4362 : : }
4363 : :
4364 : : /*
4365 : : * do_to_timestamp: shared code for to_timestamp and to_date
4366 : : *
4367 : : * Parse the 'date_txt' according to 'fmt', return results as a struct pg_tm,
4368 : : * fractional seconds, struct fmt_tz, and fractional precision.
4369 : : *
4370 : : * 'collid' identifies the collation to use, if needed.
4371 : : * 'std' specifies standard parsing mode.
4372 : : *
4373 : : * Bit mask of date/time/zone components found in 'fmt' is returned in 'flags',
4374 : : * if that is not NULL.
4375 : : *
4376 : : * Returns true on success, false on failure (if escontext points to an
4377 : : * ErrorSaveContext; otherwise errors are thrown). Note that currently,
4378 : : * soft-error behavior is provided for bad data but not bad format.
4379 : : *
4380 : : * We parse 'fmt' into a list of FormatNodes, which is then passed to
4381 : : * DCH_from_char to populate a TmFromChar with the parsed contents of
4382 : : * 'date_txt'.
4383 : : *
4384 : : * The TmFromChar is then analysed and converted into the final results in
4385 : : * struct 'tm', 'fsec', struct 'tz', and 'fprec'.
4386 : : */
4387 : : static bool
49 peter@eisentraut.org 4388 :GNC 20227 : do_to_timestamp(const text *date_txt, const text *fmt, Oid collid, bool std,
4389 : : struct pg_tm *tm, fsec_t *fsec, struct fmt_tz *tz,
4390 : : int *fprec, uint32 *flags, Node *escontext)
4391 : : {
2275 akorotkov@postgresql 4392 :CBC 20227 : FormatNode *format = NULL;
48 peter@eisentraut.org 4393 :GNC 20227 : TmFromChar tmfc = {0};
4394 : : int fmt_len;
4395 : : char *date_str;
4396 : : int fmask;
2275 akorotkov@postgresql 4397 :CBC 20227 : bool incache = false;
4398 : :
2198 michael@paquier.xyz 4399 [ - + ]: 20227 : Assert(tm != NULL);
4400 [ - + ]: 20227 : Assert(fsec != NULL);
4401 : :
3367 tgl@sss.pgh.pa.us 4402 : 20227 : date_str = text_to_cstring(date_txt);
4403 : :
8150 4404 : 20227 : ZERO_tm(tm);
4405 : 20227 : *fsec = 0;
692 4406 : 20227 : tz->has_tz = false;
2198 michael@paquier.xyz 4407 [ + + ]: 20227 : if (fprec)
4408 : 20124 : *fprec = 0;
4409 [ + + ]: 20227 : if (flags)
4410 : 19662 : *flags = 0;
3367 tgl@sss.pgh.pa.us 4411 : 20227 : fmask = 0; /* bit mask for ValidateDate() */
4412 : :
6476 4413 [ - + - - : 20227 : fmt_len = VARSIZE_ANY_EXHDR(fmt);
- - - - -
+ ]
4414 : :
8141 4415 [ + - ]: 20227 : if (fmt_len)
4416 : : {
4417 : : char *fmt_str;
4418 : :
6476 4419 : 20227 : fmt_str = text_to_cstring(fmt);
4420 : :
8141 4421 [ - + ]: 20227 : if (fmt_len > DCH_CACHE_SIZE)
4422 : : {
4423 : : /*
4424 : : * Allocate new memory if format picture is bigger than static
4425 : : * cache and do not use cache (call parser always)
4426 : : */
3367 tgl@sss.pgh.pa.us 4427 :UBC 0 : format = (FormatNode *) palloc((fmt_len + 1) * sizeof(FormatNode));
4428 : :
2275 akorotkov@postgresql 4429 [ # # ]: 0 : parse_format(format, fmt_str, DCH_keywords, DCH_suff, DCH_index,
4430 : : DCH_FLAG | (std ? STD_FLAG : 0), NULL);
4431 : : }
4432 : : else
4433 : : {
4434 : : /*
4435 : : * Use cache buffers
4436 : : */
2275 akorotkov@postgresql 4437 :CBC 20227 : DCHCacheEntry *ent = DCH_cache_fetch(fmt_str, std);
4438 : :
3045 peter_e@gmx.net 4439 : 20224 : incache = true;
9380 bruce@momjian.us 4440 : 20224 : format = ent->format;
4441 : : }
4442 : :
4443 : : #ifdef DEBUG_TO_FROM_CHAR
4444 : : /* dump_node(format, fmt_len); */
4445 : : /* dump_index(DCH_keywords, DCH_index); */
4446 : : #endif
4447 : :
1104 tgl@sss.pgh.pa.us 4448 : 20224 : DCH_from_char(format, date_str, &tmfc, collid, std, escontext);
8141 4449 : 20148 : pfree(fmt_str);
1104 4450 [ + + + - : 20148 : if (SOFT_ERROR_OCCURRED(escontext))
+ + ]
4451 : 15546 : goto fail;
4452 : :
2275 akorotkov@postgresql 4453 [ + + ]: 4602 : if (flags)
1104 tgl@sss.pgh.pa.us 4454 : 4086 : *flags = DCH_datetime_type(format);
4455 : :
8141 4456 [ - + ]: 4602 : if (!incache)
4457 : : {
9458 bruce@momjian.us 4458 :UBC 0 : pfree(format);
2275 akorotkov@postgresql 4459 : 0 : format = NULL;
4460 : : }
4461 : : }
4462 : :
4463 : : DEBUG_TMFC(&tmfc);
4464 : :
4465 : : /*
4466 : : * Convert to_date/to_timestamp input fields to standard 'tm'
4467 : : */
8868 bruce@momjian.us 4468 [ + + ]:CBC 4602 : if (tmfc.ssss)
4469 : : {
8819 4470 : 12 : int x = tmfc.ssss;
4471 : :
7454 4472 : 12 : tm->tm_hour = x / SECS_PER_HOUR;
4473 : 12 : x %= SECS_PER_HOUR;
4474 : 12 : tm->tm_min = x / SECS_PER_MINUTE;
4475 : 12 : x %= SECS_PER_MINUTE;
8150 tgl@sss.pgh.pa.us 4476 : 12 : tm->tm_sec = x;
4477 : : }
4478 : :
8868 bruce@momjian.us 4479 [ + + ]: 4602 : if (tmfc.ss)
8150 tgl@sss.pgh.pa.us 4480 : 678 : tm->tm_sec = tmfc.ss;
8868 bruce@momjian.us 4481 [ + + ]: 4602 : if (tmfc.mi)
8150 tgl@sss.pgh.pa.us 4482 : 3636 : tm->tm_min = tmfc.mi;
8868 bruce@momjian.us 4483 [ + + ]: 4602 : if (tmfc.hh)
8150 tgl@sss.pgh.pa.us 4484 : 3669 : tm->tm_hour = tmfc.hh;
4485 : :
47 peter@eisentraut.org 4486 [ + + ]:GNC 4602 : if (tmfc.clock_12_hour)
4487 : : {
5394 bruce@momjian.us 4488 [ + - + + ]:CBC 60 : if (tm->tm_hour < 1 || tm->tm_hour > HOURS_PER_DAY / 2)
4489 : : {
1104 tgl@sss.pgh.pa.us 4490 [ + - ]: 3 : errsave(escontext,
4491 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
4492 : : errmsg("hour \"%d\" is invalid for the 12-hour clock", tm->tm_hour),
4493 : : errhint("Use the 24-hour clock, or give an hour between 1 and 12.")));
1104 tgl@sss.pgh.pa.us 4494 :UBC 0 : goto fail;
4495 : : }
4496 : :
5394 bruce@momjian.us 4497 [ + + + - ]:CBC 57 : if (tmfc.pm && tm->tm_hour < HOURS_PER_DAY / 2)
4498 : 6 : tm->tm_hour += HOURS_PER_DAY / 2;
4499 [ + - - + ]: 51 : else if (!tmfc.pm && tm->tm_hour == HOURS_PER_DAY / 2)
8150 tgl@sss.pgh.pa.us 4500 :UBC 0 : tm->tm_hour = 0;
4501 : : }
4502 : :
6306 tgl@sss.pgh.pa.us 4503 [ + + ]:CBC 4599 : if (tmfc.year)
4504 : : {
4505 : : /*
4506 : : * If CC and YY (or Y) are provided, use YY as 2 low-order digits for
4507 : : * the year in the given century. Keep in mind that the 21st century
4508 : : * AD runs from 2001-2100, not 2000-2099; 6th century BC runs from
4509 : : * 600BC to 501BC.
4510 : : */
6914 4511 [ + + + - ]: 3015 : if (tmfc.cc && tmfc.yysz <= 2)
4512 : : {
4880 bruce@momjian.us 4513 [ - + ]: 9 : if (tmfc.bc)
4880 bruce@momjian.us 4514 :UBC 0 : tmfc.cc = -tmfc.cc;
6306 tgl@sss.pgh.pa.us 4515 :CBC 9 : tm->tm_year = tmfc.year % 100;
6914 4516 [ + - ]: 9 : if (tm->tm_year)
4517 : : {
4518 : : int tmp;
4519 : :
4880 bruce@momjian.us 4520 [ + + ]: 9 : if (tmfc.cc >= 0)
4521 : : {
4522 : : /* tm->tm_year += (tmfc.cc - 1) * 100; */
373 nathan@postgresql.or 4523 : 6 : tmp = tmfc.cc - 1;
4524 [ + + - + ]: 9 : if (pg_mul_s32_overflow(tmp, 100, &tmp) ||
4525 : 3 : pg_add_s32_overflow(tm->tm_year, tmp, &tm->tm_year))
4526 : : {
4527 : 3 : DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
4528 : 3 : text_to_cstring(date_txt), "timestamp",
4529 : : escontext);
373 nathan@postgresql.or 4530 :UBC 0 : goto fail;
4531 : : }
4532 : : }
4533 : : else
4534 : : {
4535 : : /* tm->tm_year = (tmfc.cc + 1) * 100 - tm->tm_year + 1; */
373 nathan@postgresql.or 4536 :CBC 3 : tmp = tmfc.cc + 1;
4537 [ - + - - ]: 3 : if (pg_mul_s32_overflow(tmp, 100, &tmp) ||
373 nathan@postgresql.or 4538 [ # # ]:UBC 0 : pg_sub_s32_overflow(tmp, tm->tm_year, &tmp) ||
4539 : 0 : pg_add_s32_overflow(tmp, 1, &tm->tm_year))
4540 : : {
373 nathan@postgresql.or 4541 :CBC 3 : DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
4542 : 3 : text_to_cstring(date_txt), "timestamp",
4543 : : escontext);
373 nathan@postgresql.or 4544 :UBC 0 : goto fail;
4545 : : }
4546 : : }
4547 : : }
4548 : : else
4549 : : {
4550 : : /* find century year for dates ending in "00" */
4585 bruce@momjian.us 4551 : 0 : tm->tm_year = tmfc.cc * 100 + ((tmfc.cc >= 0) ? 0 : 1);
4552 : : }
4553 : : }
4554 : : else
4555 : : {
4556 : : /* If a 4-digit year is provided, we use that and ignore CC. */
6306 tgl@sss.pgh.pa.us 4557 :CBC 3006 : tm->tm_year = tmfc.year;
1904 4558 [ + + ]: 3006 : if (tmfc.bc)
4559 : 18 : tm->tm_year = -tm->tm_year;
4560 : : /* correct for our representation of BC years */
4561 [ + + ]: 3006 : if (tm->tm_year < 0)
4562 : 18 : tm->tm_year++;
4563 : : }
3367 4564 : 3009 : fmask |= DTK_M(YEAR);
4565 : : }
4566 [ + + ]: 1584 : else if (tmfc.cc)
4567 : : {
4568 : : /* use first year of century */
4880 bruce@momjian.us 4569 [ - + ]: 6 : if (tmfc.bc)
4880 bruce@momjian.us 4570 :UBC 0 : tmfc.cc = -tmfc.cc;
4880 bruce@momjian.us 4571 [ + + ]:CBC 6 : if (tmfc.cc >= 0)
4572 : : {
4573 : : /* +1 because 21st century started in 2001 */
4574 : : /* tm->tm_year = (tmfc.cc - 1) * 100 + 1; */
373 nathan@postgresql.or 4575 [ - + - - ]: 3 : if (pg_mul_s32_overflow(tmfc.cc - 1, 100, &tm->tm_year) ||
373 nathan@postgresql.or 4576 :UBC 0 : pg_add_s32_overflow(tm->tm_year, 1, &tm->tm_year))
4577 : : {
373 nathan@postgresql.or 4578 :CBC 3 : DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
4579 : 3 : text_to_cstring(date_txt), "timestamp",
4580 : : escontext);
373 nathan@postgresql.or 4581 :UBC 0 : goto fail;
4582 : : }
4583 : : }
4584 : : else
4585 : : {
4586 : : /* +1 because year == 599 is 600 BC */
4587 : : /* tm->tm_year = tmfc.cc * 100 + 1; */
373 nathan@postgresql.or 4588 [ - + - - ]:CBC 3 : if (pg_mul_s32_overflow(tmfc.cc, 100, &tm->tm_year) ||
373 nathan@postgresql.or 4589 :UBC 0 : pg_add_s32_overflow(tm->tm_year, 1, &tm->tm_year))
4590 : : {
373 nathan@postgresql.or 4591 :CBC 3 : DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
4592 : 3 : text_to_cstring(date_txt), "timestamp",
4593 : : escontext);
373 nathan@postgresql.or 4594 :UBC 0 : goto fail;
4595 : : }
4596 : : }
3367 tgl@sss.pgh.pa.us 4597 : 0 : fmask |= DTK_M(YEAR);
4598 : : }
4599 : :
8868 bruce@momjian.us 4600 [ + + ]:CBC 4587 : if (tmfc.j)
4601 : : {
8150 tgl@sss.pgh.pa.us 4602 : 3 : j2date(tmfc.j, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
3367 4603 : 3 : fmask |= DTK_DATE_M;
4604 : : }
4605 : :
6306 4606 [ + + ]: 4587 : if (tmfc.ww)
4607 : : {
4608 [ + + ]: 18 : if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK)
4609 : : {
4610 : : /*
4611 : : * If tmfc.d is not set, then the date is left at the beginning of
4612 : : * the ISO week (Monday).
4613 : : */
4614 [ + - ]: 12 : if (tmfc.d)
4615 : 12 : isoweekdate2date(tmfc.ww, tmfc.d, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
4616 : : else
6306 tgl@sss.pgh.pa.us 4617 :UBC 0 : isoweek2date(tmfc.ww, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
3367 tgl@sss.pgh.pa.us 4618 :CBC 12 : fmask |= DTK_DATE_M;
4619 : : }
4620 : : else
4621 : : {
4622 : : /* tmfc.ddd = (tmfc.ww - 1) * 7 + 1; */
373 nathan@postgresql.or 4623 [ + - + + ]: 12 : if (pg_sub_s32_overflow(tmfc.ww, 1, &tmfc.ddd) ||
4624 [ - + ]: 9 : pg_mul_s32_overflow(tmfc.ddd, 7, &tmfc.ddd) ||
4625 : 3 : pg_add_s32_overflow(tmfc.ddd, 1, &tmfc.ddd))
4626 : : {
4627 : 3 : DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
4628 : : date_str, "timestamp", escontext);
373 nathan@postgresql.or 4629 :UBC 0 : goto fail;
4630 : : }
4631 : : }
4632 : : }
4633 : :
6306 tgl@sss.pgh.pa.us 4634 [ + + ]:CBC 4584 : if (tmfc.w)
4635 : : {
4636 : : /* tmfc.dd = (tmfc.w - 1) * 7 + 1; */
373 nathan@postgresql.or 4637 [ + - + + ]: 12 : if (pg_sub_s32_overflow(tmfc.w, 1, &tmfc.dd) ||
4638 [ - + ]: 9 : pg_mul_s32_overflow(tmfc.dd, 7, &tmfc.dd) ||
4639 : 3 : pg_add_s32_overflow(tmfc.dd, 1, &tmfc.dd))
4640 : : {
4641 : 3 : DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
4642 : : date_str, "timestamp", escontext);
373 nathan@postgresql.or 4643 :UBC 0 : goto fail;
4644 : : }
4645 : : }
8868 bruce@momjian.us 4646 [ + + ]:CBC 4581 : if (tmfc.dd)
4647 : : {
8150 tgl@sss.pgh.pa.us 4648 : 2940 : tm->tm_mday = tmfc.dd;
3367 4649 : 2940 : fmask |= DTK_M(DAY);
4650 : : }
8868 bruce@momjian.us 4651 [ + + ]: 4581 : if (tmfc.mm)
4652 : : {
8150 tgl@sss.pgh.pa.us 4653 : 2958 : tm->tm_mon = tmfc.mm;
3367 4654 : 2958 : fmask |= DTK_M(MONTH);
4655 : : }
4656 : :
8150 4657 [ + + - + : 4581 : if (tmfc.ddd && (tm->tm_mon <= 1 || tm->tm_mday <= 1))
- - ]
4658 : : {
4659 : : /*
4660 : : * The month and day field have not been set, so we use the
4661 : : * day-of-year field to populate them. Depending on the date mode,
4662 : : * this field may be interpreted as a Gregorian day-of-year, or an ISO
4663 : : * week date day-of-year.
4664 : : */
4665 : :
6121 4666 [ - + - - ]: 24 : if (!tm->tm_year && !tmfc.bc)
4667 : : {
1104 tgl@sss.pgh.pa.us 4668 [ # # ]:UBC 0 : errsave(escontext,
4669 : : (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
4670 : : errmsg("cannot calculate day of year without year information")));
4671 : 0 : goto fail;
4672 : : }
4673 : :
6306 tgl@sss.pgh.pa.us 4674 [ + + ]:CBC 24 : if (tmfc.mode == FROM_CHAR_DATE_ISOWEEK)
4675 : : {
4676 : : int j0; /* zeroth day of the ISO year, in Julian */
4677 : :
6121 4678 : 3 : j0 = isoweek2j(tm->tm_year, 1) - 1;
4679 : :
6879 bruce@momjian.us 4680 : 3 : j2date(j0 + tmfc.ddd, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
3367 tgl@sss.pgh.pa.us 4681 : 3 : fmask |= DTK_DATE_M;
4682 : : }
4683 : : else
4684 : : {
4685 : : const int *y;
4686 : : int i;
4687 : :
4688 : : static const int ysum[2][13] = {
4689 : : {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
4690 : : {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}};
4691 : :
6879 bruce@momjian.us 4692 [ + + - + : 21 : y = ysum[isleap(tm->tm_year)];
- - ]
4693 : :
5394 4694 [ + + ]: 246 : for (i = 1; i <= MONTHS_PER_YEAR; i++)
4695 : : {
3367 tgl@sss.pgh.pa.us 4696 [ + + ]: 240 : if (tmfc.ddd <= y[i])
6879 bruce@momjian.us 4697 : 15 : break;
4698 : : }
4699 [ + - ]: 21 : if (tm->tm_mon <= 1)
6121 tgl@sss.pgh.pa.us 4700 : 21 : tm->tm_mon = i;
4701 : :
6879 bruce@momjian.us 4702 [ + - ]: 21 : if (tm->tm_mday <= 1)
6121 tgl@sss.pgh.pa.us 4703 : 21 : tm->tm_mday = tmfc.ddd - y[i - 1];
4704 : :
3367 4705 : 21 : fmask |= DTK_M(MONTH) | DTK_M(DAY);
4706 : : }
4707 : : }
4708 : :
8641 lockhart@fourpalms.o 4709 [ + + ]: 4581 : if (tmfc.ms)
4710 : : {
373 nathan@postgresql.or 4711 : 6 : int tmp = 0;
4712 : :
4713 : : /* *fsec += tmfc.ms * 1000; */
4714 [ + + - + ]: 9 : if (pg_mul_s32_overflow(tmfc.ms, 1000, &tmp) ||
4715 : 3 : pg_add_s32_overflow(*fsec, tmp, fsec))
4716 : : {
4717 : 3 : DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
4718 : : date_str, "timestamp", escontext);
373 nathan@postgresql.or 4719 :UBC 0 : goto fail;
4720 : : }
4721 : : }
8641 lockhart@fourpalms.o 4722 [ + + ]:CBC 4578 : if (tmfc.us)
8150 tgl@sss.pgh.pa.us 4723 : 507 : *fsec += tmfc.us;
2284 akorotkov@postgresql 4724 [ + + ]: 4578 : if (fprec)
4725 : 4491 : *fprec = tmfc.ff; /* fractional precision, if specified */
4726 : :
4727 : : /* Range-check date fields according to bit mask computed above */
3367 tgl@sss.pgh.pa.us 4728 [ + + ]: 4578 : if (fmask != 0)
4729 : : {
4730 : : /* We already dealt with AD/BC, so pass isjulian = true */
4731 : 3012 : int dterr = ValidateDate(fmask, true, false, false, tm);
4732 : :
4733 [ + + ]: 3012 : if (dterr != 0)
4734 : : {
4735 : : /*
4736 : : * Force the error to be DTERR_FIELD_OVERFLOW even if ValidateDate
4737 : : * said DTERR_MD_FIELD_OVERFLOW, because we don't want to print an
4738 : : * irrelevant hint about datestyle.
4739 : : */
1104 4740 : 24 : DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
4741 : : date_str, "timestamp", escontext);
1104 tgl@sss.pgh.pa.us 4742 :UBC 0 : goto fail;
4743 : : }
4744 : : }
4745 : :
4746 : : /* Range-check time fields too */
3367 tgl@sss.pgh.pa.us 4747 [ + - + + ]:CBC 4554 : if (tm->tm_hour < 0 || tm->tm_hour >= HOURS_PER_DAY ||
4748 [ + - + + ]: 4545 : tm->tm_min < 0 || tm->tm_min >= MINS_PER_HOUR ||
4749 [ + - + + ]: 4542 : tm->tm_sec < 0 || tm->tm_sec >= SECS_PER_MINUTE ||
3219 4750 [ + - + + ]: 4539 : *fsec < INT64CONST(0) || *fsec >= USECS_PER_SEC)
4751 : : {
1104 4752 : 18 : DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL,
4753 : : date_str, "timestamp", escontext);
1104 tgl@sss.pgh.pa.us 4754 :UBC 0 : goto fail;
4755 : : }
4756 : :
4757 : : /*
4758 : : * If timezone info was present, reduce it to a GMT offset. (We cannot do
4759 : : * this until we've filled all of the tm struct, since the zone's offset
4760 : : * might be time-varying.)
4761 : : */
2899 andrew@dunslane.net 4762 [ + + ]:CBC 4536 : if (tmfc.tzsign)
4763 : : {
4764 : : /* TZH and/or TZM fields */
4765 [ + - + - ]: 1992 : if (tmfc.tzh < 0 || tmfc.tzh > MAX_TZDISP_HOUR ||
4766 [ + - - + ]: 1992 : tmfc.tzm < 0 || tmfc.tzm >= MINS_PER_HOUR)
4767 : : {
1104 tgl@sss.pgh.pa.us 4768 :UBC 0 : DateTimeParseError(DTERR_TZDISP_OVERFLOW, NULL,
4769 : : date_str, "timestamp", escontext);
4770 : 0 : goto fail;
4771 : : }
4772 : :
692 tgl@sss.pgh.pa.us 4773 :CBC 1992 : tz->has_tz = true;
4774 : 1992 : tz->gmtoffset = (tmfc.tzh * MINS_PER_HOUR + tmfc.tzm) * SECS_PER_MINUTE;
4775 : : /* note we are flipping the sign convention here */
4776 [ + + ]: 1992 : if (tmfc.tzsign > 0)
4777 : 1818 : tz->gmtoffset = -tz->gmtoffset;
4778 : : }
4779 [ + + ]: 2544 : else if (tmfc.has_tz)
4780 : : {
4781 : : /* TZ field */
4782 : 18 : tz->has_tz = true;
4783 [ + + ]: 18 : if (tmfc.tzp == NULL)
4784 : : {
4785 : : /* fixed-offset abbreviation; flip the sign convention */
4786 : 15 : tz->gmtoffset = -tmfc.gmtoffset;
4787 : : }
4788 : : else
4789 : : {
4790 : : /* dynamic-offset abbreviation, resolve using specified time */
4791 : 3 : tz->gmtoffset = DetermineTimeZoneAbbrevOffset(tm, tmfc.abbrev,
4792 : : tmfc.tzp);
4793 : : }
4794 : : }
4795 : :
4796 : : DEBUG_TM(tm);
4797 : :
2275 akorotkov@postgresql 4798 [ + - - + ]: 4536 : if (format && !incache)
2275 akorotkov@postgresql 4799 :UBC 0 : pfree(format);
1104 tgl@sss.pgh.pa.us 4800 :CBC 4536 : pfree(date_str);
4801 : :
4802 : 4536 : return true;
4803 : :
4804 : 15546 : fail:
4805 [ + - - + ]: 15546 : if (format && !incache)
1104 tgl@sss.pgh.pa.us 4806 :UBC 0 : pfree(format);
3367 tgl@sss.pgh.pa.us 4807 :CBC 15546 : pfree(date_str);
4808 : :
1104 4809 : 15546 : return false;
4810 : : }
4811 : :
4812 : :
4813 : : /**********************************************************************
4814 : : * the NUMBER version part
4815 : : *********************************************************************/
4816 : :
4817 : :
4818 : : /*
4819 : : * Fill str with character c max times, and add terminating \0. (So max+1
4820 : : * bytes are written altogether!)
4821 : : */
4822 : : static void
9458 bruce@momjian.us 4823 : 114 : fill_str(char *str, int c, int max)
4824 : : {
4825 : 114 : memset(str, c, max);
49 peter@eisentraut.org 4826 :GNC 114 : str[max] = '\0';
9458 bruce@momjian.us 4827 :GIC 114 : }
4828 : :
4829 : : /* This works the same as DCH_prevent_counter_overflow */
4830 : : static inline void
2619 tgl@sss.pgh.pa.us 4831 :CBC 528301 : NUM_prevent_counter_overflow(void)
4832 : : {
4833 [ - + ]: 528301 : if (NUMCounter >= (INT_MAX - 1))
4834 : : {
2619 tgl@sss.pgh.pa.us 4835 [ # # ]:UBC 0 : for (int i = 0; i < n_NUMCache; i++)
4836 : 0 : NUMCache[i]->age >>= 1;
4837 : 0 : NUMCounter >>= 1;
4838 : : }
2619 tgl@sss.pgh.pa.us 4839 :CBC 528301 : }
4840 : :
4841 : : /* select a NUMCacheEntry to hold the given format picture */
4842 : : static NUMCacheEntry *
3367 4843 : 310 : NUM_cache_getnew(const char *str)
4844 : : {
4845 : : NUMCacheEntry *ent;
4846 : :
4847 : : /* Ensure we can advance NUMCounter below */
2619 4848 : 310 : NUM_prevent_counter_overflow();
4849 : :
4850 : : /*
4851 : : * If cache is full, remove oldest entry (or recycle first not-valid one)
4852 : : */
3367 4853 [ + + ]: 310 : if (n_NUMCache >= NUM_CACHE_ENTRIES)
4854 : : {
2619 4855 : 147 : NUMCacheEntry *old = NUMCache[0];
4856 : :
4857 : : #ifdef DEBUG_TO_FROM_CHAR
4858 : : elog(DEBUG_elog_output, "Cache is full (%d)", n_NUMCache);
4859 : : #endif
3367 4860 [ + - ]: 147 : if (old->valid)
4861 : : {
2619 4862 [ + + ]: 2889 : for (int i = 1; i < NUM_CACHE_ENTRIES; i++)
4863 : : {
4864 : 2745 : ent = NUMCache[i];
3367 4865 [ + + ]: 2745 : if (!ent->valid)
4866 : : {
4867 : 3 : old = ent;
4868 : 3 : break;
4869 : : }
4870 [ + + ]: 2742 : if (ent->age < old->age)
4871 : 147 : old = ent;
4872 : : }
4873 : : }
4874 : : #ifdef DEBUG_TO_FROM_CHAR
4875 : : elog(DEBUG_elog_output, "OLD: \"%s\" AGE: %d", old->str, old->age);
4876 : : #endif
4877 : 147 : old->valid = false;
1955 peter@eisentraut.org 4878 : 147 : strlcpy(old->str, str, NUM_CACHE_SIZE + 1);
9407 bruce@momjian.us 4879 : 147 : old->age = (++NUMCounter);
4880 : : /* caller is expected to fill format and Num, then set valid */
3367 tgl@sss.pgh.pa.us 4881 : 147 : return old;
4882 : : }
4883 : : else
4884 : : {
4885 : : #ifdef DEBUG_TO_FROM_CHAR
4886 : : elog(DEBUG_elog_output, "NEW (%d)", n_NUMCache);
4887 : : #endif
2619 4888 [ - + ]: 163 : Assert(NUMCache[n_NUMCache] == NULL);
4889 : 163 : NUMCache[n_NUMCache] = ent = (NUMCacheEntry *)
4890 : 163 : MemoryContextAllocZero(TopMemoryContext, sizeof(NUMCacheEntry));
3367 4891 : 163 : ent->valid = false;
1955 peter@eisentraut.org 4892 : 163 : strlcpy(ent->str, str, NUM_CACHE_SIZE + 1);
9407 bruce@momjian.us 4893 : 163 : ent->age = (++NUMCounter);
4894 : : /* caller is expected to fill format and Num, then set valid */
4895 : 163 : ++n_NUMCache;
3367 tgl@sss.pgh.pa.us 4896 : 163 : return ent;
4897 : : }
4898 : : }
4899 : :
4900 : : /* look for an existing NUMCacheEntry matching the given format picture */
4901 : : static NUMCacheEntry *
4902 : 527991 : NUM_cache_search(const char *str)
4903 : : {
4904 : : /* Ensure we can advance NUMCounter below */
2619 4905 : 527991 : NUM_prevent_counter_overflow();
4906 : :
4907 [ + + ]: 662721 : for (int i = 0; i < n_NUMCache; i++)
4908 : : {
4909 : 662411 : NUMCacheEntry *ent = NUMCache[i];
4910 : :
3367 4911 [ + + + + ]: 662411 : if (ent->valid && strcmp(ent->str, str) == 0)
4912 : : {
9407 bruce@momjian.us 4913 : 527681 : ent->age = (++NUMCounter);
4914 : 527681 : return ent;
4915 : : }
4916 : : }
4917 : :
8015 neilc@samurai.com 4918 : 310 : return NULL;
4919 : : }
4920 : :
4921 : : /* Find or create a NUMCacheEntry for the given format picture */
4922 : : static NUMCacheEntry *
3367 tgl@sss.pgh.pa.us 4923 : 527991 : NUM_cache_fetch(const char *str)
4924 : : {
4925 : : NUMCacheEntry *ent;
4926 : :
4927 [ + + ]: 527991 : if ((ent = NUM_cache_search(str)) == NULL)
4928 : : {
4929 : : /*
4930 : : * Not in the cache, must run parser and save a new format-picture to
4931 : : * the cache. Do not mark the cache entry valid until parsing
4932 : : * succeeds.
4933 : : */
4934 : 310 : ent = NUM_cache_getnew(str);
4935 : :
48 peter@eisentraut.org 4936 :GNC 310 : memset(&ent->Num, 0, sizeof ent->Num);
4937 : :
3367 tgl@sss.pgh.pa.us 4938 :CBC 310 : parse_format(ent->format, str, NUM_keywords,
4939 : : NULL, NUM_index, NUM_FLAG, &ent->Num);
4940 : :
4941 : 304 : ent->valid = true;
4942 : : }
4943 : 527985 : return ent;
4944 : : }
4945 : :
4946 : : /*
4947 : : * Cache routine for NUM to_char version
4948 : : */
4949 : : static FormatNode *
49 peter@eisentraut.org 4950 :GNC 528141 : NUM_cache(int len, NUMDesc *Num, const text *pars_str, bool *shouldFree)
4951 : : {
9380 bruce@momjian.us 4952 :CBC 528141 : FormatNode *format = NULL;
4953 : : char *str;
4954 : :
6476 tgl@sss.pgh.pa.us 4955 : 528141 : str = text_to_cstring(pars_str);
4956 : :
9380 bruce@momjian.us 4957 [ + + ]: 528141 : if (len > NUM_CACHE_SIZE)
4958 : : {
4959 : : /*
4960 : : * Allocate new memory if format picture is bigger than static cache
4961 : : * and do not use cache (call parser always)
4962 : : */
9458 4963 : 150 : format = (FormatNode *) palloc((len + 1) * sizeof(FormatNode));
4964 : :
8489 4965 : 150 : *shouldFree = true;
4966 : :
48 peter@eisentraut.org 4967 :GNC 150 : memset(Num, 0, sizeof *Num);
4968 : :
9380 bruce@momjian.us 4969 :CBC 150 : parse_format(format, str, NUM_keywords,
4970 : : NULL, NUM_index, NUM_FLAG, Num);
4971 : : }
4972 : : else
4973 : : {
4974 : : /*
4975 : : * Use cache buffers
4976 : : */
3367 tgl@sss.pgh.pa.us 4977 : 527991 : NUMCacheEntry *ent = NUM_cache_fetch(str);
4978 : :
8489 bruce@momjian.us 4979 : 527985 : *shouldFree = false;
4980 : :
9380 4981 : 527985 : format = ent->format;
4982 : :
4983 : : /*
4984 : : * Copy cache to used struct
4985 : : */
4114 4986 : 527985 : Num->flag = ent->Num.flag;
4987 : 527985 : Num->lsign = ent->Num.lsign;
4988 : 527985 : Num->pre = ent->Num.pre;
4989 : 527985 : Num->post = ent->Num.post;
4990 : 527985 : Num->pre_lsign_num = ent->Num.pre_lsign_num;
4991 : 527985 : Num->need_locale = ent->Num.need_locale;
4992 : 527985 : Num->multi = ent->Num.multi;
4993 : 527985 : Num->zero_start = ent->Num.zero_start;
4994 : 527985 : Num->zero_end = ent->Num.zero_end;
4995 : : }
4996 : :
4997 : : #ifdef DEBUG_TO_FROM_CHAR
4998 : : /* dump_node(format, len); */
4999 : : dump_index(NUM_keywords, NUM_index);
5000 : : #endif
5001 : :
9407 5002 : 528135 : pfree(str);
5003 : 528135 : return format;
5004 : : }
5005 : :
5006 : :
5007 : : /*
5008 : : * Convert integer to Roman numerals
5009 : : * Result is upper-case and not blank-padded (NUM_processor converts as needed)
5010 : : * If input is out-of-range, produce '###############'
5011 : : */
5012 : : static char *
9458 5013 : 12066 : int_to_roman(int number)
5014 : : {
5015 : : int len,
5016 : : num;
5017 : : char *result,
5018 : : numstr[12];
5019 : :
329 tgl@sss.pgh.pa.us 5020 : 12066 : result = (char *) palloc(MAX_ROMAN_LEN + 1);
9458 bruce@momjian.us 5021 : 12066 : *result = '\0';
5022 : :
5023 : : /*
5024 : : * This range limit is the same as in Oracle(TM). The difficulty with
5025 : : * handling 4000 or more is that we'd need to use more than 3 "M"'s, and
5026 : : * more than 3 of the same digit isn't considered a valid Roman string.
5027 : : */
9380 5028 [ + + + + ]: 12066 : if (number > 3999 || number < 1)
5029 : : {
329 tgl@sss.pgh.pa.us 5030 : 45 : fill_str(result, '#', MAX_ROMAN_LEN);
9458 bruce@momjian.us 5031 : 45 : return result;
5032 : : }
5033 : :
5034 : : /* Convert to decimal, then examine each digit */
9059 ishii@postgresql.org 5035 : 12021 : len = snprintf(numstr, sizeof(numstr), "%d", number);
447 tgl@sss.pgh.pa.us 5036 [ + - - + ]: 12021 : Assert(len > 0 && len <= 4);
5037 : :
50 peter@eisentraut.org 5038 [ + + ]:GNC 56766 : for (char *p = numstr; *p != '\0'; p++, --len)
5039 : : {
1929 tgl@sss.pgh.pa.us 5040 :CBC 44745 : num = *p - ('0' + 1);
9458 bruce@momjian.us 5041 [ + + ]: 44745 : if (num < 0)
447 tgl@sss.pgh.pa.us 5042 : 3273 : continue; /* ignore zeroes */
5043 : : /* switch on current column position */
5044 [ + + + + : 41472 : switch (len)
- ]
5045 : : {
5046 : 9012 : case 4:
5047 [ + + ]: 27024 : while (num-- >= 0)
5048 : 18012 : strcat(result, "M");
5049 : 9012 : break;
5050 : 10821 : case 3:
9380 bruce@momjian.us 5051 : 10821 : strcat(result, rm100[num]);
447 tgl@sss.pgh.pa.us 5052 : 10821 : break;
5053 : 10818 : case 2:
9458 bruce@momjian.us 5054 : 10818 : strcat(result, rm10[num]);
447 tgl@sss.pgh.pa.us 5055 : 10818 : break;
5056 : 10821 : case 1:
9458 bruce@momjian.us 5057 : 10821 : strcat(result, rm1[num]);
447 tgl@sss.pgh.pa.us 5058 : 10821 : break;
5059 : : }
5060 : : }
9458 bruce@momjian.us 5061 : 12021 : return result;
5062 : : }
5063 : :
5064 : : /*
5065 : : * Convert a roman numeral (standard form) to an integer.
5066 : : * Result is an integer between 1 and 3999.
5067 : : * Np->inout_p is advanced past the characters consumed.
5068 : : *
5069 : : * If input is invalid, return -1.
5070 : : */
5071 : : static int
49 peter@eisentraut.org 5072 :GNC 12054 : roman_to_int(NUMProc *Np, size_t input_len)
5073 : : {
329 tgl@sss.pgh.pa.us 5074 :CBC 12054 : int result = 0;
5075 : : size_t len;
5076 : : char romanChars[MAX_ROMAN_LEN];
5077 : : int romanValues[MAX_ROMAN_LEN];
5078 : 12054 : int repeatCount = 1;
5079 : 12054 : int vCount = 0,
5080 : 12054 : lCount = 0,
5081 : 12054 : dCount = 0;
5082 : 12054 : bool subtractionEncountered = false;
5083 : 12054 : int lastSubtractedValue = 0;
5084 : :
5085 : : /*
5086 : : * Skip any leading whitespace. Perhaps we should limit the amount of
5087 : : * space skipped to MAX_ROMAN_LEN, but that seems unnecessarily picky.
5088 : : */
5089 [ + + + + ]: 102012 : while (!OVERLOAD_TEST && isspace((unsigned char) *Np->inout_p))
5090 : 89958 : Np->inout_p++;
5091 : :
5092 : : /*
5093 : : * Collect and decode valid roman numerals, consuming at most
5094 : : * MAX_ROMAN_LEN characters. We do this in a separate loop to avoid
5095 : : * repeated decoding and because the main loop needs to know when it's at
5096 : : * the last numeral.
5097 : : */
5098 [ + + + + ]: 102231 : for (len = 0; len < MAX_ROMAN_LEN && !OVERLOAD_TEST; len++)
5099 : : {
5100 : 90189 : char currChar = pg_ascii_toupper(*Np->inout_p);
5101 [ + + + + : 90189 : int currValue = ROMAN_VAL(currChar);
+ + + + +
+ + + +
+ ]
5102 : :
5103 [ + + ]: 90189 : if (currValue == 0)
5104 : 12 : break; /* Not a valid roman numeral. */
5105 : 90177 : romanChars[len] = currChar;
5106 : 90177 : romanValues[len] = currValue;
5107 : 90177 : Np->inout_p++;
5108 : : }
5109 : :
5110 [ + + ]: 12054 : if (len == 0)
5111 : 6 : return -1; /* No valid roman numerals. */
5112 : :
5113 : : /* Check for valid combinations and compute the represented value. */
49 peter@eisentraut.org 5114 [ + + ]:GNC 94950 : for (size_t i = 0; i < len; i++)
5115 : : {
329 tgl@sss.pgh.pa.us 5116 :CBC 82938 : char currChar = romanChars[i];
5117 : 82938 : int currValue = romanValues[i];
5118 : :
5119 : : /*
5120 : : * Ensure no numeral greater than or equal to the subtracted numeral
5121 : : * appears after a subtraction.
5122 : : */
5123 [ + + + + ]: 82938 : if (subtractionEncountered && currValue >= lastSubtractedValue)
5124 : 3 : return -1;
5125 : :
5126 : : /*
5127 : : * V, L, and D should not appear before a larger numeral, nor should
5128 : : * they be repeated.
5129 : : */
5130 [ + + + + : 82935 : if ((vCount && currValue >= ROMAN_VAL('V')) ||
+ + ]
5131 [ + - + + ]: 82932 : (lCount && currValue >= ROMAN_VAL('L')) ||
5132 [ - + ]: 28815 : (dCount && currValue >= ROMAN_VAL('D')))
5133 : 3 : return -1;
5134 [ + + ]: 82932 : if (currChar == 'V')
5135 : 4812 : vCount++;
5136 [ + + ]: 78120 : else if (currChar == 'L')
5137 : 4806 : lCount++;
5138 [ + + ]: 73314 : else if (currChar == 'D')
5139 : 4809 : dCount++;
5140 : :
5141 [ + + ]: 82932 : if (i < len - 1)
5142 : : {
5143 : : /* Compare current numeral to next numeral. */
5144 : 73590 : char nextChar = romanChars[i + 1];
5145 : 73590 : int nextValue = romanValues[i + 1];
5146 : :
5147 : : /*
5148 : : * If the current value is less than the next value, handle
5149 : : * subtraction. Verify valid subtractive combinations and update
5150 : : * the result accordingly.
5151 : : */
5152 [ + + ]: 73590 : if (currValue < nextValue)
5153 : : {
5154 [ + + + + : 7236 : if (!IS_VALID_SUB_COMB(currChar, nextChar))
+ + + + +
+ - + + +
+ + - + ]
5155 : 3 : return -1;
5156 : :
5157 : : /*
5158 : : * Reject cases where same numeral is repeated with
5159 : : * subtraction (e.g. 'MCCM' or 'DCCCD').
5160 : : */
5161 [ + + ]: 7233 : if (repeatCount > 1)
5162 : 6 : return -1;
5163 : :
5164 : : /*
5165 : : * We are going to skip nextChar, so first make checks needed
5166 : : * for V, L, and D. These are the same as we'd have applied
5167 : : * if we reached nextChar without a subtraction.
5168 : : */
5169 [ + + - + : 7227 : if ((vCount && nextValue >= ROMAN_VAL('V')) ||
+ + ]
5170 [ + + + + ]: 7221 : (lCount && nextValue >= ROMAN_VAL('L')) ||
5171 [ + + ]: 2406 : (dCount && nextValue >= ROMAN_VAL('D')))
5172 : 18 : return -1;
5173 [ + + ]: 7209 : if (nextChar == 'V')
5174 : 1206 : vCount++;
5175 [ + + ]: 6003 : else if (nextChar == 'L')
5176 : 1200 : lCount++;
5177 [ + + ]: 4803 : else if (nextChar == 'D')
5178 : 1200 : dCount++;
5179 : :
5180 : : /*
5181 : : * Skip the next numeral as it is part of the subtractive
5182 : : * combination.
5183 : : */
5184 : 7209 : i++;
5185 : :
5186 : : /* Update state. */
5187 : 7209 : repeatCount = 1;
5188 : 7209 : subtractionEncountered = true;
5189 : 7209 : lastSubtractedValue = currValue;
5190 : 7209 : result += (nextValue - currValue);
5191 : : }
5192 : : else
5193 : : {
5194 : : /* For same numerals, check for repetition. */
5195 [ + + ]: 66354 : if (currChar == nextChar)
5196 : : {
5197 : 30639 : repeatCount++;
5198 [ + + ]: 30639 : if (repeatCount > 3)
5199 : 3 : return -1;
5200 : : }
5201 : : else
5202 : 35715 : repeatCount = 1;
5203 : 66351 : result += currValue;
5204 : : }
5205 : : }
5206 : : else
5207 : : {
5208 : : /* This is the last numeral; just add it to the result. */
5209 : 9342 : result += currValue;
5210 : : }
5211 : : }
5212 : :
5213 : 12012 : return result;
5214 : : }
5215 : :
5216 : :
5217 : : /*
5218 : : * Locale
5219 : : */
5220 : : static void
9458 bruce@momjian.us 5221 : 527967 : NUM_prepare_locale(NUMProc *Np)
5222 : : {
4114 5223 [ + + ]: 527967 : if (Np->Num->need_locale)
5224 : : {
5225 : : struct lconv *lconv;
5226 : :
5227 : : /*
5228 : : * Get locales
5229 : : */
9458 5230 : 421 : lconv = PGLC_localeconv();
5231 : :
5232 : : /*
5233 : : * Positive / Negative number sign
5234 : : */
5235 [ + - - + ]: 421 : if (lconv->negative_sign && *lconv->negative_sign)
9458 bruce@momjian.us 5236 :UBC 0 : Np->L_negative_sign = lconv->negative_sign;
5237 : : else
9458 bruce@momjian.us 5238 :CBC 421 : Np->L_negative_sign = "-";
5239 : :
7248 5240 [ + - - + ]: 421 : if (lconv->positive_sign && *lconv->positive_sign)
9458 bruce@momjian.us 5241 :UBC 0 : Np->L_positive_sign = lconv->positive_sign;
5242 : : else
9380 bruce@momjian.us 5243 :CBC 421 : Np->L_positive_sign = "+";
5244 : :
5245 : : /*
5246 : : * Number decimal point
5247 : : */
9458 5248 [ + - + - ]: 421 : if (lconv->decimal_point && *lconv->decimal_point)
5249 : 421 : Np->decimal = lconv->decimal_point;
5250 : :
5251 : : else
9458 bruce@momjian.us 5252 :UBC 0 : Np->decimal = ".";
5253 : :
4114 bruce@momjian.us 5254 [ + + ]:CBC 421 : if (!IS_LDECIMAL(Np->Num))
6882 5255 : 354 : Np->decimal = ".";
5256 : :
5257 : : /*
5258 : : * Number thousands separator
5259 : : *
5260 : : * Some locales (e.g. broken glibc pt_BR), have a comma for decimal,
5261 : : * but "" for thousands_sep, so we set the thousands_sep too.
5262 : : * http://archives.postgresql.org/pgsql-hackers/2007-11/msg00772.php
5263 : : */
5264 [ + - - + ]: 421 : if (lconv->thousands_sep && *lconv->thousands_sep)
6882 bruce@momjian.us 5265 :UBC 0 : Np->L_thousands_sep = lconv->thousands_sep;
5266 : : /* Make sure thousands separator doesn't match decimal point symbol. */
2041 tgl@sss.pgh.pa.us 5267 [ + - ]:CBC 421 : else if (strcmp(Np->decimal, ",") != 0)
6882 bruce@momjian.us 5268 : 421 : Np->L_thousands_sep = ",";
5269 : : else
6601 bruce@momjian.us 5270 :UBC 0 : Np->L_thousands_sep = ".";
5271 : :
5272 : : /*
5273 : : * Currency symbol
5274 : : */
9380 bruce@momjian.us 5275 [ + - - + ]:CBC 421 : if (lconv->currency_symbol && *lconv->currency_symbol)
9458 bruce@momjian.us 5276 :UBC 0 : Np->L_currency_symbol = lconv->currency_symbol;
5277 : : else
9380 bruce@momjian.us 5278 :CBC 421 : Np->L_currency_symbol = " ";
5279 : : }
5280 : : else
5281 : : {
5282 : : /*
5283 : : * Default values
5284 : : */
5285 : 527546 : Np->L_negative_sign = "-";
5286 : 527546 : Np->L_positive_sign = "+";
5287 : 527546 : Np->decimal = ".";
5288 : :
5289 : 527546 : Np->L_thousands_sep = ",";
5290 : 527546 : Np->L_currency_symbol = " ";
5291 : : }
9458 5292 : 527967 : }
5293 : :
5294 : : /*
5295 : : * Return pointer of last relevant number after decimal point
5296 : : * 12.0500 --> last relevant is '5'
5297 : : * 12.0000 --> last relevant is '.'
5298 : : * If there is no decimal point, return NULL (which will result in same
5299 : : * behavior as if FM hadn't been specified).
5300 : : */
5301 : : static const char *
49 peter@eisentraut.org 5302 :GNC 339 : get_last_relevant_decnum(const char *num)
5303 : : {
5304 : : const char *result,
9036 bruce@momjian.us 5305 :CBC 339 : *p = strchr(num, '.');
5306 : :
5307 : : #ifdef DEBUG_TO_FROM_CHAR
5308 : : elog(DEBUG_elog_output, "get_last_relevant_decnum()");
5309 : : #endif
5310 : :
9380 5311 [ + + ]: 339 : if (!p)
5215 tgl@sss.pgh.pa.us 5312 : 3 : return NULL;
5313 : :
9444 bruce@momjian.us 5314 : 336 : result = p;
5315 : :
9380 5316 [ + + ]: 4971 : while (*(++p))
5317 : : {
5318 [ + + ]: 4635 : if (*p != '0')
9444 5319 : 972 : result = p;
5320 : : }
5321 : :
5322 : 336 : return result;
5323 : : }
5324 : :
5325 : : /*
5326 : : * Number extraction for TO_NUMBER()
5327 : : */
5328 : : static void
49 peter@eisentraut.org 5329 :GNC 447 : NUM_numpart_from_char(NUMProc *Np, int id, size_t input_len)
5330 : : {
3045 peter_e@gmx.net 5331 :CBC 447 : bool isread = false;
5332 : :
5333 : : #ifdef DEBUG_TO_FROM_CHAR
5334 : : elog(DEBUG_elog_output, " --- scan start --- id=%s",
5335 : : (id == NUM_0 || id == NUM_9) ? "NUM_0/9" : id == NUM_DEC ? "NUM_DEC" : "???");
5336 : : #endif
5337 : :
3418 5338 [ - + ]: 447 : if (OVERLOAD_TEST)
3418 peter_e@gmx.net 5339 :UBC 0 : return;
5340 : :
9380 bruce@momjian.us 5341 [ - + ]:CBC 447 : if (*Np->inout_p == ' ')
9380 bruce@momjian.us 5342 :UBC 0 : Np->inout_p++;
5343 : :
9415 bruce@momjian.us 5344 [ - + ]:CBC 447 : if (OVERLOAD_TEST)
9415 bruce@momjian.us 5345 :UBC 0 : return;
5346 : :
5347 : : /*
5348 : : * read sign before number
5349 : : */
4114 bruce@momjian.us 5350 [ + + + - :CBC 447 : if (*Np->number == ' ' && (id == NUM_0 || id == NUM_9) &&
+ + ]
7368 5351 [ + + ]: 303 : (Np->read_pre + Np->read_post) == 0)
5352 : : {
5353 : : #ifdef DEBUG_TO_FROM_CHAR
5354 : : elog(DEBUG_elog_output, "Try read sign (%c), locale positive: %s, negative: %s",
5355 : : *Np->inout_p, Np->L_positive_sign, Np->L_negative_sign);
5356 : : #endif
5357 : :
5358 : : /*
5359 : : * locale sign
5360 : : */
4114 5361 [ + + + + ]: 87 : if (IS_LSIGN(Np->Num) && Np->Num->lsign == NUM_LSIGN_PRE)
9380 5362 : 6 : {
49 peter@eisentraut.org 5363 :GNC 6 : size_t x = 0;
5364 : :
5365 : : #ifdef DEBUG_TO_FROM_CHAR
5366 : : elog(DEBUG_elog_output, "Try read locale pre-sign (%c)", *Np->inout_p);
5367 : : #endif
7368 bruce@momjian.us 5368 [ + - ]:CBC 6 : if ((x = strlen(Np->L_negative_sign)) &&
7720 tgl@sss.pgh.pa.us 5369 [ + - ]: 6 : AMOUNT_TEST(x) &&
7368 bruce@momjian.us 5370 [ + + ]: 6 : strncmp(Np->inout_p, Np->L_negative_sign, x) == 0)
5371 : : {
7720 tgl@sss.pgh.pa.us 5372 : 3 : Np->inout_p += x;
4114 bruce@momjian.us 5373 : 3 : *Np->number = '-';
5374 : : }
7368 5375 [ + - ]: 3 : else if ((x = strlen(Np->L_positive_sign)) &&
5376 [ + - ]: 3 : AMOUNT_TEST(x) &&
5377 [ - + ]: 3 : strncmp(Np->inout_p, Np->L_positive_sign, x) == 0)
5378 : : {
7720 tgl@sss.pgh.pa.us 5379 :UBC 0 : Np->inout_p += x;
4114 bruce@momjian.us 5380 : 0 : *Np->number = '+';
5381 : : }
5382 : : }
5383 : : else
5384 : : {
5385 : : #ifdef DEBUG_TO_FROM_CHAR
5386 : : elog(DEBUG_elog_output, "Try read simple sign (%c)", *Np->inout_p);
5387 : : #endif
5388 : :
5389 : : /*
5390 : : * simple + - < >
5391 : : */
4114 bruce@momjian.us 5392 [ + + + + ]:CBC 81 : if (*Np->inout_p == '-' || (IS_BRACKET(Np->Num) &&
7720 tgl@sss.pgh.pa.us 5393 [ + - ]: 3 : *Np->inout_p == '<'))
5394 : : {
3101 5395 : 9 : *Np->number = '-'; /* set - */
7720 5396 : 9 : Np->inout_p++;
5397 : : }
5398 [ - + ]: 72 : else if (*Np->inout_p == '+')
5399 : : {
3101 tgl@sss.pgh.pa.us 5400 :UBC 0 : *Np->number = '+'; /* set + */
7720 5401 : 0 : Np->inout_p++;
5402 : : }
5403 : : }
5404 : : }
5405 : :
9415 bruce@momjian.us 5406 [ - + ]:CBC 447 : if (OVERLOAD_TEST)
9415 bruce@momjian.us 5407 :UBC 0 : return;
5408 : :
5409 : : #ifdef DEBUG_TO_FROM_CHAR
5410 : : elog(DEBUG_elog_output, "Scan for numbers (%c), current number: '%s'", *Np->inout_p, Np->number);
5411 : : #endif
5412 : :
5413 : : /*
5414 : : * read digit or decimal point
5415 : : */
9380 bruce@momjian.us 5416 [ + + ]:CBC 447 : if (isdigit((unsigned char) *Np->inout_p))
5417 : : {
4114 5418 [ + + - + ]: 381 : if (Np->read_dec && Np->read_post == Np->Num->post)
9444 bruce@momjian.us 5419 :UBC 0 : return;
5420 : :
4114 bruce@momjian.us 5421 :CBC 381 : *Np->number_p = *Np->inout_p;
5422 : 381 : Np->number_p++;
5423 : :
9444 5424 [ + + ]: 381 : if (Np->read_dec)
5425 : 132 : Np->read_post++;
5426 : : else
7720 tgl@sss.pgh.pa.us 5427 : 249 : Np->read_pre++;
5428 : :
3045 peter_e@gmx.net 5429 : 381 : isread = true;
5430 : :
5431 : : #ifdef DEBUG_TO_FROM_CHAR
5432 : : elog(DEBUG_elog_output, "Read digit (%c)", *Np->inout_p);
5433 : : #endif
5434 : : }
5435 [ + + + + ]: 66 : else if (IS_DECIMAL(Np->Num) && Np->read_dec == false)
5436 : : {
5437 : : /*
5438 : : * We need not test IS_LDECIMAL(Np->Num) explicitly here, because
5439 : : * Np->decimal is always just "." if we don't have a D format token.
5440 : : * So we just unconditionally match to Np->decimal.
5441 : : */
49 peter@eisentraut.org 5442 :GNC 60 : size_t x = strlen(Np->decimal);
5443 : :
5444 : : #ifdef DEBUG_TO_FROM_CHAR
5445 : : elog(DEBUG_elog_output, "Try read decimal point (%c)",
5446 : : *Np->inout_p);
5447 : : #endif
4603 tgl@sss.pgh.pa.us 5448 [ + - + - :CBC 60 : if (x && AMOUNT_TEST(x) && strncmp(Np->inout_p, Np->decimal, x) == 0)
+ + ]
5449 : : {
5450 : 54 : Np->inout_p += x - 1;
4114 bruce@momjian.us 5451 : 54 : *Np->number_p = '.';
5452 : 54 : Np->number_p++;
3045 peter_e@gmx.net 5453 : 54 : Np->read_dec = true;
5454 : 54 : isread = true;
5455 : : }
5456 : : }
5457 : :
7720 tgl@sss.pgh.pa.us 5458 [ - + ]: 447 : if (OVERLOAD_TEST)
7720 tgl@sss.pgh.pa.us 5459 :UBC 0 : return;
5460 : :
5461 : : /*
5462 : : * Read sign behind "last" number
5463 : : *
5464 : : * We need sign detection because determine exact position of post-sign is
5465 : : * difficult:
5466 : : *
5467 : : * FM9999.9999999S -> 123.001- 9.9S -> .5- FM9.999999MI ->
5468 : : * 5.01-
5469 : : */
4114 bruce@momjian.us 5470 [ + + + + ]:CBC 447 : if (*Np->number == ' ' && Np->read_pre + Np->read_post > 0)
5471 : : {
5472 : : /*
5473 : : * locale sign (NUM_S) is always anchored behind a last number, if: -
5474 : : * locale sign expected - last read char was NUM_0/9 or NUM_DEC - and
5475 : : * next char is not digit
5476 : : */
5477 [ + + + - ]: 318 : if (IS_LSIGN(Np->Num) && isread &&
3418 peter_e@gmx.net 5478 [ + - ]: 87 : (Np->inout_p + 1) < Np->inout + input_len &&
7368 bruce@momjian.us 5479 [ + + ]: 87 : !isdigit((unsigned char) *(Np->inout_p + 1)))
7720 tgl@sss.pgh.pa.us 5480 : 39 : {
5481 : : size_t x;
7368 bruce@momjian.us 5482 : 39 : char *tmp = Np->inout_p++;
5483 : :
5484 : : #ifdef DEBUG_TO_FROM_CHAR
5485 : : elog(DEBUG_elog_output, "Try read locale post-sign (%c)", *Np->inout_p);
5486 : : #endif
5487 [ + - ]: 39 : if ((x = strlen(Np->L_negative_sign)) &&
7720 tgl@sss.pgh.pa.us 5488 [ + - ]: 39 : AMOUNT_TEST(x) &&
7368 bruce@momjian.us 5489 [ + + ]: 39 : strncmp(Np->inout_p, Np->L_negative_sign, x) == 0)
5490 : : {
5491 : 18 : Np->inout_p += x - 1; /* -1 .. NUM_processor() do inout_p++ */
4114 5492 : 18 : *Np->number = '-';
5493 : : }
7368 5494 [ + - ]: 21 : else if ((x = strlen(Np->L_positive_sign)) &&
5495 [ + - ]: 21 : AMOUNT_TEST(x) &&
5496 [ - + ]: 21 : strncmp(Np->inout_p, Np->L_positive_sign, x) == 0)
5497 : : {
7368 bruce@momjian.us 5498 :UBC 0 : Np->inout_p += x - 1; /* -1 .. NUM_processor() do inout_p++ */
4114 5499 : 0 : *Np->number = '+';
5500 : : }
4114 bruce@momjian.us 5501 [ + + ]:CBC 39 : if (*Np->number == ' ')
5502 : : /* no sign read */
7720 tgl@sss.pgh.pa.us 5503 : 21 : Np->inout_p = tmp;
5504 : : }
5505 : :
5506 : : /*
5507 : : * try read non-locale sign, which happens only if format is not exact
5508 : : * and we cannot determine sign position of MI/PL/SG, an example:
5509 : : *
5510 : : * FM9.999999MI -> 5.01-
5511 : : *
5512 : : * if (.... && IS_LSIGN(Np->Num)==false) prevents read wrong formats
5513 : : * like to_number('1 -', '9S') where sign is not anchored to last
5514 : : * number.
5515 : : */
3045 peter_e@gmx.net 5516 [ + + + - ]: 279 : else if (isread == false && IS_LSIGN(Np->Num) == false &&
4114 bruce@momjian.us 5517 [ + - + + ]: 12 : (IS_PLUS(Np->Num) || IS_MINUS(Np->Num)))
5518 : : {
5519 : : #ifdef DEBUG_TO_FROM_CHAR
5520 : : elog(DEBUG_elog_output, "Try read simple post-sign (%c)", *Np->inout_p);
5521 : : #endif
5522 : :
5523 : : /*
5524 : : * simple + -
5525 : : */
7720 tgl@sss.pgh.pa.us 5526 [ - + - - ]: 3 : if (*Np->inout_p == '-' || *Np->inout_p == '+')
5527 : : /* NUM_processor() do inout_p++ */
4114 bruce@momjian.us 5528 : 3 : *Np->number = *Np->inout_p;
5529 : : }
5530 : : }
5531 : : }
5532 : :
5533 : : #define IS_PREDEC_SPACE(_n) \
5534 : : (IS_ZERO((_n)->Num)==false && \
5535 : : (_n)->number == (_n)->number_p && \
5536 : : *(_n)->number == '0' && \
5537 : : (_n)->Num->post != 0)
5538 : :
5539 : : /*
5540 : : * Add digit or sign to number-string
5541 : : */
5542 : : static void
9380 5543 : 4438330 : NUM_numpart_to_char(NUMProc *Np, int id)
5544 : : {
5545 : : int end;
5546 : :
4114 5547 [ - + ]: 4438330 : if (IS_ROMAN(Np->Num))
9444 bruce@momjian.us 5548 :UBC 0 : return;
5549 : :
5550 : : /* Note: in this elog() output not set '\0' in 'inout' */
5551 : :
5552 : : #ifdef DEBUG_TO_FROM_CHAR
5553 : :
5554 : : /*
5555 : : * Np->num_curr is number of current item in format-picture, it is not
5556 : : * current position in inout!
5557 : : */
5558 : : elog(DEBUG_elog_output,
5559 : : "SIGN_WROTE: %d, CURRENT: %d, NUMBER_P: \"%s\", INOUT: \"%s\"",
5560 : : Np->sign_wrote,
5561 : : Np->num_curr,
5562 : : Np->number_p,
5563 : : Np->inout);
5564 : : #endif
3045 peter_e@gmx.net 5565 :CBC 4438330 : Np->num_in = false;
5566 : :
5567 : : /*
5568 : : * Write sign if real number will write to output Note: IS_PREDEC_SPACE()
5569 : : * handle "9.9" --> " .1"
5570 : : */
5571 [ + + ]: 4438330 : if (Np->sign_wrote == false &&
4114 bruce@momjian.us 5572 [ + + + + : 7476 : (Np->num_curr >= Np->out_pre_spaces || (IS_ZERO(Np->Num) && Np->Num->zero_start == Np->num_curr)) &&
+ + + + ]
3045 peter_e@gmx.net 5573 [ + + + + : 1521 : (IS_PREDEC_SPACE(Np) == false || (Np->last_relevant && *Np->last_relevant == '.')))
+ + + + +
+ + - ]
5574 : : {
4114 bruce@momjian.us 5575 [ + + ]: 1449 : if (IS_LSIGN(Np->Num))
5576 : : {
5577 [ + + ]: 969 : if (Np->Num->lsign == NUM_LSIGN_PRE)
5578 : : {
8301 5579 [ + + ]: 180 : if (Np->sign == '-')
5580 : 57 : strcpy(Np->inout_p, Np->L_negative_sign);
5581 : : else
5582 : 123 : strcpy(Np->inout_p, Np->L_positive_sign);
5583 : 180 : Np->inout_p += strlen(Np->inout_p);
3045 peter_e@gmx.net 5584 : 180 : Np->sign_wrote = true;
5585 : : }
5586 : : }
4114 bruce@momjian.us 5587 [ + + ]: 480 : else if (IS_BRACKET(Np->Num))
5588 : : {
8301 5589 [ + + ]: 72 : *Np->inout_p = Np->sign == '+' ? ' ' : '<';
9444 5590 : 72 : ++Np->inout_p;
3045 peter_e@gmx.net 5591 : 72 : Np->sign_wrote = true;
5592 : : }
9380 bruce@momjian.us 5593 [ + + ]: 408 : else if (Np->sign == '+')
5594 : : {
4114 5595 [ + - ]: 273 : if (!IS_FILLMODE(Np->Num))
5596 : : {
3101 tgl@sss.pgh.pa.us 5597 : 273 : *Np->inout_p = ' '; /* Write + */
8301 bruce@momjian.us 5598 : 273 : ++Np->inout_p;
5599 : : }
3045 peter_e@gmx.net 5600 : 273 : Np->sign_wrote = true;
5601 : : }
9380 bruce@momjian.us 5602 [ + - ]: 135 : else if (Np->sign == '-')
5603 : : { /* Write - */
9444 5604 : 135 : *Np->inout_p = '-';
9380 5605 : 135 : ++Np->inout_p;
3045 peter_e@gmx.net 5606 : 135 : Np->sign_wrote = true;
5607 : : }
5608 : : }
5609 : :
5610 : :
5611 : : /*
5612 : : * digits / FM / Zero / Dec. point
5613 : : */
8301 bruce@momjian.us 5614 [ + + + + : 4438330 : if (id == NUM_9 || id == NUM_0 || id == NUM_D || id == NUM_DEC)
+ + + - ]
5615 : : {
4121 5616 [ + + ]: 4438330 : if (Np->num_curr < Np->out_pre_spaces &&
4114 5617 [ + + + + ]: 2680991 : (Np->Num->zero_start > Np->num_curr || !IS_ZERO(Np->Num)))
5618 : : {
5619 : : /*
5620 : : * Write blank space
5621 : : */
5622 [ + + ]: 7821 : if (!IS_FILLMODE(Np->Num))
5623 : : {
3101 tgl@sss.pgh.pa.us 5624 : 4944 : *Np->inout_p = ' '; /* Write ' ' */
9458 bruce@momjian.us 5625 : 4944 : ++Np->inout_p;
5626 : : }
5627 : : }
4114 5628 [ + + ]: 4430509 : else if (IS_ZERO(Np->Num) &&
4121 5629 [ + + ]: 4417027 : Np->num_curr < Np->out_pre_spaces &&
4114 5630 [ + - ]: 2673170 : Np->Num->zero_start <= Np->num_curr)
5631 : : {
5632 : : /*
5633 : : * Write ZERO
5634 : : */
9380 5635 : 2673170 : *Np->inout_p = '0'; /* Write '0' */
9444 5636 : 2673170 : ++Np->inout_p;
3045 peter_e@gmx.net 5637 : 2673170 : Np->num_in = true;
5638 : : }
5639 : : else
5640 : : {
5641 : : /*
5642 : : * Write Decimal point
5643 : : */
4114 bruce@momjian.us 5644 [ + + ]: 1757339 : if (*Np->number_p == '.')
5645 : : {
9380 5646 [ + + + + ]: 784 : if (!Np->last_relevant || *Np->last_relevant != '.')
5647 : : {
9444 5648 : 694 : strcpy(Np->inout_p, Np->decimal); /* Write DEC/D */
5649 : 694 : Np->inout_p += strlen(Np->inout_p);
5650 : : }
5651 : :
5652 : : /*
5653 : : * Ora 'n' -- FM9.9 --> 'n.'
5654 : : */
4114 5655 [ + - ]: 90 : else if (IS_FILLMODE(Np->Num) &&
9380 5656 [ + - + - ]: 90 : Np->last_relevant && *Np->last_relevant == '.')
5657 : : {
9444 5658 : 90 : strcpy(Np->inout_p, Np->decimal); /* Write DEC/D */
5659 : 90 : Np->inout_p += strlen(Np->inout_p);
5660 : : }
5661 : : }
5662 : : else
5663 : : {
5664 : : /*
5665 : : * Write Digits
5666 : : */
4114 5667 [ + + + + : 1756555 : if (Np->last_relevant && Np->number_p > Np->last_relevant &&
+ + ]
5668 : : id != NUM_0)
5669 : : ;
5670 : :
5671 : : /*
5672 : : * '0.1' -- 9.9 --> ' .1'
5673 : : */
8301 5674 [ + + + + : 1753219 : else if (IS_PREDEC_SPACE(Np))
+ + + + ]
5675 : : {
4114 5676 [ + + ]: 114 : if (!IS_FILLMODE(Np->Num))
5677 : : {
9444 5678 : 78 : *Np->inout_p = ' ';
5679 : 78 : ++Np->inout_p;
5680 : : }
5681 : :
5682 : : /*
5683 : : * '0' -- FM9.9 --> '0.'
5684 : : */
9380 5685 [ + - + + ]: 36 : else if (Np->last_relevant && *Np->last_relevant == '.')
5686 : : {
9444 5687 : 30 : *Np->inout_p = '0';
5688 : 30 : ++Np->inout_p;
5689 : : }
5690 : : }
5691 : : else
5692 : : {
3101 tgl@sss.pgh.pa.us 5693 : 1753105 : *Np->inout_p = *Np->number_p; /* Write DIGIT */
9444 bruce@momjian.us 5694 : 1753105 : ++Np->inout_p;
3045 peter_e@gmx.net 5695 : 1753105 : Np->num_in = true;
5696 : : }
5697 : : }
5698 : : /* do no exceed string length */
3971 bruce@momjian.us 5699 [ + + ]: 1757339 : if (*Np->number_p)
5700 : 1757168 : ++Np->number_p;
5701 : : }
5702 : :
4114 5703 : 4438330 : end = Np->num_count + (Np->out_pre_spaces ? 1 : 0) + (IS_DECIMAL(Np->Num) ? 1 : 0);
5704 : :
5705 [ + + + + ]: 4438330 : if (Np->last_relevant && Np->last_relevant == Np->number_p)
8301 5706 : 336 : end = Np->num_curr;
5707 : :
8171 5708 [ + + ]: 4438330 : if (Np->num_curr + 1 == end)
5709 : : {
3045 peter_e@gmx.net 5710 [ + + + + ]: 503757 : if (Np->sign_wrote == true && IS_BRACKET(Np->Num))
5711 : : {
8301 bruce@momjian.us 5712 [ + + ]: 72 : *Np->inout_p = Np->sign == '+' ? ' ' : '>';
5713 : 72 : ++Np->inout_p;
5714 : : }
4114 5715 [ + + + + ]: 503685 : else if (IS_LSIGN(Np->Num) && Np->Num->lsign == NUM_LSIGN_POST)
5716 : : {
8301 5717 [ + + ]: 46 : if (Np->sign == '-')
5718 : 25 : strcpy(Np->inout_p, Np->L_negative_sign);
5719 : : else
5720 : 21 : strcpy(Np->inout_p, Np->L_positive_sign);
5721 : 46 : Np->inout_p += strlen(Np->inout_p);
5722 : : }
5723 : : }
5724 : : }
5725 : :
9444 5726 : 4438330 : ++Np->num_curr;
5727 : : }
5728 : :
5729 : : /*
5730 : : * Skip over "n" input characters, but only if they aren't numeric data
5731 : : */
5732 : : static void
49 peter@eisentraut.org 5733 :GNC 18 : NUM_eat_non_data_chars(NUMProc *Np, int n, size_t input_len)
5734 : : {
2952 tgl@sss.pgh.pa.us 5735 [ + + ]:CBC 33 : while (n-- > 0)
5736 : : {
5737 [ - + ]: 21 : if (OVERLOAD_TEST)
2952 tgl@sss.pgh.pa.us 5738 :UBC 0 : break; /* end of input */
2952 tgl@sss.pgh.pa.us 5739 [ + + ]:CBC 21 : if (strchr("0123456789.,+-", *Np->inout_p) != NULL)
5740 : 6 : break; /* it's a data character */
5741 : 15 : Np->inout_p += pg_mblen(Np->inout_p);
5742 : : }
5743 : 18 : }
5744 : :
5745 : : static char *
4114 bruce@momjian.us 5746 : 528135 : NUM_processor(FormatNode *node, NUMDesc *Num, char *inout,
5747 : : char *number, size_t input_len, int to_char_out_pre_spaces,
5748 : : int sign, bool is_to_char, Oid collid)
5749 : : {
5750 : : FormatNode *n;
5751 : : NUMProc _Np,
9036 5752 : 528135 : *Np = &_Np;
5753 : : const char *pattern;
5754 : : size_t pattern_len;
5755 : :
8295 tgl@sss.pgh.pa.us 5756 [ + - + - : 9506430 : MemSet(Np, 0, sizeof(NUMProc));
+ - + - +
+ ]
5757 : :
4114 bruce@momjian.us 5758 : 528135 : Np->Num = Num;
7427 5759 : 528135 : Np->is_to_char = is_to_char;
4114 5760 : 528135 : Np->number = number;
9380 5761 : 528135 : Np->inout = inout;
9444 5762 : 528135 : Np->last_relevant = NULL;
9380 5763 : 528135 : Np->read_post = 0;
7368 5764 : 528135 : Np->read_pre = 0;
3045 peter_e@gmx.net 5765 : 528135 : Np->read_dec = false;
5766 : :
4114 bruce@momjian.us 5767 [ + + ]: 528135 : if (Np->Num->zero_start)
5768 : 502796 : --Np->Num->zero_start;
5769 : :
5770 [ + + ]: 528135 : if (IS_EEEE(Np->Num))
5771 : : {
5973 tgl@sss.pgh.pa.us 5772 [ - + ]: 168 : if (!Np->is_to_char)
5973 tgl@sss.pgh.pa.us 5773 [ # # ]:UBC 0 : ereport(ERROR,
5774 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5775 : : errmsg("\"EEEE\" not supported for input")));
4114 bruce@momjian.us 5776 :CBC 168 : return strcpy(inout, number);
5777 : : }
5778 : :
5779 : : /*
5780 : : * Sign
5781 : : */
7427 5782 [ + + ]: 527967 : if (is_to_char)
5783 : : {
9380 5784 : 515835 : Np->sign = sign;
5785 : :
5786 : : /* MI/PL/SG - write sign itself and not in number */
4114 5787 [ + + + + ]: 515835 : if (IS_PLUS(Np->Num) || IS_MINUS(Np->Num))
5788 : : {
3045 peter_e@gmx.net 5789 [ + + + + ]: 276 : if (IS_PLUS(Np->Num) && IS_MINUS(Np->Num) == false)
5790 : 15 : Np->sign_wrote = false; /* need sign */
5791 : : else
5792 : 261 : Np->sign_wrote = true; /* needn't sign */
5793 : : }
5794 : : else
5795 : : {
8301 bruce@momjian.us 5796 [ + + ]: 515559 : if (Np->sign != '-')
5797 : : {
1065 john.naylor@postgres 5798 [ + + ]: 515297 : if (IS_FILLMODE(Np->Num))
4114 bruce@momjian.us 5799 : 502901 : Np->Num->flag &= ~NUM_F_BRACKET;
5800 : : }
5801 : :
3045 peter_e@gmx.net 5802 [ + + + + : 515559 : if (Np->sign == '+' && IS_FILLMODE(Np->Num) && IS_LSIGN(Np->Num) == false)
+ + ]
5803 : 502799 : Np->sign_wrote = true; /* needn't sign */
5804 : : else
5805 : 12760 : Np->sign_wrote = false; /* need sign */
5806 : :
4114 bruce@momjian.us 5807 [ + + + + ]: 515559 : if (Np->Num->lsign == NUM_LSIGN_PRE && Np->Num->pre == Np->Num->pre_lsign_num)
5808 : 15 : Np->Num->lsign = NUM_LSIGN_POST;
5809 : : }
5810 : : }
5811 : : else
3045 peter_e@gmx.net 5812 : 12132 : Np->sign = false;
5813 : :
5814 : : /*
5815 : : * Count
5816 : : */
4114 bruce@momjian.us 5817 : 527967 : Np->num_count = Np->Num->post + Np->Num->pre - 1;
5818 : :
7427 5819 [ + + ]: 527967 : if (is_to_char)
5820 : : {
4121 5821 : 515835 : Np->out_pre_spaces = to_char_out_pre_spaces;
5822 : :
4114 5823 [ + + + + ]: 515835 : if (IS_FILLMODE(Np->Num) && IS_DECIMAL(Np->Num))
5824 : : {
5825 : 339 : Np->last_relevant = get_last_relevant_decnum(Np->number);
5826 : :
5827 : : /*
5828 : : * If any '0' specifiers are present, make sure we don't strip
5829 : : * those digits. But don't advance last_relevant beyond the last
5830 : : * character of the Np->number string, which is a hazard if the
5831 : : * number got shortened due to precision limitations.
5832 : : */
5833 [ + + + + ]: 339 : if (Np->last_relevant && Np->Num->zero_end > Np->out_pre_spaces)
5834 : : {
5835 : : size_t last_zero_pos;
5836 : : char *last_zero;
5837 : :
5838 : : /* note that Np->number cannot be zero-length here */
1009 tgl@sss.pgh.pa.us 5839 : 138 : last_zero_pos = strlen(Np->number) - 1;
5840 : 138 : last_zero_pos = Min(last_zero_pos,
5841 : : Np->Num->zero_end - Np->out_pre_spaces);
5842 : 138 : last_zero = Np->number + last_zero_pos;
5215 5843 [ + + ]: 138 : if (Np->last_relevant < last_zero)
5844 : 72 : Np->last_relevant = last_zero;
5845 : : }
5846 : : }
5847 : :
3045 peter_e@gmx.net 5848 [ + + + + ]: 515835 : if (Np->sign_wrote == false && Np->out_pre_spaces == 0)
9444 bruce@momjian.us 5849 : 12265 : ++Np->num_count;
5850 : : }
5851 : : else
5852 : : {
4121 5853 : 12132 : Np->out_pre_spaces = 0;
4114 5854 : 12132 : *Np->number = ' '; /* sign space */
5855 : 12132 : *(Np->number + 1) = '\0';
5856 : : }
5857 : :
9380 5858 : 527967 : Np->num_in = 0;
5859 : 527967 : Np->num_curr = 0;
5860 : :
5861 : : #ifdef DEBUG_TO_FROM_CHAR
5862 : : elog(DEBUG_elog_output,
5863 : : "\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\n\tEEEE: %s",
5864 : : Np->sign,
5865 : : Np->number,
5866 : : Np->Num->pre,
5867 : : Np->Num->post,
5868 : : Np->num_count,
5869 : : Np->out_pre_spaces,
5870 : : Np->sign_wrote ? "Yes" : "No",
5871 : : IS_ZERO(Np->Num) ? "Yes" : "No",
5872 : : Np->Num->zero_start,
5873 : : Np->Num->zero_end,
5874 : : Np->last_relevant ? Np->last_relevant : "<not set>",
5875 : : IS_BRACKET(Np->Num) ? "Yes" : "No",
5876 : : IS_PLUS(Np->Num) ? "Yes" : "No",
5877 : : IS_MINUS(Np->Num) ? "Yes" : "No",
5878 : : IS_FILLMODE(Np->Num) ? "Yes" : "No",
5879 : : IS_ROMAN(Np->Num) ? "Yes" : "No",
5880 : : IS_EEEE(Np->Num) ? "Yes" : "No"
5881 : : );
5882 : : #endif
5883 : :
5884 : : /*
5885 : : * Locale
5886 : : */
9458 5887 : 527967 : NUM_prepare_locale(Np);
5888 : :
5889 : : /*
5890 : : * Processor direct cycle
5891 : : */
7427 5892 [ + + ]: 527967 : if (Np->is_to_char)
4114 5893 : 515835 : Np->number_p = Np->number;
5894 : : else
5895 : 12132 : Np->number_p = Np->number + 1; /* first char is space for sign */
5896 : :
9380 5897 [ + + ]: 5499742 : for (n = node, Np->inout_p = Np->inout; n->type != NODE_TYPE_END; n++)
5898 : : {
7427 5899 [ + + ]: 4971862 : if (!Np->is_to_char)
5900 : : {
5901 : : /*
5902 : : * Check at least one byte remains to be scanned. (In actions
5903 : : * below, must use AMOUNT_TEST if we want to read more bytes than
5904 : : * that.)
5905 : : */
2952 tgl@sss.pgh.pa.us 5906 [ + + ]: 12672 : if (OVERLOAD_TEST)
9458 bruce@momjian.us 5907 : 45 : break;
5908 : : }
5909 : :
5910 : : /*
5911 : : * Format pictures actions
5912 : : */
9380 5913 [ + + ]: 4971817 : if (n->type == NODE_TYPE_ACTION)
5914 : : {
5915 : : /*
5916 : : * Create/read digit/zero/blank/sign/special-case
5917 : : *
5918 : : * 'NUM_S' note: The locale sign is anchored to number and we
5919 : : * read/write it when we work with first or last number
5920 : : * (NUM_0/NUM_9). This is why NUM_S is missing in switch().
5921 : : *
5922 : : * Notice the "Np->inout_p++" at the bottom of the loop. This is
5923 : : * why most of the actions advance inout_p one less than you might
5924 : : * expect. In cases where we don't want that increment to happen,
5925 : : * a switch case ends with "continue" not "break".
5926 : : */
5927 [ + + + + : 4967491 : switch (n->key->id)
+ + + + +
+ + ]
5928 : : {
5929 : 4438777 : case NUM_9:
5930 : : case NUM_0:
5931 : : case NUM_DEC:
5932 : : case NUM_D:
7427 5933 [ + + ]: 4438777 : if (Np->is_to_char)
5934 : : {
9380 5935 : 4438330 : NUM_numpart_to_char(Np, n->key->id);
3101 tgl@sss.pgh.pa.us 5936 : 4438330 : continue; /* for() */
5937 : : }
5938 : : else
5939 : : {
2952 5940 : 447 : NUM_numpart_from_char(Np, n->key->id, input_len);
9380 bruce@momjian.us 5941 : 447 : break; /* switch() case: */
5942 : : }
5943 : :
5944 : 183 : case NUM_COMMA:
7427 5945 [ + + ]: 183 : if (Np->is_to_char)
5946 : : {
9380 5947 [ + + ]: 165 : if (!Np->num_in)
5948 : : {
4114 5949 [ - + ]: 60 : if (IS_FILLMODE(Np->Num))
9380 bruce@momjian.us 5950 :UBC 0 : continue;
5951 : : else
9380 bruce@momjian.us 5952 :CBC 60 : *Np->inout_p = ' ';
5953 : : }
5954 : : else
5955 : 105 : *Np->inout_p = ',';
5956 : : }
5957 : : else
5958 : : {
5959 [ + - ]: 18 : if (!Np->num_in)
5960 : : {
4114 5961 [ - + ]: 18 : if (IS_FILLMODE(Np->Num))
9380 bruce@momjian.us 5962 :UBC 0 : continue;
5963 : : }
2952 tgl@sss.pgh.pa.us 5964 [ + - ]:CBC 18 : if (*Np->inout_p != ',')
5965 : 18 : continue;
5966 : : }
9380 bruce@momjian.us 5967 : 165 : break;
5968 : :
5969 : 612 : case NUM_G:
2952 tgl@sss.pgh.pa.us 5970 : 612 : pattern = Np->L_thousands_sep;
5971 : 612 : pattern_len = strlen(pattern);
7427 bruce@momjian.us 5972 [ + + ]: 612 : if (Np->is_to_char)
5973 : : {
9380 5974 [ + + ]: 585 : if (!Np->num_in)
5975 : : {
4114 5976 [ - + ]: 294 : if (IS_FILLMODE(Np->Num))
9380 bruce@momjian.us 5977 :UBC 0 : continue;
5978 : : else
5979 : : {
5980 : : /* just in case there are MB chars */
2952 tgl@sss.pgh.pa.us 5981 :CBC 294 : pattern_len = pg_mbstrlen(pattern);
5982 : 294 : memset(Np->inout_p, ' ', pattern_len);
5983 : 294 : Np->inout_p += pattern_len - 1;
5984 : : }
5985 : : }
5986 : : else
5987 : : {
5988 : 291 : strcpy(Np->inout_p, pattern);
5989 : 291 : Np->inout_p += pattern_len - 1;
5990 : : }
5991 : : }
5992 : : else
5993 : : {
9380 bruce@momjian.us 5994 [ + - ]: 27 : if (!Np->num_in)
5995 : : {
4114 5996 [ - + ]: 27 : if (IS_FILLMODE(Np->Num))
9380 bruce@momjian.us 5997 :UBC 0 : continue;
5998 : : }
5999 : :
6000 : : /*
6001 : : * Because L_thousands_sep typically contains data
6002 : : * characters (either '.' or ','), we can't use
6003 : : * NUM_eat_non_data_chars here. Instead skip only if
6004 : : * the input matches L_thousands_sep.
6005 : : */
2952 tgl@sss.pgh.pa.us 6006 [ + - ]:CBC 27 : if (AMOUNT_TEST(pattern_len) &&
6007 [ + + ]: 27 : strncmp(Np->inout_p, pattern, pattern_len) == 0)
6008 : 24 : Np->inout_p += pattern_len - 1;
6009 : : else
6010 : 3 : continue;
6011 : : }
9380 bruce@momjian.us 6012 : 609 : break;
6013 : :
6014 : 60 : case NUM_L:
2952 tgl@sss.pgh.pa.us 6015 : 60 : pattern = Np->L_currency_symbol;
7427 bruce@momjian.us 6016 [ + + ]: 60 : if (Np->is_to_char)
6017 : : {
2952 tgl@sss.pgh.pa.us 6018 : 45 : strcpy(Np->inout_p, pattern);
6019 : 45 : Np->inout_p += strlen(pattern) - 1;
6020 : : }
6021 : : else
6022 : : {
6023 : 15 : NUM_eat_non_data_chars(Np, pg_mbstrlen(pattern), input_len);
6024 : 15 : continue;
6025 : : }
9380 bruce@momjian.us 6026 : 45 : break;
6027 : :
6028 : 24120 : case NUM_RN:
6029 : : case NUM_rn:
329 tgl@sss.pgh.pa.us 6030 [ + + ]: 24120 : if (Np->is_to_char)
6031 : : {
6032 : : const char *number_p;
6033 : :
6034 [ + + ]: 12066 : if (n->key->id == NUM_rn)
6035 : 15 : number_p = asc_tolower_z(Np->number_p);
6036 : : else
6037 : 12051 : number_p = Np->number_p;
6038 [ + + ]: 12066 : if (IS_FILLMODE(Np->Num))
6039 : 48 : strcpy(Np->inout_p, number_p);
6040 : : else
6041 : 12018 : sprintf(Np->inout_p, "%15s", number_p);
9380 bruce@momjian.us 6042 : 12066 : Np->inout_p += strlen(Np->inout_p) - 1;
6043 : : }
6044 : : else
8778 ishii@postgresql.org 6045 : 12012 : {
329 tgl@sss.pgh.pa.us 6046 : 12054 : int roman_result = roman_to_int(Np, input_len);
6047 : : int numlen;
6048 : :
6049 [ + + ]: 12054 : if (roman_result < 0)
6050 [ + - ]: 42 : ereport(ERROR,
6051 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
6052 : : errmsg("invalid Roman numeral")));
6053 : 12012 : numlen = sprintf(Np->number_p, "%d", roman_result);
6054 : 12012 : Np->number_p += numlen;
6055 : 12012 : Np->Num->pre = numlen;
6056 : 12012 : Np->Num->post = 0;
6057 : 12012 : continue; /* roman_to_int ate all the chars */
6058 : : }
9380 bruce@momjian.us 6059 : 12066 : break;
6060 : :
6061 : 48 : case NUM_th:
4114 6062 [ + - + - ]: 48 : if (IS_ROMAN(Np->Num) || *Np->number == '#' ||
6063 [ + + + + ]: 48 : Np->sign == '-' || IS_DECIMAL(Np->Num))
9380 6064 : 33 : continue;
6065 : :
7427 6066 [ + + ]: 15 : if (Np->is_to_char)
6067 : : {
4114 6068 : 12 : strcpy(Np->inout_p, get_th(Np->number, TH_LOWER));
2952 tgl@sss.pgh.pa.us 6069 : 12 : Np->inout_p += 1;
6070 : : }
6071 : : else
6072 : : {
6073 : : /* All variants of 'th' occupy 2 characters */
6074 : 3 : NUM_eat_non_data_chars(Np, 2, input_len);
6075 : 3 : continue;
6076 : : }
9380 bruce@momjian.us 6077 : 12 : break;
6078 : :
6079 : 45 : case NUM_TH:
4114 6080 [ + - + - ]: 45 : if (IS_ROMAN(Np->Num) || *Np->number == '#' ||
6081 [ + + + + ]: 45 : Np->sign == '-' || IS_DECIMAL(Np->Num))
9380 6082 : 33 : continue;
6083 : :
7427 6084 [ + - ]: 12 : if (Np->is_to_char)
6085 : : {
4114 6086 : 12 : strcpy(Np->inout_p, get_th(Np->number, TH_UPPER));
2952 tgl@sss.pgh.pa.us 6087 : 12 : Np->inout_p += 1;
6088 : : }
6089 : : else
6090 : : {
6091 : : /* All variants of 'TH' occupy 2 characters */
2952 tgl@sss.pgh.pa.us 6092 :UBC 0 : NUM_eat_non_data_chars(Np, 2, input_len);
6093 : 0 : continue;
6094 : : }
9380 bruce@momjian.us 6095 :CBC 12 : break;
6096 : :
6097 : 171 : case NUM_MI:
7427 6098 [ + - ]: 171 : if (Np->is_to_char)
6099 : : {
9380 6100 [ + + ]: 171 : if (Np->sign == '-')
6101 : 48 : *Np->inout_p = '-';
4114 6102 [ - + ]: 123 : else if (IS_FILLMODE(Np->Num))
8301 bruce@momjian.us 6103 :UBC 0 : continue;
6104 : : else
9380 bruce@momjian.us 6105 :CBC 123 : *Np->inout_p = ' ';
6106 : : }
6107 : : else
6108 : : {
9380 bruce@momjian.us 6109 [ # # ]:UBC 0 : if (*Np->inout_p == '-')
4114 6110 : 0 : *Np->number = '-';
6111 : : else
6112 : : {
2952 tgl@sss.pgh.pa.us 6113 : 0 : NUM_eat_non_data_chars(Np, 1, input_len);
6114 : 0 : continue;
6115 : : }
6116 : : }
9380 bruce@momjian.us 6117 :CBC 171 : break;
6118 : :
6119 : 15 : case NUM_PL:
7427 6120 [ + - ]: 15 : if (Np->is_to_char)
6121 : : {
9380 6122 [ + + ]: 15 : if (Np->sign == '+')
6123 : 12 : *Np->inout_p = '+';
4114 6124 [ - + ]: 3 : else if (IS_FILLMODE(Np->Num))
8301 bruce@momjian.us 6125 :UBC 0 : continue;
6126 : : else
9380 bruce@momjian.us 6127 :CBC 3 : *Np->inout_p = ' ';
6128 : : }
6129 : : else
6130 : : {
9380 bruce@momjian.us 6131 [ # # ]:UBC 0 : if (*Np->inout_p == '+')
4114 6132 : 0 : *Np->number = '+';
6133 : : else
6134 : : {
2952 tgl@sss.pgh.pa.us 6135 : 0 : NUM_eat_non_data_chars(Np, 1, input_len);
6136 : 0 : continue;
6137 : : }
6138 : : }
9380 bruce@momjian.us 6139 :CBC 15 : break;
6140 : :
6141 : 90 : case NUM_SG:
7427 6142 [ + - ]: 90 : if (Np->is_to_char)
9380 6143 : 90 : *Np->inout_p = Np->sign;
6144 : : else
6145 : : {
9380 bruce@momjian.us 6146 [ # # ]:UBC 0 : if (*Np->inout_p == '-')
4114 6147 : 0 : *Np->number = '-';
9380 6148 [ # # ]: 0 : else if (*Np->inout_p == '+')
4114 6149 : 0 : *Np->number = '+';
6150 : : else
6151 : : {
2952 tgl@sss.pgh.pa.us 6152 : 0 : NUM_eat_non_data_chars(Np, 1, input_len);
6153 : 0 : continue;
6154 : : }
6155 : : }
9380 bruce@momjian.us 6156 :CBC 90 : break;
6157 : :
6158 : 503370 : default:
6159 : 503370 : continue;
6160 : : break;
6161 : : }
6162 : : }
6163 : : else
6164 : : {
6165 : : /*
6166 : : * In TO_CHAR, non-pattern characters in the format are copied to
6167 : : * the output. In TO_NUMBER, we skip one input character for each
6168 : : * non-pattern format character, whether or not it matches the
6169 : : * format character.
6170 : : */
7427 6171 [ + + ]: 4326 : if (Np->is_to_char)
6172 : : {
2951 tgl@sss.pgh.pa.us 6173 : 4281 : strcpy(Np->inout_p, n->character);
6174 : 4281 : Np->inout_p += strlen(Np->inout_p);
6175 : : }
6176 : : else
6177 : : {
6178 : 45 : Np->inout_p += pg_mblen(Np->inout_p);
6179 : : }
6180 : 4326 : continue;
6181 : : }
9380 bruce@momjian.us 6182 : 13632 : Np->inout_p++;
6183 : : }
6184 : :
7427 6185 [ + + ]: 527925 : if (Np->is_to_char)
6186 : : {
9458 6187 : 515835 : *Np->inout_p = '\0';
9380 6188 : 515835 : return Np->inout;
6189 : : }
6190 : : else
6191 : : {
4114 6192 [ - + ]: 12090 : if (*(Np->number_p - 1) == '.')
4114 bruce@momjian.us 6193 :UBC 0 : *(Np->number_p - 1) = '\0';
6194 : : else
4114 bruce@momjian.us 6195 :CBC 12090 : *Np->number_p = '\0';
6196 : :
6197 : : /*
6198 : : * Correction - precision of dec. number
6199 : : */
6200 : 12090 : Np->Num->post = Np->read_post;
6201 : :
6202 : : #ifdef DEBUG_TO_FROM_CHAR
6203 : : elog(DEBUG_elog_output, "TO_NUMBER (number): '%s'", Np->number);
6204 : : #endif
6205 : 12090 : return Np->number;
6206 : : }
6207 : : }
6208 : :
6209 : : /*
6210 : : * MACRO: Start part of NUM - for all NUM's to_char variants
6211 : : * (sorry, but I hate copy same code - macro is better..)
6212 : : */
6213 : : #define NUM_TOCHAR_prepare \
6214 : : do { \
6215 : : int len = VARSIZE_ANY_EXHDR(fmt); \
6216 : : if (len <= 0 || len >= (INT_MAX-VARHDRSZ)/NUM_MAX_ITEM_SIZ) \
6217 : : PG_RETURN_TEXT_P(cstring_to_text("")); \
6218 : : result = (text *) palloc0((len * NUM_MAX_ITEM_SIZ) + 1 + VARHDRSZ); \
6219 : : format = NUM_cache(len, &Num, fmt, &shouldFree); \
6220 : : } while (0)
6221 : :
6222 : : /*
6223 : : * MACRO: Finish part of NUM
6224 : : */
6225 : : #define NUM_TOCHAR_finish \
6226 : : do { \
6227 : : size_t len; \
6228 : : \
6229 : : NUM_processor(format, &Num, VARDATA(result), numstr, 0, out_pre_spaces, sign, true, PG_GET_COLLATION()); \
6230 : : \
6231 : : if (shouldFree) \
6232 : : pfree(format); \
6233 : : \
6234 : : /* \
6235 : : * Convert null-terminated representation of result to standard text. \
6236 : : * The result is usually much bigger than it needs to be, but there \
6237 : : * seems little point in realloc'ing it smaller. \
6238 : : */ \
6239 : : len = strlen(VARDATA(result)); \
6240 : : SET_VARSIZE(result, len + VARHDRSZ); \
6241 : : } while (0)
6242 : :
6243 : : /*
6244 : : * NUMERIC to_number() (convert string to numeric)
6245 : : */
6246 : : Datum
9300 6247 : 12138 : numeric_to_number(PG_FUNCTION_ARGS)
6248 : : {
3202 noah@leadboat.com 6249 : 12138 : text *value = PG_GETARG_TEXT_PP(0);
6250 : 12138 : text *fmt = PG_GETARG_TEXT_PP(1);
6251 : : NUMDesc Num;
6252 : : Datum result;
6253 : : FormatNode *format;
6254 : : char *numstr;
6255 : : bool shouldFree;
9036 bruce@momjian.us 6256 : 12138 : int len = 0;
6257 : : int scale,
6258 : : precision;
6259 : :
3202 noah@leadboat.com 6260 [ - + - - : 12138 : len = VARSIZE_ANY_EXHDR(fmt);
- - - - -
+ ]
6261 : :
6607 bruce@momjian.us 6262 [ + - - + ]: 12138 : if (len <= 0 || len >= INT_MAX / NUM_MAX_ITEM_SIZ)
9300 bruce@momjian.us 6263 :UBC 0 : PG_RETURN_NULL();
6264 : :
4114 bruce@momjian.us 6265 :CBC 12138 : format = NUM_cache(len, &Num, fmt, &shouldFree);
6266 : :
9380 6267 : 12132 : numstr = (char *) palloc((len * NUM_MAX_ITEM_SIZ) + 1);
6268 : :
3202 noah@leadboat.com 6269 [ - + ]: 12132 : NUM_processor(format, &Num, VARDATA_ANY(value), numstr,
3202 noah@leadboat.com 6270 [ - + - - :ECB (12132) : VARSIZE_ANY_EXHDR(value), 0, 0, false, PG_GET_COLLATION());
- - - - -
+ ]
6271 : :
4114 bruce@momjian.us 6272 :CBC 12090 : scale = Num.post;
3726 6273 : 12090 : precision = Num.pre + Num.multi + scale;
6274 : :
8489 6275 [ - + ]: 12090 : if (shouldFree)
9407 bruce@momjian.us 6276 :UBC 0 : pfree(format);
6277 : :
9300 bruce@momjian.us 6278 :CBC 12090 : result = DirectFunctionCall3(numeric_in,
6279 : : CStringGetDatum(numstr),
6280 : : ObjectIdGetDatum(InvalidOid),
6281 : : Int32GetDatum(((precision << 16) | scale) + VARHDRSZ));
6282 : :
3726 6283 [ + + ]: 12087 : if (IS_MULTI(&Num))
6284 : : {
6285 : : Numeric x;
1925 peter@eisentraut.org 6286 : 3 : Numeric a = int64_to_numeric(10);
6287 : 3 : Numeric b = int64_to_numeric(-Num.multi);
6288 : :
3726 bruce@momjian.us 6289 : 3 : x = DatumGetNumeric(DirectFunctionCall2(numeric_power,
6290 : : NumericGetDatum(a),
6291 : : NumericGetDatum(b)));
6292 : 3 : result = DirectFunctionCall2(numeric_mul,
6293 : : result,
6294 : : NumericGetDatum(x));
6295 : : }
6296 : :
9407 6297 : 12087 : pfree(numstr);
6298 : 12087 : return result;
6299 : : }
6300 : :
6301 : : /*
6302 : : * NUMERIC to_char()
6303 : : */
6304 : : Datum
9300 6305 : 901 : numeric_to_char(PG_FUNCTION_ARGS)
6306 : : {
9036 6307 : 901 : Numeric value = PG_GETARG_NUMERIC(0);
3202 noah@leadboat.com 6308 : 901 : text *fmt = PG_GETARG_TEXT_PP(1);
6309 : : NUMDesc Num;
6310 : : FormatNode *format;
6311 : : text *result;
6312 : : bool shouldFree;
4121 bruce@momjian.us 6313 : 901 : int out_pre_spaces = 0,
9036 6314 : 901 : sign = 0;
6315 : : char *numstr,
6316 : : *orgnum,
6317 : : *p;
6318 : :
9458 6319 [ + - - + : 901 : NUM_TOCHAR_prepare;
- - - - -
+ + - -
+ ]
6320 : :
6321 : : /*
6322 : : * On DateType depend part (numeric)
6323 : : */
4114 6324 [ + + ]: 901 : if (IS_ROMAN(&Num))
6325 : : {
6326 : : int32 intvalue;
103 michael@paquier.xyz 6327 :GNC 39 : ErrorSaveContext escontext = {T_ErrorSaveContext};
6328 : :
6329 : : /* Round and convert to int */
6330 : 39 : intvalue = numeric_int4_safe(value, (Node *) &escontext);
6331 : : /* On overflow, just use PG_INT32_MAX; int_to_roman will cope */
6332 [ + + ]: 39 : if (escontext.error_occurred)
447 tgl@sss.pgh.pa.us 6333 :CBC 3 : intvalue = PG_INT32_MAX;
6334 : 39 : numstr = int_to_roman(intvalue);
6335 : : }
4114 bruce@momjian.us 6336 [ + + ]: 862 : else if (IS_EEEE(&Num))
6337 : : {
6338 : 117 : orgnum = numeric_out_sci(value, Num.post);
6339 : :
6340 : : /*
6341 : : * numeric_out_sci() does not emit a sign for positive numbers. We
6342 : : * need to add a space in this case so that positive and negative
6343 : : * numbers are aligned. Also must check for NaN/infinity cases, which
6344 : : * we handle the same way as in float8_to_char.
6345 : : */
1974 tgl@sss.pgh.pa.us 6346 [ + + ]: 117 : if (strcmp(orgnum, "NaN") == 0 ||
6347 [ + + ]: 114 : strcmp(orgnum, "Infinity") == 0 ||
6348 [ + + ]: 111 : strcmp(orgnum, "-Infinity") == 0)
6349 : : {
6350 : : /*
6351 : : * Allow 6 characters for the leading sign, the decimal point,
6352 : : * "e", the exponent's sign and two exponent digits.
6353 : : */
4114 bruce@momjian.us 6354 : 9 : numstr = (char *) palloc(Num.pre + Num.post + 7);
6355 : 9 : fill_str(numstr, '#', Num.pre + Num.post + 6);
5973 tgl@sss.pgh.pa.us 6356 : 9 : *numstr = ' ';
4114 bruce@momjian.us 6357 : 9 : *(numstr + Num.pre + 1) = '.';
6358 : : }
5973 tgl@sss.pgh.pa.us 6359 [ + + ]: 108 : else if (*orgnum != '-')
6360 : : {
6361 : 96 : numstr = (char *) palloc(strlen(orgnum) + 2);
6362 : 96 : *numstr = ' ';
6363 : 96 : strcpy(numstr + 1, orgnum);
6364 : : }
6365 : : else
6366 : : {
6367 : 12 : numstr = orgnum;
6368 : : }
6369 : : }
6370 : : else
6371 : : {
6372 : : size_t numstr_pre_len;
9380 bruce@momjian.us 6373 : 745 : Numeric val = value;
6374 : : Numeric x;
6375 : :
4114 6376 [ + + ]: 745 : if (IS_MULTI(&Num))
6377 : : {
1925 peter@eisentraut.org 6378 : 3 : Numeric a = int64_to_numeric(10);
6379 : 3 : Numeric b = int64_to_numeric(Num.multi);
6380 : :
9272 tgl@sss.pgh.pa.us 6381 : 3 : x = DatumGetNumeric(DirectFunctionCall2(numeric_power,
6382 : : NumericGetDatum(a),
6383 : : NumericGetDatum(b)));
6384 : 3 : val = DatumGetNumeric(DirectFunctionCall2(numeric_mul,
6385 : : NumericGetDatum(value),
6386 : : NumericGetDatum(x)));
4114 bruce@momjian.us 6387 : 3 : Num.pre += Num.multi;
6388 : : }
6389 : :
9300 6390 : 745 : x = DatumGetNumeric(DirectFunctionCall2(numeric_round,
6391 : : NumericGetDatum(val),
6392 : : Int32GetDatum(Num.post)));
6393 : 745 : orgnum = DatumGetCString(DirectFunctionCall1(numeric_out,
6394 : : NumericGetDatum(x)));
6395 : :
9380 6396 [ + + ]: 745 : if (*orgnum == '-')
6397 : : {
9458 6398 : 211 : sign = '-';
9380 6399 : 211 : numstr = orgnum + 1;
6400 : : }
6401 : : else
6402 : : {
9458 6403 : 534 : sign = '+';
6404 : 534 : numstr = orgnum;
6405 : : }
6406 : :
6407 [ + + ]: 745 : if ((p = strchr(numstr, '.')))
4121 6408 : 598 : numstr_pre_len = p - numstr;
6409 : : else
6410 : 147 : numstr_pre_len = strlen(numstr);
6411 : :
6412 : : /* needs padding? */
4114 6413 [ + + ]: 745 : if (numstr_pre_len < Num.pre)
6414 : 690 : out_pre_spaces = Num.pre - numstr_pre_len;
6415 : : /* overflowed prefix digit format? */
6416 [ + + ]: 55 : else if (numstr_pre_len > Num.pre)
6417 : : {
6418 : 15 : numstr = (char *) palloc(Num.pre + Num.post + 2);
6419 : 15 : fill_str(numstr, '#', Num.pre + Num.post + 1);
6420 : 15 : *(numstr + Num.pre) = '.';
6421 : : }
6422 : : }
6423 : :
9458 6424 [ + + ]: 901 : NUM_TOCHAR_finish;
9300 6425 : 901 : PG_RETURN_TEXT_P(result);
6426 : : }
6427 : :
6428 : : /*
6429 : : * INT4 to_char()
6430 : : */
6431 : : Datum
6432 : 514588 : int4_to_char(PG_FUNCTION_ARGS)
6433 : : {
9036 6434 : 514588 : int32 value = PG_GETARG_INT32(0);
3202 noah@leadboat.com 6435 : 514588 : text *fmt = PG_GETARG_TEXT_PP(1);
6436 : : NUMDesc Num;
6437 : : FormatNode *format;
6438 : : text *result;
6439 : : bool shouldFree;
4121 bruce@momjian.us 6440 : 514588 : int out_pre_spaces = 0,
9036 6441 : 514588 : sign = 0;
6442 : : char *numstr,
6443 : : *orgnum;
6444 : :
9458 6445 [ + - - + : 514588 : NUM_TOCHAR_prepare;
- - - - -
+ + - -
+ ]
6446 : :
6447 : : /*
6448 : : * On DateType depend part (int32)
6449 : : */
4114 6450 [ + + ]: 514588 : if (IS_ROMAN(&Num))
1929 tgl@sss.pgh.pa.us 6451 : 11997 : numstr = int_to_roman(value);
4114 bruce@momjian.us 6452 [ + + ]: 502591 : else if (IS_EEEE(&Num))
6453 : : {
6454 : : /* we can do it easily because float8 won't lose any precision */
5773 6455 : 3 : float8 val = (float8) value;
6456 : :
2834 peter_e@gmx.net 6457 : 3 : orgnum = (char *) psprintf("%+.*e", Num.post, val);
6458 : :
6459 : : /*
6460 : : * Swap a leading positive sign for a space.
6461 : : */
5973 tgl@sss.pgh.pa.us 6462 [ + - ]: 3 : if (*orgnum == '+')
6463 : 3 : *orgnum = ' ';
6464 : :
6465 : 3 : numstr = orgnum;
6466 : : }
6467 : : else
6468 : : {
6469 : : size_t numstr_pre_len;
6470 : :
4114 bruce@momjian.us 6471 [ + + ]: 502588 : if (IS_MULTI(&Num))
6472 : : {
9326 tgl@sss.pgh.pa.us 6473 : 3 : orgnum = DatumGetCString(DirectFunctionCall1(int4out,
6474 : : Int32GetDatum(value * ((int32) pow((double) 10, (double) Num.multi)))));
4114 bruce@momjian.us 6475 : 3 : Num.pre += Num.multi;
6476 : : }
6477 : : else
6478 : : {
9326 tgl@sss.pgh.pa.us 6479 : 502585 : orgnum = DatumGetCString(DirectFunctionCall1(int4out,
6480 : : Int32GetDatum(value)));
6481 : : }
6482 : :
9380 bruce@momjian.us 6483 [ - + ]: 502588 : if (*orgnum == '-')
6484 : : {
9458 bruce@momjian.us 6485 :UBC 0 : sign = '-';
6746 tgl@sss.pgh.pa.us 6486 : 0 : orgnum++;
6487 : : }
6488 : : else
9458 bruce@momjian.us 6489 :CBC 502588 : sign = '+';
6490 : :
4121 6491 : 502588 : numstr_pre_len = strlen(orgnum);
6492 : :
6493 : : /* post-decimal digits? Pad out with zeros. */
4114 6494 [ - + ]: 502588 : if (Num.post)
6495 : : {
4114 bruce@momjian.us 6496 :UBC 0 : numstr = (char *) palloc(numstr_pre_len + Num.post + 2);
6746 tgl@sss.pgh.pa.us 6497 : 0 : strcpy(numstr, orgnum);
4121 bruce@momjian.us 6498 : 0 : *(numstr + numstr_pre_len) = '.';
4114 6499 : 0 : memset(numstr + numstr_pre_len + 1, '0', Num.post);
6500 : 0 : *(numstr + numstr_pre_len + Num.post + 1) = '\0';
6501 : : }
6502 : : else
6746 tgl@sss.pgh.pa.us 6503 :CBC 502588 : numstr = orgnum;
6504 : :
6505 : : /* needs padding? */
4114 bruce@momjian.us 6506 [ + + ]: 502588 : if (numstr_pre_len < Num.pre)
6507 : 495436 : out_pre_spaces = Num.pre - numstr_pre_len;
6508 : : /* overflowed prefix digit format? */
6509 [ - + ]: 7152 : else if (numstr_pre_len > Num.pre)
6510 : : {
4114 bruce@momjian.us 6511 :UBC 0 : numstr = (char *) palloc(Num.pre + Num.post + 2);
6512 : 0 : fill_str(numstr, '#', Num.pre + Num.post + 1);
6513 : 0 : *(numstr + Num.pre) = '.';
6514 : : }
6515 : : }
6516 : :
9458 bruce@momjian.us 6517 [ - + ]:CBC 514588 : NUM_TOCHAR_finish;
9300 6518 : 514588 : PG_RETURN_TEXT_P(result);
6519 : : }
6520 : :
6521 : : /*
6522 : : * INT8 to_char()
6523 : : */
6524 : : Datum
6525 : 355 : int8_to_char(PG_FUNCTION_ARGS)
6526 : : {
9036 6527 : 355 : int64 value = PG_GETARG_INT64(0);
3202 noah@leadboat.com 6528 : 355 : text *fmt = PG_GETARG_TEXT_PP(1);
6529 : : NUMDesc Num;
6530 : : FormatNode *format;
6531 : : text *result;
6532 : : bool shouldFree;
4121 bruce@momjian.us 6533 : 355 : int out_pre_spaces = 0,
9036 6534 : 355 : sign = 0;
6535 : : char *numstr,
6536 : : *orgnum;
6537 : :
9458 6538 [ + - - + : 355 : NUM_TOCHAR_prepare;
- - - - -
+ + - -
+ ]
6539 : :
6540 : : /*
6541 : : * On DateType depend part (int64)
6542 : : */
4114 6543 [ + + ]: 355 : if (IS_ROMAN(&Num))
6544 : : {
6545 : : int32 intvalue;
6546 : :
6547 : : /* On overflow, just use PG_INT32_MAX; int_to_roman will cope */
447 tgl@sss.pgh.pa.us 6548 [ + + + + ]: 15 : if (value <= PG_INT32_MAX && value >= PG_INT32_MIN)
6549 : 6 : intvalue = (int32) value;
6550 : : else
6551 : 9 : intvalue = PG_INT32_MAX;
6552 : 15 : numstr = int_to_roman(intvalue);
6553 : : }
4114 bruce@momjian.us 6554 [ + + ]: 340 : else if (IS_EEEE(&Num))
6555 : : {
6556 : : /* to avoid loss of precision, must go via numeric not float8 */
1925 peter@eisentraut.org 6557 : 6 : orgnum = numeric_out_sci(int64_to_numeric(value),
6558 : : Num.post);
6559 : :
6560 : : /*
6561 : : * numeric_out_sci() does not emit a sign for positive numbers. We
6562 : : * need to add a space in this case so that positive and negative
6563 : : * numbers are aligned. We don't have to worry about NaN/inf here.
6564 : : */
5973 tgl@sss.pgh.pa.us 6565 [ + + ]: 6 : if (*orgnum != '-')
6566 : : {
6567 : 3 : numstr = (char *) palloc(strlen(orgnum) + 2);
6568 : 3 : *numstr = ' ';
6569 : 3 : strcpy(numstr + 1, orgnum);
6570 : : }
6571 : : else
6572 : : {
6573 : 3 : numstr = orgnum;
6574 : : }
6575 : : }
6576 : : else
6577 : : {
6578 : : size_t numstr_pre_len;
6579 : :
4114 bruce@momjian.us 6580 [ + + ]: 334 : if (IS_MULTI(&Num))
6581 : : {
6582 : 3 : double multi = pow((double) 10, (double) Num.multi);
6583 : :
9300 6584 : 3 : value = DatumGetInt64(DirectFunctionCall2(int8mul,
6585 : : Int64GetDatum(value),
6586 : : DirectFunctionCall1(dtoi8,
6587 : : Float8GetDatum(multi))));
4114 6588 : 3 : Num.pre += Num.multi;
6589 : : }
6590 : :
9300 6591 : 334 : orgnum = DatumGetCString(DirectFunctionCall1(int8out,
6592 : : Int64GetDatum(value)));
6593 : :
9380 6594 [ + + ]: 334 : if (*orgnum == '-')
6595 : : {
9458 6596 : 102 : sign = '-';
6746 tgl@sss.pgh.pa.us 6597 : 102 : orgnum++;
6598 : : }
6599 : : else
9458 bruce@momjian.us 6600 : 232 : sign = '+';
6601 : :
4121 6602 : 334 : numstr_pre_len = strlen(orgnum);
6603 : :
6604 : : /* post-decimal digits? Pad out with zeros. */
4114 6605 [ + + ]: 334 : if (Num.post)
6606 : : {
6607 : 105 : numstr = (char *) palloc(numstr_pre_len + Num.post + 2);
6746 tgl@sss.pgh.pa.us 6608 : 105 : strcpy(numstr, orgnum);
4121 bruce@momjian.us 6609 : 105 : *(numstr + numstr_pre_len) = '.';
4114 6610 : 105 : memset(numstr + numstr_pre_len + 1, '0', Num.post);
6611 : 105 : *(numstr + numstr_pre_len + Num.post + 1) = '\0';
6612 : : }
6613 : : else
6746 tgl@sss.pgh.pa.us 6614 : 229 : numstr = orgnum;
6615 : :
6616 : : /* needs padding? */
4114 bruce@momjian.us 6617 [ + + ]: 334 : if (numstr_pre_len < Num.pre)
6618 : 135 : out_pre_spaces = Num.pre - numstr_pre_len;
6619 : : /* overflowed prefix digit format? */
6620 [ - + ]: 199 : else if (numstr_pre_len > Num.pre)
6621 : : {
4114 bruce@momjian.us 6622 :UBC 0 : numstr = (char *) palloc(Num.pre + Num.post + 2);
6623 : 0 : fill_str(numstr, '#', Num.pre + Num.post + 1);
6624 : 0 : *(numstr + Num.pre) = '.';
6625 : : }
6626 : : }
6627 : :
9458 bruce@momjian.us 6628 [ + + ]:CBC 355 : NUM_TOCHAR_finish;
9300 6629 : 355 : PG_RETURN_TEXT_P(result);
6630 : : }
6631 : :
6632 : : /*
6633 : : * FLOAT4 to_char()
6634 : : */
6635 : : Datum
6636 : 74 : float4_to_char(PG_FUNCTION_ARGS)
6637 : : {
9036 6638 : 74 : float4 value = PG_GETARG_FLOAT4(0);
3202 noah@leadboat.com 6639 : 74 : text *fmt = PG_GETARG_TEXT_PP(1);
6640 : : NUMDesc Num;
6641 : : FormatNode *format;
6642 : : text *result;
6643 : : bool shouldFree;
4121 bruce@momjian.us 6644 : 74 : int out_pre_spaces = 0,
9036 6645 : 74 : sign = 0;
6646 : : char *numstr,
6647 : : *p;
6648 : :
9458 6649 [ + - - + : 74 : NUM_TOCHAR_prepare;
- - - - -
+ + - -
+ ]
6650 : :
4114 6651 [ + + ]: 74 : if (IS_ROMAN(&Num))
6652 : : {
6653 : : int32 intvalue;
6654 : :
6655 : : /* See notes in ftoi4() */
447 tgl@sss.pgh.pa.us 6656 : 6 : value = rint(value);
6657 : : /* On overflow, just use PG_INT32_MAX; int_to_roman will cope */
6658 [ + - + - : 6 : if (!isnan(value) && FLOAT4_FITS_IN_INT32(value))
+ + ]
6659 : 3 : intvalue = (int32) value;
6660 : : else
6661 : 3 : intvalue = PG_INT32_MAX;
6662 : 6 : numstr = int_to_roman(intvalue);
6663 : : }
4114 bruce@momjian.us 6664 [ + + ]: 68 : else if (IS_EEEE(&Num))
6665 : : {
2629 tgl@sss.pgh.pa.us 6666 [ + + + + ]: 21 : if (isnan(value) || isinf(value))
6667 : : {
6668 : : /*
6669 : : * Allow 6 characters for the leading sign, the decimal point,
6670 : : * "e", the exponent's sign and two exponent digits.
6671 : : */
4114 bruce@momjian.us 6672 : 9 : numstr = (char *) palloc(Num.pre + Num.post + 7);
6673 : 9 : fill_str(numstr, '#', Num.pre + Num.post + 6);
5973 tgl@sss.pgh.pa.us 6674 : 9 : *numstr = ' ';
4114 bruce@momjian.us 6675 : 9 : *(numstr + Num.pre + 1) = '.';
6676 : : }
6677 : : else
6678 : : {
1930 tgl@sss.pgh.pa.us 6679 : 12 : numstr = psprintf("%+.*e", Num.post, value);
6680 : :
6681 : : /*
6682 : : * Swap a leading positive sign for a space.
6683 : : */
6684 [ + + ]: 12 : if (*numstr == '+')
6685 : 9 : *numstr = ' ';
6686 : : }
6687 : : }
6688 : : else
6689 : : {
9300 bruce@momjian.us 6690 : 47 : float4 val = value;
6691 : : char *orgnum;
6692 : : size_t numstr_pre_len;
6693 : :
4114 6694 [ + + ]: 47 : if (IS_MULTI(&Num))
6695 : : {
6696 : 3 : float multi = pow((double) 10, (double) Num.multi);
6697 : :
9300 6698 : 3 : val = value * multi;
4114 6699 : 3 : Num.pre += Num.multi;
6700 : : }
6701 : :
1930 tgl@sss.pgh.pa.us 6702 : 47 : orgnum = psprintf("%.0f", fabs(val));
3923 bruce@momjian.us 6703 : 47 : numstr_pre_len = strlen(orgnum);
6704 : :
6705 : : /* adjust post digits to fit max float digits */
6706 [ + + ]: 47 : if (numstr_pre_len >= FLT_DIG)
6707 : 21 : Num.post = 0;
6708 [ - + ]: 26 : else if (numstr_pre_len + Num.post > FLT_DIG)
3923 bruce@momjian.us 6709 :UBC 0 : Num.post = FLT_DIG - numstr_pre_len;
2834 peter_e@gmx.net 6710 :CBC 47 : orgnum = psprintf("%.*f", Num.post, val);
6711 : :
9380 bruce@momjian.us 6712 [ + + ]: 47 : if (*orgnum == '-')
6713 : : { /* < 0 */
9458 6714 : 12 : sign = '-';
9380 6715 : 12 : numstr = orgnum + 1;
6716 : : }
6717 : : else
6718 : : {
9458 6719 : 35 : sign = '+';
6720 : 35 : numstr = orgnum;
6721 : : }
6722 : :
6723 [ + + ]: 47 : if ((p = strchr(numstr, '.')))
4121 6724 : 20 : numstr_pre_len = p - numstr;
6725 : : else
6726 : 27 : numstr_pre_len = strlen(numstr);
6727 : :
6728 : : /* needs padding? */
4114 6729 [ + + ]: 47 : if (numstr_pre_len < Num.pre)
6730 : 30 : out_pre_spaces = Num.pre - numstr_pre_len;
6731 : : /* overflowed prefix digit format? */
6732 [ + + ]: 17 : else if (numstr_pre_len > Num.pre)
6733 : : {
6734 : 12 : numstr = (char *) palloc(Num.pre + Num.post + 2);
6735 : 12 : fill_str(numstr, '#', Num.pre + Num.post + 1);
6736 : 12 : *(numstr + Num.pre) = '.';
6737 : : }
6738 : : }
6739 : :
9458 6740 [ - + ]: 74 : NUM_TOCHAR_finish;
9300 6741 : 74 : PG_RETURN_TEXT_P(result);
6742 : : }
6743 : :
6744 : : /*
6745 : : * FLOAT8 to_char()
6746 : : */
6747 : : Datum
6748 : 85 : float8_to_char(PG_FUNCTION_ARGS)
6749 : : {
9036 6750 : 85 : float8 value = PG_GETARG_FLOAT8(0);
3202 noah@leadboat.com 6751 : 85 : text *fmt = PG_GETARG_TEXT_PP(1);
6752 : : NUMDesc Num;
6753 : : FormatNode *format;
6754 : : text *result;
6755 : : bool shouldFree;
4121 bruce@momjian.us 6756 : 85 : int out_pre_spaces = 0,
9036 6757 : 85 : sign = 0;
6758 : : char *numstr,
6759 : : *p;
6760 : :
9458 6761 [ + - - + : 85 : NUM_TOCHAR_prepare;
- - - - -
+ + - -
+ ]
6762 : :
4114 6763 [ + + ]: 85 : if (IS_ROMAN(&Num))
6764 : : {
6765 : : int32 intvalue;
6766 : :
6767 : : /* See notes in dtoi4() */
447 tgl@sss.pgh.pa.us 6768 : 9 : value = rint(value);
6769 : : /* On overflow, just use PG_INT32_MAX; int_to_roman will cope */
6770 [ + - + - : 9 : if (!isnan(value) && FLOAT8_FITS_IN_INT32(value))
+ + ]
6771 : 6 : intvalue = (int32) value;
6772 : : else
6773 : 3 : intvalue = PG_INT32_MAX;
6774 : 9 : numstr = int_to_roman(intvalue);
6775 : : }
4114 bruce@momjian.us 6776 [ + + ]: 76 : else if (IS_EEEE(&Num))
6777 : : {
2629 tgl@sss.pgh.pa.us 6778 [ + + + + ]: 21 : if (isnan(value) || isinf(value))
6779 : : {
6780 : : /*
6781 : : * Allow 6 characters for the leading sign, the decimal point,
6782 : : * "e", the exponent's sign and two exponent digits.
6783 : : */
4114 bruce@momjian.us 6784 : 9 : numstr = (char *) palloc(Num.pre + Num.post + 7);
6785 : 9 : fill_str(numstr, '#', Num.pre + Num.post + 6);
5973 tgl@sss.pgh.pa.us 6786 : 9 : *numstr = ' ';
4114 bruce@momjian.us 6787 : 9 : *(numstr + Num.pre + 1) = '.';
6788 : : }
6789 : : else
6790 : : {
1930 tgl@sss.pgh.pa.us 6791 : 12 : numstr = psprintf("%+.*e", Num.post, value);
6792 : :
6793 : : /*
6794 : : * Swap a leading positive sign for a space.
6795 : : */
6796 [ + + ]: 12 : if (*numstr == '+')
6797 : 9 : *numstr = ' ';
6798 : : }
6799 : : }
6800 : : else
6801 : : {
9300 bruce@momjian.us 6802 : 55 : float8 val = value;
6803 : : char *orgnum;
6804 : : size_t numstr_pre_len;
6805 : :
4114 6806 [ + + ]: 55 : if (IS_MULTI(&Num))
6807 : : {
6808 : 3 : double multi = pow((double) 10, (double) Num.multi);
6809 : :
9300 6810 : 3 : val = value * multi;
4114 6811 : 3 : Num.pre += Num.multi;
6812 : : }
6813 : :
2834 peter_e@gmx.net 6814 : 55 : orgnum = psprintf("%.0f", fabs(val));
6815 : 55 : numstr_pre_len = strlen(orgnum);
6816 : :
6817 : : /* adjust post digits to fit max double digits */
3923 bruce@momjian.us 6818 [ + + ]: 55 : if (numstr_pre_len >= DBL_DIG)
6819 : 3 : Num.post = 0;
6820 [ + + ]: 52 : else if (numstr_pre_len + Num.post > DBL_DIG)
6821 : 3 : Num.post = DBL_DIG - numstr_pre_len;
2834 peter_e@gmx.net 6822 : 55 : orgnum = psprintf("%.*f", Num.post, val);
6823 : :
9380 bruce@momjian.us 6824 [ + + ]: 55 : if (*orgnum == '-')
6825 : : { /* < 0 */
9458 6826 : 12 : sign = '-';
9380 6827 : 12 : numstr = orgnum + 1;
6828 : : }
6829 : : else
6830 : : {
9458 6831 : 43 : sign = '+';
6832 : 43 : numstr = orgnum;
6833 : : }
6834 : :
6835 [ + + ]: 55 : if ((p = strchr(numstr, '.')))
4121 6836 : 31 : numstr_pre_len = p - numstr;
6837 : : else
6838 : 24 : numstr_pre_len = strlen(numstr);
6839 : :
6840 : : /* needs padding? */
4114 6841 [ + + ]: 55 : if (numstr_pre_len < Num.pre)
6842 : 33 : out_pre_spaces = Num.pre - numstr_pre_len;
6843 : : /* overflowed prefix digit format? */
6844 [ + + ]: 22 : else if (numstr_pre_len > Num.pre)
6845 : : {
6846 : 15 : numstr = (char *) palloc(Num.pre + Num.post + 2);
6847 : 15 : fill_str(numstr, '#', Num.pre + Num.post + 1);
6848 : 15 : *(numstr + Num.pre) = '.';
6849 : : }
6850 : : }
6851 : :
9458 6852 [ - + ]: 85 : NUM_TOCHAR_finish;
9300 6853 : 85 : PG_RETURN_TEXT_P(result);
6854 : : }
|