blob: b3002a9aebb28cf2a16328f23bf600bd49b0abfa [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 <algorithm>
#include <cstring>
#include <boost/assign/list_of.hpp>
#include <boost/date_time/gregorian/gregorian.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include "common/status.h"
#include "runtime/raw-value.inline.h"
#include "runtime/timestamp-parse-util.h"
#include "runtime/timestamp-value.h"
#include "testutil/gtest-util.h"
#include "util/string-parser.h"
#include "common/names.h"
using boost::assign::list_of;
using boost::date_time::Dec;
using boost::date_time::not_a_date_time;
using boost::gregorian::date;
using boost::posix_time::time_duration;
namespace impala {
// Used for defining a custom date/time format test. The structure can be used to
// indicate whether the format or value is expected to fail. In a happy path test,
// the values for year, month, day etc will be validated against the parsed result.
// Further validation will also be performed if the should_format flag is enabled,
// whereby the parsed date/time will be translated back to a string and checked
// against the expected value.
struct TimestampTC {
const char* fmt;
const char* str;
bool fmt_should_fail;
bool str_should_fail;
bool should_format;
int expected_year;
int expected_month;
int expected_day;
int expected_hours;
int expected_minutes;
int expected_seconds;
int expected_fraction;
bool fmt_has_date_toks;
bool fmt_has_time_toks;
TimestampTC(const char* fmt, const char* str, bool fmt_should_fail = true,
bool str_should_fail = true)
: fmt(fmt),
str(str),
fmt_should_fail(fmt_should_fail),
str_should_fail(str_should_fail),
should_format(true),
expected_year(0),
expected_month(0),
expected_day(0),
expected_hours(0),
expected_minutes(0),
expected_seconds(0),
expected_fraction(0),
fmt_has_date_toks(false),
fmt_has_time_toks(false) {
}
TimestampTC(const char* fmt, const char* str, bool should_format,
bool fmt_has_date_toks, bool fmt_has_time_toks, int expected_year,
int expected_month, int expected_day, int expected_hours = 0,
int expected_minutes = 0, int expected_seconds = 0,
int expected_fraction = 0)
: fmt(fmt),
str(str),
fmt_should_fail(false),
str_should_fail(false),
should_format(should_format),
expected_year(expected_year),
expected_month(expected_month),
expected_day(expected_day),
expected_hours(expected_hours),
expected_minutes(expected_minutes),
expected_seconds(expected_seconds),
expected_fraction(expected_fraction),
fmt_has_date_toks(fmt_has_date_toks),
fmt_has_time_toks(fmt_has_time_toks) {
}
};
// Used to test custom date/time output test cases i.e. timestamp value -> string.
struct TimestampFormatTC {
const long ts;
const char* fmt;
const char* str;
bool should_fail;
TimestampFormatTC(long ts, const char* fmt, const char* str, bool should_fail = false)
: ts(ts),
fmt(fmt),
str(str),
should_fail(should_fail) {
}
};
// Used to represent a parsed timestamp token. For example, it may represent a year.
struct TimestampToken {
const char* fmt;
int val;
const char* str;
TimestampToken(const char* fmt, int val)
: fmt(fmt),
val(val),
str(NULL) {
}
TimestampToken(const char* fmt, int val, const char* str)
: fmt(fmt),
val(val),
str(str) {
}
friend bool operator<(const TimestampToken& lhs, const TimestampToken& rhs) {
return strcmp(lhs.fmt, rhs.fmt) < 0;
}
};
inline void ValidateTimestamp(TimestampValue& tv, string& fmt, string& val,
string& fmt_val, int year, int month, int day, int hours, int mins, int secs,
int frac) {
boost::gregorian::date not_a_date;
boost::gregorian::date cust_date = tv.date();
boost::posix_time::time_duration cust_time = tv.time();
EXPECT_NE(not_a_date, cust_date) << fmt_val;
EXPECT_NE(not_a_date_time, cust_time) << fmt_val;
EXPECT_EQ(year, cust_date.year()) << fmt_val;
EXPECT_EQ(month, cust_date.month()) << fmt_val;
EXPECT_EQ(day, cust_date.day()) << fmt_val;
EXPECT_EQ(hours, cust_time.hours()) << fmt_val;
EXPECT_EQ(mins, cust_time.minutes()) << fmt_val;
EXPECT_EQ(secs, cust_time.seconds()) << fmt_val;
EXPECT_EQ(frac, cust_time.fractional_seconds()) << fmt_val;
}
// This function will generate all permutations of tokens to test that the parsing and
// formatting is correct (position of tokens should be irrelevant). Note that separators
// are also combined with EACH token permutation to get the widest coverage on formats.
// This forces out the parsing and format logic edge cases.
void TestTimestampTokens(vector<TimestampToken>* toks, int year, int month,
int day, int hours, int mins, int secs, int frac) {
const char* SEPARATORS = " ~!@%^&*_+-:;|\\,./";
int toks_len = toks->size();
sort(toks->begin(), toks->end());
string fmt;
string val;
do {
// Validate we can parse date/time raw tokens (no separators)
{
for (int i = 0; i < toks_len; ++i) {
fmt.append((*toks)[i].fmt);
if ((*toks)[i].str != NULL) {
val.append(string((*toks)[i].str));
} else {
val.append(lexical_cast<string>((*toks)[i].val));
}
}
string fmt_val = "Format: " + fmt + ", Val: " + val;
DateTimeFormatContext dt_ctx(fmt.c_str(), fmt.length());
ASSERT_TRUE(TimestampParser::ParseFormatTokens(&dt_ctx)) << fmt_val;
TimestampValue tv(val.c_str(), val.length(), dt_ctx);
ValidateTimestamp(tv, fmt, val, fmt_val, year, month, day, hours, mins, secs,
frac);
int buff_len = dt_ctx.fmt_out_len + 1;
char buff[buff_len];
int actual_len = tv.Format(dt_ctx, buff_len, buff);
EXPECT_GT(actual_len, 0) << fmt_val;
EXPECT_LE(actual_len, dt_ctx.fmt_len) << fmt_val;
string buff_str(buff);
EXPECT_EQ(buff_str, val) << fmt_val << " " << buff_str;
fmt.clear();
val.clear();
}
// Validate we can parse date/time with separators
{
for (const char* separator = SEPARATORS; *separator != 0;
++separator) {
for (int i = 0; i < toks_len; ++i) {
fmt.append((*toks)[i].fmt);
if (i + 1 < toks_len) fmt.push_back(*separator);
if ((*toks)[i].str != NULL) {
val.append(string((*toks)[i].str));
} else {
val.append(lexical_cast<string>((*toks)[i].val));
}
if (i + 1 < toks_len) val.push_back(*separator);
}
string fmt_val = "Format: " + fmt + ", Val: " + val;
DateTimeFormatContext dt_ctx(fmt.c_str(), fmt.length());
ASSERT_TRUE(TimestampParser::ParseFormatTokens(&dt_ctx)) << fmt_val;
TimestampValue tv(val.c_str(), val.length(), dt_ctx);
ValidateTimestamp(tv, fmt, val, fmt_val, year, month, day, hours, mins, secs,
frac);
int buff_len = dt_ctx.fmt_out_len + 1;
char buff[buff_len];
int actual_len = tv.Format(dt_ctx, buff_len, buff);
EXPECT_GT(actual_len, 0) << fmt_val;
EXPECT_LE(actual_len, dt_ctx.fmt_len) << fmt_val;
string buff_str(buff);
EXPECT_EQ(buff_str, val) << fmt_val << " " << buff_str;
fmt.clear();
val.clear();
}
}
} while (next_permutation(toks->begin(), toks->end()));
}
TEST(TimestampTest, Basic) {
char s1[] = "2012-01-20 01:10:01";
char s2[] = "1990-10-20 10:10:10.123456789 ";
char s3[] = " 1990-10-20 10:10:10.123456789";
TimestampValue v1(s1, strlen(s1));
TimestampValue v2(s2, strlen(s2));
TimestampValue v3(s3, strlen(s3));
EXPECT_EQ(v1.date().year(), 2012);
EXPECT_EQ(v1.date().month(), 1);
EXPECT_EQ(v1.date().day(), 20);
EXPECT_EQ(v1.time().hours(), 1);
EXPECT_EQ(v1.time().minutes(), 10);
EXPECT_EQ(v1.time().seconds(), 1);
EXPECT_EQ(v1.time().fractional_seconds(), 0);
EXPECT_EQ(v2.time().fractional_seconds(), 123456789);
EXPECT_NE(v1, v2);
EXPECT_EQ(v2, v3);
EXPECT_LT(v2, v1);
EXPECT_LE(v2, v1);
EXPECT_GT(v1, v2);
EXPECT_GE(v2, v3);
EXPECT_NE(RawValue::GetHashValue(&v1, TYPE_TIMESTAMP, 0),
RawValue::GetHashValue(&v2, TYPE_TIMESTAMP, 0));
EXPECT_EQ(RawValue::GetHashValue(&v3, TYPE_TIMESTAMP, 0),
RawValue::GetHashValue(&v2, TYPE_TIMESTAMP, 0));
char s4[] = "2012-01-20T01:10:01";
char s5[] = "1990-10-20T10:10:10.123456789";
TimestampValue v4(s4, strlen(s4));
TimestampValue v5(s5, strlen(s5));
EXPECT_EQ(v4.date().year(), 2012);
EXPECT_EQ(v4.date().month(), 1);
EXPECT_EQ(v4.date().day(), 20);
EXPECT_EQ(v4.time().hours(), 1);
EXPECT_EQ(v4.time().minutes(), 10);
EXPECT_EQ(v4.time().seconds(), 1);
EXPECT_EQ(v4.time().fractional_seconds(), 0);
EXPECT_EQ(v5.date().year(), 1990);
EXPECT_EQ(v5.date().month(), 10);
EXPECT_EQ(v5.date().day(), 20);
EXPECT_EQ(v5.time().hours(), 10);
EXPECT_EQ(v5.time().minutes(), 10);
EXPECT_EQ(v5.time().seconds(), 10);
EXPECT_EQ(v5.time().fractional_seconds(), 123456789);
// Test Dates and Times as timestamps.
char d1[] = "2012-01-20";
char d2[] = "1990-10-20";
TimestampValue dv1(d1, strlen(d1));
TimestampValue dv2(d2, strlen(d2));
EXPECT_NE(dv1, dv2);
EXPECT_LT(dv1, v1);
EXPECT_LE(dv1, v1);
EXPECT_GT(v1, dv1);
EXPECT_GE(v1, dv1);
EXPECT_NE(dv2, v2);
EXPECT_EQ(dv1.date().year(), 2012);
EXPECT_EQ(dv1.date().month(), 1);
EXPECT_EQ(dv1.date().day(), 20);
char t1[] = "10:11:12.123456789";
char t2[] = "00:00:00";
TimestampValue tv1(t1, strlen(t1));
TimestampValue tv2(t2, strlen(t2));
EXPECT_NE(tv1, tv2);
EXPECT_NE(tv1, v2);
EXPECT_EQ(tv1.time().hours(), 10);
EXPECT_EQ(tv1.time().minutes(), 11);
EXPECT_EQ(tv1.time().seconds(), 12);
EXPECT_EQ(tv1.time().fractional_seconds(), 123456789);
EXPECT_EQ(tv2.time().fractional_seconds(), 0);
// Test variable fraction lengths
const char* FRACTION_MAX_STR = "123456789";
const char* TEST_VALS[] = { "2013-12-10 12:04:17.", "2013-12-10T12:04:17.",
"12:04:17." };
const int TEST_VAL_CNT = sizeof(TEST_VALS) / sizeof(char*);
for (int i = 0; i < TEST_VAL_CNT; ++i) {
const int VAL_LEN = strlen(TEST_VALS[i]);
int fraction_len = strlen(FRACTION_MAX_STR);
char frac_buff[VAL_LEN + fraction_len + 1];
while (fraction_len > 0) {
memcpy(frac_buff, TEST_VALS[i], VAL_LEN);
memcpy(frac_buff + VAL_LEN, FRACTION_MAX_STR, fraction_len);
*(frac_buff + VAL_LEN + fraction_len) = '\0';
TimestampValue tv_frac(frac_buff, strlen(frac_buff));
if (frac_buff[4] == '-') {
EXPECT_EQ(tv_frac.date().year(), 2013);
EXPECT_EQ(tv_frac.date().month(), 12);
EXPECT_EQ(tv_frac.date().day(), 10);
}
EXPECT_EQ(tv_frac.time().hours(), 12);
EXPECT_EQ(tv_frac.time().minutes(), 4);
EXPECT_EQ(tv_frac.time().seconds(), 17);
StringParser::ParseResult status;
int32_t fraction =
StringParser::StringToInt<int32_t>(FRACTION_MAX_STR, fraction_len, &status);
EXPECT_TRUE(StringParser::PARSE_SUCCESS == status);
for (int i = fraction_len; i < 9; ++i) fraction *= 10;
EXPECT_EQ(tv_frac.time().fractional_seconds(), fraction);
--fraction_len;
}
}
// Bad formats
char b1[] = "1990-10 10:10:10.123456789";
TimestampValue bv1(b1, strlen(b1));
boost::gregorian::date not_a_date;
EXPECT_EQ(bv1.date(), not_a_date);
EXPECT_EQ(bv1.time(), not_a_date_time);
char b2[] = "1991-10-10 99:10:10.123456789";
TimestampValue bv2(b2, strlen(b2));
EXPECT_EQ(bv2.time(), not_a_date_time);
EXPECT_EQ(bv2.date(), not_a_date);
char b3[] = "1990-10- 10:10:10.123456789";
TimestampValue bv3(b3, strlen(b3));
EXPECT_EQ(bv3.date(), not_a_date);
EXPECT_EQ(bv3.time(), not_a_date_time);
char b4[] = "10:1010.123456789";
TimestampValue bv4(b4, strlen(b4));
EXPECT_EQ(bv4.date(), not_a_date);
EXPECT_EQ(bv4.time(), not_a_date_time);
char b5[] = "10:11:12.123456 1991-10-10";
TimestampValue bv5(b5, strlen(b5));
EXPECT_EQ(bv5.date(), not_a_date);
EXPECT_EQ(bv5.time(), not_a_date_time);
char b6[] = "2012-01-20 01:10:00.123.466";
TimestampValue bv6(b6, strlen(b6));
EXPECT_EQ(bv6.date(), not_a_date);
EXPECT_EQ(bv6.time(), not_a_date_time);
char b7[] = "2012-01-20 01:10:00.123 477 ";
TimestampValue bv7(b7, strlen(b7));
EXPECT_EQ(bv7.date(), not_a_date);
EXPECT_EQ(bv7.time(), not_a_date_time);
// Test custom formats by generating all permutations of tokens to check parsing and
// formatting is behaving correctly (position of tokens should be irrelevant). Note
// that separators are also combined with EACH token permutation to get the widest
// coverage on formats.
const int YEAR = 2013;
const int MONTH = 10;
const int DAY = 14;
const int HOURS = 14;
const int MINS = 25;
const int SECS = 44;
const int FRAC = 123456789;
// Test parsing/formatting with numeric date/time tokens
vector<TimestampToken> dt_toks = list_of
(TimestampToken("dd", DAY))(TimestampToken("MM", MONTH))
(TimestampToken("yyyy", YEAR))(TimestampToken("HH", HOURS))
(TimestampToken("mm", MINS))(TimestampToken("ss", SECS))
(TimestampToken("SSSSSSSSS", FRAC));
TestTimestampTokens(&dt_toks, YEAR, MONTH, DAY, HOURS, MINS, SECS, FRAC);
// Test literal months
const char* months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
"Sep", "Oct", "Nov", "Dec"
};
// Test parsing/formatting of literal months (short)
const int MONTH_CNT = (sizeof(months) / sizeof(char**));
for (int i = 0; i < MONTH_CNT; ++i) {
// Test parsing/formatting with short literal months
vector<TimestampToken> dt_lm_toks = list_of
(TimestampToken("dd", DAY))(TimestampToken("MMM", i + 1, months[i]))
(TimestampToken("yyyy", YEAR))(TimestampToken("HH", HOURS))
(TimestampToken("mm", MINS))(TimestampToken("ss", SECS))
(TimestampToken("SSSSSSSSS", FRAC));
TestTimestampTokens(&dt_lm_toks, YEAR, i + 1, DAY, HOURS, MINS, SECS, FRAC);
}
// Test parsing/formatting of complex date/time formats
vector<TimestampTC> test_cases = boost::assign::list_of
// Test case on literal short months
(TimestampTC("yyyy-MMM-dd", "2013-OCT-01", false, true, false, 2013, 10, 1))
// Test case on literal short months
(TimestampTC("yyyy-MMM-dd", "2013-oct-01", false, true, false, 2013, 10, 1))
// Test case on literal short months
(TimestampTC("yyyy-MMM-dd", "2013-oCt-01", false, true, false, 2013, 10, 1))
// Test padding on numeric and literal tokens (short)
(TimestampTC("MMMyyyyyydd", "Apr00201309", true, true, false, 2013, 4, 9))
// Test duplicate tokens
(TimestampTC("yyyy MM dd ddMMMyyyy (HH:mm:ss.SSSS)",
"2013 05 12 16Apr1952 (16:53:21.1234)", false, true, true, 1952, 4, 16, 16,
53, 21, 123400000))
// Test missing separator on short date format
(TimestampTC("Myyd", "4139", true, true))
// Test bad year format
(TimestampTC("YYYYmmdd", "20131001"))
// Test unknown formatting character
(TimestampTC("yyyyUUdd", "2013001001"))
// Test markers
(TimestampTC("TTZZ", "TTZZ"))
// Test numeric formatting character
(TimestampTC("yyyyMM1dd", "201301111"))
// Test out of range year
(TimestampTC("yyyyyMMdd", "120130101", false, true))
// Test out of range month
(TimestampTC("yyyyMMdd", "20131301", false, true))
// Test out of range month
(TimestampTC("yyyyMMdd", "20130001", false, true))
// Test out of range day
(TimestampTC("yyyyMMdd", "20130132", false, true))
// Test out of range day
(TimestampTC("yyyyMMdd", "20130100", false, true))
// Test out of range hour
(TimestampTC("HH:mm:ss", "24:01:01", false, true))
// Test out of range minute
(TimestampTC("HH:mm:ss", "23:60:01", false, true))
// Test out of range second
(TimestampTC("HH:mm:ss", "23:01:60", false, true))
// Test characters where numbers should be
(TimestampTC("HH:mm:ss", "aa:01:01", false, true))
// Test missing year
(TimestampTC("MMdd", "1201", false, true))
// Test missing month
(TimestampTC("yyyydd", "201301", false, true))
// Test missing month
(TimestampTC("yyyymm", "201301", false, true))
// Test short year token
(TimestampTC("y-MM-dd", "2013-11-13", false, true, false, 2013, 11, 13))
(TimestampTC("y-MM-dd", "13-11-13", false, true, false, 2013, 11, 13))
// Test short month token
(TimestampTC("yyyy-M-dd", "2013-11-13", false, true, false, 2013, 11, 13))
(TimestampTC("yyyy-M-dd", "2013-1-13", false, true, false, 2013, 1, 13))
// Test short day token
(TimestampTC("yyyy-MM-d", "2013-11-13", false, true, false, 2013, 11, 13))
(TimestampTC("yyyy-MM-d", "2013-11-3", false, true, false, 2013, 11, 3))
// Test short all date tokens
(TimestampTC("y-M-d", "2013-11-13", false, true, false, 2013, 11, 13))
(TimestampTC("y-M-d", "13-1-3", false, true, false, 2013, 1, 3))
// Test short hour token
(TimestampTC("H:mm:ss", "14:24:34", false, false, true, 0, 0, 0, 14, 24, 34))
(TimestampTC("H:mm:ss", "4:24:34", false, false, true, 0, 0, 0, 4, 24, 34))
// Test short minute token
(TimestampTC("HH:m:ss", "14:24:34", false, false, true, 0, 0, 0, 14, 24, 34))
(TimestampTC("HH:m:ss", "1:24:34", false, true))
// Test short second token
(TimestampTC("HH:mm:s", "14:24:34", false, false, true, 0, 0, 0, 14, 24, 34))
// Test short all time tokens
(TimestampTC("H:m:s", "11:22:33", false, false, true, 0, 0, 0, 11, 22, 33))
(TimestampTC("H:m:s", "1:2:3", false, false, true, 0, 0, 0, 1, 2, 3))
// Test short fraction token
(TimestampTC("HH:mm:ss:S", "14:24:34:1234", false, false, true, 0, 0, 0, 14, 24, 34,
123400000));
// Loop through custom parse/format test cases and execute each one. Each test case
// will be explicitly set with a pass/fail expectation related to either the format
// or literal value.
for (int i = 0; i < test_cases.size(); ++i) {
TimestampTC test_case = test_cases[i];
DateTimeFormatContext dt_ctx(test_case.fmt, strlen(test_case.fmt));
bool parse_result = TimestampParser::ParseFormatTokens(&dt_ctx);
if (test_case.fmt_should_fail) {
EXPECT_FALSE(parse_result) << "TC: " << i;
continue;
} else {
ASSERT_TRUE(parse_result) << "TC: " << i;
}
TimestampValue cust_tv(test_case.str, strlen(test_case.str), dt_ctx);
boost::gregorian::date cust_date = cust_tv.date();
boost::posix_time::time_duration cust_time = cust_tv.time();
if (test_case.str_should_fail) {
EXPECT_EQ(not_a_date, cust_date) << "TC: " << i;
EXPECT_EQ(cust_time, not_a_date_time) << "TC: " << i;
continue;
}
// Check that we have something valid in the timestamp value
EXPECT_TRUE((!cust_date.is_special())
|| (cust_time != not_a_date_time)) << "TC: " << i;
// Check the date component (based on any date format tokens being present)
if (test_case.fmt_has_date_toks) {
EXPECT_NE(cust_date, not_a_date) << "TC: " << i;
EXPECT_EQ(test_case.expected_year, cust_date.year()) << "TC: " << i;
EXPECT_EQ(test_case.expected_month, cust_date.month()) << "TC: " << i;
EXPECT_EQ(test_case.expected_day, cust_date.day()) << "TC: " << i;
} else {
EXPECT_EQ(not_a_date, cust_date) << "TC: " << i;
}
// Check the time component (based on any time format tokens being present). Note
// that if the date is specified, the time will at least be 00:00:00.0
if (test_case.fmt_has_time_toks || test_case.fmt_has_date_toks) {
EXPECT_NE(cust_time, not_a_date_time) << "TC: " << i;
EXPECT_EQ(test_case.expected_hours, cust_time.hours()) << "TC: " << i;
EXPECT_EQ(test_case.expected_minutes, cust_time.minutes()) << "TC: " << i;
EXPECT_EQ(test_case.expected_seconds, cust_time.seconds()) << "TC: " << i;
EXPECT_EQ(test_case.expected_fraction, cust_time.fractional_seconds()) << "TC: "
<< i;
if (!test_case.should_format) continue;
int buff_len = dt_ctx.fmt_out_len + 1;
char buff[buff_len];
int actual_len = cust_tv.Format(dt_ctx, buff_len, buff);
EXPECT_GT(actual_len, 0) << "TC: " << i;
EXPECT_LE(actual_len, dt_ctx.fmt_len) << "TC: " << i;
EXPECT_EQ(string(test_case.str, strlen(test_case.str)), string(buff, actual_len))
<< "TC: " << i;
} else {
EXPECT_EQ(cust_time, not_a_date_time) << test_case.fmt << " " << test_case.str;
}
}
// Test complex formatting of date/times
vector<TimestampFormatTC> fmt_test_cases = list_of
(TimestampFormatTC(1382337792, "yyyy-MM-dd HH:mm:ss.SSSSSSSSS",
"2013-10-21 06:43:12.000000000"))
// Test just formatting time tokens
(TimestampFormatTC(1382337792, "HH:mm:ss.SSSSSSSSS", "06:43:12.000000000"))
(TimestampFormatTC(0, "yyyy-MM-ddTHH:mm:SS.SSSSSSSSSZ",
"1970-01-01T00:00:00.000000000Z"))
// Test just formatting date tokens
(TimestampFormatTC(965779200, "yyyy-MM-dd", "2000-08-09"))
// Test short form date tokens
(TimestampFormatTC(965779200, "yyyy-M-d", "2000-8-9"))
// Test short form tokens on wide dates
(TimestampFormatTC(1382337792, "d", "21"))
// Test month expansion
(TimestampFormatTC(965779200, "MMM/MM/M", "Aug/08/8"))
// Test padding on single digits
(TimestampFormatTC(965779200, "dddddd/dd/d", "000009/09/9"))
// Test padding on double digits
(TimestampFormatTC(1382337792, "dddddd/dd/dd", "000021/21/21"))
// Test just formatting time tokens on a ts value generated from a date
(TimestampFormatTC(965779200, "HH:mm:ss", "00:00:00"));
// Loop through format test cases
for (int i = 0; i < fmt_test_cases.size(); ++i) {
TimestampFormatTC test_case = fmt_test_cases[i];
DateTimeFormatContext dt_ctx(test_case.fmt, strlen(test_case.fmt));
ASSERT_TRUE(TimestampParser::ParseFormatTokens(&dt_ctx)) << "TC: " << i;
TimestampValue cust_tv(test_case.ts);
EXPECT_NE(cust_tv.date(), not_a_date) << "TC: " << i;
EXPECT_NE(cust_tv.time(), not_a_date_time) << "TC: " << i;
EXPECT_GE(dt_ctx.fmt_out_len, dt_ctx.fmt_len);
int buff_len = dt_ctx.fmt_out_len + 1;
char buff[buff_len];
int actual_len = cust_tv.Format(dt_ctx, buff_len, buff);
EXPECT_GT(actual_len, 0) << "TC: " << i;
EXPECT_LE(actual_len, dt_ctx.fmt_out_len) << "TC: " << i;
EXPECT_EQ(string(buff, actual_len),
string(test_case.str, strlen(test_case.str))) << "TC: " << i;
}
// Test edge cases
TimestampValue min_date = TimestampValue("1400-01-01", 10);
EXPECT_TRUE(min_date.HasDate());
EXPECT_TRUE(min_date.HasTime());
time_t tm_min;
EXPECT_TRUE(min_date.ToUnixTime(&tm_min));
EXPECT_EQ(-17987443200, tm_min);
EXPECT_EQ("1400-01-01 00:00:00", TimestampValue(-17987443200).DebugString());
TimestampValue too_early(-17987443201);
EXPECT_FALSE(too_early.HasDate());
EXPECT_FALSE(too_early.HasTime());
// Apparently 5 digit years don't parse (at least by default) but can be printed.
// Boost's documented says the max year supported is 9,999 but 10K seems to be
// the actual limit.
TimestampValue max_date =
TimestampValue(date(10000, Dec, 31), time_duration(23, 59, 59));
EXPECT_TRUE(max_date.HasDate());
EXPECT_TRUE(max_date.HasTime());
time_t tm_max;
EXPECT_TRUE(max_date.ToUnixTime(&tm_max));
EXPECT_EQ(253433923199, tm_max);
EXPECT_EQ("10000-12-31 23:59:59", TimestampValue(253433923199).DebugString());
TimestampValue too_late(253433923200);
EXPECT_FALSE(too_late.HasDate());
EXPECT_FALSE(too_late.HasTime());
// Regression tests for IMPALA-1676, Unix times overflow int32 during year 2038
EXPECT_EQ("2038-01-19 03:14:08", TimestampValue(2147483648).DebugString());
EXPECT_EQ("2038-01-19 03:14:09", TimestampValue(2147483649).DebugString());
// Test Unix time as a float
double result;
EXPECT_TRUE(TimestampValue("2013-10-21 06:43:12.07", 22).ToSubsecondUnixTime(&result));
EXPECT_EQ(1382337792.07, result);
EXPECT_EQ("1970-01-01 00:00:00.008000000", TimestampValue(0.008).DebugString());
}
}
IMPALA_TEST_MAIN();