2 May 2010 cdfrey   » (Journeyer)

Time Conversion: ISO timestamps, mktime(), and UTC

    In the C library, there is a very handy function called mktime() which takes a struct tm pointer, pointing to time data in the local timezone, and converts it to a unix time_t.

    There is also a very handy set of functions that goes in the other direction. The function gmtime() takes a time_t and converts it to a struct tm in the UTC timezone. And localtime() does the reverse of mktime(), converting a time_t to a struct tm in the local timezone.

    The one function missing is the reverse of gmtime().

    One way to do it, via standard library function calls, is to adjust the TZ environment variable to "UTC" and then call mktime(), which will then use it as the current timezone. But that gets messy. Not only do you have to save the old TZ setting to restore it afterward, if your program uses threads, messing about with the global TZ variable is hardly safe.

    Another way to do it is to use gmtime() as a reference point, and loop while adjusting the original struct tm until mktime() + gmtime() gives you the result you started with. This is brute force and inelegant, but it manages to do the job without fiddling with the global environment.

    When parsing ISO timestamp strings, this problem is hit pretty quick. An ISO timestamp can contain a time in either the local timezone or UTC, depending on the trailing 'Z' flag. In my research, it does not appear that more specific timezones can be specified in the timestamp format, so at least we only have two states to worry about.

    After much searching, I've ended up with something that I find suitable enough to release. I tackled this problem back in 2007 for the OpenSync project (you can see similar code in the opensync_time.c file), but looking back on it now, the code is not clean enough for me to reuse very easily. And even though there's a vtime2unix converter function, it leaves the burden of timezones to the user.

    I took a look at Boost's date_time library as well, and while huge and comprehensive, and though it breaks the problem of time, dates, and durations into nice manageable chunks, it seemed to reverse the status of the C library: it made UTC conversions easy, and local timezone conversions hard. And it reads timezone information out of CSV files... I don't want to have to maintain my own timezone database when the OS does it for me.

    So, if I don't want to write my own conversion routines, and if I don't want to maintain my own timezone database, I'm stuck with the C library method, and the first two solutions. Might as well make it easy to use.

    The source files for TzWrapper contain the following utilities:

    • iso_to_tm() - simple ISO timestamp converter
    • utc_mktime() - the brute force UTC to time_t converter
    • TzWrapper - C++ wrapper class for setting and restoring TZ

    Using TzWrapper, it's now possible to do things like:

	struct tm pacific_tm = { ... };
	time_t t = TzWrapper("Canada/Pacific").mktime(&pacific_tm);

If you have any further tips to make this code better, or pointers to better libraries that make this code obsolete, please let me know!

Syndicated 2010-05-01 22:37:19 from Chris Frey's Blog - Entries tagged advogato

Latest blog entries     Older blog entries

New Advogato Features

New HTML Parser: The long-awaited libxml2 based HTML parser code is live. It needs further work but already handles most markup better than the original parser.

Keep up with the latest Advogato features by reading the Advogato status blog.

If you're a C programmer with some spare time, take a look at the mod_virgule project page and help us with one of the tasks on the ToDo list!