From afa8c959e8d277069c5524dc9f1647ca2d4e0935 Mon Sep 17 00:00:00 2001 From: Debao Zhang Date: Wed, 23 Oct 2013 15:31:01 +0800 Subject: [PATCH] Improve the QDateTime support --- src/xlsx/xlsxcell.h | 3 +- src/xlsx/xlsxutility.cpp | 26 ++++++++++++ src/xlsx/xlsxutility_p.h | 3 ++ src/xlsx/xlsxworkbook.cpp | 25 +++++++----- src/xlsx/xlsxworksheet.cpp | 17 ++------ tests/auto/utility/tst_utilitytest.cpp | 56 ++++++++++++++++++++++++++ 6 files changed, 106 insertions(+), 24 deletions(-) diff --git a/src/xlsx/xlsxcell.h b/src/xlsx/xlsxcell.h index 1a96f54..d76a687 100644 --- a/src/xlsx/xlsxcell.h +++ b/src/xlsx/xlsxcell.h @@ -44,9 +44,8 @@ public: String, Number, Formula, - ArrayFormula, Boolean, - DateTime + Error }; DataType dataType() const; diff --git a/src/xlsx/xlsxutility.cpp b/src/xlsx/xlsxutility.cpp index 5e30289..16760c9 100755 --- a/src/xlsx/xlsxutility.cpp +++ b/src/xlsx/xlsxutility.cpp @@ -30,6 +30,8 @@ #include #include #include +#include +#include namespace QXlsx { @@ -62,6 +64,30 @@ QColor fromARGBString(const QString &c) return color; } +double datetimeToNumber(const QDateTime &dt, bool is1904) +{ + //Note, for number 0, Excel2007 shown as 1900-1-0, which should be 1899-12-31 + QDateTime epoch(is1904 ? QDate(1904, 1, 1): QDate(1899, 12, 31), QTime(0,0), Qt::UTC); + + double excel_time = epoch.msecsTo(dt) / (1000*60*60*24.0); + if (!is1904 && excel_time > 59) {//31+28 + //Account for Excel erroneously treating 1900 as a leap year. + excel_time += 1; + } + return excel_time; +} + +QDateTime datetimeFromNumber(double num, bool is1904) +{ + if (!is1904 && num > 60) + num = num - 1; + + qint64 msecs = static_cast(num * 1000*60*60*24.0); + QDateTime epoch(is1904 ? QDate(1904, 1, 1): QDate(1899, 12, 31), QTime(0,0), Qt::UTC); + + return epoch.addMSecs(msecs); +} + QPoint xl_cell_to_rowcol(const QString &cell_str) { if (cell_str.isEmpty()) diff --git a/src/xlsx/xlsxutility_p.h b/src/xlsx/xlsxutility_p.h index 7c72800..958797f 100755 --- a/src/xlsx/xlsxutility_p.h +++ b/src/xlsx/xlsxutility_p.h @@ -30,12 +30,15 @@ class QPoint; class QString; class QStringList; class QColor; +class QDateTime; namespace QXlsx { XLSX_AUTOTEST_EXPORT int intPow(int x, int p); XLSX_AUTOTEST_EXPORT QStringList splitPath(const QString &path); XLSX_AUTOTEST_EXPORT QColor fromARGBString(const QString &c); +XLSX_AUTOTEST_EXPORT double datetimeToNumber(const QDateTime &dt, bool is1904=false); +XLSX_AUTOTEST_EXPORT QDateTime datetimeFromNumber(double num, bool is1904=false); XLSX_AUTOTEST_EXPORT QPoint xl_cell_to_rowcol(const QString &cell_str); XLSX_AUTOTEST_EXPORT QString xl_col_to_name(int col_num); diff --git a/src/xlsx/xlsxworkbook.cpp b/src/xlsx/xlsxworkbook.cpp index 316e2f6..2902e8b 100755 --- a/src/xlsx/xlsxworkbook.cpp +++ b/src/xlsx/xlsxworkbook.cpp @@ -51,7 +51,7 @@ WorkbookPrivate::WorkbookPrivate(Workbook *q) : strings_to_numbers_enabled = false; date1904 = false; - defaultDateFormat = QStringLiteral("dd/mm/yyyy hh:mm"); + defaultDateFormat = QStringLiteral("yyyy-mm-ddThh:mm:ss"); activesheet = 0; firstsheet = 0; table_count = 0; @@ -74,12 +74,15 @@ bool Workbook::isDate1904() const return d->date1904; } -/* - Excel for Windows uses a default epoch of 1900 and Excel - for Mac uses an epoch of 1904. However, Excel on either - platform will convert automatically between one system - and the other. QtXlsxWriter stores dates in the 1900 format - by default. +/*! + Excel for Windows uses a default epoch of 1900 and Excel + for Mac uses an epoch of 1904. However, Excel on either + platform will convert automatically between one system + and the other. Qt Xlsx stores dates in the 1900 format + by default. + + \note This function should be called before any date/time + has been written. */ void Workbook::setDate1904(bool date1904) { @@ -310,7 +313,7 @@ QByteArray Workbook::saveToXmlData() QSharedPointer Workbook::loadFromXmlFile(QIODevice *device) { - Workbook *book = new Workbook; + QSharedPointer book(new Workbook); XmlStreamReader reader(device); while(!reader.atEnd()) { @@ -321,10 +324,14 @@ QSharedPointer Workbook::loadFromXmlFile(QIODevice *device) QString sheetName = attributes.value(QLatin1String("name")).toString(); QString rId = attributes.value(QLatin1String("r:id")).toString(); book->d_func()->sheetNameIdPairList.append(QPair(sheetName, rId)); + } else if (reader.name() == QLatin1String("workbookPr")) { + QXmlStreamAttributes attrs = reader.attributes(); + if (attrs.hasAttribute(QLatin1String("date1904"))) + book->d_ptr->date1904 = true; } } } - return QSharedPointer(book); + return book; } QSharedPointer Workbook::loadFromXmlData(const QByteArray &data) diff --git a/src/xlsx/xlsxworksheet.cpp b/src/xlsx/xlsxworksheet.cpp index 8b8f7d1..03e4de5 100755 --- a/src/xlsx/xlsxworksheet.cpp +++ b/src/xlsx/xlsxworksheet.cpp @@ -423,7 +423,10 @@ int Worksheet::writeDateTime(int row, int column, const QDateTime &dt, Format *f format = d->workbook->createFormat(); format->setNumberFormat(d->workbook->defaultDateFormat()); } - d->cellTable[row][column] = QSharedPointer(new Cell(dt, Cell::DateTime, format)); + + double value = datetimeToNumber(dt, d->workbook->isDate1904()); + + d->cellTable[row][column] = QSharedPointer(new Cell(value, Cell::Number, format)); d->workbook->styles()->addFormat(format); return 0; @@ -729,23 +732,11 @@ void WorksheetPrivate::writeCellData(XmlStreamWriter &writer, int row, int col, writer.writeAttribute(QStringLiteral("t"), QStringLiteral("str")); writer.writeTextElement(QStringLiteral("f"), cell->formula()); writer.writeTextElement(QStringLiteral("v"), cell->value().toString()); - } else if (cell->dataType() == Cell::ArrayFormula) { - } else if (cell->dataType() == Cell::Boolean) { writer.writeAttribute(QStringLiteral("t"), QStringLiteral("b")); writer.writeTextElement(QStringLiteral("v"), cell->value().toBool() ? QStringLiteral("1") : QStringLiteral("0")); } else if (cell->dataType() == Cell::Blank) { //Ok, empty here. - } else if (cell->dataType() == Cell::DateTime) { - QDateTime epoch(QDate(1899, 12, 31)); - if (workbook->isDate1904()) - epoch = QDateTime(QDate(1904, 1, 1)); - qint64 delta = epoch.msecsTo(cell->value().toDateTime()); - double excel_time = delta / (1000*60*60*24); - //Account for Excel erroneously treating 1900 as a leap year. - if (!workbook->isDate1904() && excel_time > 59) - excel_time += 1; - writer.writeTextElement(QStringLiteral("v"), QString::number(excel_time, 'g', 15)); } writer.writeEndElement(); //c } diff --git a/tests/auto/utility/tst_utilitytest.cpp b/tests/auto/utility/tst_utilitytest.cpp index c5d25b6..e5ea725 100644 --- a/tests/auto/utility/tst_utilitytest.cpp +++ b/tests/auto/utility/tst_utilitytest.cpp @@ -25,6 +25,7 @@ #include "private/xlsxutility_p.h" #include #include +#include class UtilityTest : public QObject { @@ -39,6 +40,12 @@ private Q_SLOTS: void test_rowcol_to_cell(); void test_rowcol_to_cell_data(); + + void test_datetimeToNumber_data(); + void test_datetimeToNumber(); + + void test_datetimeFromNumber_data(); + void test_datetimeFromNumber(); }; UtilityTest::UtilityTest() @@ -101,6 +108,55 @@ void UtilityTest::test_rowcol_to_cell_data() QTest::newRow("...") << 1048576 << 16384 << false << false << "XFE1048577"; } +void UtilityTest::test_datetimeToNumber_data() +{ + QTest::addColumn("dt"); + QTest::addColumn("is1904"); + QTest::addColumn("num"); + + //Note, for number 0, Excel2007 shown as 1900-1-0, which should be 1899-12-31 + QTest::newRow("0") << QDateTime(QDate(1899, 12, 31), QTime(0,0), Qt::UTC) << false << 0.0; + QTest::newRow("1.25") << QDateTime(QDate(1900, 1, 1), QTime(6, 0), Qt::UTC) << false << 1.25; + QTest::newRow("59") << QDateTime(QDate(1900, 2, 28), QTime(0, 0), Qt::UTC) << false << 59.0; + QTest::newRow("61") << QDateTime(QDate(1900, 3, 1), QTime(0, 0), Qt::UTC) << false << 61.0; + + QTest::newRow("1904: 0") << QDateTime(QDate(1904, 1, 1), QTime(0,0), Qt::UTC) << true << 0.0; + QTest::newRow("1904: 1.25") << QDateTime(QDate(1904, 1, 2), QTime(6, 0), Qt::UTC) << true << 1.25; +} + +void UtilityTest::test_datetimeToNumber() +{ + QFETCH(QDateTime, dt); + QFETCH(bool, is1904); + QFETCH(double, num); + + QCOMPARE(QXlsx::datetimeToNumber(dt, is1904), num); +} + +void UtilityTest::test_datetimeFromNumber_data() +{ + QTest::addColumn("dt"); + QTest::addColumn("is1904"); + QTest::addColumn("num"); + + QTest::newRow("0") << QDateTime(QDate(1899, 12, 31), QTime(0,0), Qt::UTC) << false << 0.0; + QTest::newRow("1.25") << QDateTime(QDate(1900, 1, 1), QTime(6, 0), Qt::UTC) << false << 1.25; + QTest::newRow("59") << QDateTime(QDate(1900, 2, 28), QTime(0,0), Qt::UTC) << false << 59.0; + QTest::newRow("61") << QDateTime(QDate(1900, 3, 1), QTime(0,0), Qt::UTC) << false << 61.0; + + QTest::newRow("1904: 0") << QDateTime(QDate(1904, 1, 1), QTime(0,0), Qt::UTC) << true << 0.0; + QTest::newRow("1904: 1.25") << QDateTime(QDate(1904, 1, 2), QTime(6, 0), Qt::UTC) << true << 1.25; +} + +void UtilityTest::test_datetimeFromNumber() +{ + QFETCH(QDateTime, dt); + QFETCH(bool, is1904); + QFETCH(double, num); + + QCOMPARE(QXlsx::datetimeFromNumber(num, is1904), dt); +} + QTEST_APPLESS_MAIN(UtilityTest) #include "tst_utilitytest.moc"