mirror of
https://github.com/rbock/sqlpp11.git
synced 2024-11-15 20:31:16 +08:00
Replace regex-based date/time parsing with manual parser (#520)
* Replace regex-based date/time string parsing with manually written parsing code. * Add date/time parser tests.
This commit is contained in:
parent
1cd47c77dd
commit
25bca54ba7
@ -27,100 +27,237 @@
|
|||||||
* POSSIBILITY OF SUCH DAMAGE.
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <regex>
|
#include <sqlpp11/chrono.h>
|
||||||
|
|
||||||
|
#include <cctype>
|
||||||
|
|
||||||
namespace sqlpp
|
namespace sqlpp
|
||||||
{
|
{
|
||||||
namespace detail
|
namespace detail
|
||||||
{
|
{
|
||||||
// Parse a date string formatted as YYYY-MM-DD
|
inline bool parse_unsigned(int& value, const char*& input, int length)
|
||||||
//
|
|
||||||
inline bool parse_string_date(::sqlpp::chrono::day_point& value, const char* date_string)
|
|
||||||
{
|
{
|
||||||
static const std::regex rx{"(\\d{4})-(\\d{2})-(\\d{2})"};
|
value = 0;
|
||||||
std::cmatch mr;
|
auto new_input = input;
|
||||||
if (std::regex_match(date_string, mr, rx) == false)
|
while (length--)
|
||||||
{
|
{
|
||||||
return false;
|
auto ch = *new_input++;
|
||||||
|
if (std::isdigit(ch) == false)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
value = value * 10 + ch - '0';
|
||||||
}
|
}
|
||||||
value = ::sqlpp::chrono::day_point{
|
input = new_input;
|
||||||
::date::year{std::atoi(date_string + mr.position(1))} / // Year
|
|
||||||
std::atoi(date_string + mr.position(2)) / // Month
|
|
||||||
std::atoi(date_string + mr.position(3)) // Day of month
|
|
||||||
};
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse a date string formatted as YYYY-MM-DD HH:MM:SS.US TZ
|
inline bool parse_character(const char*& input, char ch)
|
||||||
// .US are optional fractional seconds, up to 6 digits in length
|
|
||||||
// TZ is an optional time zone offset formatted as +HH[:MM] or -HH[:MM]
|
|
||||||
//
|
|
||||||
inline bool parse_string_date_time(::sqlpp::chrono::microsecond_point& value, const char* date_time_string)
|
|
||||||
{
|
{
|
||||||
static const std::regex rx{
|
if (*input != ch)
|
||||||
"(\\d{4})-(\\d{2})-(\\d{2}) "
|
|
||||||
"(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d{1,6}))?"
|
|
||||||
"(?:([+-])(\\d{2})(?::(\\d{2})(?::(\\d{2}))?)?)?"
|
|
||||||
};
|
|
||||||
std::cmatch mr;
|
|
||||||
if (std::regex_match(date_time_string, mr, rx) == false)
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
value =
|
++input;
|
||||||
::sqlpp::chrono::day_point{
|
return true;
|
||||||
::date::year{std::atoi(date_time_string + mr.position(1))} / // Year
|
}
|
||||||
std::atoi(date_time_string + mr.position(2)) / // Month
|
|
||||||
std::atoi(date_time_string + mr.position(3)) // Day of month
|
inline bool parse_yyyy_mm_dd(sqlpp::chrono::day_point& dp, const char*& input)
|
||||||
} +
|
{
|
||||||
std::chrono::hours{std::atoi(date_time_string + mr.position(4))} + // Hour
|
auto new_input = input;
|
||||||
std::chrono::minutes{std::atoi(date_time_string + mr.position(5))} + // Minute
|
int year, month, day;
|
||||||
std::chrono::seconds{std::atoi(date_time_string + mr.position(6))} + // Second
|
if ((parse_unsigned(year, new_input, 4) == false) || (parse_character(new_input, '-') == false) ||
|
||||||
::std::chrono::microseconds{ // Second fraction
|
(parse_unsigned(month, new_input, 2) == false) || (parse_character(new_input, '-') == false) ||
|
||||||
mr[7].matched ? std::stoi((mr[7].str() + "000000").substr(0, 6)) : 0
|
(parse_unsigned(day, new_input, 2) == false))
|
||||||
};
|
|
||||||
if (mr[8].matched)
|
|
||||||
{
|
{
|
||||||
const auto tz_sign = (date_time_string[mr.position(8)] == '+') ? 1 : -1;
|
return false;
|
||||||
const auto tz_offset =
|
}
|
||||||
std::chrono::hours{std::atoi(date_time_string + mr.position(9))} +
|
dp = ::date::year{year} / month / day;
|
||||||
std::chrono::minutes{mr[10].matched ? std::atoi(date_time_string + mr.position(10)) : 0} +
|
input = new_input;
|
||||||
std::chrono::seconds{mr[11].matched ? std::atoi(date_time_string + mr.position(11)) : 0};
|
return true;
|
||||||
value -= tz_sign * tz_offset;
|
}
|
||||||
|
|
||||||
|
inline bool parse_hh_mm_ss(std::chrono::microseconds& us, const char*& input)
|
||||||
|
{
|
||||||
|
auto new_input = input;
|
||||||
|
int hour, minute, second;
|
||||||
|
if ((parse_unsigned(hour, new_input, 2) == false) || (parse_character(new_input, ':') == false) ||
|
||||||
|
(parse_unsigned(minute, new_input, 2) == false) || (parse_character(new_input, ':') == false) ||
|
||||||
|
(parse_unsigned(second, new_input, 2) == false))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Strings that have valid format but year, month and/or day values that fall outside of the
|
||||||
|
// correct ranges are still mapped to day_point values. For the exact rules of the mapping see
|
||||||
|
// https://en.cppreference.com/w/cpp/chrono/year_month_day/operator_days
|
||||||
|
us = std::chrono::hours{hour} + std::chrono::minutes{minute} + std::chrono::seconds{second};
|
||||||
|
input = new_input;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool parse_ss_fraction(std::chrono::microseconds& us, const char*& input)
|
||||||
|
{
|
||||||
|
auto new_input = input;
|
||||||
|
if (parse_character(new_input, '.') == false)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int value = 0;
|
||||||
|
int len_max = 6;
|
||||||
|
int len_actual;
|
||||||
|
for (len_actual = 0; (len_actual < len_max) && std::isdigit(*new_input); ++len_actual, ++new_input)
|
||||||
|
{
|
||||||
|
value = value * 10 + *new_input - '0';
|
||||||
|
}
|
||||||
|
if (len_actual == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (; len_actual < len_max; ++len_actual)
|
||||||
|
{
|
||||||
|
value *= 10;
|
||||||
|
}
|
||||||
|
us = std::chrono::microseconds{value};
|
||||||
|
input = new_input;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool parse_tz(std::chrono::microseconds& offset, const char*& input)
|
||||||
|
{
|
||||||
|
auto new_input = input;
|
||||||
|
int tz_sign;
|
||||||
|
if (parse_character(new_input, '+'))
|
||||||
|
{
|
||||||
|
tz_sign = 1;
|
||||||
|
}
|
||||||
|
else if (parse_character(new_input, '-'))
|
||||||
|
{
|
||||||
|
tz_sign = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int hour;
|
||||||
|
if (parse_unsigned(hour, new_input, 2) == false)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
offset = tz_sign * std::chrono::hours{hour};
|
||||||
|
input = new_input;
|
||||||
|
int minute;
|
||||||
|
if ((parse_character(new_input, ':') == false) || (parse_unsigned(minute, new_input, 2) == false))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
offset += tz_sign * std::chrono::minutes{minute};
|
||||||
|
input = new_input;
|
||||||
|
int second;
|
||||||
|
if ((parse_character(new_input, ':') == false) || (parse_unsigned(second, new_input, 2) == false))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
offset += tz_sign * std::chrono::seconds{second};
|
||||||
|
input = new_input;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool parse_hh_mm_ss_us_tz(std::chrono::microseconds& us, const char*& input)
|
||||||
|
{
|
||||||
|
if (parse_hh_mm_ss(us, input) == false)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::chrono::microseconds fraction;
|
||||||
|
if (parse_ss_fraction(fraction, input))
|
||||||
|
{
|
||||||
|
us += fraction;
|
||||||
|
}
|
||||||
|
std::chrono::microseconds tz_offset;
|
||||||
|
if (parse_tz(tz_offset, input))
|
||||||
|
{
|
||||||
|
us -= tz_offset;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse a time string formatted as HH:MM:SS[.US][ TZ]
|
// Parse timestamp formatted as YYYY-MM-DD HH:MM:SS.U+HH:MM:SS
|
||||||
// .US is up to 6 digits in length
|
// The microseconds and timezone offset are optional
|
||||||
// TZ is an optional time zone offset formatted as +HH[:MM] or -HH[:MM]
|
|
||||||
//
|
//
|
||||||
inline bool parse_string_time_of_day(::std::chrono::microseconds& value, const char* time_string)
|
inline bool parse_timestamp(sqlpp::chrono::microsecond_point& tp, const char* date_time_string)
|
||||||
{
|
{
|
||||||
static const std::regex rx{
|
sqlpp::chrono::day_point parsed_ymd;
|
||||||
"(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d{1,6}))?"
|
std::chrono::microseconds parsed_tod;
|
||||||
"(?:([+-])(\\d{2})(?::(\\d{2})(?::(\\d{2}))?)?)?"
|
if ((parse_yyyy_mm_dd(parsed_ymd, date_time_string) == false) ||
|
||||||
};
|
(parse_character(date_time_string, ' ') == false) ||
|
||||||
std::cmatch mr;
|
(parse_hh_mm_ss_us_tz(parsed_tod, date_time_string) == false))
|
||||||
if (std::regex_match (time_string, mr, rx) == false)
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
value =
|
if (*date_time_string)
|
||||||
std::chrono::hours{std::atoi(time_string + mr.position(1))} + // Hour
|
|
||||||
std::chrono::minutes{std::atoi(time_string + mr.position(2))} + // Minute
|
|
||||||
std::chrono::seconds{std::atoi(time_string + mr.position(3))} + // Second
|
|
||||||
::std::chrono::microseconds{ // Second fraction
|
|
||||||
mr[4].matched ? std::stoi((mr[4].str() + "000000").substr(0, 6)) : 0
|
|
||||||
};
|
|
||||||
if (mr[5].matched)
|
|
||||||
{
|
{
|
||||||
const auto tz_sign = (time_string[mr.position(5)] == '+') ? 1 : -1;
|
return false;
|
||||||
const auto tz_offset =
|
}
|
||||||
std::chrono::hours{std::atoi(time_string + mr.position(6))} +
|
tp = parsed_ymd + parsed_tod;
|
||||||
std::chrono::minutes{mr[7].matched ? std::atoi(time_string + mr.position(7)) : 0} +
|
return true;
|
||||||
std::chrono::seconds{mr[8].matched ? std::atoi(time_string + mr.position(8)) : 0};
|
}
|
||||||
value -= tz_sign * tz_offset;
|
|
||||||
|
// Parse date string formatted as YYYY-MM-DD
|
||||||
|
//
|
||||||
|
inline bool parse_date(sqlpp::chrono::day_point& dp, const char* date_string)
|
||||||
|
{
|
||||||
|
if (parse_yyyy_mm_dd(dp, date_string) == false)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (*date_string)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse time string formatted as YYYY-MM-DD HH:MM:SS.U+HH:MM:SS
|
||||||
|
// The time-of-day part is optional
|
||||||
|
//
|
||||||
|
inline bool parse_date_or_timestamp(sqlpp::chrono::microsecond_point& tp, const char* date_time_string)
|
||||||
|
{
|
||||||
|
sqlpp::chrono::day_point parsed_ymd;
|
||||||
|
if (parse_yyyy_mm_dd(parsed_ymd, date_time_string) == false)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (*date_time_string == 0)
|
||||||
|
{
|
||||||
|
tp = parsed_ymd;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
std::chrono::microseconds parsed_tod;
|
||||||
|
if ((parse_character(date_time_string, ' ') == false) ||
|
||||||
|
(parse_hh_mm_ss_us_tz(parsed_tod, date_time_string) == false))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (*date_time_string == 0)
|
||||||
|
{
|
||||||
|
tp = parsed_ymd + parsed_tod;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse time of day string formatted as HH:MM:SS.U+HH:MM:SS
|
||||||
|
// The microseconds and timezone offset are optional
|
||||||
|
//
|
||||||
|
inline bool parse_time_of_day(std::chrono::microseconds& us, const char* time_string)
|
||||||
|
{
|
||||||
|
if (parse_hh_mm_ss_us_tz(us, time_string) == false)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (*time_string)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -152,7 +152,7 @@ namespace sqlpp
|
|||||||
if (_handle->debug)
|
if (_handle->debug)
|
||||||
std::cerr << "MySQL debug: date string: " << date_string << std::endl;
|
std::cerr << "MySQL debug: date string: " << date_string << std::endl;
|
||||||
|
|
||||||
if (::sqlpp::detail::parse_string_date(*value, date_string) == false)
|
if (::sqlpp::detail::parse_date(*value, date_string) == false)
|
||||||
{
|
{
|
||||||
if (_handle->debug)
|
if (_handle->debug)
|
||||||
std::cerr << "MySQL debug: invalid date result: " << date_string << std::endl;
|
std::cerr << "MySQL debug: invalid date result: " << date_string << std::endl;
|
||||||
@ -175,7 +175,7 @@ namespace sqlpp
|
|||||||
if (_handle->debug)
|
if (_handle->debug)
|
||||||
std::cerr << "MySQL debug: date_time string: " << date_time_string << std::endl;
|
std::cerr << "MySQL debug: date_time string: " << date_time_string << std::endl;
|
||||||
|
|
||||||
if (::sqlpp::detail::parse_string_date_time(*value, date_time_string) == false)
|
if (::sqlpp::detail::parse_timestamp(*value, date_time_string) == false)
|
||||||
{
|
{
|
||||||
if (_handle->debug)
|
if (_handle->debug)
|
||||||
std::cerr << "MySQL debug: invalid date_time result: " << date_time_string << std::endl;
|
std::cerr << "MySQL debug: invalid date_time result: " << date_time_string << std::endl;
|
||||||
|
@ -239,7 +239,7 @@ namespace sqlpp
|
|||||||
{
|
{
|
||||||
std::cerr << "PostgreSQL debug: date string: " << date_string << std::endl;
|
std::cerr << "PostgreSQL debug: date string: " << date_string << std::endl;
|
||||||
}
|
}
|
||||||
if (::sqlpp::detail::parse_string_date(*value, date_string) == false)
|
if (::sqlpp::detail::parse_date(*value, date_string) == false)
|
||||||
{
|
{
|
||||||
if (_handle->debug())
|
if (_handle->debug())
|
||||||
{
|
{
|
||||||
@ -269,7 +269,7 @@ namespace sqlpp
|
|||||||
{
|
{
|
||||||
std::cerr << "PostgreSQL debug: got date_time string: " << date_string << std::endl;
|
std::cerr << "PostgreSQL debug: got date_time string: " << date_string << std::endl;
|
||||||
}
|
}
|
||||||
if (::sqlpp::detail::parse_string_date_time(*value, date_string) == false)
|
if (::sqlpp::detail::parse_timestamp(*value, date_string) == false)
|
||||||
{
|
{
|
||||||
if (_handle->debug())
|
if (_handle->debug())
|
||||||
{
|
{
|
||||||
@ -301,7 +301,7 @@ namespace sqlpp
|
|||||||
std::cerr << "PostgreSQL debug: got time string: " << time_string << std::endl;
|
std::cerr << "PostgreSQL debug: got time string: " << time_string << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (::sqlpp::detail::parse_string_time_of_day(*value, time_string) == false)
|
if (::sqlpp::detail::parse_time_of_day(*value, time_string) == false)
|
||||||
{
|
{
|
||||||
if (_handle->debug()) {
|
if (_handle->debug()) {
|
||||||
std::cerr << "PostgreSQL debug: got invalid time '" << time_string << "'" << std::endl;
|
std::cerr << "PostgreSQL debug: got invalid time '" << time_string << "'" << std::endl;
|
||||||
|
@ -34,7 +34,6 @@
|
|||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <regex>
|
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
#include <iso646.h>
|
#include <iso646.h>
|
||||||
@ -46,40 +45,6 @@ namespace sqlpp
|
|||||||
{
|
{
|
||||||
namespace sqlite3
|
namespace sqlite3
|
||||||
{
|
{
|
||||||
namespace detail
|
|
||||||
{
|
|
||||||
// Parse a date string formatted as YYYY-MM-DD[ HH:MM:SS[.US]]
|
|
||||||
//
|
|
||||||
inline bool parse_string_date_opt_time(::sqlpp::chrono::microsecond_point& value, const char* date_time_string)
|
|
||||||
{
|
|
||||||
static const std::regex rx{
|
|
||||||
"(\\d{4})-(\\d{2})-(\\d{2})"
|
|
||||||
"(?: (\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d{1,6}))?)?"
|
|
||||||
};
|
|
||||||
std::cmatch mr;
|
|
||||||
if (std::regex_match(date_time_string, mr, rx) == false)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
value = ::sqlpp::chrono::day_point{
|
|
||||||
::date::year{std::atoi(date_time_string + mr.position(1))} / // Year
|
|
||||||
std::atoi(date_time_string + mr.position(2)) / // Month
|
|
||||||
std::atoi(date_time_string + mr.position(3)) // Day of month
|
|
||||||
};
|
|
||||||
if (mr[4].matched)
|
|
||||||
{
|
|
||||||
value +=
|
|
||||||
std::chrono::hours{std::atoi(date_time_string + mr.position(4))} + // Hour
|
|
||||||
std::chrono::minutes{std::atoi(date_time_string + mr.position(5))} + // Minute
|
|
||||||
std::chrono::seconds{std::atoi(date_time_string + mr.position(6))} + // Second
|
|
||||||
::std::chrono::microseconds{ // Second fraction
|
|
||||||
mr[7].matched ? std::stoi((mr[7].str() + "000000").substr(0, 6)) : 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
class SQLPP11_SQLITE3_EXPORT bind_result_t
|
class SQLPP11_SQLITE3_EXPORT bind_result_t
|
||||||
{
|
{
|
||||||
std::shared_ptr<detail::prepared_statement_handle_t> _handle;
|
std::shared_ptr<detail::prepared_statement_handle_t> _handle;
|
||||||
@ -208,7 +173,7 @@ namespace sqlpp
|
|||||||
reinterpret_cast<const char*>(sqlite3_column_text(_handle->sqlite_statement, static_cast<int>(index)));
|
reinterpret_cast<const char*>(sqlite3_column_text(_handle->sqlite_statement, static_cast<int>(index)));
|
||||||
if (_handle->debug)
|
if (_handle->debug)
|
||||||
std::cerr << "Sqlite3 debug: date string: " << date_string << std::endl;
|
std::cerr << "Sqlite3 debug: date string: " << date_string << std::endl;
|
||||||
if (::sqlpp::detail::parse_string_date(*value, date_string) == false)
|
if (::sqlpp::detail::parse_date(*value, date_string) == false)
|
||||||
{
|
{
|
||||||
if (_handle->debug)
|
if (_handle->debug)
|
||||||
std::cerr << "Sqlite3 debug: invalid date result: " << date_string << std::endl;
|
std::cerr << "Sqlite3 debug: invalid date result: " << date_string << std::endl;
|
||||||
@ -232,7 +197,7 @@ namespace sqlpp
|
|||||||
if (_handle->debug)
|
if (_handle->debug)
|
||||||
std::cerr << "Sqlite3 debug: date_time string: " << date_time_string << std::endl;
|
std::cerr << "Sqlite3 debug: date_time string: " << date_time_string << std::endl;
|
||||||
// We treat DATETIME fields as containing either date+time or just date.
|
// We treat DATETIME fields as containing either date+time or just date.
|
||||||
if (detail::parse_string_date_opt_time(*value, date_time_string) == false)
|
if (::sqlpp::detail::parse_date_or_timestamp(*value, date_time_string) == false)
|
||||||
{
|
{
|
||||||
if (_handle->debug)
|
if (_handle->debug)
|
||||||
std::cerr << "Sqlite3 debug: invalid date_time result: " << date_time_string << std::endl;
|
std::cerr << "Sqlite3 debug: invalid date_time result: " << date_time_string << std::endl;
|
||||||
|
@ -33,6 +33,7 @@ set(test_files
|
|||||||
BooleanExpression.cpp
|
BooleanExpression.cpp
|
||||||
CustomQuery.cpp
|
CustomQuery.cpp
|
||||||
DateTime.cpp
|
DateTime.cpp
|
||||||
|
DateTimeParser.cpp
|
||||||
Interpret.cpp
|
Interpret.cpp
|
||||||
Insert.cpp
|
Insert.cpp
|
||||||
Remove.cpp
|
Remove.cpp
|
||||||
|
330
tests/core/usage/DateTimeParser.cpp
Normal file
330
tests/core/usage/DateTimeParser.cpp
Normal file
@ -0,0 +1,330 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023, Vesselin Atanasov
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
* are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||||
|
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||||
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||||
|
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||||
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <sqlpp11/detail/parse_date_time.h>
|
||||||
|
#include <sqlpp11/sqlpp11.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
std::chrono::microseconds build_tod(int hour = 0, int minute = 0, int second = 0, int us = 0, bool tz_plus = true, int tz_hour = 0, int tz_minute = 0, int tz_second = 0)
|
||||||
|
{
|
||||||
|
std::chrono::microseconds result{0};
|
||||||
|
// We add time components one by one to the resulting microsecond_point in order to avoid going through temporary time_point values
|
||||||
|
// with small bitsize which could cause in integer overflow.
|
||||||
|
result += std::chrono::hours{hour};
|
||||||
|
result += std::chrono::minutes{minute};
|
||||||
|
result += std::chrono::seconds{second};
|
||||||
|
result += std::chrono::microseconds{us};
|
||||||
|
std::chrono::microseconds tz_offset{std::chrono::hours{tz_hour} + std::chrono::minutes{tz_minute} + std::chrono::seconds{tz_second}};
|
||||||
|
if (tz_plus) {
|
||||||
|
tz_offset = -tz_offset;
|
||||||
|
}
|
||||||
|
result += tz_offset;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlpp::chrono::microsecond_point build_timestamp(int year, int month, int day, int hour = 0, int minute = 0, int second = 0, int us = 0, bool tz_plus = true, int tz_hour = 0, int tz_minute = 0, int tz_second = 0)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
date::sys_days{date::year{year}/month/day} +
|
||||||
|
build_tod(hour, minute, second, us, tz_plus, tz_hour,tz_minute,tz_second);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename L, typename R>
|
||||||
|
void require_equal(int line, const L& l, const R& r)
|
||||||
|
{
|
||||||
|
if (l != r)
|
||||||
|
{
|
||||||
|
std::cerr << line << ": ";
|
||||||
|
serialize(sqlpp::wrap_operand_t<L>{l}, std::cerr);
|
||||||
|
std::cerr << " != ";
|
||||||
|
serialize(sqlpp::wrap_operand_t<R>{r}, std::cerr);
|
||||||
|
std::cerr << std::endl;
|
||||||
|
throw std::runtime_error("Unexpected result");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_valid_dates()
|
||||||
|
{
|
||||||
|
using namespace sqlpp::chrono;
|
||||||
|
using namespace date;
|
||||||
|
|
||||||
|
for (const auto& date_pair : std::vector<std::pair<const char*, day_point>>{
|
||||||
|
// Minimum and maximum dates
|
||||||
|
{"0001-01-01", year{1}/1/1}, {"9999-12-31", year{9999}/12/31},
|
||||||
|
// Month minimum and maximum days
|
||||||
|
{"1999-01-01", year{1999}/1/1}, {"1999-01-31", year{1999}/1/31},
|
||||||
|
{"1999-02-01", year{1999}/2/1}, {"1999-02-28", year{1999}/2/28},
|
||||||
|
{"1999-03-01", year{1999}/3/1}, {"1999-03-31", year{1999}/3/31},
|
||||||
|
{"1999-04-01", year{1999}/4/1}, {"1999-04-30", year{1999}/4/30},
|
||||||
|
{"1999-05-01", year{1999}/5/1}, {"1999-05-31", year{1999}/5/31},
|
||||||
|
{"1999-06-01", year{1999}/6/1}, {"1999-06-30", year{1999}/6/30},
|
||||||
|
{"1999-07-01", year{1999}/7/1}, {"1999-07-31", year{1999}/7/31},
|
||||||
|
{"1999-08-01", year{1999}/8/1}, {"1999-08-31", year{1999}/8/31},
|
||||||
|
{"1999-09-01", year{1999}/9/1}, {"1999-09-30", year{1999}/9/30},
|
||||||
|
{"1999-10-01", year{1999}/10/1}, {"1999-10-31", year{1999}/10/31},
|
||||||
|
{"1999-11-01", year{1999}/11/1}, {"1999-11-30", year{1999}/11/30},
|
||||||
|
{"1999-12-01", year{1999}/12/1}, {"1999-12-31", year{1999}/12/31},
|
||||||
|
// YYYY-02-29
|
||||||
|
{"2396-02-29", year{2396}/2/29}, {"2400-02-29", year{2400}/2/29},
|
||||||
|
// Valid format, but the year, month and/or day fall outside of the correct ranges
|
||||||
|
{"1980-00-02", year{1980}/0/2}, {"1980-13-02", year{1980}/13/2},
|
||||||
|
{"1980-01-00", year{1980}/1/0}, {"1980-01-32", year{1980}/1/32},
|
||||||
|
{"1981-02-29", year{1981}/2/29}, {"2100-02-29", year{2100}/2/29}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
day_point dp;
|
||||||
|
if (sqlpp::detail::parse_date(dp, date_pair.first) == false)
|
||||||
|
{
|
||||||
|
std::cerr << "Could not parse a valid date string: " << date_pair.first << std::endl;
|
||||||
|
throw std::runtime_error{"Parse error"};
|
||||||
|
}
|
||||||
|
require_equal(__LINE__, dp, date_pair.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_invalid_dates()
|
||||||
|
{
|
||||||
|
using namespace sqlpp::chrono;
|
||||||
|
|
||||||
|
for (const auto& date_str : std::vector<const char*>{
|
||||||
|
// Invalid year
|
||||||
|
"", "1", "12", "123" , "1234", "A",
|
||||||
|
// Invalid month
|
||||||
|
"1980--02", "1980-1-02", "1980-123-02", "1980-W-02",
|
||||||
|
// Invalid day
|
||||||
|
"1980-01-", "1980-01-0", "1980-01-123", "1980-01-Q",
|
||||||
|
// Invalid separator
|
||||||
|
"1980 01 02", "1980- 01-02", "1980 -01-02", "1980-01 -02", "1980-01- 02", "1980-01T02"
|
||||||
|
// Trailing characters
|
||||||
|
"1980-01-02 ", "1980-01-02T", "1980-01-02 UTC", "1980-01-02EST", "1980-01-02+01"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
day_point dp;
|
||||||
|
if (sqlpp::detail::parse_date(dp, date_str))
|
||||||
|
{
|
||||||
|
std::cerr << "Parsed successfully an invalid date string " << date_str << ", value ";
|
||||||
|
serialize(sqlpp::wrap_operand_t<day_point>{dp}, std::cerr);
|
||||||
|
std::cerr << std::endl;
|
||||||
|
throw std::runtime_error{"Parse error"};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_valid_time_of_day()
|
||||||
|
{
|
||||||
|
using namespace std::chrono;
|
||||||
|
|
||||||
|
for (const auto& tod_pair : std::vector<std::pair<const char*, microseconds>>{
|
||||||
|
// Minimum value
|
||||||
|
{"00:00:00", build_tod()},
|
||||||
|
// Maximum hours
|
||||||
|
{"23:00:00", build_tod(23)},
|
||||||
|
// Maximum minutes
|
||||||
|
{"00:59:00", build_tod(0, 59)},
|
||||||
|
// Maximum seconds
|
||||||
|
{"00:00:59", build_tod(0, 0, 59)},
|
||||||
|
// Second fractions
|
||||||
|
{"01:23:54.000001", build_tod(1, 23, 54, 1)},
|
||||||
|
{"01:23:54.999999", build_tod(1, 23, 54, 999999)},
|
||||||
|
// Timezone offsets
|
||||||
|
{"10:09:08+03", build_tod(10, 9, 8, 0, true, 3)},
|
||||||
|
{"10:09:08-03", build_tod(10, 9, 8, 0, false, 3)},
|
||||||
|
{"10:09:08+03:02", build_tod(10, 9, 8, 0, true, 3, 2)},
|
||||||
|
{"10:09:08-03:02", build_tod(10, 9, 8, 0, false, 3, 2)},
|
||||||
|
{"10:09:08+13:12:11", build_tod(10, 9, 8, 0, true, 13, 12, 11)},
|
||||||
|
{"10:09:08-13:12:11", build_tod(10, 9, 8, 0, false, 13, 12, 11)},
|
||||||
|
// Second fraction and timezone offset
|
||||||
|
{"10:09:08.1+03", build_tod(10, 9, 8, 100000, true, 3)},
|
||||||
|
{"10:09:08.12-07:40", build_tod(10, 9, 8, 120000, false, 7, 40)},
|
||||||
|
{"10:09:08.123+12:38:49", build_tod(10, 9, 8, 123000, true, 12, 38, 49)},
|
||||||
|
// Valid format but invalid hour, minute or second range
|
||||||
|
{"25:00:10", build_tod(25, 0, 10)}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
microseconds us;
|
||||||
|
if (sqlpp::detail::parse_time_of_day(us, tod_pair.first) == false)
|
||||||
|
{
|
||||||
|
std::cerr << "Could not parse a valid time-of-day string: " << tod_pair.first << std::endl;
|
||||||
|
throw std::runtime_error{"Parse error"};
|
||||||
|
}
|
||||||
|
require_equal(__LINE__, us, tod_pair.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_invalid_time_of_day()
|
||||||
|
{
|
||||||
|
using namespace std::chrono;
|
||||||
|
|
||||||
|
for (const auto& tod_str : std::vector<const char*>{
|
||||||
|
// Generic string
|
||||||
|
"A", "BC", "!()",
|
||||||
|
// Invalid hour
|
||||||
|
"-01:23:45", "AA:10:11",
|
||||||
|
// Invalid minute
|
||||||
|
"13::07", "13:A:07", "13:1:07", "13:-01:07"
|
||||||
|
// Invalid second
|
||||||
|
"04:07:", "04:07:A", "04:07:1", "04:07:-01"
|
||||||
|
// Invalid fraction
|
||||||
|
"01:02:03.", "01:02:03.A", "01:02:03.1234567", "01:02:03.1A2",
|
||||||
|
// Invalid timezone
|
||||||
|
"01:03:03!01", "01:03:03+A", "01:03:03+1", "01:03:03+1A", "01:03:03+456",
|
||||||
|
"01:03:03+12:", "01:03:03+12:1", "01:03:03+12:1A", "01:03:03+12:01:",
|
||||||
|
"01:03:03+12:01:1", "01:03:03+12:01:1A"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
microseconds us;
|
||||||
|
if (sqlpp::detail::parse_time_of_day(us, tod_str))
|
||||||
|
{
|
||||||
|
std::cerr << "Parsed successfully an invalid time-of-day string " << tod_str << ", value ";
|
||||||
|
serialize(sqlpp::wrap_operand_t<microseconds>{us}, std::cerr);
|
||||||
|
std::cerr << std::endl;
|
||||||
|
throw std::runtime_error{"Parse error"};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_valid_timestamp()
|
||||||
|
{
|
||||||
|
using namespace sqlpp::chrono;
|
||||||
|
using namespace date;
|
||||||
|
using namespace std::chrono;
|
||||||
|
|
||||||
|
for (const auto& timestamp_pair : std::vector<std::pair<const char*, microsecond_point>>{
|
||||||
|
// Minimum and maximum timestamps
|
||||||
|
{"0001-01-01 00:00:00", build_timestamp(1, 1, 1)}, {"9999-12-31 23:59:59.999999", build_timestamp(9999, 12, 31, 23, 59, 59, 999999)},
|
||||||
|
// Timestamp with time zone
|
||||||
|
{"1234-03-25 23:17:08.479210+10:17:29", build_timestamp(1234, 3, 25, 23, 17, 8, 479210, true, 10, 17, 29)}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
microsecond_point tp;
|
||||||
|
if (sqlpp::detail::parse_timestamp(tp, timestamp_pair.first) == false)
|
||||||
|
{
|
||||||
|
std::cerr << "Could not parse a valid timestamp string: " << timestamp_pair.first << std::endl;
|
||||||
|
throw std::runtime_error{"Parse error"};
|
||||||
|
}
|
||||||
|
require_equal(__LINE__, tp, timestamp_pair.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_invalid_timestamp()
|
||||||
|
{
|
||||||
|
using namespace sqlpp::chrono;
|
||||||
|
|
||||||
|
for (const auto& timestamp_str : std::vector<const char*>{
|
||||||
|
// Generic string
|
||||||
|
"", "B", ")-#\\",
|
||||||
|
// Invalid date
|
||||||
|
"197%-03-17 10:32:09",
|
||||||
|
// Invalid time of day
|
||||||
|
"2020-02-18 22:2:28"
|
||||||
|
// Invalid time zone
|
||||||
|
"1924-02-28 18:35:36+1"
|
||||||
|
// Leading space
|
||||||
|
" 2030-17-01 15:20:30",
|
||||||
|
// Trailing space
|
||||||
|
"2030-17-01 15:20:30 "
|
||||||
|
})
|
||||||
|
{
|
||||||
|
microsecond_point tp;
|
||||||
|
if (sqlpp::detail::parse_timestamp(tp, timestamp_str))
|
||||||
|
{
|
||||||
|
std::cerr << "Parsed successfully an invalid timestamp string " << timestamp_str << ", value ";
|
||||||
|
serialize(sqlpp::wrap_operand_t<microsecond_point>{tp}, std::cerr);
|
||||||
|
std::cerr << std::endl;
|
||||||
|
throw std::runtime_error{"Parse error"};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_valid_date_or_timestamp()
|
||||||
|
{
|
||||||
|
using namespace sqlpp::chrono;
|
||||||
|
using namespace date;
|
||||||
|
using namespace std::chrono;
|
||||||
|
|
||||||
|
for (const auto& timestamp_pair : std::vector<std::pair<const char*, microsecond_point>>{
|
||||||
|
// Valid date
|
||||||
|
{"1998-02-03", build_timestamp(1998, 2, 3)},
|
||||||
|
// Valid timestamp
|
||||||
|
{"2015-07-08 06:32:45.872+23:14:39", build_timestamp(2015,7,8,6,32,45,872000,true,23,14,39)}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
microsecond_point tp;
|
||||||
|
if (sqlpp::detail::parse_date_or_timestamp(tp, timestamp_pair.first) == false)
|
||||||
|
{
|
||||||
|
std::cerr << "Could not parse a valid date or timestamp string: " << timestamp_pair.first << std::endl;
|
||||||
|
throw std::runtime_error{"Parse error"};
|
||||||
|
}
|
||||||
|
require_equal(__LINE__, tp, timestamp_pair.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_invalid_date_or_timestamp()
|
||||||
|
{
|
||||||
|
using namespace sqlpp::chrono;
|
||||||
|
|
||||||
|
for (const auto& timestamp_str : std::vector<const char*>{
|
||||||
|
// Generic string
|
||||||
|
"", "C", "/=",
|
||||||
|
// Invalid dates
|
||||||
|
"A123-01-02", "1980-E-04", "1981-09-",
|
||||||
|
// Invalid timestamps
|
||||||
|
"2023-12-31 1:02:03", "2024-03-04 05::06",
|
||||||
|
// Invalid time zone
|
||||||
|
"1930-03-18 17:30:31+01:",
|
||||||
|
// Leading space
|
||||||
|
" 1930-03-18 17:30:31+01",
|
||||||
|
// Trailing space
|
||||||
|
"1930-03-18 17:30:31+01 "
|
||||||
|
})
|
||||||
|
{
|
||||||
|
microsecond_point tp;
|
||||||
|
if (sqlpp::detail::parse_date_or_timestamp(tp, timestamp_str))
|
||||||
|
{
|
||||||
|
std::cerr << "Parsed successfully an invalid date or timestamp string " << timestamp_str << ", value ";
|
||||||
|
serialize(sqlpp::wrap_operand_t<microsecond_point>{tp}, std::cerr);
|
||||||
|
std::cerr << std::endl;
|
||||||
|
throw std::runtime_error{"Parse error"};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int DateTimeParser(int, char*[])
|
||||||
|
{
|
||||||
|
test_valid_dates();
|
||||||
|
test_invalid_dates();
|
||||||
|
test_valid_time_of_day();
|
||||||
|
test_invalid_time_of_day();
|
||||||
|
test_valid_timestamp();
|
||||||
|
test_invalid_timestamp();
|
||||||
|
test_valid_date_or_timestamp();
|
||||||
|
test_invalid_date_or_timestamp();
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user