Revised C Code for draft-newman-datetime-00.txt

Markus G. Kuhn kuhn at cs.purdue.edu
Thu Jan 16 03:23:40 UTC 1997


Here comes the revised version of my example ISO 8601 output routine.
It includes almost all suggestions that I have received plus some more
and I have added test calls that should print on all POSIX systems the
following:

Show time in various forms:

Fri Feb 13 18:59:59 2009

2009-02-13 18:59:59-05
2009-02-13 18:59:59.012-05
2009-02-13 23:59:59Z
2009-02-13 23:59:59.012Z
2009-02-13 23:59:60.012Z
2009-02-13 18:59:60-05
2009-02-13 23:59:59+00
2009-02-14 05:14:59+05:15
2009-02-14 05:14:59.012345678+05:15

Show a DST switch (Central Europe, October 1996):

1996-10-26 23:30:00Z   =   1996-10-27 01:30:00+02
1996-10-27 00:00:00Z   =   1996-10-27 02:00:00+02
1996-10-27 00:30:00Z   =   1996-10-27 02:30:00+02
1996-10-27 01:00:00Z   =   1996-10-27 02:00:00+01
1996-10-27 01:30:00Z   =   1996-10-27 02:30:00+01
1996-10-27 02:00:00Z   =   1996-10-27 03:00:00+01
1996-10-27 02:30:00Z   =   1996-10-27 03:30:00+01

Thanks for the many valuable suggestions.  Since I know now finally, why localtime() uses call-by-reference, I feel very comfortable with breaking
the consistency with this legacy interface.

Do you see any other portability or elegance problems?

Markus

-- 
Markus G. Kuhn, Computer Science grad student, Purdue
University, Indiana, USA -- email: kuhn at cs.purdue.edu

-------------- next part --------------
/*
 * Example implementation of the RFC xxxx profile of ISO 8601
 * ASCII standard date and time representation in ANSI/ISO C.
 *
 * This implementation is only an informal appendix of RFC xxxx and not
 * part of the standard. This is an early draft version, do not use
 * this in products yet.
 *
 * Please send comments for improvement to
 *
 *   Markus Kuhn <kuhn at cs.purdue.edu>
 *
 * Thanks for suggestions to:
 *
 *   Chris Newman <Chris.Newman at innosoft.com>
 *   Ken Pizzini <ken at spry.com>
 *   Antoine Leca <Antoine.Leca at renault.fr>
 *
 * $Id: demo8601.c,v 1.1 1997-01-15 22:02:37-05 kuhn Exp $
 */

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

/*
 * Create an ISO 8601 time string conforming to the RFC xxxx profile
 *
 * Input:
 *
 *   t       the time that will be written as returned by time()
 *   utc     if true, then UTC instead of local time will be shown
 *   nsec    nanoseconds since start of current second or 0 if unknown
 *   prec    number of digits (0..9) for decimal fractions of the
 *           second (set to 0 if nsec is unknown)
 *
 * Output:
 *
 *   s       time string with strlen(buf) < 36
 *
 * When time_t does not allow to encode leap seconds (like in POSIX),
 * you can use the value of second 59 in t and add 1000000000 to nsec
 * in order to represent a leap second.  If input or libary errors
 * are detected, NULL will be returned, otherwise a pointer to the
 * output string.  If buf is NULL, the time string will be written to
 * a local buffer. This function is not multithreading safe.
 *
 * Markus Kuhn <kuhn at cs.purdue.edu>, 1997
 */

char *
create_timestring(char *s, time_t t, int utc, long nsec, unsigned prec)
{
  char *buf, buffer[36];
  struct tm ltm, utm, *tmp;
  long offset;

  /* safety checks */
  s = buf = (s == NULL) ? buffer : s;
  *buf = '\0';
  if (nsec < 0 || nsec > 1999999999L)
    return NULL;
  if (prec > 9)
    prec = 9;

  /* convert time */
  if ((tmp = gmtime(&t)) == NULL)
    return NULL;
  utm = ltm = *tmp;
  if (!utc)
    ltm = *localtime(&t);

  /* leap second handling */
  if (nsec > 1000000000L) {
    ltm.tm_sec++;
    utm.tm_sec++;
    nsec -= 1000000000L;
  }

  /* basic format */
  buf += strftime(buf, 20, "%Y-%m-%d %H:%M:%S", utc ? &utm : &ltm);
  if (prec > 0) {
    sprintf(buf, ".%09ld", nsec);
    buf += prec + 1;
  }

  /* time zone offset */
  if (utc) {
    *(buf++) = 'Z';
    *buf = '\0';
  } else {
    /* we assume without check that the offset is less than 24 hours */
    offset = ltm.tm_yday - utm.tm_yday;
    if (offset > 1)
      offset = -24;
    else if (offset < -1)
      offset = 24;
    else
      offset *= 24;
    offset += ltm.tm_hour - utm.tm_hour;
    offset *= 60;
    offset += ltm.tm_min - utm.tm_min;
    sprintf(buf, "%+03ld", offset / 60); /* hour offset */
    buf += 3;
    if (offset < 0)
      offset = -offset;
    offset %= 60;
    if (offset != 0)
      sprintf(buf, ":%02ld", offset);   /* minute offset */
  }
  
  return s;
}



/* Some test code */

int main()
{
  char buf[50];
  time_t now;
  time_t last_dst_hour;
  int i;

  puts("Show time in various forms:\n");
  now = 1234569599L; /* on POSIX this is 2009-02-13 23:59:59Z */
  putenv("TZ=EST5");
  puts(ctime(&now));
  create_timestring(buf, now, 0, 0, 0);          puts(buf);
  create_timestring(buf, now, 0, 12345678, 3);   puts(buf);
  create_timestring(buf, now, 1, 0, 0);          puts(buf);
  create_timestring(buf, now, 1, 12345678, 3);   puts(buf);
  create_timestring(buf, now, 1, 1012345678, 3); puts(buf); /* leap */
  create_timestring(buf, now, 0, 1012345678, 0); puts(buf); /* leap */
  putenv("TZ=UTC0");
  create_timestring(buf, now, 0, 0, 0);          puts(buf);
  putenv("TZ=TST-5:15");
  create_timestring(buf, now, 0, 0, 0);          puts(buf);
  create_timestring(buf, now, 0, 12345678, 9);   puts(buf);

  puts("\nShow a DST switch (Central Europe, October 1996):\n");
  last_dst_hour = (((26L * 365) + 6 + 300) * 24) * 3600;
  putenv("TZ=CET-1CEST,M3.5.0/2,M10.5.0/3");
  for (i = -1; i <= 5; i++) {
    printf("%s   =   ",
	   create_timestring(NULL, last_dst_hour + i * 1800, 1, 0, 0));
    puts(create_timestring(NULL, last_dst_hour + i * 1800, 0, 0, 0));
  }
  return 0;
}


More information about the tz mailing list