| // 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)); |
| } |
| } |
| } |