Coverage Report

Created: 2024-06-24 06:12

/src/muduo/muduo/base/TimeZone.cc
Line
Count
Source (jump to first uncovered line)
1
// Use of this source code is governed by a BSD-style license
2
// that can be found in the License file.
3
//
4
// Author: Shuo Chen (chenshuo at chenshuo dot com)
5
6
#include "muduo/base/TimeZone.h"
7
#include "muduo/base/noncopyable.h"
8
#include "muduo/base/Date.h"
9
10
#include <algorithm>
11
#include <memory>
12
#include <stdexcept>
13
#include <string>
14
#include <vector>
15
16
#include <assert.h>
17
//#define _BSD_SOURCE
18
#include <endian.h>
19
20
#include <stdint.h>
21
#include <stdio.h>
22
23
using namespace muduo;
24
25
struct TimeZone::Data
26
{
27
  struct Transition
28
  {
29
    int64_t utctime;
30
    int64_t localtime;  // Shifted Epoch
31
    int localtimeIdx;
32
33
    Transition(int64_t t, int64_t l, int localIdx)
34
        : utctime(t), localtime(l), localtimeIdx(localIdx)
35
0
    { }
36
  };
37
38
  struct LocalTime
39
  {
40
    int32_t utcOffset;  // East of UTC
41
    bool isDst;
42
    int desigIdx;
43
44
    LocalTime(int32_t offset, bool dst, int idx)
45
        : utcOffset(offset), isDst(dst), desigIdx(idx)
46
0
    { }
47
  };
48
49
  void addLocalTime(int32_t utcOffset, bool isDst, int desigIdx)
50
0
  {
51
0
    localtimes.push_back(LocalTime(utcOffset, isDst, desigIdx));
52
0
  }
53
54
  void addTransition(int64_t utcTime, int localtimeIdx)
55
0
  {
56
0
    LocalTime lt = localtimes.at(localtimeIdx);
57
0
    transitions.push_back(Transition(utcTime, utcTime + lt.utcOffset, localtimeIdx));
58
0
  }
59
60
  const LocalTime* findLocalTime(int64_t utcTime) const;
61
  const LocalTime* findLocalTime(const struct DateTime& local, bool postTransition) const;
62
63
  struct CompareUtcTime
64
  {
65
    bool operator()(const Transition& lhs, const Transition& rhs) const
66
0
    {
67
0
      return lhs.utctime < rhs.utctime;
68
0
    }
69
  };
70
71
  struct CompareLocalTime
72
  {
73
    bool operator()(const Transition& lhs, const Transition& rhs) const
74
0
    {
75
0
      return lhs.localtime < rhs.localtime;
76
0
    }
77
  };
78
79
  std::vector<Transition> transitions;
80
  std::vector<LocalTime> localtimes;
81
  string abbreviation;
82
  string tzstring;
83
};
84
85
namespace muduo
86
{
87
88
const int kSecondsPerDay = 24*60*60;
89
90
namespace detail
91
{
92
93
class File : noncopyable
94
{
95
 public:
96
  File(const char* file)
97
    : fp_(::fopen(file, "rb"))
98
0
  {
99
0
  }
100
101
  ~File()
102
0
  {
103
0
    if (fp_)
104
0
    {
105
0
      ::fclose(fp_);
106
0
    }
107
0
  }
108
109
0
  bool valid() const { return fp_; }
110
111
  string readBytes(int n)
112
0
  {
113
0
    char buf[n];
114
0
    ssize_t nr = ::fread(buf, 1, n, fp_);
115
0
    if (nr != n)
116
0
      throw std::logic_error("no enough data");
117
0
    return string(buf, n);
118
0
  }
119
120
  string readToEnd()
121
0
  {
122
0
    char buf[4096];
123
0
    string result;
124
0
    ssize_t nr = 0;
125
0
    while ( (nr = ::fread(buf, 1, sizeof buf, fp_)) > 0)
126
0
    {
127
0
      result.append(buf, nr);
128
0
    }
129
0
    return result;
130
0
  }
131
132
  int64_t readInt64()
133
0
  {
134
0
    int64_t x = 0;
135
0
    ssize_t nr = ::fread(&x, 1, sizeof(int64_t), fp_);
136
0
    if (nr != sizeof(int64_t))
137
0
      throw std::logic_error("bad int64_t data");
138
0
    return be64toh(x);
139
0
  }
140
141
  int32_t readInt32()
142
0
  {
143
0
    int32_t x = 0;
144
0
    ssize_t nr = ::fread(&x, 1, sizeof(int32_t), fp_);
145
0
    if (nr != sizeof(int32_t))
146
0
      throw std::logic_error("bad int32_t data");
147
0
    return be32toh(x);
148
0
  }
149
150
  uint8_t readUInt8()
151
0
  {
152
0
    uint8_t x = 0;
153
0
    ssize_t nr = ::fread(&x, 1, sizeof(uint8_t), fp_);
154
0
    if (nr != sizeof(uint8_t))
155
0
      throw std::logic_error("bad uint8_t data");
156
0
    return x;
157
0
  }
158
159
  off_t skip(ssize_t bytes)
160
0
  {
161
0
    return ::fseek(fp_, bytes, SEEK_CUR);
162
0
  }
163
164
 private:
165
  FILE* fp_;
166
};
167
168
// RFC 8536: https://www.rfc-editor.org/rfc/rfc8536.html
169
bool readDataBlock(File& f, struct TimeZone::Data* data, bool v1)
170
0
{
171
0
  const int time_size = v1 ? sizeof(int32_t) : sizeof(int64_t);
172
0
  const int32_t isutccnt = f.readInt32();
173
0
  const int32_t isstdcnt = f.readInt32();
174
0
  const int32_t leapcnt = f.readInt32();
175
0
  const int32_t timecnt = f.readInt32();
176
0
  const int32_t typecnt = f.readInt32();
177
0
  const int32_t charcnt = f.readInt32();
178
179
0
  if (leapcnt != 0)
180
0
    return false;
181
0
  if (isutccnt != 0 && isutccnt != typecnt)
182
0
    return false;
183
0
  if (isstdcnt != 0 && isstdcnt != typecnt)
184
0
    return false;
185
186
0
  std::vector<int64_t> trans;
187
0
  trans.reserve(timecnt);
188
0
  for (int i = 0; i < timecnt; ++i)
189
0
  {
190
0
    if (v1)
191
0
    {
192
0
      trans.push_back(f.readInt32());
193
0
    }
194
0
    else
195
0
    {
196
0
      trans.push_back(f.readInt64());
197
0
    }
198
0
  }
199
200
0
  std::vector<int> localtimes;
201
0
  localtimes.reserve(timecnt);
202
0
  for (int i = 0; i < timecnt; ++i)
203
0
  {
204
0
    uint8_t local = f.readUInt8();
205
0
    localtimes.push_back(local);
206
0
  }
207
208
0
  data->localtimes.reserve(typecnt);
209
0
  for (int i = 0; i < typecnt; ++i)
210
0
  {
211
0
    int32_t gmtoff = f.readInt32();
212
0
    uint8_t isdst = f.readUInt8();
213
0
    uint8_t abbrind = f.readUInt8();
214
215
0
    data->addLocalTime(gmtoff, isdst, abbrind);
216
0
  }
217
218
0
  for (int i = 0; i < timecnt; ++i)
219
0
  {
220
0
    int localIdx = localtimes[i];
221
0
    data->addTransition(trans[i], localIdx);
222
0
  }
223
224
0
  data->abbreviation = f.readBytes(charcnt);
225
0
  f.skip(leapcnt * (time_size + 4));
226
0
  f.skip(isstdcnt);
227
0
  f.skip(isutccnt);
228
229
0
  if (!v1)
230
0
  {
231
    // FIXME: read to next new-line.
232
0
    data->tzstring = f.readToEnd();
233
0
  }
234
235
0
  return true;
236
0
}
237
238
bool readTimeZoneFile(const char* zonefile, struct TimeZone::Data* data)
239
0
{
240
0
  File f(zonefile);
241
0
  if (f.valid())
242
0
  {
243
0
    try
244
0
    {
245
0
      string head = f.readBytes(4);
246
0
      if (head != "TZif")
247
0
        throw std::logic_error("bad head");
248
0
      string version = f.readBytes(1);
249
0
      f.readBytes(15);
250
251
0
      const int32_t isgmtcnt = f.readInt32();
252
0
      const int32_t isstdcnt = f.readInt32();
253
0
      const int32_t leapcnt = f.readInt32();
254
0
      const int32_t timecnt = f.readInt32();
255
0
      const int32_t typecnt = f.readInt32();
256
0
      const int32_t charcnt = f.readInt32();
257
258
0
      if (version == "2")
259
0
      {
260
0
        size_t skip = sizeof(int32_t) * timecnt + timecnt + 6 * typecnt +
261
0
            charcnt +  8 * leapcnt + isstdcnt + isgmtcnt;
262
0
        f.skip(skip);
263
264
0
        head = f.readBytes(4);
265
0
        if (head != "TZif")
266
0
          throw std::logic_error("bad head");
267
0
        f.skip(16);
268
0
        return readDataBlock(f, data, false);
269
0
      }
270
0
      else
271
0
      {
272
        // TODO: Test with real v1 file.
273
0
        f.skip(-4 * 6);  // Rewind to counters
274
0
        return readDataBlock(f, data, true);
275
0
      }
276
0
    }
277
0
    catch (std::logic_error& e)
278
0
    {
279
0
      fprintf(stderr, "%s\n", e.what());
280
0
    }
281
0
  }
282
0
  return false;
283
0
}
284
285
inline void fillHMS(unsigned seconds, struct DateTime* dt)
286
0
{
287
0
  dt->second = seconds % 60;
288
0
  unsigned minutes = seconds / 60;
289
0
  dt->minute = minutes % 60;
290
0
  dt->hour = minutes / 60;
291
0
}
292
293
DateTime BreakTime(int64_t t)
294
0
{
295
0
  struct DateTime dt;
296
0
  int seconds = static_cast<int>(t % kSecondsPerDay);
297
0
  int days = static_cast<int>(t / kSecondsPerDay);
298
  // C++11 rounds towards zero.
299
0
  if (seconds < 0)
300
0
  {
301
0
    seconds += kSecondsPerDay;
302
0
    --days;
303
0
  }
304
0
  detail::fillHMS(seconds, &dt);
305
0
  Date date(days + Date::kJulianDayOf1970_01_01);
306
0
  Date::YearMonthDay ymd = date.yearMonthDay();
307
0
  dt.year = ymd.year;
308
0
  dt.month = ymd.month;
309
0
  dt.day = ymd.day;
310
311
0
  return dt;
312
0
}
313
314
}  // namespace detail
315
316
}  // namespace muduo
317
318
const TimeZone::Data::LocalTime* TimeZone::Data::findLocalTime(int64_t utcTime) const
319
0
{
320
0
  const LocalTime* local = NULL;
321
322
  // row UTC time             isdst  offset  Local time (PRC)
323
  //  1  1989-09-16 17:00:00Z   0      8.0   1989-09-17 01:00:00
324
  //  2  1990-04-14 18:00:00Z   1      9.0   1990-04-15 03:00:00
325
  //  3  1990-09-15 17:00:00Z   0      8.0   1990-09-16 01:00:00
326
  //  4  1991-04-13 18:00:00Z   1      9.0   1991-04-14 03:00:00
327
  //  5  1991-09-14 17:00:00Z   0      8.0   1991-09-15 01:00:00
328
329
  // input '1990-06-01 00:00:00Z', std::upper_bound returns row 3,
330
  // so the input is in range of row 2, offset is 9 hours,
331
  // local time is 1990-06-01 09:00:00
332
0
  if (transitions.empty() || utcTime < transitions.front().utctime)
333
0
  {
334
    // FIXME: should be first non dst time zone
335
0
    local = &localtimes.front();
336
0
  }
337
0
  else
338
0
  {
339
0
    Transition sentry(utcTime, 0, 0);
340
0
    std::vector<Transition>::const_iterator transI =
341
0
        std::upper_bound(transitions.begin(), transitions.end(), sentry, CompareUtcTime());
342
0
    assert(transI != transitions.begin());
343
0
    if (transI != transitions.end())
344
0
    {
345
0
      --transI;
346
0
      local = &localtimes[transI->localtimeIdx];
347
0
    }
348
0
    else
349
0
    {
350
      // FIXME: use TZ-env
351
0
      local = &localtimes[transitions.back().localtimeIdx];
352
0
    }
353
0
  }
354
355
0
  return local;
356
0
}
357
358
const TimeZone::Data::LocalTime* TimeZone::Data::findLocalTime(
359
    const struct DateTime& lt, bool postTransition) const
360
0
{
361
0
  const int64_t localtime = fromUtcTime(lt);
362
363
0
  if (transitions.empty() || localtime < transitions.front().localtime)
364
0
  {
365
    // FIXME: should be first non dst time zone
366
0
    return &localtimes.front();
367
0
  }
368
369
0
  Transition sentry(0, localtime, 0);
370
0
  std::vector<Transition>::const_iterator transI =
371
0
      std::upper_bound(transitions.begin(), transitions.end(), sentry, CompareLocalTime());
372
0
  assert(transI != transitions.begin());
373
374
0
  if (transI == transitions.end())
375
0
  {
376
    // FIXME: use TZ-env
377
0
    return &localtimes[transitions.back().localtimeIdx];
378
0
  }
379
380
0
  Transition prior_trans = *(transI - 1);
381
0
  int64_t prior_second = transI->utctime - 1 + localtimes[prior_trans.localtimeIdx].utcOffset;
382
383
  // row UTC time             isdst  offset  Local time (PRC)     Prior second local time
384
  //  1  1989-09-16 17:00:00Z   0      8.0   1989-09-17 01:00:00
385
  //  2  1990-04-14 18:00:00Z   1      9.0   1990-04-15 03:00:00  1990-04-15 01:59:59
386
  //  3  1990-09-15 17:00:00Z   0      8.0   1990-09-16 01:00:00  1990-09-16 01:59:59
387
  //  4  1991-04-13 18:00:00Z   1      9.0   1991-04-14 03:00:00  1991-04-14 01:59:59
388
  //  5  1991-09-14 17:00:00Z   0      8.0   1991-09-15 01:00:00
389
390
  // input 1991-04-14 02:30:00, found row 4,
391
  //  4  1991-04-13 18:00:00Z   1      9.0   1991-04-14 03:00:00  1991-04-14 01:59:59
392
0
  if (prior_second < localtime)
393
0
  {
394
    // it's a skip
395
    // printf("SKIP: prev %ld local %ld start %ld\n", prior_second, localtime, transI->localtime);
396
0
    if (postTransition)
397
0
    {
398
0
      return &localtimes[transI->localtimeIdx];
399
0
    }
400
0
    else
401
0
    {
402
0
      return &localtimes[prior_trans.localtimeIdx];
403
0
    }
404
0
  }
405
406
  // input 1990-09-16 01:30:00, found row 4, looking at row 3
407
  //  3  1990-09-15 17:00:00Z   0      8.0   1990-09-16 01:00:00  1990-09-16 01:59:59
408
0
  --transI;
409
0
  if (transI != transitions.begin())
410
0
  {
411
0
    prior_trans = *(transI - 1);
412
0
    prior_second = transI->utctime - 1 + localtimes[prior_trans.localtimeIdx].utcOffset;
413
0
  }
414
0
  if (localtime <= prior_second)
415
0
  {
416
    // it's repeat
417
    // printf("REPEAT: prev %ld local %ld start %ld\n", prior_second, localtime, transI->localtime);
418
0
    if (postTransition)
419
0
    {
420
0
      return &localtimes[transI->localtimeIdx];
421
0
    }
422
0
    else
423
0
    {
424
0
      return &localtimes[prior_trans.localtimeIdx];
425
0
    }
426
0
  }
427
428
  // otherwise, it's unique
429
0
  return &localtimes[transI->localtimeIdx];
430
0
}
431
432
// static
433
TimeZone TimeZone::UTC()
434
0
{
435
0
  return TimeZone(0, "UTC");
436
0
}
437
438
// static
439
TimeZone TimeZone::loadZoneFile(const char* zonefile)
440
0
{
441
0
  std::unique_ptr<Data> data(new Data);
442
0
  if (!detail::readTimeZoneFile(zonefile, data.get()))
443
0
  {
444
0
    data.reset();
445
0
  }
446
0
  return TimeZone(std::move(data));
447
0
}
448
449
TimeZone::TimeZone(std::unique_ptr<Data> data)
450
  : data_(std::move(data))
451
0
{
452
0
}
453
454
TimeZone::TimeZone(int eastOfUtc, const char* name)
455
  : data_(new TimeZone::Data)
456
0
{
457
0
  data_->addLocalTime(eastOfUtc, false, 0);
458
0
  data_->abbreviation = name;
459
0
}
460
461
struct DateTime TimeZone::toLocalTime(int64_t seconds, int* utcOffset) const
462
0
{
463
0
  struct DateTime localTime;
464
0
  assert(data_ != NULL);
465
466
0
  const Data::LocalTime* local = data_->findLocalTime(seconds);
467
468
0
  if (local)
469
0
  {
470
0
    localTime = detail::BreakTime(seconds + local->utcOffset);
471
0
    if (utcOffset)
472
0
    {
473
0
      *utcOffset = local->utcOffset;
474
0
    }
475
0
  }
476
477
0
  return localTime;
478
0
}
479
480
int64_t TimeZone::fromLocalTime(const struct DateTime& localtime, bool postTransition) const
481
0
{
482
0
  assert(data_ != NULL);
483
0
  const Data::LocalTime* local = data_->findLocalTime(localtime, postTransition);
484
0
  const int64_t localSeconds = fromUtcTime(localtime);
485
0
  if (local)
486
0
  {
487
0
    return localSeconds - local->utcOffset;
488
0
  }
489
  // fallback as if it's UTC time.
490
0
  return localSeconds;
491
0
}
492
493
DateTime TimeZone::toUtcTime(int64_t secondsSinceEpoch)
494
0
{
495
0
  return detail::BreakTime(secondsSinceEpoch);
496
0
}
497
498
int64_t TimeZone::fromUtcTime(const DateTime& dt)
499
0
{
500
0
  Date date(dt.year, dt.month, dt.day);
501
0
  int secondsInDay = dt.hour * 3600 + dt.minute * 60 + dt.second;
502
0
  int64_t days = date.julianDayNumber() - Date::kJulianDayOf1970_01_01;
503
0
  return days * kSecondsPerDay + secondsInDay;
504
0
}
505
506
507
DateTime::DateTime(const struct tm& t)
508
  : year(t.tm_year + 1900), month(t.tm_mon + 1), day(t.tm_mday),
509
    hour(t.tm_hour), minute(t.tm_min), second(t.tm_sec)
510
0
{
511
0
}
512
513
string DateTime::toIsoString() const
514
0
{
515
0
  char buf[64];
516
0
  snprintf(buf, sizeof buf, "%04d-%02d-%02d %02d:%02d:%02d",
517
0
           year, month, day, hour, minute, second);
518
0
  return buf;
519
0
}