1#pragma once
2
3#include "Types.h"
4#include "DayNum.h"
5#include "likely.h"
6#include <ctime>
7#include <string>
8
9#define DATE_LUT_MAX (0xFFFFFFFFU - 86400)
10#define DATE_LUT_MAX_DAY_NUM (0xFFFFFFFFU / 86400)
11/// Table size is bigger than DATE_LUT_MAX_DAY_NUM to fill all indices within UInt16 range: this allows to remove extra check.
12#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#if defined(__PPC__)
18#if !__clang__
19#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
20#endif
21#endif
22
23/// Flags for toYearWeek() function.
24enum class WeekModeFlag : UInt8
25{
26 MONDAY_FIRST = 1,
27 YEAR = 2,
28 FIRST_WEEKDAY = 4,
29 NEWYEAR_DAY = 8
30};
31using YearWeek = std::pair<UInt16, UInt8>;
32
33/** Lookup table to conversion of time to date, and to month / year / day of week / day of month and so on.
34 * First time was implemented for OLAPServer, that needed to do billions of such transformations.
35 */
36class DateLUTImpl
37{
38public:
39 DateLUTImpl(const std::string & time_zone);
40
41public:
42 /// The order of fields matters for alignment and sizeof.
43 struct Values
44 {
45 /// Least significat 32 bits from time_t at beginning of the day.
46 /// If the unix timestamp of beginning of the day is negative (example: 1970-01-01 MSK, where time_t == -10800), then value is zero.
47 /// Change to time_t; change constants above; and recompile the sources if you need to support time after 2105 year.
48 UInt32 date;
49
50 /// Properties of the day.
51 UInt16 year;
52 UInt8 month;
53 UInt8 day_of_month;
54 UInt8 day_of_week;
55
56 /// Total number of days in current month. Actually we can use separate table that is independent of time zone.
57 /// But due to alignment, this field is totally zero cost.
58 UInt8 days_in_month;
59
60 /// For days, when offset from UTC was changed due to daylight saving time or permanent change, following values could be non zero.
61 Int16 amount_of_offset_change; /// Usually -3600 or 3600, but look at Lord Howe Island.
62 UInt32 time_at_offset_change; /// In seconds from beginning of the day.
63 };
64
65 static_assert(sizeof(Values) == 16);
66
67private:
68 /// Lookup table is indexed by DayNum.
69 /// Day nums are the same in all time zones. 1970-01-01 is 0 and so on.
70 /// Table is relatively large, so better not to place the object on stack.
71 /// In comparison to std::vector, plain array is cheaper by one indirection.
72 Values lut[DATE_LUT_SIZE];
73
74 /// Year number after DATE_LUT_MIN_YEAR -> day num for start of year.
75 DayNum years_lut[DATE_LUT_YEARS];
76
77 /// Year number after DATE_LUT_MIN_YEAR * month number starting at zero -> day num for first day of month
78 DayNum years_months_lut[DATE_LUT_YEARS * 12];
79
80 /// UTC offset at beginning of the Unix epoch. The same as unix timestamp of 1970-01-01 00:00:00 local time.
81 time_t offset_at_start_of_epoch;
82 bool offset_is_whole_number_of_hours_everytime;
83
84 /// Time zone name.
85 std::string time_zone;
86
87
88 /// We can correctly process only timestamps that less DATE_LUT_MAX (i.e. up to 2105 year inclusively)
89 /// We don't care about overflow.
90 inline DayNum findIndex(time_t t) const
91 {
92 /// First guess.
93 DayNum guess(t / 86400);
94
95 /// UTC offset is from -12 to +14 in all known time zones. This requires checking only three indices.
96
97 if ((guess == 0 || t >= lut[guess].date) && t < lut[DayNum(guess + 1)].date)
98 return guess;
99
100 /// Time zones that have offset 0 from UTC do daylight saving time change (if any) towards increasing UTC offset (example: British Standard Time).
101 if (offset_at_start_of_epoch >= 0)
102 return DayNum(guess + 1);
103
104 return DayNum(guess - 1);
105 }
106
107 inline const Values & find(time_t t) const
108 {
109 return lut[findIndex(t)];
110 }
111
112public:
113 const std::string & getTimeZone() const { return time_zone; }
114
115 /// All functions below are thread-safe; arguments are not checked.
116
117 inline time_t toDate(time_t t) const { return find(t).date; }
118 inline unsigned toMonth(time_t t) const { return find(t).month; }
119 inline unsigned toQuarter(time_t t) const { return (find(t).month - 1) / 3 + 1; }
120 inline unsigned toYear(time_t t) const { return find(t).year; }
121 inline unsigned toDayOfWeek(time_t t) const { return find(t).day_of_week; }
122 inline unsigned toDayOfMonth(time_t t) const { return find(t).day_of_month; }
123
124 /// Round down to start of monday.
125 inline time_t toFirstDayOfWeek(time_t t) const
126 {
127 DayNum index = findIndex(t);
128 return lut[DayNum(index - (lut[index].day_of_week - 1))].date;
129 }
130
131 inline DayNum toFirstDayNumOfWeek(DayNum d) const
132 {
133 return DayNum(d - (lut[d].day_of_week - 1));
134 }
135
136 inline DayNum toFirstDayNumOfWeek(time_t t) const
137 {
138 return toFirstDayNumOfWeek(toDayNum(t));
139 }
140
141 /// Round down to start of month.
142 inline time_t toFirstDayOfMonth(time_t t) const
143 {
144 DayNum index = findIndex(t);
145 return lut[index - (lut[index].day_of_month - 1)].date;
146 }
147
148 inline DayNum toFirstDayNumOfMonth(DayNum d) const
149 {
150 return DayNum(d - (lut[d].day_of_month - 1));
151 }
152
153 inline DayNum toFirstDayNumOfMonth(time_t t) const
154 {
155 return toFirstDayNumOfMonth(toDayNum(t));
156 }
157
158 /// Round down to start of quarter.
159 inline DayNum toFirstDayNumOfQuarter(DayNum d) const
160 {
161 DayNum index = d;
162 size_t month_inside_quarter = (lut[index].month - 1) % 3;
163
164 index -= lut[index].day_of_month;
165 while (month_inside_quarter)
166 {
167 index -= lut[index].day_of_month;
168 --month_inside_quarter;
169 }
170
171 return DayNum(index + 1);
172 }
173
174 inline DayNum toFirstDayNumOfQuarter(time_t t) const
175 {
176 return toFirstDayNumOfQuarter(toDayNum(t));
177 }
178
179 inline time_t toFirstDayOfQuarter(time_t t) const
180 {
181 return fromDayNum(toFirstDayNumOfQuarter(t));
182 }
183
184 /// Round down to start of year.
185 inline time_t toFirstDayOfYear(time_t t) const
186 {
187 return lut[years_lut[lut[findIndex(t)].year - DATE_LUT_MIN_YEAR]].date;
188 }
189
190 inline DayNum toFirstDayNumOfYear(DayNum d) const
191 {
192 return years_lut[lut[d].year - DATE_LUT_MIN_YEAR];
193 }
194
195 inline DayNum toFirstDayNumOfYear(time_t t) const
196 {
197 return toFirstDayNumOfYear(toDayNum(t));
198 }
199
200 inline time_t toFirstDayOfNextMonth(time_t t) const
201 {
202 DayNum index = findIndex(t);
203 index += 32 - lut[index].day_of_month;
204 return lut[index - (lut[index].day_of_month - 1)].date;
205 }
206
207 inline time_t toFirstDayOfPrevMonth(time_t t) const
208 {
209 DayNum index = findIndex(t);
210 index -= lut[index].day_of_month;
211 return lut[index - (lut[index].day_of_month - 1)].date;
212 }
213
214 inline UInt8 daysInMonth(DayNum d) const
215 {
216 return lut[d].days_in_month;
217 }
218
219 inline UInt8 daysInMonth(time_t t) const
220 {
221 return find(t).days_in_month;
222 }
223
224 inline UInt8 daysInMonth(UInt16 year, UInt8 month) const
225 {
226 /// 32 makes arithmetic more simple.
227 DayNum any_day_of_month = DayNum(years_lut[year - DATE_LUT_MIN_YEAR] + 32 * (month - 1));
228 return lut[any_day_of_month].days_in_month;
229 }
230
231 /** Round to start of day, then shift for specified amount of days.
232 */
233 inline time_t toDateAndShift(time_t t, Int32 days) const
234 {
235 return lut[DayNum(findIndex(t) + days)].date;
236 }
237
238 inline time_t toTime(time_t t) const
239 {
240 DayNum index = findIndex(t);
241
242 if (unlikely(index == 0))
243 return t + offset_at_start_of_epoch;
244
245 time_t res = t - lut[index].date;
246
247 if (res >= lut[index].time_at_offset_change)
248 res += lut[index].amount_of_offset_change;
249
250 return res - offset_at_start_of_epoch; /// Starting at 1970-01-01 00:00:00 local time.
251 }
252
253 inline unsigned toHour(time_t t) const
254 {
255 DayNum index = findIndex(t);
256
257 /// If it is not 1970 year (findIndex found nothing appropriate),
258 /// than limit number of hours to avoid insane results like 1970-01-01 89:28:15
259 if (unlikely(index == 0))
260 return static_cast<unsigned>((t + offset_at_start_of_epoch) / 3600) % 24;
261
262 time_t res = t - lut[index].date;
263
264 /// Data is cleaned to avoid possibility of underflow.
265 if (res >= lut[index].time_at_offset_change)
266 res += lut[index].amount_of_offset_change;
267
268 return res / 3600;
269 }
270
271 /** Only for time zones with/when offset from UTC is multiple of five minutes.
272 * This is true for all time zones: right now, all time zones have an offset that is multiple of 15 minutes.
273 *
274 * "By 1929, most major countries had adopted hourly time zones. Nepal was the last
275 * country to adopt a standard offset, shifting slightly to UTC+5:45 in 1986."
276 * - https://en.wikipedia.org/wiki/Time_zone#Offsets_from_UTC
277 *
278 * Also please note, that unix timestamp doesn't count "leap seconds":
279 * each minute, with added or subtracted leap second, spans exactly 60 unix timestamps.
280 */
281
282 inline unsigned toSecond(time_t t) const { return t % 60; }
283
284 inline unsigned toMinute(time_t t) const
285 {
286 if (offset_is_whole_number_of_hours_everytime)
287 return (t / 60) % 60;
288
289 time_t date = find(t).date;
290 return (t - date) / 60 % 60;
291 }
292
293 inline time_t toStartOfMinute(time_t t) const { return t / 60 * 60; }
294 inline time_t toStartOfFiveMinute(time_t t) const { return t / 300 * 300; }
295 inline time_t toStartOfFifteenMinutes(time_t t) const { return t / 900 * 900; }
296 inline time_t toStartOfTenMinutes(time_t t) const { return t / 600 * 600; }
297
298 inline time_t toStartOfHour(time_t t) const
299 {
300 if (offset_is_whole_number_of_hours_everytime)
301 return t / 3600 * 3600;
302
303 time_t date = find(t).date;
304 /// Still can return wrong values for time at 1970-01-01 if the UTC offset was non-whole number of hours.
305 return date + (t - date) / 3600 * 3600;
306 }
307
308 /** Number of calendar day since the beginning of UNIX epoch (1970-01-01 is zero)
309 * We use just two bytes for it. It covers the range up to 2105 and slightly more.
310 *
311 * This is "calendar" day, it itself is independent of time zone
312 * (conversion from/to unix timestamp will depend on time zone,
313 * because the same calendar day starts/ends at different timestamps in different time zones)
314 */
315
316 inline DayNum toDayNum(time_t t) const { return findIndex(t); }
317 inline time_t fromDayNum(DayNum d) const { return lut[d].date; }
318
319 inline time_t toDate(DayNum d) const { return lut[d].date; }
320 inline unsigned toMonth(DayNum d) const { return lut[d].month; }
321 inline unsigned toQuarter(DayNum d) const { return (lut[d].month - 1) / 3 + 1; }
322 inline unsigned toYear(DayNum d) const { return lut[d].year; }
323 inline unsigned toDayOfWeek(DayNum d) const { return lut[d].day_of_week; }
324 inline unsigned toDayOfMonth(DayNum d) const { return lut[d].day_of_month; }
325 inline unsigned toDayOfYear(DayNum d) const { return d + 1 - toFirstDayNumOfYear(d); }
326
327 inline unsigned toDayOfYear(time_t t) const { return toDayOfYear(toDayNum(t)); }
328
329 /// Number of week from some fixed moment in the past. Week begins at monday.
330 /// (round down to monday and divide DayNum by 7; we made an assumption,
331 /// that in domain of the function there was no weeks with any other number of days than 7)
332 inline unsigned toRelativeWeekNum(DayNum d) const
333 {
334 /// We add 8 to avoid underflow at beginning of unix epoch.
335 return (d + 8 - toDayOfWeek(d)) / 7;
336 }
337
338 inline unsigned toRelativeWeekNum(time_t t) const
339 {
340 return toRelativeWeekNum(toDayNum(t));
341 }
342
343 /// Get year that contains most of the current week. Week begins at monday.
344 inline unsigned toISOYear(DayNum d) const
345 {
346 /// That's effectively the year of thursday of current week.
347 return toYear(DayNum(d + 4 - toDayOfWeek(d)));
348 }
349
350 inline unsigned toISOYear(time_t t) const
351 {
352 return toISOYear(toDayNum(t));
353 }
354
355 /// ISO year begins with a monday of the week that is contained more than by half in the corresponding calendar year.
356 /// Example: ISO year 2019 begins at 2018-12-31. And ISO year 2017 begins at 2017-01-02.
357 /// https://en.wikipedia.org/wiki/ISO_week_date
358 inline DayNum toFirstDayNumOfISOYear(DayNum d) const
359 {
360 auto iso_year = toISOYear(d);
361
362 DayNum first_day_of_year = years_lut[iso_year - DATE_LUT_MIN_YEAR];
363 auto first_day_of_week_of_year = lut[first_day_of_year].day_of_week;
364
365 return DayNum(first_day_of_week_of_year <= 4
366 ? first_day_of_year + 1 - first_day_of_week_of_year
367 : first_day_of_year + 8 - first_day_of_week_of_year);
368 }
369
370 inline DayNum toFirstDayNumOfISOYear(time_t t) const
371 {
372 return toFirstDayNumOfISOYear(toDayNum(t));
373 }
374
375 inline time_t toFirstDayOfISOYear(time_t t) const
376 {
377 return fromDayNum(toFirstDayNumOfISOYear(t));
378 }
379
380 /// ISO 8601 week number. Week begins at monday.
381 /// The week number 1 is the first week in year that contains 4 or more days (that's more than half).
382 inline unsigned toISOWeek(DayNum d) const
383 {
384 return 1 + DayNum(toFirstDayNumOfWeek(d) - toFirstDayNumOfISOYear(d)) / 7;
385 }
386
387 inline unsigned toISOWeek(time_t t) const
388 {
389 return toISOWeek(toDayNum(t));
390 }
391
392 /*
393 The bits in week_mode has the following meaning:
394 WeekModeFlag::MONDAY_FIRST (0) If not set Sunday is first day of week
395 If set Monday is first day of week
396 WeekModeFlag::YEAR (1) If not set Week is in range 0-53
397
398 Week 0 is returned for the the last week of the previous year (for
399 a date at start of january) In this case one can get 53 for the
400 first week of next year. This flag ensures that the week is
401 relevant for the given year. Note that this flag is only
402 releveant if WeekModeFlag::JANUARY is not set.
403
404 If set Week is in range 1-53.
405
406 In this case one may get week 53 for a date in January (when
407 the week is that last week of previous year) and week 1 for a
408 date in December.
409
410 WeekModeFlag::FIRST_WEEKDAY (2) If not set Weeks are numbered according
411 to ISO 8601:1988
412 If set The week that contains the first
413 'first-day-of-week' is week 1.
414
415 WeekModeFlag::NEWYEAR_DAY (3) If not set no meaning
416 If set The week that contains the January 1 is week 1.
417 Week is in range 1-53.
418 And ignore WeekModeFlag::YEAR, WeekModeFlag::FIRST_WEEKDAY
419
420 ISO 8601:1988 means that if the week containing January 1 has
421 four or more days in the new year, then it is week 1;
422 Otherwise it is the last week of the previous year, and the
423 next week is week 1.
424 */
425 inline YearWeek toYearWeek(DayNum d, UInt8 week_mode) const
426 {
427 bool newyear_day_mode = week_mode & static_cast<UInt8>(WeekModeFlag::NEWYEAR_DAY);
428 week_mode = check_week_mode(week_mode);
429 bool monday_first_mode = week_mode & static_cast<UInt8>(WeekModeFlag::MONDAY_FIRST);
430 bool week_year_mode = week_mode & static_cast<UInt8>(WeekModeFlag::YEAR);
431 bool first_weekday_mode = week_mode & static_cast<UInt8>(WeekModeFlag::FIRST_WEEKDAY);
432
433 // Calculate week number of WeekModeFlag::NEWYEAR_DAY mode
434 if (newyear_day_mode)
435 {
436 return toYearWeekOfNewyearMode(d, monday_first_mode);
437 }
438
439 YearWeek yw(toYear(d), 0);
440 UInt16 days = 0;
441 UInt16 daynr = makeDayNum(yw.first, toMonth(d), toDayOfMonth(d));
442 UInt16 first_daynr = makeDayNum(yw.first, 1, 1);
443
444 // 0 for monday, 1 for tuesday ...
445 // get weekday from first day in year.
446 UInt16 weekday = calc_weekday(DayNum(first_daynr), !monday_first_mode);
447
448 if (toMonth(d) == 1 && toDayOfMonth(d) <= static_cast<UInt32>(7 - weekday))
449 {
450 if (!week_year_mode && ((first_weekday_mode && weekday != 0) || (!first_weekday_mode && weekday >= 4)))
451 return yw;
452 week_year_mode = 1;
453 (yw.first)--;
454 first_daynr -= (days = calc_days_in_year(yw.first));
455 weekday = (weekday + 53 * 7 - days) % 7;
456 }
457
458 if ((first_weekday_mode && weekday != 0) || (!first_weekday_mode && weekday >= 4))
459 days = daynr - (first_daynr + (7 - weekday));
460 else
461 days = daynr - (first_daynr - weekday);
462
463 if (week_year_mode && days >= 52 * 7)
464 {
465 weekday = (weekday + calc_days_in_year(yw.first)) % 7;
466 if ((!first_weekday_mode && weekday < 4) || (first_weekday_mode && weekday == 0))
467 {
468 (yw.first)++;
469 yw.second = 1;
470 return yw;
471 }
472 }
473 yw.second = days / 7 + 1;
474 return yw;
475 }
476
477 /// Calculate week number of WeekModeFlag::NEWYEAR_DAY mode
478 /// The week number 1 is the first week in year that contains January 1,
479 inline YearWeek toYearWeekOfNewyearMode(DayNum d, bool monday_first_mode) const
480 {
481 YearWeek yw(0, 0);
482 UInt16 offset_day = monday_first_mode ? 0U : 1U;
483
484 // Checking the week across the year
485 yw.first = toYear(DayNum(d + 7 - toDayOfWeek(DayNum(d + offset_day))));
486
487 DayNum first_day = makeDayNum(yw.first, 1, 1);
488 DayNum this_day = d;
489
490 if (monday_first_mode)
491 {
492 // Rounds down a date to the nearest Monday.
493 first_day = toFirstDayNumOfWeek(first_day);
494 this_day = toFirstDayNumOfWeek(d);
495 }
496 else
497 {
498 // Rounds down a date to the nearest Sunday.
499 if (toDayOfWeek(first_day) != 7)
500 first_day = DayNum(first_day - toDayOfWeek(first_day));
501 if (toDayOfWeek(d) != 7)
502 this_day = DayNum(d - toDayOfWeek(d));
503 }
504 yw.second = (this_day - first_day) / 7 + 1;
505 return yw;
506 }
507
508 /**
509 * get first day of week with week_mode, return Sunday or Monday
510 */
511 inline DayNum toFirstDayNumOfWeek(DayNum d, UInt8 week_mode) const
512 {
513 bool monday_first_mode = week_mode & static_cast<UInt8>(WeekModeFlag::MONDAY_FIRST);
514 if (monday_first_mode)
515 {
516 return toFirstDayNumOfWeek(d);
517 }
518 else
519 {
520 return (toDayOfWeek(d) != 7) ? DayNum(d - toDayOfWeek(d)) : d;
521 }
522 }
523
524 /*
525 * check and change mode to effective
526 */
527 inline UInt8 check_week_mode(UInt8 mode) const
528 {
529 UInt8 week_format = (mode & 7);
530 if (!(week_format & static_cast<UInt8>(WeekModeFlag::MONDAY_FIRST)))
531 week_format ^= static_cast<UInt8>(WeekModeFlag::FIRST_WEEKDAY);
532 return week_format;
533 }
534
535 /*
536 * Calc weekday from d
537 * Returns 0 for monday, 1 for tuesday ...
538 */
539 inline unsigned calc_weekday(DayNum d, bool sunday_first_day_of_week) const
540 {
541 if (!sunday_first_day_of_week)
542 return toDayOfWeek(d) - 1;
543 else
544 return toDayOfWeek(DayNum(d + 1)) - 1;
545 }
546
547 /* Calc days in one year. */
548 inline unsigned calc_days_in_year(UInt16 year) const
549 {
550 return ((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)) ? 366 : 365);
551 }
552
553 /// Number of month from some fixed moment in the past (year * 12 + month)
554 inline unsigned toRelativeMonthNum(DayNum d) const
555 {
556 return lut[d].year * 12 + lut[d].month;
557 }
558
559 inline unsigned toRelativeMonthNum(time_t t) const
560 {
561 return toRelativeMonthNum(toDayNum(t));
562 }
563
564 inline unsigned toRelativeQuarterNum(DayNum d) const
565 {
566 return lut[d].year * 4 + (lut[d].month - 1) / 3;
567 }
568
569 inline unsigned toRelativeQuarterNum(time_t t) const
570 {
571 return toRelativeQuarterNum(toDayNum(t));
572 }
573
574 /// We count all hour-length intervals, unrelated to offset changes.
575 inline time_t toRelativeHourNum(time_t t) const
576 {
577 if (offset_is_whole_number_of_hours_everytime)
578 return t / 3600;
579
580 /// Assume that if offset was fractional, then the fraction is the same as at the beginning of epoch.
581 /// NOTE This assumption is false for "Pacific/Pitcairn" time zone.
582 return (t + 86400 - offset_at_start_of_epoch) / 3600;
583 }
584
585 inline time_t toRelativeHourNum(DayNum d) const
586 {
587 return toRelativeHourNum(lut[d].date);
588 }
589
590 inline time_t toRelativeMinuteNum(time_t t) const
591 {
592 return t / 60;
593 }
594
595 inline time_t toRelativeMinuteNum(DayNum d) const
596 {
597 return toRelativeMinuteNum(lut[d].date);
598 }
599
600 inline DayNum toStartOfYearInterval(DayNum d, UInt64 years) const
601 {
602 if (years == 1)
603 return toFirstDayNumOfYear(d);
604 return years_lut[(lut[d].year - DATE_LUT_MIN_YEAR) / years * years];
605 }
606
607 inline DayNum toStartOfQuarterInterval(DayNum d, UInt64 quarters) const
608 {
609 if (quarters == 1)
610 return toFirstDayNumOfQuarter(d);
611 return toStartOfMonthInterval(d, quarters * 3);
612 }
613
614 inline DayNum toStartOfMonthInterval(DayNum d, UInt64 months) const
615 {
616 if (months == 1)
617 return toFirstDayNumOfMonth(d);
618 const auto & date = lut[d];
619 UInt32 month_total_index = (date.year - DATE_LUT_MIN_YEAR) * 12 + date.month - 1;
620 return years_months_lut[month_total_index / months * months];
621 }
622
623 inline DayNum toStartOfWeekInterval(DayNum d, UInt64 weeks) const
624 {
625 if (weeks == 1)
626 return toFirstDayNumOfWeek(d);
627 UInt64 days = weeks * 7;
628 // January 1st 1970 was Thursday so we need this 4-days offset to make weeks start on Monday.
629 return DayNum(4 + (d - 4) / days * days);
630 }
631
632 inline time_t toStartOfDayInterval(DayNum d, UInt64 days) const
633 {
634 if (days == 1)
635 return toDate(d);
636 return lut[d / days * days].date;
637 }
638
639 inline time_t toStartOfHourInterval(time_t t, UInt64 hours) const
640 {
641 if (hours == 1)
642 return toStartOfHour(t);
643 UInt64 seconds = hours * 3600;
644 t = t / seconds * seconds;
645 if (offset_is_whole_number_of_hours_everytime)
646 return t;
647 return toStartOfHour(t);
648 }
649
650 inline time_t toStartOfMinuteInterval(time_t t, UInt64 minutes) const
651 {
652 if (minutes == 1)
653 return toStartOfMinute(t);
654 UInt64 seconds = 60 * minutes;
655 return t / seconds * seconds;
656 }
657
658 inline time_t toStartOfSecondInterval(time_t t, UInt64 seconds) const
659 {
660 if (seconds == 1)
661 return t;
662 return t / seconds * seconds;
663 }
664
665 /// Create DayNum from year, month, day of month.
666 inline DayNum makeDayNum(UInt16 year, UInt8 month, UInt8 day_of_month) const
667 {
668 if (unlikely(year < DATE_LUT_MIN_YEAR || year > DATE_LUT_MAX_YEAR || month < 1 || month > 12 || day_of_month < 1 || day_of_month > 31))
669 return DayNum(0); // TODO (nemkov, DateTime64 phase 2): implement creating real date for year outside of LUT range.
670
671 return DayNum(years_months_lut[(year - DATE_LUT_MIN_YEAR) * 12 + month - 1] + day_of_month - 1);
672 }
673
674 inline time_t makeDate(UInt16 year, UInt8 month, UInt8 day_of_month) const
675 {
676 return lut[makeDayNum(year, month, day_of_month)].date;
677 }
678
679 /** Does not accept daylight saving time as argument: in case of ambiguity, it choose greater timestamp.
680 */
681 inline time_t makeDateTime(UInt16 year, UInt8 month, UInt8 day_of_month, UInt8 hour, UInt8 minute, UInt8 second) const
682 {
683 size_t index = makeDayNum(year, month, day_of_month);
684 time_t time_offset = hour * 3600 + minute * 60 + second;
685
686 if (time_offset >= lut[index].time_at_offset_change)
687 time_offset -= lut[index].amount_of_offset_change;
688
689 return lut[index].date + time_offset;
690 }
691
692 inline const Values & getValues(DayNum d) const { return lut[d]; }
693 inline const Values & getValues(time_t t) const { return lut[findIndex(t)]; }
694
695 inline UInt32 toNumYYYYMM(time_t t) const
696 {
697 const Values & values = find(t);
698 return values.year * 100 + values.month;
699 }
700
701 inline UInt32 toNumYYYYMM(DayNum d) const
702 {
703 const Values & values = lut[d];
704 return values.year * 100 + values.month;
705 }
706
707 inline UInt32 toNumYYYYMMDD(time_t t) const
708 {
709 const Values & values = find(t);
710 return values.year * 10000 + values.month * 100 + values.day_of_month;
711 }
712
713 inline UInt32 toNumYYYYMMDD(DayNum d) const
714 {
715 const Values & values = lut[d];
716 return values.year * 10000 + values.month * 100 + values.day_of_month;
717 }
718
719 inline time_t YYYYMMDDToDate(UInt32 num) const
720 {
721 return makeDate(num / 10000, num / 100 % 100, num % 100);
722 }
723
724 inline DayNum YYYYMMDDToDayNum(UInt32 num) const
725 {
726 return makeDayNum(num / 10000, num / 100 % 100, num % 100);
727 }
728
729
730 inline UInt64 toNumYYYYMMDDhhmmss(time_t t) const
731 {
732 const Values & values = find(t);
733 return
734 toSecond(t)
735 + toMinute(t) * 100
736 + toHour(t) * 10000
737 + UInt64(values.day_of_month) * 1000000
738 + UInt64(values.month) * 100000000
739 + UInt64(values.year) * 10000000000;
740 }
741
742 inline time_t YYYYMMDDhhmmssToTime(UInt64 num) const
743 {
744 return makeDateTime(
745 num / 10000000000,
746 num / 100000000 % 100,
747 num / 1000000 % 100,
748 num / 10000 % 100,
749 num / 100 % 100,
750 num % 100);
751 }
752
753 /// Adding calendar intervals.
754 /// Implementation specific behaviour when delta is too big.
755
756 inline time_t addDays(time_t t, Int64 delta) const
757 {
758 DayNum index = findIndex(t);
759 time_t time_offset = toHour(t) * 3600 + toMinute(t) * 60 + toSecond(t);
760
761 index += delta;
762
763 if (time_offset >= lut[index].time_at_offset_change)
764 time_offset -= lut[index].amount_of_offset_change;
765
766 return lut[index].date + time_offset;
767 }
768
769 inline time_t addWeeks(time_t t, Int64 delta) const
770 {
771 return addDays(t, delta * 7);
772 }
773
774 inline UInt8 saturateDayOfMonth(UInt16 year, UInt8 month, UInt8 day_of_month) const
775 {
776 if (likely(day_of_month <= 28))
777 return day_of_month;
778
779 UInt8 days_in_month = daysInMonth(year, month);
780
781 if (day_of_month > days_in_month)
782 day_of_month = days_in_month;
783
784 return day_of_month;
785 }
786
787 /// If resulting month has less deys than source month, then saturation can happen.
788 /// Example: 31 Aug + 1 month = 30 Sep.
789 inline time_t addMonths(time_t t, Int64 delta) const
790 {
791 DayNum result_day = addMonths(toDayNum(t), delta);
792
793 time_t time_offset = toHour(t) * 3600 + toMinute(t) * 60 + toSecond(t);
794
795 if (time_offset >= lut[result_day].time_at_offset_change)
796 time_offset -= lut[result_day].amount_of_offset_change;
797
798 return lut[result_day].date + time_offset;
799 }
800
801 inline DayNum addMonths(DayNum d, Int64 delta) const
802 {
803 const Values & values = lut[d];
804
805 Int64 month = static_cast<Int64>(values.month) + delta;
806
807 if (month > 0)
808 {
809 auto year = values.year + (month - 1) / 12;
810 month = ((month - 1) % 12) + 1;
811 auto day_of_month = saturateDayOfMonth(year, month, values.day_of_month);
812
813 return makeDayNum(year, month, day_of_month);
814 }
815 else
816 {
817 auto year = values.year - (12 - month) / 12;
818 month = 12 - (-month % 12);
819 auto day_of_month = saturateDayOfMonth(year, month, values.day_of_month);
820
821 return makeDayNum(year, month, day_of_month);
822 }
823 }
824
825 inline time_t addQuarters(time_t t, Int64 delta) const
826 {
827 return addMonths(t, delta * 3);
828 }
829
830 inline DayNum addQuarters(DayNum d, Int64 delta) const
831 {
832 return addMonths(d, delta * 3);
833 }
834
835 /// Saturation can occur if 29 Feb is mapped to non-leap year.
836 inline time_t addYears(time_t t, Int64 delta) const
837 {
838 DayNum result_day = addYears(toDayNum(t), delta);
839
840 time_t time_offset = toHour(t) * 3600 + toMinute(t) * 60 + toSecond(t);
841
842 if (time_offset >= lut[result_day].time_at_offset_change)
843 time_offset -= lut[result_day].amount_of_offset_change;
844
845 return lut[result_day].date + time_offset;
846 }
847
848 inline DayNum addYears(DayNum d, Int64 delta) const
849 {
850 const Values & values = lut[d];
851
852 auto year = values.year + delta;
853 auto month = values.month;
854 auto day_of_month = values.day_of_month;
855
856 /// Saturation to 28 Feb can happen.
857 if (unlikely(day_of_month == 29 && month == 2))
858 day_of_month = saturateDayOfMonth(year, month, day_of_month);
859
860 return makeDayNum(year, month, day_of_month);
861 }
862
863
864 inline std::string timeToString(time_t t) const
865 {
866 const Values & values = find(t);
867
868 std::string s {"0000-00-00 00:00:00"};
869
870 s[0] += values.year / 1000;
871 s[1] += (values.year / 100) % 10;
872 s[2] += (values.year / 10) % 10;
873 s[3] += values.year % 10;
874 s[5] += values.month / 10;
875 s[6] += values.month % 10;
876 s[8] += values.day_of_month / 10;
877 s[9] += values.day_of_month % 10;
878
879 auto hour = toHour(t);
880 auto minute = toMinute(t);
881 auto second = toSecond(t);
882
883 s[11] += hour / 10;
884 s[12] += hour % 10;
885 s[14] += minute / 10;
886 s[15] += minute % 10;
887 s[17] += second / 10;
888 s[18] += second % 10;
889
890 return s;
891 }
892
893 inline std::string dateToString(time_t t) const
894 {
895 const Values & values = find(t);
896
897 std::string s {"0000-00-00"};
898
899 s[0] += values.year / 1000;
900 s[1] += (values.year / 100) % 10;
901 s[2] += (values.year / 10) % 10;
902 s[3] += values.year % 10;
903 s[5] += values.month / 10;
904 s[6] += values.month % 10;
905 s[8] += values.day_of_month / 10;
906 s[9] += values.day_of_month % 10;
907
908 return s;
909 }
910
911 inline std::string dateToString(DayNum d) const
912 {
913 const Values & values = lut[d];
914
915 std::string s {"0000-00-00"};
916
917 s[0] += values.year / 1000;
918 s[1] += (values.year / 100) % 10;
919 s[2] += (values.year / 10) % 10;
920 s[3] += values.year % 10;
921 s[5] += values.month / 10;
922 s[6] += values.month % 10;
923 s[8] += values.day_of_month / 10;
924 s[9] += values.day_of_month % 10;
925
926 return s;
927 }
928};
929
930#if defined(__PPC__)
931#if !__clang__
932#pragma GCC diagnostic pop
933#endif
934#endif
935