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

0001 /*
0002     Copyright 2009-2010 John Layt <john@layt.net>
0003     Copyright 2005-2010 David Jarvie <djarvie@kde.org>
0004 
0005     This library is free software; you can redistribute it and/or
0006     modify it under the terms of the GNU Library General Public
0007     License as published by the Free Software Foundation; either
0008     version 2 of the License, or (at your option) any later version.
0009 
0010     This library is distributed in the hope that it will be useful,
0011     but WITHOUT ANY WARRANTY; without even the implied warranty of
0012     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013     Library General Public License for more details.
0014 
0015     You should have received a copy of the GNU Library General Public License
0016     along with this library; see the file COPYING.LIB.  If not, write to
0017     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018     Boston, MA 02110-1301, USA.
0019 */
0020 
0021 #include "kdatetimeformatter_p.h"
0022 
0023 #include <QDebug>
0024 #include <QDate>
0025 #include <QString>
0026 #include <QStringList>
0027 #include <QChar>
0028 
0029 #include "kdatetime.h"
0030 #include "ktimezone.h"
0031 #include "kcalendarsystem.h"
0032 #include "kdayperiod_p.h"
0033 #include "klocale_p.h"
0034 
0035 KDateTimeFormatter::KDateTimeFormatter()
0036     : m_englishLocale(nullptr),
0037       m_englishCalendar(nullptr)
0038 {
0039 }
0040 
0041 KDateTimeFormatter::~KDateTimeFormatter()
0042 {
0043     delete m_englishCalendar;
0044     delete m_englishLocale;
0045 }
0046 
0047 QString KDateTimeFormatter::formatDate(const QDate &fromDate,
0048                                        const QString &toFormat,
0049                                        const KCalendarSystem *calendar,
0050                                        const KLocale *locale,
0051                                        KLocale::DigitSet digitSet,
0052                                        KLocale::DateTimeFormatStandard formatStandard) const
0053 {
0054     // If not valid input, don't waste our time
0055     if (!calendar->isValid(fromDate) || toFormat.isEmpty()) {
0056         return QString();
0057     }
0058 
0059     return formatDateTime(KDateTime(fromDate), toFormat, KLocale::TimeFormatOption(), calendar, locale, digitSet, formatStandard);
0060 }
0061 
0062 QString KDateTimeFormatter::formatTime(const QTime &fromTime,
0063                                        const QString &toFormat,
0064                                        KLocale::TimeFormatOptions timeOptions,
0065                                        const KCalendarSystem *calendar,
0066                                        const KLocale *locale,
0067                                        KLocale::DigitSet digitSet,
0068                                        KLocale::DateTimeFormatStandard formatStandard) const
0069 {
0070     // If not valid input, don't waste our time
0071     if (fromTime.isValid() || toFormat.isEmpty()) {
0072         return QString();
0073     }
0074 
0075     return formatDateTime(KDateTime(QDate::currentDate(), fromTime), toFormat, timeOptions, calendar, locale, digitSet, formatStandard);
0076 }
0077 
0078 // Format an input date to match a POSIX date format string
0079 QString KDateTimeFormatter::formatDateTime(const KDateTime &fromDateTime,
0080         const QString &toFormat,
0081         KLocale::TimeFormatOptions timeOptions,
0082         const KCalendarSystem *calendar,
0083         const KLocale *locale,
0084         KLocale::DigitSet digitSet,
0085         KLocale::DateTimeFormatStandard formatStandard) const
0086 {
0087     // If not valid input, don't waste our time
0088     if (!fromDateTime.isValid() || !calendar->isValid(fromDateTime.date()) || toFormat.isEmpty()) {
0089         return QString();
0090     }
0091 
0092     if (formatStandard == KLocale::UnicodeFormat) {
0093         return formatDateTimeUnicode(fromDateTime, toFormat, timeOptions, calendar, locale, digitSet);
0094     } else {
0095         return formatDateTimePosix(fromDateTime, toFormat, timeOptions, calendar, locale, digitSet, formatStandard);
0096     }
0097 }
0098 
0099 // Format an input date to match a POSIX date format string
0100 QString KDateTimeFormatter::formatDateTimePosix(const KDateTime &fromDateTime,
0101         const QString &toFormat,
0102         KLocale::TimeFormatOptions timeOptions,
0103         const KCalendarSystem *calendar,
0104         const KLocale *locale,
0105         KLocale::DigitSet digitSet,
0106         KLocale::DateTimeFormatStandard formatStandard) const
0107 {
0108 //qDebug() << "formatDateTimePosix(" << fromDateTime << toFormat << ")";
0109     // If not valid input, don't waste our time
0110     if (!fromDateTime.isValid() || toFormat.isEmpty()) {
0111         return QString();
0112     }
0113 
0114     QChar thisChar;  // Current toFormat char being processed
0115     QString result;  // Output string
0116 
0117     int padWidth = 0;     // The width to pad numbers to
0118     QChar padChar = QLatin1Char('0');  // The char to use when padding numbers
0119     QChar signChar;       // The sign to use when formatting numbers
0120     QChar caseChar;       // The case modifier to use
0121 
0122     bool escape = false;       // Are we processing an escape char (%)
0123     bool escapeWidth = false;  // Are we processing an escape width
0124     bool escapePad = false;    // Are we processing an escape pad char
0125     bool escapeMod = false;    // Are we processing an escape modifier
0126     int escapeIndex = 0;         // Position in string of current escape char (%)
0127 
0128     QChar modifierChar = QChar();
0129     bool invalidModifier = false;
0130 
0131     // Pre-fetch the core date components as they get used a lot
0132     // and it is 1/3rd more efficient than 3 separatre calls
0133     int year, month, day;
0134     calendar->getDate(fromDateTime.date(), &year, &month, &day);
0135 
0136     for (int formatIndex = 0; formatIndex < toFormat.length(); ++formatIndex) {
0137 
0138         thisChar = toFormat.at(formatIndex);
0139 
0140         if (!escape) {
0141 
0142             if (thisChar == QLatin1Char('%')) {
0143                 escape = true;
0144                 escapeIndex = formatIndex;
0145             } else {
0146                 result.append(toFormat.at(formatIndex));
0147             }
0148 
0149         } else if (!escapeMod && !escapeWidth && thisChar == QLatin1Char('-')) {   // no padding
0150 
0151             padChar = QChar();
0152             escapePad = true;
0153 
0154         } else if (!escapeMod && !escapeWidth && thisChar == QLatin1Char('_')) {   // space padding
0155 
0156             padChar = QLatin1Char(' ');
0157             escapePad = true;
0158 
0159         } else if (!escapeMod && !escapeWidth && thisChar == QLatin1Char('0')) {   // 0 padding
0160 
0161             padChar = QLatin1Char('0');
0162             escapePad = true;
0163 
0164         } else if (!escapeMod && !escapeWidth && (thisChar == QLatin1Char('^') || thisChar == QLatin1Char('#'))) {     // Change case
0165 
0166             caseChar = thisChar;
0167 
0168         } else if (!escapeMod &&
0169                    ((!escapeWidth && thisChar >= QLatin1Char('1') && thisChar <= QLatin1Char('9')) ||
0170                     (escapeWidth && thisChar >= QLatin1Char('0') && thisChar <= QLatin1Char('9')))) {     // Change width
0171 
0172             if (escapeWidth) {
0173                 padWidth = padWidth * 10;
0174             }
0175             padWidth = padWidth + QString(thisChar).toInt();
0176             escapeWidth = true;
0177 
0178         } else if (!escapeMod && (thisChar == QLatin1Char('E') || thisChar == QLatin1Char('O') || thisChar == QLatin1Char(':'))) {     // Set modifier
0179 
0180             escapeMod = true;
0181             modifierChar = thisChar;
0182             if (thisChar == QLatin1Char(':')) {
0183                 invalidModifier = true;
0184             }
0185 
0186         } else {
0187 
0188             bool invalidComponent = false;
0189             QString componentString;
0190             int componentInteger = 0;
0191             int minWidth = 0;
0192             int isoWeekYear = year;
0193             QDate yearDate;
0194             KDateTime::SpecType timeSpecType;
0195 
0196             //Default settings unless overridden by pad and case flags and width: are 0 pad to 0 width no sign
0197             //Names will override 0 pad with no pad unless flagged
0198             //Numbers will override with correct width unless flagged
0199             QChar thisChar = toFormat.at(formatIndex).unicode();
0200             switch (thisChar.unicode()) {
0201             case '%':  //Literal %
0202                 if (modifierChar != QLatin1Char(':')) {    // E and O mods are ignored if not used, but : is treated as literal
0203                     componentString = QLatin1Char('%');
0204                     if (!escapePad) {
0205                         padChar = QChar();
0206                     }
0207                 }
0208                 break;
0209             case 't':  //Tab
0210                 if (modifierChar != QLatin1Char(':')) {
0211                     componentString = QString::fromLatin1("\t");
0212                     if (!escapePad) {
0213                         padChar = QChar();
0214                     }
0215                 }
0216                 break;
0217             case 'Y':
0218                 if (modifierChar == QLatin1Char('E')) {    //Era Year, default no pad to 0 places no sign
0219                     if (!escapePad) {
0220                         padChar = QLatin1Char(' ');
0221                     }
0222                     componentString = calendar->eraYear(fromDateTime.date());
0223                 } else if (modifierChar != QLatin1Char(':')) {    //Long year numeric, default 0 pad to 4 places with sign
0224                     componentInteger = qAbs(year);
0225                     minWidth = 4;
0226                     if (year < 0) {
0227                         signChar = QLatin1Char('-');
0228                     }
0229                 }
0230                 break;
0231             case 'C':
0232                 if (modifierChar == QLatin1Char('E')) {    //Era name, default no pad to 0 places no sign
0233                     if (!escapePad) {
0234                         padChar = QLatin1Char(' ');
0235                     }
0236                     componentString = calendar->eraName(fromDateTime.date());
0237                 } else if (modifierChar != QLatin1Char(':')) {    //Century numeric, default 0 pad to 2 places with sign
0238                     componentInteger =  qAbs(year) / 100;
0239                     minWidth = 2;
0240                     if (year < 0) {
0241                         signChar = QLatin1Char('-');
0242                     }
0243                 }
0244                 break;
0245             case 'y':
0246                 if (modifierChar == QLatin1Char('E')) {    //Year in Era number, default 0 pad to 1 places no sign
0247                     componentInteger =  calendar->yearInEra(fromDateTime.date());
0248                     minWidth = 1;
0249                 } else if (modifierChar != QLatin1Char(':')) {    //Short year numeric, default 0 pad to 2 places with sign
0250                     componentInteger =  qAbs(year) % 100;
0251                     minWidth = 2;
0252                     if (year < 0) {
0253                         signChar = QLatin1Char('-');
0254                     }
0255                 }
0256                 break;
0257             case 'm':  // Month numeric
0258                 componentInteger =  month;
0259                 if (modifierChar == QLatin1Char(':')) {    //Short month numeric, default no pad to 1 places no sign
0260                     minWidth = 1;
0261                     if (!escapePad) {
0262                         padChar = QChar();
0263                     }
0264                     invalidModifier = false;
0265                 } else {  //Long month numeric, default 0 pad to 2 places no sign
0266                     componentInteger =  month;
0267                     minWidth = 2;
0268                 }
0269                 break;
0270             case 'n':
0271                 //PosixFormat %n is newline
0272                 //KdeFormat %n is short month numeric
0273                 if (modifierChar != QLatin1Char(':')) {
0274                     if (formatStandard == KLocale::KdeFormat) {
0275                         //Copy what %e does, no padding by default
0276                         //Short month numeric, default no pad to 1 places no sign
0277                         componentInteger =  month;
0278                         minWidth = 1;
0279                         if (!escapePad) {
0280                             padChar = QChar();
0281                         }
0282                     } else {  // formatStandard == KLocale::PosixFormat
0283                         componentString = QLatin1Char('\n');
0284                     }
0285                 }
0286                 break;
0287             case 'd':  //Long day numeric, default 0 pad to 2 places no sign
0288                 if (modifierChar != QLatin1Char(':')) {
0289                     componentInteger =  day;
0290                     minWidth = 2;
0291                 }
0292                 break;
0293             case 'e':  //Short day numeric, default no sign
0294                 //PosixFormat %e is space pad to 2 places
0295                 //KdeFormat %e is no pad to 1 place
0296                 if (modifierChar != QLatin1Char(':')) {
0297                     componentInteger =  day;
0298                     if (formatStandard == KLocale::KdeFormat) {
0299                         minWidth = 1;
0300                         if (!escapePad) {
0301                             padChar = QChar();
0302                         }
0303                     } else {  // formatStandard == KLocale::PosixFormat
0304                         minWidth = 2;
0305                         if (!escapePad) {
0306                             padChar = QLatin1Char(' ');
0307                         }
0308                     }
0309                 }
0310                 break;
0311             case 'B':  //Long month name, default space pad to 0 places no sign
0312                 if (locale->dateMonthNamePossessive()) {
0313                     if (modifierChar == QLatin1Char(':')) {
0314                         invalidModifier = false;
0315                         initEnglish(calendar, locale);
0316                         componentString = m_englishCalendar->monthName(month, year, KCalendarSystem::LongNamePossessive);
0317                     } else {
0318                         componentString = calendar->monthName(month, year, KCalendarSystem::LongNamePossessive);
0319                     }
0320                 } else {
0321                     if (modifierChar == QLatin1Char(':')) {
0322                         invalidModifier = false;
0323                         initEnglish(calendar, locale);
0324                         componentString = m_englishCalendar->monthName(month, year, KCalendarSystem::LongName);
0325                     } else {
0326                         componentString = calendar->monthName(month, year, KCalendarSystem::LongName);
0327                     }
0328                 }
0329                 if (!escapePad) {
0330                     padChar = QLatin1Char(' ');
0331                 }
0332                 break;
0333             case 'h':  //Short month name, default space pad to 0 places no sign
0334             case 'b':  //Short month name, default space pad to 0 places no sign
0335                 if (locale->dateMonthNamePossessive()) {
0336                     if (modifierChar == QLatin1Char(':')) {
0337                         invalidModifier = false;
0338                         initEnglish(calendar, locale);
0339                         componentString = m_englishCalendar->monthName(month, year, KCalendarSystem::ShortNamePossessive);
0340                     } else {
0341                         componentString = calendar->monthName(month, year, KCalendarSystem::ShortNamePossessive);
0342                     }
0343                 } else {
0344                     if (modifierChar == QLatin1Char(':')) {
0345                         invalidModifier = false;
0346                         initEnglish(calendar, locale);
0347                         componentString = m_englishCalendar->monthName(month, year, KCalendarSystem::ShortName);
0348                     } else {
0349                         componentString = calendar->monthName(month, year, KCalendarSystem::ShortName);
0350                     }
0351                 }
0352                 if (!escapePad) {
0353                     padChar = QLatin1Char(' ');
0354                 }
0355                 break;
0356             case 'A':  //Long weekday name, default space pad to 0 places no sign
0357                 if (modifierChar == QLatin1Char(':')) {
0358                     invalidModifier = false;
0359                     initEnglish(calendar, locale);
0360                     componentString = m_englishCalendar->weekDayName(fromDateTime.date(), KCalendarSystem::LongDayName);
0361                 } else {
0362                     componentString = calendar->weekDayName(fromDateTime.date(), KCalendarSystem::LongDayName);
0363                 }
0364                 if (!escapePad) {
0365                     padChar = QLatin1Char(' ');
0366                 }
0367                 break;
0368             case 'a':  //Short weekday name, default space pad to 0 places no sign
0369                 if (modifierChar == QLatin1Char(':')) {
0370                     invalidModifier = false;
0371                     initEnglish(calendar, locale);
0372                     componentString = m_englishCalendar->weekDayName(fromDateTime.date(), KCalendarSystem::ShortDayName);
0373                 } else {
0374                     componentString = calendar->weekDayName(fromDateTime.date(), KCalendarSystem::ShortDayName);
0375                 }
0376                 if (!escapePad) {
0377                     padChar = QLatin1Char(' ');
0378                 }
0379                 break;
0380             case 'j':  //Long day of year numeric, default 0 pad to 3 places no sign
0381                 if (modifierChar != QLatin1Char(':')) {
0382                     componentInteger = calendar->dayOfYear(fromDateTime.date());
0383                     minWidth = 3;
0384                 }
0385                 break;
0386             case 'V':  //Long ISO week of year numeric, default 0 pad to 2 places no sign
0387                 if (modifierChar != QLatin1Char(':')) {
0388                     componentInteger = calendar->week(fromDateTime.date(), KLocale::IsoWeekNumber);
0389                     minWidth = 2;
0390                 }
0391                 break;
0392             case 'G':  //Long year of ISO week of year numeric, default 0 pad to 4 places with sign
0393                 if (modifierChar != QLatin1Char(':')) {
0394                     calendar->week(fromDateTime.date(), KLocale::IsoWeekNumber, &isoWeekYear);
0395                     calendar->setDate(yearDate, isoWeekYear, 1, 1);
0396                     componentInteger = qAbs(isoWeekYear);
0397                     minWidth = 4;
0398                     if (isoWeekYear < 0) {
0399                         signChar = QLatin1Char('-');
0400                     }
0401                 }
0402                 break;
0403             case 'g':  //Short year of ISO week of year numeric, default 0 pad to 2 places with sign
0404                 if (modifierChar != QLatin1Char(':')) {
0405                     calendar->week(fromDateTime.date(), KLocale::IsoWeekNumber, &isoWeekYear);
0406                     calendar->setDate(yearDate, isoWeekYear, 1, 1);
0407                     componentInteger = qAbs(isoWeekYear) % 100;
0408                     minWidth = 2;
0409                     if (isoWeekYear < 0) {
0410                         signChar = QLatin1Char('-');
0411                     }
0412                 }
0413                 break;
0414             case 'u':
0415                 if (modifierChar == QLatin1Char(':')) {    // TZ UTC offset hours
0416                     invalidModifier = false;
0417                     KDateTime::SpecType timeSpecType = fromDateTime.timeType();
0418                     if (timeSpecType == KDateTime::UTC || timeSpecType == KDateTime::TimeZone ||
0419                             timeSpecType == KDateTime::OffsetFromUTC) {
0420                         componentInteger = fromDateTime.utcOffset() / 3600;
0421                         if (componentInteger >= 0) {
0422                             signChar = QLatin1Char('+');
0423                         } else {
0424                             componentInteger = -componentInteger;
0425                             signChar = QLatin1Char('-');
0426                         }
0427                         minWidth = 2;
0428                     }
0429                 } else {  // Short day of week numeric
0430                     componentInteger = calendar->dayOfWeek(fromDateTime.date());
0431                     minWidth = 1;
0432                 }
0433                 break;
0434             case 'D':  // US short date format, ignore any overrides
0435                 if (modifierChar != QLatin1Char(':')) {
0436                     componentString = formatDateTimePosix(fromDateTime, QString::fromLatin1("%m/%d/%y"), timeOptions, calendar, locale, digitSet, formatStandard);
0437                     padWidth = 0;
0438                     padChar = QChar();
0439                     caseChar = QChar();
0440                 }
0441                 break;
0442             case 'F':  // Full or ISO short date format, ignore any overrides
0443                 if (modifierChar != QLatin1Char(':')) {
0444                     componentString = formatDateTimePosix(fromDateTime, QString::fromLatin1("%Y-%m-%d"), timeOptions, calendar, locale, digitSet, formatStandard);
0445                     padWidth = 0;
0446                     padChar = QChar();
0447                     caseChar = QChar();
0448                 }
0449                 break;
0450             case 'x':  // Locale short date format, ignore any overrides
0451                 if (modifierChar != QLatin1Char(':')) {
0452                     componentString = formatDateTimePosix(fromDateTime, locale->dateFormatShort(), timeOptions, calendar, locale, digitSet, formatStandard);
0453                     padWidth = 0;
0454                     padChar = QChar();
0455                     caseChar = QChar();
0456                 }
0457                 break;
0458             case 'H':  // Long 24 hour
0459             case 'k':  // Short 24 hour
0460                 if (modifierChar != QLatin1Char(':')) {
0461                     componentInteger =  fromDateTime.time().hour();
0462                     minWidth = 1;
0463                     if (!escapePad) {
0464                         padChar = QChar();
0465                     }
0466                 }
0467                 break;
0468             case 'I':  // Long 12 hour
0469             case 'l':  // Short 12 hour
0470                 if (modifierChar != QLatin1Char(':')) {
0471                     if ((timeOptions & KLocale::TimeDuration) == KLocale::TimeDuration) {
0472                         componentInteger =  fromDateTime.time().hour();
0473                     } else {
0474                         componentInteger = locale->d->dayPeriodForTime(fromDateTime.time()).hourInPeriod(fromDateTime.time());
0475                     }
0476                     if (thisChar == QLatin1Char('I')) {
0477                         minWidth = 2;
0478                     } else {
0479                         minWidth = 1;
0480                         if (!escapePad) {
0481                             padChar = QChar();
0482                         }
0483                     }
0484                 }
0485                 break;
0486             case 'M':   // Long minutes
0487                 if (modifierChar != QLatin1Char(':')) {
0488                     componentInteger = fromDateTime.time().minute();
0489                     minWidth = 2;
0490                 }
0491                 break;
0492             case 'S':   // Long seconds
0493                 invalidModifier = false;
0494                 if ((timeOptions & KLocale::TimeWithoutSeconds) == KLocale::TimeWithoutSeconds) {
0495                     //TODO strip the preceding/following punctuation
0496                 } else {
0497                     componentInteger = fromDateTime.time().second();
0498                     if (modifierChar == QLatin1Char(':')) {    // Only if not 00 seconds
0499                         if (componentInteger > 0 || fromDateTime.time().msec() > 0) {
0500                             result.append(QLatin1Char(':'));
0501                             minWidth = 2;
0502                         }
0503                     } else {
0504                         minWidth = 2;
0505                     }
0506                 }
0507                 break;
0508             case 's':
0509                 if (modifierChar == QLatin1Char(':')) {    // Milliseconds
0510                     invalidModifier = false;
0511                     componentInteger = fromDateTime.time().msec();
0512                     minWidth = 3;
0513                 } else {  // Whole seconds since Unix Epoch
0514                     KDateTime unixEpoch;
0515                     unixEpoch.setTime_t(0);
0516                     componentInteger = unixEpoch.secsTo(fromDateTime);
0517                 }
0518                 break;
0519             case 'p':   // AM/PM symbol
0520             case 'P':   // AM/PM symbol in lowercase
0521                 if ((timeOptions & KLocale::TimeWithoutAmPm) == KLocale::TimeWithoutAmPm) {
0522                     //TODO strip the preceding/following punctuation
0523                 } else {
0524                     if (modifierChar == QLatin1Char(':')) {
0525                         invalidModifier = false;
0526                         initEnglish(calendar, locale);
0527                         componentString = m_englishLocale->d->dayPeriodForTime(fromDateTime.time()).periodName(KLocale::ShortName);
0528                     } else {
0529                         componentString = locale->d->dayPeriodForTime(fromDateTime.time()).periodName(KLocale::ShortName);
0530                     }
0531                     if (thisChar == QLatin1Char('P')) {
0532                         componentString = componentString.toLower();
0533                     }
0534                 }
0535                 break;
0536             case 'z':  // TZ UTC Offset
0537                 invalidModifier = false;
0538                 timeSpecType = fromDateTime.timeType();
0539                 if (timeSpecType == KDateTime::UTC || timeSpecType == KDateTime::TimeZone ||
0540                         timeSpecType == KDateTime::OffsetFromUTC) {
0541                     if (modifierChar == QLatin1Char(':')) {    // TZ UTC offset hours & minutes with colon
0542                         int offsetInSeconds = fromDateTime.utcOffset();
0543                         if (offsetInSeconds >= 0) {
0544                             signChar = QLatin1Char('+');
0545                         } else {
0546                             offsetInSeconds = -offsetInSeconds;
0547                             signChar = QLatin1Char('-');
0548                         }
0549                         int offsetHours = offsetInSeconds / 3600;
0550                         int offsetMinutes = (offsetInSeconds / 60) % 60;
0551                         //int offsetSeconds = offsetInSeconds % 60;
0552                         QString hourComponent = stringFromInteger(offsetHours, 2, QLatin1Char('0'), signChar, digitSet, locale);
0553                         QString minuteComponent = stringFromInteger(offsetMinutes, 2, QLatin1Char('0'), QChar(), digitSet, locale);
0554                         componentString = hourComponent + QLatin1Char(':') + minuteComponent;
0555                         minWidth = 0;
0556                         padChar = QChar();
0557                         padWidth = 0;
0558                     } else {  // TZ UTC offset hours & minutes
0559                         componentInteger = fromDateTime.utcOffset() / 60;
0560                         if (componentInteger >= 0) {
0561                             signChar = QLatin1Char('+');
0562                         } else {
0563                             componentInteger = -componentInteger;
0564                             signChar = QLatin1Char('-');
0565                         }
0566                         minWidth = 4;
0567                     }
0568                 }
0569                 break;
0570             case 'Z':  // TZ Name
0571                 invalidModifier = false;
0572                 timeSpecType = fromDateTime.timeType();
0573                 if (timeSpecType == KDateTime::UTC || timeSpecType == KDateTime::TimeZone) {
0574                     KTimeZone tz = fromDateTime.timeZone();
0575                     if (tz.isValid()) {
0576                         if (modifierChar == QLatin1Char(':')) {    // TZ full name
0577                             componentString = QString::fromLatin1(tz.abbreviation(fromDateTime.toUtc().dateTime()));
0578                         } else {  // TZ abbreviated name
0579                             componentString = tz.name();
0580                         }
0581                     }
0582                 }
0583                 break;
0584             default:  //No valid format code, treat as literal
0585                 invalidComponent = true;
0586                 break;
0587             }
0588 
0589             if (invalidComponent || invalidModifier) {    // If escape sequence invalid treat as literal
0590                 componentString = toFormat.mid(escapeIndex, formatIndex);
0591             } else if (componentString.isEmpty()) {    //i.e. is a number component
0592                 padWidth = qMax(minWidth, padWidth);
0593                 componentString = stringFromInteger(componentInteger, padWidth, padChar, signChar, digitSet, locale);
0594             } else { //i.e. is a string component
0595                 if (padChar != QChar() && padWidth != 0) {
0596                     componentString = componentString.rightJustified(padWidth, padChar);
0597                 }
0598 
0599                 if (caseChar == QLatin1Char('^')) {
0600                     componentString = componentString.toUpper();
0601                 } else if (caseChar == QLatin1Char('#')) {
0602                     componentString = componentString.toUpper(); // JPL ???
0603                 }
0604             }
0605 
0606             result.append(componentString);
0607 
0608             escape = false;
0609             escapePad = false;
0610             padChar = QLatin1Char('0');
0611             escapeMod = false;
0612             invalidModifier = false;
0613             invalidComponent = false;
0614             modifierChar = QChar();
0615             caseChar = QChar();
0616             escapeWidth = false;
0617             padWidth = 0;
0618             signChar = QChar();
0619         }
0620     }
0621 //qDebug() << " return = " << result;
0622 //qDebug() << "";
0623     return result;
0624 }
0625 
0626 void KDateTimeFormatter::initEnglish(const KCalendarSystem *calendar, const KLocale *locale) const
0627 {
0628     if (!m_englishCalendar || m_englishCalendar->calendarSystem() != calendar->calendarSystem()) {
0629         // Set up an English locale and calendar for use with ':' modifier which forces English names
0630         if (!m_englishLocale) {
0631             m_englishLocale = new KLocale(*locale);
0632             m_englishLocale->setLanguage(QStringList() << QString::fromLatin1("en_US"));
0633         }
0634         delete m_englishCalendar;
0635         m_englishCalendar = KCalendarSystem::create(calendar->calendarSystem(), m_englishLocale);
0636     }
0637 }
0638 
0639 // Reimplement if special string handling required
0640 // Format an input date to match a UNICODE date format string
0641 // Original QDate::fmtDateTime() code taken from Qt 4.7 under LGPL, now heavily modifed
0642 // Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
0643 QString KDateTimeFormatter::formatDateTimeUnicode(const KDateTime &fromDateTime,
0644         const QString &toFormat,
0645         KLocale::TimeFormatOptions timeOptions,
0646         const KCalendarSystem *calendar,
0647         const KLocale *locale,
0648         KLocale::DigitSet digitSet) const
0649 {
0650     const QLatin1Char quote('\'');
0651 
0652     QString result;
0653     QString format;
0654     QChar status(QLatin1Char('0'));
0655 
0656     for (int i = 0; i < toFormat.length(); ++i) {
0657         if (toFormat.at(i) == quote) {
0658             if (status == quote) {
0659                 if (i > 0 && toFormat.at(i - 1) == quote) {
0660                     result += QLatin1Char('\'');
0661                 }
0662                 status = QLatin1Char('0');
0663             } else {
0664                 if (!format.isEmpty()) {
0665                     result += getUnicodeString(fromDateTime, format, timeOptions, calendar, locale, digitSet);
0666                     format.clear();
0667                 }
0668                 status = quote;
0669             }
0670         } else if (status == quote) {
0671             result += toFormat.at(i);
0672         } else if (toFormat.at(i) == status) {
0673             if (toFormat.at(i) == QLatin1Char('P') ||
0674                     toFormat.at(i) == QLatin1Char('p')) {
0675                 status = QLatin1Char('0');
0676             }
0677             format += toFormat.at(i);
0678         } else {
0679             result += getUnicodeString(fromDateTime, format, timeOptions, calendar, locale, digitSet);
0680             format.clear();
0681             if ((toFormat.at(i) == QLatin1Char('d')) ||
0682                     (toFormat.at(i) == QLatin1Char('M')) ||
0683                     (toFormat.at(i) == QLatin1Char('y'))) {
0684                 status = toFormat.at(i);
0685                 format += toFormat.at(i);
0686             } else {
0687                 result += toFormat.at(i);
0688                 status = QLatin1Char('0');
0689             }
0690         }
0691     }
0692 
0693     result += getUnicodeString(fromDateTime, format, timeOptions, calendar, locale, digitSet);
0694 
0695     return result;
0696 }
0697 
0698 // Original QDate::getFmtString() code taken from Qt 4.7 under LGPL, now heavily modifed
0699 // Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
0700 // Replaces tokens by their value. See QDateTime::toString() for a list of valid tokens
0701 QString KDateTimeFormatter::getUnicodeString(const KDateTime &fromDateTime,
0702         const QString &toFormat,
0703         KLocale::TimeFormatOptions timeOptions,
0704         const KCalendarSystem *calendar,
0705         const KLocale *locale,
0706         KLocale::DigitSet digitSet) const
0707 {
0708     if (toFormat.isEmpty()) {
0709         return QString();
0710     }
0711 
0712     QString result = toFormat;
0713     int removed = 0;
0714 
0715     if (toFormat.startsWith(QLatin1String("dddd"))) {
0716         result = calendar->weekDayName(fromDateTime.date(), KCalendarSystem::LongDayName);
0717         removed = 4;
0718     } else if (toFormat.startsWith(QLatin1String("ddd"))) {
0719         result = calendar->weekDayName(fromDateTime.date(), KCalendarSystem::ShortDayName);
0720         removed = 3;
0721     } else if (toFormat.startsWith(QLatin1String("dd"))) {
0722         result = QString::number(calendar->day(fromDateTime.date())).rightJustified(2, QLatin1Char('0'), true);
0723         removed = 2;
0724     } else if (toFormat.at(0) == QLatin1Char('d')) {
0725         result = QString::number(calendar->day(fromDateTime.date()));
0726         removed = 1;
0727     } else if (toFormat.startsWith(QLatin1String("MMMM"))) {
0728         result = calendar->monthName(calendar->month(fromDateTime.date()), calendar->year(fromDateTime.date()), KCalendarSystem::LongName);
0729         removed = 4;
0730     } else if (toFormat.startsWith(QLatin1String("MMM"))) {
0731         result = calendar->monthName(calendar->month(fromDateTime.date()), calendar->year(fromDateTime.date()), KCalendarSystem::ShortName);
0732         removed = 3;
0733     } else if (toFormat.startsWith(QLatin1String("MM"))) {
0734         result = QString::number(calendar->month(fromDateTime.date())).rightJustified(2, QLatin1Char('0'), true);
0735         removed = 2;
0736     } else if (toFormat.at(0) == QLatin1Char('M')) {
0737         result = QString::number(calendar->month(fromDateTime.date()));
0738         removed = 1;
0739     } else if (toFormat.startsWith(QLatin1String("yyyy"))) {
0740         const int year = calendar->year(fromDateTime.date());
0741         result = QString::number(qAbs(year)).rightJustified(4, QLatin1Char('0'));
0742         if (year > 0) {
0743             removed = 4;
0744         } else {
0745             result.prepend(QLatin1Char('-'));
0746             removed = 5;
0747         }
0748     } else if (toFormat.startsWith(QLatin1String("yy"))) {
0749         const int year = calendar->year(fromDateTime.date());
0750         result = QString::number(year).right(2).rightJustified(2, QLatin1Char('0'));
0751         if (year > 0) {
0752             removed = 2;
0753         } else {
0754             if (result.startsWith('0')) {
0755                 result = result.right(1);
0756             }
0757             result.prepend(QLatin1Char('-'));
0758             removed = 3;
0759         }
0760     }
0761 
0762     if (removed == 0 || removed >= toFormat.size()) {
0763         return result;
0764     }
0765 
0766     return result + getUnicodeString(fromDateTime, toFormat.mid(removed), timeOptions, calendar, locale, digitSet);
0767 }
0768 
0769 // Reimplement if special integer to string handling required, e.g. Hebrew.
0770 // Utility to convert an integer into the correct display string form
0771 QString KDateTimeFormatter::stringFromInteger(int number, int padWidth, QChar padChar, QChar signChar,
0772         KLocale::DigitSet digitSet, const KLocale *locale) const
0773 {
0774     if (padChar == QChar() && signChar == QChar()) {
0775 //qDebug() << "  stringFromInteger(" << number << padWidth << "null" << "null" << ")";
0776     } else if (padChar == QChar()) {
0777 //qDebug() << "  stringFromInteger(" << number << padWidth << "null" << signChar << ")";
0778     } else if (signChar == QChar()) {
0779 //qDebug() << "  stringFromInteger(" << number << padWidth << padChar << "null" << ")";
0780     } else if (signChar == QChar()) {
0781 //qDebug() << "  stringFromInteger(" << number << padWidth << padChar << signChar << ")";
0782     }
0783     QString result;
0784     if (padChar == QChar() || padWidth == 0) {   // If null pad char or 0 width don't bother padding
0785 //qDebug() << "    no pad";
0786         if (signChar == QChar()) {
0787             result = locale->convertDigits(QString::number(number), digitSet);
0788         } else {
0789             result = locale->convertDigits(QString::number(number).prepend(signChar), digitSet);
0790         }
0791     } else if (signChar != QChar()) {    // If sign required
0792         if (padChar == QLatin1Char('0')) {   // If zero-padded, zero considered part of the number, so pad the number then prepend the sign
0793 //qDebug() << "    zero pad with sign";
0794             result = locale->convertDigits(QString::number(number).rightJustified(padWidth, padChar).prepend(signChar), digitSet);
0795         } else { // If space-padded space not considered part of the number, so prepend the sign and then pad the number
0796 //qDebug() << "    space pad with sign";
0797             result = locale->convertDigits(QString::number(number).prepend(signChar).rightJustified(padWidth, padChar), digitSet);
0798         }
0799     } else {  // No sign required so just pad
0800 //qDebug() << "    pad no sign";
0801         result = locale->convertDigits(QString::number(number).rightJustified(padWidth, padChar), digitSet);
0802     }
0803 //qDebug() << "    result = " << result;
0804     return result;
0805 }