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