| 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 | |