File indexing completed on 2023-09-24 04:04:48
0001 /* 0002 Copyright 2009, 2010 John Layt <john@layt.net> 0003 0004 This library is free software; you can redistribute it and/or 0005 modify it under the terms of the GNU Library General Public 0006 License as published by the Free Software Foundation; either 0007 version 2 of the License, or (at your option) any later version. 0008 0009 This library is distributed in the hope that it will be useful, 0010 but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0012 Library General Public License for more details. 0013 0014 You should have received a copy of the GNU Library General Public License 0015 along with this library; see the file COPYING.LIB. If not, write to 0016 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0017 Boston, MA 02110-1301, USA. 0018 */ 0019 0020 #include "kdatetimeparser_p.h" 0021 0022 #include "kcalendarsystemprivate_p.h" 0023 #include "kcalendarsystem.h" 0024 #include "kcalendarera_p.h" 0025 #include "kdebug.h" 0026 0027 KDateTimeParser::KDateTimeParser() 0028 { 0029 } 0030 0031 KDateTimeParser::~KDateTimeParser() 0032 { 0033 } 0034 0035 // Parse a DateTime input string and return just the Date component 0036 QDate KDateTimeParser::parseDate(const QString &inputString, 0037 const QString &formatString, 0038 const KCalendarSystem *calendar, 0039 const KLocale *locale, 0040 KLocale::DigitSet digitSet, 0041 KLocale::DateTimeFormatStandard formatStandard) const 0042 { 0043 DateTimeComponents result; 0044 if (formatStandard == KLocale::UnicodeFormat) { 0045 result = parseDateUnicode(inputString, formatString, calendar, locale, digitSet); 0046 } else { 0047 result = parseDatePosix(inputString, formatString, calendar, locale, digitSet, formatStandard); 0048 } 0049 0050 QDate resultDate; 0051 0052 if (!result.error && 0053 formatString.simplified().length() <= result.formatPosition && 0054 inputString.simplified().length() <= result.inputPosition) { 0055 0056 // If there were no parsing errors, and we have reached the end of both the input and 0057 // format strings, then see if we have a valid date based on the components parsed 0058 0059 // If we haven't parsed a year component, then assume this year 0060 if (!result.parsedYear) { 0061 result.year = calendar->year(QDate::currentDate()); 0062 } 0063 0064 if ((!result.eraName.isEmpty() || result.yearInEra > -1) && result.month > 0 && result.day > 0) { 0065 // Have parsed Era components as well as month and day components 0066 calendar->setDate(resultDate, result.eraName, result.yearInEra, result.month, result.day); 0067 } else if (result.month > 0 && result.day > 0) { 0068 // Have parsed month and day components 0069 calendar->setDate(resultDate, result.year, result.month, result.day); 0070 } else if (result.dayInYear > 0) { 0071 // Have parsed Day In Year component 0072 calendar->setDate(resultDate, result.year, result.dayInYear); 0073 } else if (result.isoWeekNumber > 0 && result.dayOfIsoWeek > 0) { 0074 // Have parsed ISO Week components 0075 calendar->setDateIsoWeek(resultDate, result.year, result.isoWeekNumber, result.dayOfIsoWeek); 0076 } 0077 0078 } 0079 0080 return resultDate; 0081 } 0082 0083 DateTimeComponents KDateTimeParser::parseDatePosix(const QString &inputString, 0084 const QString &formatString, 0085 const KCalendarSystem *calendar, 0086 const KLocale *locale, 0087 KLocale::DigitSet digitSet, 0088 KLocale::DateTimeFormatStandard standard) const 0089 { 0090 QString str = inputString.simplified().toLower(); 0091 QString fmt = formatString.simplified(); 0092 int dd = -1; 0093 int mm = -1; 0094 int yy = 0; 0095 bool parsedYear = false; 0096 int ey = -1; 0097 QString ee; 0098 int dayInYear = -1; 0099 int isoWeekNumber = -1; 0100 int dayOfIsoWeek = -1; 0101 int strpos = 0; 0102 int fmtpos = 0; 0103 int readLength; // Temporary variable used when reading input 0104 bool error = false; 0105 0106 while (fmt.length() > fmtpos && str.length() > strpos && !error) { 0107 0108 QChar fmtChar = fmt.at(fmtpos++); 0109 0110 if (fmtChar != QLatin1Char('%')) { 0111 0112 if (fmtChar.isSpace() && str.at(strpos).isSpace()) { 0113 strpos++; 0114 } else if (fmtChar.toLower() == str.at(strpos)) { 0115 strpos++; 0116 } else { 0117 error = true; 0118 } 0119 0120 } else { 0121 int j; 0122 QString shortName, longName; 0123 QChar modifierChar; 0124 // remove space at the beginning 0125 if (str.length() > strpos && str.at(strpos).isSpace()) { 0126 strpos++; 0127 } 0128 0129 fmtChar = fmt.at(fmtpos++); 0130 if (fmtChar == QLatin1Char('E')) { 0131 modifierChar = fmtChar; 0132 fmtChar = fmt.at(fmtpos++); 0133 } 0134 0135 switch (fmtChar.unicode()) { 0136 case 'a': // Weekday Name Short 0137 case 'A': // Weekday Name Long 0138 error = true; 0139 j = 1; 0140 while (error && j <= calendar->d_ptr->daysInWeek()) { 0141 shortName = calendar->weekDayName(j, KCalendarSystem::ShortDayName).toLower(); 0142 longName = calendar->weekDayName(j, KCalendarSystem::LongDayName).toLower(); 0143 if (str.mid(strpos, longName.length()) == longName) { 0144 strpos += longName.length(); 0145 error = false; 0146 } else if (str.mid(strpos, shortName.length()) == shortName) { 0147 strpos += shortName.length(); 0148 error = false; 0149 } 0150 ++j; 0151 } 0152 break; 0153 case 'b': // Month Name Short 0154 case 'h': // Month Name Short 0155 case 'B': // Month Name Long 0156 error = true; 0157 j = 1; 0158 while (error && j <= calendar->d_ptr->maxMonthsInYear()) { 0159 // This may be a problem in calendar systems with variable number of months 0160 // in the year and/or names of months that change depending on the year, e.g 0161 // Hebrew. We really need to know the correct year first, but we may not have 0162 // read it yet and will be using the current year instead 0163 int monthYear; 0164 if (parsedYear) { 0165 monthYear = yy; 0166 } else { 0167 monthYear = calendar->year(QDate::currentDate()); 0168 } 0169 if (calendar->locale()->dateMonthNamePossessive()) { 0170 shortName = calendar->monthName(j, monthYear, KCalendarSystem::ShortNamePossessive).toLower(); 0171 longName = calendar->monthName(j, monthYear, KCalendarSystem::LongNamePossessive).toLower(); 0172 } else { 0173 shortName = calendar->monthName(j, monthYear, KCalendarSystem::ShortName).toLower(); 0174 longName = calendar->monthName(j, monthYear, KCalendarSystem::LongName).toLower(); 0175 } 0176 if (str.mid(strpos, longName.length()) == longName) { 0177 mm = j; 0178 strpos += longName.length(); 0179 error = false; 0180 } else if (str.mid(strpos, shortName.length()) == shortName) { 0181 mm = j; 0182 strpos += shortName.length(); 0183 error = false; 0184 } 0185 ++j; 0186 } 0187 break; 0188 case 'd': // Day Number Long 0189 case 'e': // Day Number Short 0190 dd = calendar->dayStringToInteger(str.mid(strpos), readLength); 0191 strpos += readLength; 0192 error = readLength <= 0; 0193 break; 0194 case 'n': 0195 // PosixFormat %n is Newline 0196 // KdeFormat %n is Month Number Short 0197 if (standard == KLocale::KdeFormat) { 0198 mm = calendar->monthStringToInteger(str.mid(strpos), readLength); 0199 strpos += readLength; 0200 error = readLength <= 0; 0201 } 0202 // standard == KLocale::PosixFormat 0203 // all whitespace already 'eaten', no action required 0204 break; 0205 case 'm': // Month Number Long 0206 mm = calendar->monthStringToInteger(str.mid(strpos), readLength); 0207 strpos += readLength; 0208 error = readLength <= 0; 0209 break; 0210 case 'Y': // Year Number Long 0211 case 'y': // Year Number Short 0212 if (modifierChar == QLatin1Char('E')) { // Year In Era 0213 if (fmtChar == QLatin1Char('y')) { 0214 ey = calendar->yearStringToInteger(str.mid(strpos), readLength); 0215 strpos += readLength; 0216 error = readLength <= 0; 0217 } else { 0218 error = true; 0219 j = calendar->eraList()->count() - 1; // Start with the most recent 0220 while (error && j >= 0) { 0221 QString subFormat = calendar->eraList()->at(j).format(); 0222 QString subInput = str.mid(strpos); 0223 DateTimeComponents subResult = parseDatePosix(subInput, subFormat, calendar, locale, digitSet, standard); 0224 if (!subResult.error) { 0225 if (subResult.parsedYear) { 0226 yy = subResult.year; 0227 parsedYear = true; 0228 error = false; 0229 strpos += subResult.inputPosition; 0230 } else if (!subResult.eraName.isEmpty() && subResult.yearInEra >= 0) { 0231 ee = subResult.eraName; 0232 ey = subResult.yearInEra; 0233 error = false; 0234 strpos += subResult.inputPosition; 0235 } 0236 } 0237 --j; 0238 } 0239 } 0240 } else { 0241 yy = calendar->yearStringToInteger(str.mid(strpos), readLength); 0242 strpos += readLength; 0243 if (fmtChar == QLatin1Char('y')) { 0244 yy = calendar->applyShortYearWindow(yy); 0245 } 0246 error = readLength <= 0; 0247 if (!error) { 0248 parsedYear = true; 0249 } 0250 } 0251 break; 0252 case 'C': // Era 0253 error = true; 0254 if (modifierChar == QLatin1Char('E')) { 0255 j = calendar->eraList()->count() - 1; // Start with the most recent 0256 while (error && j >= 0) { 0257 shortName = calendar->d_ptr->m_eraList->at(j).name(KLocale::ShortName).toLower(); 0258 longName = calendar->eraList()->at(j).name(KLocale::LongName).toLower(); 0259 if (str.mid(strpos, longName.length()) == longName) { 0260 strpos += longName.length(); 0261 ee = longName; 0262 error = false; 0263 } else if (str.mid(strpos, shortName.length()) == shortName) { 0264 strpos += shortName.length(); 0265 ee = shortName; 0266 error = false; 0267 } 0268 --j; 0269 } 0270 } 0271 break; 0272 case 'j': // Day Of Year Number 0273 dayInYear = integerFromString(str.mid(strpos), 3, readLength); 0274 strpos += readLength; 0275 error = readLength <= 0; 0276 break; 0277 case 'V': // ISO Week Number 0278 isoWeekNumber = integerFromString(str.mid(strpos), 2, readLength); 0279 strpos += readLength; 0280 error = readLength <= 0; 0281 break; 0282 case 'u': // ISO Day Of Week 0283 dayOfIsoWeek = integerFromString(str.mid(strpos), 1, readLength); 0284 strpos += readLength; 0285 error = readLength <= 0; 0286 break; 0287 } 0288 } 0289 } 0290 0291 DateTimeComponents result; 0292 result.error = error; 0293 result.inputPosition = strpos; 0294 result.formatPosition = fmtpos; 0295 if (error) { 0296 result.day = -1; 0297 result.month = -1; 0298 result.year = 0; 0299 result.parsedYear = false; 0300 result.eraName.clear(); 0301 result.yearInEra = -1; 0302 result.dayInYear = -1; 0303 result.isoWeekNumber = -1; 0304 result.dayOfIsoWeek = -1; 0305 } else { 0306 result.day = dd; 0307 result.month = mm; 0308 result.year = yy; 0309 result.parsedYear = parsedYear; 0310 result.eraName = ee; 0311 result.yearInEra = ey; 0312 result.dayInYear = dayInYear; 0313 result.isoWeekNumber = isoWeekNumber; 0314 result.dayOfIsoWeek = dayOfIsoWeek; 0315 } 0316 return result; 0317 } 0318 0319 // Parse an input string to match a UNICODE DateTime format string and return any components found 0320 DateTimeComponents KDateTimeParser::parseDateUnicode(const QString &inputString, 0321 const QString &formatString, 0322 const KCalendarSystem *calendar, 0323 const KLocale *locale, 0324 KLocale::DigitSet digitSet) const 0325 { 0326 Q_UNUSED(calendar); 0327 Q_UNUSED(locale); 0328 Q_UNUSED(digitSet); 0329 Q_UNUSED(inputString); 0330 Q_UNUSED(formatString); 0331 0332 kWarning() << "KDateTimeParser::parseDateUnicode is not implemented"; 0333 0334 DateTimeComponents result; 0335 result.error = true; 0336 result.inputPosition = 0; 0337 result.formatPosition = 0; 0338 result.day = -1; 0339 result.month = -1; 0340 result.year = 0; 0341 result.parsedYear = false; 0342 result.eraName.clear(); 0343 result.yearInEra = -1; 0344 result.dayInYear = -1; 0345 result.isoWeekNumber = -1; 0346 result.dayOfIsoWeek = -1; 0347 return result; 0348 } 0349 0350 // Peel a number off the front of a string which may have other trailing chars after the number 0351 // Stop either at either maxLength, eos, or first non-digit char 0352 int KDateTimeParser::integerFromString(const QString &string, int maxLength, int &readLength) const 0353 { 0354 int value = -1; 0355 int position = 0; 0356 readLength = 0; 0357 bool ok = false; 0358 0359 if (maxLength < 0) { 0360 maxLength = string.length(); 0361 } 0362 0363 while (position < string.length() && 0364 position < maxLength && 0365 string.at(position).isDigit()) { 0366 position++; 0367 } 0368 0369 if (position > 0) { 0370 value = string.left(position).toInt(&ok); 0371 if (ok) { 0372 readLength = position; 0373 } else { 0374 value = -1; 0375 } 0376 } 0377 0378 return value; 0379 }