2014-11-13 01:04:59 +02:00
|
|
|
/*
|
|
|
|
Copyright 2009, 2010 John Layt <john@layt.net>
|
|
|
|
|
|
|
|
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()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
2023-07-21 10:55:09 +03:00
|
|
|
result = parseDateUnicode(inputString, formatString);
|
2014-11-13 01:04:59 +02:00
|
|
|
} 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,
|
2023-07-21 10:55:09 +03:00
|
|
|
const QString &formatString) const
|
2014-11-13 01:04:59 +02:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|