Main Page | File List | Globals

src/strptime.c

Go to the documentation of this file.
00001 /* Convert a string representation of time to a time value.
00002    Copyright (C) 1996, 1997, 1998, 1999, 2000 Free Software Foundation, Inc.
00003    This file is part of the GNU C Library.
00004    Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
00005 
00006    The GNU C Library is free software; you can redistribute it and/or
00007    modify it under the terms of the GNU Library General Public License as
00008    published by the Free Software Foundation; either version 2 of the
00009    License, or (at your option) any later version.
00010 
00011    The GNU C Library is distributed in the hope that it will be useful,
00012    but WITHOUT ANY WARRANTY; without even the implied warranty of
00013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014    Library General Public License for more details.
00015 
00016    You should have received a copy of the GNU Library General Public
00017    License along with the GNU C Library; see the file COPYING.LIB.  If not,
00018    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00019    Boston, MA 02111-1307, USA.  */
00020 
00027 void get_locale_strings(void);
00028 
00029 /* XXX This version of the implementation is not really complete.
00030    Some of the fields cannot add information alone.  But if seeing
00031    some of them in the same format (such as year, week and weekday)
00032    this is enough information for determining the date.  */
00033 
00034 #include <ctype.h>
00035 #include <limits.h>
00036 #include <string.h>
00037 #include <time.h>
00038 
00039 #define match_char(ch1, ch2) if (ch1 != ch2) return NULL
00040 
00041 #ifndef Macintosh
00042 #if defined __GNUC__ && __GNUC__ >= 2
00043 # define match_string(cs1, s2) \
00044   ({ size_t len = strlen (cs1);                                               \
00045      int result = strncasecmp ((cs1), (s2), len) == 0;                        \
00046      if (result) (s2) += len;                                                 \
00047      result; })
00048 #else
00049 /* Oh come on.  Get a reasonable compiler.  */
00050 # define match_string(cs1, s2) \
00051   (strncasecmp ((cs1), (s2), strlen (cs1)) ? 0 : ((s2) += strlen (cs1), 1))
00052 #endif
00053 #else
00054 # define match_string(cs1, s2) \
00055   (strncmp ((cs1), (s2), strlen (cs1)) ? 0 : ((s2) += strlen (cs1), 1))
00056 #endif /* mac */
00057 
00058 /* We intentionally do not use isdigit() for testing because this will
00059    lead to problems with the wide character version.  */
00060 #define get_number(from, to, n) \
00061   do {                                                                        \
00062     int __n = n;                                                              \
00063     val = 0;                                                                  \
00064     while (*rp == ' ')                                                        \
00065       ++rp;                                                                   \
00066     if (*rp < '0' || *rp > '9')                                               \
00067       return NULL;                                                            \
00068     do {                                                                      \
00069       val *= 10;                                                              \
00070       val += *rp++ - '0';                                                     \
00071     } while (--__n > 0 && val * 10 <= to && *rp >= '0' && *rp <= '9');        \
00072     if (val < from || val > to)                                               \
00073       return NULL;                                                            \
00074   } while (0)
00075 # define get_alt_number(from, to, n) \
00076   /* We don't have the alternate representation.  */                          \
00077   get_number(from, to, n)
00078 #define recursive(new_fmt) \
00079   (*(new_fmt) != '\0'                                                         \
00080    && (rp = strptime_internal (rp, (new_fmt), tm, decided)) != NULL)
00081 
00082 /* This version: may overwrite these with versions for the locale */
00083 static char weekday_name[][20] =
00084 {
00085     "Sunday", "Monday", "Tuesday", "Wednesday",
00086     "Thursday", "Friday", "Saturday"
00087 };
00088 static char ab_weekday_name[][10] =
00089 {
00090     "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
00091 };
00092 static char month_name[][20] =
00093 {
00094     "January", "February", "March", "April", "May", "June",
00095     "July", "August", "September", "October", "November", "December"
00096 };
00097 static char ab_month_name[][10] =
00098 {
00099     "Jan", "Feb", "Mar", "Apr", "May", "Jun",
00100     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
00101 };
00102 
00103 static char am_pm[][4] = {"AM", "PM"};
00104 
00105 
00106 # define HERE_D_T_FMT "%a %b %e %H:%M:%S %Y"
00107 # define HERE_D_FMT "%y/%m/%d"
00108 # define HERE_T_FMT_AMPM "%I:%M:%S %p"
00109 # define HERE_T_FMT "%H:%M:%S"
00110 
00111 static const unsigned short int __mon_yday[2][13] =
00112 {
00113     /* Normal years.  */
00114     { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
00115     /* Leap years.  */
00116     { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
00117 };
00118 
00119 
00120 /* Status of lookup: do we use the locale data or the raw data?  */
00121 enum locale_status { not, loc, raw };
00122 
00123 # define __isleap(year) \
00124   ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
00125 
00126 /* Compute the day of the week.  */
00127 void
00128 day_of_the_week (struct tm *tm)
00129 {
00130     /* We know that January 1st 1970 was a Thursday (= 4).  Compute the
00131        the difference between this data in the one on TM and so determine
00132        the weekday.  */
00133     int corr_year = 1900 + tm->tm_year - (tm->tm_mon < 2);
00134     int wday = (-473
00135                 + (365 * (tm->tm_year - 70))
00136                 + (corr_year / 4)
00137                 - ((corr_year / 4) / 25) + ((corr_year / 4) % 25 < 0)
00138                 + (((corr_year / 4) / 25) / 4)
00139                 + __mon_yday[0][tm->tm_mon]
00140                 + tm->tm_mday - 1);
00141     tm->tm_wday = ((wday % 7) + 7) % 7;
00142 }
00143 
00144 /* Compute the day of the year.  */
00145 void
00146 day_of_the_year (struct tm *tm)
00147 {
00148     tm->tm_yday = (__mon_yday[__isleap (1900 + tm->tm_year)][tm->tm_mon]
00149                    + (tm->tm_mday - 1));
00150 }
00151 
00152 char *
00153 strptime_internal (const char *rp, const char *fmt, struct tm *tm,
00154                    enum locale_status *decided)
00155 {
00156     const char *rp_backup;
00157     int cnt;
00158     size_t val;
00159     int have_I, is_pm;
00160     int century, want_century;
00161     int have_wday, want_xday;
00162     int have_yday;
00163     int have_mon, have_mday;
00164 
00165     have_I = is_pm = 0;
00166     century = -1;
00167     want_century = 0;
00168     have_wday = want_xday = have_yday = have_mon = have_mday = 0;
00169 
00170     while (*fmt != '\0')
00171     {
00172         /* A white space in the format string matches 0 more or white
00173            space in the input string.  */
00174         if (isspace (*fmt))
00175         {
00176             while (isspace (*rp))
00177                 ++rp;
00178             ++fmt;
00179             continue;
00180         }
00181 
00182         /* Any character but `%' must be matched by the same character
00183            in the iput string.  */
00184         if (*fmt != '%')
00185         {
00186             match_char (*fmt++, *rp++);
00187             continue;
00188         }
00189 
00190         ++fmt;
00191 
00192         /* We need this for handling the `E' modifier.  */
00193     start_over:
00194 
00195         /* Make back up of current processing pointer.  */
00196         rp_backup = rp;
00197 
00198         switch (*fmt++)
00199         {
00200         case '%':
00201             /* Match the `%' character itself.  */
00202             match_char ('%', *rp++);
00203             break;
00204         case 'a':
00205         case 'A':
00206             /* Match day of week.  */
00207             for (cnt = 0; cnt < 7; ++cnt)
00208             {
00209                 if (*decided != loc
00210                     && (match_string (weekday_name[cnt], rp)
00211                         || match_string (ab_weekday_name[cnt], rp)))
00212                 {
00213                     *decided = raw;
00214                     break;
00215                 }
00216             }
00217             if (cnt == 7)
00218                 /* Does not match a weekday name.  */
00219                 return NULL;
00220             tm->tm_wday = cnt;
00221             have_wday = 1;
00222             break;
00223         case 'b':
00224         case 'B':
00225         case 'h':
00226             /* Match month name.  */
00227             for (cnt = 0; cnt < 12; ++cnt)
00228             {
00229                 if (match_string (month_name[cnt], rp)
00230                     || match_string (ab_month_name[cnt], rp))
00231                 {
00232                     *decided = raw;
00233                     break;
00234                 }
00235             }
00236             if (cnt == 12)
00237                 /* Does not match a month name.  */
00238                 return NULL;
00239             tm->tm_mon = cnt;
00240             want_xday = 1;
00241             break;
00242         case 'c':
00243             /* Match locale's date and time format.  */
00244             if (!recursive (HERE_T_FMT_AMPM))
00245                 return NULL;
00246             break;
00247         case 'C':
00248           /* Match century number.  */
00249           get_number (0, 99, 2);
00250           century = val;
00251           want_xday = 1;
00252           break;
00253         case 'd':
00254         case 'e':
00255           /* Match day of month.  */
00256           get_number (1, 31, 2);
00257           tm->tm_mday = val;
00258           have_mday = 1;
00259           want_xday = 1;
00260           break;
00261         case 'F':
00262           if (!recursive ("%Y-%m-%d"))
00263             return NULL;
00264           want_xday = 1;
00265           break;
00266         case 'x':
00267           /* Fall through.  */
00268         case 'D':
00269           /* Match standard day format.  */
00270           if (!recursive (HERE_D_FMT))
00271             return NULL;
00272           want_xday = 1;
00273           break;
00274         case 'k':
00275         case 'H':
00276           /* Match hour in 24-hour clock.  */
00277           get_number (0, 23, 2);
00278           tm->tm_hour = val;
00279           have_I = 0;
00280           break;
00281         case 'I':
00282           /* Match hour in 12-hour clock.  */
00283           get_number (1, 12, 2);
00284           tm->tm_hour = val % 12;
00285           have_I = 1;
00286           break;
00287         case 'j':
00288           /* Match day number of year.  */
00289           get_number (1, 366, 3);
00290           tm->tm_yday = val - 1;
00291           have_yday = 1;
00292           break;
00293         case 'm':
00294           /* Match number of month.  */
00295           get_number (1, 12, 2);
00296           tm->tm_mon = val - 1;
00297           have_mon = 1;
00298           want_xday = 1;
00299           break;
00300         case 'M':
00301           /* Match minute.  */
00302           get_number (0, 59, 2);
00303           tm->tm_min = val;
00304           break;
00305         case 'n':
00306         case 't':
00307           /* Match any white space.  */
00308           while (isspace (*rp))
00309             ++rp;
00310           break;
00311         case 'p':
00312           /* Match locale's equivalent of AM/PM.  */
00313           if (!match_string (am_pm[0], rp))
00314             if (match_string (am_pm[1], rp))
00315               is_pm = 1;
00316             else
00317               return NULL;
00318           break;
00319         case 'r':
00320           if (!recursive (HERE_T_FMT_AMPM))
00321             return NULL;
00322           break;
00323         case 'R':
00324             if (!recursive ("%H:%M"))
00325                 return NULL;
00326             break;
00327         case 's':
00328         {
00329             /* The number of seconds may be very high so we cannot use
00330                the `get_number' macro.  Instead read the number
00331                character for character and construct the result while
00332                doing this.  */
00333             time_t secs = 0;
00334             if (*rp < '0' || *rp > '9')
00335                 /* We need at least one digit.  */
00336                 return NULL;
00337 
00338             do
00339             {
00340                 secs *= 10;
00341                 secs += *rp++ - '0';
00342             }
00343             while (*rp >= '0' && *rp <= '9');
00344 
00345             if ((tm = localtime (&secs)) == NULL)
00346                 /* Error in function.  */
00347                 return NULL;
00348         }
00349         break;
00350         case 'S':
00351             get_number (0, 61, 2);
00352             tm->tm_sec = val;
00353             break;
00354         case 'X':
00355             /* Fall through.  */
00356         case 'T':
00357             if (!recursive (HERE_T_FMT))
00358                 return NULL;
00359             break;
00360         case 'u':
00361             get_number (1, 7, 1);
00362             tm->tm_wday = val % 7;
00363             have_wday = 1;
00364             break;
00365         case 'g':
00366             get_number (0, 99, 2);
00367             /* XXX This cannot determine any field in TM.  */
00368             break;
00369         case 'G':
00370             if (*rp < '0' || *rp > '9')
00371                 return NULL;
00372             /* XXX Ignore the number since we would need some more
00373                information to compute a real date.  */
00374             do
00375                 ++rp;
00376             while (*rp >= '0' && *rp <= '9');
00377             break;
00378         case 'U':
00379         case 'V':
00380         case 'W':
00381             get_number (0, 53, 2);
00382             /* XXX This cannot determine any field in TM without some
00383                information.  */
00384             break;
00385         case 'w':
00386             /* Match number of weekday.  */
00387             get_number (0, 6, 1);
00388             tm->tm_wday = val;
00389             have_wday = 1;
00390             break;
00391         case 'y':
00392             /* Match year within century.  */
00393             get_number (0, 99, 2);
00394             /* The "Year 2000: The Millennium Rollover" paper suggests that
00395                values in the range 69-99 refer to the twentieth century.  */
00396             tm->tm_year = val >= 69 ? val : val + 100;
00397             /* Indicate that we want to use the century, if specified.  */
00398             want_century = 1;
00399             want_xday = 1;
00400             break;
00401         case 'Y':
00402             /* Match year including century number.  */
00403             get_number (0, 9999, 4);
00404             tm->tm_year = val - 1900;
00405             want_century = 0;
00406             want_xday = 1;
00407             break;
00408         case 'Z':
00409             /* XXX How to handle this?  */
00410             break;
00411         case 'E':
00412             /* We have no information about the era format.  Just use
00413                the normal format.  */
00414             if (*fmt != 'c' && *fmt != 'C' && *fmt != 'y' && *fmt != 'Y'
00415                 && *fmt != 'x' && *fmt != 'X')
00416                 /* This is an invalid format.  */
00417                 return NULL;
00418 
00419             goto start_over;
00420         case 'O':
00421             switch (*fmt++)
00422             {
00423             case 'd':
00424             case 'e':
00425                 /* Match day of month using alternate numeric symbols.  */
00426                 get_alt_number (1, 31, 2);
00427                 tm->tm_mday = val;
00428                 have_mday = 1;
00429                 want_xday = 1;
00430                 break;
00431             case 'H':
00432                 /* Match hour in 24-hour clock using alternate numeric
00433                    symbols.  */
00434                 get_alt_number (0, 23, 2);
00435                 tm->tm_hour = val;
00436                 have_I = 0;
00437                 break;
00438             case 'I':
00439                 /* Match hour in 12-hour clock using alternate numeric
00440                    symbols.  */
00441                 get_alt_number (1, 12, 2);
00442                 tm->tm_hour = val - 1;
00443                 have_I = 1;
00444                 break;
00445             case 'm':
00446                 /* Match month using alternate numeric symbols.  */
00447                 get_alt_number (1, 12, 2);
00448                 tm->tm_mon = val - 1;
00449                 have_mon = 1;
00450                 want_xday = 1;
00451                 break;
00452             case 'M':
00453                 /* Match minutes using alternate numeric symbols.  */
00454                 get_alt_number (0, 59, 2);
00455                 tm->tm_min = val;
00456                 break;
00457             case 'S':
00458                 /* Match seconds using alternate numeric symbols.  */
00459                 get_alt_number (0, 61, 2);
00460                 tm->tm_sec = val;
00461                 break;
00462             case 'U':
00463             case 'V':
00464             case 'W':
00465                 get_alt_number (0, 53, 2);
00466                 /* XXX This cannot determine any field in TM without
00467                    further information.  */
00468                 break;
00469             case 'w':
00470                 /* Match number of weekday using alternate numeric symbols.  */
00471                 get_alt_number (0, 6, 1);
00472                 tm->tm_wday = val;
00473                 have_wday = 1;
00474                 break;
00475             case 'y':
00476                 /* Match year within century using alternate numeric symbols.  */
00477                 get_alt_number (0, 99, 2);
00478                 tm->tm_year = val >= 69 ? val : val + 100;
00479                 want_xday = 1;
00480                 break;
00481             default:
00482                 return NULL;
00483             }
00484             break;
00485         default:
00486             return NULL;
00487         }
00488     }
00489 
00490     if (have_I && is_pm)
00491         tm->tm_hour += 12;
00492 
00493     if (century != -1)
00494     {
00495         if (want_century)
00496             tm->tm_year = tm->tm_year % 100 + (century - 19) * 100;
00497         else
00498             /* Only the century, but not the year.  Strange, but so be it.  */
00499             tm->tm_year = (century - 19) * 100;
00500     }
00501 
00502     if (want_xday && !have_wday) {
00503         if ( !(have_mon && have_mday) && have_yday)  {
00504             /* we don't have tm_mon and/or tm_mday, compute them */
00505             int t_mon = 0;
00506             while (__mon_yday[__isleap(1900 + tm->tm_year)][t_mon] <= tm->tm_yday)
00507                 t_mon++;
00508             if (!have_mon)
00509                 tm->tm_mon = t_mon - 1;
00510             if (!have_mday)
00511                 tm->tm_mday = tm->tm_yday - __mon_yday[__isleap(1900 + tm->tm_year)][t_mon - 1] + 1;
00512         }
00513         day_of_the_week (tm);
00514     }
00515     if (want_xday && !have_yday)
00516         day_of_the_year (tm);
00517 
00518     return (char *) rp;
00519 }
00520 
00521 char *
00522 strptime (const char *buf, const char *format, struct tm *tm)
00523 {
00524     enum locale_status decided;
00525 #ifdef HAVE_LOCALE_H
00526     if(!have_used_strptime) {
00527         get_locale_strings();
00528         /* have_used_strptime = 1; might change locale during session */
00529     }
00530 #endif
00531     decided = raw;
00532     return strptime_internal (buf, format, tm, &decided);
00533 }
00534 
00535 #ifdef HAVE_LOCALE_H
00536 void get_locale_strings(void)
00537 {
00538     int i;
00539     struct tm tm;
00540     char buff[4];
00541 
00542     tm.tm_sec = tm.tm_min = tm.tm_hour = tm.tm_mday = tm.tm_mon
00543         = tm.tm_isdst = 0;
00544     tm.tm_year = 30;
00545     for(i = 0; i < 12; i++) {
00546         tm.tm_mon = i;
00547         strftime(ab_month_name[i], 10, "%b", &tm);
00548         strftime(month_name[i], 20, "%B", &tm);
00549     }
00550     tm.tm_mon = 0;
00551     for(i = 0; i < 7; i++) {
00552         tm.tm_mday = tm.tm_yday = i+1; /* 2000-1-2 was a Sunday */
00553         tm.tm_wday = i;
00554         strftime(ab_weekday_name[i], 10, "%a", &tm);
00555         strftime(weekday_name[i], 20, "%A", &tm);
00556     }
00557     tm.tm_hour = 1;
00558     /* in locales where these are unused, they may be empty: better
00559        not to reset them then */
00560     strftime(buff, 4, "%p", &tm);
00561     if(strlen(buff)) strcpy(am_pm[0], buff);
00562     tm.tm_hour = 13;
00563     strftime(buff, 4, "%p", &tm);
00564     if(strlen(buff)) strcpy(am_pm[1], buff);
00565 }
00566 #endif

Generated on Sun Sep 4 11:16:47 2005 for PlibC by  doxygen 1.4.2