简体   繁体   中英

Human readable string of 64bits time_t value

I'm trying to print out the date, in a human readable format, for the maximum time_t value possible. The following code seems to work just fine on 32bits machines (initializing m_time with 0x7fffffff), but it outputs null for the theoretically highest value on a 64bits machine. Is that a ctime limitation or am I missing something?

compilation : gcc -Wall -g3 main.c -o time_test
host machine: x86_64.

#include <stdio.h>
#include <time.h>
#include <stddef.h>

int main(int argc, char** argv) {
  time_t m_time = 0x7fffffffffffffff;
  time_t current_time;
  time(&current_time);

  printf("time_t info: sizeof [%ld] bytes or [%ld] bits.\n", sizeof(time_t), sizeof(time_t) *8 );
  printf("m_time val: [%ld]-> %s\n", m_time, ctime(&m_time)); 
  printf("current_time val: [%ld]-> %s\n", current_time, ctime(&current_time));
  return 0;
}

Output:

time_t info: sizeof [8] bytes or [64] bits.
m_time val: [9223372036854775807]-> (null)
current_time val: [1430678274]-> Sun May  3 15:37:54 2015

tks.

BTW, ctime (& ctime(3) ) is documented as giving a string with the year represented by four digits (for a total of 26 bytes). So the maximal time is in the year 9999 (certainly less than maximal time_t on a machine with 64 bits time_t ).

Also (as I commented), pragmatically, if time_t has more than 40 bits (eg 64 bits) you don't care about the maximally representable time. You and everyone reading that forum (and all our grand grand children) would be dead, the computers running your program will all be destroyed, and at that time C won't exist anymore. The Y2038 problem don't practically have any 64 bits equivalent. So just special case when time_t is 32 bits.

It is very unlikely than any C program would matter after the year 3000; software, hardware, standards, and human technical expertise don't last that long...

The POSIX ctime documentation says explicitly :

Attempts to use ctime() or ctime_r() for times before the Epoch or for times beyond the year 9999 produce undefined results . Refer to asctime .

BTW, musl-libc seems to be conformant to the standard: its time/__asctime.c (indirectly called by ctime ) has a nice comment:

if (snprintf(buf, 26, "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n",
    __nl_langinfo(ABDAY_1+tm->tm_wday),
    __nl_langinfo(ABMON_1+tm->tm_mon),
    tm->tm_mday, tm->tm_hour,
    tm->tm_min, tm->tm_sec,
    1900 + tm->tm_year) >= 26)
{
    /* ISO C requires us to use the above format string,
     * even if it will not fit in the buffer. Thus asctime_r
     * is _supposed_ to crash if the fields in tm are too large.
     * We follow this behavior and crash "gracefully" to warn
     * application developers that they may not be so lucky
     * on other implementations (e.g. stack smashing..).
     */
    a_crash();
}

and GNU glibc has in its time/asctime.c file:

/* We limit the size of the year which can be printed.  Using the %d
   format specifier used the addition of 1900 would overflow the
   number and a negative vaue is printed.  For some architectures we
   could in theory use %ld or an evern larger integer format but
   this would mean the output needs more space.  This would not be a
   problem if the 'asctime_r' interface would be defined sanely and
   a buffer size would be passed.  */
if (__glibc_unlikely (tp->tm_year > INT_MAX - 1900))
  {
  eoverflow:
    __set_errno (EOVERFLOW);
    return NULL;
  }

int n = __snprintf (buf, buflen, format,
          (tp->tm_wday < 0 || tp->tm_wday >= 7 ?
           "???" : ab_day_name (tp->tm_wday)),
          (tp->tm_mon < 0 || tp->tm_mon >= 12 ?
           "???" : ab_month_name (tp->tm_mon)),
          tp->tm_mday, tp->tm_hour, tp->tm_min,
            tp->tm_sec, 1900 + tp->tm_year);
if (n < 0)
 return NULL;
if (n >= buflen)
  goto eoverflow;

So I believe that both GNU glibc and musl-libc are better than MacOSX implementation (as cited in zneak's answer ) on that aspect. The standards requires ctime to give 26 bytes. Also, POSIX 2008 is marking ctime as obsolete , new code should use strftime (see also strftime(3) ).

To get to the bottom of this, the best idea is to find an implementation and look at what it does. I downloaded Apple's Libc tarball for OS X 10.1.1 (whose link can be found on this page ), and found that ctime is defined in stdtime/FreeBSD/localtime.c.

The function goes like this:

char *
ctime(timep)
const time_t * const    timep;
{
/*
** Section 4.12.3.2 of X3.159-1989 requires that
**  The ctime function converts the calendar time pointed to by timer
**  to local time in the form of a string.  It is equivalent to
**      asctime(localtime(timer))
*/
#ifdef __LP64__
    /*
     * In 64-bit, the timep value may produce a time value with a year
     * that exceeds 32-bits in size (won't fit in struct tm), so localtime
     * will return NULL.
     */
    struct tm *tm = localtime(timep);

    if (tm == NULL)
        return NULL;
    return asctime(tm);
#else /* !__LP64__ */
    return asctime(localtime(timep));
#endif /* __LP64__ */
}

From a second-hand reference , struct tm appears to be defined in terms of integers, and the tm_year field is an offset from 1900. Assuming conformance to that, even a non-conforming ctime cannot possibly accept a timestamp after year 2 31 +1900-1.

Here is a program that finds (and tests) the largest timestamp ctime will accept with Apple's implementation:

#include <limits.h>
#include <stdio.h>
#include <time.h>

int main(int argc, char** argv) {
    struct tm t = {
        .tm_sec = 59,
        .tm_min = 59,
        .tm_hour = 23,
        .tm_mday = 31,
        .tm_mon = 11,
        .tm_year = INT_MAX,
    };
    time_t max = mktime(&t);

    printf("Maximum time: %li\n", max);
    printf("ctime max: %s\n", ctime(&max));
    max++;
    printf("ctime max+1: %s\n", ctime(&max));
}

Output:

Maximum time: 67768036191694799
ctime max: Wed Dec 31 23:59:59 2147485547
ctime max+1: (null)

This is a 56-bit number, so the maximum year a 64-bit time_t can hold (though struct tm can't) is probably between 547,608,814,485 and 549,756,300,032, or like 36 times the age of the universe. In other words, it's going to be a while.

For what it's worth, Apple's implementation is not conforming. The standard says that the output of ctime has to fit inside 26 bytes, including a newline character and a null character. For a conforming implementation, this means that the year has to be within -999 and 9999.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM