String to Date - Datumsparsing für Qt

2011-06-12 12:13


Eines der Projekte an denen ich gerade arbeite ist ja ein RSS Reader. Wie ich schon vorher in einem anderen Blogbeitrag schrieb, definiert RSS das Datumsformat als RFC822. Dieses wird aber nicht von Qt unterstützt.
Deshalb hatte ich einen RFC822 Parser dieses Frühjahr in boost::spirit implementiert.

Dieser funktioniert auch wunderbar, sogar mit Qt. Aber leider nicht unter Symbian, ich konnte den Code mit der Symbian Toolchain einfach nicht zum compilieren bekommen. Es musste also eine andere, reine Qt Lösung her.

Bei der Beschäftigung mit verschiedenen RSS Feeds diverser Seiten fällt beim Datum auf, das auch das ISO Date Format von einigen Seiten verwendet wird. Man kann sich also nicht darauf verlassen, das ein RSS Feed immer auch das Datum als RFC822 ausliefert. QDateTime bietet mit fromString schon eine entsprechende Methode, um aus einem String ein Datum zu machen. Leider muss man fromString bei jedem Aufruf explizit angeben, welches Format denn der String enthält, bzw. für die vordefinierten Formate gibt es einen Enum Wert:

date = QDateTime::fromString(strdate,Qt::ISODate);

eine generelle Methode, welche versucht aus einem String ein QDateTime zu machen, gibt es leider nicht. Aber genau dies benötige ich für meinen Feedreader. Ich will eine Funktion/Methode, der ich einen String und ein QDateTime gebe, und dann entsprechend darin die Umwandlung vornehme. Enthält der String kein Datum, bzw. kann keines erfolgreich extrahiert werden, gibt die Methode false zurück, sonst true:

bool parse_date(QString& strdate, QDateTime& date)
{
    readRFC822Qt(strdate,date);
    if(date.isValid())
        return true;
/*#ifndef Q_OS_SYMBIAN // der auskommentierete Spirit parser
    DateTimeType dt;
    std::string sdate = strdate.toStdString();
    parse_date(sdate.begin(),sdate.end(),dt);
    date.setDate(QDate(dt.tm_year+1900,dt.tm_mon,dt.tm_mday));
    date.setTime(QTime(dt.tm_hour,dt.tm_min,dt.tm_sec));
#endif*/
    if(date.isValid())
        return true;
    date = QDateTime::fromString(strdate,Qt::ISODate);
    if(date.isValid())
        return true;
    date = QDateTime::fromString(strdate,Qt::TextDate);
    if(date.isValid())
        return true;
    date = QDateTime::fromString(strdate,Qt::SystemLocaleDate);
    if(date.isValid())
        return true;
    date = QDateTime::fromString(strdate,Qt::SystemLocaleLongDate);
    if(date.isValid())
        return true;
    date = QDateTime::fromString(strdate,Qt::DefaultLocaleLongDate);
    if(date.isValid())
        return true;
    date = QDateTime::fromString(strdate,Qt::DefaultLocaleShortDate);
    return date.isValid();
}

Die Methode ruft alle gängigen Datumsformate, welche Qt als Enumwert definiert, ab und testet nach jeder Umwandlung, ob date.isValid() nun true zurückgibt. readRFC822 ist die entsprechende Funktion um ein RFC822 Datum aus dem String zu lesen. Diese Methode versucht die einzelnen RFC822 Elemente auszulesen, und entsprechend umzuwandeln:

bool readRFC822Qt(QString& strdate, QDateTime& date)
{
    QString token,str = strdate;
    int i = str.indexOf(",")+1;
    if(i != 4)
        return false;
    //Mon, 30 May 2011 15:03:45 +0200
    str = str.right(str.length()-i).trimmed();
    i = str.indexOf(' ');
    if(i == -1)
        return false;
    token = str.left(i);
    bool ok;
    int day = token.toInt(&ok);
    if(!ok)
        return false;
    str = str.right(str.length()-i).trimmed();
    static std::map<QString,int> month;
    i = 1;
    if(month.empty())
    {
        month.insert(std::make_pair("jan",i));++i;
        month.insert(std::make_pair("feb",i));++i;
        month.insert(std::make_pair("mar",i));++i;
        month.insert(std::make_pair("apr",i));++i;
        month.insert(std::make_pair("may",i));++i;
        month.insert(std::make_pair("jun",i));++i;
        month.insert(std::make_pair("jul",i));++i;
        month.insert(std::make_pair("aug",i));++i;
        month.insert(std::make_pair("sep",i));++i;
        month.insert(std::make_pair("nov",i));++i;
        month.insert(std::make_pair("dec",i));
    }
    i = str.indexOf(' ');
    if(i == -1)
        return false;
    token = str.left(i).toLower();
    int mon;
    if(month.find(token) != month.end())
        mon = month[token];
    else
        return false;
    str = str.right(str.length()-i).trimmed();
    i = str.indexOf(' ');
    if(i == -1)
        return false;
    token = str.left(i);
    int year = token.toInt(&ok);
    if(!ok)
        return false;
    if(year < 100)
        year += 2000;
    str = str.right(str.length()-i).trimmed();
    i = str.indexOf(' ');
    token = str.left(i); // zeitstempel
    i = token.indexOf(':');
    if(i == -1)
        return false;
    QString temp = token.left(i);
    int hour = temp.toInt(&ok);
    if(!ok)
        return false;
    token = token.right(token.length()-i-1);
    i = token.indexOf(':');
    if(i == -1)
        return false;
    temp = token.left(i);
    int min = temp.toInt(&ok);
    if(!ok)
        return false;
    token = token.right(token.length()-i-1);
    int sec = token.toInt(&ok);
    if(!ok)
        return false;
    date.setDate(QDate(year,mon,day));
    date.setTime(QTime(hour,min,sec));
    return date.isValid();
}

Das Ganze ist in gewisser Weise ein Hack, aber funktioniert. Aktuell denke ich darüber nach, evtl. Teile des Codes mit QTextStream zu ersetzen, da dies den Code wohl verschlanken dürfte. Aktuell liest der Parser auch nur Schön-Wetter RFC822, Kommentare z.B. würden ihn noch aus der Bahn werfen. Diese könnte man aber mittels einer RegEx entfernen aus dem String. ("([^)]+").


Zurück