Sync strptime with NetBSD:
authorPeter Avalos <pavalos@theshell.com>
Fri, 16 Jan 2009 23:45:43 +0000 (18:45 -0500)
committerPeter Avalos <pavalos@theshell.com>
Tue, 7 Apr 2009 07:08:37 +0000 (21:08 -1000)
-A few code optimiztions.

-%y failed to subtract 1900 from tm_year

-%p (am/pm) only worked at the end of the string

-%E% was treated as %% (ie match a % character)

-Add support for %Z.

-Add %F the ISO 8601 date format which is equivalent to %Y-%m-%d.

-Add gnu extensions: %z, %g, %G, %u.

-Bring in NetBSD's strptime(3) manual page.

lib/libc/stdtime/strptime.3
lib/libc/stdtime/strptime.c

index c77001e..4ab046d 100644 (file)
@@ -1,8 +1,10 @@
+.\"    $NetBSD: strptime.3,v 1.22 2008/11/04 18:37:28 christos Exp $
 .\"
-.\" Copyright (c) 1997 Joerg Wunsch
-.\"
+.\" Copyright (c) 1997, 1998, 2008 The NetBSD Foundation, Inc.
 .\" All rights reserved.
 .\"
+.\" This file was contributed to The NetBSD Foundation by Klaus Klein.
+.\"
 .\" Redistribution and use in source and binary forms, with or without
 .\" modification, are permitted provided that the following conditions
 .\" are met:
 .\"    notice, this list of conditions and the following disclaimer in the
 .\"    documentation and/or other materials provided with the distribution.
 .\"
-.\" THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY EXPRESS OR
-.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
-.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
-.\" IN NO EVENT SHALL THE DEVELOPERS BE LIABLE FOR ANY DIRECT, INDIRECT,
-.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
-.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
-.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
 .\"
-.\" $FreeBSD: src/lib/libc/stdtime/strptime.3,v 1.9.2.9 2003/05/24 00:01:31 keramida Exp $
-.\" $DragonFly: src/lib/libc/stdtime/strptime.3,v 1.3 2006/02/17 19:35:06 swildner Exp $
-.\" "
-.Dd January 4, 2003
-.Dt STRPTIME 3
+.Dd November 4, 2008
 .Os
+.Dt STRPTIME 3
 .Sh NAME
 .Nm strptime
-.Nd parse date and time string
+.Nd converts a character string to a time value
 .Sh LIBRARY
 .Lb libc
 .Sh SYNOPSIS
 .In time.h
 .Ft char *
-.Fo strptime
-.Fa "const char * restrict buf"
-.Fa "const char * restrict format"
-.Fa "struct tm * restrict timeptr"
-.Fc
+.Fn strptime "const char * restrict buf" "const char * restrict format" "struct tm * restrict tm"
 .Sh DESCRIPTION
 The
 .Fn strptime
-function parses the string in the buffer
+function converts the character string pointed to by
 .Fa buf
-according to the string pointed to by
-.Fa format ,
-and fills in the elements of the structure pointed to by
-.Fa timeptr .
-The resulting values will be relative to the local time zone.
-Thus, it can be considered the reverse operation of
-.Xr strftime 3 .
+to values which are stored in the
+.Va tm
+structure pointed to by
+.Fa tm ,
+using the format specified by
+.Fa format .
 .Pp
 The
 .Fa format
-string consists of zero or more conversion specifications and
-ordinary characters.
-All ordinary characters are matched exactly with the buffer, where
-white space in the format string will match any amount of white space
-in the buffer.
-All conversion specifications are identical to those described in
-.Xr strftime 3 .
-.Pp
-Two-digit year values, including formats
-.Fa %y
-and
-.Fa \&%D ,
-are now interpreted as beginning at 1969 per POSIX requirements.
-Years 69-00 are interpreted in the 20th century (1969-2000), years
-01-68 in the 21st century (2001-2068).
-.Pp
-If the
+string consists of zero or more conversion specifications, whitespace
+characters as defined by
+.Fn isspace ,
+and ordinary characters.
+All ordinary characters in
 .Fa format
-string does not contain enough conversion specifications to completely
-specify the resulting
-.Vt struct tm ,
-the unspecified members of
-.Va timeptr
-are left untouched.
-For example, if
+are compared directly against the corresponding characters in
+.Fa buf ;
+comparisons which fail will cause
+.Fn strptime
+to fail.
+Whitespace characters in
 .Fa format
-is
-.Dq Li "%H:%M:%S" ,
-only
-.Va tm_hour ,
-.Va tm_sec
+match any number of whitespace characters in
+.Fa buf ,
+including none.
+.Pp
+A conversion specification consists of a percent sign
+.Ql %
+followed by one
+or two conversion characters which specify the replacement required.
+There must be white-space or other non-alphanumeric characters between any
+two conversion specifications.
+.Pp
+Conversion of alphanumeric strings (such as month and weekday names) is
+done without regard to case.
+Conversion specifications which cannot be matched will cause
+.Fn strptime
+to fail.
+.Pp
+The LC_TIME category defines the locale values for the conversion
+specifications.
+The following conversion specifications are supported:
+.Bl -tag -width "xxxx"
+.It Cm \&%a
+the day of week, using the locale's weekday names;
+either the abbreviated or full name may be specified.
+.It Cm \&%A
+the same as
+.Cm \&%a .
+.It Cm \&%b
+the month, using the locale's month names;
+either the abbreviated or full name may be specified.
+.It Cm \&%B
+the same as
+.Cm \&%b .
+.It Cm \&%c
+the date and time, using the locale's date and time format.
+.It Cm \&%C
+the century number [0,99];
+leading zeros are permitted but not required.
+This conversion should be used in conjunction with the \&%y conversion.
+.It Cm \&%d
+the day of month [1,31];
+leading zeros are permitted but not required.
+.It Cm \&%D
+the date as %m/%d/%y.
+.It Cm \&%e
+the same as
+.Cm \&%d .
+.It Cm \&%F
+the date as %Y-%m-%d
+(the ISO 8601 date format).
+.It Cm \&%g
+the year corresponding to the ISO week number, without the century.
+.Po
+A
+.Nx
+extension.
+.Pc
+.It Cm \&%G
+the year corresponding to the ISO week number, with the century.
+.Po
+A
+.Nx
+extension.
+.Pc
+.It Cm \&%h
+the same as
+.Cm \&%b .
+.It Cm \&%H
+the hour (24-hour clock) [0,23];
+leading zeros are permitted but not required.
+.It Cm \&%I
+the hour (12-hour clock) [1,12];
+leading zeros are permitted but not required.
+.It Cm \&%j
+the day number of the year [1,366];
+leading zeros are permitted but not required.
+.It Cm \&%k
+the same as
+.Cm \&%H .
+.It Cm \&%l
+the same as
+.Cm \&%I .
+.It Cm \&%m
+the month number [1,12];
+leading zeros are permitted but not required.
+.It Cm \&%M
+the minute [0,59];
+leading zeros are permitted but not required.
+.It Cm \&%n
+any white-space, including none.
+.It Cm \&%p
+the locale's equivalent of a.m. or p.m.
+.It Cm \&%r
+the time (12-hour clock) with %p, using the locale's time format.
+.It Cm \&%R
+the time as %H:%M.
+.It Cm \&%S
+the seconds [0,61];
+leading zeros are permitted but not required.
+.It Cm \&%t
+any white-space, including none.
+.It Cm \&%T
+the time as %H:%M:%S.
+.It Cm \&%u
+the day of the week as a decimal number, where Monday = 1.
+.Po
+A
+.Nx
+extension.
+.Pc
+.It Cm \&%U
+the week number of the year (Sunday as the first day of the week)
+as a decimal number [0,53];
+leading zeros are permitted but not required.
+All days in a year preceding the first Sunday are considered to be in week 0.
+.It Cm \&%V
+the ISO 8601:1988 week number as a decimal number.
+If the week (starting on Monday) that contains January 1 has more than
+three days in the new year, then it is considered the first week of the
+year.
+If it has fewer than four days in the new year, then it is considered
+the last week of the previous year.
+Weeks are numbered from 1 to 53.
+A
+.Nx
+extension.
+.It Cm \&%w
+the weekday as a decimal number [0,6], with 0 representing Sunday;
+leading zeros are permitted but not required.
+.It Cm \&%W
+the week number of the year (Monday as the first day of the week)
+as a decimal number [0,53];
+leading zeros are permitted but not required.
+All days in a year preceding the first Monday are considered to be in week 0.
+.It Cm \&%x
+the date, using the locale's date format.
+.It Cm \&%X
+the time, using the locale's time format.
+.It Cm \&%y
+the year within the 20th century [69,99] or the 21st century [0,68];
+leading zeros are permitted but not required.
+If specified in conjunction
+with \&%C, specifies the year [0,99] within that century.
+.It Cm \&%Y
+the year, including the century (i.e., 1996).
+.It Cm \&%z
+an ISO 8601 timezone specification.
+This is either,
+.Dq Z
+for
+.Ql UTC ,
+or the offset specified as:
+.Dq [+-]hhmm
+or
+.Dq [+-]hh:mm
+or
+.Dq [+-]hh .
+.Po
+A
+.Nx
+extension.
+.Pc
+.It Cm \&%Z
+timezone name or no characters when time zone information is unavailable.
+.Po
+A
+.Nx
+extension.
+.Pc
+.It Cm \&%%
+matches a literal `%'.
+No argument is converted.
+.El
+.Ss Modified conversion specifications
+For compatibility, certain conversion specifications can be modified
+by the
+.Cm E
 and
-.Va tm_min
-will be modified.
-If time relative to today is desired, initialize the
-.Fa timeptr
-structure with today's date before passing it to
-.Fn strptime .
+.Cm O
+modifier characters to indicate that an alternative format or specification
+should be used rather than the one normally used by the unmodified
+conversion specification.
+As there are currently neither alternative formats
+nor specifications supported by the system, the behavior will be as if the
+unmodified conversion specification were used.
+.Pp
+Case is ignored when matching string items in
+.Fa buf ,
+such as month and weekday names.
 .Sh RETURN VALUES
-Upon successful completion,
+If successful, the
 .Fn strptime
-returns the pointer to the first character in
-.Fa buf
-that has not been required to satisfy the specified conversions in
-.Fa format .
-It returns
-.Dv NULL
-if one of the conversions failed.
+function returns a pointer to the character following the last character
+parsed.
+Otherwise, a null pointer is returned.
 .Sh SEE ALSO
-.Xr date 1 ,
-.Xr scanf 3 ,
+.Xr ctime 3 ,
+.Xr isspace 3 ,
+.Xr localtime 3 ,
 .Xr strftime 3
-.Sh HISTORY
-The
-.Fn strptime
-function appeared in
-.Fx 3.0 .
-.Sh AUTHORS
+.Sh STANDARDS
 The
 .Fn strptime
-function has been contributed by Powerdog Industries.
-.Pp
-This man page was written by
-.An J\(:org Wunsch .
+function conforms to
+.St -xpg4 .
 .Sh BUGS
-Both the
-.Fa %e
-and
-.Fa %l
-format specifiers may incorrectly scan one too many digits
-if the intended values comprise only a single digit
-and that digit is followed immediately by another digit.
-Both specifiers accept zero-padded values,
-even though they are both defined as taking unpadded values.
-.Pp
-The
-.Fa %p
-format specifier has no effect unless it is parsed
-.Em after
-hour-related specifiers.
-Specifying
-.Fa %l
-without
-.Fa %p
-will produce undefined results.
-Note that 12AM
-(ante meridiem)
-is taken as midnight
-and 12PM
-(post meridiem)
-is taken as noon.
-.Pp
 The
-.Fa %U
-and
-.Fa %W
-format specifiers accept any value within the range 00 to 53
-without validating against other values supplied (like month
-or day of the year, for example).
-.Pp
-The
-.Fa %Z
-format specifier only accepts time zone abbreviations of the local time zone,
-or the value "GMT".
-This limitation is because of ambiguity due to of the over loading of time
-zone abbreviations.  One such example is
-.Fa EST
-which is both Eastern Standard Time and Eastern Australia Summer Time.
-.Pp
-The
-.Fn strptime
-function does not correctly handle multibyte characters in the
-.Fa format
-argument.
+.Cm \&%Z
+format specifier only accepts timezone
+abbreviations of the local timezone,
+or the value
+.Dq GMT .
+This limitation is caused by the ambiguity
+of overloaded timezone abbreviations,
+for example EST is both Eastern Standard
+Time and Eastern Australia Summer Time.
index 8682f75..7f3084b 100644 (file)
@@ -1,11 +1,12 @@
-/*     $NetBSD: src/lib/libc/time/strptime.c,v 1.22 2000/12/20 20:56:34 christos Exp $ */
+/*     $NetBSD: strptime.c,v 1.31 2008/11/04 21:08:33 christos Exp $   */
 /*     $DragonFly: src/lib/libc/stdtime/strptime.c,v 1.5 2005/12/04 23:25:40 swildner Exp $ */
 
 /*-
- * Copyright (c) 1997, 1998 The NetBSD Foundation, Inc.
+ * Copyright (c) 1997, 1998, 2005, 2008 The NetBSD Foundation, Inc.
  * All rights reserved.
  *
  * This code was contributed to The NetBSD Foundation by Klaus Klein.
+ * Heavily optimised by David Laight
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
- * 3. All advertising materials mentioning features or use of this software
- *    must display the following acknowledgement:
- *        This product includes software developed by the NetBSD
- *        Foundation, Inc. and its contributors.
- * 4. Neither the name of The NetBSD Foundation nor the names of its
- *    contributors may be used to endorse or promote products derived
- *    from this software without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  */
 #define        ALT_E                   0x01
 #define        ALT_O                   0x02
-#define        LEGAL_ALT(x)            { if (alt_format & ~(x)) return (0); }
+#define        LEGAL_ALT(x)            { if (alt_format & ~(x)) return NULL; }
 
-static int conv_num(const unsigned char **, int *, int, int);
+static char gmt[] = { "GMT" };
+static char utc[] = { "UTC" };
+
+static const u_char *conv_num(const unsigned char *, int *, uint, uint);
+static const u_char *find_string(const u_char *, int *, const char * const *,
+       const char * const *, int);
 
 char *
 strptime(const char *buf, const char *fmt, struct tm *tm)
 {
        unsigned char c;
        const unsigned char *bp;
-       size_t len = 0;
-       int alt_format, i, split_year = 0;
+       int alt_format, i, split_year = 0, neg, offs;
+       const char *new_fmt;
 
        bp = (const u_char *)buf;
 
-       while ((c = *fmt) != '\0') {
+       while (bp != NULL && (c = *fmt++) != '\0') {
                /* Clear `alternate' modifier prior to new conversion. */
                alt_format = 0;
+               i = 0;
 
                /* Eat up white-space. */
                if (isspace(c)) {
                        while (isspace(*bp))
                                bp++;
-
-                       fmt++;
                        continue;
                }
-                               
-               if ((c = *fmt++) != '%')
+
+               if (c != '%')
                        goto literal;
 
 
@@ -87,8 +85,9 @@ again:                switch (c = *fmt++) {
                case '%':       /* "%%" is converted to "%". */
 literal:
                        if (c != *bp++)
-                               return (0);
-                       break;
+                               return NULL;
+                       LEGAL_ALT(0);
+                       continue;
 
                /*
                 * "Alternative" modifiers. Just set the appropriate flag
@@ -103,290 +102,365 @@ literal:
                        LEGAL_ALT(0);
                        alt_format |= ALT_O;
                        goto again;
-                       
+
                /*
                 * "Complex" conversion rules, implemented through recursion.
                 */
                case 'c':       /* Date and time, using the locale's format. */
-                       LEGAL_ALT(ALT_E);
-                       if (!(bp = (const u_char *)strptime((const char *)bp,
-                           _ctloc(d_t_fmt), tm)))
-                               return (0);
-                       break;
+                       new_fmt = _ctloc(d_t_fmt);
+                       goto recurse;
 
                case 'D':       /* The date as "%m/%d/%y". */
+                       new_fmt = "%m/%d/%y";
+                       LEGAL_ALT(0);
+                       goto recurse;
+
+               case 'F':       /* The date as "%Y-%m-%d". */
+                       new_fmt = "%Y-%m-%d";
                        LEGAL_ALT(0);
-                       if (!(bp = (const u_char *) strptime((const char *)bp,
-                           "%m/%d/%y", tm)))
-                               return (0);
-                       break;
+                       goto recurse;
 
                case 'R':       /* The time as "%H:%M". */
+                       new_fmt = "%H:%M";
                        LEGAL_ALT(0);
-                       if (!(bp = (const u_char *)strptime((const char *)bp,
-                           "%H:%M", tm)))
-                               return (0);
-                       break;
+                       goto recurse;
 
                case 'r':       /* The time in 12-hour clock representation. */
+                       new_fmt =_ctloc(t_fmt_ampm);
                        LEGAL_ALT(0);
-                       if (!(bp = (const u_char *)strptime((const char *)bp,
-                           _ctloc(t_fmt_ampm), tm)))
-                               return (0);
-                       break;
+                       goto recurse;
 
                case 'T':       /* The time as "%H:%M:%S". */
+                       new_fmt = "%H:%M:%S";
                        LEGAL_ALT(0);
-                       if (!(bp = (const u_char *)strptime((const char *)bp,
-                           "%H:%M:%S", tm)))
-                               return (0);
-                       break;
+                       goto recurse;
 
                case 'X':       /* The time, using the locale's format. */
-                       LEGAL_ALT(ALT_E);
-                       if (!(bp = (const u_char *)strptime((const char *)bp,
-                           _ctloc(t_fmt), tm)))
-                               return (0);
-                       break;
+                       new_fmt =_ctloc(t_fmt);
+                       goto recurse;
 
                case 'x':       /* The date, using the locale's format. */
+                       new_fmt =_ctloc(d_fmt);
+                   recurse:
+                       bp = (const u_char *)strptime((const char *)bp,
+                                                           new_fmt, tm);
                        LEGAL_ALT(ALT_E);
-                       if (!(bp = (const u_char *)strptime((const char *)bp,
-                           _ctloc(d_fmt), tm)))
-                               return (0);
-                       break;
+                       continue;
 
                /*
                 * "Elementary" conversion rules.
                 */
                case 'A':       /* The day of week, using the locale's form. */
                case 'a':
+                       bp = find_string(bp, &tm->tm_wday, _ctloc(day),
+                                       _ctloc(abday), 7);
                        LEGAL_ALT(0);
-                       for (i = 0; i < 7; i++) {
-                               /* Full name. */
-                               len = strlen(_ctloc(day[i]));
-                               if (strncasecmp(_ctloc(day[i]),
-                                   (const char *)bp, len) == 0)
-                                       break;
-
-                               /* Abbreviated name. */
-                               len = strlen(_ctloc(abday[i]));
-                               if (strncasecmp(_ctloc(abday[i]),
-                                   (const char *)bp, len) == 0)
-                                       break;
-                       }
-
-                       /* Nothing matched. */
-                       if (i == 7)
-                               return (0);
-
-                       tm->tm_wday = i;
-                       bp += len;
-                       break;
+                       continue;
 
                case 'B':       /* The month, using the locale's form. */
                case 'b':
                case 'h':
+                       bp = find_string(bp, &tm->tm_mon, _ctloc(mon),
+                                       _ctloc(abmon), 12);
                        LEGAL_ALT(0);
-                       for (i = 0; i < 12; i++) {
-                               /* Full name. */
-                               len = strlen(_ctloc(mon[i]));
-                               if (strncasecmp(_ctloc(mon[i]),
-                                   (const char *)bp, len) == 0)
-                                       break;
-
-                               /* Abbreviated name. */
-                               len = strlen(_ctloc(abmon[i]));
-                               if (strncasecmp(_ctloc(abmon[i]),
-                                   (const char *)bp, len) == 0)
-                                       break;
-                       }
-
-                       /* Nothing matched. */
-                       if (i == 12)
-                               return (0);
-
-                       tm->tm_mon = i;
-                       bp += len;
-                       break;
+                       continue;
 
                case 'C':       /* The century number. */
-                       LEGAL_ALT(ALT_E);
-                       if (!(conv_num(&bp, &i, 0, 99)))
-                               return (0);
+                       i = 20;
+                       bp = conv_num(bp, &i, 0, 99);
 
-                       if (split_year) {
-                               tm->tm_year = (tm->tm_year % 100) + (i * 100);
-                       } else {
-                               tm->tm_year = i * 100;
-                               split_year = 1;
-                       }
-                       break;
+                       i = i * 100 - TM_YEAR_BASE;
+                       if (split_year)
+                               i += tm->tm_year % 100;
+                       split_year = 1;
+                       tm->tm_year = i;
+                       LEGAL_ALT(ALT_E);
+                       continue;
 
                case 'd':       /* The day of month. */
                case 'e':
+                       bp = conv_num(bp, &tm->tm_mday, 1, 31);
                        LEGAL_ALT(ALT_O);
-                       if (!(conv_num(&bp, &tm->tm_mday, 1, 31)))
-                               return (0);
-                       break;
+                       continue;
 
                case 'k':       /* The hour (24-hour clock representation). */
                        LEGAL_ALT(0);
                        /* FALLTHROUGH */
                case 'H':
+                       bp = conv_num(bp, &tm->tm_hour, 0, 23);
                        LEGAL_ALT(ALT_O);
-                       if (!(conv_num(&bp, &tm->tm_hour, 0, 23)))
-                               return (0);
-                       break;
+                       continue;
 
                case 'l':       /* The hour (12-hour clock representation). */
                        LEGAL_ALT(0);
                        /* FALLTHROUGH */
                case 'I':
-                       LEGAL_ALT(ALT_O);
-                       if (!(conv_num(&bp, &tm->tm_hour, 1, 12)))
-                               return (0);
+                       bp = conv_num(bp, &tm->tm_hour, 1, 12);
                        if (tm->tm_hour == 12)
                                tm->tm_hour = 0;
-                       break;
+                       LEGAL_ALT(ALT_O);
+                       continue;
 
                case 'j':       /* The day of year. */
-                       LEGAL_ALT(0);
-                       if (!(conv_num(&bp, &i, 1, 366)))
-                               return (0);
+                       i = 1;
+                       bp = conv_num(bp, &i, 1, 366);
                        tm->tm_yday = i - 1;
-                       break;
+                       LEGAL_ALT(0);
+                       continue;
 
                case 'M':       /* The minute. */
+                       bp = conv_num(bp, &tm->tm_min, 0, 59);
                        LEGAL_ALT(ALT_O);
-                       if (!(conv_num(&bp, &tm->tm_min, 0, 59)))
-                               return (0);
-                       break;
+                       continue;
 
                case 'm':       /* The month. */
-                       LEGAL_ALT(ALT_O);
-                       if (!(conv_num(&bp, &i, 1, 12)))
-                               return (0);
+                       i = 1;
+                       bp = conv_num(bp, &i, 1, 12);
                        tm->tm_mon = i - 1;
-                       break;
+                       LEGAL_ALT(ALT_O);
+                       continue;
 
                case 'p':       /* The locale's equivalent of AM/PM. */
+                       bp = find_string(bp, &i, _ctloc(am_pm), NULL, 2);
+                       if (tm->tm_hour > 11)
+                               return NULL;
+                       tm->tm_hour += i * 12;
                        LEGAL_ALT(0);
-                       /* AM? */
-                       if (strcasecmp(_ctloc(am_pm[0]),
-                           (const char *)bp) == 0) {
-                               if (tm->tm_hour > 11)
-                                       return (0);
-
-                               bp += strlen(_ctloc(am_pm[0]));
-                               break;
-                       }
-                       /* PM? */
-                       else if (strcasecmp(_ctloc(am_pm[1]),
-                           (const char *)bp) == 0) {
-                               if (tm->tm_hour > 11)
-                                       return (0);
-
-                               tm->tm_hour += 12;
-                               bp += strlen(_ctloc(am_pm[1]));
-                               break;
-                       }
-
-                       /* Nothing matched. */
-                       return (0);
+                       continue;
 
                case 'S':       /* The seconds. */
+                       bp = conv_num(bp, &tm->tm_sec, 0, 61);
                        LEGAL_ALT(ALT_O);
-                       if (!(conv_num(&bp, &tm->tm_sec, 0, 61)))
-                               return (0);
-                       break;
+                       continue;
 
                case 'U':       /* The week of year, beginning on sunday. */
                case 'W':       /* The week of year, beginning on monday. */
-                       LEGAL_ALT(ALT_O);
                        /*
                         * XXX This is bogus, as we can not assume any valid
                         * information present in the tm structure at this
                         * point to calculate a real value, so just check the
                         * range for now.
                         */
-                        if (!(conv_num(&bp, &i, 0, 53)))
-                               return (0);
-                        break;
+                        bp = conv_num(bp, &i, 0, 53);
+                        LEGAL_ALT(ALT_O);
+                        continue;
 
                case 'w':       /* The day of week, beginning on sunday. */
+                       bp = conv_num(bp, &tm->tm_wday, 0, 6);
                        LEGAL_ALT(ALT_O);
-                       if (!(conv_num(&bp, &tm->tm_wday, 0, 6)))
-                               return (0);
-                       break;
+                       continue;
 
-               case 'Y':       /* The year. */
-                       LEGAL_ALT(ALT_E);
-                       if (!(conv_num(&bp, &i, 0, 9999)))
-                               return (0);
+               case 'u':       /* The day of week, monday = 1. */
+                       bp = conv_num(bp, &i, 1, 7);
+                       tm->tm_wday = i % 7;
+                       LEGAL_ALT(ALT_O);
+                       continue;
+
+               case 'g':       /* The year corresponding to the ISO week
+                                * number but without the century.
+                                */
+                       bp = conv_num(bp, &i, 0, 99);
+                       continue;
 
+               case 'G':       /* The year corresponding to the ISO week
+                                * number with century.
+                                */
+                       do
+                               bp++;
+                       while (isdigit(*bp));
+                       continue;
+
+               case 'V':       /* The ISO 8601:1988 week number as decimal */
+                       bp = conv_num(bp, &i, 0, 53);
+                       continue;
+
+               case 'Y':       /* The year. */
+                       i = TM_YEAR_BASE;       /* just for data sanity... */
+                       bp = conv_num(bp, &i, 0, 9999);
                        tm->tm_year = i - TM_YEAR_BASE;
-                       break;
+                       LEGAL_ALT(ALT_E);
+                       continue;
 
                case 'y':       /* The year within 100 years of the epoch. */
-                       LEGAL_ALT(ALT_E | ALT_O);
-                       if (!(conv_num(&bp, &i, 0, 99)))
-                               return (0);
+                       /* LEGAL_ALT(ALT_E | ALT_O); */
+                       bp = conv_num(bp, &i, 0, 99);
+
+                       if (split_year)
+                               /* preserve century */
+                               i += (tm->tm_year / 100) * 100;
+                       else {
+                               split_year = 1;
+                               if (i <= 68)
+                                       i = i + 2000 - TM_YEAR_BASE;
+                               else
+                                       i = i + 1900 - TM_YEAR_BASE;
+                       }
+                       tm->tm_year = i;
+                       continue;
 
-                       if (split_year) {
-                               tm->tm_year = ((tm->tm_year / 100) * 100) + i;
+               case 'Z':
+                       tzset();
+                       if (strncmp((const char *)bp, gmt, 3) == 0) {
+                               tm->tm_isdst = 0;
+#ifdef TM_GMTOFF
+                               tm->TM_GMTOFF = 0;
+#endif
+#ifdef TM_ZONE
+                               tm->TM_ZONE = gmt;
+#endif
+                               bp += 3;
+                       } else {
+                               const unsigned char *ep;
+
+                               ep = find_string(bp, &i,
+                                                (const char * const *)tzname,
+                                                 NULL, 2);
+                               if (ep != NULL) {
+                                       tm->tm_isdst = i;
+#ifdef TM_GMTOFF
+                                       tm->TM_GMTOFF = -(timezone);
+#endif
+#ifdef TM_ZONE
+                                       tm->TM_ZONE = tzname[i];
+#endif
+                               }
+                               bp = ep;
+                       }
+                       continue;
+
+               case 'z':
+                       /*
+                        * We recognize all ISO 8601 formats:
+                        * Z    = Zulu time/UTC
+                        * [+-]hhmm
+                        * [+-]hh:mm
+                        * [+-]hh
+                        */
+                       while (isspace(*bp))
+                               bp++;
+
+                       switch (*bp++) {
+                       case 'Z':
+                               tm->tm_isdst = 0;
+#ifdef TM_GMTOFF
+                               tm->TM_GMTOFF = 0;
+#endif
+#ifdef TM_ZONE
+                               tm->TM_ZONE = utc;
+#endif
+                               continue;
+                       case '+':
+                               neg = 0;
                                break;
+                       case '-':
+                               neg = 1;
+                               break;
+                       default:
+                               return NULL;
                        }
-                       split_year = 1;
-                       if (i <= 68)
-                               tm->tm_year = i + 2000 - TM_YEAR_BASE;
-                       else
-                               tm->tm_year = i + 1900 - TM_YEAR_BASE;
-                       break;
+                       offs = 0;
+                       for (i = 0; i < 4; ) {
+                               if (isdigit(*bp)) {
+                                       offs = offs * 10 + (*bp++ - '0');
+                                       i++;
+                                       continue;
+                               }
+                               if (i == 2 && *bp == ':') {
+                                       bp++;
+                                       continue;
+                               }
+                               break;
+                       }
+                       switch (i) {
+                       case 2:
+                               offs *= 100;
+                               break;
+                       case 4:
+                               i = offs % 100;
+                               if (i >= 60)
+                                       return NULL;
+                               /* Convert minutes into decimal */
+                               offs = (offs / 100) * 100 + (i * 50) / 30;
+                               break;
+                       default:
+                               return NULL;
+                       }
+                       if (neg)
+                               offs = -offs;
+                       tm->tm_isdst = 0;       /* XXX */
+#ifdef TM_GMTOFF
+                       tm->TM_GMTOFF = offs;
+#endif
+#ifdef TM_ZONE
+                       tm->TM_ZONE = NULL;     /* XXX */
+#endif
+                       continue;
 
                /*
                 * Miscellaneous conversions.
                 */
                case 'n':       /* Any kind of white-space. */
                case 't':
-                       LEGAL_ALT(0);
                        while (isspace(*bp))
                                bp++;
-                       break;
+                       LEGAL_ALT(0);
+                       continue;
 
 
                default:        /* Unknown/unsupported conversion. */
-                       return (0);
+                       return NULL;
                }
-
-
        }
 
-       /* LINTED functional specification */
-       return ((char *)bp);
+       return __DECONST(char *, bp);
 }
 
 
-static int
-conv_num(const unsigned char **buf, int *dest, int llim, int ulim)
+static const u_char *
+conv_num(const unsigned char *buf, int *dest, uint llim, uint ulim)
 {
-       int result = 0;
+       uint result = 0;
+       unsigned char ch;
 
        /* The limit also determines the number of valid digits. */
-       int rulim = ulim;
+       uint rulim = ulim;
 
-       if (**buf < '0' || **buf > '9')
-               return (0);
+       ch = *buf;
+       if (ch < '0' || ch > '9')
+               return NULL;
 
        do {
                result *= 10;
-               result += *(*buf)++ - '0';
+               result += ch - '0';
                rulim /= 10;
-       } while ((result * 10 <= ulim) && rulim && **buf >= '0' && **buf <= '9');
+               ch = *++buf;
+       } while ((result * 10 <= ulim) && rulim && ch >= '0' && ch <= '9');
 
        if (result < llim || result > ulim)
-               return (0);
+               return NULL;
 
        *dest = result;
-       return (1);
+       return buf;
+}
+
+static const u_char *
+find_string(const u_char *bp, int *tgt, const char * const *n1,
+               const char * const *n2, int c)
+{
+       int i;
+       unsigned int len;
+
+       /* check full name - then abbreviated ones */
+       for (; n1 != NULL; n1 = n2, n2 = NULL) {
+               for (i = 0; i < c; i++, n1++) {
+                       len = strlen(*n1);
+                       if (strncasecmp(*n1, (const char *)bp, len) == 0) {
+                               *tgt = i;
+                               return bp + len;
+                       }
+               }
+       }
+
+       /* Nothing matched */
+       return NULL;
 }