File indexing completed on 2024-04-21 14:55:33

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 }