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);
cout
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