blob: d3fd4a067b4672f1e5660946f521528f74f6a1c4 [file] [log] [blame]
// 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());
}
}
}
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);
}
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);
}
}
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 [0, 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::WeekOfYear() const {
if (UNLIKELY(!IsValid())) return -1;
const cctz::civil_day today = EPOCH_DATE + days_since_epoch_;
cctz::civil_day jan1 = cctz::civil_day(today.year(), 1, 1);
cctz::civil_day first_monday;
if (cctz::get_weekday(jan1) <= cctz::weekday::thursday) {
// Get the previous Monday if 'jan1' is not already a Monday.
first_monday = cctz::next_weekday(jan1, cctz::weekday::monday) - 7;
} else {
// Get the next Monday.
first_monday = cctz::next_weekday(jan1, cctz::weekday::monday);
}
cctz::civil_day dec31 = cctz::civil_day(today.year(), 12, 31);
cctz::civil_day last_sunday;
if (cctz::get_weekday(dec31) >= cctz::weekday::thursday) {
// Get the next Sunday if 'dec31' is not already a Sunday.
last_sunday = cctz::prev_weekday(dec31, cctz::weekday::sunday) + 7;
} else {
// Get the previous Sunday.
last_sunday = cctz::prev_weekday(dec31, cctz::weekday::sunday);
}
// 0001-01-01 is Monday in the proleptic Gregorian calendar.
// 0001-01-01 belongs to year 0001.
if (today >= first_monday && today <= last_sunday) {
return (today - first_monday) / 7 + 1;
} else if (today > last_sunday) {
return 1;
} else {
// today < first_monday && today.year() > 0
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;
}
}
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();
}
}