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@cs.purdue.edu /* * 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@cs.purdue.edu> * * Thanks for suggestions to: * * Chris Newman <Chris.Newman@innosoft.com> * Ken Pizzini <ken@spry.com> * Antoine Leca <Antoine.Leca@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@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 : <m); 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; }