QLocale - Improve date formatter

Modify the QLocale date formatter to be consistent with the QDateTime
date formatter and able to replace the QDateTime formatter in a
subsequent change.

Fix the treatment of negative years.

The internal QLocale::timeZone() has been replaced by the
QDateTime::timeZoneAbbreviation() to ensure the correct tz for the
date/time is used rather than always the current system default.

Change-Id: I2ef26700856e2e69b979069226aa504ecbb50071
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
Reviewed-by: Mitch Curtis <mitch.curtis@digia.com>
This commit is contained in:
John Layt 2013-07-18 10:58:21 +02:00 committed by The Qt Project
parent ad83be2cd3
commit dd488bb7a4
3 changed files with 163 additions and 77 deletions

View File

@ -1574,7 +1574,7 @@ QString QLocale::toString(qulonglong i) const
QString QLocale::toString(const QDate &date, const QString &format) const
{
return d->dateTimeToString(format, &date, 0, this);
return d->dateTimeToString(format, QDateTime(), date, QTime(), this);
}
/*!
@ -1618,33 +1618,6 @@ static bool timeFormatContainsAP(const QString &format)
return false;
}
static QString timeZone()
{
#if defined(Q_OS_WINCE)
TIME_ZONE_INFORMATION info;
DWORD res = GetTimeZoneInformation(&info);
if (res == TIME_ZONE_ID_UNKNOWN)
return QString();
return QString::fromWCharArray(info.StandardName);
#elif defined(Q_OS_WIN)
_tzset();
# if defined(_MSC_VER) && _MSC_VER >= 1400
size_t returnSize = 0;
char timeZoneName[512];
if (_get_tzname(&returnSize, timeZoneName, 512, 1))
return QString();
return QString::fromLocal8Bit(timeZoneName);
# else
return QString::fromLocal8Bit(_tzname[1]);
# endif
#elif defined(Q_OS_VXWORKS)
return QString();
#else
tzset();
return QString::fromLocal8Bit(tzname[1]);
#endif
}
/*!
Returns a localized string representation of the given \a time according
to the specified \a format.
@ -1652,7 +1625,7 @@ static QString timeZone()
*/
QString QLocale::toString(const QTime &time, const QString &format) const
{
return d->dateTimeToString(format, 0, &time, this);
return d->dateTimeToString(format, QDateTime(), QDate(), time, this);
}
/*!
@ -1665,9 +1638,7 @@ QString QLocale::toString(const QTime &time, const QString &format) const
QString QLocale::toString(const QDateTime &dateTime, const QString &format) const
{
const QDate dt = dateTime.date();
const QTime tm = dateTime.time();
return d->dateTimeToString(format, &dt, &tm, this);
return d->dateTimeToString(format, dateTime, QDate(), QTime(), this);
}
/*!
@ -2553,28 +2524,27 @@ QString QLocale::pmText() const
}
QString QLocalePrivate::dateTimeToString(const QString &format, const QDate *date, const QTime *time,
QString QLocalePrivate::dateTimeToString(const QString &format, const QDateTime &datetime,
const QDate &dateOnly, const QTime &timeOnly,
const QLocale *q) const
{
Q_ASSERT(date || time);
if ((date && !date->isValid()) || (time && !time->isValid()))
QDate date;
QTime time;
bool formatDate = false;
bool formatTime = false;
if (datetime.isValid()) {
date = datetime.date();
time = datetime.time();
formatDate = true;
formatTime = true;
} else if (dateOnly.isValid()) {
date = dateOnly;
formatDate = true;
} else if (timeOnly.isValid()) {
time = timeOnly;
formatTime = true;
} else {
return QString();
const bool format_am_pm = time && timeFormatContainsAP(format);
enum { AM, PM } am_pm = AM;
int hour12 = time ? time->hour() : -1;
if (time) {
if (hour12 == 0) {
am_pm = AM;
hour12 = 12;
} else if (hour12 < 12) {
am_pm = AM;
} else if (hour12 == 12) {
am_pm = PM;
} else {
am_pm = PM;
hour12 -= 12;
}
}
QString result;
@ -2589,7 +2559,7 @@ QString QLocalePrivate::dateTimeToString(const QString &format, const QDate *dat
const QChar c = format.at(i);
int repeat = qt_repeatCount(format, i);
bool used = false;
if (date) {
if (formatDate) {
switch (c.unicode()) {
case 'y':
used = true;
@ -2599,11 +2569,14 @@ QString QLocalePrivate::dateTimeToString(const QString &format, const QDate *dat
repeat = 2;
switch (repeat) {
case 4:
result.append(longLongToString(date->year(), -1, 10, 4, QLocalePrivate::ZeroPadded));
case 4: {
const int yr = date.year();
const int len = (yr < 0) ? 5 : 4;
result.append(longLongToString(yr, -1, 10, len, QLocalePrivate::ZeroPadded));
break;
}
case 2:
result.append(longLongToString(date->year() % 100, -1, 10, 2,
result.append(longLongToString(date.year() % 100, -1, 10, 2,
QLocalePrivate::ZeroPadded));
break;
default:
@ -2618,16 +2591,16 @@ QString QLocalePrivate::dateTimeToString(const QString &format, const QDate *dat
repeat = qMin(repeat, 4);
switch (repeat) {
case 1:
result.append(longLongToString(date->month()));
result.append(longLongToString(date.month()));
break;
case 2:
result.append(longLongToString(date->month(), -1, 10, 2, QLocalePrivate::ZeroPadded));
result.append(longLongToString(date.month(), -1, 10, 2, QLocalePrivate::ZeroPadded));
break;
case 3:
result.append(q->monthName(date->month(), QLocale::ShortFormat));
result.append(q->monthName(date.month(), QLocale::ShortFormat));
break;
case 4:
result.append(q->monthName(date->month(), QLocale::LongFormat));
result.append(q->monthName(date.month(), QLocale::LongFormat));
break;
}
break;
@ -2637,16 +2610,16 @@ QString QLocalePrivate::dateTimeToString(const QString &format, const QDate *dat
repeat = qMin(repeat, 4);
switch (repeat) {
case 1:
result.append(longLongToString(date->day()));
result.append(longLongToString(date.day()));
break;
case 2:
result.append(longLongToString(date->day(), -1, 10, 2, QLocalePrivate::ZeroPadded));
result.append(longLongToString(date.day(), -1, 10, 2, QLocalePrivate::ZeroPadded));
break;
case 3:
result.append(q->dayName(date->dayOfWeek(), QLocale::ShortFormat));
result.append(q->dayName(date.dayOfWeek(), QLocale::ShortFormat));
break;
case 4:
result.append(q->dayName(date->dayOfWeek(), QLocale::LongFormat));
result.append(q->dayName(date.dayOfWeek(), QLocale::LongFormat));
break;
}
break;
@ -2655,12 +2628,18 @@ QString QLocalePrivate::dateTimeToString(const QString &format, const QDate *dat
break;
}
}
if (!used && time) {
if (!used && formatTime) {
switch (c.unicode()) {
case 'h': {
used = true;
repeat = qMin(repeat, 2);
const int hour = format_am_pm ? hour12 : time->hour();
int hour = time.hour();
if (timeFormatContainsAP(format)) {
if (hour > 12)
hour -= 12;
else if (hour == 0)
hour = 12;
}
switch (repeat) {
case 1:
@ -2677,10 +2656,10 @@ QString QLocalePrivate::dateTimeToString(const QString &format, const QDate *dat
repeat = qMin(repeat, 2);
switch (repeat) {
case 1:
result.append(longLongToString(time->hour()));
result.append(longLongToString(time.hour()));
break;
case 2:
result.append(longLongToString(time->hour(), -1, 10, 2, QLocalePrivate::ZeroPadded));
result.append(longLongToString(time.hour(), -1, 10, 2, QLocalePrivate::ZeroPadded));
break;
}
break;
@ -2690,10 +2669,10 @@ QString QLocalePrivate::dateTimeToString(const QString &format, const QDate *dat
repeat = qMin(repeat, 2);
switch (repeat) {
case 1:
result.append(longLongToString(time->minute()));
result.append(longLongToString(time.minute()));
break;
case 2:
result.append(longLongToString(time->minute(), -1, 10, 2, QLocalePrivate::ZeroPadded));
result.append(longLongToString(time.minute(), -1, 10, 2, QLocalePrivate::ZeroPadded));
break;
}
break;
@ -2703,10 +2682,10 @@ QString QLocalePrivate::dateTimeToString(const QString &format, const QDate *dat
repeat = qMin(repeat, 2);
switch (repeat) {
case 1:
result.append(longLongToString(time->second()));
result.append(longLongToString(time.second()));
break;
case 2:
result.append(longLongToString(time->second(), -1, 10, 2, QLocalePrivate::ZeroPadded));
result.append(longLongToString(time.second(), -1, 10, 2, QLocalePrivate::ZeroPadded));
break;
}
break;
@ -2718,7 +2697,7 @@ QString QLocalePrivate::dateTimeToString(const QString &format, const QDate *dat
} else {
repeat = 1;
}
result.append(am_pm == AM ? q->amText().toLower() : q->pmText().toLower());
result.append(time.hour() < 12 ? q->amText().toLower() : q->pmText().toLower());
break;
case 'A':
@ -2728,7 +2707,7 @@ QString QLocalePrivate::dateTimeToString(const QString &format, const QDate *dat
} else {
repeat = 1;
}
result.append(am_pm == AM ? q->amText().toUpper() : q->pmText().toUpper());
result.append(time.hour() < 12 ? q->amText().toUpper() : q->pmText().toUpper());
break;
case 'z':
@ -2740,10 +2719,10 @@ QString QLocalePrivate::dateTimeToString(const QString &format, const QDate *dat
}
switch (repeat) {
case 1:
result.append(longLongToString(time->msec()));
result.append(longLongToString(time.msec()));
break;
case 3:
result.append(longLongToString(time->msec(), -1, 10, 3, QLocalePrivate::ZeroPadded));
result.append(longLongToString(time.msec(), -1, 10, 3, QLocalePrivate::ZeroPadded));
break;
}
break;
@ -2751,8 +2730,14 @@ QString QLocalePrivate::dateTimeToString(const QString &format, const QDate *dat
case 't':
used = true;
repeat = 1;
result.append(timeZone());
// If we have a QDateTime use the time spec otherwise use the current system tzname
if (formatDate) {
result.append(datetime.timeZoneAbbreviation());
} else {
result.append(QDateTime::currentDateTime().timeZoneAbbreviation());
}
break;
default:
break;
}

View File

@ -330,7 +330,8 @@ public:
enum NumberMode { IntegerMode, DoubleStandardMode, DoubleScientificMode };
bool validateChars(const QString &str, NumberMode numMode, QByteArray *buff, int decDigits = -1) const;
QString dateTimeToString(const QString &format, const QDate *date, const QTime *time,
QString dateTimeToString(const QString &format, const QDateTime &datetime,
const QDate &dateOnly, const QTime &timeOnly,
const QLocale *q) const;
const QLocaleData *m_data;

View File

@ -163,6 +163,7 @@ private slots:
void formatTime_data();
void formatDateTime();
void formatDateTime_data();
void formatTimeZone();
void toDateTime_data();
void toDateTime();
void negativeNumbers();
@ -199,6 +200,7 @@ private slots:
private:
QString m_decimal, m_thousand, m_sdate, m_ldate, m_time;
QString m_sysapp;
bool europeanTimeZone;
#ifdef Q_OS_BLACKBERRY
int m_languageFd;
@ -208,6 +210,11 @@ private:
tst_QLocale::tst_QLocale()
{
qRegisterMetaType<QLocale::FormatType>("QLocale::FormatType");
// Test if in Central European Time zone
uint x1 = QDateTime(QDate(1990, 1, 1), QTime()).toTime_t();
uint x2 = QDateTime(QDate(1990, 6, 1), QTime()).toTime_t();
europeanTimeZone = (x1 == 631148400 && x2 == 644191200);
}
void tst_QLocale::initTestCase()
@ -1198,6 +1205,59 @@ void tst_QLocale::formatDateTime_data()
QTest::newRow("28no_NO") << "no_NO" << QDateTime()
<< "'\"yymm\"'" << "";
QDateTime testLongHour(QDate(1999, 12, 31), QTime(23, 59, 59, 999));
QDateTime testShortHour(QDate(1999, 12, 31), QTime(3, 59, 59, 999));
QDateTime testZeroHour(QDate(1999, 12, 31), QTime(0, 59, 59, 999));
QTest::newRow("datetime0") << "en_US" << QDateTime()
<< QString("dd-MM-yyyy hh:mm:ss") << QString();
QTest::newRow("datetime1") << "en_US" << testLongHour
<< QString("dd-'mmddyy'MM-yyyy hh:mm:ss.zzz")
<< QString("31-mmddyy12-1999 23:59:59.999");
QTest::newRow("datetime2") << "en_US" << testLongHour
<< QString("dd-'apAP'MM-yyyy hh:mm:ss.zzz")
<< QString("31-apAP12-1999 23:59:59.999");
QTest::newRow("datetime3") << "en_US" << testLongHour
<< QString("Apdd-MM-yyyy hh:mm:ss.zzz")
<< QString("PMp31-12-1999 11:59:59.999");
QTest::newRow("datetime4") << "en_US" << testLongHour
<< QString("'ap'apdd-MM-yyyy 'AP'hh:mm:ss.zzz")
<< QString("appm31-12-1999 AP11:59:59.999");
QTest::newRow("datetime5") << "en_US" << testLongHour
<< QString("'''") << QString("'");
QTest::newRow("datetime6") << "en_US" << testLongHour
<< QString("'ap") << QString("ap");
QTest::newRow("datetime7") << "en_US" << testLongHour
<< QString("' ' 'hh' hh") << QString(" hh 23");
QTest::newRow("datetime8") << "en_US" << testLongHour
<< QString("d'foobar'") << QString("31foobar");
QTest::newRow("datetime9") << "en_US" << testShortHour
<< QString("hhhhh") << QString("03033");
QTest::newRow("datetime11") << "en_US" << testLongHour
<< QString("HHHhhhAaAPap") << QString("23231111PMpmPMpm");
QTest::newRow("datetime12") << "en_US" << testShortHour
<< QString("HHHhhhAaAPap") << QString("033033AMamAMam");
QTest::newRow("datetime13") << "en_US" << QDateTime(QDate(1974, 12, 1), QTime(14, 14, 20))
<< QString("hh''mm''ss dd''MM''yyyy")
<< QString("14'14'20 01'12'1974");
QTest::newRow("AM no p") << "en_US" << testZeroHour
<< QString("hhAX") << QString("12AMX");
QTest::newRow("AM no p, x 2") << "en_US" << testShortHour
<< QString("hhhhhaA") << QString("03033amAM");
QTest::newRow("am 0 hour") << "en_US" << testZeroHour
<< QString("hAP") << QString("12AM");
QTest::newRow("AM zero hour") << "en_US" << testZeroHour
<< QString("hhAP") << QString("12AM");
QTest::newRow("dddd") << "en_US" << testZeroHour
<< QString("dddd") << QString("Friday");
QTest::newRow("ddd") << "en_US" << testZeroHour
<< QString("ddd") << QString("Fri");
QTest::newRow("MMMM") << "en_US" << testZeroHour
<< QString("MMMM") << QString("December");
QTest::newRow("MMM") << "en_US" << testZeroHour
<< QString("MMM") << QString("Dec");
QTest::newRow("empty") << "en_US" << testZeroHour
<< QString("") << QString("");
}
void tst_QLocale::formatDateTime()
@ -1211,6 +1271,46 @@ void tst_QLocale::formatDateTime()
QCOMPARE(l.toString(dateTime, format), result);
}
void tst_QLocale::formatTimeZone()
{
QLocale enUS("en_US");
QDateTime dt1(QDate(2013, 1, 1), QTime(1, 0, 0), Qt::OffsetFromUTC, 60 * 60);
QCOMPARE(enUS.toString(dt1, "t"), QString("UTC+01:00"));
QDateTime dt2(QDate(2013, 1, 1), QTime(1, 0, 0), Qt::OffsetFromUTC, -60 * 60);
QCOMPARE(enUS.toString(dt2, "t"), QString("UTC-01:00"));
QDateTime dt3(QDate(2013, 1, 1), QTime(0, 0, 0), Qt::UTC);
QCOMPARE(enUS.toString(dt3, "t"), QString("UTC"));
// LocalTime should vary
if (europeanTimeZone) {
// Time definitely in Standard Time
QDateTime dt4(QDate(2013, 1, 1), QTime(0, 0, 0), Qt::LocalTime);
#ifdef Q_OS_WIN
QEXPECT_FAIL("", "Windows only returns long name (QTBUG-32759)", Continue);
#endif // Q_OS_WIN
QCOMPARE(enUS.toString(dt4, "t"), QString("CET"));
// Time definitely in Daylight Time
QDateTime dt5(QDate(2013, 6, 1), QTime(0, 0, 0), Qt::LocalTime);
#ifdef Q_OS_WIN
QEXPECT_FAIL("", "Windows only returns long name (QTBUG-32759)", Continue);
#endif // Q_OS_WIN
QCOMPARE(enUS.toString(dt5, "t"), QString("CEST"));
} else {
QSKIP("You must test using Central European (CET/CEST) time zone, e.g. TZ=Europe/Oslo");
}
// Current datetime should return current abbreviation
QCOMPARE(enUS.toString(QDateTime::currentDateTime(), "t"),
QDateTime::currentDateTime().timeZoneAbbreviation());
// Time on its own will always be current local time zone
QCOMPARE(enUS.toString(QTime(1, 2, 3), "t"), QDateTime::currentDateTime().timeZoneAbbreviation());
}
void tst_QLocale::toDateTime_data()
{
QTest::addColumn<QString>("localeName");