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