Silmor . de
Site Links:
Impressum / Publisher

Time Zones with Qt

The standard Qt class for handling time stamps is QDateTime. The QDateTime class delivered by Qt can handle exactly two time zones as reference: the local time and UTC (Universal Coordinated Time). For this it relies upon the operating system to handle those correctly. In 99% of all cases this is more than enough, but things get tricky when you want to do conversions with different time zones or if you want to know more about your own time zone.

Note: this statement will be obsolete with Qt 5.1 which will get its own full Time Zone support directly in QDateTime.

Time Zones - a Primer

It has been known for a few centuries (except among some die hard fanaticals) that the earth is (almost) a sphere and only one side faces the sun at any time. It also tends to spin so that day and night repeat at an approximately 24 hour long cycle. So every meridian on earth has a "natural" time zone in which the sun stands exactly overhead at 12 o'clock noon. Unfortunately that's where things start to get complicated.

Using natural time would be most convenient from an astronomers point of view - you can easily calculate which astronomical object (like say a specific star or the sun) is supposed to be exactly where in the sky at which time. But it is quite inconvenient for the rest of us - it would mean that the time is slightly different in town A compared to town B if they are a few kilometers apart. So time is standardized to one or few meridians per country. Luckily most countries' legislators managed to align themselves with the Greenwich meridian so that only the hours differ, but the minutes and seconds are the same (some countries differ by half hours though). In theory this should give us exactly 24 time zones - one for every major meridian (one every 15 degrees longitude).

Usually countries try to minimize the number of time zones in their territory or even align themselves with neighboring countries to ease international commerce. For example France crosses two major meridians, but uses only one time zone which isn't even on its own territory, but east of it - it uses the same time zone as Germany. This can lead to the effect that countries on the same meridian have different time zones, sometimes one hour apart, sometimes more.

During industrialization legislators brought up the great idea to save energy. In itself this is a noble cause. Their means was a simple one: during summer move time by exactly one hour, so that we will not need to switch on the electric lights quite so early. Despite it being proven to not work, most countries still observe this one-hour shift twice a year causing major headaches for factories around the world who have to deal with a one hour jump in the middle of continuous production. Unfortunately the exact date and time of this shift differs per country and tends to be "adjusted" every few decades (or sometimes multiple times in a single decade).

Now countries are not forever. They change territory, join (most of the time against the will of at least one of them), or split up. Usually those changes result in changes in regards to "standards", like time zones, currency, allowed package sizes, etc.

To make matters worse earth does not spin in a perfect 24 hour cycle. So from time to time leap seconds are inserted.

On the bright side: most entities have realized that standards are a good thing. Human nature dictates that everybody has to develop his/her own standard then. So time zones are identified officially by some complex legalese term which is either carried on when time zone rules change or changed while the rules stay the same. Consequently these terms are ignored by everybody else (including state radio and television stations and neighboring states). Businesses usually refer to time zones by some abbreviation, like EST (which means (North American) Eastern Standard Time for Americans and (Australian) Eastern Summer Time for Australians) or IST (Indian/Israel Standard Time). So these are not very helpful either in a global context. Common folk usually abbreviate "summer time" or "winter time" with sometimes a prefix like "Texan", "Eastern", "German", etc.

Are you confused yet? If yes, you have just understood the problems connected to time zones.

The only point international standardizing bodies were ever able to agree on was a time reference - UTC or Universal Coordinated Time. Which is roughly equal to the natural time of the meridian that crosses through Greenwich and Paris (so at least the British and the French didn't fight too much over it). With that local time can be expressed as UTC time plus the difference between the local time zone and UTC.

Time Zones and Computers

A computers concept of time is quite different from the mess that comprises reality for the rest of us. Most computers run at a very specific speed (usually given in MHz or GHz) and simply count their internal clock ticks from the time they were switched on. For a computer time starts with being switched on, then it is a perfect line of increasing ticks, and ends with being switched off. Unfortunately for the computer it has to deal with users and other computers which both live in a different kind of time.

The first approximation is that most computers have a built-in clock as a time reference at start up and the computer knows how many ticks make a second. With this information it can calculate the current time by adding its ticks to the time reference. This would be enough if we only needed local time, but in the Internet age computers have to deal with computers across the globe. For this computers usually use UTC as a time reference. So the computer also needs to know about its time zone - what the difference between its own time zone and UTC is and when to switch between "summer" and "winter" time.

Let's use Windows and Linux/Unix as examples. Both solve this problem quite differently.

Windows keeps the built-in reference clock on local time, adjusting it twice a year. If it needs UTC it calculates it from the local time. Time zones use unique abbreviations and/or names that may change from one version to the next. The exact rules applied to each time zone are built deep into the system.

Unix on the other hand keeps the reference clock aligned to UTC and calculates local time as a difference from UTC. On most Unixoid systems that difference it specified in a simple environment variable, so every user can have a different time zone. The POSIX standard uses a special string to specify the rules for the local time zone - it contains a name for the zone and two rules, one for summer and one for winter time, with each rule containing the difference from UTC and the exact day on which the time is switched between those two rules - this day can be expressed as a day count relative to January 1st, relative to a specific month or as a specific week day in a specific week in a specific month. The abbreviation used in this rule has no significance to the system, it is just shown to the user.

Modern Unixoid systems and Linux enhance on this system. They use the "Olson" database. This database names each time zone after a major city that geographically lies within that time zone - for most of them this is true even when accounting for a century of history. The database contains the exact time and offset for all switches between summer and winter time and switches to completely different time frames. Its history usually starts at around 1900 and includes all the changes and rules up to the year 2038 (at least as far as the rules are known now) with a POSIX rule for extending even beyond that. The database is updated regularly (multiple times a year) to reflect changes in local policy and to correct bugs (like several sources claiming to be official giving different views of time zone history).

Many other systems also use the Olson DB as reference implementation, since it is freely available.

An Implementation For Qt

My own implementation of a small Qt library that accesses the Olson DB is available through SVN:

svn co https://silmor.de/svn/softmagic/tzone/trunk tzone

Or via GIT:

git clone git://silmor.de/konrad/tzone.git tzone

This code is licensed under the GNU GPLv3, so you can use it in your own projects if they are licensed under a GPL compatible license.

To build it simply follow the instructions in the README file. Below I will show some examples on how and for what you can use this library.

The library comes with a built in version of the Olson DB data. This version may not always be up to date, but can be easily replaced if you follow the instructions in the README. If you are on a Linux system you can also use the DB version that is installed locally.

Using TimeStamp

Let's create a small example project:

#file: mytime.pro
TARGET=mytime
TEMPLATE=app
QT-=gui
SOURCES+=mytime.cpp
LIBS+= -Ltzone -ltzdata
INCLUDEPATH+=tzone/include

The statements above assume that you checked out the tzone source tree into the subdirectory tzone. The corresponding code we start with looks like this:

//file: mytime.cpp
#include <TimeStamp>
#include <QtCore>

int main(int argc,char**argv)
{
	QCoreApplication app(argc,argv);
	qDebug()<<"system time zone"<<TimeStamp::systemLocalZone();
	TimeStamp tstamp=TimeStamp::now();
	qDebug()<<"current local time "<<tstamp.toISO();
	qDebug()<<"current time in UTC"<<tstamp.toUTC().toISO();
	qDebug()<<"time in Shanghai   "<<tstamp.toZone("Asia/Shanghai").toISO();
}

The TimeStamp::systemLocalZone() statement returns the local time zone that was detected on the system. For Windows systems the name will differ from what you have set in the system settings, but will usually correspond exactly. On Linux systems it will simply probe the known variables and settings files to find out what the correct zone is.

The TimeStamp::now() statement returns the current local time. You can add false or "UTC" as an argument to get the current time in UTC. Or you can use any other Olson DB zone name to get the current time local to that zone. As you see in the lines below that you can easily convert the time stamp to differnt time zones - even with these conversions the time stamps will always refer to the exact same moment in time, just different places. Each time stamp will know its current time zone, its offset from UTC and consequently its corresponding UTC time - conversions are simply done by fetching the new offset from the database.

Timestamps can be converted to and from QDateTime:

qDebug()<<"convert to QDateTime"<<tstamp.toSystemDateTime().toString();
qDebug()<<"from QDateTime"<<TimeStamp::fromDateTime(QDateTime::currentDateTime()).toISO();

You can change the local time zone that the library uses:

TimeStamp::setDefaultZone("Asia/Shanghai");
qDebug()<<"new local time"<<TimeStamp::now().toISO();

A warning: QDateTime knows only the two time zones "UTC" and "local". So TimeStamp objects that are on UTC are converted to QDateTime objects with UTC time zone, all other TimeStamp objects are converted to "local" QDateTime objects, so those may not behave exactly as you expect if you convert them to UTC or try to let them output their time zone.

You can also change the directories from which the library gets its zone descriptions:

TimeStamp::setSearchPath(QStringList()<<"/usr/share/zoneinfo"<<":/zoneinfo");

The statement above tells the library to first searhc in the system folder /usr/share/zoneinfo, which is the default location on Linux, and then in the built-in database (the Qt-Ressource path :/zoneinfo). You should call setSearchPath before you create any TimeStamp objects or call resetRepository afterwards - otherwise the internal zone cache might disagree with the new zone definitions. Per default the library looks into its built-in database and then searches in several likely places where you normally find the system database as a fall-back.

Accessing Zone Data

Normally you will not need to access the time zone rules directly. If you do you can do this with the tzone library.

#include <TZFile>
#include <TZRule>
#include <PosixRule>
using namespace TimeZoneLib;

...

TZFile file("Asia/Shanghai");
TZRule rule=file.ruleForTime(tstamp.toUnix());
qDebug()<<"current offset from UTC"<<rule.offsetFromUTC()<<"seconds";
qDebug()<<"this is daylight saving time:"<<rule.isDST();
qDebug()<<"the local abbreviation is:"<<rule.abbreviation();
qDebug()<<"this time offset started at:"<<TimeStamp(rule.startTime(),"Asia/Shanghai").toISO();

The TZFile class wraps an Olson DB file describing one specific time zone. You can query it for all conversion rules or for the rule matching a specific time stamp (as seen above). Each conversion rule contains exactly one switch between summer and winter time with its time stamp, its offset and some other meta data. Rules beyond 2038 are automatically generated on the fly from the stored POSIX rule, which can also be queried:

PosixRule posix=file.posixRule();
qDebug()<<"POSIX rule:"<<posix.asString();
qDebug()<<"standard time zone name:"<<posix.standardName();
qDebug()<<"daylight time zone name:"<<posix.daylightName();
qDebug()<<"daylight offset:"<<posix.daylightOffset()<<"seconds";

Webmaster: webmaster AT silmor DOT de