Age Owner Branch data TLA Line data Source code
1 : : /*
2 : : * src/interfaces/ecpg/pgtypeslib/timestamp.c
3 : : */
4 : : #include "postgres_fe.h"
5 : :
6 : : #include <time.h>
7 : : #include <limits.h>
8 : : #include <math.h>
9 : :
10 : : #include "common/int.h"
11 : : #include "dt.h"
12 : : #include "pgtypes_date.h"
13 : : #include "pgtypes_timestamp.h"
14 : : #include "pgtypeslib_extern.h"
15 : :
16 : : static int64
8447 meskes@postgresql.or 17 :CBC 147 : time2t(const int hour, const int min, const int sec, const fsec_t fsec)
18 : : {
7593 bruce@momjian.us 19 : 147 : return (((((hour * MINS_PER_HOUR) + min) * SECS_PER_MINUTE) + sec) * USECS_PER_SEC) + fsec;
20 : : } /* time2t() */
21 : :
22 : : static timestamp
8274 meskes@postgresql.or 23 : 21 : dt2local(timestamp dt, int tz)
24 : : {
7652 bruce@momjian.us 25 : 21 : dt -= (tz * USECS_PER_SEC);
8435 meskes@postgresql.or 26 : 21 : return dt;
27 : : } /* dt2local() */
28 : :
29 : : /* tm2timestamp()
30 : : * Convert a tm structure to a timestamp data type.
31 : : * Note that year is _not_ 1900-based, but is an explicit full value.
32 : : * Also, month is one-based, _not_ zero-based.
33 : : *
34 : : * Returns -1 on failure (overflow).
35 : : */
36 : : int
3240 tgl@sss.pgh.pa.us 37 : 147 : tm2timestamp(struct tm *tm, fsec_t fsec, int *tzp, timestamp * result)
38 : : {
39 : : int dDate;
40 : : int64 time;
41 : :
42 : : /* Prevent overflow in Julian-day routines */
8447 meskes@postgresql.or 43 [ - + - - : 147 : if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday))
- - - + -
- - - ]
8447 meskes@postgresql.or 44 :UBC 0 : return -1;
45 : :
8274 meskes@postgresql.or 46 :CBC 147 : dDate = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - date2j(2000, 1, 1);
8447 47 : 147 : time = time2t(tm->tm_hour, tm->tm_min, tm->tm_sec, fsec);
628 nathan@postgresql.or 48 [ + - - + : 147 : if (unlikely(pg_mul_s64_overflow(dDate, USECS_PER_DAY, result) ||
- + ]
49 : : pg_add_s64_overflow(*result, time, result)))
8341 tgl@sss.pgh.pa.us 50 :UBC 0 : return -1;
8447 meskes@postgresql.or 51 [ + + ]:CBC 147 : if (tzp != NULL)
52 : 21 : *result = dt2local(*result, -(*tzp));
53 : :
54 : : /* final range check catches just-out-of-range timestamps */
3702 tgl@sss.pgh.pa.us 55 [ + - - + ]: 147 : if (!IS_VALID_TIMESTAMP(*result))
3702 tgl@sss.pgh.pa.us 56 :UBC 0 : return -1;
57 : :
8447 meskes@postgresql.or 58 :CBC 147 : return 0;
59 : : } /* tm2timestamp() */
60 : :
61 : : static timestamp
8447 meskes@postgresql.or 62 :UBC 0 : SetEpochTimestamp(void)
63 : : {
6299 64 : 0 : int64 noresult = 0;
65 : : timestamp dt;
66 : : struct tm tt,
8310 bruce@momjian.us 67 : 0 : *tm = &tt;
68 : :
6299 meskes@postgresql.or 69 [ # # ]: 0 : if (GetEpochTime(tm) < 0)
70 : 0 : return noresult;
71 : :
8447 72 : 0 : tm2timestamp(tm, 0, NULL, &dt);
8435 73 : 0 : return dt;
74 : : } /* SetEpochTimestamp() */
75 : :
76 : : /* timestamp2tm()
77 : : * Convert timestamp data type to POSIX time structure.
78 : : * Note that year is _not_ 1900-based, but is an explicit full value.
79 : : * Also, month is one-based, _not_ zero-based.
80 : : * Returns:
81 : : * 0 on success
82 : : * -1 on out of range
83 : : *
84 : : * For dates within the system-supported time_t range, convert to the
85 : : * local time zone. If out of this range, leave as GMT. - tgl 97/05/27
86 : : */
87 : : static int
3240 tgl@sss.pgh.pa.us 88 :CBC 164 : timestamp2tm(timestamp dt, int *tzp, struct tm *tm, fsec_t *fsec, const char **tzn)
89 : : {
90 : : int64 dDate,
91 : : date0;
92 : : int64 time;
93 : : #if defined(HAVE_STRUCT_TM_TM_ZONE) || defined(HAVE_INT_TIMEZONE)
94 : : time_t utime;
95 : : struct tm *tx;
96 : : #endif
97 : :
8447 meskes@postgresql.or 98 : 164 : date0 = date2j(2000, 1, 1);
99 : :
7513 tgl@sss.pgh.pa.us 100 : 164 : time = dt;
7652 bruce@momjian.us 101 [ + + ]: 164 : TMODULO(time, dDate, USECS_PER_DAY);
102 : :
8447 meskes@postgresql.or 103 [ + + ]: 164 : if (time < INT64CONST(0))
104 : : {
7652 bruce@momjian.us 105 : 92 : time += USECS_PER_DAY;
8274 meskes@postgresql.or 106 : 92 : dDate -= 1;
107 : : }
108 : :
109 : : /* add offset to go from J2000 back to standard Julian date */
7513 tgl@sss.pgh.pa.us 110 : 164 : dDate += date0;
111 : :
112 : : /* Julian day routine does not work for negative Julian days */
113 [ + - - + ]: 164 : if (dDate < 0 || dDate > (timestamp) INT_MAX)
7513 tgl@sss.pgh.pa.us 114 :UBC 0 : return -1;
115 : :
7513 tgl@sss.pgh.pa.us 116 :CBC 164 : j2date((int) dDate, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
117 : 164 : dt2time(time, &tm->tm_hour, &tm->tm_min, &tm->tm_sec, fsec);
118 : :
8447 meskes@postgresql.or 119 [ - + ]: 164 : if (tzp != NULL)
120 : : {
121 : : /*
122 : : * Does this fall within the capabilities of the localtime()
123 : : * interface? Then use this to rotate to the local time zone.
124 : : */
8447 meskes@postgresql.or 125 [ # # # # :UBC 0 : if (IS_VALID_UTIME(tm->tm_year, tm->tm_mon, tm->tm_mday))
# # # # #
# # # # #
# # # # #
# ]
126 : 0 : {
127 : : #if defined(HAVE_STRUCT_TM_TM_ZONE) || defined(HAVE_INT_TIMEZONE)
128 : : struct tm tmbuf;
129 : :
7651 bruce@momjian.us 130 : 0 : utime = dt / USECS_PER_SEC +
131 : 0 : ((date0 - date2j(1970, 1, 1)) * INT64CONST(86400));
132 : :
620 peter@eisentraut.org 133 : 0 : tx = localtime_r(&utime, &tmbuf);
8447 meskes@postgresql.or 134 : 0 : tm->tm_year = tx->tm_year + 1900;
135 : 0 : tm->tm_mon = tx->tm_mon + 1;
136 : 0 : tm->tm_mday = tx->tm_mday;
137 : 0 : tm->tm_hour = tx->tm_hour;
138 : 0 : tm->tm_min = tx->tm_min;
139 : 0 : tm->tm_isdst = tx->tm_isdst;
140 : :
141 : : #if defined(HAVE_STRUCT_TM_TM_ZONE)
142 : 0 : tm->tm_gmtoff = tx->tm_gmtoff;
143 : 0 : tm->tm_zone = tx->tm_zone;
144 : :
3240 tgl@sss.pgh.pa.us 145 : 0 : *tzp = -tm->tm_gmtoff; /* tm_gmtoff is Sun/DEC-ism */
8447 meskes@postgresql.or 146 [ # # ]: 0 : if (tzn != NULL)
5164 peter_e@gmx.net 147 : 0 : *tzn = tm->tm_zone;
148 : : #elif defined(HAVE_INT_TIMEZONE)
149 : : *tzp = (tm->tm_isdst > 0) ? TIMEZONE_GLOBAL - SECS_PER_HOUR : TIMEZONE_GLOBAL;
150 : : if (tzn != NULL)
151 : : *tzn = TZNAME_GLOBAL[(tm->tm_isdst > 0)];
152 : : #endif
153 : : #else /* not (HAVE_STRUCT_TM_TM_ZONE ||
154 : : * HAVE_INT_TIMEZONE) */
155 : : *tzp = 0;
156 : : /* Mark this as *no* time zone available */
157 : : tm->tm_isdst = -1;
158 : : if (tzn != NULL)
159 : : *tzn = NULL;
160 : : #endif
161 : : }
162 : : else
163 : : {
8447 meskes@postgresql.or 164 : 0 : *tzp = 0;
165 : : /* Mark this as *no* time zone available */
166 : 0 : tm->tm_isdst = -1;
167 [ # # ]: 0 : if (tzn != NULL)
168 : 0 : *tzn = NULL;
169 : : }
170 : : }
171 : : else
172 : : {
8447 meskes@postgresql.or 173 :CBC 164 : tm->tm_isdst = -1;
174 [ - + ]: 164 : if (tzn != NULL)
8447 meskes@postgresql.or 175 :UBC 0 : *tzn = NULL;
176 : : }
177 : :
4673 meskes@postgresql.or 178 :CBC 164 : tm->tm_yday = dDate - date2j(tm->tm_year, 1, 1) + 1;
179 : :
8447 180 : 164 : return 0;
181 : : } /* timestamp2tm() */
182 : :
183 : : /* EncodeSpecialTimestamp()
184 : : * * Convert reserved timestamp data type to string.
185 : : * */
186 : : static void
8274 meskes@postgresql.or 187 :UBC 0 : EncodeSpecialTimestamp(timestamp dt, char *str)
188 : : {
8435 189 [ # # ]: 0 : if (TIMESTAMP_IS_NOBEGIN(dt))
190 : 0 : strcpy(str, EARLY);
191 [ # # ]: 0 : else if (TIMESTAMP_IS_NOEND(dt))
192 : 0 : strcpy(str, LATE);
193 : : else
3158 peter_e@gmx.net 194 : 0 : abort(); /* shouldn't happen */
195 : 0 : }
196 : :
197 : : timestamp
8435 meskes@postgresql.or 198 :CBC 126 : PGTYPEStimestamp_from_asc(char *str, char **endptr)
199 : : {
200 : : timestamp result;
8447 201 : 126 : int64 noresult = 0;
202 : : fsec_t fsec;
203 : : struct tm tt,
8310 bruce@momjian.us 204 : 126 : *tm = &tt;
205 : : int dtype;
206 : : int nf;
207 : : char *field[MAXDATEFIELDS];
208 : : int ftype[MAXDATEFIELDS];
209 : : char lowstr[MAXDATELEN + MAXDATEFIELDS];
210 : : char *realptr;
211 [ + + ]: 126 : char **ptr = (endptr != NULL) ? endptr : &realptr;
212 : :
4460 noah@leadboat.com 213 [ - + ]: 126 : if (strlen(str) > MAXDATELEN)
214 : : {
8437 meskes@postgresql.or 215 :UBC 0 : errno = PGTYPES_TS_BAD_TIMESTAMP;
3183 peter_e@gmx.net 216 : 0 : return noresult;
217 : : }
218 : :
6194 meskes@postgresql.or 219 [ + - + + ]:CBC 252 : if (ParseDateTime(str, lowstr, field, ftype, &nf, ptr) != 0 ||
6839 220 : 126 : DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, 0) != 0)
221 : : {
8310 bruce@momjian.us 222 : 2 : errno = PGTYPES_TS_BAD_TIMESTAMP;
3183 peter_e@gmx.net 223 : 2 : return noresult;
224 : : }
225 : :
8447 meskes@postgresql.or 226 [ + - - - : 124 : switch (dtype)
- ]
227 : : {
228 : 124 : case DTK_DATE:
229 [ - + ]: 124 : if (tm2timestamp(tm, fsec, NULL, &result) != 0)
230 : : {
8437 meskes@postgresql.or 231 :UBC 0 : errno = PGTYPES_TS_BAD_TIMESTAMP;
3183 peter_e@gmx.net 232 : 0 : return noresult;
233 : : }
8447 meskes@postgresql.or 234 :CBC 124 : break;
235 : :
8447 meskes@postgresql.or 236 :UBC 0 : case DTK_EPOCH:
237 : 0 : result = SetEpochTimestamp();
238 : 0 : break;
239 : :
240 : 0 : case DTK_LATE:
241 : 0 : TIMESTAMP_NOEND(result);
242 : 0 : break;
243 : :
244 : 0 : case DTK_EARLY:
245 : 0 : TIMESTAMP_NOBEGIN(result);
246 : 0 : break;
247 : :
248 : 0 : default:
8437 249 : 0 : errno = PGTYPES_TS_BAD_TIMESTAMP;
3183 peter_e@gmx.net 250 : 0 : return noresult;
251 : : }
252 : :
253 : : /* AdjustTimestampForTypmod(&result, typmod); */
254 : :
255 : : /*
256 : : * Since it's difficult to test for noresult, make sure errno is 0 if no
257 : : * error occurred.
258 : : */
8106 meskes@postgresql.or 259 :CBC 124 : errno = 0;
8447 260 : 124 : return result;
261 : : }
262 : :
263 : : char *
8274 264 : 159 : PGTYPEStimestamp_to_asc(timestamp tstamp)
265 : : {
266 : : struct tm tt,
8310 bruce@momjian.us 267 : 159 : *tm = &tt;
268 : : char buf[MAXDATELEN + 1];
269 : : fsec_t fsec;
2479 michael@paquier.xyz 270 : 159 : int DateStyle = 1; /* this defaults to USE_ISO_DATES, shall we
271 : : * make it an option? */
272 : :
8447 meskes@postgresql.or 273 [ + - - + ]: 159 : if (TIMESTAMP_NOT_FINITE(tstamp))
8435 meskes@postgresql.or 274 :UBC 0 : EncodeSpecialTimestamp(tstamp, buf);
8435 meskes@postgresql.or 275 [ + - ]:CBC 159 : else if (timestamp2tm(tstamp, NULL, tm, &fsec, NULL) == 0)
5165 peter_e@gmx.net 276 : 159 : EncodeDateTime(tm, fsec, false, 0, NULL, DateStyle, buf, 0);
277 : : else
278 : : {
8437 meskes@postgresql.or 279 :UBC 0 : errno = PGTYPES_TS_BAD_TIMESTAMP;
8447 280 : 0 : return NULL;
281 : : }
8435 meskes@postgresql.or 282 :CBC 159 : return pgtypes_strdup(buf);
283 : : }
284 : :
285 : : void
7507 bruce@momjian.us 286 : 1 : PGTYPEStimestamp_current(timestamp * ts)
287 : : {
288 : : struct tm tm;
289 : :
8435 meskes@postgresql.or 290 : 1 : GetCurrentDateTime(&tm);
6299 291 [ + - ]: 1 : if (errno == 0)
292 : 1 : tm2timestamp(&tm, 0, NULL, ts);
8435 293 : 1 : }
294 : :
295 : : static int
3240 tgl@sss.pgh.pa.us 296 : 4 : dttofmtasc_replace(timestamp * ts, date dDate, int dow, struct tm *tm,
297 : : char *output, int *pstr_len, const char *fmtstr)
298 : : {
299 : : union un_fmt_comb replace_val;
300 : : int replace_type;
301 : : int i;
5252 meskes@postgresql.or 302 : 4 : const char *p = fmtstr;
8310 bruce@momjian.us 303 : 4 : char *q = output;
304 : :
305 [ + + ]: 73 : while (*p)
306 : : {
307 [ + + ]: 69 : if (*p == '%')
308 : : {
8435 meskes@postgresql.or 309 : 19 : p++;
310 : : /* fix compiler warning */
8313 311 : 19 : replace_type = PGTYPES_TYPE_NOTHING;
8310 bruce@momjian.us 312 [ + - + - : 19 : switch (*p)
- - + - -
- - - + -
+ - - - +
- - - - -
- + - - -
- - - - +
+ - + - -
+ - - ]
313 : : {
314 : : /* the abbreviated name of the day in the week */
315 : : /* XXX should be locale aware */
8435 meskes@postgresql.or 316 : 2 : case 'a':
8313 317 : 2 : replace_val.str_val = pgtypes_date_weekdays_short[dow];
318 : 2 : replace_type = PGTYPES_TYPE_STRING_CONSTANT;
8435 319 : 2 : break;
320 : : /* the full name of the day in the week */
321 : : /* XXX should be locale aware */
8435 meskes@postgresql.or 322 :UBC 0 : case 'A':
8313 323 : 0 : replace_val.str_val = days[dow];
324 : 0 : replace_type = PGTYPES_TYPE_STRING_CONSTANT;
8435 325 : 0 : break;
326 : : /* the abbreviated name of the month */
327 : : /* XXX should be locale aware */
8435 meskes@postgresql.or 328 :CBC 2 : case 'b':
329 : : case 'h':
2348 tomas.vondra@postgre 330 : 2 : replace_val.str_val = months[tm->tm_mon - 1];
8313 meskes@postgresql.or 331 : 2 : replace_type = PGTYPES_TYPE_STRING_CONSTANT;
8435 332 : 2 : break;
333 : : /* the full name of the month */
334 : : /* XXX should be locale aware */
8435 meskes@postgresql.or 335 :UBC 0 : case 'B':
2348 tomas.vondra@postgre 336 : 0 : replace_val.str_val = pgtypes_date_months[tm->tm_mon - 1];
8313 meskes@postgresql.or 337 : 0 : replace_type = PGTYPES_TYPE_STRING_CONSTANT;
8435 338 : 0 : break;
339 : :
340 : : /*
341 : : * The preferred date and time representation for the
342 : : * current locale.
343 : : */
344 : 0 : case 'c':
345 : : /* XXX */
346 : 0 : break;
347 : : /* the century number with leading zeroes */
348 : 0 : case 'C':
8282 349 : 0 : replace_val.uint_val = tm->tm_year / 100;
8313 350 : 0 : replace_type = PGTYPES_TYPE_UINT_2_LZ;
8435 351 : 0 : break;
352 : : /* day with leading zeroes (01 - 31) */
8435 meskes@postgresql.or 353 :CBC 2 : case 'd':
8313 354 : 2 : replace_val.uint_val = tm->tm_mday;
355 : 2 : replace_type = PGTYPES_TYPE_UINT_2_LZ;
8435 356 : 2 : break;
357 : : /* the date in the format mm/dd/yy */
8435 meskes@postgresql.or 358 :UBC 0 : case 'D':
359 : :
360 : : /*
361 : : * ts, dDate, dow, tm is information about the timestamp
362 : : *
363 : : * q is the start of the current output buffer
364 : : *
365 : : * pstr_len is a pointer to the remaining size of output,
366 : : * i.e. the size of q
367 : : */
368 : 0 : i = dttofmtasc_replace(ts, dDate, dow, tm,
369 : : q, pstr_len,
370 : : "%m/%d/%y");
8310 bruce@momjian.us 371 [ # # ]: 0 : if (i)
372 : 0 : return i;
8435 meskes@postgresql.or 373 : 0 : break;
374 : : /* day with leading spaces (01 - 31) */
375 : 0 : case 'e':
8313 376 : 0 : replace_val.uint_val = tm->tm_mday;
377 : 0 : replace_type = PGTYPES_TYPE_UINT_2_LS;
8435 378 : 0 : break;
379 : :
380 : : /*
381 : : * alternative format modifier
382 : : */
383 : 0 : case 'E':
384 : : {
8310 bruce@momjian.us 385 : 0 : char tmp[4] = "%Ex";
386 : :
8435 meskes@postgresql.or 387 : 0 : p++;
8310 bruce@momjian.us 388 [ # # ]: 0 : if (*p == '\0')
8435 meskes@postgresql.or 389 : 0 : return -1;
390 : 0 : tmp[2] = *p;
391 : :
392 : : /*
393 : : * strftime's month is 0 based, ours is 1 based
394 : : */
395 : 0 : tm->tm_mon -= 1;
396 : 0 : i = strftime(q, *pstr_len, tmp, tm);
8310 bruce@momjian.us 397 [ # # ]: 0 : if (i == 0)
398 : 0 : return -1;
399 [ # # ]: 0 : while (*q)
400 : : {
8435 meskes@postgresql.or 401 : 0 : q++;
402 : 0 : (*pstr_len)--;
403 : : }
404 : 0 : tm->tm_mon += 1;
8313 405 : 0 : replace_type = PGTYPES_TYPE_NOTHING;
8435 406 : 0 : break;
407 : : }
408 : :
409 : : /*
410 : : * The ISO 8601 year with century as a decimal number. The
411 : : * 4-digit year corresponding to the ISO week number.
412 : : */
413 : 0 : case 'G':
414 : : {
415 : : /* Keep compiler quiet - Don't use a literal format */
5444 bruce@momjian.us 416 : 0 : const char *fmt = "%G";
417 : :
5486 andrew@dunslane.net 418 : 0 : tm->tm_mon -= 1;
419 : 0 : i = strftime(q, *pstr_len, fmt, tm);
420 [ # # ]: 0 : if (i == 0)
421 : 0 : return -1;
422 [ # # ]: 0 : while (*q)
423 : : {
424 : 0 : q++;
425 : 0 : (*pstr_len)--;
426 : : }
427 : 0 : tm->tm_mon += 1;
428 : 0 : replace_type = PGTYPES_TYPE_NOTHING;
429 : : }
8435 meskes@postgresql.or 430 : 0 : break;
431 : :
432 : : /*
433 : : * Like %G, but without century, i.e., with a 2-digit year
434 : : * (00-99).
435 : : */
436 : 0 : case 'g':
437 : : {
6989 438 : 0 : const char *fmt = "%g"; /* Keep compiler quiet about
439 : : * 2-digit year */
440 : :
8350 441 : 0 : tm->tm_mon -= 1;
442 : 0 : i = strftime(q, *pstr_len, fmt, tm);
8310 bruce@momjian.us 443 [ # # ]: 0 : if (i == 0)
444 : 0 : return -1;
445 [ # # ]: 0 : while (*q)
446 : : {
8350 meskes@postgresql.or 447 : 0 : q++;
448 : 0 : (*pstr_len)--;
449 : : }
450 : 0 : tm->tm_mon += 1;
8313 451 : 0 : replace_type = PGTYPES_TYPE_NOTHING;
452 : : }
8435 453 : 0 : break;
454 : : /* hour (24 hour clock) with leading zeroes */
8435 meskes@postgresql.or 455 :CBC 2 : case 'H':
8313 456 : 2 : replace_val.uint_val = tm->tm_hour;
457 : 2 : replace_type = PGTYPES_TYPE_UINT_2_LZ;
8435 458 : 2 : break;
459 : : /* hour (12 hour clock) with leading zeroes */
8435 meskes@postgresql.or 460 :UBC 0 : case 'I':
8313 461 : 0 : replace_val.uint_val = tm->tm_hour % 12;
462 : 0 : replace_type = PGTYPES_TYPE_UINT_2_LZ;
8435 463 : 0 : break;
464 : :
465 : : /*
466 : : * The day of the year as a decimal number with leading
467 : : * zeroes. It ranges from 001 to 366.
468 : : */
8435 meskes@postgresql.or 469 :CBC 1 : case 'j':
8313 470 : 1 : replace_val.uint_val = tm->tm_yday;
471 : 1 : replace_type = PGTYPES_TYPE_UINT_3_LZ;
8435 472 : 1 : break;
473 : :
474 : : /*
475 : : * The hour (24 hour clock). Leading zeroes will be turned
476 : : * into spaces.
477 : : */
8435 meskes@postgresql.or 478 :UBC 0 : case 'k':
8313 479 : 0 : replace_val.uint_val = tm->tm_hour;
480 : 0 : replace_type = PGTYPES_TYPE_UINT_2_LS;
8435 481 : 0 : break;
482 : :
483 : : /*
484 : : * The hour (12 hour clock). Leading zeroes will be turned
485 : : * into spaces.
486 : : */
487 : 0 : case 'l':
8313 488 : 0 : replace_val.uint_val = tm->tm_hour % 12;
489 : 0 : replace_type = PGTYPES_TYPE_UINT_2_LS;
8435 490 : 0 : break;
491 : : /* The month as a decimal number with a leading zero */
492 : 0 : case 'm':
8313 493 : 0 : replace_val.uint_val = tm->tm_mon;
494 : 0 : replace_type = PGTYPES_TYPE_UINT_2_LZ;
8435 495 : 0 : break;
496 : : /* The minute as a decimal number with a leading zero */
8435 meskes@postgresql.or 497 :CBC 2 : case 'M':
8313 498 : 2 : replace_val.uint_val = tm->tm_min;
499 : 2 : replace_type = PGTYPES_TYPE_UINT_2_LZ;
8435 500 : 2 : break;
501 : : /* A newline character */
8435 meskes@postgresql.or 502 :UBC 0 : case 'n':
8313 503 : 0 : replace_val.char_val = '\n';
504 : 0 : replace_type = PGTYPES_TYPE_CHAR;
8435 505 : 0 : break;
506 : : /* the AM/PM specifier (uppercase) */
507 : : /* XXX should be locale aware */
508 : 0 : case 'p':
8310 bruce@momjian.us 509 [ # # ]: 0 : if (tm->tm_hour < 12)
8313 meskes@postgresql.or 510 : 0 : replace_val.str_val = "AM";
511 : : else
512 : 0 : replace_val.str_val = "PM";
513 : 0 : replace_type = PGTYPES_TYPE_STRING_CONSTANT;
8435 514 : 0 : break;
515 : : /* the AM/PM specifier (lowercase) */
516 : : /* XXX should be locale aware */
517 : 0 : case 'P':
8310 bruce@momjian.us 518 [ # # ]: 0 : if (tm->tm_hour < 12)
8313 meskes@postgresql.or 519 : 0 : replace_val.str_val = "am";
520 : : else
521 : 0 : replace_val.str_val = "pm";
522 : 0 : replace_type = PGTYPES_TYPE_STRING_CONSTANT;
8435 523 : 0 : break;
524 : : /* the time in the format %I:%M:%S %p */
525 : : /* XXX should be locale aware */
526 : 0 : case 'r':
527 : 0 : i = dttofmtasc_replace(ts, dDate, dow, tm,
528 : : q, pstr_len,
529 : : "%I:%M:%S %p");
8310 bruce@momjian.us 530 [ # # ]: 0 : if (i)
531 : 0 : return i;
8435 meskes@postgresql.or 532 : 0 : break;
533 : : /* The time in 24 hour notation (%H:%M) */
534 : 0 : case 'R':
535 : 0 : i = dttofmtasc_replace(ts, dDate, dow, tm,
536 : : q, pstr_len,
537 : : "%H:%M");
8310 bruce@momjian.us 538 [ # # ]: 0 : if (i)
539 : 0 : return i;
8435 meskes@postgresql.or 540 : 0 : break;
541 : : /* The number of seconds since the Epoch (1970-01-01) */
542 : 0 : case 's':
7602 bruce@momjian.us 543 : 0 : replace_val.int64_val = (*ts - SetEpochTimestamp()) / 1000000.0;
8313 meskes@postgresql.or 544 : 0 : replace_type = PGTYPES_TYPE_INT64;
8435 545 : 0 : break;
546 : : /* seconds as a decimal number with leading zeroes */
8435 meskes@postgresql.or 547 :CBC 2 : case 'S':
8313 548 : 2 : replace_val.uint_val = tm->tm_sec;
8263 549 : 2 : replace_type = PGTYPES_TYPE_UINT_2_LZ;
8435 550 : 2 : break;
551 : : /* A tabulator */
8435 meskes@postgresql.or 552 :UBC 0 : case 't':
8313 553 : 0 : replace_val.char_val = '\t';
554 : 0 : replace_type = PGTYPES_TYPE_CHAR;
8435 555 : 0 : break;
556 : : /* The time in 24 hour notation (%H:%M:%S) */
557 : 0 : case 'T':
558 : 0 : i = dttofmtasc_replace(ts, dDate, dow, tm,
559 : : q, pstr_len,
560 : : "%H:%M:%S");
8310 bruce@momjian.us 561 [ # # ]: 0 : if (i)
562 : 0 : return i;
8435 meskes@postgresql.or 563 : 0 : break;
564 : :
565 : : /*
566 : : * The day of the week as a decimal, Monday = 1, Sunday =
567 : : * 7
568 : : */
569 : 0 : case 'u':
8313 570 : 0 : replace_val.uint_val = dow;
7203 571 [ # # ]: 0 : if (replace_val.uint_val == 0)
572 : 0 : replace_val.uint_val = 7;
8313 573 : 0 : replace_type = PGTYPES_TYPE_UINT;
8435 574 : 0 : break;
575 : : /* The week number of the year as a decimal number */
576 : 0 : case 'U':
577 : 0 : tm->tm_mon -= 1;
578 : 0 : i = strftime(q, *pstr_len, "%U", tm);
8310 bruce@momjian.us 579 [ # # ]: 0 : if (i == 0)
580 : 0 : return -1;
581 [ # # ]: 0 : while (*q)
582 : : {
8435 meskes@postgresql.or 583 : 0 : q++;
584 : 0 : (*pstr_len)--;
585 : : }
586 : 0 : tm->tm_mon += 1;
8313 587 : 0 : replace_type = PGTYPES_TYPE_NOTHING;
8435 588 : 0 : break;
589 : :
590 : : /*
591 : : * The ISO 8601:1988 week number of the current year as a
592 : : * decimal number.
593 : : */
594 : 0 : case 'V':
595 : : {
596 : : /* Keep compiler quiet - Don't use a literal format */
5444 bruce@momjian.us 597 : 0 : const char *fmt = "%V";
598 : :
5486 andrew@dunslane.net 599 : 0 : i = strftime(q, *pstr_len, fmt, tm);
600 [ # # ]: 0 : if (i == 0)
601 : 0 : return -1;
602 [ # # ]: 0 : while (*q)
603 : : {
604 : 0 : q++;
605 : 0 : (*pstr_len)--;
606 : : }
607 : 0 : replace_type = PGTYPES_TYPE_NOTHING;
608 : : }
8435 meskes@postgresql.or 609 : 0 : break;
610 : :
611 : : /*
612 : : * The day of the week as a decimal, Sunday being 0 and
613 : : * Monday 1.
614 : : */
615 : 0 : case 'w':
8313 616 : 0 : replace_val.uint_val = dow;
617 : 0 : replace_type = PGTYPES_TYPE_UINT;
8435 618 : 0 : break;
619 : : /* The week number of the year (another definition) */
620 : 0 : case 'W':
621 : 0 : tm->tm_mon -= 1;
622 : 0 : i = strftime(q, *pstr_len, "%U", tm);
8310 bruce@momjian.us 623 [ # # ]: 0 : if (i == 0)
624 : 0 : return -1;
625 [ # # ]: 0 : while (*q)
626 : : {
8435 meskes@postgresql.or 627 : 0 : q++;
628 : 0 : (*pstr_len)--;
629 : : }
630 : 0 : tm->tm_mon += 1;
8313 631 : 0 : replace_type = PGTYPES_TYPE_NOTHING;
8435 632 : 0 : break;
633 : :
634 : : /*
635 : : * The preferred date representation for the current
636 : : * locale without the time.
637 : : */
8435 meskes@postgresql.or 638 :CBC 1 : case 'x':
639 : : {
6989 640 : 1 : const char *fmt = "%x"; /* Keep compiler quiet about
641 : : * 2-digit year */
642 : :
8350 643 : 1 : tm->tm_mon -= 1;
644 : 1 : i = strftime(q, *pstr_len, fmt, tm);
8310 bruce@momjian.us 645 [ - + ]: 1 : if (i == 0)
8310 bruce@momjian.us 646 :UBC 0 : return -1;
8310 bruce@momjian.us 647 [ + + ]:CBC 9 : while (*q)
648 : : {
8350 meskes@postgresql.or 649 : 8 : q++;
650 : 8 : (*pstr_len)--;
651 : : }
652 : 1 : tm->tm_mon += 1;
8313 653 : 1 : replace_type = PGTYPES_TYPE_NOTHING;
654 : : }
8435 655 : 1 : break;
656 : :
657 : : /*
658 : : * The preferred time representation for the current
659 : : * locale without the date.
660 : : */
661 : 1 : case 'X':
662 : 1 : tm->tm_mon -= 1;
663 : 1 : i = strftime(q, *pstr_len, "%X", tm);
8310 bruce@momjian.us 664 [ - + ]: 1 : if (i == 0)
8310 bruce@momjian.us 665 :UBC 0 : return -1;
8310 bruce@momjian.us 666 [ + + ]:CBC 9 : while (*q)
667 : : {
8435 meskes@postgresql.or 668 : 8 : q++;
669 : 8 : (*pstr_len)--;
670 : : }
671 : 1 : tm->tm_mon += 1;
8313 672 : 1 : replace_type = PGTYPES_TYPE_NOTHING;
8435 673 : 1 : break;
674 : : /* The year without the century (2 digits, leading zeroes) */
8435 meskes@postgresql.or 675 :UBC 0 : case 'y':
8313 676 : 0 : replace_val.uint_val = tm->tm_year % 100;
677 : 0 : replace_type = PGTYPES_TYPE_UINT_2_LZ;
8435 678 : 0 : break;
679 : : /* The year with the century (4 digits) */
8435 meskes@postgresql.or 680 :CBC 3 : case 'Y':
8282 681 : 3 : replace_val.uint_val = tm->tm_year;
8313 682 : 3 : replace_type = PGTYPES_TYPE_UINT;
8435 683 : 3 : break;
684 : : /* The time zone offset from GMT */
8435 meskes@postgresql.or 685 :UBC 0 : case 'z':
686 : 0 : tm->tm_mon -= 1;
687 : 0 : i = strftime(q, *pstr_len, "%z", tm);
8310 bruce@momjian.us 688 [ # # ]: 0 : if (i == 0)
689 : 0 : return -1;
690 [ # # ]: 0 : while (*q)
691 : : {
8435 meskes@postgresql.or 692 : 0 : q++;
693 : 0 : (*pstr_len)--;
694 : : }
695 : 0 : tm->tm_mon += 1;
8313 696 : 0 : replace_type = PGTYPES_TYPE_NOTHING;
8435 697 : 0 : break;
698 : : /* The name or abbreviation of the time zone */
699 : 0 : case 'Z':
700 : 0 : tm->tm_mon -= 1;
701 : 0 : i = strftime(q, *pstr_len, "%Z", tm);
8310 bruce@momjian.us 702 [ # # ]: 0 : if (i == 0)
703 : 0 : return -1;
704 [ # # ]: 0 : while (*q)
705 : : {
8435 meskes@postgresql.or 706 : 0 : q++;
707 : 0 : (*pstr_len)--;
708 : : }
709 : 0 : tm->tm_mon += 1;
8313 710 : 0 : replace_type = PGTYPES_TYPE_NOTHING;
8435 711 : 0 : break;
712 : : /* A % sign */
8435 meskes@postgresql.or 713 :CBC 1 : case '%':
8313 714 : 1 : replace_val.char_val = '%';
715 : 1 : replace_type = PGTYPES_TYPE_CHAR;
8435 716 : 1 : break;
8435 meskes@postgresql.or 717 :UBC 0 : case '\0':
718 : : /* fmtstr: foo%' - The string ends with a % sign */
719 : :
720 : : /*
721 : : * this is not compliant to the specification
722 : : */
723 : 0 : return -1;
724 : 0 : default:
725 : :
726 : : /*
727 : : * if we don't know the pattern, we just copy it
728 : : */
8310 bruce@momjian.us 729 [ # # ]: 0 : if (*pstr_len > 1)
730 : : {
8435 meskes@postgresql.or 731 : 0 : *q = '%';
8310 bruce@momjian.us 732 : 0 : q++;
733 : 0 : (*pstr_len)--;
734 [ # # ]: 0 : if (*pstr_len > 1)
735 : : {
8435 meskes@postgresql.or 736 : 0 : *q = *p;
8310 bruce@momjian.us 737 : 0 : q++;
738 : 0 : (*pstr_len)--;
739 : : }
740 : : else
741 : : {
8435 meskes@postgresql.or 742 : 0 : *q = '\0';
743 : 0 : return -1;
744 : : }
745 : 0 : *q = '\0';
746 : : }
747 : : else
8310 bruce@momjian.us 748 : 0 : return -1;
8435 meskes@postgresql.or 749 : 0 : break;
750 : : }
8435 meskes@postgresql.or 751 :CBC 19 : i = pgtypes_fmt_replace(replace_val, replace_type, &q, pstr_len);
8310 bruce@momjian.us 752 [ - + ]: 19 : if (i)
8435 meskes@postgresql.or 753 :UBC 0 : return i;
754 : : }
755 : : else
756 : : {
8310 bruce@momjian.us 757 [ + - ]:CBC 50 : if (*pstr_len > 1)
758 : : {
8435 meskes@postgresql.or 759 : 50 : *q = *p;
760 : 50 : (*pstr_len)--;
761 : 50 : q++;
762 : 50 : *q = '\0';
763 : : }
764 : : else
8310 bruce@momjian.us 765 :UBC 0 : return -1;
766 : : }
8435 meskes@postgresql.or 767 :CBC 69 : p++;
768 : : }
769 : 4 : return 0;
770 : : }
771 : :
772 : :
773 : : int
5252 774 : 4 : PGTYPEStimestamp_fmt_asc(timestamp * ts, char *output, int str_len, const char *fmtstr)
775 : : {
776 : : struct tm tm;
777 : : fsec_t fsec;
778 : : date dDate;
779 : : int dow;
780 : :
8435 781 : 4 : dDate = PGTYPESdate_from_timestamp(*ts);
782 : 4 : dow = PGTYPESdate_dayofweek(dDate);
783 : 4 : timestamp2tm(*ts, NULL, &tm, &fsec, NULL);
784 : :
785 : 4 : return dttofmtasc_replace(ts, dDate, dow, &tm, output, &str_len, fmtstr);
786 : : }
787 : :
788 : : int
7507 bruce@momjian.us 789 :UBC 0 : PGTYPEStimestamp_sub(timestamp * ts1, timestamp * ts2, interval * iv)
790 : : {
8435 meskes@postgresql.or 791 [ # # # # : 0 : if (TIMESTAMP_NOT_FINITE(*ts1) || TIMESTAMP_NOT_FINITE(*ts2))
# # # # ]
792 : 0 : return PGTYPES_TS_ERR_EINFTIME;
793 : : else
6599 794 : 0 : iv->time = (*ts1 - *ts2);
795 : :
8435 796 : 0 : iv->month = 0;
797 : :
798 : 0 : return 0;
799 : : }
800 : :
801 : : int
3108 peter_e@gmx.net 802 :CBC 25 : PGTYPEStimestamp_defmt_asc(const char *str, const char *fmt, timestamp * d)
803 : : {
804 : : int year,
805 : : month,
806 : : day;
807 : : int hour,
808 : : minute,
809 : : second;
810 : : int tz;
811 : :
812 : : int i;
813 : : char *mstr;
814 : : char *mfmt;
815 : :
8310 bruce@momjian.us 816 [ + + ]: 25 : if (!fmt)
8313 meskes@postgresql.or 817 : 1 : fmt = "%Y-%m-%d %H:%M:%S";
8310 bruce@momjian.us 818 [ + + ]: 25 : if (!fmt[0])
8313 meskes@postgresql.or 819 : 1 : return 1;
820 : :
821 : 24 : mstr = pgtypes_strdup(str);
822 : 24 : mfmt = pgtypes_strdup(fmt);
823 : :
824 : : /*
825 : : * initialize with impossible values so that we can see if the fields
826 : : * where specified at all
827 : : */
828 : : /* XXX ambiguity with 1 BC for year? */
8310 bruce@momjian.us 829 : 24 : year = -1;
830 : 24 : month = -1;
831 : 24 : day = -1;
832 : 24 : hour = 0;
833 : 24 : minute = -1;
834 : 24 : second = -1;
8313 meskes@postgresql.or 835 : 24 : tz = 0;
836 : :
837 : 24 : i = PGTYPEStimestamp_defmt_scan(&mstr, mfmt, d, &year, &month, &day, &hour, &minute, &second, &tz);
838 : 24 : free(mstr);
839 : 24 : free(mfmt);
840 : 24 : return i;
841 : : }
842 : :
843 : : /*
844 : : * add an interval to a time stamp
845 : : *
846 : : * *tout = tin + span
847 : : *
848 : : * returns 0 if successful
849 : : * returns -1 if it fails
850 : : *
851 : : */
852 : :
853 : : int
7507 bruce@momjian.us 854 : 7 : PGTYPEStimestamp_add_interval(timestamp * tin, interval * span, timestamp * tout)
855 : : {
856 [ + - - + ]: 7 : if (TIMESTAMP_NOT_FINITE(*tin))
7507 bruce@momjian.us 857 :UBC 0 : *tout = *tin;
858 : : else
859 : : {
7507 bruce@momjian.us 860 [ + + ]:CBC 7 : if (span->month != 0)
861 : : {
862 : : struct tm tt,
863 : 1 : *tm = &tt;
864 : : fsec_t fsec;
865 : :
866 [ - + ]: 1 : if (timestamp2tm(*tin, NULL, tm, &fsec, NULL) != 0)
7507 bruce@momjian.us 867 :UBC 0 : return -1;
7507 bruce@momjian.us 868 :CBC 1 : tm->tm_mon += span->month;
869 [ + - ]: 1 : if (tm->tm_mon > MONTHS_PER_YEAR)
870 : : {
871 : 1 : tm->tm_year += (tm->tm_mon - 1) / MONTHS_PER_YEAR;
872 : 1 : tm->tm_mon = (tm->tm_mon - 1) % MONTHS_PER_YEAR + 1;
873 : : }
7507 bruce@momjian.us 874 [ # # ]:UBC 0 : else if (tm->tm_mon < 1)
875 : : {
876 : 0 : tm->tm_year += tm->tm_mon / MONTHS_PER_YEAR - 1;
877 : 0 : tm->tm_mon = tm->tm_mon % MONTHS_PER_YEAR + MONTHS_PER_YEAR;
878 : : }
879 : :
880 : :
881 : : /* adjust for end of month boundary problems... */
7507 bruce@momjian.us 882 [ - + - - :CBC 1 : if (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1])
- - - + ]
7507 bruce@momjian.us 883 [ # # # # :UBC 0 : tm->tm_mday = (day_tab[isleap(tm->tm_year)][tm->tm_mon - 1]);
# # ]
884 : :
885 : :
7507 bruce@momjian.us 886 [ - + ]:CBC 1 : if (tm2timestamp(tm, fsec, NULL, tin) != 0)
7507 bruce@momjian.us 887 :UBC 0 : return -1;
888 : : }
889 : :
7507 bruce@momjian.us 890 :CBC 7 : *tin += span->time;
891 : 7 : *tout = *tin;
892 : : }
893 : :
1483 alvherre@alvh.no-ip. 894 : 7 : return 0;
895 : : }
896 : :
897 : :
898 : : /*
899 : : * subtract an interval from a time stamp
900 : : *
901 : : * *tout = tin - span
902 : : *
903 : : * returns 0 if successful
904 : : * returns -1 if it fails
905 : : *
906 : : */
907 : :
908 : : int
7507 bruce@momjian.us 909 :UBC 0 : PGTYPEStimestamp_sub_interval(timestamp * tin, interval * span, timestamp * tout)
910 : : {
911 : : interval tspan;
912 : :
913 : 0 : tspan.month = -span->month;
914 : 0 : tspan.time = -span->time;
915 : :
916 : 0 : return PGTYPEStimestamp_add_interval(tin, &tspan, tout);
917 : : }
|