/src/postgres/src/timezone/strftime.c
Line | Count | Source |
1 | | /* Convert a broken-down timestamp to a string. */ |
2 | | |
3 | | /* |
4 | | * Copyright 1989 The Regents of the University of California. |
5 | | * All rights reserved. |
6 | | * |
7 | | * Redistribution and use in source and binary forms, with or without |
8 | | * modification, are permitted provided that the following conditions |
9 | | * are met: |
10 | | * 1. Redistributions of source code must retain the above copyright |
11 | | * notice, this list of conditions and the following disclaimer. |
12 | | * 2. Redistributions in binary form must reproduce the above copyright |
13 | | * notice, this list of conditions and the following disclaimer in the |
14 | | * documentation and/or other materials provided with the distribution. |
15 | | * 3. Neither the name of the University nor the names of its contributors |
16 | | * may be used to endorse or promote products derived from this software |
17 | | * without specific prior written permission. |
18 | | * |
19 | | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND |
20 | | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
21 | | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
22 | | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
23 | | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
24 | | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
25 | | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
26 | | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
27 | | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
28 | | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
29 | | * SUCH DAMAGE. |
30 | | */ |
31 | | |
32 | | /* |
33 | | * Based on the UCB version with the copyright notice appearing above. |
34 | | * |
35 | | * This is ANSIish only when "multibyte character == plain character". |
36 | | * |
37 | | * IDENTIFICATION |
38 | | * src/timezone/strftime.c |
39 | | */ |
40 | | |
41 | | #include "postgres.h" |
42 | | |
43 | | #include <fcntl.h> |
44 | | |
45 | | #include "private.h" |
46 | | |
47 | | |
48 | | struct lc_time_T |
49 | | { |
50 | | const char *mon[MONSPERYEAR]; |
51 | | const char *month[MONSPERYEAR]; |
52 | | const char *wday[DAYSPERWEEK]; |
53 | | const char *weekday[DAYSPERWEEK]; |
54 | | const char *X_fmt; |
55 | | const char *x_fmt; |
56 | | const char *c_fmt; |
57 | | const char *am; |
58 | | const char *pm; |
59 | | const char *date_fmt; |
60 | | }; |
61 | | |
62 | | static const struct lc_time_T C_time_locale = { |
63 | | { |
64 | | "Jan", "Feb", "Mar", "Apr", "May", "Jun", |
65 | | "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" |
66 | | }, { |
67 | | "January", "February", "March", "April", "May", "June", |
68 | | "July", "August", "September", "October", "November", "December" |
69 | | }, { |
70 | | "Sun", "Mon", "Tue", "Wed", |
71 | | "Thu", "Fri", "Sat" |
72 | | }, { |
73 | | "Sunday", "Monday", "Tuesday", "Wednesday", |
74 | | "Thursday", "Friday", "Saturday" |
75 | | }, |
76 | | |
77 | | /* X_fmt */ |
78 | | "%H:%M:%S", |
79 | | |
80 | | /* |
81 | | * x_fmt C99 and later require this format. Using just numbers (as here) |
82 | | * makes Quakers happier; it's also compatible with SVR4. |
83 | | */ |
84 | | "%m/%d/%y", |
85 | | |
86 | | /* |
87 | | * c_fmt C99 and later require this format. Previously this code used "%D |
88 | | * %X", but we now conform to C99. Note that "%a %b %d %H:%M:%S %Y" is |
89 | | * used by Solaris 2.3. |
90 | | */ |
91 | | "%a %b %e %T %Y", |
92 | | |
93 | | /* am */ |
94 | | "AM", |
95 | | |
96 | | /* pm */ |
97 | | "PM", |
98 | | |
99 | | /* date_fmt */ |
100 | | "%a %b %e %H:%M:%S %Z %Y" |
101 | | }; |
102 | | |
103 | | enum warn |
104 | | { |
105 | | IN_NONE, IN_SOME, IN_THIS, IN_ALL |
106 | | }; |
107 | | |
108 | | static char *_add(const char *str, char *pt, const char *ptlim); |
109 | | static char *_conv(int n, const char *format, char *pt, const char *ptlim); |
110 | | static char *_fmt(const char *format, const struct pg_tm *t, char *pt, const char *ptlim, |
111 | | enum warn *warnp); |
112 | | static char *_yconv(int a, int b, bool convert_top, bool convert_yy, char *pt, char const *ptlim); |
113 | | |
114 | | |
115 | | /* |
116 | | * Convert timestamp t to string s, a caller-allocated buffer of size maxsize, |
117 | | * using the given format pattern. |
118 | | * |
119 | | * Unlike standard strftime(), we guarantee to provide a null-terminated |
120 | | * result even on failure, so long as maxsize > 0. If we overrun the buffer, |
121 | | * return an empty string rather than risking mis-encoded multibyte output. |
122 | | * (Since this module only supports C locale, you might think multibyte |
123 | | * characters are impossible --- but the time zone name printed by %Z comes |
124 | | * from outside and could contain such.) |
125 | | * |
126 | | * See also timestamptz_to_str. |
127 | | */ |
128 | | size_t |
129 | | pg_strftime(char *s, size_t maxsize, const char *format, const struct pg_tm *t) |
130 | 0 | { |
131 | 0 | char *p; |
132 | 0 | int saved_errno = errno; |
133 | 0 | enum warn warn = IN_NONE; |
134 | |
|
135 | 0 | p = _fmt(format, t, s, s + maxsize, &warn); |
136 | 0 | if (!p) |
137 | 0 | { |
138 | 0 | errno = EOVERFLOW; |
139 | 0 | if (maxsize > 0) |
140 | 0 | *s = '\0'; |
141 | 0 | return 0; |
142 | 0 | } |
143 | 0 | if (p == s + maxsize) |
144 | 0 | { |
145 | 0 | errno = ERANGE; |
146 | 0 | if (maxsize > 0) |
147 | 0 | *s = '\0'; |
148 | 0 | return 0; |
149 | 0 | } |
150 | 0 | *p = '\0'; |
151 | 0 | errno = saved_errno; |
152 | 0 | return p - s; |
153 | 0 | } |
154 | | |
155 | | static char * |
156 | | _fmt(const char *format, const struct pg_tm *t, char *pt, |
157 | | const char *ptlim, enum warn *warnp) |
158 | 0 | { |
159 | 0 | struct lc_time_T const *Locale = &C_time_locale; |
160 | |
|
161 | 0 | for (; *format; ++format) |
162 | 0 | { |
163 | 0 | if (*format == '%') |
164 | 0 | { |
165 | 0 | label: |
166 | 0 | switch (*++format) |
167 | 0 | { |
168 | 0 | default: |
169 | | |
170 | | /* |
171 | | * Output unknown conversion specifiers as-is, to aid |
172 | | * debugging. This includes '%' at format end. This |
173 | | * conforms to C23 section 7.29.3.5 paragraph 6, which |
174 | | * says behavior is undefined here. |
175 | | */ |
176 | 0 | --format; |
177 | 0 | break; |
178 | 0 | case 'A': |
179 | 0 | pt = _add((t->tm_wday < 0 || |
180 | 0 | t->tm_wday >= DAYSPERWEEK) ? |
181 | 0 | "?" : Locale->weekday[t->tm_wday], |
182 | 0 | pt, ptlim); |
183 | 0 | continue; |
184 | 0 | case 'a': |
185 | 0 | pt = _add((t->tm_wday < 0 || |
186 | 0 | t->tm_wday >= DAYSPERWEEK) ? |
187 | 0 | "?" : Locale->wday[t->tm_wday], |
188 | 0 | pt, ptlim); |
189 | 0 | continue; |
190 | 0 | case 'B': |
191 | 0 | pt = _add((t->tm_mon < 0 || |
192 | 0 | t->tm_mon >= MONSPERYEAR) ? |
193 | 0 | "?" : Locale->month[t->tm_mon], |
194 | 0 | pt, ptlim); |
195 | 0 | continue; |
196 | 0 | case 'b': |
197 | 0 | case 'h': |
198 | 0 | pt = _add((t->tm_mon < 0 || |
199 | 0 | t->tm_mon >= MONSPERYEAR) ? |
200 | 0 | "?" : Locale->mon[t->tm_mon], |
201 | 0 | pt, ptlim); |
202 | 0 | continue; |
203 | 0 | case 'C': |
204 | | |
205 | | /* |
206 | | * %C used to do a... _fmt("%a %b %e %X %Y", t); |
207 | | * ...whereas now POSIX 1003.2 calls for something |
208 | | * completely different. (ado, 1993-05-24) |
209 | | */ |
210 | 0 | pt = _yconv(t->tm_year, TM_YEAR_BASE, |
211 | 0 | true, false, pt, ptlim); |
212 | 0 | continue; |
213 | 0 | case 'c': |
214 | 0 | { |
215 | 0 | enum warn warn2 = IN_SOME; |
216 | |
|
217 | 0 | pt = _fmt(Locale->c_fmt, t, pt, ptlim, &warn2); |
218 | 0 | if (warn2 == IN_ALL) |
219 | 0 | warn2 = IN_THIS; |
220 | 0 | if (warn2 > *warnp) |
221 | 0 | *warnp = warn2; |
222 | 0 | } |
223 | 0 | continue; |
224 | 0 | case 'D': |
225 | 0 | pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp); |
226 | 0 | continue; |
227 | 0 | case 'd': |
228 | 0 | pt = _conv(t->tm_mday, "%02d", pt, ptlim); |
229 | 0 | continue; |
230 | 0 | case 'E': |
231 | 0 | case 'O': |
232 | | |
233 | | /* |
234 | | * Locale modifiers of C99 and later. The sequences %Ec |
235 | | * %EC %Ex %EX %Ey %EY %Od %oe %OH %OI %Om %OM %OS %Ou %OU |
236 | | * %OV %Ow %OW %Oy are supposed to provide alternative |
237 | | * representations. |
238 | | */ |
239 | 0 | goto label; |
240 | 0 | case 'e': |
241 | 0 | pt = _conv(t->tm_mday, "%2d", pt, ptlim); |
242 | 0 | continue; |
243 | 0 | case 'F': |
244 | 0 | pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp); |
245 | 0 | continue; |
246 | 0 | case 'H': |
247 | 0 | pt = _conv(t->tm_hour, "%02d", pt, ptlim); |
248 | 0 | continue; |
249 | 0 | case 'I': |
250 | 0 | pt = _conv((t->tm_hour % 12) ? |
251 | 0 | (t->tm_hour % 12) : 12, |
252 | 0 | "%02d", pt, ptlim); |
253 | 0 | continue; |
254 | 0 | case 'j': |
255 | 0 | pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim); |
256 | 0 | continue; |
257 | 0 | case 'k': |
258 | | |
259 | | /* |
260 | | * This used to be... _conv(t->tm_hour % 12 ? t->tm_hour % |
261 | | * 12 : 12, 2, ' '); ...and has been changed to the below |
262 | | * to match SunOS 4.1.1 and Arnold Robbins' strftime |
263 | | * version 3.0. That is, "%k" and "%l" have been swapped. |
264 | | * (ado, 1993-05-24) |
265 | | */ |
266 | 0 | pt = _conv(t->tm_hour, "%2d", pt, ptlim); |
267 | 0 | continue; |
268 | | #ifdef KITCHEN_SINK |
269 | | case 'K': |
270 | | |
271 | | /* |
272 | | * After all this time, still unclaimed! |
273 | | */ |
274 | | pt = _add("kitchen sink", pt, ptlim); |
275 | | continue; |
276 | | #endif /* defined KITCHEN_SINK */ |
277 | 0 | case 'l': |
278 | | |
279 | | /* |
280 | | * This used to be... _conv(t->tm_hour, 2, ' '); ...and |
281 | | * has been changed to the below to match SunOS 4.1.1 and |
282 | | * Arnold Robbin's strftime version 3.0. That is, "%k" and |
283 | | * "%l" have been swapped. (ado, 1993-05-24) |
284 | | */ |
285 | 0 | pt = _conv((t->tm_hour % 12) ? |
286 | 0 | (t->tm_hour % 12) : 12, |
287 | 0 | "%2d", pt, ptlim); |
288 | 0 | continue; |
289 | 0 | case 'M': |
290 | 0 | pt = _conv(t->tm_min, "%02d", pt, ptlim); |
291 | 0 | continue; |
292 | 0 | case 'm': |
293 | 0 | pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim); |
294 | 0 | continue; |
295 | 0 | case 'n': |
296 | 0 | pt = _add("\n", pt, ptlim); |
297 | 0 | continue; |
298 | 0 | case 'p': |
299 | 0 | pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ? |
300 | 0 | Locale->pm : |
301 | 0 | Locale->am, |
302 | 0 | pt, ptlim); |
303 | 0 | continue; |
304 | 0 | case 'R': |
305 | 0 | pt = _fmt("%H:%M", t, pt, ptlim, warnp); |
306 | 0 | continue; |
307 | 0 | case 'r': |
308 | 0 | pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp); |
309 | 0 | continue; |
310 | 0 | case 'S': |
311 | 0 | pt = _conv(t->tm_sec, "%02d", pt, ptlim); |
312 | 0 | continue; |
313 | 0 | case 'T': |
314 | 0 | pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp); |
315 | 0 | continue; |
316 | 0 | case 't': |
317 | 0 | pt = _add("\t", pt, ptlim); |
318 | 0 | continue; |
319 | 0 | case 'U': |
320 | 0 | pt = _conv((t->tm_yday + DAYSPERWEEK - |
321 | 0 | t->tm_wday) / DAYSPERWEEK, |
322 | 0 | "%02d", pt, ptlim); |
323 | 0 | continue; |
324 | 0 | case 'u': |
325 | | |
326 | | /* |
327 | | * From Arnold Robbins' strftime version 3.0: "ISO 8601: |
328 | | * Weekday as a decimal number [1 (Monday) - 7]" (ado, |
329 | | * 1993-05-24) |
330 | | */ |
331 | 0 | pt = _conv((t->tm_wday == 0) ? |
332 | 0 | DAYSPERWEEK : t->tm_wday, |
333 | 0 | "%d", pt, ptlim); |
334 | 0 | continue; |
335 | 0 | case 'V': /* ISO 8601 week number */ |
336 | 0 | case 'G': /* ISO 8601 year (four digits) */ |
337 | 0 | case 'g': /* ISO 8601 year (two digits) */ |
338 | | /* |
339 | | * From Arnold Robbins' strftime version 3.0: "the week number of the |
340 | | * year (the first Monday as the first day of week 1) as a decimal number |
341 | | * (01-53)." |
342 | | * (ado, 1993-05-24) |
343 | | * |
344 | | * From <https://www.cl.cam.ac.uk/~mgk25/iso-time.html> by Markus Kuhn: |
345 | | * "Week 01 of a year is per definition the first week which has the |
346 | | * Thursday in this year, which is equivalent to the week which contains |
347 | | * the fourth day of January. In other words, the first week of a new year |
348 | | * is the week which has the majority of its days in the new year. Week 01 |
349 | | * might also contain days from the previous year and the week before week |
350 | | * 01 of a year is the last week (52 or 53) of the previous year even if |
351 | | * it contains days from the new year. A week starts with Monday (day 1) |
352 | | * and ends with Sunday (day 7). For example, the first week of the year |
353 | | * 1997 lasts from 1996-12-30 to 1997-01-05..." |
354 | | * (ado, 1996-01-02) |
355 | | */ |
356 | 0 | { |
357 | 0 | int year; |
358 | 0 | int base; |
359 | 0 | int yday; |
360 | 0 | int wday; |
361 | 0 | int w; |
362 | |
|
363 | 0 | year = t->tm_year; |
364 | 0 | base = TM_YEAR_BASE; |
365 | 0 | yday = t->tm_yday; |
366 | 0 | wday = t->tm_wday; |
367 | 0 | for (;;) |
368 | 0 | { |
369 | 0 | int len; |
370 | 0 | int bot; |
371 | 0 | int top; |
372 | |
|
373 | 0 | len = isleap_sum(year, base) ? |
374 | 0 | DAYSPERLYEAR : |
375 | 0 | DAYSPERNYEAR; |
376 | | |
377 | | /* |
378 | | * What yday (-3 ... 3) does the ISO year begin |
379 | | * on? |
380 | | */ |
381 | 0 | bot = ((yday + 11 - wday) % |
382 | 0 | DAYSPERWEEK) - 3; |
383 | | |
384 | | /* |
385 | | * What yday does the NEXT ISO year begin on? |
386 | | */ |
387 | 0 | top = bot - |
388 | 0 | (len % DAYSPERWEEK); |
389 | 0 | if (top < -3) |
390 | 0 | top += DAYSPERWEEK; |
391 | 0 | top += len; |
392 | 0 | if (yday >= top) |
393 | 0 | { |
394 | 0 | ++base; |
395 | 0 | w = 1; |
396 | 0 | break; |
397 | 0 | } |
398 | 0 | if (yday >= bot) |
399 | 0 | { |
400 | 0 | w = 1 + ((yday - bot) / |
401 | 0 | DAYSPERWEEK); |
402 | 0 | break; |
403 | 0 | } |
404 | 0 | --base; |
405 | 0 | yday += isleap_sum(year, base) ? |
406 | 0 | DAYSPERLYEAR : |
407 | 0 | DAYSPERNYEAR; |
408 | 0 | } |
409 | 0 | if (*format == 'V') |
410 | 0 | pt = _conv(w, "%02d", |
411 | 0 | pt, ptlim); |
412 | 0 | else if (*format == 'g') |
413 | 0 | { |
414 | 0 | *warnp = IN_ALL; |
415 | 0 | pt = _yconv(year, base, |
416 | 0 | false, true, |
417 | 0 | pt, ptlim); |
418 | 0 | } |
419 | 0 | else |
420 | 0 | pt = _yconv(year, base, |
421 | 0 | true, true, |
422 | 0 | pt, ptlim); |
423 | 0 | } |
424 | 0 | continue; |
425 | 0 | case 'v': |
426 | | |
427 | | /* |
428 | | * From Arnold Robbins' strftime version 3.0: "date as |
429 | | * dd-bbb-YYYY" (ado, 1993-05-24) |
430 | | */ |
431 | 0 | pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp); |
432 | 0 | continue; |
433 | 0 | case 'W': |
434 | 0 | pt = _conv((t->tm_yday + DAYSPERWEEK - |
435 | 0 | (t->tm_wday ? |
436 | 0 | (t->tm_wday - 1) : |
437 | 0 | (DAYSPERWEEK - 1))) / DAYSPERWEEK, |
438 | 0 | "%02d", pt, ptlim); |
439 | 0 | continue; |
440 | 0 | case 'w': |
441 | 0 | pt = _conv(t->tm_wday, "%d", pt, ptlim); |
442 | 0 | continue; |
443 | 0 | case 'X': |
444 | 0 | pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp); |
445 | 0 | continue; |
446 | 0 | case 'x': |
447 | 0 | { |
448 | 0 | enum warn warn2 = IN_SOME; |
449 | |
|
450 | 0 | pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2); |
451 | 0 | if (warn2 == IN_ALL) |
452 | 0 | warn2 = IN_THIS; |
453 | 0 | if (warn2 > *warnp) |
454 | 0 | *warnp = warn2; |
455 | 0 | } |
456 | 0 | continue; |
457 | 0 | case 'y': |
458 | 0 | *warnp = IN_ALL; |
459 | 0 | pt = _yconv(t->tm_year, TM_YEAR_BASE, |
460 | 0 | false, true, |
461 | 0 | pt, ptlim); |
462 | 0 | continue; |
463 | 0 | case 'Y': |
464 | 0 | pt = _yconv(t->tm_year, TM_YEAR_BASE, |
465 | 0 | true, true, |
466 | 0 | pt, ptlim); |
467 | 0 | continue; |
468 | 0 | case 'Z': |
469 | 0 | if (t->tm_zone != NULL) |
470 | 0 | pt = _add(t->tm_zone, pt, ptlim); |
471 | | |
472 | | /* |
473 | | * C99 and later say that %Z must be replaced by the empty |
474 | | * string if the time zone abbreviation is not |
475 | | * determinable. |
476 | | */ |
477 | 0 | continue; |
478 | 0 | case 'z': |
479 | 0 | { |
480 | 0 | long diff; |
481 | 0 | char const *sign; |
482 | 0 | bool negative; |
483 | |
|
484 | 0 | diff = t->TM_GMTOFF; |
485 | 0 | negative = diff < 0; |
486 | 0 | if (diff == 0) |
487 | 0 | { |
488 | 0 | if (t->tm_zone != NULL) |
489 | 0 | negative = t->tm_zone[0] == '-'; |
490 | 0 | } |
491 | 0 | if (negative) |
492 | 0 | { |
493 | 0 | sign = "-"; |
494 | 0 | diff = -diff; |
495 | 0 | } |
496 | 0 | else |
497 | 0 | sign = "+"; |
498 | 0 | pt = _add(sign, pt, ptlim); |
499 | 0 | diff /= SECSPERMIN; |
500 | 0 | diff = (diff / MINSPERHOUR) * 100 + |
501 | 0 | (diff % MINSPERHOUR); |
502 | 0 | pt = _conv(diff, "%04d", pt, ptlim); |
503 | 0 | } |
504 | 0 | continue; |
505 | 0 | case '+': |
506 | 0 | pt = _fmt(Locale->date_fmt, t, pt, ptlim, |
507 | 0 | warnp); |
508 | 0 | continue; |
509 | 0 | case '%': |
510 | 0 | break; |
511 | 0 | } |
512 | 0 | } |
513 | 0 | if (pt == ptlim) |
514 | 0 | break; |
515 | 0 | *pt++ = *format; |
516 | 0 | } |
517 | 0 | return pt; |
518 | 0 | } |
519 | | |
520 | | static char * |
521 | | _conv(int n, const char *format, char *pt, const char *ptlim) |
522 | 0 | { |
523 | 0 | char buf[INT_STRLEN_MAXIMUM(int) + 1]; |
524 | |
|
525 | 0 | sprintf(buf, format, n); |
526 | 0 | return _add(buf, pt, ptlim); |
527 | 0 | } |
528 | | |
529 | | static char * |
530 | | _add(const char *str, char *pt, const char *ptlim) |
531 | 0 | { |
532 | 0 | while (pt < ptlim && (*pt = *str++) != '\0') |
533 | 0 | ++pt; |
534 | 0 | return pt; |
535 | 0 | } |
536 | | |
537 | | /* |
538 | | * POSIX and the C Standard are unclear or inconsistent about |
539 | | * what %C and %y do if the year is negative or exceeds 9999. |
540 | | * Use the convention that %C concatenated with %y yields the |
541 | | * same output as %Y, and that %Y contains at least 4 bytes, |
542 | | * with more only if necessary. |
543 | | */ |
544 | | |
545 | | static char * |
546 | | _yconv(int a, int b, bool convert_top, bool convert_yy, |
547 | | char *pt, const char *ptlim) |
548 | 0 | { |
549 | 0 | int lead; |
550 | 0 | int trail; |
551 | |
|
552 | 0 | int DIVISOR = 100; |
553 | |
|
554 | 0 | trail = a % DIVISOR + b % DIVISOR; |
555 | 0 | lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR; |
556 | 0 | trail %= DIVISOR; |
557 | 0 | if (trail < 0 && lead > 0) |
558 | 0 | { |
559 | 0 | trail += DIVISOR; |
560 | 0 | --lead; |
561 | 0 | } |
562 | 0 | else if (lead < 0 && trail > 0) |
563 | 0 | { |
564 | 0 | trail -= DIVISOR; |
565 | 0 | ++lead; |
566 | 0 | } |
567 | 0 | if (convert_top) |
568 | 0 | { |
569 | 0 | if (lead == 0 && trail < 0) |
570 | 0 | pt = _add("-0", pt, ptlim); |
571 | 0 | else |
572 | 0 | pt = _conv(lead, "%02d", pt, ptlim); |
573 | 0 | } |
574 | 0 | if (convert_yy) |
575 | 0 | pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim); |
576 | 0 | return pt; |
577 | 0 | } |