On Sun, Feb 24, 2019 at 01:11:39PM +0100, Hiltjo Posthuma wrote:
> Hi,
> 
> I noticed some things in the strptime(3) implementing when parsing timezone
> strings using the %z format string.
> 
> 1. I noticed the tm_gmtoff value is not correctly set in some cases. It should
> set tm_gmtoff to the offset from UTC in seconds (as described in mktime(3)).
> 
> 2. The military/nautical UTC offsets are also reversed. This was also actually
> a bug in RFC822:
> 
> RFC5322 (referenced in strptime(3) man page):
> https://tools.ietf.org/html/rfc5322
> Section 4.3, page 34 says:
> "
>    The 1 character military time zones were defined in a non-standard
>    way in [RFC0822] and are therefore unpredictable in their meaning.
>    The original definitions of the military zones "A" through "I" are
>    equivalent to "+0100" through "+0900", respectively; "K", "L", and
>    "M" are equivalent to "+1000", "+1100", and "+1200", respectively;
>    "N" through "Y" are equivalent to "-0100" through "-1200".
>    respectively; and "Z" is equivalent to "+0000".  However, because of
>    the error in [RFC0822], they SHOULD all be considered equivalent to
>    "-0000" unless there is out-of-band information confirming their
>    meaning.
> "
> 
> While comparing the other BSD and Linux code-bases I noticed NetBSD has fixed
> this on 2017-08-24. It is not in NetBSD 8 stable yet, but in NetBSD-current:
> http://cvsweb.netbsd.org/bsdweb.cgi/src/lib/libc/time/strptime.c
> 
> 3. The military zone J is also ambiguous. Some standards define it as unused,
> while others define it as "local observed time". NetBSD handles it as local
> observed time, but OpenBSD returns NULL in strptime(3). I left this as is.
> 
> 4. While at it I also fixed some trailing whitespaces.
> 
> Thanks for not falling asleep and reading the long text :)
> 
> Patch and test program below:
> 
> diff --git lib/libc/time/strptime.c lib/libc/time/strptime.c
> index eaf182dc773..86a0d5353ee 100644
> --- lib/libc/time/strptime.c
> +++ lib/libc/time/strptime.c
> @@ -116,7 +116,7 @@ _strptime(const char *buf, const char *fmt, struct tm 
> *tm, int initialize)
>                       fmt++;
>                       continue;
>               }
> -                             
> +
>               if ((c = *fmt++) != '%')
>                       goto literal;
>  
> @@ -142,7 +142,7 @@ literal:
>                       _LEGAL_ALT(0);
>                       alt_format |= _ALT_O;
>                       goto again;
> -                     
> +
>               /*
>                * "Complex" conversion rules, implemented through recursion.
>                */
> @@ -380,7 +380,7 @@ literal:
>                                * number but without the century.
>                                */
>                       if (!(_conv_num(&bp, &i, 0, 99)))
> -                             return (NULL);                          
> +                             return (NULL);
>                       continue;
>  
>               case 'G':       /* The year corresponding to the ISO week
> @@ -500,7 +500,7 @@ literal:
>                               ep = _find_string(bp, &i, nast, NULL, 4);
>                               if (ep != NULL) {
>  #ifdef TM_GMTOFF
> -                                     tm->TM_GMTOFF = -5 - i;
> +                                     tm->TM_GMTOFF = (-5 - i) * SECSPERHOUR;
>  #endif
>  #ifdef TM_ZONE
>                                       tm->TM_ZONE = (char *)nast[i];
> @@ -512,7 +512,7 @@ literal:
>                               if (ep != NULL) {
>                                       tm->tm_isdst = 1;
>  #ifdef TM_GMTOFF
> -                                     tm->TM_GMTOFF = -4 - i;
> +                                     tm->TM_GMTOFF = (-4 - i) * SECSPERHOUR;
>  #endif
>  #ifdef TM_ZONE
>                                       tm->TM_ZONE = (char *)nadt[i];
> @@ -527,11 +527,12 @@ literal:
>                                       /* Argh! No 'J'! */
>                                       if (*bp >= 'A' && *bp <= 'I')
>                                               tm->TM_GMTOFF =
> -                                                 ('A' - 1) - (int)*bp;
> +                                                 (int)*bp - ('A' - 1);
>                                       else if (*bp >= 'L' && *bp <= 'M')
> -                                             tm->TM_GMTOFF = 'A' - (int)*bp;
> +                                             tm->TM_GMTOFF = (int)*bp - 'A';
>                                       else if (*bp >= 'N' && *bp <= 'Y')
> -                                             tm->TM_GMTOFF = (int)*bp - 'M';
> +                                             tm->TM_GMTOFF = 'M' - (int)*bp;
> +                                     tm->TM_GMTOFF *= SECSPERHOUR;
>  #endif
>  #ifdef TM_ZONE
>                                       tm->TM_ZONE = NULL; /* XXX */
> @@ -556,14 +557,15 @@ literal:
>                       }
>                       switch (i) {
>                       case 2:
> -                             offs *= 100;
> +                             offs *= SECSPERHOUR;
>                               break;
>                       case 4:
>                               i = offs % 100;
> -                             if (i >= 60)
> +                             offs /= 100;
> +                             if (i >= SECSPERMIN)
>                                       return NULL;
>                               /* Convert minutes into decimal */
> -                             offs = (offs / 100) * 100 + (i * 50) / 30;
> +                             offs = offs * SECSPERHOUR + i * SECSPERMIN;
>                               break;
>                       default:
>                               return NULL;
> @@ -719,7 +721,7 @@ _find_string(const u_char *bp, int *tgt, const char * 
> const *n1,
>       return NULL;
>  }
>  
> -static int              
> +static int
>  leaps_thru_end_of(const int y)
>  {
>       return (y >= 0) ? (y / 4 - y / 100 + y / 400) :
> 
> 
> Quick & dirty test program below:
> 
> 
> #include <sys/types.h>
> 
> #include <err.h>
> #include <stdio.h>
> #include <stdlib.h>
> #include <string.h>
> #include <time.h>
> 
> void
> testtime(const char *s)
> {
>       struct tm tm;
> 
>       memset(&tm, 0, sizeof(tm));
>       if (strptime(s, "%Y-%m-%d %H:%M:%S %z", &tm) == NULL)
>               errx(1, "strptime: s=%s", s);
> 
>       printf("s=%s, gmtoff=%ld, zone=%s\n", s, tm.tm_gmtoff,
>              tm.tm_zone ? tm.tm_zone : "(null)");
> }
> 
> int
> main(void)
> {
>       testtime("2000-01-01 00:00:01 Z");
>       testtime("2000-01-01 00:00:01 UT");
>       testtime("2000-01-01 00:00:01 GMT");
>       testtime("2000-01-01 00:00:01 -04:00");
>       testtime("2000-01-01 00:00:01 -0400");
>       testtime("2000-01-01 00:00:01 +04:00");
>       testtime("2000-01-01 00:00:01 +0400");
>       testtime("2000-01-01 00:00:01 EST");
>       testtime("2000-01-01 00:00:01 EDT");
>       testtime("2000-01-01 00:00:01 A");
>       testtime("2000-01-01 00:00:01 B");
>       testtime("2000-01-01 00:00:01 C");
>       //testtime("2000-01-01 00:00:01 J"); // strptime returns NULL.
>       testtime("2000-01-01 00:00:01 Q");
>       testtime("2000-01-01 00:00:01 R");
> 
>       return 0;
> }
> 

Last weekly bump,

Any further comments or OK's to get this in or reject it?

As discussed previously for point 2 I think it is fine to either return NULL
and not handle military zones like most libc's do or handle them properly like
NetBSD (reversed offsets).

Thanks,

-- 
Kind regards,
Hiltjo

Reply via email to