DateLUTImpl.h 22.1 KB
Newer Older
1 2
#pragma once

3
#include <common/Types.h>
4
#include <common/DayNum.h>
5 6
#include <common/likely.h>
#include <ctime>
P
proller 已提交
7
#include <string>
8

9 10
#define DATE_LUT_MAX (0xFFFFFFFFU - 86400)
#define DATE_LUT_MAX_DAY_NUM (0xFFFFFFFFU / 86400)
11 12
/// Table size is bigger than DATE_LUT_MAX_DAY_NUM to fill all indices within UInt16 range: this allows to remove extra check.
#define DATE_LUT_SIZE 0x10000
13
#define DATE_LUT_MIN_YEAR 1970
14
#define DATE_LUT_MAX_YEAR 2105 /// Last supported year
15
#define DATE_LUT_YEARS (1 + DATE_LUT_MAX_YEAR - DATE_LUT_MIN_YEAR) /// Number of years in lookup table
16 17


18 19
/** Lookup table to conversion of time to date, and to month / year / day of week / day of month and so on.
  * First time was implemented for OLAPServer, that needed to do billions of such transformations.
20 21 22 23
  */
class DateLUTImpl
{
public:
24
    DateLUTImpl(const std::string & time_zone);
25 26

public:
27 28 29
    struct Values
    {
        /// Least significat 32 bits from time_t at beginning of the day.
30 31 32
        /// If the unix timestamp of beginning of the day is negative (example: 1970-01-01 MSK, where time_t == -10800), then value is zero.
        /// Change to time_t; change constants above; and recompile the sources if you need to support time after 2105 year.
        UInt32 date;
33 34 35 36 37 38 39

        /// Properties of the day.
        UInt16 year;
        UInt8 month;
        UInt8 day_of_month;
        UInt8 day_of_week;

40 41 42 43
        /// Total number of days in current month. Actually we can use separate table that is independent of time zone.
        /// But due to alignment, this field is totally zero cost.
        UInt8 days_in_month;

44
        /// For days, when offset from UTC was changed due to daylight saving time or permanent change, following values could be non zero.
45 46
        UInt16 time_at_offset_change; /// In seconds from beginning of the day. Assuming offset never changed close to the end of day (so, value < 65536).
        Int16 amount_of_offset_change; /// Usually -3600 or 3600, but look at Lord Howe Island.
47
    };
48 49

private:
50 51
    /// Lookup table is indexed by DayNum.
    /// Day nums are the same in all time zones. 1970-01-01 is 0 and so on.
52
    /// Table is relatively large, so better not to place the object on stack.
53
    /// In comparison to std::vector, plain array is cheaper by one indirection.
54
    Values lut[DATE_LUT_SIZE];
55 56

    /// Year number after DATE_LUT_MIN_YEAR -> day num for start of year.
57
    DayNum years_lut[DATE_LUT_YEARS];
58

59
    /// Year number after DATE_LUT_MIN_YEAR * month number starting at zero -> day num for first day of month
60
    DayNum years_months_lut[DATE_LUT_YEARS * 12];
61

62
    /// UTC offset at beginning of the Unix epoch. The same as unix timestamp of 1970-01-01 00:00:00 local time.
63
    time_t offset_at_start_of_epoch;
64
    bool offset_is_whole_number_of_hours_everytime;
65 66 67 68 69

    /// Time zone name.
    std::string time_zone;


A
alexey-milovidov 已提交
70
    /// We can correctly process only timestamps that less DATE_LUT_MAX (i.e. up to 2105 year inclusively)
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
    inline size_t findIndex(time_t t) const
    {
        /// First guess.
        size_t guess = t / 86400;
        if (guess >= DATE_LUT_MAX_DAY_NUM)
            return 0;
        if (t >= lut[guess].date && t < lut[guess + 1].date)
            return guess;

        for (size_t i = 1;; ++i)
        {
            if (guess + i >= DATE_LUT_MAX_DAY_NUM)
                return 0;
            if (t >= lut[guess + i].date && t < lut[guess + i + 1].date)
                return guess + i;
            if (guess < i)
                return 0;
            if (t >= lut[guess - i].date && t < lut[guess - i + 1].date)
                return guess - i;
        }
    }

    inline const Values & find(time_t t) const
    {
        return lut[findIndex(t)];
    }

98
public:
99 100
    const std::string & getTimeZone() const { return time_zone; }

101
    /// All functions below are thread-safe; arguments are not checked.
102

103 104
    inline time_t toDate(time_t t) const { return find(t).date; }
    inline unsigned toMonth(time_t t) const { return find(t).month; }
105
    inline unsigned toQuarter(time_t t) const { return (find(t).month - 1) / 3 + 1; }
106 107 108
    inline unsigned toYear(time_t t) const { return find(t).year; }
    inline unsigned toDayOfWeek(time_t t) const { return find(t).day_of_week; }
    inline unsigned toDayOfMonth(time_t t) const { return find(t).day_of_month; }
109

110
    /// Round down to start of monday.
111
    inline time_t toFirstDayOfWeek(time_t t) const
112 113 114 115 116
    {
        size_t index = findIndex(t);
        return lut[index - (lut[index].day_of_week - 1)].date;
    }

117
    inline DayNum toFirstDayNumOfWeek(DayNum d) const
118
    {
119
        return DayNum(d - (lut[d].day_of_week - 1));
120 121
    }

122
    inline DayNum toFirstDayNumOfWeek(time_t t) const
123
    {
124
        return toFirstDayNumOfWeek(toDayNum(t));
125 126
    }

127
    /// Round down to start of month.
128
    inline time_t toFirstDayOfMonth(time_t t) const
129 130 131 132 133
    {
        size_t index = findIndex(t);
        return lut[index - (lut[index].day_of_month - 1)].date;
    }

134
    inline DayNum toFirstDayNumOfMonth(DayNum d) const
135
    {
136
        return DayNum(d - (lut[d].day_of_month - 1));
137 138
    }

139
    inline DayNum toFirstDayNumOfMonth(time_t t) const
140
    {
141
        return toFirstDayNumOfMonth(toDayNum(t));
142 143
    }

144
    /// Round down to start of quarter.
145
    inline DayNum toFirstDayNumOfQuarter(DayNum d) const
146
    {
147
        size_t index = d;
148 149
        size_t month_inside_quarter = (lut[index].month - 1) % 3;

A
Alexey Milovidov 已提交
150
        index = index - lut[index].day_of_month;
151
        while (month_inside_quarter)
152
        {
153 154
            index = index - lut[index].day_of_month;
            --month_inside_quarter;
155
        }
156

157
        return DayNum(index + 1);
158 159
    }

160
    inline DayNum toFirstDayNumOfQuarter(time_t t) const
161
    {
162 163 164 165 166 167
        return toFirstDayNumOfQuarter(toDayNum(t));
    }

    inline time_t toFirstDayOfQuarter(time_t t) const
    {
        return fromDayNum(toFirstDayNumOfQuarter(t));
168 169
    }

170
    /// Round down to start of year.
171
    inline time_t toFirstDayOfYear(time_t t) const
172 173 174 175
    {
        return lut[years_lut[lut[findIndex(t)].year - DATE_LUT_MIN_YEAR]].date;
    }

176
    inline DayNum toFirstDayNumOfYear(DayNum d) const
177
    {
178
        return years_lut[lut[d].year - DATE_LUT_MIN_YEAR];
179 180
    }

181
    inline DayNum toFirstDayNumOfYear(time_t t) const
182
    {
183
        return toFirstDayNumOfYear(toDayNum(t));
184 185
    }

186
    inline time_t toFirstDayOfNextMonth(time_t t) const
187 188 189 190 191 192
    {
        size_t index = findIndex(t);
        index += 32 - lut[index].day_of_month;
        return lut[index - (lut[index].day_of_month - 1)].date;
    }

193
    inline time_t toFirstDayOfPrevMonth(time_t t) const
194 195 196 197 198 199
    {
        size_t index = findIndex(t);
        index -= lut[index].day_of_month;
        return lut[index - (lut[index].day_of_month - 1)].date;
    }

200
    inline UInt8 daysInMonth(DayNum d) const
201 202 203 204
    {
        return lut[d].days_in_month;
    }

205
    inline UInt8 daysInMonth(time_t t) const
206
    {
207 208 209
        return find(t).days_in_month;
    }

210
    inline UInt8 daysInMonth(UInt16 year, UInt8 month) const
211
    {
A
Alexey Milovidov 已提交
212 213
        /// 32 makes arithmetic more simple.
        auto any_day_of_month = years_lut[year - DATE_LUT_MIN_YEAR] + 32 * (month - 1);
214
        return lut[any_day_of_month].days_in_month;
215 216
    }

217
    /** Round to start of day, then shift for specified amount of days.
218
      */
219
    inline time_t toDateAndShift(time_t t, Int32 days) const
220 221 222 223
    {
        return lut[findIndex(t) + days].date;
    }

224
    inline time_t toTime(time_t t) const
225 226
    {
        size_t index = findIndex(t);
227 228

        if (unlikely(index == 0))
229
            return t + offset_at_start_of_epoch;
230

231 232 233 234 235
        time_t res = t - lut[index].date;

        if (res >= lut[index].time_at_offset_change)
            res += lut[index].amount_of_offset_change;

236
        return res - offset_at_start_of_epoch; /// Starting at 1970-01-01 00:00:00 local time.
237 238
    }

239
    inline unsigned toHour(time_t t) const
240 241
    {
        size_t index = findIndex(t);
242

243 244
        /// If it is not 1970 year (findIndex found nothing appropriate),
        ///  than limit number of hours to avoid insane results like 1970-01-01 89:28:15
245
        if (unlikely(index == 0))
246
            return static_cast<unsigned>((t + offset_at_start_of_epoch) / 3600) % 24;
247

248 249
        time_t res = t - lut[index].date;

250 251
        /// NOTE We doesn't support cases when time change result in switching to previous day.
        /// Data is cleaned to avoid these cases, so no underflow occurs here.
252 253 254 255 256 257
        if (res >= lut[index].time_at_offset_change)
            res += lut[index].amount_of_offset_change;

        return res / 3600;
    }

258 259 260 261 262 263 264 265 266 267 268 269
    /** Only for time zones with/when offset from UTC is multiple of five minutes.
      * This is true for all time zones: right now, all time zones have an offset that is multiple of 15 minutes.
      *
      * "By 1929, most major countries had adopted hourly time zones. Nepal was the last
      *  country to adopt a standard offset, shifting slightly to UTC+5:45 in 1986."
      * - https://en.wikipedia.org/wiki/Time_zone#Offsets_from_UTC
      *
      * Also please note, that unix timestamp doesn't count "leap seconds":
      *  each minute, with added or subtracted leap second, spans exactly 60 unix timestamps.
      */

    inline unsigned toSecond(time_t t) const { return t % 60; }
270

271
    inline unsigned toMinute(time_t t) const
272
    {
273 274 275
        if (offset_is_whole_number_of_hours_everytime)
            return (t / 60) % 60;

276
        time_t date = find(t).date;
277
        return (t - date) / 60 % 60;
278 279
    }

280 281
    inline time_t toStartOfMinute(time_t t) const { return t / 60 * 60; }
    inline time_t toStartOfFiveMinute(time_t t) const { return t / 300 * 300; }
282
    inline time_t toStartOfFifteenMinutes(time_t t) const { return t / 900 * 900; }
283 284

    inline time_t toStartOfHour(time_t t) const
285
    {
286 287 288
        if (offset_is_whole_number_of_hours_everytime)
            return t / 3600 * 3600;

289
        time_t date = find(t).date;
290
        /// Still can return wrong values for time at 1970-01-01 if the UTC offset was non-whole number of hours.
291 292 293
        return date + (t - date) / 3600 * 3600;
    }

294 295 296 297 298 299 300
    /** Number of calendar day since the beginning of UNIX epoch (1970-01-01 is zero)
      * We use just two bytes for it. It covers the range up to 2105 and slightly more.
      *
      * This is "calendar" day, it itself is independent of time zone
      * (conversion from/to unix timestamp will depend on time zone,
      *  because the same calendar day starts/ends at different timestamps in different time zones)
      */
301

302 303
    inline DayNum toDayNum(time_t t) const { return static_cast<DayNum>(findIndex(t)); }
    inline time_t fromDayNum(DayNum d) const { return lut[d].date; }
304

305 306 307 308 309 310
    inline time_t toDate(DayNum d) const { return lut[d].date; }
    inline unsigned toMonth(DayNum d) const { return lut[d].month; }
    inline unsigned toQuarter(DayNum d) const { return (lut[d].month - 1) / 3 + 1; }
    inline unsigned toYear(DayNum d) const { return lut[d].year; }
    inline unsigned toDayOfWeek(DayNum d) const { return lut[d].day_of_week; }
    inline unsigned toDayOfMonth(DayNum d) const { return lut[d].day_of_month; }
311 312 313
    inline unsigned toDayOfYear(DayNum d) const { return d + 1 - toFirstDayNumOfYear(d); }

    inline unsigned toDayOfYear(time_t t) const { return toDayOfYear(toDayNum(t)); }
314

315 316 317
    /// Number of week from some fixed moment in the past. Week begins at monday.
    /// (round down to monday and divide DayNum by 7; we made an assumption,
    ///  that in domain of the function there was no weeks with any other number of days than 7)
318
    inline unsigned toRelativeWeekNum(DayNum d) const
319 320
    {
        /// We add 8 to avoid underflow at beginning of unix epoch.
321
        return (d + 8 - toDayOfWeek(d)) / 7;
322 323 324 325
    }

    inline unsigned toRelativeWeekNum(time_t t) const
    {
326
        return toRelativeWeekNum(toDayNum(t));
327 328
    }

329 330 331 332
    /// Get year that contains most of the current week. Week begins at monday.
    inline unsigned toISOYear(DayNum d) const
    {
        /// That's effectively the year of thursday of current week.
333
        return toYear(DayNum(d + 4 - toDayOfWeek(d)));
334 335 336 337 338 339 340
    }

    inline unsigned toISOYear(time_t t) const
    {
        return toISOYear(toDayNum(t));
    }

341 342 343
    /// ISO year begins with a monday of the week that is contained more than by half in the corresponding calendar year.
    /// Example: ISO year 2019 begins at 2018-12-31. And ISO year 2017 begins at 2017-01-02.
    /// https://en.wikipedia.org/wiki/ISO_week_date
344 345
    inline DayNum toFirstDayNumOfISOYear(DayNum d) const
    {
346 347 348
        auto iso_year = toISOYear(d);

        DayNum first_day_of_year = years_lut[iso_year - DATE_LUT_MIN_YEAR];
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
        auto first_day_of_week_of_year = lut[first_day_of_year].day_of_week;

        return DayNum(first_day_of_week_of_year <= 4
            ? first_day_of_year + 1 - first_day_of_week_of_year
            : first_day_of_year + 8 - first_day_of_week_of_year);
    }

    inline DayNum toFirstDayNumOfISOYear(time_t t) const
    {
        return toFirstDayNumOfISOYear(toDayNum(t));
    }

    inline time_t toFirstDayOfISOYear(time_t t) const
    {
        return fromDayNum(toFirstDayNumOfISOYear(t));
    }

    /// ISO 8601 week number. Week begins at monday.
    /// The week number 1 is the first week in year that contains 4 or more days (that's more than half).
    inline unsigned toISOWeek(DayNum d) const
    {
370
        return 1 + (toFirstDayNumOfWeek(d) - toFirstDayNumOfISOYear(d)) / 7;
371 372 373 374 375 376 377
    }

    inline unsigned toISOWeek(time_t t) const
    {
        return toISOWeek(toDayNum(t));
    }

378
    /// Number of month from some fixed moment in the past (year * 12 + month)
379
    inline unsigned toRelativeMonthNum(DayNum d) const
380 381 382 383 384 385
    {
        return lut[d].year * 12 + lut[d].month;
    }

    inline unsigned toRelativeMonthNum(time_t t) const
    {
386 387 388
        return toRelativeMonthNum(toDayNum(t));
    }

389
    inline unsigned toRelativeQuarterNum(DayNum d) const
390 391 392 393 394 395 396
    {
        return lut[d].year * 4 + (lut[d].month - 1) / 3;
    }

    inline unsigned toRelativeQuarterNum(time_t t) const
    {
        return toRelativeQuarterNum(toDayNum(t));
397 398
    }

399
    /// We count all hour-length intervals, unrelated to offset changes.
400 401 402 403 404 405
    inline time_t toRelativeHourNum(time_t t) const
    {
        if (offset_is_whole_number_of_hours_everytime)
            return t / 3600;

        /// Assume that if offset was fractional, then the fraction is the same as at the beginning of epoch.
406
        /// NOTE This assumption is false for "Pacific/Pitcairn" time zone.
407 408
        return (t + 86400 - offset_at_start_of_epoch) / 3600;
    }
409

410
    inline time_t toRelativeHourNum(DayNum d) const
411 412 413 414
    {
        return toRelativeHourNum(lut[d].date);
    }

415 416 417 418 419
    inline time_t toRelativeMinuteNum(time_t t) const
    {
        return t / 60;
    }

420
    inline time_t toRelativeMinuteNum(DayNum d) const
421 422 423 424
    {
        return toRelativeMinuteNum(lut[d].date);
    }

425 426
    /// Create DayNum from year, month, day of month.
    inline DayNum makeDayNum(UInt16 year, UInt8 month, UInt8 day_of_month) const
427 428
    {
        if (unlikely(year < DATE_LUT_MIN_YEAR || year > DATE_LUT_MAX_YEAR || month < 1 || month > 12 || day_of_month < 1 || day_of_month > 31))
429
            return DayNum(0);
430

431
        return DayNum(years_months_lut[(year - DATE_LUT_MIN_YEAR) * 12 + month - 1] + day_of_month - 1);
432 433
    }

434
    inline time_t makeDate(UInt16 year, UInt8 month, UInt8 day_of_month) const
435 436 437 438 439 440
    {
        return lut[makeDayNum(year, month, day_of_month)].date;
    }

    /** Does not accept daylight saving time as argument: in case of ambiguity, it choose greater timestamp.
      */
441
    inline time_t makeDateTime(UInt16 year, UInt8 month, UInt8 day_of_month, UInt8 hour, UInt8 minute, UInt8 second) const
442 443 444 445 446 447 448 449 450 451
    {
        size_t index = makeDayNum(year, month, day_of_month);
        time_t time_offset = hour * 3600 + minute * 60 + second;

        if (time_offset >= lut[index].time_at_offset_change)
            time_offset -= lut[index].amount_of_offset_change;

        return lut[index].date + time_offset;
    }

452
    inline const Values & getValues(DayNum d) const { return lut[d]; }
453 454
    inline const Values & getValues(time_t t) const { return lut[findIndex(t)]; }

455 456 457 458 459 460
    inline UInt32 toNumYYYYMM(time_t t) const
    {
        const Values & values = find(t);
        return values.year * 100 + values.month;
    }

461
    inline UInt32 toNumYYYYMM(DayNum d) const
462
    {
463
        const Values & values = lut[d];
464 465
        return values.year * 100 + values.month;
    }
466 467 468 469 470 471 472

    inline UInt32 toNumYYYYMMDD(time_t t) const
    {
        const Values & values = find(t);
        return values.year * 10000 + values.month * 100 + values.day_of_month;
    }

473
    inline UInt32 toNumYYYYMMDD(DayNum d) const
474
    {
475
        const Values & values = lut[d];
476 477 478 479 480 481 482 483
        return values.year * 10000 + values.month * 100 + values.day_of_month;
    }

    inline time_t YYYYMMDDToDate(UInt32 num) const
    {
        return makeDate(num / 10000, num / 100 % 100, num % 100);
    }

484
    inline DayNum YYYYMMDDToDayNum(UInt32 num) const
485 486 487 488 489 490 491 492 493
    {
        return makeDayNum(num / 10000, num / 100 % 100, num % 100);
    }


    inline UInt64 toNumYYYYMMDDhhmmss(time_t t) const
    {
        const Values & values = find(t);
        return
494 495
              toSecond(t)
            + toMinute(t) * 100
496 497 498 499
            + toHour(t) * 10000
            + UInt64(values.day_of_month) * 1000000
            + UInt64(values.month) * 100000000
            + UInt64(values.year) * 10000000000;
500 501 502 503 504 505 506 507 508 509 510 511 512
    }

    inline time_t YYYYMMDDhhmmssToTime(UInt64 num) const
    {
        return makeDateTime(
            num / 10000000000,
            num / 100000000 % 100,
            num / 1000000 % 100,
            num / 10000 % 100,
            num / 100 % 100,
            num % 100);
    }

513 514 515
    /// Adding calendar intervals.
    /// Implementation specific behaviour when delta is too big.

516
    inline time_t addDays(time_t t, Int64 delta) const
517 518 519 520 521 522 523 524 525 526 527 528
    {
        size_t index = findIndex(t);
        time_t time_offset = toHour(t) * 3600 + toMinute(t) * 60 + toSecond(t);

        index += delta;

        if (time_offset >= lut[index].time_at_offset_change)
            time_offset -= lut[index].amount_of_offset_change;

        return lut[index].date + time_offset;
    }

529
    inline time_t addWeeks(time_t t, Int64 delta) const
530 531 532 533
    {
        return addDays(t, delta * 7);
    }

534
    inline UInt8 saturateDayOfMonth(UInt16 year, UInt8 month, UInt8 day_of_month) const
535 536 537 538
    {
        if (likely(day_of_month <= 28))
            return day_of_month;

539
        UInt8 days_in_month = daysInMonth(year, month);
540 541 542 543 544 545 546 547 548

        if (day_of_month > days_in_month)
            day_of_month = days_in_month;

        return day_of_month;
    }

    /// If resulting month has less deys than source month, then saturation can happen.
    /// Example: 31 Aug + 1 month = 30 Sep.
549
    inline time_t addMonths(time_t t, Int64 delta) const
550
    {
551
        DayNum result_day = addMonths(toDayNum(t), delta);
552 553 554 555 556 557 558 559 560

        time_t time_offset = toHour(t) * 3600 + toMinute(t) * 60 + toSecond(t);

        if (time_offset >= lut[result_day].time_at_offset_change)
            time_offset -= lut[result_day].amount_of_offset_change;

        return lut[result_day].date + time_offset;
    }

561
    inline DayNum addMonths(DayNum d, Int64 delta) const
562 563 564
    {
        const Values & values = lut[d];

565 566 567 568 569 570 571
        Int64 month = static_cast<Int64>(values.month) + delta;

        if (month > 0)
        {
            auto year = values.year + (month - 1) / 12;
            month = ((month - 1) % 12) + 1;
            auto day_of_month = saturateDayOfMonth(year, month, values.day_of_month);
572

573 574 575 576 577 578 579 580 581 582
            return makeDayNum(year, month, day_of_month);
        }
        else
        {
            auto year = values.year - (12 - month) / 12;
            month = 12 - (-month % 12);
            auto day_of_month = saturateDayOfMonth(year, month, values.day_of_month);

            return makeDayNum(year, month, day_of_month);
        }
583 584 585
    }

    /// Saturation can occur if 29 Feb is mapped to non-leap year.
586
    inline time_t addYears(time_t t, Int64 delta) const
587
    {
588
        DayNum result_day = addYears(toDayNum(t), delta);
589 590 591 592 593 594 595 596 597

        time_t time_offset = toHour(t) * 3600 + toMinute(t) * 60 + toSecond(t);

        if (time_offset >= lut[result_day].time_at_offset_change)
            time_offset -= lut[result_day].amount_of_offset_change;

        return lut[result_day].date + time_offset;
    }

598
    inline DayNum addYears(DayNum d, Int64 delta) const
599 600 601 602 603 604 605 606 607 608 609 610 611 612 613
    {
        const Values & values = lut[d];

        auto year = values.year + delta;
        auto month = values.month;
        auto day_of_month = values.day_of_month;

        /// Saturation to 28 Feb can happen.
        if (unlikely(day_of_month == 29 && month == 2))
            day_of_month = saturateDayOfMonth(year, month, day_of_month);

        return makeDayNum(year, month, day_of_month);
    }


614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629
    inline std::string timeToString(time_t t) const
    {
        const Values & values = find(t);

        std::string s {"0000-00-00 00:00:00"};

        s[0] += values.year / 1000;
        s[1] += (values.year / 100) % 10;
        s[2] += (values.year / 10) % 10;
        s[3] += values.year % 10;
        s[5] += values.month / 10;
        s[6] += values.month % 10;
        s[8] += values.day_of_month / 10;
        s[9] += values.day_of_month % 10;

        auto hour = toHour(t);
630 631
        auto minute = toMinute(t);
        auto second = toSecond(t);
632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660

        s[11] += hour / 10;
        s[12] += hour % 10;
        s[14] += minute / 10;
        s[15] += minute % 10;
        s[17] += second / 10;
        s[18] += second % 10;

        return s;
    }

    inline std::string dateToString(time_t t) const
    {
        const Values & values = find(t);

        std::string s {"0000-00-00"};

        s[0] += values.year / 1000;
        s[1] += (values.year / 100) % 10;
        s[2] += (values.year / 10) % 10;
        s[3] += values.year % 10;
        s[5] += values.month / 10;
        s[6] += values.month % 10;
        s[8] += values.day_of_month / 10;
        s[9] += values.day_of_month % 10;

        return s;
    }

661
    inline std::string dateToString(DayNum d) const
662
    {
663
        const Values & values = lut[d];
664 665 666 667 668 669 670 671 672 673 674 675 676 677

        std::string s {"0000-00-00"};

        s[0] += values.year / 1000;
        s[1] += (values.year / 100) % 10;
        s[2] += (values.year / 10) % 10;
        s[3] += values.year % 10;
        s[5] += values.month / 10;
        s[6] += values.month % 10;
        s[8] += values.day_of_month / 10;
        s[9] += values.day_of_month % 10;

        return s;
    }
678 679

    inline bool isOffsetWholeNumberOfHoursEveryTime() const { return offset_is_whole_number_of_hours_everytime; }
680
};