Time
Let us first consider the time zone problem. We can easily see that there is no simple relationship between time zones and locales. All of Switzerland shares a single time zone, including Daylight Saving Time (DST) rules, but it has four official languages: French, German, Italian, and Romansch. On the other hand, Hawaii and New York share a common language, but occupy time zones five hours apart — sometimes six hours apart, because Hawaii does not observe DST. Furthermore, time zone formulas have little to do with cultural formatting preferences. For these reasons, the Essential Tools Module uses a separate time zone class
RWZone, rather than letting
RWLocale incorporate time zone responsibilities.
In the Essential Tools Module, the class
RWZone encapsulates knowledge about time zones. It is an abstract base class, with a public implementation in the class
RWZoneSimple.
RWZone provides two instances by default, one that encapsulates the rules returned by the operating system, and a second representing the rules for Universal Coordinated Time (UTC).
RWZone also allows your application to specify a local zone which, by default, represents the operating system rules. Whenever you convert an absolute time to or from a string, as in the class
RWDateTime, an instance of
RWZone is involved. By default, the local time is assumed, but you can pass a reference to any
RWZone instance.If your primary need is to represent times in the current zone, you will likely not need to use the
RWZone class directly. If, however, your application deals with multiple time zones or a zone that is not the system default, you can explicitly instantiate an
RWZone instance to represent a different time zone. The easiest way to do so is through the
RWZoneSimple class.
Here are some examples. Imagine you had scheduled a trip from New York to Paris. You were to leave New York on December 20, 2008, at 11:00 p.m., and return on March 30, 2009, leaving Paris at 5:00 a.m., Paris time. What will the clocks show at your destination when you arrive?
First, construct the time zones and the departure times:
RWZoneSimple newYorkZone(RWZone::USEastern, RWZone::NoAm);
RWZoneSimple parisZone (RWZone::Europe, RWZone::WeEu);
RWDateTime leaveNewYork (20, 12, 2008, 23, 0, 0, 0, newYorkZone);
RWDateTime leaveParis (30, 3, 2009, 5, 0, 0, 0, parisZone);
The flight is about seven hours long each way, so:
RWDateTime arriveParis (leaveNewYork + long(7 * 3600));
RWDateTime arriveNewYork(leaveParis + long(7 * 3600));
Now, display the arrival times and dates according to their respective local conventions, French in Paris and American English in New York:
RWLocaleSnapshot french("fr"); // or vendor specific
cout << "Arrivée à Paris à "
<< arriveParis.asString('c', parisZone, french)
<< ", heure locale." << endl;
cout << "Arrive in New York at "
<< arriveNewYork.asString('c', newYorkZone)
<< ", local time." << endl;
The code works even though your flight crosses several time zones and arrives on a different day than it departed; even though, on the day of the return trip in the following year, France has already begun observing DST, but the U.S. has not. None of these details are visible in the example code above — they are handled silently and invisibly by
RWDateTime and
RWZone.
All this is easy for places that follow the Essential Tools Module built-in DST rules for North America, Western Europe, and “no DST”. But what about places that follow other rules, such as Argentina, where spring begins in September and summer ends in March?
RWZoneSimple is table-driven; if the rule is simple enough, you can construct your own table of type
RWDaylightRule, and specify it as you construct an
RWZoneSimple. For example, imagine that DST begins at 2 a.m. on the last Sunday in September, and ends the first Sunday in March. Two rules are required for the southern hemisphere because DST starts
later in a calendar year (September) than it ends (March), so the additional rule sets its initial starting point.
This example creates two static instances of RWDaylightRule, one indicating the transition to DST in September, and the second the transition to STD in March and then the transition back to DST in September. (The year 1970 is for example purposes only and is not relevant to any specific DST rules.)
static RWDaylightRule sudAmerica1970 = {
0, 1970, true, {8, 4, 0, 120}, {-1, -1, -1, -1}
};
static RWDaylightRule sudAmerica = {
&sudAmerica1970, 1971, true, {8, 4, 0, 120}, {2, 0, 0, 120}
};
For details on an
RWDaylightRule instance, see the documentation for
RWZoneSimple.
Then construct an
RWZone object:
RWZoneSimple ciudadSud( RWZone::Atlantic, &sudAmerica );
Now you can use ciudadSud just like you used paris or newYork above.
But what about places where the DST rules are too complicated to describe with a simple table, such as Great Britain? There, DST begins on the morning after the third Saturday in April, unless that is Easter, in which case it begins the week prior! For such jurisdictions, you might best use standard time, properly labeled. If that just won't do, you can derive from
RWZone and implement its interface for Britain alone. This strategy is much easier than trying to generalize a case to handle all possibilities including Britain, and it's smaller and faster besides.
As you can see, Daylight Saving Time rules are volatile, often reflecting geographical and political changes. In some cases, the hard-coded table-driven struct
RWDaylightRule does not accurately reflect the locale installed on your machine. For these cases,
RWZone::os() can create a new
RWZone containing the daylight rule discovered from the underlying operating system. The onus of correctness for this DST rule is on the operating system itself.
In cases where you want more explicit control of the DST rule for the intended
RWZoneSimple, you can build a DST rule with arbitrary begin and end times, and provide it as a parameter to
RWZoneSimple.
The last time problem we will discuss here is that there is no standard way to discover what DST rules are in force for any particular place. In this the Standard C Library is no help; you must get the information you need from the local environment your application is running on, perhaps by asking the user.
One example of this problem is that the local wall clock time
RWZone instance is constructed to use North American DST rules, if DST is observed at all. If the user is not in North America, the default local time zone probably performs DST conversions wrong, and you must replace it. If you are a user in Paris, for example, you could solve this problem as follows:
RWZone::local(new RWZoneSimple(RWZone::Europe, RWZone::WeEu));
If you look closely into
<rw/locale.h>, you will find that
RWDateTime is never mentioned. Instead,
RWLocale uses the Standard C Library type
struct tm.
RWDateTime provides conversions to this type, and you may prefer using it directly rather than using
RWDateTime::asString(). For example, suppose you must write out a time string containing only hours and minutes, such as
12:33. The standard formats defined for
strftime() and implemented by
RWLocale do not include that option, but you can work around it. Here's one way:
RWDateTime now = RWDateTime::now();
cout << now.hour() << ":" << now.minute() << endl;
Without using various manipulators, this code might produce a string like 9:5. Here is another option:
RWDateTime now = RWDateTime::now();
cout << now.asString('H') << ":" << now.asString('M') << endl;
This produces 09:05.
In each of the previous examples, now is disassembled into component parts twice, once to extract the hour and once to extract the minute. This is an expensive operation. If you expect to work often with the components of a time or date, you may be better off disassembling the time only once:
RWDateTime now = RWDateTime::now();
struct tm tmbuf;
now.extract(&tmbuf);
const RWLocale& here = RWLocale::global(); // the default
// global locale
cout << here.asString(&tmbuf, 'H') << ":"
<< here.asString(&tmbuf, 'M'); << endl;