/* 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 #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 (&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; } }; }