mirror of
https://github.com/mariadb-corporation/mariadb-columnstore-engine.git
synced 2025-05-31 11:41:14 +03:00
Date limit of year 1400 was used due to Boost's limits. This patch strips out the use of Boost for date handling and sets the lower limit to year 1000.
449 lines
12 KiB
C++
449 lines
12 KiB
C++
/* Copyright (C) 2014 InfiniDB, Inc.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; version 2 of
|
|
the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
MA 02110-1301, USA. */
|
|
|
|
#include <string>
|
|
#include "funchelpers.h"
|
|
#include "dataconvert.h"
|
|
|
|
namespace funcexp
|
|
{
|
|
|
|
class TimeExtractor
|
|
{
|
|
private:
|
|
int32_t dayOfWeek;
|
|
int32_t dayOfYear;
|
|
int32_t weekOfYear;
|
|
bool sundayFirst;
|
|
|
|
public:
|
|
TimeExtractor() :
|
|
dayOfWeek( -1 ),
|
|
dayOfYear( -1 ),
|
|
weekOfYear( -1 ),
|
|
sundayFirst( false ) {;}
|
|
|
|
/**
|
|
* extractTime is an implementation that matches MySQL behavior for the
|
|
* STR_TO_DATE() function. See MySQL documentation for details.
|
|
*
|
|
* Returns 0 on success, -1 on failure. On failure the DateTime is
|
|
* reset to 0s in all fields.
|
|
*/
|
|
int extractTime (const std::string& valStr, const std::string& formatStr, dataconvert::DateTime & dateTime)
|
|
{
|
|
uint32_t fcur = 0;
|
|
uint32_t vcur = 0;
|
|
while( fcur != formatStr.length() )
|
|
{
|
|
if( !handleNextToken(valStr, vcur, formatStr, fcur, dateTime) )
|
|
return returnError( dateTime );
|
|
}
|
|
|
|
if( dayOfYear > 0 )
|
|
{
|
|
// they set day of year - we also need to make sure there is a year to work with
|
|
if( !dataconvert::isDateValid( 1, 1, dateTime.year ) )
|
|
return returnError( dateTime );
|
|
|
|
helpers::get_date_from_mysql_daynr( helpers::calc_mysql_daynr( dateTime.year, 1, 1 ) + dayOfYear - 1, dateTime );
|
|
}
|
|
else if( weekOfYear > 0 )
|
|
{
|
|
if( dayOfWeek < 0 || !dataconvert::isDateValid( 1, 1, dateTime.year ) )
|
|
return returnError( dateTime );
|
|
|
|
uint32_t yearfirst = helpers::calc_mysql_daynr(dateTime.year, 1, 1);
|
|
// figure out which day of week Jan-01 is
|
|
uint32_t firstweekday = helpers::calc_mysql_weekday( dateTime.year, 1, 1, sundayFirst );
|
|
|
|
// calculate the offset to the first week starting day
|
|
uint32_t firstoffset = firstweekday ? ( 7 - firstweekday ) : 0;
|
|
|
|
firstoffset += ( ( weekOfYear - 1) * 7 ) + dayOfWeek - ( sundayFirst ? 0 : 1 );
|
|
yearfirst += firstoffset;
|
|
helpers::get_date_from_mysql_daynr(yearfirst, dateTime);
|
|
}
|
|
|
|
if( !dataconvert::isDateTimeValid( dateTime.hour, dateTime.minute, dateTime.second, dateTime.msecond ) )
|
|
return returnError( dateTime );
|
|
|
|
return 0;
|
|
}
|
|
|
|
private:
|
|
int returnError( dataconvert::DateTime & dateTime )
|
|
{
|
|
(*(reinterpret_cast<uint64_t *> (&dateTime))) = (uint64_t) 0;
|
|
return -1;
|
|
}
|
|
|
|
bool scanDecimalVal(const char *nptr, const char **endptr, int32_t& value)
|
|
{
|
|
value = 0;
|
|
const char* p = nptr;
|
|
while( p < *endptr && isdigit( *p ) )
|
|
{
|
|
value = value * 10 + ((*p) - '0');
|
|
++p;
|
|
}
|
|
*endptr = p;
|
|
return (*endptr != nptr);
|
|
}
|
|
|
|
bool handleNextToken(const std::string& valStr, uint32_t& vptr, const std::string& formatStr, uint32_t& fptr, dataconvert::DateTime & dateTime)
|
|
{
|
|
// advance both strings to the first non-whitespace character
|
|
while( valStr[vptr] == ' ' && vptr < valStr.length() )
|
|
++vptr;
|
|
bool vend = (vptr == valStr.length());
|
|
while( formatStr[fptr] == ' ' && fptr < formatStr.length() )
|
|
++fptr;
|
|
bool fend = (fptr == formatStr.length());
|
|
|
|
if( vend && fend )
|
|
{
|
|
// apparent trailing whitespace
|
|
return true;
|
|
}
|
|
else if( !vend && !fend )
|
|
{
|
|
if( formatStr[fptr] == '%' )
|
|
{
|
|
// has to be at least one more character in format string
|
|
if( fptr >= formatStr.length() - 1)
|
|
return false;
|
|
|
|
fptr++; // skip over %
|
|
// check for special case of %%
|
|
if( formatStr[fptr] == '%' )
|
|
{
|
|
bool ret = formatStr[fptr] == valStr[vptr];
|
|
++fptr;
|
|
++vptr;
|
|
return ret;
|
|
}
|
|
else
|
|
{
|
|
char field = formatStr[fptr];
|
|
++fptr; // also skip the format code
|
|
bool ret = handleField( field, valStr, vptr, dateTime );
|
|
return ret;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bool ret = formatStr[fptr] == valStr[vptr];
|
|
++fptr;
|
|
++vptr;
|
|
return ret;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// one string finish before the other one - not good
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool handleField(char field, const std::string& valStr, uint32_t& vptr, dataconvert::DateTime & dateTime)
|
|
{
|
|
int32_t value;
|
|
const char* valptr = valStr.c_str() + vptr;
|
|
switch( field )
|
|
{
|
|
case 'a':
|
|
{
|
|
// weekday abbreviations are always exactly 3 characters
|
|
std::string weekday_str( valStr, vptr, 3 );
|
|
vptr += 3;
|
|
dayOfWeek = helpers::dayOfWeek(weekday_str);
|
|
if( dayOfWeek < 0 )
|
|
{
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
case 'b':
|
|
{
|
|
// month abbreviations are always exactly 3 characters
|
|
std::string month_str( valStr, vptr, 3 );
|
|
vptr += 3;
|
|
value = helpers::convertMonth(month_str);
|
|
if( value < 0 )
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
dateTime.month = value;
|
|
}
|
|
break;
|
|
}
|
|
case 'c':
|
|
case 'm':
|
|
{
|
|
// Month, numeric (0..12)
|
|
const char* vend = valptr + min(2, (int)(valStr.length() - vptr));
|
|
if( !scanDecimalVal( valptr, &vend, value ) )
|
|
return false;
|
|
vptr += (vend - valptr);
|
|
// we have to do range checking on the month value here because
|
|
// dateTime will arbitrarily truncate to 4 bits and may turn a
|
|
// bad value into a good one
|
|
if( value < 0 || value > 12 )
|
|
return false;
|
|
dateTime.month = value;
|
|
break;
|
|
}
|
|
case 'D':
|
|
case 'd':
|
|
case 'e':
|
|
{
|
|
// %D - Day of the month with English suffix (0th, 1st, 2nd, 3rd, …)
|
|
// %d - Day of the month, numeric (00..31)
|
|
// %e - Day of the month, numeric (0..31)
|
|
const char* vend = valptr + min(2, (int)(valStr.length() - vptr));
|
|
if( !scanDecimalVal( valptr, &vend, value ) )
|
|
return false;
|
|
vptr += (vend - valptr);
|
|
// now also skip suffix if required - always 2 characters
|
|
if( field == 'D')
|
|
vptr += 2;
|
|
// we have to do range checking on the month value here because
|
|
// dateTime will arbitrarily truncate to 6 bits and may turn a
|
|
// bad value into a good one
|
|
if( value < 0 || value > 31 )
|
|
return false;
|
|
dateTime.day = value;
|
|
break;
|
|
}
|
|
case 'f':
|
|
{
|
|
// Microseconds (000000..999999)
|
|
const char* vend = valptr + min(6, (int)(valStr.length() - vptr));
|
|
if( !scanDecimalVal( valptr, &vend, value ) )
|
|
return false;
|
|
vptr += (vend - valptr);
|
|
for( int i = (vend - valptr); i < 6; ++i )
|
|
value = value * 10;
|
|
dateTime.msecond = value;
|
|
break;
|
|
}
|
|
case 'H':
|
|
case 'k':
|
|
{
|
|
// Hour (00..23)
|
|
const char* vend = valptr + min(2, (int)(valStr.length() - vptr));
|
|
if( !scanDecimalVal( valptr, &vend, value ) )
|
|
return false;
|
|
vptr += (vend - valptr);
|
|
dateTime.hour = value;
|
|
break;
|
|
}
|
|
case 'h':
|
|
case 'I':
|
|
case 'l':
|
|
{
|
|
// Hour (01..12)
|
|
const char* vend = valptr + min(2, (int)(valStr.length() - vptr));
|
|
if( !scanDecimalVal( valptr, &vend, value ) )
|
|
return false;
|
|
vptr += (vend - valptr);
|
|
dateTime.hour = value;
|
|
break;
|
|
}
|
|
case 'i':
|
|
{
|
|
// Minutes, numeric (00..59)
|
|
const char* vend = valptr + min(2, (int)(valStr.length() - vptr));
|
|
if( !scanDecimalVal( valptr, &vend, value ) )
|
|
return false;
|
|
vptr += (vend - valptr);
|
|
dateTime.minute = value;
|
|
break;
|
|
}
|
|
case 'j':
|
|
{
|
|
// Day of year (001..366)
|
|
const char* vend = valptr + min(3, (int)(valStr.length() - vptr));
|
|
if( !scanDecimalVal( valptr, &vend, value ) )
|
|
return false;
|
|
vptr += (vend - valptr);
|
|
if( value < 1 || value > 366 )
|
|
return false;
|
|
dayOfYear = value;
|
|
break;
|
|
}
|
|
case 'M':
|
|
{
|
|
// Month name (January..December)
|
|
// look for the first non-alphabetic character
|
|
size_t endpos = vptr;
|
|
while( tolower(valStr[endpos]) >= 'a' && tolower(valStr[endpos]) <= 'z' && endpos < valStr.length() )
|
|
++endpos;
|
|
std::string month_str( valStr, vptr, endpos );
|
|
vptr += month_str.length();
|
|
value = helpers::convertMonth(month_str);
|
|
if( value < 0 )
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
dateTime.month = value;
|
|
}
|
|
break;
|
|
}
|
|
case 'p':
|
|
{
|
|
// AM or PM
|
|
if( tolower(valStr[vptr]) == 'p' && tolower(valStr[vptr+1]) == 'm' )
|
|
{
|
|
if( dateTime.hour < 12 )
|
|
dateTime.hour += 12;
|
|
}
|
|
else if( tolower(valStr[vptr]) == 'a' && tolower(valStr[vptr+1]) == 'm' )
|
|
{
|
|
if( dateTime.hour == 12 )
|
|
dateTime.hour = 0;
|
|
}
|
|
else
|
|
{
|
|
vptr += 2;
|
|
return false;
|
|
}
|
|
vptr += 2;
|
|
break;
|
|
}
|
|
case 'r':
|
|
case 'T':
|
|
{
|
|
// Time, 12-hour (hh:mm:ss followed by AM or PM)
|
|
// Time, 24-hour (hh:mm:ss)
|
|
int32_t hour = -1;
|
|
int32_t min = -1;
|
|
int32_t sec = -1;
|
|
int32_t numread;
|
|
int32_t sscanf_ck;
|
|
|
|
if( ( sscanf_ck = sscanf( valptr, "%2d:%2d:%2d%n", &hour, &min, &sec, &numread) ) != 3 )
|
|
{
|
|
return false;
|
|
}
|
|
valptr += numread;
|
|
vptr += numread;
|
|
|
|
dateTime.hour = hour;
|
|
dateTime.minute = min;
|
|
dateTime.second = sec;
|
|
break;
|
|
}
|
|
case 'S':
|
|
case 's':
|
|
{
|
|
// Seconds (00..59)
|
|
const char* vend = valptr + min(2, (int)(valStr.length() - vptr));
|
|
if( !scanDecimalVal( valptr, &vend, value ) )
|
|
return false;
|
|
vptr += (vend - valptr);
|
|
dateTime.second = value;
|
|
break;
|
|
}
|
|
case 'U':
|
|
case 'u':
|
|
case 'V':
|
|
case 'v':
|
|
{
|
|
// %U - Week (00..53), where Sunday is the first day of the week
|
|
// %u - Week (00..53), where Monday is the first day of the week
|
|
// %V - Week (01..53), where Sunday is the first day of the week; used with %X
|
|
// %v - Week (01..53), where Monday is the first day of the week; used with %x
|
|
sundayFirst = (isupper(field) != 0);
|
|
const char* vend = valptr + min(2, (int)(valStr.length() - vptr));
|
|
if( !scanDecimalVal( valptr, &vend, value ) )
|
|
return false;
|
|
vptr += (vend - valptr);
|
|
weekOfYear = value;
|
|
break;
|
|
}
|
|
case 'X':
|
|
case 'x':
|
|
case 'Y':
|
|
case 'y':
|
|
{
|
|
// %X - Year for the week where Sunday is the first day of the week, numeric, four digits; used with %V
|
|
// %x - Year for the week, where Monday is the first day of the week, numeric, four digits; used with %v
|
|
// %Y - Year, numeric, four digits
|
|
// %y - Year, numeric (two digits)
|
|
sundayFirst = ( field == 'X' );
|
|
int minFieldWidth = ( field == 'y' ? 2 : 4 );
|
|
const char* vend = valptr + min( minFieldWidth, (int)(valStr.length() - vptr) );
|
|
if( !scanDecimalVal( valptr, &vend, value ) )
|
|
return false;
|
|
vptr += (vend - valptr);
|
|
if( (vend - valptr) <= 2 )
|
|
{
|
|
// regardless of whether field was supposed to be 4 characters.
|
|
// If we read two then apply year 2000 handling
|
|
value += 2000;
|
|
if( value > 2069 )
|
|
value -= 100;
|
|
}
|
|
dateTime.year = value;
|
|
break;
|
|
}
|
|
case 'W':
|
|
{
|
|
// Weekday name (Sunday..Saturday)
|
|
// look for the first non-alphabetic character
|
|
size_t endpos = vptr;
|
|
while( tolower(valStr[endpos]) >= 'a' && tolower(valStr[endpos]) <= 'z' && endpos < valStr.length() )
|
|
++endpos;
|
|
std::string weekday_str( valStr, vptr, endpos );
|
|
vptr += weekday_str.length();
|
|
value = helpers::dayOfWeek(weekday_str);
|
|
if( value < 0 )
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
dayOfWeek = value;
|
|
}
|
|
break;
|
|
}
|
|
case 'w':
|
|
{
|
|
// Day of the week (0=Sunday..6=Saturday)
|
|
const char* vend = valptr + min(1, (int)(valStr.length() - vptr));
|
|
if( !scanDecimalVal( valptr, &vend, value ) )
|
|
return false;
|
|
vptr += (vend - valptr);
|
|
dayOfWeek = value;
|
|
break;
|
|
}
|
|
default:
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
};
|
|
|
|
}
|