File: vmtz.h

package info (click to toggle)
qtads 2.1.6-1.1
  • links: PTS
  • area: main
  • in suites: stretch
  • size: 16,156 kB
  • ctags: 18,767
  • sloc: cpp: 133,078; ansic: 26,048; xml: 18; makefile: 11
file content (494 lines) | stat: -rw-r--r-- 17,474 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
/* 
 *   Copyright (c) 1991, 2002 Michael J. Roberts.  All Rights Reserved.
 *   
 *   Please see the accompanying license file, LICENSE.TXT, for information
 *   on using and copying this software.  
 */
/*
Name
  vmtz.h - TADS time zone management
Function
  TADS uses the IANA zoneinfo database (aka the Olson database) for
  converting time values between universal time (UTC) and local time
  representations.  This is used in particular in the Date intrinsic class,
  which provides a bytecode API for time and date conversions, formatting,
  and arithmetic.

  This zoneinfo database lets us determine the correct local time, given a
  UTC time, in just about any regional time zone on just about any date since
  time zones were invented.  It takes into account the ever-changing daylight
  savings rules as well as historical time zone realignments and
  redefinitions.  We don't just project the current time zone rules back in
  time, as most operating systems do in their time managers; the zoneinfo
  database tells us the actual changes that occurred.  For example, in the
  United States, we'll properly start daylight savings time from the last
  Sunday in April in 1986, but use the second Sunday in April in 2007 and
  later.  Likewise, we'll properly display EWT (Eastern War Time) if asked
  for a local time in New York in 1943.

  We use a custom binary version of the zoneinfo database.  The TADS source
  code distribution includes a compiler (written in TADS) that reads the
  zoneinfo sources files as distributed by IANA and generates our custom
  binary file.  We don't compile the file merely to have it in binary form,
  but rather because the source form of the information requires quite a lot
  of work to interpret.  It uses a considerable amount of abstraction, both
  to make the zone histories and rules more readily human-readable and to
  make them more compact and reusable (e.g., it allows for expressing rules
  at the national level that span multiple time zones, such as in the US).
  The main reason to compile the material is to boil down the abstraction to
  a form that can be used efficiently at run-time.  While we're at it we
  generate a binary file format that's fast and easy to read.

  This package includes a cache manager that reads zone data from the
  binary file on demand.  The zoneinfo database (and thus our custom binary
  version of it) contains quite a lot of information, since it has historical
  records on each of about 450 time zones around the world.  It amounts to
  about 300k of compiled binary data in our format.  Rather than read all of
  that into memory, we'll read it as needed per time zone.  Most programs
  will use only the local time zone of the local system, so in most cases
  we'll never need to read more than one zone.
Notes
  None
Modified
  02/05/12 MJRoberts  - creation
*/

#ifndef VMTZ_H
#define VMTZ_H

#include "t3std.h"
#include "vmglob.h"
#include "vmobj.h"


/* ------------------------------------------------------------------------ */
/*
 *   Time zone cache.  This loads time zone data on demand.
 */
class CVmTimeZoneCache
{
    friend class CVmTimeZone;
    
public:
    CVmTimeZoneCache();
    ~CVmTimeZoneCache();

    /*
     *   Parse a timezone name, returning a CVmTimeZone object representing
     *   the zone.  This accepts names in the following formats:
     *   
     *.    :local       - the local zone
     *   
     *.    UTC+offset   - or GMT+ofs, Z+ofs, or simply +ofs: a zone at the
     *.                   given hh[:mm[:ss]] offset from UTC.  This type of
     *.                   zone uses a fixed offset year round and for all
     *.                   dates, not tied to any location's history.
     *   
     *.    zoneinfoName - the name of a zoneinfo database entry, such as
     *.                   "America/Los_Angeles".  This type uses the zone
     *.                   history from the database.
     */
    class CVmTimeZone *parse_zone(VMG_ const char *name, size_t len);

    /* 
     *   Get a time zone object by name from the zoneinfo database; returns
     *   the cached copy if present, otherwise loads the entry from disk.
     */
    class CVmTimeZone *get_db_zone(VMG_ const char *name, size_t len);
    class CVmTimeZone *get_db_zone(VMG_ const char *name)
        { return get_db_zone(vmg_ name, strlen(name)); }

    /* 
     *   Get a time zone object by GMT offset, in seconds.  Returns a zone
     *   that represents the fixed GMT offset, not for any particular
     *   location.
     */
    class CVmTimeZone *get_gmtofs_zone(VMG_ int32_t gmtofs_secs);

    /*
     *   Get a time zone object by abbreviation.  This searches the
     *   abbreviation list for a match, and returns the corresponding
     *   timezone object if found.  We also fill in the GMT offset for the
     *   abbreviation - this is required when the zone is specified by
     *   abbreviation because an abbreviation carries a standard time or
     *   daylight time designation, which we'd normally infer from the zone
     *   and date information.  An explicit daylight or standard time
     *   designation overrides the time that's normally in effect on the
     *   given date.  For example, "1/1/2012 12:00 PDT" corresopnds to
     *   1/1/2012 19:00 GMT, whereas "1/1/2012 12:00" America/Los_Angeles
     *   corresponds to 20:00 GMT.  The difference is that standard time is
     *   normally in effect on that date: a time specified by location
     *   (America/Los_Angeles) is interpreted as a wall clock time for that
     *   date according to the prevailing standard/daylight rules for the
     *   zone, whereas a time explicitly in PDT is interpreted as daylight
     *   time regardless of whether daylight or standard time is actually in
     *   effect in the zone on the given date.
     */
    class CVmTimeZone *get_zone_by_abbr(VMG_ int32_t *gmtofs_ms,
                                        const char *name, size_t len);

    /* 
     *   Create a missing database zone.  This creates a placeholder zone
     *   representing a zone imported from a saved game using a different
     *   version of the database that includes a zone that's not defined in
     *   the version we're using.
     */
    class CVmTimeZone *create_missing_zone(
        VMG_ const char *name, size_t namelen, const os_tzinfo_t *desc);

    /* open the zoneinfo binary file */
    static osfildef *open_zoneinfo_file(VMG0_);

    /*
     *   Get the system default local time zone.  Returns a CVmTimeZone
     *   object representing the local zone. 
     */
    class CVmTimeZone *get_local_zone(VMG0_);

    /* is the given zone the special local zone? */
    int is_local_zone(class CVmTimeZone *zone) const
        { return zone == local_zone_; }

protected:
    /* load the zoneinfo database index from the binary file */
    int load_db_index(VMG0_);

    /* get or load the CVmTimeZone object for a hash entry */
    CVmTimeZone *tz_from_hash(VMG_ class ZoneHashEntry *entry);

    /* parse a "UTC+-h[:mm[:ss]]" offset string into a zone */
    CVmTimeZone *parse_zone_hhmmss(
        VMG_ const char *prefix, const char *name, size_t len);

    /* parse a zone spec in POSIX TZ format (EST5, EST5EDT, EST5EDT4) */
    CVmTimeZone *parse_zone_posixTZ(VMG_ const char *name, size_t len);

    /* parse an [+-]h[:mm[:ss]] time/offset value */
    int parse_hhmmss(int32_t &ofs, const char *&name, size_t &len,
                     int sign_required);

    /* search for a set of zone specifications */
    class ZoneHashEntry *search(
        VMG_ const struct ZoneSearchSpec *specs, int cnt);
    class ZoneHashEntry *search(
        VMG_ const struct ZoneSearchSpec *specs, class AbbrHashEntry **e,
        int cnt, class ZoneHashEntry *zone);

    /* have we attempted to load the index yet? */
    int index_loaded_;

    /* did we successfully load the index? */
    int index_loaded_ok_;

    /* 
     *   Cached local zone object.  Determining the local time zone can be a
     *   non-trivial amount of work, so we cache this after we figure it out
     *   the first time. 
     */
    class CVmTimeZone *local_zone_;

    /* zone name hash table for zones loaded from the zoneinfo database */
    class CVmHashTable *db_tab_;

    /* zone name hash table for synthesized zones - GMT+ofs, EST5EDT, etc */
    class CVmHashTable *synth_tab_;

    /* abbreviation hash table */
    class CVmHashTable *abbr_tab_;

    /* head of linked list of *loaded* zone objects */
    class ZoneHashEntry *first_loaded_;

    /* zone index, loaded from the binary file */
    char *zone_bytes_;

    /* link index, loaded from the binary file */
    char *link_bytes_;

    /* abbreviation mapping index, loaded from the binary file */
    char *abbr_bytes_;
};

/* ------------------------------------------------------------------------ */
/*
 *   Time type.  This describes the settings for a time zone during the
 *   period covered by a transition.
 */
struct vmtz_ttype
{
    vmtz_ttype() { }
    vmtz_ttype(const struct vmtzquery *query);
    vmtz_ttype(int32_t gmtofs, int32_t save, const char *fmt)
    {
        this->gmtofs = gmtofs;
        this->save = save;
        this->fmt = fmt;
    }

    /* 
     *   standard time ofset, in milliseconds - add this to UTC to get local
     *   standard time 
     */
    int32_t gmtofs;

    /* 
     *   daylight time offset, in milliseconds - add to local standard time
     *   to get local wall clock time 
     */
    int32_t save;

    /* time zone format string abbreviation ("EDT", etc) */
    const char *fmt;
};

/*
 *   Transition - this records a change in a time zone's rules.  Each
 *   transition covers the period from its FROM date (inclusive) to (but not
 *   including) the next transition's FROM date.
 */
struct vmtz_trans
{
    vmtz_trans() { }
    vmtz_trans(int32_t dayno, int32_t daytime, vmtz_ttype *ttype)
    {
        this->dayno = dayno;
        this->daytime = daytime;
        this->ttype = ttype;
    }
        
    /* 
     *   Date/time of the transition, relative to 3/1/0000 UTC; the time is
     *   in milliseconds after midnight.  This is starting time for the
     *   transition - it's the moment that our ttype comes into effect.
     */
    int32_t dayno;
    int32_t daytime;

    /* compare my date to the given date/time (time in milliseconds) */
    int compare_to(int32_t dayno, int32_t daytime, int local) const;

    /* create a List object representing the transition */
    vm_obj_id_t get_as_list(VMG0_) const;

    /* time zone settings in effect on and after this transition */
    struct vmtz_ttype *ttype;
};

/*
 *   Time zone daylight savings rule.  This describes a switch between
 *   standard time and daylight time.  Any given rule fires once a year.
 *   Rules apply to the period after the last enumerated transition in the
 *   time zone; this allows us to project the current rules that apply in the
 *   zone forward in time indefinitely, without having to enumerate all of
 *   the possible transitions (which would be impractical given that our time
 *   type can in principle represent millions of years into the future).
 */
struct vmtz_rule
{
    /* the format abbreviation to use when the rule is in effect */
    const char *fmt;

    /* the gmt offset for standard time during this rule, in milliseconds */
    int32_t gmtofs;

    /* daylight savings time offset from standard time, in milliseconds */
    int32_t save;

    /* the month the rule takes effect (1=January) */
    char mm;

    /* 
     *   When in the month the rule takes effect:
     *   
     *.    0 -> takes effect on the fixed day of the month <dd>
     *.    1 -> last <weekday> of the month
     *.    2 -> first <weekday> on or after <dd>
     *.    3 -> first <weekday> on or before <dd>
     */
    char when;

    /* 
     *   The day of the month the rule takes effect (1-366).  (This can be
     *   greater than 31, because it's also used to encode POSIX TZ-style
     *   rules with "Julian day of year counting Feb 29".  Our calendar
     *   calculator can handle overflows in the day of month, carrying them
     *   automatically into subsequent months, so we can encode the Nth day
     *   of the year as simply January Nth, and we'll get the right result
     *   even if N > 31.
     */
    short dd;
    
    /* the day of the week the rule takes effect (1=Sunday) */
    char weekday;

    /* 
     *   Time zone for the effective time/date: 0 -> local wall clock time
     *   (with the zone settings for the period immediately before the rule
     *   takes effect); 0x40 -> local standard time (for the period
     *   immediately before the rule takes effect), 0x80 -> UTC 
     */
    uchar at_zone;

    /* time of day the rule takes effect, as milliseconds after midnight */
    int32_t at;

    /* resolve this rule in the given year */
    void resolve(int32_t &dayno, int32_t &daytime, int year,
                 const vmtz_rule *prev_rule);

    /* set from an OS DST start/end date descriptor */
    void set(const os_tzrule_t *desc, const char *abbr,
             int32_t gmtofs, int32_t save);
};


/* ------------------------------------------------------------------------ */
/*
 *   Time zone info query structure
 */
struct vmtzquery
{
    /* starting date/time of the transition or rule that applies */
    int32_t start_dayno;
    int32_t start_daytime;

    /* GMT offset for standard time, in milliseconds */
    int32_t gmtofs;

    /* daylight savings offset from standard time, in milliseconds */
    int32_t save;

    /* zone name abbreviation ("EST", etc) */
    const char *abbr;

    /* set from a transition item */
    void set(const vmtz_trans *trans)
        { set(trans->ttype, trans->dayno, trans->daytime); }

    /* set from a time type */
    void set(const vmtz_ttype *tty, int32_t dayno, int32_t daytime)
    {
        start_dayno = dayno;
        start_daytime = daytime;
        gmtofs = tty->gmtofs;
        save = tty->save;
        abbr = tty->fmt;
    }

    /* set from a rule */
    void set(const vmtz_rule *rule, int32_t dayno, int32_t daytime)
    {
        start_dayno = dayno;
        start_daytime = daytime;
        gmtofs = rule->gmtofs;
        save = rule->save;
        abbr = rule->fmt;
    }
};

/* ------------------------------------------------------------------------ */
/*
 *   Time zone object.
 */
class CVmTimeZone
{
public:
    /* create from a timezone descriptor */
    CVmTimeZone(const os_tzinfo_t *desc);

    /* destruction */
    ~CVmTimeZone();

    /* load from the zoneinfo database file */
    static CVmTimeZone *load(VMG_ class ZoneHashEntry *entry);

    /* get the primary name of the zone */
    const char *get_name(size_t &len) const;

    /* create a List object with this zone's names and aliases */
    vm_obj_id_t get_name_list(VMG0_) const;

    /* get the single history item that applies to the given date */
    vm_obj_id_t get_history_item(VMG_ int32_t dayno, int32_t daytime) const;

    /* create a List object populated with the transition list */
    vm_obj_id_t get_history_list(VMG0_) const;

    /* create a List object populated with the ongoing rules */
    vm_obj_id_t get_rule_list(VMG0_) const;

    /* country code, if available (ISO3166 two-letter country code) */
    const char *country() const { return country_; }

    /* coordinates, if available (+DDMM+DDDMM or +DDMMSS+DDDMMSS format) */
    const char *coords() const { return coords_; }

    /* zone.tab description/comment */
    const char *desc() const { return desc_; }

    /* 
     *   Convert a local wall clock time in this zone to UTC.  'daytime' is
     *   in milliseconds after midnight.
     */
    void local_to_utc(int32_t &dayno, int32_t &daytime);

    /* 
     *   Convert a UTC time to local time in this zone.  Returns the format
     *   abbreviation for the converted time (e.g., PDT for Pacific Daylight
     *   Time).  'daytime' is in milliseconds after midnight.  Fills in
     *   'tzofs' with the time zone's offset from UTC, in milliseconds.
     */
    const char *utc_to_local(int32_t &dayno, int32_t &daytime, int32_t &tzofs);

    /* 
     *   Query the time zone information at the given time/right now.  The
     *   time is in milliseconds after midnight. 
     */
    void query(vmtzquery *result,
               int32_t dayno, int32_t daytime, int local) const;
    void query(vmtzquery *result) const;

protected:
    /* create from binary data loaded from the file */
    CVmTimeZone(class ZoneHashEntryDb *entry, const char *file_data,
                unsigned int trans_cnt, unsigned int type_cnt,
                unsigned int rule_cnt, unsigned int abbr_bytes);

    /* create a synthetic zone */
    CVmTimeZone(class ZoneHashEntrySynth *entry);

    /* initialize from an OS timezone descriptor */
    void init(const os_tzinfo_t *desc);

    /* my hash table entry */
    class ZoneHashEntry *hashentry_;

    /* my name; used only if we don't have a hash entry */
    char *name_;

    /* transition list */
    int trans_cnt_;
    vmtz_trans *trans_;

    /* time type list */
    int ttype_cnt_;
    vmtz_ttype *ttype_;

    /* rule list */
    int rule_cnt_;
    vmtz_rule *rule_;

    /* abbreviations */
    char *abbr_;

    /* country code */
    char country_[3];

    /* coordinates */
    char coords_[16];

    /* comments/description from zone.tab */
    char *desc_;
};

#endif