blob: 9429704b8b71e57313772a7b99b836c9fe680ef0 [file]
// 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/timestamp-value.h"
#include <boost/algorithm/string/predicate.hpp>
#include "exprs/timestamp-functions.h"
#include "exprs/timezone_db.h"
#include "runtime/datetime-simple-date-format-parser.h"
#include "runtime/exec-env.h"
#include "runtime/timestamp-parse-util.h"
#include "runtime/timestamp-value.inline.h"
#include "service/frontend.h"
#include "common/names.h"
using boost::algorithm::starts_with;
using boost::date_time::not_a_date_time;
using boost::gregorian::date;
using boost::gregorian::date_duration;
using boost::posix_time::from_time_t;
using boost::posix_time::nanoseconds;
using boost::posix_time::ptime;
using boost::posix_time::ptime_from_tm;
using boost::posix_time::time_duration;
using boost::posix_time::to_tm;
namespace impala {
using datetime_parse_util::DateTimeFormatContext;
using datetime_parse_util::SimpleDateFormatTokenizer;
using impala_udf::FunctionContext;
using impala_udf::StringVal;
const char* TimestampValue::LLVM_CLASS_NAME = "class.impala::TimestampValue";
const double TimestampValue::ONE_BILLIONTH = 0.000000001;
TimestampValue TimestampValue::ParseSimpleDateFormat(const char* str, int len) {
TimestampValue tv;
discard_result(TimestampParser::ParseSimpleDateFormat(str, len, &tv.date_, &tv.time_));
return tv;
}
TimestampValue TimestampValue::ParseSimpleDateFormat(const string& str) {
return ParseSimpleDateFormat(str.c_str(), str.size());
}
TimestampValue TimestampValue::ParseSimpleDateFormat(const char* str, int len,
const DateTimeFormatContext& dt_ctx) {
TimestampValue tv;
discard_result(TimestampParser::ParseSimpleDateFormat(str, len, dt_ctx, &tv.date_,
&tv.time_));
return tv;
}
TimestampValue TimestampValue::ParseIsoSqlFormat(const char* str, int len,
const datetime_parse_util::DateTimeFormatContext& dt_ctx) {
TimestampValue tv;
discard_result(TimestampParser::ParseIsoSqlFormat(str, len, dt_ctx, &tv.date_,
&tv.time_));
return tv;
}
void TimestampValue::Format(const DateTimeFormatContext& dt_ctx, string& dst) const {
int max_length = dt_ctx.fmt_out_len;
dst.resize(max_length);
int written = TimestampParser::Format(dt_ctx, date_, time_, max_length, &dst[0]);
if (UNLIKELY(written < 0)) {
dst.clear();
} else {
dst.resize(written);
}
}
namespace {
inline cctz::time_point<cctz::sys_seconds> UnixTimeToTimePoint(time_t t) {
static const cctz::time_point<cctz::sys_seconds> epoch =
std::chrono::time_point_cast<cctz::sys_seconds>(
std::chrono::system_clock::from_time_t(0));
return epoch + cctz::sys_seconds(t);
}
inline time_t TimePointToUnixTime(const cctz::time_point<cctz::sys_seconds>& tp) {
static const cctz::time_point<cctz::sys_seconds> epoch =
std::chrono::time_point_cast<cctz::sys_seconds>(
std::chrono::system_clock::from_time_t(0));
return (tp - epoch).count();
}
// Returns 'true' iff 'cs' is out of valid range (years 1400..9999 are considered valid).
inline bool IsDateOutOfRange(const cctz::civil_second& cs) {
// Smallest valid year.
const static int MIN_YEAR =
boost::gregorian::date(boost::date_time::min_date_time).year();
// Largest valid year.
const static int MAX_YEAR =
boost::gregorian::date(boost::date_time::max_date_time).year();
return cs.year() < MIN_YEAR || cs.year() > MAX_YEAR;
}
TimestampValue CivilSecondsToTimestampValue(const cctz::civil_second& cs, int64_t nanos) {
// boost::gregorian::date() throws boost::gregorian::bad_year if year is not in the
// 1400..9999 range. Need to check validity before creating the date object.
if (UNLIKELY(IsDateOutOfRange(cs))) {
return TimestampValue();
} else {
return TimestampValue(
date(cs.year(), cs.month(), cs.day()),
time_duration(cs.hour(), cs.minute(), cs.second(), nanos));
}
}
}
void TimestampValue::UtcToLocal(const Timezone& local_tz,
TimestampValue* start_of_repeated_period, TimestampValue* end_of_repeated_period) {
DCHECK(HasDateAndTime());
time_t unix_time;
if (UNLIKELY(!UtcToUnixTime(&unix_time))) {
SetToInvalidDateTime();
return;
}
cctz::time_point<cctz::sys_seconds> from_tp = UnixTimeToTimePoint(unix_time);
cctz::civil_second to_cs = cctz::convert(from_tp, local_tz);
*this = CivilSecondsToTimestampValue(to_cs, time_.fractional_seconds());
if (start_of_repeated_period == nullptr && end_of_repeated_period == nullptr) return;
// Do the reverse conversion if repeated period boundaries are needed.
const cctz::time_zone::civil_lookup from_cl = local_tz.lookup(to_cs);
if (UNLIKELY(from_cl.kind == cctz::time_zone::civil_lookup::REPEATED)) {
if (start_of_repeated_period != nullptr) {
// Start of the period is simply the transition time converted to local time.
to_cs = cctz::convert(from_cl.trans, local_tz);
*start_of_repeated_period = CivilSecondsToTimestampValue(to_cs, 0);
}
if (end_of_repeated_period != nullptr) {
// End of the period is last nanosecond before transition time converted to
// local time.
to_cs = cctz::convert(from_cl.trans - std::chrono::seconds(1), local_tz);
*end_of_repeated_period =
CivilSecondsToTimestampValue(to_cs, NANOS_PER_SEC - 1);
}
}
}
void TimestampValue::HiveLegacyUtcToLocal(const Timezone& local_tz) {
DCHECK(HasDateAndTime());
int64_t utc_time_millis;
if (UNLIKELY(!FloorUtcToUnixTimeMillis(&utc_time_millis))) {
SetToInvalidDateTime();
return;
}
string tz = local_tz.name();
static constexpr std::string_view zoneinfo = "/usr/share/zoneinfo/";
if (starts_with(tz, zoneinfo)) {
tz = tz.substr(zoneinfo.size());
}
TCivilTime cs;
Status status = ExecEnv::GetInstance()->frontend()->HiveLegacyTimezoneConvert(
tz, utc_time_millis, &cs);
if (UNLIKELY(!status.ok())) {
// This would result in log spam. However it should be impossible to fail.
LOG(ERROR) << "Timezone " << tz << " cannot be used with legacy Hive conversion.";
return;
}
date_ = boost::gregorian::date(cs.year, cs.month, cs.day);
time_ = time_duration(cs.hour, cs.minute, cs.second, time_.fractional_seconds());
}
void TimestampValue::LocalToUtc(const Timezone& local_tz,
TimestampValue* pre_utc_if_repeated, TimestampValue* post_utc_if_repeated) {
DCHECK(HasDateAndTime());
// Time-zone conversion rules don't affect fractional seconds, leave them intact.
const auto nanos = nanoseconds(time_.fractional_seconds());
const cctz::civil_second from_cs(date_.year(), date_.month(), date_.day(),
time_.hours(), time_.minutes(), time_.seconds());
// 'from_cl' represents the 'time_point' that corresponds to 'from_cs' civil time within
// 'local_tz' time-zone.
const cctz::time_zone::civil_lookup from_cl = local_tz.lookup(from_cs);
if (LIKELY(from_cl.kind == cctz::time_zone::civil_lookup::UNIQUE)) {
*this = UtcFromUnixTimeTicks<1>(TimePointToUnixTime(from_cl.pre));
time_ += nanos;
return;
}
// In case the resulting 'time_point' is ambiguous, we have to invalidate this
// TimestampValue and set pre/post_utc_if_repeated if needed.
// 'civil_lookup' members and the details of handling ambiguity are described at:
// https://github.com/google/cctz/blob/a2dd3d0fbc811fe0a1d4d2dbb0341f1a3d28cb2a/
// include/cctz/time_zone.h#L106
SetToInvalidDateTime();
if (from_cl.kind == cctz::time_zone::civil_lookup::REPEATED){
if (pre_utc_if_repeated != nullptr) {
*pre_utc_if_repeated = UtcFromUnixTimeTicks<1>(TimePointToUnixTime(from_cl.pre));
pre_utc_if_repeated->time_ += nanos;
}
if (post_utc_if_repeated != nullptr) {
*post_utc_if_repeated = UtcFromUnixTimeTicks<1>(TimePointToUnixTime(from_cl.post));
post_utc_if_repeated->time_ += nanos;
}
} else {
DCHECK(from_cl.kind == cctz::time_zone::civil_lookup::SKIPPED);
if (pre_utc_if_repeated != nullptr) {
*pre_utc_if_repeated = UtcFromUnixTimeTicks<1>(TimePointToUnixTime(from_cl.trans));
pre_utc_if_repeated->time_ += nanos;
}
if (post_utc_if_repeated != nullptr) {
*post_utc_if_repeated = UtcFromUnixTimeTicks<1>(TimePointToUnixTime(from_cl.trans));
post_utc_if_repeated->time_ += nanos;
}
}
}
ostream& operator<<(ostream& os, const TimestampValue& timestamp_value) {
char dst[SimpleDateFormatTokenizer::DEFAULT_DATE_TIME_FMT_LEN + 1];
const int out_len = TimestampParser::FormatDefault(timestamp_value.date(),
timestamp_value.time(), dst);
if (LIKELY(out_len >= 0)) {
dst[out_len] = '\0';
os << dst;
}
return os;
}
TimestampValue TimestampValue::UnixTimeToLocal(
time_t unix_time, const Timezone& local_tz) {
cctz::time_point<cctz::sys_seconds> from_tp = UnixTimeToTimePoint(unix_time);
cctz::civil_second to_cs = cctz::convert(from_tp, local_tz);
// boost::gregorian::date() throws boost::gregorian::bad_year if year is not in the
// 1400..9999 range. Need to check validity before creating the date object.
if (UNLIKELY(IsDateOutOfRange(to_cs))) {
return ptime(not_a_date_time);
} else {
return TimestampValue(
boost::gregorian::date(to_cs.year(), to_cs.month(), to_cs.day()),
boost::posix_time::time_duration(to_cs.hour(), to_cs.minute(), to_cs.second()));
}
}
void TimestampValue::ToString(string& dst) const {
dst.resize(SimpleDateFormatTokenizer::DEFAULT_DATE_TIME_FMT_LEN);
const int out_len = TimestampParser::FormatDefault(date(), time(), dst.data());
if (UNLIKELY(out_len != SimpleDateFormatTokenizer::DEFAULT_DATE_TIME_FMT_LEN)) {
if (UNLIKELY(out_len < 0)) {
dst.clear();
} else {
dst.resize(out_len);
}
}
}
string TimestampValue::ToString() const {
string dst;
ToString(dst);
return dst;
}
TimestampValue TimestampValue::Add(const boost::posix_time::time_duration& t) const {
if (!IsValidTime(t) || !HasDateAndTime()) return TimestampValue();
int64_t this_in_nano_sec = time_.total_nanoseconds();
int64_t t_in_nano_sec = t.total_nanoseconds();
if (this_in_nano_sec + t_in_nano_sec < NANOS_PER_DAY) {
return TimestampValue(date_, time_ + t);
} else {
int64_t total_in_nano_sec = this_in_nano_sec + t_in_nano_sec;
int64_t days = total_in_nano_sec / NANOS_PER_DAY;
int64_t nano_secs_remaining = total_in_nano_sec % NANOS_PER_DAY;
return TimestampValue(date_ + boost::gregorian::date_duration(days),
boost::posix_time::time_duration(0, 0, 0, nano_secs_remaining));
}
}
TimestampValue TimestampValue::Subtract(const boost::posix_time::time_duration& t) const {
if (!IsValidTime(t) || !HasDateAndTime()) return TimestampValue();
int64_t this_in_nano_sec = time_.total_nanoseconds();
int64_t t_in_nano_sec = t.total_nanoseconds();
if (this_in_nano_sec - t_in_nano_sec >= 0) {
return TimestampValue(date_, time_ - t);
} else {
return TimestampValue(date_ - boost::gregorian::date_duration(1),
boost::posix_time::time_duration(
0, 0, 0, NANOS_PER_DAY + this_in_nano_sec - t_in_nano_sec));
}
}
}