10

Does anyone know of a cleaner way to get the time zone included in the ISO string representation of a QDateTime?

I should be able to just use the following:

qDebug() << QDateTime::currentDateTime().toString(Qt::ISODate);

but this always comes out in UTC format:

2014-02-24T01:29:00Z

Currently, the way I'm working round this is to force the TimeSpec to be Qt::offsetFromUtc by explicitly setting the offset, which I'm getting from the QDateTime originally.

QDateTime now = QDateTime::currentDateTime();
int offset = now.offsetFromUtc();
now.setOffsetFromUtc(offset);
qDebug() << now.toString(Qt::ISODate);

This gives what was originally expected:

2014-02-24T01:29:00+02:00

Does anyone know how to do this in a cleaner way or must this be logged as a bug?

EDIT: I'm using Qt5.2.1

UPDATE:

The following small program shows what I mean:

#include <QtCore/QDateTime>
#include <QtCore/QDebug>

int main(int argc, int argv){
    qDebug() << QDateTime::currentDateTime().toString(Qt::ISODate);
    qDebug() << QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate);

    QDateTime now = QDateTime::currentDateTime();
    int offset = now.offsetFromUtc();
    now.setOffsetFromUtc(offset);
    qDebug() << now.toString(Qt::ISODate);

    return 0;
}

The following output is generated:

"2014-02-24T10:20:49" 
"2014-02-24T08:20:49Z" 
"2014-02-24T10:20:49+02:00"

The last line is the one that is expected. Please note that the second time has been converted to UTC, which is not what is wanted.

László Papp
  • 51,870
  • 39
  • 111
  • 135
RobbieE
  • 4,280
  • 3
  • 22
  • 36
  • 1
    Also, the output is different on my machine, so I cannot even reproduce the exact issue myself:`"2014-02-24T08:51:07" "2014-02-24T08:51:07Z" "2014-02-24T08:51:07Z"` – László Papp Feb 24 '14 at 08:51
  • The question has not changed it's meaning, perhaps your understanding of the question has changed. In short, I asked if there was a cleaner way of getting the desired result, or if I should log a bug. I thought I was very clear on that. – RobbieE Feb 24 '14 at 09:46
  • What version of Qt are you using. I'm using 5.2.1. The code that I supplied there is code that I have actually compiled and run, not merely thought in my head. The results are actual results that my machine has generated, therefore it qualifies as an SSCCE. Thank you Laszlo for your input. I shall mark your answer as correct. Even though the answer doesn't help me, I shall assume that your code is how Qt should work and I shall log a bug with Digia. – RobbieE Feb 24 '14 at 09:50
  • Yep, I am using the same, on Archlinux if that matters. Not sure if it is related to my timezone which is London, so no +/- hours difference. – László Papp Feb 24 '14 at 09:58
  • I'm compiling on Windows 8 but I don't think that would make much difference. Your timezone being London does make a big difference in your results because you ARE in UTC. My timezone is SAST (UTC+2). You can see from the test output that QDateTime::toTimeSpec(Qt::OffsetFromUTC) shifts my local time to UTC time, i.e. makes the time 2 hours earlier. For you, the shift wouldn't happen because you are already in the UTC timezone. – RobbieE Feb 24 '14 at 10:13
  • Change your system timezone to "Harare, Pretoria, or Johannesburg (UTC+2)" and rerun your code and you should get the same results as I do. – RobbieE Feb 24 '14 at 10:15
  • I already tried, but I cannot reproduce the issue personally. – László Papp Feb 24 '14 at 10:16

3 Answers3

6

When I need that I use the following workaround:

QDateTime::currentDateTime().toOffsetFromUtc(QDateTime::currentDateTime().offsetFromUtc()).toString(Qt::ISODate);

I have not tested if @lpappa is working on new versions. The above workaround has been tested on Qt 5.3

RDP
  • 946
  • 11
  • 14
5

This had not been present before 5.2, but it was integrated in there. It seems that you got the syntax incorrect though because it should be like this:

QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate)

as per the corresponding bugreport. Note that toTimeSpec(Qt::OffsetFromUTC) call in the middle.

László Papp
  • 51,870
  • 39
  • 111
  • 135
  • That was the first thing I tried. It turns out that, whenever you set the timespec to `Qt::OffsetFromUTC`, it zeros out the internal offset minutes. Later in the function execution, because the offset is 0, the timespec is changed to `Qt::UTC`, resulting in `2014-02-24T08:23Z` – RobbieE Feb 24 '14 at 06:24
  • 1
    My original question asks if there is a cleaner way to get the ISOdate with the timezone part included or if this is a bug. You indeed give me a cleaner method but this does not give me the correct output, it shifts my timezone to UTC. If you're getting the correct output, I suspect we're not using the same minor version. As I said, I'm using 5.2.1. Perhaps it's a bug that was introduced. I shall accept your answer but I shall also log it as a bug with Digia. – RobbieE Feb 24 '14 at 07:40
  • @RobbieE: you have not provided a [self-contained test case](http://sscce.org) yet. Even Digia would request that, I think. By works, I meant that printing that line out gives correct result as expected in an empty main function, just as you seem to have requested. – László Papp Feb 24 '14 at 07:41
  • My apologies, I thought I was being clear. I will put a fully self-contained example showing what I mean. – RobbieE Feb 24 '14 at 08:24
  • FWIW, on an embedded linux based system, Qt 5.3, this does not fix the issue. I needed to use a custom method similar to @rudolf-cardinal answer – Matthew Eshleman Dec 01 '16 at 18:42
  • Note that QDateTime::toTimeSpec() will be deprecated as of Qt 6.9 so this function should now be avoided. https://doc.qt.io/qt-6/qdatetime-obsolete.html#toTimeSpec – Paul Masri-Stone May 15 '23 at 18:10
4

This seems to work, with millisecond accuracy and preservation of timezone information:

#include <QDebug>
#include <QTimeZone>

QString datetimeToIsoMs(const QDateTime& dt)
{
    // An ISO-8601 format preserving millisecond accuracy and timezone.
    // Equivalent in moment.js: thing.format("YYYY-MM-DDTHH:mm:ss.SSSZ")
    // Example: '2016-06-02T10:04:03.588+01:00'
    // Here we also allow 'Z' for UTC.

    // In Qt, BEWARE:
    //      dt;  // QDateTime(2016-06-02 10:28:06.708 BST Qt::TimeSpec(LocalTime))
    //      dt.toString(Qt::ISODate);  // "2016-06-02T10:28:06" -- DROPS timezone
    QString localtime = dt.toString("yyyy-MM-ddTHH:mm:ss.zzz");
    int offsetFromUtcSec = dt.offsetFromUtc();
    // FOR TESTING: offsetFromUtcSec = -(3600 * 2.5);
    QString tzinfo;
    if (offsetFromUtcSec == 0) {
        tzinfo = "Z";
    } else {
        QString sign = offsetFromUtcSec < 0 ? "-" : "+";
        offsetFromUtcSec = abs(offsetFromUtcSec);
        int hours = offsetFromUtcSec / 3600;
        int minutes = (offsetFromUtcSec % 3600) / 60;
        tzinfo += QString("%1%2:%3").arg(sign)
            .arg(hours, 2, 10, QChar('0'))
            .arg(minutes, 2, 10, QChar('0'));
        // http://stackoverflow.com/questions/2618414/convert-an-int-to-a-qstring-with-zero-padding-leading-zeroes
    }
    return localtime + tzinfo;
}

QString datetimeToIsoMsUtc(const QDateTime& dt)
{
    QDateTime utc_dt = dt.toTimeSpec(Qt::UTC);
    return datetimeToIsoMs(utc_dt);
}

QDateTime isoToDateTime(const QString& iso)
{
    return QDateTime::fromString(iso, Qt::ISODate);
}
Rudolf Cardinal
  • 738
  • 8
  • 11