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. |
24 | enum class WeekModeFlag : UInt8 |
25 | { |
26 | MONDAY_FIRST = 1, |
27 | YEAR = 2, |
28 | FIRST_WEEKDAY = 4, |
29 | NEWYEAR_DAY = 8 |
30 | }; |
31 | using 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 | */ |
36 | class DateLUTImpl |
37 | { |
38 | public: |
39 | DateLUTImpl(const std::string & time_zone); |
40 | |
41 | public: |
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 | |
67 | private: |
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 | |
112 | public: |
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 | |