blob: 579a8958c87f8462769715e2835ae6660dd6f069 [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-parse-util.h"
#include <boost/date_time/gregorian/gregorian.hpp>
#include "cctz/civil_time.h"
#include "runtime/datetime-iso-sql-format-parser.h"
#include "runtime/datetime-simple-date-format-parser.h"
#include "runtime/string-value.inline.h"
#include "runtime/timestamp-value.h"
#include "util/string-parser.h"
#include "common/names.h"
using boost::gregorian::date;
using boost::posix_time::ptime;
using boost::posix_time::time_duration;
namespace impala {
using namespace datetime_parse_util;
bool DateParser::ParseSimpleDateFormat(const char* str, int len,
const DateTimeFormatContext& dt_ctx, DateValue* date) {
DCHECK(dt_ctx.toks.size() > 0);
// 'dt_ctx' must have date tokens. Time tokens are accepted but ignored.
DCHECK(dt_ctx.has_date_toks);
DCHECK(date != nullptr);
DateTimeParseResult dt_result;
if (UNLIKELY(str == nullptr || len <= 0
|| !SimpleDateFormatParser::ParseDateTime(str, len, dt_ctx, &dt_result))) {
*date = DateValue();
return false;
}
int year = dt_result.realign_year ?
RealignYear(dt_result, dt_ctx.century_break_ptime) :
dt_result.year;
*date = DateValue(year, dt_result.month, dt_result.day);
return date->IsValid();
}
int DateParser::RealignYear(const DateTimeParseResult& dt_result,
const ptime& century_break) {
DCHECK(dt_result.realign_year);
DCHECK(dt_result.year < 100);
DCHECK(!century_break.is_special());
const date& cb_date = century_break.date();
const time_duration& cb_time = century_break.time_of_day();
DCHECK(!cb_time.is_negative());
// Let the century start at AABB and the year parsed be YY, this gives us AAYY.
int year = dt_result.year + (cb_date.year() / 100) * 100;
cctz::civil_day aligned_day(year, dt_result.month, dt_result.day);
cctz::civil_day century_break_day(cb_date.year(), cb_date.month(), cb_date.day());
// Advance 100 years if parsed time is before the century break.
// For example if the century breaks at 1937 but year = 1936, the correct year would
// be 2036.
if (aligned_day < century_break_day
|| (aligned_day == century_break_day && (cb_time.ticks() > 0))) {
year += 100;
}
return year;
}
bool DateParser::ParseSimpleDateFormat(const char* str, int len, bool accept_time_toks,
DateValue* date) {
DCHECK(date != nullptr);
if (UNLIKELY(str == nullptr)) return IndicateDateParseFailure(date);
int trimmed_len = len;
// Remove leading white space.
while (trimmed_len > 0 && isspace(*str)) {
++str;
--trimmed_len;
}
// Strip the trailing blanks.
while (trimmed_len > 0 && isspace(str[trimmed_len - 1])) --trimmed_len;
if (UNLIKELY(trimmed_len <= 0)) return IndicateDateParseFailure(date);
const DateTimeFormatContext* dt_ctx =
SimpleDateFormatTokenizer::GetDefaultFormatContext(str, trimmed_len,
accept_time_toks, false);
if (dt_ctx != nullptr) return ParseSimpleDateFormat(str, trimmed_len, *dt_ctx, date);
// Generating context lazily as a fall back if default formats fail.
// ParseFormatTokenByStr() does not require a template format string.
DateTimeFormatContext lazy_ctx(str, trimmed_len);
if (!SimpleDateFormatTokenizer::TokenizeByStr(&lazy_ctx, accept_time_toks, false)) {
return IndicateDateParseFailure(date);
}
return ParseSimpleDateFormat(str, trimmed_len, lazy_ctx, date);
}
bool DateParser::ParseIsoSqlFormat(const char* str, int len,
const DateTimeFormatContext& dt_ctx, DateValue* date) {
DCHECK(dt_ctx.toks.size() > 0);
DCHECK(date != nullptr);
DCHECK(dt_ctx.has_date_toks);
if (UNLIKELY(str == nullptr || len <= 0)) return IndicateDateParseFailure(date);
DateTimeParseResult dt_result;
bool result = IsoSqlFormatParser::ParseDateTime(str, len, dt_ctx, &dt_result);
if (!result) return IndicateDateParseFailure(date);
*date = DateValue(dt_result.year, dt_result.month, dt_result.day);
return date->IsValid();
}
string DateParser::Format(const DateTimeFormatContext& dt_ctx, const DateValue& date) {
DCHECK(dt_ctx.toks.size() > 0);
DCHECK(dt_ctx.has_date_toks && !dt_ctx.has_time_toks);
int year, month, day;
if (!date.ToYearMonthDay(&year, &month, &day)) return "";
string result;
result.reserve(dt_ctx.fmt_out_len);
for (const DateTimeFormatToken& tok: dt_ctx.toks) {
int32_t num_val = -1;
switch (tok.type) {
case YEAR:
case ROUND_YEAR: {
num_val = year;
if (tok.len < 4) {
int adjust_factor = std::pow(10, tok.len);
num_val %= adjust_factor;
}
break;
}
case QUARTER_OF_YEAR: {
num_val = GetQuarter(month);
break;
}
case MONTH_IN_YEAR: num_val = month; break;
case MONTH_NAME:
case MONTH_NAME_SHORT: {
result.append(FormatMonthName(month, tok));
break;
}
case WEEK_OF_YEAR: {
num_val = GetWeekOfYear(year, month, day);
break;
}
case WEEK_OF_MONTH: {
num_val = GetWeekOfMonth(day);
break;
}
case DAY_OF_WEEK: {
num_val = GetDayOfWeek(date);
break;
}
case DAY_IN_MONTH: num_val = day; break;
case DAY_IN_YEAR: {
num_val = GetDayInYear(year, month, day);
break;
}
case DAY_NAME:
case DAY_NAME_SHORT: {
result.append(FormatDayName(GetDayOfWeek(date), tok));
break;
}
case SEPARATOR: {
result.append(tok.val, tok.len);
break;
}
case TEXT: {
result.append(FormatTextToken(tok));
break;
}
default: DCHECK(false) << "Unknown date format token";
}
if (num_val > -1) {
string tmp_str = std::to_string(num_val);
if (!tok.fm_modifier && tmp_str.length() < tok.len) {
tmp_str.insert(0, tok.len - tmp_str.length(), '0');
}
result.append(tmp_str);
}
}
return result;
}
bool DateParser::IndicateDateParseFailure(DateValue* date) {
*date = DateValue();
return false;
}
int DateParser::GetDayOfWeek(const DateValue& date) {
DCHECK(date.IsValid());
// DateValue::WeekDay() returns [0-6] where Monday is 0.
int dow = date.WeekDay();
// Convert to [1-7] where Sunday is 1.
return (dow + 1) % 7 + 1;
}
}