On 2024-02-08 09:00, Bruno Haible wrote:
     char *str_from_time (char *resultbuf, size_t *lengthp,
                          bool i18n, const char *format,
                          time_t t,            bool local_time, int ns);
     char *str_from_tm   (char *resultbuf, size_t *lengthp,
                          bool i18n, const char *format,
                          struct tm const *tp, bool local_time, int ns);

I suggest some simplifications and some complications:

1. There's no need for the from_tm flavor. That just complicates things, because callers use localtime or localtime_r or gmtime or gmtime_r or whatever, and deal with those other functions failing. Many programs don't do this correctly, so let's have str_from_done do it and be done with it.

2. Instead of i18n being a boolean, why not make it a locale_t? That's more general.

3. Likewise for localtime; why not make it a timezone_t? Sure it's overkill, but having a boolean at all is overkill in some sense.

4. We can also establish simpler APIs for common cases, which call the more-general API.

  Migration from ctime:
    s = ctime (tp);
  =>
    char buf[64];
    size_t buf_size = sizeof (buf);
    s = str_from_time (buf, &buf_size, false, "%a %b %e %H:%M:%S %Y\n", *tp, 
true, 0);
    if (s == NULL) /* handle error case */;

The migration is more complicated than that, since one must have 'if (s != buf) free (s);' afterwards.

Better to have struct timezone than separate time_t and ns args.

In practice I don't think these buffers will overflow, as time strings are short. So there's little need for malloc.

So how about the following API instead?

   size_t str_from_time (char *restrict s, idx_t maxsize,
                         char const *format, struct timespec t,
                         locale_t locale, timezone_t tz);

If localtime_rz fails, this substitutes the decimal representation of T. If the string doesn't fit this returns 0 and (if MAXSIZE is nonzero) stores some truncation of the string into S.

We also arrange for identifiers C_locale and local_tz to stand for
the C locale and the local timezone, and CTIME_BUFSIZE and ctime_fmt to be appropriate buffer sizes and formats for ctime-equivalent calls.

Migration from ctime:

  c = ctime (tp);
 =>
  char c[CTIME_BUFSIZE];
  str_from_time (c, sizeof c, ctime_fmt,
                 (struct timespec) { .tv_sec = *tp },
                 C_locale, local_tz);

and we can package this up into a function like this:

  char c[CTIME_BUFSIZE];
  safer_ctime (c, *tp);

if people prefer simplicity.



Reply via email to