Header And Logo

PostgreSQL
| The world's most advanced open source database.

strftime.c

Go to the documentation of this file.
00001 /*
00002  * Copyright (c) 1989 The Regents of the University of California.
00003  * All rights reserved.
00004  *
00005  * Redistribution and use in source and binary forms are permitted
00006  * provided that the above copyright notice and this paragraph are
00007  * duplicated in all such forms and that any documentation,
00008  * advertising materials, and other materials related to such
00009  * distribution and use acknowledge that the software was developed
00010  * by the University of California, Berkeley. The name of the
00011  * University may not be used to endorse or promote products derived
00012  * from this software without specific prior written permission.
00013  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
00014  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
00015  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
00016  *
00017  * IDENTIFICATION
00018  *    src/timezone/strftime.c
00019  */
00020 
00021 #include "postgres.h"
00022 
00023 #include <fcntl.h>
00024 #include <locale.h>
00025 
00026 #include "private.h"
00027 #include "tzfile.h"
00028 
00029 
00030 struct lc_time_T
00031 {
00032     const char *mon[MONSPERYEAR];
00033     const char *month[MONSPERYEAR];
00034     const char *wday[DAYSPERWEEK];
00035     const char *weekday[DAYSPERWEEK];
00036     const char *X_fmt;
00037     const char *x_fmt;
00038     const char *c_fmt;
00039     const char *am;
00040     const char *pm;
00041     const char *date_fmt;
00042 };
00043 
00044 #define Locale  (&C_time_locale)
00045 
00046 static const struct lc_time_T C_time_locale = {
00047     {
00048         "Jan", "Feb", "Mar", "Apr", "May", "Jun",
00049         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
00050     }, {
00051         "January", "February", "March", "April", "May", "June",
00052         "July", "August", "September", "October", "November", "December"
00053     }, {
00054         "Sun", "Mon", "Tue", "Wed",
00055         "Thu", "Fri", "Sat"
00056     }, {
00057         "Sunday", "Monday", "Tuesday", "Wednesday",
00058         "Thursday", "Friday", "Saturday"
00059     },
00060 
00061     /* X_fmt */
00062     "%H:%M:%S",
00063 
00064     /*
00065      * x_fmt
00066      *
00067      * C99 requires this format. Using just numbers (as here) makes Quakers
00068      * happier; it's also compatible with SVR4.
00069      */
00070     "%m/%d/%y",
00071 
00072     /*
00073      * c_fmt
00074      *
00075      * C99 requires this format. Previously this code used "%D %X", but we now
00076      * conform to C99. Note that "%a %b %d %H:%M:%S %Y" is used by Solaris
00077      * 2.3.
00078      */
00079     "%a %b %e %T %Y",
00080 
00081     /* am */
00082     "AM",
00083 
00084     /* pm */
00085     "PM",
00086 
00087     /* date_fmt */
00088     "%a %b %e %H:%M:%S %Z %Y"
00089 };
00090 
00091 static char *_add(const char *, char *, const char *);
00092 static char *_conv(int, const char *, char *, const char *);
00093 static char *_fmt(const char *, const struct pg_tm *, char *,
00094      const char *, int *);
00095 static char *_yconv(const int, const int, const int, const int,
00096        char *, const char *const);
00097 
00098 #define IN_NONE 0
00099 #define IN_SOME 1
00100 #define IN_THIS 2
00101 #define IN_ALL  3
00102 
00103 
00104 size_t
00105 pg_strftime(char *s, size_t maxsize, const char *format,
00106             const struct pg_tm * t)
00107 {
00108     char       *p;
00109     int         warn;
00110 
00111     warn = IN_NONE;
00112     p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize, &warn);
00113     if (p == s + maxsize)
00114         return 0;
00115     *p = '\0';
00116     return p - s;
00117 }
00118 
00119 static char *
00120 _fmt(const char *format, const struct pg_tm * t, char *pt, const char *ptlim,
00121      int *warnp)
00122 {
00123     for (; *format; ++format)
00124     {
00125         if (*format == '%')
00126         {
00127     label:
00128             switch (*++format)
00129             {
00130                 case '\0':
00131                     --format;
00132                     break;
00133                 case 'A':
00134                     pt = _add((t->tm_wday < 0 ||
00135                                t->tm_wday >= DAYSPERWEEK) ?
00136                               "?" : Locale->weekday[t->tm_wday],
00137                               pt, ptlim);
00138                     continue;
00139                 case 'a':
00140                     pt = _add((t->tm_wday < 0 ||
00141                                t->tm_wday >= DAYSPERWEEK) ?
00142                               "?" : Locale->wday[t->tm_wday],
00143                               pt, ptlim);
00144                     continue;
00145                 case 'B':
00146                     pt = _add((t->tm_mon < 0 ||
00147                                t->tm_mon >= MONSPERYEAR) ?
00148                               "?" : Locale->month[t->tm_mon],
00149                               pt, ptlim);
00150                     continue;
00151                 case 'b':
00152                 case 'h':
00153                     pt = _add((t->tm_mon < 0 ||
00154                                t->tm_mon >= MONSPERYEAR) ?
00155                               "?" : Locale->mon[t->tm_mon],
00156                               pt, ptlim);
00157                     continue;
00158                 case 'C':
00159 
00160                     /*
00161                      * %C used to do a... _fmt("%a %b %e %X %Y", t);
00162                      * ...whereas now POSIX 1003.2 calls for something
00163                      * completely different. (ado, 1993-05-24)
00164                      */
00165                     pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 0,
00166                                 pt, ptlim);
00167                     continue;
00168                 case 'c':
00169                     {
00170                         int         warn2 = IN_SOME;
00171 
00172                         pt = _fmt(Locale->c_fmt, t, pt, ptlim, &warn2);
00173                         if (warn2 == IN_ALL)
00174                             warn2 = IN_THIS;
00175                         if (warn2 > *warnp)
00176                             *warnp = warn2;
00177                     }
00178                     continue;
00179                 case 'D':
00180                     pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp);
00181                     continue;
00182                 case 'd':
00183                     pt = _conv(t->tm_mday, "%02d", pt, ptlim);
00184                     continue;
00185                 case 'E':
00186                 case 'O':
00187 
00188                     /*
00189                      * C99 locale modifiers. The sequences  %Ec %EC %Ex %EX
00190                      * %Ey %EY  %Od %oe %OH %OI %Om %OM  %OS %Ou %OU %OV %Ow
00191                      * %OW %Oy are supposed to provide alternate
00192                      * representations.
00193                      */
00194                     goto label;
00195                 case 'e':
00196                     pt = _conv(t->tm_mday, "%2d", pt, ptlim);
00197                     continue;
00198                 case 'F':
00199                     pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp);
00200                     continue;
00201                 case 'H':
00202                     pt = _conv(t->tm_hour, "%02d", pt, ptlim);
00203                     continue;
00204                 case 'I':
00205                     pt = _conv((t->tm_hour % 12) ?
00206                                (t->tm_hour % 12) : 12,
00207                                "%02d", pt, ptlim);
00208                     continue;
00209                 case 'j':
00210                     pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim);
00211                     continue;
00212                 case 'k':
00213 
00214                     /*
00215                      * This used to be...  _conv(t->tm_hour % 12 ? t->tm_hour
00216                      * % 12 : 12, 2, ' '); ...and has been changed to the
00217                      * below to match SunOS 4.1.1 and Arnold Robbins' strftime
00218                      * version 3.0. That is, "%k" and "%l" have been swapped.
00219                      * (ado, 1993-05-24)
00220                      */
00221                     pt = _conv(t->tm_hour, "%2d", pt, ptlim);
00222                     continue;
00223 #ifdef KITCHEN_SINK
00224                 case 'K':
00225 
00226                     /*
00227                      * * After all this time, still unclaimed!
00228                      */
00229                     pt = _add("kitchen sink", pt, ptlim);
00230                     continue;
00231 #endif   /* defined KITCHEN_SINK */
00232                 case 'l':
00233 
00234                     /*
00235                      * This used to be...  _conv(t->tm_hour, 2, ' '); ...and
00236                      * has been changed to the below to match SunOS 4.1.1 and
00237                      * Arnold Robbin's strftime version 3.0. That is, "%k" and
00238                      * "%l" have been swapped. (ado, 1993-05-24)
00239                      */
00240                     pt = _conv((t->tm_hour % 12) ?
00241                                (t->tm_hour % 12) : 12,
00242                                "%2d", pt, ptlim);
00243                     continue;
00244                 case 'M':
00245                     pt = _conv(t->tm_min, "%02d", pt, ptlim);
00246                     continue;
00247                 case 'm':
00248                     pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim);
00249                     continue;
00250                 case 'n':
00251                     pt = _add("\n", pt, ptlim);
00252                     continue;
00253                 case 'p':
00254                     pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
00255                               Locale->pm :
00256                               Locale->am,
00257                               pt, ptlim);
00258                     continue;
00259                 case 'R':
00260                     pt = _fmt("%H:%M", t, pt, ptlim, warnp);
00261                     continue;
00262                 case 'r':
00263                     pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp);
00264                     continue;
00265                 case 'S':
00266                     pt = _conv(t->tm_sec, "%02d", pt, ptlim);
00267                     continue;
00268                 case 'T':
00269                     pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp);
00270                     continue;
00271                 case 't':
00272                     pt = _add("\t", pt, ptlim);
00273                     continue;
00274                 case 'U':
00275                     pt = _conv((t->tm_yday + DAYSPERWEEK -
00276                                 t->tm_wday) / DAYSPERWEEK,
00277                                "%02d", pt, ptlim);
00278                     continue;
00279                 case 'u':
00280 
00281                     /*
00282                      * From Arnold Robbins' strftime version 3.0: "ISO 8601:
00283                      * Weekday as a decimal number [1 (Monday) - 7]" (ado,
00284                      * 1993-05-24)
00285                      */
00286                     pt = _conv((t->tm_wday == 0) ?
00287                                DAYSPERWEEK : t->tm_wday,
00288                                "%d", pt, ptlim);
00289                     continue;
00290                 case 'V':       /* ISO 8601 week number */
00291                 case 'G':       /* ISO 8601 year (four digits) */
00292                 case 'g':       /* ISO 8601 year (two digits) */
00293 /*
00294  * From Arnold Robbins' strftime version 3.0: "the week number of the
00295  * year (the first Monday as the first day of week 1) as a decimal number
00296  * (01-53)."
00297  * (ado, 1993-05-24)
00298  *
00299  * From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn:
00300  * "Week 01 of a year is per definition the first week which has the
00301  * Thursday in this year, which is equivalent to the week which contains
00302  * the fourth day of January. In other words, the first week of a new year
00303  * is the week which has the majority of its days in the new year. Week 01
00304  * might also contain days from the previous year and the week before week
00305  * 01 of a year is the last week (52 or 53) of the previous year even if
00306  * it contains days from the new year. A week starts with Monday (day 1)
00307  * and ends with Sunday (day 7). For example, the first week of the year
00308  * 1997 lasts from 1996-12-30 to 1997-01-05..."
00309  * (ado, 1996-01-02)
00310  */
00311                     {
00312                         int         year;
00313                         int         base;
00314                         int         yday;
00315                         int         wday;
00316                         int         w;
00317 
00318                         year = t->tm_year;
00319                         base = TM_YEAR_BASE;
00320                         yday = t->tm_yday;
00321                         wday = t->tm_wday;
00322                         for (;;)
00323                         {
00324                             int         len;
00325                             int         bot;
00326                             int         top;
00327 
00328                             len = isleap_sum(year, base) ?
00329                                 DAYSPERLYEAR :
00330                                 DAYSPERNYEAR;
00331 
00332                             /*
00333                              * What yday (-3 ... 3) does the ISO year begin
00334                              * on?
00335                              */
00336                             bot = ((yday + 11 - wday) %
00337                                    DAYSPERWEEK) - 3;
00338 
00339                             /*
00340                              * What yday does the NEXT ISO year begin on?
00341                              */
00342                             top = bot -
00343                                 (len % DAYSPERWEEK);
00344                             if (top < -3)
00345                                 top += DAYSPERWEEK;
00346                             top += len;
00347                             if (yday >= top)
00348                             {
00349                                 ++base;
00350                                 w = 1;
00351                                 break;
00352                             }
00353                             if (yday >= bot)
00354                             {
00355                                 w = 1 + ((yday - bot) /
00356                                          DAYSPERWEEK);
00357                                 break;
00358                             }
00359                             --base;
00360                             yday += isleap_sum(year, base) ?
00361                                 DAYSPERLYEAR :
00362                                 DAYSPERNYEAR;
00363                         }
00364                         if (*format == 'V')
00365                             pt = _conv(w, "%02d",
00366                                        pt, ptlim);
00367                         else if (*format == 'g')
00368                         {
00369                             *warnp = IN_ALL;
00370                             pt = _yconv(year, base, 0, 1,
00371                                         pt, ptlim);
00372                         }
00373                         else
00374                             pt = _yconv(year, base, 1, 1,
00375                                         pt, ptlim);
00376                     }
00377                     continue;
00378                 case 'v':
00379 
00380                     /*
00381                      * From Arnold Robbins' strftime version 3.0: "date as
00382                      * dd-bbb-YYYY" (ado, 1993-05-24)
00383                      */
00384                     pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp);
00385                     continue;
00386                 case 'W':
00387                     pt = _conv((t->tm_yday + DAYSPERWEEK -
00388                                 (t->tm_wday ?
00389                                  (t->tm_wday - 1) :
00390                                  (DAYSPERWEEK - 1))) / DAYSPERWEEK,
00391                                "%02d", pt, ptlim);
00392                     continue;
00393                 case 'w':
00394                     pt = _conv(t->tm_wday, "%d", pt, ptlim);
00395                     continue;
00396                 case 'X':
00397                     pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp);
00398                     continue;
00399                 case 'x':
00400                     {
00401                         int         warn2 = IN_SOME;
00402 
00403                         pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2);
00404                         if (warn2 == IN_ALL)
00405                             warn2 = IN_THIS;
00406                         if (warn2 > *warnp)
00407                             *warnp = warn2;
00408                     }
00409                     continue;
00410                 case 'y':
00411                     *warnp = IN_ALL;
00412                     pt = _yconv(t->tm_year, TM_YEAR_BASE, 0, 1,
00413                                 pt, ptlim);
00414                     continue;
00415                 case 'Y':
00416                     pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 1,
00417                                 pt, ptlim);
00418                     continue;
00419                 case 'Z':
00420                     if (t->tm_zone != NULL)
00421                         pt = _add(t->tm_zone, pt, ptlim);
00422 
00423                     /*
00424                      * C99 says that %Z must be replaced by the empty string
00425                      * if the time zone is not determinable.
00426                      */
00427                     continue;
00428                 case 'z':
00429                     {
00430                         int         diff;
00431                         char const *sign;
00432 
00433                         if (t->tm_isdst < 0)
00434                             continue;
00435                         diff = t->tm_gmtoff;
00436                         if (diff < 0)
00437                         {
00438                             sign = "-";
00439                             diff = -diff;
00440                         }
00441                         else
00442                             sign = "+";
00443                         pt = _add(sign, pt, ptlim);
00444                         diff /= 60;
00445                         pt = _conv((diff / 60) * 100 + diff % 60,
00446                                    "%04d", pt, ptlim);
00447                     }
00448                     continue;
00449                 case '+':
00450                     pt = _fmt(Locale->date_fmt, t, pt, ptlim,
00451                               warnp);
00452                     continue;
00453                 case '%':
00454 
00455                     /*
00456                      * X311J/88-090 (4.12.3.5): if conversion char is
00457                      * undefined, behavior is undefined.  Print out the
00458                      * character itself as printf(3) also does.
00459                      */
00460                 default:
00461                     break;
00462             }
00463         }
00464         if (pt == ptlim)
00465             break;
00466         *pt++ = *format;
00467     }
00468     return pt;
00469 }
00470 
00471 static char *
00472 _conv(int n, const char *format, char *pt, const char *ptlim)
00473 {
00474     char        buf[INT_STRLEN_MAXIMUM(int) +1];
00475 
00476     (void) sprintf(buf, format, n);
00477     return _add(buf, pt, ptlim);
00478 }
00479 
00480 static char *
00481 _add(const char *str, char *pt, const char *ptlim)
00482 {
00483     while (pt < ptlim && (*pt = *str++) != '\0')
00484         ++pt;
00485     return pt;
00486 }
00487 
00488 /*
00489  * POSIX and the C Standard are unclear or inconsistent about
00490  * what %C and %y do if the year is negative or exceeds 9999.
00491  * Use the convention that %C concatenated with %y yields the
00492  * same output as %Y, and that %Y contains at least 4 bytes,
00493  * with more only if necessary.
00494  */
00495 static char *
00496 _yconv(const int a, const int b, const int convert_top,
00497        const int convert_yy, char *pt, const char *const ptlim)
00498 {
00499     int         lead;
00500     int         trail;
00501 
00502 #define DIVISOR       100
00503     trail = a % DIVISOR + b % DIVISOR;
00504     lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
00505     trail %= DIVISOR;
00506     if (trail < 0 && lead > 0)
00507     {
00508         trail += DIVISOR;
00509         --lead;
00510     }
00511     else if (lead < 0 && trail > 0)
00512     {
00513         trail -= DIVISOR;
00514         ++lead;
00515     }
00516     if (convert_top)
00517     {
00518         if (lead == 0 && trail < 0)
00519             pt = _add("-0", pt, ptlim);
00520         else
00521             pt = _conv(lead, "%02d", pt, ptlim);
00522     }
00523     if (convert_yy)
00524         pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim);
00525     return pt;
00526 }