+ // NOTE: Move regExpMatchTime to a static regular expression if used anywhere that performance
+ // matters.
+
+ // Don't allow a runaway regular expression to loop for too long. This might not happen.. but
+ // when dealing with user input, better to be safe..?
+ int numIterations = 0;
+
+ // Find each group of %%text here%% starting from the beginning
+ int index = regExpMatchTime.indexIn(formattedStr);
+ int matchLength;
+ QString matchedFormat;
+ while (index >= 0 && numIterations < 512) {
+ // Get the total length of the matched expression
+ matchLength = regExpMatchTime.cap(0).length();
+ // Get the format string, e.g. "this text here" from "%%this text here%%"
+ matchedFormat = regExpMatchTime.cap(1);
+ // Check that there's actual characters inside. A quadruple % (%%%%) represents two %%
+ // signs.
+ if (matchedFormat.length() > 0) {
+ // Format the string according to the current date and time. Invalid time format
+ // strings are ignored.
+ formattedStr.replace(index, matchLength,
+ QDateTime::currentDateTime().toString(matchedFormat));
+ // Subtract the length of the removed % signs
+ // E.g. "%%h:mm ap%%" turns into "h:mm ap", removing four % signs, thus -4. This is
+ // used below to determine how far to advance when looking for the next formatting code.
+ matchLength -= 4;
+ } else if (matchLength == 4) {
+ // Remove two of the four percent signs, so '%%%%' escapes to '%%'
+ formattedStr.remove(index, 2);
+ // Subtract the length of the removed % signs, this time removing two % signs, thus -2.
+ matchLength -= 2;
+ } else {
+ // If neither of these match, something went wrong. Don't modify it to be safe.
+ qDebug() << "Unexpected time format when parsing string, no matchedFormat, matchLength "
+ "should be 4, actually is" << matchLength;
+ }
+
+ // Find the next group of %%text here%% starting from where the last group ended
+ index = regExpMatchTime.indexIn(formattedStr, index + matchLength);
+ numIterations++;
+ }
+
+ return formattedStr;
+}
+
+
+QString tryFormatUnixEpoch(const QString &possibleEpochDate, Qt::DateFormat dateFormat, bool useUTC)
+{
+ // Does the string resemble a Unix epoch? Parse as 64-bit time
+ qint64 secsSinceEpoch = possibleEpochDate.toLongLong();
+ if (secsSinceEpoch == 0) {
+ // Parsing either failed, or '0' was sent. No need to distinguish; either way, it's not
+ // useful as epoch.
+ // See https://doc.qt.io/qt-5/qstring.html#toLongLong
+ return possibleEpochDate;
+ }
+
+ // Time checks out, parse it
+ QDateTime date;
+#if QT_VERSION >= 0x050800
+ date.setSecsSinceEpoch(secsSinceEpoch);
+#else
+ // toSecsSinceEpoch() was added in Qt 5.8. Manually downconvert to seconds for now.
+ // See https://doc.qt.io/qt-5/qdatetime.html#toMSecsSinceEpoch
+ date.setMSecsSinceEpoch(secsSinceEpoch * 1000);
+#endif
+
+ // Return the localized date/time
+ if (useUTC) {
+ // Return UTC time
+ if (dateFormat == Qt::DateFormat::ISODate) {
+ // Replace the "T" date/time separator with " " for readability. This isn't quite the
+ // ISO 8601 spec (it specifies omitting the "T" entirely), but RFC 3339 allows this.
+ // Go with RFC 3339 for human readability that's still machine-parseable, too.
+ //
+ // Before: 2018-06-21T21:35:52Z
+ // After: 2018-06-21 21:35:52Z
+ // ..........^ (10th character)
+ //
+ // See https://en.wikipedia.org/wiki/ISO_8601#cite_note-32
+ // And https://www.ietf.org/rfc/rfc3339.txt
+ return date.toUTC().toString(dateFormat).replace(10, 1, " ");
+ } else {
+ return date.toUTC().toString(dateFormat);
+ }
+ } else if (dateFormat == Qt::DateFormat::ISODate) {
+ // Add in ISO local timezone information via special handling below
+ // formatDateTimeToOffsetISO() handles converting "T" to " "
+ return formatDateTimeToOffsetISO(date);
+ } else {
+ // Return local time
+ return date.toString(dateFormat);
+ }