Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP - remove scanf parsing in GPX date parser #1109

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 35 additions & 22 deletions gpx.cc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
Access GPX data files.

Copyright (C) 2002-2015 Robert Lipe, gpsbabel.org
Copyright (C) 2002-2023 Robert Lipe, gpsbabel.org

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand All @@ -23,7 +23,7 @@

#include <cmath> // for lround
#include <cstdio> // for sscanf
#include <cstring> // for strchr, strncpy
#include <cstring> // for strchr

#include <QByteArray> // for QByteArray
#include <QDate> // for QDate
Expand All @@ -32,6 +32,8 @@
#include <QIODevice> // for QIODevice, operator|, QIODevice::ReadOnly, QIODevice::Text, QIODevice::WriteOnly
#include <QLatin1Char> // for QLatin1Char
#include <QLatin1String> // for QLatin1String
#include <QRegularExpression> // for QRegularExpression
#include <QRegularExpressionMatch> // for QRegularExpressionMatch
#include <QString> // for QString, QStringLiteral, operator+, operator==
#include <QStringList> // for QStringList
#include <QStringView> // for QStringView
Expand Down Expand Up @@ -404,6 +406,7 @@ xml_parse_time(const QString& dateTimeString)
/* zulu time; offsets stay at defaults */
*offsetstr = '\0';
} else {
#if 0
offsetstr = strchr(timestr, '+');
if (offsetstr) {
/* positive offset; parse it */
Expand All @@ -421,34 +424,44 @@ xml_parse_time(const QString& dateTimeString)
}
}
}
}

double fsec = 0;
char* pointstr = strchr(timestr, '.');
if (pointstr) {
sscanf(pointstr, "%le", &fsec);
#if 0
/* Round to avoid FP jitter */
if (microsecs) {
*microsecs = .5 + (fsec * 1000000.0) ;
#else
// static const QRegularExpression re(R"([+-])(\d+):(\d+)");
static const QRegularExpression re("([+-])\(\\d+):(\\d+)");
QRegularExpressionMatch match = re.match(dateTimeString);
assert(re.isValid());
if (match.hasMatch()) {
off_sign = match.captured(1) == '-' ? -1 : 1;
off_hr = match.captured(2).toInt();
off_min = match.captured(3).toInt();
}
#endif
*pointstr = '\0';
}

int year = 0, mon = 0, mday = 0, hour = 0, min = 0, sec = 0;
if (off_hr) qDebug() << dateTimeString << off_hr << off_min;
gpsbabel::DateTime dt;
int res = sscanf(timestr, "%d-%d-%dT%d:%d:%d", &year, &mon, &mday, &hour,
#if 0
int year = 0, mon = 0, mday = 0, hour = 0, min = 0;
double sec;
int res = sscanf(timestr, "%d-%d-%dT%d:%d:%lf", &year, &mon, &mday, &hour,
&min, &sec);
if (res > 0) {
#else
static const QRegularExpression
re("(\\d+)-(\\d+)-(\\d+)T(\\d+):(\\d+):([0-9.]+)");
QRegularExpressionMatch match = re.match(dateTimeString);
assert(re.isValid());
if (match.hasMatch()) {
int year = match.captured(1).toInt();
int mon = match.captured(2).toInt();
int mday = match.captured(3).toInt();
int hour = match.captured(4).toInt();
int min = match.captured(5).toInt();
double sec = match.captured(6).toDouble();
#endif
QDate date(year, mon, mday);
QTime time(hour, min, sec);
dt = QDateTime(date, time, Qt::UTC);
// The ctor for time accepts int secs, so we have to break out the msecs.
QTime time(hour, min, sec, ((sec - (int)sec + .0005) * 1000));

// Fractional part of time.
if (fsec) {
dt = dt.addMSecs(lround(fsec * 1000));
}
dt = QDateTime(date, time, Qt::UTC);

// Any offsets that were stuck at the end.
dt = dt.addSecs(-off_sign * off_hr * 3600 - off_sign * off_min * 60);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the replacement of sscanf with QRegularExpression, and elimination of cstdio, cstring.

I had WIP, including a bunch of issues that appear with Qt 6.5.0, with an idea regarding offsets. Reviewing your ideas and incorporating mine got messy, a modified version to compare is attached. To use this also change the declaration in defs.h adding a default to match current code.
gpsbabel::DateTime xml_parse_time(const QString& dateTimeString, bool local_is_utc = true);

I modified the regular expressions based on https://www.w3.org/TR/xmlschema-2/#dateTime. I didn't fret over negative years, or years beyond 9999.

I do worry a bit about the offset regular expression matching something it shouldn't. I haven't thought of a way it could mismatch something besides a timezone from https://www.w3.org/TR/xmlschema-2/#isoformats

qRound and qRound64 let you round a double to a int or qint64.

It also got messy with the conditional code and keeping them both running, I did the lazy thing and chopped it.

I haven't tried the enhanced kml tests to see if they work with these changes.

I attached the modified gpx.cc, or a patch for both gpx.cc and defs.h:
gpx.cc.gz
xsddatetime.patch

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I knew we were about to collide. That's why I RFC'ed this to you before hand. The #if 0 mess was just to mark my before and after. It was on the chopping block for after your vague agreement that this was a sane thing to do anyway.

The regexes will definitely match theoretical things they shouldn't. But those things shouldn't be valid ISO/GPX/KML times. We could have fooled scanf, too.

I'll take your version of this, fix the rounding issues pushing us over a second and then calling Time.addSec() with the rest, and run with it.

Thanx for talking it through.

Expand Down