Re: [PATCH v5] ctime.3: Document how to detect invalid or ambiguous times

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Paul,

On Sat, Aug 24, 2024 at 10:46:20AM GMT, Alejandro Colomar wrote:
> This example documents the corner cases of mktime(3), such as what
> happens during DST transitions, and other jumps in the calendar.
> 
> Link: <https://www.redhat.com/en/blog/brief-history-mktime>
> Reported-by: DJ Delorie <dj@xxxxxxxxxx>
> Cc: Carlos O'Donell <carlos@xxxxxxxxxx>
> Cc: Paul Eggert <eggert@xxxxxxxxxxx>
> Signed-off-by: Alejandro Colomar <alx@xxxxxxxxxx>
> ---

Should I wait for a review, or are you ok with this patch?

> 
> Hi DJ, Paul,
> 
> This is just a resend of v4.  Forget v4.
> 
> I forgot to send it in reply to the previous versions, and forgot to
> paste the rendered output.
> 
> Here's how this looks:
> 
> CAVEATS
>      ...
> 
>    mktime()
>      ...
> 
>      Passing an invalid time to mktime()  or  an  invalid  tm‐>tm_isdst
>      value yields an unspecified result.  Also, passing the value -1 in
>      tm‐>tm_isdst  will  result  in  an  ambiguous time during some DST
>      transitions, which will also yield an unspecified result.  See the
>      example program in EXAMPLES.

I think the wording is more or less what you suggested, leaving it as
unspecified in those corner cases.

Only in the example I show more details, for those who need to know what
to do in those cases.

> 
> EXAMPLES
>      The program below defines a wrapper that allows detecting  invalid
>      and ambiguous times, with EINVAL and ENOTUNIQ, respectively.
> 
>      The following shell session shows sample runs of the program:
> 
>          $ TZ=UTC ./a.out 1969 12 31 23 59 59 0;
>          -1
>          $
>          $ export TZ=Europe/Madrid;
>          $
>          $ ./a.out 2147483647 2147483647 00 00 00 00 ‐1;
>          a.out: mktime: Value too large for defined data type
>          $
>          $ ./a.out 2024 08 23 00 17 53 -1;
>          1724365073
>          $ ./a.out 2024 08 23 00 17 53 0;
>          a.out: mktime: Invalid argument
>          1724368673
>          $ ./a.out 2024 08 23 00 17 53 1;
>          1724365073
>          $
>          $ ./a.out 2024 02 23 00 17 53 -1;
>          1708643873
>          $ ./a.out 2024 02 23 00 17 53 0;
>          1708643873
>          $ ./a.out 2024 02 23 00 17 53 1;
>          a.out: mktime: Invalid argument
>          1708640273
>          $
>          $ ./a.out 2023 03 26 02 17 53 -1;
>          a.out: mktime: Invalid argument
>          1679793473
>          $
>          $ ./a.out 2023 10 29 02 17 53 -1;
>          a.out: mktime: Name not unique on network
>          1698542273
>          $ ./a.out 2023 10 29 02 17 53 0;
>          1698542273
>          $ ./a.out 2023 10 29 02 17 53 1;
>          1698538673
>          $
>          $ ./a.out 2023 02 29 12 00 00 -1;
>          a.out: mktime: Invalid argument
>          1677668400
> 
>    Program source: mktime.c
> 
>      #include <err.h>
>      #include <errno.h>
>      #include <stdint.h>
>      #include <stdio.h>
>      #include <stdlib.h>
>      #include <string.h>
>      #include <time.h>
> 
>      #define is_signed(T)  ((T) -1 < 1)
> 
>      time_t my_mktime(struct tm *tp);
> 
>      int
>      main(int argc, char *argv[])
>      {
>          char       **p;
>          time_t     t;
>          struct tm  tm;
> 
>          if (argc != 8) {
>              fprintf(stderr, "Usage: %s yyyy mm dd HH MM SS isdst\n", argv[0]);
>              exit(EXIT_FAILURE);
>          }
> 
>          p = &argv[1];
>          tm.tm_year  = atoi(*p++) - 1900;
>          tm.tm_mon   = atoi(*p++) - 1;
>          tm.tm_mday  = atoi(*p++);
>          tm.tm_hour  = atoi(*p++);
>          tm.tm_min   = atoi(*p++);
>          tm.tm_sec   = atoi(*p++);
>          tm.tm_isdst = atoi(*p++);
> 
>          errno = 0;
>          tm.tm_wday = -1;
>          t = my_mktime(&tm);
>          if (tm.tm_wday == -1)
>              err(EXIT_FAILURE, "mktime");
>          if (errno == EINVAL || errno == ENOTUNIQ)
>              warn("mktime");
> 
>          if (is_signed(time_t))
>              printf("%jd\n", (intmax_t) t);
>          else
>              printf("%ju\n", (uintmax_t) t);
> 
>          exit(EXIT_SUCCESS);
>      }
> 
>      time_t
>      my_mktime(struct tm *tp)
>      {
>          int            e, isdst;
>          time_t         t;
>          struct tm      tm;
>          unsigned char  wday[sizeof(tp->tm_wday)];
> 
>          e = errno;
> 
>          tm = *tp;
>          isdst = tp->tm_isdst;
> 
>          memcpy(wday, &tp->tm_wday, sizeof(wday));
>          tp->tm_wday = -1;
>          t = mktime(tp);
>          if (tp->tm_wday == -1) {
>              memcpy(&tp->tm_wday, wday, sizeof(wday));
>              return -1;
>          }
> 
>          if (isdst == -1)
>              tm.tm_isdst = tp->tm_isdst;
> 
>          if (   tm.tm_sec   != tp->tm_sec
>              || tm.tm_min   != tp->tm_min
>              || tm.tm_hour  != tp->tm_hour
>              || tm.tm_mday  != tp->tm_mday
>              || tm.tm_mon   != tp->tm_mon
>              || tm.tm_year  != tp->tm_year
>              || tm.tm_isdst != tp->tm_isdst)
>          {
>              errno = EINVAL;
>              return t;
>          }
> 
>          if (isdst != -1)
>              goto out;
> 
>          tm = *tp;
>          tm.tm_isdst = !tm.tm_isdst;
> 
>          tm.tm_wday = -1;
>          mktime(&tm);
>          if (tm.tm_wday == -1)
>              goto out;
> 
>          if (tm.tm_isdst != tp->tm_isdst) {
>              errno = ENOTUNIQ;
>              return t;
>          }
>      out:
>          errno = e;
>          return t;
>      }
> 
> Cheers,
> Alex
> 
> 
> Range-diff against v3:
> 1:  e9e31a505 < -:  --------- ctime.3: EXAMPLES: Add example program
> -:  --------- > 1:  b7ed55965 ctime.3: Document how to detect invalid or ambiguous times
> 
>  man/man3/ctime.3 | 93 +++++++++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 92 insertions(+), 1 deletion(-)
> 
> diff --git a/man/man3/ctime.3 b/man/man3/ctime.3
> index 0ad2b530f..934da149e 100644
> --- a/man/man3/ctime.3
> +++ b/man/man3/ctime.3
> @@ -427,7 +427,30 @@ .SS mktime()
>  .I tm->tm_wday
>  field.
>  See the example program in EXAMPLES.
> +.P
> +Passing an invalid time to
> +.BR mktime ()
> +or an invalid
> +.I tm->tm_isdst
> +value
> +yields an unspecified result.
> +Also,
> +passing the value
> +.I \-1
> +in
> +.I tm->tm_isdst
> +will result in an ambiguous time during some DST transitions,
> +which will also yield an unspecified result.
> +See the example program in EXAMPLES.
>  .SH EXAMPLES
> +The program below defines a wrapper that
> +allows detecting invalid and ambiguous times,
> +with
> +.B EINVAL
> +and
> +.BR ENOTUNIQ ,
> +respectively.
> +.P
>  The following shell session shows sample runs of the program:
>  .P
>  .in +4n
> @@ -443,6 +466,7 @@ .SH EXAMPLES
>  .RB $\~ "./a.out 2024 08 23 00 17 53 \-1" ;
>  1724365073
>  .RB $\~ "./a.out 2024 08 23 00 17 53 0" ;
> +a.out: mktime: Invalid argument
>  1724368673
>  .RB $\~ "./a.out 2024 08 23 00 17 53 1" ;
>  1724365073
> @@ -452,12 +476,15 @@ .SH EXAMPLES
>  .RB $\~ "./a.out 2024 02 23 00 17 53 0" ;
>  1708643873
>  .RB $\~ "./a.out 2024 02 23 00 17 53 1" ;
> +a.out: mktime: Invalid argument

I'll change these error messages to say "my_mktime" instead of "mktime",
since it's not a standard error.

Have a lovely day!
Alex

>  1708640273
>  $
>  .RB $\~ "./a.out 2023 03 26 02 17 53 \-1" ;
> +a.out: mktime: Invalid argument
>  1679793473
>  $
>  .RB $\~ "./a.out 2023 10 29 02 17 53 \-1" ;
> +a.out: mktime: Name not unique on network
>  1698542273
>  .RB $\~ "./a.out 2023 10 29 02 17 53 0" ;
>  1698542273
> @@ -465,6 +492,7 @@ .SH EXAMPLES
>  1698538673
>  $
>  .RB $\~ "./a.out 2023 02 29 12 00 00 \-1" ;
> +a.out: mktime: Invalid argument
>  1677668400
>  .EE
>  .SS Program source: mktime.c
> @@ -472,13 +500,17 @@ .SS Program source: mktime.c
>  .\" SRC BEGIN (mktime.c)
>  .EX
>  #include <err.h>
> +#include <errno.h>
>  #include <stdint.h>
>  #include <stdio.h>
>  #include <stdlib.h>
> +#include <string.h>
>  #include <time.h>
>  \&
>  #define is_signed(T)  ((T) \-1 < 1)
>  \&
> +time_t my_mktime(struct tm *tp);
> +\&
>  int
>  main(int argc, char *argv[])
>  {
> @@ -500,10 +532,13 @@ .SS Program source: mktime.c
>      tm.tm_sec   = atoi(*p++);
>      tm.tm_isdst = atoi(*p++);
>  \&
> +    errno = 0;
>      tm.tm_wday = \-1;
> -    t = mktime(&tm);
> +    t = my_mktime(&tm);
>      if (tm.tm_wday == \-1)
>          err(EXIT_FAILURE, "mktime");
> +    if (errno == EINVAL || errno == ENOTUNIQ)
> +        warn("mktime");
>  \&
>      if (is_signed(time_t))
>          printf("%jd\[rs]n", (intmax_t) t);
> @@ -512,6 +547,62 @@ .SS Program source: mktime.c
>  \&
>      exit(EXIT_SUCCESS);
>  }
> +\&
> +time_t
> +my_mktime(struct tm *tp)
> +{
> +    int            e, isdst;
> +    time_t         t;
> +    struct tm      tm;
> +    unsigned char  wday[sizeof(tp\->tm_wday)];
> +\&
> +    e = errno;
> +\&
> +    tm = *tp;
> +    isdst = tp\->tm_isdst;
> +\&
> +    memcpy(wday, &tp\->tm_wday, sizeof(wday));
> +    tp\->tm_wday = \-1;
> +    t = mktime(tp);
> +    if (tp\->tm_wday == \-1) {
> +        memcpy(&tp\->tm_wday, wday, sizeof(wday));
> +        return \-1;
> +    }
> +\&
> +    if (isdst == \-1)
> +        tm.tm_isdst = tp\->tm_isdst;
> +\&
> +    if (   tm.tm_sec   != tp\->tm_sec
> +        || tm.tm_min   != tp\->tm_min
> +        || tm.tm_hour  != tp\->tm_hour
> +        || tm.tm_mday  != tp\->tm_mday
> +        || tm.tm_mon   != tp\->tm_mon
> +        || tm.tm_year  != tp\->tm_year
> +        || tm.tm_isdst != tp\->tm_isdst)
> +    {
> +        errno = EINVAL;
> +        return t;
> +    }
> +\&
> +    if (isdst != \-1)
> +        goto out;
> +\&
> +    tm = *tp;
> +    tm.tm_isdst = !tm.tm_isdst;
> +\&
> +    tm.tm_wday = \-1;
> +    mktime(&tm);
> +    if (tm.tm_wday == \-1)
> +        goto out;
> +\&
> +    if (tm.tm_isdst != tp\->tm_isdst) {
> +        errno = ENOTUNIQ;
> +        return t;
> +    }
> +out:
> +    errno = e;
> +    return t;
> +}
>  .EE
>  .\" SRC END
>  .SH SEE ALSO
> 
> base-commit: 6a7f1461b0e5474d50ef1920558dec103c0c058f
> -- 
> 2.45.2
> 



-- 
<https://www.alejandro-colomar.es/>

Attachment: signature.asc
Description: PGP signature


[Index of Archives]     [Kernel Documentation]     [Netdev]     [Linux Ethernet Bridging]     [Linux Wireless]     [Kernel Newbies]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux