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