| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| |
| #include "runtime/date-value.h" |
| |
| #include <iomanip> |
| #include "cctz/civil_time.h" |
| #include "runtime/date-parse-util.h" |
| |
| #include "common/names.h" |
| |
| namespace impala { |
| |
| using datetime_parse_util::DateTimeFormatContext; |
| using datetime_parse_util::GetMonthAndDayFromDaysSinceJan1; |
| using datetime_parse_util::IsLeapYear; |
| |
| const int EPOCH_YEAR = 1970; |
| const int MIN_YEAR = 1; |
| const int MAX_YEAR = 9999; |
| |
| const cctz::civil_day EPOCH_DATE(EPOCH_YEAR, 1, 1); |
| |
| const int32_t DateValue::MIN_DAYS_SINCE_EPOCH = |
| cctz::civil_day(MIN_YEAR, 1, 1) - EPOCH_DATE; |
| const int32_t DateValue::MAX_DAYS_SINCE_EPOCH = |
| cctz::civil_day(MAX_YEAR, 12, 31) - EPOCH_DATE; |
| |
| const DateValue DateValue::MIN_DATE(MIN_DAYS_SINCE_EPOCH); |
| const DateValue DateValue::MAX_DATE(MAX_DAYS_SINCE_EPOCH); |
| |
| DateValue::DateValue(int64_t year, int64_t month, int64_t day) |
| : days_since_epoch_(INVALID_DAYS_SINCE_EPOCH) { |
| DCHECK(!IsValid()); |
| // Check year range and whether year-month-day is a valid date. |
| if (LIKELY(year >= MIN_YEAR && year <= MAX_YEAR)) { |
| // Use CCTZ for validity check. |
| cctz::civil_day date(year, month, day); |
| if (LIKELY(year == date.year() && month == date.month() && day == date.day())) { |
| days_since_epoch_ = date - EPOCH_DATE; |
| DCHECK(IsValid()); |
| } |
| } |
| } |
| |
| namespace { |
| |
| inline int32_t CalcFirstDayOfYearSinceEpoch(int year) { |
| int m400 = year % 400; |
| int m100 = m400 % 100; |
| int m4 = m100 % 4; |
| |
| return (year - EPOCH_YEAR) * 365 |
| + ((year - EPOCH_YEAR / 4 * 4 + ((m4 != 0) ? 4 - m4 : 0)) / 4 - 1) |
| - ((year - EPOCH_YEAR / 100 * 100 + ((m100 != 0) ? 100 - m100 : 0)) / 100 - 1) |
| + ((year - EPOCH_YEAR / 400 * 400 + ((m400 != 0) ? 400 - m400 : 0)) / 400 - 1); |
| } |
| |
| // Returns the Monday that belongs to the first ISO 8601 week of 'year'. That is: |
| // - If Jan 1 falls on Monday, Tuesday, Wednesday or Thursday, then the week of Jan 1 is |
| // the first ISO 8601 week of 'year'. Therefore the Monday of the first ISO 8601 week in |
| // 'year' is the first Monday before Jan 2. |
| // - If Jan 1 falls on Friday, Saturday or Sunday, then the week of Jan 1 is the last ISO |
| // 8601 week of the previous year. Therefore the Monday of the first ISO 8601 week in |
| // 'year' is the first Monday following Jan 1. |
| inline cctz::civil_day GetFirstIso8601MondayOfYear(int year) { |
| cctz::civil_day jan1 = cctz::civil_day(year, 1, 1); |
| if (cctz::get_weekday(jan1) <= cctz::weekday::thursday) { |
| // Get the previous Monday if 'jan1' is not already a Monday. |
| return cctz::next_weekday(jan1, cctz::weekday::monday) - 7; |
| } else { |
| // Get the next Monday. |
| return cctz::next_weekday(jan1, cctz::weekday::monday); |
| } |
| } |
| |
| // Returns Sunday that belongs to the last ISO 8601 week of 'year'. That is: |
| // - If Dec 31 falls on Thursday, Friday, Saturday or Sunday, then the week of Dec 31 |
| // is the last ISO 8601 week of 'year'. Therefore the Sunday of the last ISO 8601 week |
| // in 'year' is the first Sunday after Dec 30. |
| // - If Dec 31 falls on Monday, Tuesday or Wednesday, then the week of Dec 31 is the first |
| // ISO 8601 week of the next year. Therefore the Sunday of the last ISO 8601 week in |
| // 'year' is the first Sunday previous to Dec 31. |
| inline cctz::civil_day GetLastIso8601SundayOfYear(int year) { |
| cctz::civil_day dec31 = cctz::civil_day(year, 12, 31); |
| if (cctz::get_weekday(dec31) >= cctz::weekday::thursday) { |
| // Get the next Sunday if 'dec31' is not already a Sunday. |
| return cctz::prev_weekday(dec31, cctz::weekday::sunday) + 7; |
| } else { |
| // Get the previous Sunday. |
| return cctz::prev_weekday(dec31, cctz::weekday::sunday); |
| } |
| } |
| |
| } |
| |
| DateValue DateValue::CreateFromIso8601WeekBasedDateVals(int year, |
| int week_of_year, int day_of_week) { |
| if (year >= MIN_YEAR && year <= MAX_YEAR |
| && week_of_year >= 1 && week_of_year <= 53 |
| && 1 <= day_of_week && day_of_week <= 7) { |
| const cctz::civil_day first_monday = GetFirstIso8601MondayOfYear(year); |
| const cctz::civil_day last_sunday = GetLastIso8601SundayOfYear(year); |
| |
| const cctz::civil_day today = first_monday + (week_of_year - 1) * 7 + day_of_week - 1; |
| if (today <= last_sunday) return DateValue(today - EPOCH_DATE); |
| } |
| return DateValue(); |
| } |
| |
| DateValue DateValue::ParseSimpleDateFormat(const char* str, int len, |
| bool accept_time_toks) { |
| DateValue dv; |
| discard_result(DateParser::ParseSimpleDateFormat(str, len, accept_time_toks, &dv)); |
| return dv; |
| } |
| |
| DateValue DateValue::ParseSimpleDateFormat(const string& str, bool accept_time_toks) { |
| return ParseSimpleDateFormat(str.c_str(), str.size(), accept_time_toks); |
| } |
| |
| DateValue DateValue::ParseSimpleDateFormat(const char* str, int len, |
| const DateTimeFormatContext& dt_ctx) { |
| DateValue dv; |
| discard_result(DateParser::ParseSimpleDateFormat(str, len, dt_ctx, &dv)); |
| return dv; |
| } |
| |
| DateValue DateValue::ParseIsoSqlFormat(const char* str, int len, |
| const DateTimeFormatContext& dt_ctx) { |
| DateValue dv; |
| discard_result(DateParser::ParseIsoSqlFormat(str, len, dt_ctx, &dv)); |
| return dv; |
| } |
| |
| string DateValue::Format(const DateTimeFormatContext& dt_ctx) const { |
| return DateParser::Format(dt_ctx, *this); |
| } |
| |
| bool DateValue::ToYear(int* year) const { |
| DCHECK(year != nullptr); |
| if (UNLIKELY(!IsValid())) return false; |
| |
| // This function was introduced to extract year of a DateValue efficiently. |
| // It will be fast for most days of the year and only slightly slower for days around |
| // the beginning and end of the year. |
| // |
| // Here's a quick explanation. Let's use the following notation: |
| // m400 = year % 400 |
| // m100 = m400 % 100 |
| // m4 = m100 % 4 |
| // |
| // If 'days' is the number of days between 1970-01-01 and the first day of 'year' |
| // (excluding the endpoint), then the following is true: |
| // days == (year - 1970) * 365 |
| // + ((year - 1968 + ((m4 != 0) ? 4 - m4 : 0)) / 4 - 1) |
| // - ((year - 1900 + ((m100 != 0) ? 100 - m100 : 0)) / 100 - 1) |
| // + ((year - 1600 + ((m400 != 0) ? 400 - m400 : 0)) / 400 - 1) |
| // |
| // Reordering the equation we get: |
| // days * 400 == (year - 1970) * 365 * 400 |
| // + ((year - 1968) * 100 + ((m4 != 0) ? (4 - m4) * 100 : 0) - 400) |
| // - ((year - 1900) * 4 + ((m100 != 0) ? (100 - m100) * 4 : 0) - 400) |
| // + (year - 1600 + ((m400 != 0) ? 400 - m400 : 0) - 400) |
| // |
| // then: |
| // days * 400 == year * 146000 - 287620000 |
| // + (year * 100 - 196800 + ((m4 != 0) ? (4 - m4) * 100 : 0) - 400) |
| // - (year * 4 - 7600 + ((m100 != 0) ? (100 - m100) * 4 : 0) - 400) |
| // + (year - 1600 + ((m400 != 0) ? 400 - m400 : 0) - 400) |
| // |
| // which means that (A): |
| // year * 146097 == days * 400 + 287811200 |
| // - ((m4 != 0) ? (4 - m4) * 100 : 0) |
| // + ((m100 != 0) ? (100 - m100) * 4 : 0) |
| // - ((m400 != 0) ? 400 - m400 : 0) |
| // |
| // On the other hand, if |
| // f(year) = - ((m4 != 0) ? (4 - m4) * 100 : 0) |
| // + ((m100 != 0) ? (100 - m100) * 4 : 0) |
| // - ((m400 != 0) ? 400 - m400 : 0) |
| // and 'year' is in the [1, 9999] range, then it follows that (B): |
| // f(year) must fall into the [-591, 288] range. |
| // |
| // Finally, if we put (A) and (B) together we can conclude that 'year' must fall into |
| // the |
| // [ (days * 400 + 287811200 - 591) / 146097, (days * 400 + 287811200 + 288) / 146097 ] |
| // range. |
| |
| int tmp = days_since_epoch_ * 400 + 287811200; |
| int first_year = (tmp - 591) / 146097; |
| int last_year = (tmp + 288) / 146097; |
| |
| if (first_year == last_year) { |
| *year = first_year; |
| } else if (CalcFirstDayOfYearSinceEpoch(last_year) <= days_since_epoch_) { |
| *year = last_year; |
| } else { |
| *year = first_year; |
| } |
| DCHECK(*year >= MIN_YEAR && *year <= MAX_YEAR); |
| |
| return true; |
| } |
| |
| bool DateValue::ToYearMonthDay(int* year, int* month, int* day) const { |
| DCHECK(year != nullptr); |
| DCHECK(month != nullptr); |
| DCHECK(day != nullptr); |
| if (UNLIKELY(!IsValid())) return false; |
| |
| // Uses the same method to calculate the year as DateValue::ToYear(). |
| int tmp = days_since_epoch_ * 400 + 287811200; |
| int first_year = (tmp - 591) / 146097; |
| int last_year = (tmp + 288) / 146097; |
| |
| int jan1_dse = CalcFirstDayOfYearSinceEpoch(last_year); |
| if (jan1_dse <= days_since_epoch_) { |
| *year = last_year; |
| } else { |
| *year = first_year; |
| jan1_dse -= IsLeapYear(first_year) ? 366 : 365; |
| } |
| DCHECK(*year >= MIN_YEAR && *year <= MAX_YEAR); |
| |
| // Day of year. 0 is used for January 1. |
| int days_since_jan1 = days_since_epoch_ - jan1_dse; |
| |
| return GetMonthAndDayFromDaysSinceJan1(*year, days_since_jan1, month, day); |
| } |
| |
| int DateValue::WeekDay() const { |
| if (UNLIKELY(!IsValid())) return -1; |
| const cctz::civil_day cd = EPOCH_DATE + days_since_epoch_; |
| return static_cast<int>(cctz::get_weekday(cd)); |
| } |
| |
| int DateValue::DayOfYear() const { |
| if (UNLIKELY(!IsValid())) return -1; |
| const cctz::civil_day cd = EPOCH_DATE + days_since_epoch_; |
| return static_cast<int>(cctz::get_yearday(cd)); |
| } |
| |
| int DateValue::Iso8601WeekOfYear() const { |
| if (UNLIKELY(!IsValid())) return -1; |
| const cctz::civil_day today = EPOCH_DATE + days_since_epoch_; |
| const cctz::civil_day first_monday = GetFirstIso8601MondayOfYear(today.year()); |
| const cctz::civil_day last_sunday = GetLastIso8601SundayOfYear(today.year()); |
| |
| // 0001-01-01 is Monday in the proleptic Gregorian calendar. |
| // 0001-01-01 belongs to year 0001. |
| // 9999-12-31 is Friday in the proleptic Gregorian calendar. |
| // 9999-12-31 belongs to year 9999. |
| if (today >= first_monday && today <= last_sunday) { |
| return (today - first_monday) / 7 + 1; |
| } else if (today > last_sunday) { |
| DCHECK(today.year() >= MIN_YEAR && today.year() < MAX_YEAR); |
| return 1; |
| } else { |
| DCHECK(today < first_monday); |
| DCHECK(today.year() > MIN_YEAR && today.year() <= MAX_YEAR); |
| cctz::civil_day prev_jan1 = cctz::civil_day(today.year() - 1, 1, 1); |
| cctz::civil_day prev_first_monday; |
| if (cctz::get_weekday(prev_jan1) <= cctz::weekday::thursday) { |
| // Get the previous Monday if 'prev_jan1' is not already a Monday. |
| prev_first_monday = cctz::next_weekday(prev_jan1, cctz::weekday::monday) - 7; |
| } else { |
| // Get the next Monday. |
| prev_first_monday = cctz::next_weekday(prev_jan1, cctz::weekday::monday); |
| } |
| return (today - prev_first_monday) / 7 + 1; |
| } |
| } |
| |
| int DateValue::Iso8601WeekNumberingYear() const { |
| if (UNLIKELY(!IsValid())) return -1; |
| const cctz::civil_day today = EPOCH_DATE + days_since_epoch_; |
| const cctz::civil_day first_monday = GetFirstIso8601MondayOfYear(today.year()); |
| const cctz::civil_day last_sunday = GetLastIso8601SundayOfYear(today.year()); |
| |
| // 0001-01-01 is Monday in the proleptic Gregorian calendar. |
| // 0001-01-01 belongs to year 0001. |
| // 9999-12-31 is Friday in the proleptic Gregorian calendar. |
| // 9999-12-31 belongs to year 9999. |
| if (today >= first_monday && today <= last_sunday) { |
| return today.year(); |
| } else if (today > last_sunday) { |
| DCHECK(today.year() >= MIN_YEAR && today.year() < MAX_YEAR); |
| return today.year() + 1; |
| } else { |
| DCHECK(today < first_monday); |
| DCHECK(today.year() > MIN_YEAR && today.year() <= MAX_YEAR); |
| return today.year() - 1; |
| } |
| } |
| |
| DateValue DateValue::AddDays(int64_t days) const { |
| if (UNLIKELY(!IsValid() |
| || days < MIN_DAYS_SINCE_EPOCH - days_since_epoch_ |
| || days > MAX_DAYS_SINCE_EPOCH - days_since_epoch_)) { |
| return DateValue(); |
| } |
| return DateValue(days_since_epoch_ + days); |
| } |
| |
| DateValue DateValue::AddMonths(int64_t months, bool keep_last_day) const { |
| if (UNLIKELY(!IsValid())) return DateValue(); |
| |
| const cctz::civil_day today = EPOCH_DATE + days_since_epoch_; |
| int64_t total_months = today.year()*12 + today.month() - 1; |
| if (UNLIKELY(months < MIN_YEAR*12 - total_months |
| || months > MAX_YEAR*12 + 11 - total_months)) { |
| return DateValue(); |
| } |
| const cctz::civil_month month = cctz::civil_month(today); |
| const cctz::civil_month result_month = month + months; |
| const cctz::civil_day last_day_of_result_month = |
| cctz::civil_day(result_month + 1) - 1; |
| |
| if (keep_last_day) { |
| const cctz::civil_day last_day_of_month = cctz::civil_day(month + 1) - 1; |
| if (today == last_day_of_month) { |
| return DateValue(last_day_of_result_month.year(), |
| last_day_of_result_month.month(), last_day_of_result_month.day()); |
| } |
| } |
| |
| const cctz::civil_day ans_normalized = cctz::civil_day(result_month.year(), |
| result_month.month(), today.day()); |
| const cctz::civil_day ans_capped = std::min(ans_normalized, last_day_of_result_month); |
| return DateValue(ans_capped.year(), ans_capped.month(), ans_capped.day()); |
| } |
| |
| DateValue DateValue::AddYears(int64_t years) const { |
| if (UNLIKELY(!IsValid())) return DateValue(); |
| |
| const cctz::civil_day today = EPOCH_DATE + days_since_epoch_; |
| if (UNLIKELY(years < MIN_YEAR - today.year() || years > MAX_YEAR - today.year())) { |
| return DateValue(); |
| } |
| const int64_t result_year = today.year() + years; |
| |
| // Feb 29 in leap years requires special attention. |
| if (UNLIKELY(today.month() == 2 && today.day() == 29)) { |
| const cctz::civil_month result_month(result_year, today.month()); |
| const cctz::civil_day last_day_of_result_month = |
| cctz::civil_day(result_month + 1) - 1; |
| return DateValue(result_year, last_day_of_result_month.month(), |
| last_day_of_result_month.day()); |
| } |
| return DateValue(result_year, today.month(), today.day()); |
| } |
| |
| bool DateValue::ToDaysSinceEpoch(int32_t* days) const { |
| DCHECK(days != nullptr); |
| if (UNLIKELY(!IsValid())) return false; |
| |
| *days = days_since_epoch_; |
| return true; |
| } |
| |
| DateValue DateValue::LastDay() const { |
| if (UNLIKELY(!IsValid())) return DateValue(); |
| |
| const cctz::civil_day today = EPOCH_DATE + days_since_epoch_; |
| const cctz::civil_month month = cctz::civil_month(today); |
| const cctz::civil_day last_day_of_month = cctz::civil_day(month + 1) - 1; |
| return DateValue(last_day_of_month - EPOCH_DATE); |
| } |
| |
| bool DateValue::MonthsBetween(const DateValue& other, double* months_between) const { |
| DCHECK(months_between != nullptr); |
| if (UNLIKELY(!IsValid() || !other.IsValid())) return false; |
| |
| const cctz::civil_day today = EPOCH_DATE + days_since_epoch_; |
| const cctz::civil_month month(today); |
| const cctz::civil_day last_day_of_month = cctz::civil_day(month + 1) - 1; |
| |
| const cctz::civil_day other_date = EPOCH_DATE + other.days_since_epoch_; |
| const cctz::civil_month other_month(other_date); |
| const cctz::civil_day last_day_of_other_month = cctz::civil_day(other_month + 1) - 1; |
| |
| // If both dates are last days of different months they don't contribute |
| // a fractional value to the number of months, therefore there is no need to |
| // calculate difference in their days. |
| int days_diff = 0; |
| if (today != last_day_of_month || other_date != last_day_of_other_month) { |
| days_diff = today.day() - other_date.day(); |
| } |
| |
| *months_between = (today.year() - other_date.year()) * 12 + |
| today.month() - other_date.month() + (static_cast<double>(days_diff) / 31.0); |
| return true; |
| } |
| |
| string DateValue::ToString() const { |
| stringstream ss; |
| int year, month, day; |
| if (ToYearMonthDay(&year, &month, &day)) { |
| ss << std::setfill('0') << setw(4) << year << "-" << setw(2) << month << "-" |
| << setw(2) << day; |
| } |
| return ss.str(); |
| } |
| |
| ostream& operator<<(ostream& os, const DateValue& date_value) { |
| return os << date_value.ToString(); |
| } |
| |
| } |