I want to print the current time in ISO 8601 format as close as possible to Python's isoformat()
function, which produces output like 2024-01-16T09:32:46.090+01:00
.
As a starting point, I have this code that uses gettimeofday()
, localtime()
, and strftime()
:
#include <stdio.h> //printf
#include <sys/time.h>
int main(){
struct timeval tv;
struct timezone tz;
gettimeofday(&tv, &tz);
char buf[30]; // 29 chars + '\0'
strftime(buf, 30, "%Y-%m-%dT%H:%M:%S%z", localtime(&tv.tv_sec));
printf("using strftime: %s\n", buf);
}
That generates 2024-01-16T09:32:46+01:00
(missing milliseconds).
I didn't not find any format specifier for strftime()
to print milliseconds.
strftime()
takes a struct tm
as input, and struct tm
has no notion of milliseconds/microseconds so strftime()
can't get milliseconds out of it.
In order to the the milliseconds you will need to resort to the struct timeval
returned by gettimeofday()
which has a member called tv_usec
with the microseconds that you can easily convert to milliseconds by dividing by 1000. Or use clock_gettime()
which provides nanoseconds. See an example with clock_gettime
at the bottom.
#include <stdio.h> //printf
#include <sys/time.h>
int main(){
struct timeval tv;
struct timezone tz;
gettimeofday(&tv, &tz);
char buf[sizeof "9999-12-31T23:59:59.999+0000"]; // 28 chars + '\0'
size_t bufsize = sizeof buf;
int off = 0;
struct tm *local = localtime(&tv.tv_sec);
off = strftime(buf, bufsize, "%FT%T", local); // same as "%Y-%m-%dT%H:%M:%S"
off += snprintf(buf+off, bufsize-off, ".%03d", tv.tv_usec/1000);
off += strftime(buf+off, bufsize-off, "%z", local);
printf("using strftime: %s\n", buf);
}
When executed it will produce 2024-01-16T14:08:15.079+0100
which matches ISO 8601 with milliseconds and UTC offset. Unfortunately the %z
produces a UTC offset in the form +0100
(no colon) instead of +01:00
gcc test.c -g -Wall -std=c11 && ./a.out
using strftime: 2024-01-16T14:08:15.079+0100
If you really really need the UTC offset with colon, then you can use struct timezone
from gettimeofday()
. You need to replace the off += strftime(buf+off, 30-off, "%z", local);
to more convoluted:
if (tz.tz_minuteswest >= 0) {
off += snprintf(buf+off, 30-off, "+%02d:%02d", tz.tz_minuteswest/60, tz.tz_minuteswest%60);
} else {
off += snprintf(buf+off, 30-off, "-%02d:%02d", -tz.tz_minuteswest/60, -tz.tz_minuteswest%60);
}
Note that gettimeofday()
is considered deprecated in favor of clock_gettime()
, so the best way to do all this would be:
#include <stdio.h> // printf
#include <stdlib.h> // labs
#include <sys/time.h>
int main(){
struct timespec tp;
clock_gettime(CLOCK_REALTIME, &tp);
char buf[sizeof "9999-12-31T23:59:59.999+00:00"]; // 29 chars + '\0'
size_t bufsize = sizeof buf;
int off = 0;
struct tm *local = localtime(&tp.tv_sec);
off = strftime(buf, bufsize, "%FT%T", local); // same as "%Y-%m-%dT%H:%M:%S"
off += snprintf(buf+off, bufsize-off, ".%03ld", tp.tv_nsec/1000000);
off += snprintf(buf+off, bufsize-off, "%c%02ld:%02ld", local->tm_gmtoff >= 0 ? '+' : '-', labs(local->tm_gmtoff)/3600, labs(local->tm_gmtoff)%3600/60);
printf("using strftime: %s\n", buf);
}
The only changes are to use clock_gettime()
instead of gettimeofday()
and use the time zone information that localtime()
provides in the struct tm
as tm_gmtoff
.
gcc test.c -g -Wall -std=c11
TZ="Europe/Madrid" ./a.out;
using strftime: 2024-01-16T16:18:12.161+01:00
TZ="America/Anchorage" ./a.out;
using strftime: 2024-01-16T06:18:12.173-09:00
TZ="UTC" ./a.out
using strftime: 2024-01-16T15:18:12.184+00:00
TZ="Asia/Kolkata" ./a.out
using strftime: 2024-01-16T23:38:31.766+05:30