/* Copyright 2009, 2010 John Layt This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kdatetimeparser_p.h" #include "kcalendarsystemprivate_p.h" #include "kcalendarsystem.h" #include "kcalendarera_p.h" #include "kdebug.h" KDateTimeParser::KDateTimeParser() { } KDateTimeParser::~KDateTimeParser() { } // Parse a DateTime input string and return just the Date component QDate KDateTimeParser::parseDate(const QString &inputString, const QString &formatString, const KCalendarSystem *calendar, const KLocale *locale, KLocale::DigitSet digitSet, KLocale::DateTimeFormatStandard formatStandard) const { DateTimeComponents result; if (formatStandard == KLocale::UnicodeFormat) { result = parseDateUnicode(inputString, formatString, calendar, locale, digitSet); } else { result = parseDatePosix(inputString, formatString, calendar, locale, digitSet, formatStandard); } QDate resultDate; if (!result.error && formatString.simplified().length() <= result.formatPosition && inputString.simplified().length() <= result.inputPosition) { // If there were no parsing errors, and we have reached the end of both the input and // format strings, then see if we have a valid date based on the components parsed // If we haven't parsed a year component, then assume this year if (!result.parsedYear) { result.year = calendar->year(QDate::currentDate()); } if ((!result.eraName.isEmpty() || result.yearInEra > -1) && result.month > 0 && result.day > 0) { // Have parsed Era components as well as month and day components calendar->setDate(resultDate, result.eraName, result.yearInEra, result.month, result.day); } else if (result.month > 0 && result.day > 0) { // Have parsed month and day components calendar->setDate(resultDate, result.year, result.month, result.day); } else if (result.dayInYear > 0) { // Have parsed Day In Year component calendar->setDate(resultDate, result.year, result.dayInYear); } else if (result.isoWeekNumber > 0 && result.dayOfIsoWeek > 0) { // Have parsed ISO Week components calendar->setDateIsoWeek(resultDate, result.year, result.isoWeekNumber, result.dayOfIsoWeek); } } return resultDate; } DateTimeComponents KDateTimeParser::parseDatePosix(const QString &inputString, const QString &formatString, const KCalendarSystem *calendar, const KLocale *locale, KLocale::DigitSet digitSet, KLocale::DateTimeFormatStandard standard) const { QString str = inputString.simplified().toLower(); QString fmt = formatString.simplified(); int dd = -1; int mm = -1; int yy = 0; bool parsedYear = false; int ey = -1; QString ee; int dayInYear = -1; int isoWeekNumber = -1; int dayOfIsoWeek = -1; int strpos = 0; int fmtpos = 0; int readLength; // Temporary variable used when reading input bool error = false; while (fmt.length() > fmtpos && str.length() > strpos && !error) { QChar fmtChar = fmt.at(fmtpos++); if (fmtChar != QLatin1Char('%')) { if (fmtChar.isSpace() && str.at(strpos).isSpace()) { strpos++; } else if (fmtChar.toLower() == str.at(strpos)) { strpos++; } else { error = true; } } else { int j; QString shortName, longName; QChar modifierChar; // remove space at the beginning if (str.length() > strpos && str.at(strpos).isSpace()) { strpos++; } fmtChar = fmt.at(fmtpos++); if (fmtChar == QLatin1Char('E')) { modifierChar = fmtChar; fmtChar = fmt.at(fmtpos++); } switch (fmtChar.unicode()) { case 'a': // Weekday Name Short case 'A': // Weekday Name Long error = true; j = 1; while (error && j <= calendar->d_ptr->maxDaysInWeek()) { shortName = calendar->weekDayName(j, KCalendarSystem::ShortDayName).toLower(); longName = calendar->weekDayName(j, KCalendarSystem::LongDayName).toLower(); if (str.mid(strpos, longName.length()) == longName) { strpos += longName.length(); error = false; } else if (str.mid(strpos, shortName.length()) == shortName) { strpos += shortName.length(); error = false; } ++j; } break; case 'b': // Month Name Short case 'h': // Month Name Short case 'B': // Month Name Long error = true; j = 1; while (error && j <= calendar->d_ptr->maxMonthsInYear()) { // This may be a problem in calendar systems with variable number of months // in the year and/or names of months that change depending on the year, e.g // Hebrew. We really need to know the correct year first, but we may not have // read it yet and will be using the current year instead int monthYear; if (parsedYear) { monthYear = yy; } else { monthYear = calendar->year(QDate::currentDate()); } if (calendar->locale()->dateMonthNamePossessive()) { shortName = calendar->monthName(j, monthYear, KCalendarSystem::ShortNamePossessive).toLower(); longName = calendar->monthName(j, monthYear, KCalendarSystem::LongNamePossessive).toLower(); } else { shortName = calendar->monthName(j, monthYear, KCalendarSystem::ShortName).toLower(); longName = calendar->monthName(j, monthYear, KCalendarSystem::LongName).toLower(); } if (str.mid(strpos, longName.length()) == longName) { mm = j; strpos += longName.length(); error = false; } else if (str.mid(strpos, shortName.length()) == shortName) { mm = j; strpos += shortName.length(); error = false; } ++j; } break; case 'd': // Day Number Long case 'e': // Day Number Short dd = calendar->dayStringToInteger(str.mid(strpos), readLength); strpos += readLength; error = readLength <= 0; break; case 'n': // PosixFormat %n is Newline // KdeFormat %n is Month Number Short if (standard == KLocale::KdeFormat) { mm = calendar->monthStringToInteger(str.mid(strpos), readLength); strpos += readLength; error = readLength <= 0; } // standard == KLocale::PosixFormat // all whitespace already 'eaten', no action required break; case 'm': // Month Number Long mm = calendar->monthStringToInteger(str.mid(strpos), readLength); strpos += readLength; error = readLength <= 0; break; case 'Y': // Year Number Long case 'y': // Year Number Short if (modifierChar == QLatin1Char('E')) { // Year In Era if (fmtChar == QLatin1Char('y')) { ey = calendar->yearStringToInteger(str.mid(strpos), readLength); strpos += readLength; error = readLength <= 0; } else { error = true; j = calendar->eraList()->count() - 1; // Start with the most recent while (error && j >= 0) { QString subFormat = calendar->eraList()->at(j).format(); QString subInput = str.mid(strpos); DateTimeComponents subResult = parseDatePosix(subInput, subFormat, calendar, locale, digitSet, standard); if (!subResult.error) { if (subResult.parsedYear) { yy = subResult.year; parsedYear = true; error = false; strpos += subResult.inputPosition; } else if (!subResult.eraName.isEmpty() && subResult.yearInEra >= 0) { ee = subResult.eraName; ey = subResult.yearInEra; error = false; strpos += subResult.inputPosition; } } --j; } } } else { yy = calendar->yearStringToInteger(str.mid(strpos), readLength); strpos += readLength; if (fmtChar == QLatin1Char('y')) { yy = calendar->applyShortYearWindow(yy); } error = readLength <= 0; if (!error) { parsedYear = true; } } break; case 'C': // Era error = true; if (modifierChar == QLatin1Char('E')) { j = calendar->eraList()->count() - 1; // Start with the most recent while (error && j >= 0) { shortName = calendar->d_ptr->m_eraList->at(j).name(KLocale::ShortName).toLower(); longName = calendar->eraList()->at(j).name(KLocale::LongName).toLower(); if (str.mid(strpos, longName.length()) == longName) { strpos += longName.length(); ee = longName; error = false; } else if (str.mid(strpos, shortName.length()) == shortName) { strpos += shortName.length(); ee = shortName; error = false; } --j; } } break; case 'j': // Day Of Year Number dayInYear = integerFromString(str.mid(strpos), 3, readLength); strpos += readLength; error = readLength <= 0; break; case 'V': // ISO Week Number isoWeekNumber = integerFromString(str.mid(strpos), 2, readLength); strpos += readLength; error = readLength <= 0; break; case 'u': // ISO Day Of Week dayOfIsoWeek = integerFromString(str.mid(strpos), 1, readLength); strpos += readLength; error = readLength <= 0; break; } } } DateTimeComponents result; result.error = error; result.inputPosition = strpos; result.formatPosition = fmtpos; if (error) { result.day = -1; result.month = -1; result.year = 0; result.parsedYear = false; result.eraName.clear(); result.yearInEra = -1; result.dayInYear = -1; result.isoWeekNumber = -1; result.dayOfIsoWeek = -1; } else { result.day = dd; result.month = mm; result.year = yy; result.parsedYear = parsedYear; result.eraName = ee; result.yearInEra = ey; result.dayInYear = dayInYear; result.isoWeekNumber = isoWeekNumber; result.dayOfIsoWeek = dayOfIsoWeek; } return result; } // Parse an input string to match a UNICODE DateTime format string and return any components found DateTimeComponents KDateTimeParser::parseDateUnicode(const QString &inputString, const QString &formatString, const KCalendarSystem *calendar, const KLocale *locale, KLocale::DigitSet digitSet) const { Q_UNUSED(calendar); Q_UNUSED(locale); Q_UNUSED(digitSet); QString str = inputString.simplified().toLower(); QString fmt = formatString.simplified(); int dd = -1; int mm = -1; int yy = 0; bool parsedYear = false; int ey = -1; QString ee; int dayInYear = -1; int isoWeekNumber = -1; int dayOfIsoWeek = -1; int strpos = 0; int fmtpos = 0; //int readLength; // Temporary variable used when reading input bool error = false; DateTimeComponents result; result.error = error; result.inputPosition = strpos; result.formatPosition = fmtpos; if (error) { result.day = -1; result.month = -1; result.year = 0; result.parsedYear = false; result.eraName.clear(); result.yearInEra = -1; result.dayInYear = -1; result.isoWeekNumber = -1; result.dayOfIsoWeek = -1; } else { result.day = dd; result.month = mm; result.year = yy; result.parsedYear = parsedYear; result.eraName = ee; result.yearInEra = ey; result.dayInYear = dayInYear; result.isoWeekNumber = isoWeekNumber; result.dayOfIsoWeek = dayOfIsoWeek; } return result; } // Peel a number off the front of a string which may have other trailing chars after the number // Stop either at either maxLength, eos, or first non-digit char int KDateTimeParser::integerFromString(const QString &string, int maxLength, int &readLength) const { int value = -1; int position = 0; readLength = 0; bool ok = false; if (maxLength < 0) { maxLength = string.length(); } while (position < string.length() && position < maxLength && string.at(position).isDigit()) { position++; } if (position > 0) { value = string.left(position).toInt(&ok); if (ok) { readLength = position; } else { value = -1; } } return value; }