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