From 0ac352c719811d8dc6b9082319f1d4d472f02087 Mon Sep 17 00:00:00 2001 From: tsteven4 <13596209+tsteven4@users.noreply.github.com> Date: Thu, 2 Nov 2023 14:14:14 -0600 Subject: [PATCH 1/3] refactor garmin_txt date/time format handling --- defs.h | 4 +- garmin_txt.cc | 162 +++++++++------------------------ gpsbabel-sample.ini | 2 +- reference/garmincategories.gpx | 2 +- reference/garmincategories.txt | 4 +- util.cc | 110 ++++++++++++---------- 6 files changed, 112 insertions(+), 172 deletions(-) diff --git a/defs.h b/defs.h index 1ab851799..43822a86e 100644 --- a/defs.h +++ b/defs.h @@ -966,8 +966,8 @@ QDateTime dotnet_time_to_qdatetime(long long dotnet); long long qdatetime_to_dotnet_time(const QDateTime& dt); QString strip_html(const QString& utfstring); QString strip_nastyhtml(const QString& in); -QString convert_human_date_format(const char* human_datef); /* "MM,YYYY,DD" -> "%m,%Y,%d" */ -QString convert_human_time_format(const char* human_timef); /* "HH+mm+ss" -> "%H+%M+%S" */ +QString convert_human_date_format(const char* human_datef, bool read); /* "MM,YYYY,DD" -> "%m,%Y,%d" */ +QString convert_human_time_format(const char* human_timef, bool read); /* "HH+mm+ss" -> "%H+%M+%S" */ QString pretty_deg_format(double lat, double lon, char fmt, const char* sep, bool html); /* decimal -> dd.dddd or dd mm.mmm or dd mm ss */ QString get_filename(const QString& fname); /* extract the filename portion */ diff --git a/garmin_txt.cc b/garmin_txt.cc index 8a6a1433f..09d4a788f 100644 --- a/garmin_txt.cc +++ b/garmin_txt.cc @@ -29,10 +29,9 @@ #include // for toupper #include // for fabs, floor #include // for uint16_t -#include // for sscanf, fprintf, snprintf, stderr -#include // for abs -#include // for strstr, strlen -#include // for time_t, gmtime, localtime, strftime +#include // for sscanf, fprintf, stderr +#include // for abs, div +#include // for strstr #include // for optional #include // for pair, make_pair @@ -83,7 +82,7 @@ static const char* datum_str; static int current_line; static QString date_time_format; static int precision = 3; -static time_t utc_offs = 0; +static int utc_offs = 0; static gtxt_flags_t gtxt_flags; enum header_type { @@ -154,8 +153,8 @@ class PathInfo { public: double length {0}; - time_t start {0}; - time_t time {0}; + QDateTime start; + int time {0}; double speed {0}; double total {0}; int count {0}; @@ -188,16 +187,16 @@ get_option_val(const char* option, const char* def) } static void -init_date_and_time_format() +init_date_and_time_format(bool read) { // This is old, and weird, code.. date_time_format is a global that's // explicitly malloced and freed elsewhere. This isn't very C++ at all, // but this format is on its deathbead for deprecation. const char* d = get_option_val(opt_date_format, kDefaultDateFormat); - QString d1 = convert_human_date_format(d); + QString d1 = convert_human_date_format(d, read); const char* t = get_option_val(opt_time_format, kDefaultTimeFormat); - QString t1 = convert_human_time_format(t); + QString t1 = convert_human_time_format(t, read); date_time_format = QStringLiteral("%1 %2").arg(d1, t1); } @@ -263,11 +262,11 @@ prework_wpt_cb(const Waypoint* wpt) const Waypoint* prev = cur_info->prev_wpt; if (prev != nullptr) { - cur_info->time += (wpt->GetCreationTime().toTime_t() - prev->GetCreationTime().toTime_t()); + cur_info->time += prev->GetCreationTime().secsTo(wpt->GetCreationTime()); cur_info->length += waypt_distance_ex(prev, wpt); } else { cur_info->first_wpt = wpt; - cur_info->start = wpt->GetCreationTime().toTime_t(); + cur_info->start = wpt->GetCreationTime(); } cur_info->prev_wpt = wpt; cur_info->count++; @@ -364,28 +363,35 @@ print_position(const Waypoint* wpt) } static void -print_date_and_time(const time_t time, const bool time_only) +print_duration(int duration_secs) { - std::tm tm{}; - char tbuf[32]; + if (duration_secs < 0) { + *fout << "\t"; + return; + } +#if 1 + // perhaps durations can be longer than the max QTime of 23:59:59 + auto res = std::div(duration_secs, 60); + auto sec = res.rem; + res = std::div(res.quot, 60); + *fout << QString::asprintf("%d:%02d:%02d\t", res.quot, res.rem, sec); +#else + QTime qt = QTime(0, 0).addSecs(duration_secs); + *fout << qt.toString("H:mm:ss\t"); +#endif +} - if (time < 0) { +static void +print_date_and_time(const QDateTime& dt) +{ + if (!dt.isValid()) { *fout << "\t"; return; } - if (time_only) { - tm = *gmtime(&time); - snprintf(tbuf, sizeof(tbuf), "%d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec); - *fout << QString::asprintf("%s", tbuf); - } else if (time != 0) { if (gtxt_flags.utc) { - time_t t = time + utc_offs; - tm = *gmtime(&t); + *fout << dt.toOffsetFromUtc(utc_offs).toString(date_time_format); } else { - tm = *localtime(&time); - } - strftime(tbuf, sizeof(tbuf), CSTR(date_time_format), &tm); - *fout << QString::asprintf("%s ", tbuf); + *fout << dt.toLocalTime().toString(date_time_format); } *fout << "\t"; } @@ -444,7 +450,7 @@ print_distance(const double distance, const bool no_scale, const bool with_tab, } static void -print_speed(const double distance, const time_t time) +print_speed(const double distance, int time) { double dist = distance; const char* unit; @@ -576,7 +582,7 @@ write_waypt(const Waypoint* wpt) print_string("%s\t", garmin_fs_t::get_state(gmsd, "")); const char* country = gt_get_icao_country(garmin_fs_t::get_cc(gmsd, "")); print_string("%s\t", (country != nullptr) ? country : ""); - print_date_and_time(wpt->GetCreationTime().toTime_t(), false); + print_date_and_time(wpt->GetCreationTime()); if (wpt->HasUrlLink()) { UrlLink l = wpt->GetUrlLink(); print_string("%s\t", l.url_); @@ -657,8 +663,8 @@ track_disp_hdr_cb(const route_head* track) *fout << QStringLiteral("\r\n\r\nHeader\t%1\r\n").arg(headers[track_header]); } print_string("\r\nTrack\t%s\t", track->rte_name); - print_date_and_time(cur_info->start, false); - print_date_and_time(cur_info->time, true); + print_date_and_time(cur_info->start); + print_duration(cur_info->time); print_distance(cur_info->length, false, true, 0); print_speed(cur_info->length, cur_info->time); if (track->rte_urls.HasUrlLink()) { @@ -679,13 +685,11 @@ static void track_disp_wpt_cb(const Waypoint* wpt) { const Waypoint* prev = cur_info->prev_wpt; - time_t delta; - double dist; *fout << "Trackpoint\t"; print_position(wpt); - print_date_and_time(wpt->GetCreationTime().toTime_t(), false); + print_date_and_time(wpt->GetCreationTime()); if (is_valid_alt(wpt->altitude)) { print_distance(wpt->altitude, true, false, 0); } @@ -698,15 +702,15 @@ track_disp_wpt_cb(const Waypoint* wpt) if (prev != nullptr) { *fout << "\t"; - delta = wpt->GetCreationTime().toTime_t() - prev->GetCreationTime().toTime_t(); + int delta = prev->GetCreationTime().secsTo(wpt->GetCreationTime()); float temp = wpt->temperature_value_or(-999); if (temp != -999) { print_temperature(temp); } *fout << "\t"; - dist = waypt_distance_ex(prev, wpt); + double dist = waypt_distance_ex(prev, wpt); print_distance(dist, false, true, 0); - print_date_and_time(delta, true); + print_duration(delta); print_speed(dist, delta); print_course(prev, wpt); } @@ -737,7 +741,7 @@ static void garmin_txt_adjust_time(QDateTime& dt) { if (gtxt_flags.utc) { - dt = dt.toUTC().addSecs(dt.offsetFromUtc() - utc_offs); + dt.setOffsetFromUtc(utc_offs); } } @@ -751,7 +755,7 @@ garmin_txt_wr_init(const QString& fname) gtxt_flags.metric = (toupper(*get_option_val(opt_dist, "m")) == 'M'); gtxt_flags.celsius = (toupper(*get_option_val(opt_temp, "c")) == 'C'); - init_date_and_time_format(); + init_date_and_time_format(false); if (opt_precision) { precision = xstrtoi(opt_precision, nullptr, 10); if (precision < 0) { @@ -867,88 +871,12 @@ free_headers() [](auto& list)->void { list.clear(); }); } -// Super simple attempt to convert strftime/strptime spec to Qt spec. -// This misses a LOT of cases and vagaries, but the reality is that we -// see very few date formats here. -static QString -strftime_to_timespec(const char* s) -{ - QString q; - int l = strlen(s); - q.reserve(l * 2); // no penalty if our guess is wrong. - - for (int i = 0; i < l; i++) { - switch (s[i]) { - case '%': - if (i < l-1) { - switch (s[++i]) { - case 'd': - q += "dd"; - continue; - case 'm': - q += "MM"; - continue; - case 'y': - q += "yy"; - continue; - case 'Y': - q += "yyyy"; - continue; - case 'H': - q += "HH"; - continue; - case 'M': - q += "mm"; - continue; - case 'S': - q += "ss"; - continue; - case 'A': - q += "dddd"; - continue; - case 'a': - q += "ddd"; - continue; - case 'B': - q += "MMMM"; - continue; - case 'C': - q += "yy"; - continue; - case 'D': - q += "MM/dd/yyyy"; - continue; - case 'T': - q += "hh:mm:ss"; - continue; - case 'F': - q += "yyyy-MM-dd"; - continue; - case 'p': - q += "AP"; - continue; - default: - warning(MYNAME ": omitting unknown strptime conversion \"%%%c\" in \"%s\"\n", s[i], s); - break; - } - } - break; - default: - q += s[i]; - break; - } - } - return q; -} - - /* data parsers */ static QDateTime parse_date_and_time(const QString& str) { - QString timespec = strftime_to_timespec(CSTR(date_time_format)); - return QDateTime::fromString(QString(str).trimmed(), timespec); + return QDateTime::fromString(QString(str).trimmed(), date_time_format); } static uint16_t @@ -1363,7 +1291,7 @@ garmin_txt_rd_init(const QString& fname) datum_index = -1; grid_index = (grid_type) -1; - init_date_and_time_format(); + init_date_and_time_format(true); garmin_txt_utc_option(); } diff --git a/gpsbabel-sample.ini b/gpsbabel-sample.ini index d2feb0217..93928a95b 100644 --- a/gpsbabel-sample.ini +++ b/gpsbabel-sample.ini @@ -17,7 +17,7 @@ ;------------------------------------------------------------------ [ garmin_txt ] Date = DD.MM.YYYY -Time = HH:mm:ss XX +Time = hh:mm:ss XX Dist = M Temp = C Prec = 6 diff --git a/reference/garmincategories.gpx b/reference/garmincategories.gpx index 95b140d19..68b650583 100644 --- a/reference/garmincategories.gpx +++ b/reference/garmincategories.gpx @@ -5,7 +5,7 @@ - + Hwy 119 The Diagonal The Diagonal diff --git a/reference/garmincategories.txt b/reference/garmincategories.txt index b7ec5292c..fcc44f450 100644 --- a/reference/garmincategories.txt +++ b/reference/garmincategories.txt @@ -3,5 +3,5 @@ Datum WGS 84 Header Name Description Type Position Altitude Depth Proximity Temperature Display Mode Color Symbol Facility City State Country Date Modified Link Categories -Waypoint Hwy 119 The Diagonal User Waypoint N39 58.432183 W105 27.951022 Symbol & Name Unknown Flag, Blue 09.03.2013 13:45:12 PM Slow food,Category 11 -Waypoint Hwy 72 The Peak to Peak User Waypoint N40 00.238037 W105 29.937744 Symbol & Name Unknown Flag, Blue 09.03.2013 13:45:02 PM +Waypoint Hwy 119 The Diagonal User Waypoint N39 58.432183 W105 27.951022 Symbol & Name Unknown Flag, Blue 09.03.2013 01:45:12 AM Slow food,Category 11 +Waypoint Hwy 72 The Peak to Peak User Waypoint N40 00.238037 W105 29.937744 Symbol & Name Unknown Flag, Blue 09.03.2013 01:45:02 PM diff --git a/util.cc b/util.cc index c625e95d4..df5c162f3 100644 --- a/util.cc +++ b/util.cc @@ -569,15 +569,17 @@ rot13(const QString& s) */ QString -convert_human_date_format(const char* human_datef) +convert_human_date_format(const char* human_datef, bool read) { - char* result = (char*) xcalloc((2*strlen(human_datef)) + 1, 1); - char* cout = result; - char prev = '\0'; + constexpr char quote = '\''; + constexpr char zero = '\0'; + char prev = zero; + int ylen = 0; + QStringList out; for (const char* cin = human_datef; *cin; cin++) { - char okay = 1; + bool okay = true; if (toupper(*cin) != 'Y') { ylen = 0; @@ -587,48 +589,54 @@ convert_human_date_format(const char* human_datef) case 'y': case 'Y': if (prev != 'Y') { - strcat(cout, "%y"); - cout += 2; + out.append("yy"); prev = 'Y'; } ylen++; if (ylen > 2) { - *(cout-1) = 'Y'; + out.last() = "yyyy"; } break; case 'm': case 'M': if (prev != 'M') { - strcat(cout, "%m"); - cout += 2; + out.append("MM"); prev = 'M'; } break; case 'd': case 'D': if (prev != 'D') { - strcat(cout, "%d"); - cout += 2; + out.append("dd"); prev = 'D'; } break; default: - okay = 0; + okay = false; } } else if (ispunct(*cin)) { - *cout++ = *cin; - prev = '\0'; + if (*cin == quote) { + if (read) { + // fromString methods use a single quoted backslashed single quote. + // good luck finding this in the Qt documentation! + out.append(R"('\'')"); + } else { // write + // toString methods used a double single quote. + out.append(R"('')"); + } + } else { + out.append(QChar(*cin)); + } + prev = zero; } else { - okay = 0; + okay = false; } - if (okay == 0) { + if (!okay) { fatal("Invalid character \"%c\" in date format \"%s\"!\n", *cin, human_datef); } } - QString rv(result); - xfree(result); - return rv; + return out.join(""); } /* @@ -637,22 +645,22 @@ convert_human_date_format(const char* human_datef) */ QString -convert_human_time_format(const char* human_timef) +convert_human_time_format(const char* human_timef, bool read) { - char* result = (char*) xcalloc((2*strlen(human_timef)) + 1, 1); - char* cout = result; - char prev = '\0'; + constexpr char quote = '\''; + constexpr char zero = '\0'; + char prev = zero; + QStringList out; for (const char* cin = human_timef; *cin; cin++) { - int okay = 1; + bool okay = true; if (isalpha(*cin)) { switch (*cin) { case 'S': case 's': if (prev != 'S') { - strcat(cout, "%S"); - cout += 2; + out.append("ss"); prev = 'S'; } break; @@ -660,69 +668,73 @@ convert_human_time_format(const char* human_timef) case 'M': case 'm': if (prev != 'M') { - strcat(cout, "%M"); - cout += 2; + out.append("mm"); prev = 'M'; } break; case 'h': /* 12-hour-clock */ if (prev != 'H') { - strcat(cout, "%l"); /* 1 .. 12 */ - cout += 2; + out.append("h"); prev = 'H'; } else { - *(cout-1) = 'I'; /* 01 .. 12 */ + out.last() = "hh"; } break; case 'H': /* 24-hour-clock */ if (prev != 'H') { - strcat(cout, "%k"); - cout += 2; + out.append("H"); prev = 'H'; } else { - *(cout-1) = 'H'; + out.last() = "HH"; } break; case 'x': if (prev != 'X') { - strcat(cout, "%P"); - cout += 2; + out.append("ap"); prev = 'X'; } else { - *(cout-1) = 'P'; + out.last() = "ap"; } break; case 'X': if (prev != 'X') { - strcat(cout, "%p"); - cout += 2; + out.append("AP"); prev = 'X'; } else { - *(cout-1) = 'p'; + out.last() = "AP"; } break; default: - okay = 0; + okay = false; } } else if (ispunct(*cin) || isspace(*cin)) { - *cout++ = *cin; - prev = '\0'; + if (*cin == quote) { + if (read) { + // fromString methods use a single quoted backslashed single quote. + // good luck finding this in the Qt documentation! + out.append(R"('\'')"); + } else { // write + // toString methods used a double single quote. + out.append(R"('')"); + } + } else { + out.append(QChar(*cin)); + } + prev = zero; } else { - okay = 0; + okay = false; } - if (okay == 0) { + if (!okay) { fatal("Invalid character \"%c\" in time format \"%s\"!\n", *cin, human_timef); } } - QString rv(result); - xfree(result); - return rv; + return out.join(""); } From 2df1566156cc37a9a96093c985987f58aacd9e90 Mon Sep 17 00:00:00 2001 From: tsteven4 <13596209+tsteven4@users.noreply.github.com> Date: Thu, 2 Nov 2023 14:19:50 -0600 Subject: [PATCH 2/3] correct comment --- defs.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/defs.h b/defs.h index 43822a86e..5bf7e2eea 100644 --- a/defs.h +++ b/defs.h @@ -966,8 +966,8 @@ QDateTime dotnet_time_to_qdatetime(long long dotnet); long long qdatetime_to_dotnet_time(const QDateTime& dt); QString strip_html(const QString& utfstring); QString strip_nastyhtml(const QString& in); -QString convert_human_date_format(const char* human_datef, bool read); /* "MM,YYYY,DD" -> "%m,%Y,%d" */ -QString convert_human_time_format(const char* human_timef, bool read); /* "HH+mm+ss" -> "%H+%M+%S" */ +QString convert_human_date_format(const char* human_datef, bool read); /* "MM,YYYY,DD" -> "MM,yyyy,dd" */ +QString convert_human_time_format(const char* human_timef, bool read); /* "HH+mm+ss" -> "HH+mm+ss" */ QString pretty_deg_format(double lat, double lon, char fmt, const char* sep, bool html); /* decimal -> dd.dddd or dd mm.mmm or dd mm ss */ QString get_filename(const QString& fname); /* extract the filename portion */ From 6e10de554d3c67fec9c89bca0d9df1a888ea18ea Mon Sep 17 00:00:00 2001 From: tsteven4 <13596209+tsteven4@users.noreply.github.com> Date: Thu, 2 Nov 2023 14:23:12 -0600 Subject: [PATCH 3/3] fix ws --- garmin_txt.cc | 8 ++++---- util.cc | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/garmin_txt.cc b/garmin_txt.cc index 09d4a788f..ab5f1afc3 100644 --- a/garmin_txt.cc +++ b/garmin_txt.cc @@ -388,9 +388,9 @@ print_date_and_time(const QDateTime& dt) *fout << "\t"; return; } - if (gtxt_flags.utc) { + if (gtxt_flags.utc) { *fout << dt.toOffsetFromUtc(utc_offs).toString(date_time_format); - } else { + } else { *fout << dt.toLocalTime().toString(date_time_format); } *fout << "\t"; @@ -1110,7 +1110,7 @@ parse_waypoint(const QStringList& lineparts) garmin_txt_adjust_time(dt); wpt->SetCreationTime(dt); } - break; + break; case 17: { wpt->AddUrlLink(str); } @@ -1246,7 +1246,7 @@ parse_track_waypoint(const QStringList& lineparts) garmin_txt_adjust_time(dt); wpt->SetCreationTime(dt); } - break; + break; case 3: if (parse_distance(str, &x, 1, MYNAME)) { wpt->altitude = x; diff --git a/util.cc b/util.cc index df5c162f3..8213729dd 100644 --- a/util.cc +++ b/util.cc @@ -619,7 +619,7 @@ convert_human_date_format(const char* human_datef, bool read) if (read) { // fromString methods use a single quoted backslashed single quote. // good luck finding this in the Qt documentation! - out.append(R"('\'')"); + out.append(R"('\'')"); } else { // write // toString methods used a double single quote. out.append(R"('')"); @@ -717,7 +717,7 @@ convert_human_time_format(const char* human_timef, bool read) if (read) { // fromString methods use a single quoted backslashed single quote. // good luck finding this in the Qt documentation! - out.append(R"('\'')"); + out.append(R"('\'')"); } else { // write // toString methods used a double single quote. out.append(R"('')");