From 9b1835c16f602120a545494e85dcbcb830b1bd6c Mon Sep 17 00:00:00 2001 From: Stefan Steiner Date: Thu, 2 Oct 2025 18:11:54 -0700 Subject: [PATCH 1/5] Added simple microsecond handling --- src/treemodel.cpp | 51 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/src/treemodel.cpp b/src/treemodel.cpp index c5dcbb9..39be148 100644 --- a/src/treemodel.cpp +++ b/src/treemodel.cpp @@ -90,11 +90,54 @@ QVariant TreeModel::data(const QModelIndex &index, int role) const switch (m_timeMode) { case TimeMode::GlobalDateTime: - return dateTime.toString("MM/dd/yyyy - hh:mm:ss.zzz"); case TimeMode::GlobalTime: - return dateTime.toString("hh:mm:ss.zzz"); + { + // Get original timestamp string from the event data to check for microseconds + QModelIndex rootIdx = index; + while (rootIdx.parent().isValid()) { + rootIdx = rootIdx.parent(); + } + + if (rootIdx.isValid() && rootIdx.row() >= 0 && rootIdx.row() < m_allEvents->size()) { + QJsonObject event = m_allEvents->at(rootIdx.row()); + QString originalTs = event["ts"].toString(); + + // Check if it's a microsecond timestamp (26 chars: "yyyy-MM-ddTHH:mm:ss.zzzzzz") + if (originalTs.length() == 26) { + // Custom format for microsecond display + QString formatted = originalTs; + formatted.replace('T', ' '); + + if (m_timeMode == TimeMode::GlobalDateTime) { + // Convert "2023-01-01 12:34:56.123456" to "01/01/2023 - 12:34:56.123456" + QStringList parts = formatted.split(' '); + if (parts.size() == 2) { + QStringList dateParts = parts[0].split('-'); + if (dateParts.size() == 3) { + return QString("%1/%2/%3 - %4") + .arg(dateParts[1]) // month + .arg(dateParts[2]) // day + .arg(dateParts[0]) // year + .arg(parts[1]); // time with microseconds + } + } + } else { + // Just return time part for GlobalTime mode + QStringList parts = formatted.split(' '); + if (parts.size() == 2) { + return parts[1]; // time with microseconds + } + } + } + } + + // Fallback to standard formatting for millisecond timestamps + QString format = (m_timeMode == TimeMode::GlobalDateTime) ? + "MM/dd/yyyy - hh:mm:ss.zzz" : "hh:mm:ss.zzz"; + return dateTime.toString(format); + } case TimeMode::TimeDeltas: - return GetDeltaMSecs(dateTime); + return GetDeltaMSecs(dateTime); } } else if (col == COL::ART) @@ -640,7 +683,7 @@ QJsonValue TreeModel::ConsolidateValueAndActivity(const QJsonObject& eventObject { bool showART = eventObject.contains("a") && Options::GetInstance().getShowArtDataInValue(); bool showErrorCode = eventObject.contains("e") && Options::GetInstance().getShowErrorCodeInValue(); - + if (showART || showErrorCode) { QJsonObject obj; From b89b6073ce9cb1daf854143d96f81479cb3e22c9 Mon Sep 17 00:00:00 2001 From: Stefan Steiner Date: Thu, 2 Oct 2025 18:43:40 -0700 Subject: [PATCH 2/5] Made a more extensive change to support microsecond timestamps. They now support sorting at microsecond level so when merging several logs together, they are properly merged at microsecond instead of millisecond granularity. --- src/logtab.cpp | 19 ++- src/microtimestamp.cpp | 255 +++++++++++++++++++++++++++++++++++++++++ src/microtimestamp.h | 50 ++++++++ src/treemodel.cpp | 110 +++++++----------- 4 files changed, 366 insertions(+), 68 deletions(-) create mode 100644 src/microtimestamp.cpp create mode 100644 src/microtimestamp.h diff --git a/src/logtab.cpp b/src/logtab.cpp index 1b1c83c..2d6da73 100644 --- a/src/logtab.cpp +++ b/src/logtab.cpp @@ -1,6 +1,7 @@ #include "logtab.h" #include "ui_logtab.h" +#include "microtimestamp.h" #include "options.h" #include "pathhelper.h" #include "processevent.h" @@ -47,8 +48,22 @@ void LogTab::InitTreeView(const EventListPtr events) bool multipleDays = false; if (events->size() >= 2) { QModelIndex idx = ui->treeView->currentIndex(); - auto firstDatetime = m_treeModel->index(0, COL::Time, idx.parent()).data(Qt::UserRole).toDateTime(); - auto lastDatetime = m_treeModel->index(m_treeModel->rowCount()-1, COL::Time, idx.parent()).data(Qt::UserRole).toDateTime(); + auto firstData = m_treeModel->index(0, COL::Time, idx.parent()).data(Qt::UserRole); + auto lastData = m_treeModel->index(m_treeModel->rowCount()-1, COL::Time, idx.parent()).data(Qt::UserRole); + + QDateTime firstDatetime, lastDatetime; + if (firstData.canConvert()) { + firstDatetime = firstData.value().toDateTime(); + } else { + firstDatetime = firstData.toDateTime(); + } + + if (lastData.canConvert()) { + lastDatetime = lastData.value().toDateTime(); + } else { + lastDatetime = lastData.toDateTime(); + } + multipleDays = firstDatetime.date() != lastDatetime.date(); } m_treeModel->SetTimeMode(multipleDays ? TimeMode::GlobalDateTime : TimeMode::GlobalTime); diff --git a/src/microtimestamp.cpp b/src/microtimestamp.cpp new file mode 100644 index 0000000..ba6ced0 --- /dev/null +++ b/src/microtimestamp.cpp @@ -0,0 +1,255 @@ +#include "microtimestamp.h" +#include +#include + +MicroTimestamp::MicroTimestamp() + : m_microseconds(0), m_valid(false), m_hasMicros(false) +{ +} + +MicroTimestamp::MicroTimestamp(const QString ×tampString) + : m_microseconds(0), m_valid(false), m_hasMicros(false) +{ + parseTimestamp(timestampString); +} + +MicroTimestamp::MicroTimestamp(const QDateTime &dateTime) + : m_dateTime(dateTime), m_microseconds(0), m_valid(dateTime.isValid()), m_hasMicros(false) +{ +} + +void MicroTimestamp::parseTimestamp(const QString ×tampString) +{ + if (timestampString.isEmpty()) { + m_valid = false; + return; + } + + // Check for microsecond format: yyyy-MM-ddTHH:mm:ss.zzzzzz (26 chars) + if (timestampString.length() == 26) { + // Parse the microsecond timestamp manually + QRegularExpression regex(R"(^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.(\d{6})$)"); + QRegularExpressionMatch match = regex.match(timestampString); + + if (match.hasMatch()) { + int year = match.captured(1).toInt(); + int month = match.captured(2).toInt(); + int day = match.captured(3).toInt(); + int hour = match.captured(4).toInt(); + int minute = match.captured(5).toInt(); + int second = match.captured(6).toInt(); + QString microStr = match.captured(7); + + // Create QDateTime with millisecond precision + int milliseconds = microStr.left(3).toInt(); + m_microseconds = microStr.right(3).toInt(); // Additional microseconds + + QDate date(year, month, day); + QTime time(hour, minute, second, milliseconds); + m_dateTime = QDateTime(date, time); + + m_valid = m_dateTime.isValid(); + m_hasMicros = true; + return; + } + } + + // Try standard millisecond format: yyyy-MM-ddTHH:mm:ss.zzz (23 chars) + if (timestampString.length() == 23) { + m_dateTime = QDateTime::fromString(timestampString, "yyyy-MM-ddTHH:mm:ss.zzz"); + m_microseconds = 0; + m_valid = m_dateTime.isValid(); + m_hasMicros = false; + return; + } + + // Try other common formats + QStringList formats = { + "yyyy-MM-ddTHH:mm:ss", + "yyyy-MM-dd HH:mm:ss.zzz", + "yyyy-MM-dd HH:mm:ss" + }; + + for (const QString &format : formats) { + m_dateTime = QDateTime::fromString(timestampString, format); + if (m_dateTime.isValid()) { + m_microseconds = 0; + m_valid = true; + m_hasMicros = false; + return; + } + } + + m_valid = false; +} + +bool MicroTimestamp::operator<(const MicroTimestamp &other) const +{ + if (!m_valid || !other.m_valid) { + return !m_valid && other.m_valid; // Invalid timestamps sort first + } + + if (m_dateTime != other.m_dateTime) { + return m_dateTime < other.m_dateTime; + } + + // If QDateTime is equal, compare microseconds + return m_microseconds < other.m_microseconds; +} + +bool MicroTimestamp::operator<=(const MicroTimestamp &other) const +{ + return *this < other || *this == other; +} + +bool MicroTimestamp::operator>(const MicroTimestamp &other) const +{ + return !(*this <= other); +} + +bool MicroTimestamp::operator>=(const MicroTimestamp &other) const +{ + return !(*this < other); +} + +bool MicroTimestamp::operator==(const MicroTimestamp &other) const +{ + if (!m_valid || !other.m_valid) { + return m_valid == other.m_valid; + } + + return m_dateTime == other.m_dateTime && m_microseconds == other.m_microseconds; +} + +bool MicroTimestamp::operator!=(const MicroTimestamp &other) const +{ + return !(*this == other); +} + +QString MicroTimestamp::toString(const QString &format) const +{ + if (!m_valid) { + return QString(); + } + + QString result = m_dateTime.toString(format); + + // If format contains microsecond placeholder and we have microseconds + if (m_hasMicros && format.contains("zzzzzz")) { + // Replace the millisecond part with full microsecond precision + QString msStr = QString("%1").arg(m_dateTime.time().msec(), 3, 10, QChar('0')); + QString microStr = QString("%1%2").arg(msStr).arg(m_microseconds, 3, 10, QChar('0')); + result.replace(msStr, microStr); + } + + return result; +} + +QString MicroTimestamp::toDisplayString(bool includeDate) const +{ + if (!m_valid) { + return QString(); + } + + if (m_hasMicros) { + // Custom formatting for microsecond display with half space separator + QString dateStr = m_dateTime.date().toString("MM/dd/yyyy"); + QString timeStr = m_dateTime.time().toString("hh:mm:ss"); + QString millisStr = QString("%1").arg(m_dateTime.time().msec(), 3, 10, QChar('0')); + QString microsStr = QString("%1").arg(m_microseconds, 3, 10, QChar('0')); + + // Use regular space to separate milliseconds from microseconds + QString microStr = QString("%1 %2").arg(millisStr, microsStr); + + if (includeDate) { + return QString("%1 - %2.%3").arg(dateStr, timeStr, microStr); + } else { + return QString("%1.%2").arg(timeStr, microStr); + } + } else { + // Standard formatting for millisecond display + if (includeDate) { + return m_dateTime.toString("MM/dd/yyyy - hh:mm:ss.zzz"); + } else { + return m_dateTime.toString("hh:mm:ss.zzz"); + } + } +} + +QDateTime MicroTimestamp::toDateTime() const +{ + return m_dateTime; +} + +qint64 MicroTimestamp::toMSecsSinceEpoch() const +{ + if (!m_valid) { + return 0; + } + return m_dateTime.toMSecsSinceEpoch(); +} + +qint64 MicroTimestamp::toMicroSecsSinceEpoch() const +{ + if (!m_valid) { + return 0; + } + return m_dateTime.toMSecsSinceEpoch() * 1000 + m_microseconds; +} + +bool MicroTimestamp::isValid() const +{ + return m_valid; +} + +bool MicroTimestamp::hasMicroseconds() const +{ + return m_hasMicros; +} + +void MicroTimestamp::registerMetaType() +{ + qRegisterMetaType("MicroTimestamp"); +} + +// QVariant conversion functions +namespace { + bool convertToMicroTimestamp(const QVariant &from, MicroTimestamp *to) + { + if (from.canConvert()) { + *to = from.value(); + return true; + } + if (from.canConvert()) { + *to = MicroTimestamp(from.toDateTime()); + return true; + } + if (from.canConvert()) { + *to = MicroTimestamp(from.toString()); + return true; + } + return false; + } + + bool convertFromMicroTimestamp(const MicroTimestamp &from, QVariant *to, int targetType) + { + switch (targetType) { + case QMetaType::QDateTime: + *to = from.toDateTime(); + return true; + case QMetaType::QString: + *to = from.toDisplayString(true); + return true; + case QMetaType::LongLong: + *to = from.toMicroSecsSinceEpoch(); + return true; + default: + return false; + } + } +} + +static bool microTimestampRegistered = []() { + MicroTimestamp::registerMetaType(); + return true; +}(); diff --git a/src/microtimestamp.h b/src/microtimestamp.h new file mode 100644 index 0000000..f4d0128 --- /dev/null +++ b/src/microtimestamp.h @@ -0,0 +1,50 @@ +#ifndef MICROTIMESTAMP_H +#define MICROTIMESTAMP_H + +#include +#include +#include + +class MicroTimestamp +{ +public: + MicroTimestamp(); + explicit MicroTimestamp(const QString ×tampString); + explicit MicroTimestamp(const QDateTime &dateTime); + + // Comparison operators for sorting + bool operator<(const MicroTimestamp &other) const; + bool operator<=(const MicroTimestamp &other) const; + bool operator>(const MicroTimestamp &other) const; + bool operator>=(const MicroTimestamp &other) const; + bool operator==(const MicroTimestamp &other) const; + bool operator!=(const MicroTimestamp &other) const; + + // Display formatting + QString toString(const QString &format) const; + QString toDisplayString(bool includeDate = true) const; + + // Conversion functions + QDateTime toDateTime() const; + qint64 toMSecsSinceEpoch() const; + qint64 toMicroSecsSinceEpoch() const; + + // Validation + bool isValid() const; + bool hasMicroseconds() const; + + // For QVariant storage + static void registerMetaType(); + +private: + QDateTime m_dateTime; + int m_microseconds; // Additional microseconds (0-999) + bool m_valid; + bool m_hasMicros; + + void parseTimestamp(const QString ×tampString); +}; + +Q_DECLARE_METATYPE(MicroTimestamp) + +#endif // MICROTIMESTAMP_H diff --git a/src/treemodel.cpp b/src/treemodel.cpp index 39be148..0ddf216 100644 --- a/src/treemodel.cpp +++ b/src/treemodel.cpp @@ -1,5 +1,6 @@ #include "treemodel.h" +#include "microtimestamp.h" #include "options.h" #include "qjsonutils.h" #include "themeutils.h" @@ -83,61 +84,37 @@ QVariant TreeModel::data(const QModelIndex &index, int role) const TreeItem* item = GetItem(index); if (col == COL::Time) { - QDateTime dateTime = item->Data(col).toDateTime(); - if (!dateTime.isValid()) - return ""; - - switch (m_timeMode) - { - case TimeMode::GlobalDateTime: - case TimeMode::GlobalTime: - { - // Get original timestamp string from the event data to check for microseconds - QModelIndex rootIdx = index; - while (rootIdx.parent().isValid()) { - rootIdx = rootIdx.parent(); - } - - if (rootIdx.isValid() && rootIdx.row() >= 0 && rootIdx.row() < m_allEvents->size()) { - QJsonObject event = m_allEvents->at(rootIdx.row()); - QString originalTs = event["ts"].toString(); - - // Check if it's a microsecond timestamp (26 chars: "yyyy-MM-ddTHH:mm:ss.zzzzzz") - if (originalTs.length() == 26) { - // Custom format for microsecond display - QString formatted = originalTs; - formatted.replace('T', ' '); - - if (m_timeMode == TimeMode::GlobalDateTime) { - // Convert "2023-01-01 12:34:56.123456" to "01/01/2023 - 12:34:56.123456" - QStringList parts = formatted.split(' '); - if (parts.size() == 2) { - QStringList dateParts = parts[0].split('-'); - if (dateParts.size() == 3) { - return QString("%1/%2/%3 - %4") - .arg(dateParts[1]) // month - .arg(dateParts[2]) // day - .arg(dateParts[0]) // year - .arg(parts[1]); // time with microseconds - } - } - } else { - // Just return time part for GlobalTime mode - QStringList parts = formatted.split(' '); - if (parts.size() == 2) { - return parts[1]; // time with microseconds - } - } - } - } - - // Fallback to standard formatting for millisecond timestamps - QString format = (m_timeMode == TimeMode::GlobalDateTime) ? - "MM/dd/yyyy - hh:mm:ss.zzz" : "hh:mm:ss.zzz"; - return dateTime.toString(format); - } - case TimeMode::TimeDeltas: - return GetDeltaMSecs(dateTime); + // Try to get MicroTimestamp first, fall back to QDateTime + QVariant data = item->Data(col); + if (data.canConvert()) { + MicroTimestamp microTs = data.value(); + if (!microTs.isValid()) + return ""; + + switch (m_timeMode) + { + case TimeMode::GlobalDateTime: + return microTs.toDisplayString(true); + case TimeMode::GlobalTime: + return microTs.toDisplayString(false); + case TimeMode::TimeDeltas: + return GetDeltaMSecs(microTs.toDateTime()); + } + } else { + // Fallback to original QDateTime logic + QDateTime dateTime = data.toDateTime(); + if (!dateTime.isValid()) + return ""; + + switch (m_timeMode) + { + case TimeMode::GlobalDateTime: + return dateTime.toString("MM/dd/yyyy - hh:mm:ss.zzz"); + case TimeMode::GlobalTime: + return dateTime.toString("hh:mm:ss.zzz"); + case TimeMode::TimeDeltas: + return GetDeltaMSecs(dateTime); + } } } else if (col == COL::ART) @@ -415,14 +392,8 @@ void TreeModel::SetTabType(TABTYPE type) m_fileType = type; } -static QDateTime parseTs(QString value) { - QString microsecondFormat = "yyyy-MM-ddTHH:mm:ss.zzzzzz"; - QString millisecondFormat = "yyyy-MM-ddTHH:mm:ss.zzz"; - if (value.size() == microsecondFormat.size()) { - // Hyper prints higher precision times that QT can't parse, so truncating here - value = value.left(millisecondFormat.size()); - } - return QDateTime::fromString(value, millisecondFormat); +static MicroTimestamp parseTs(QString value) { + return MicroTimestamp(value); } /// @@ -442,10 +413,17 @@ int TreeModel::MergeIntoModelData(const EventList& events) for (int mergeIter = events.size() - 1; mergeIter >= 0; mergeIter--) { - QDateTime mergeTime = parseTs(events[mergeIter]["ts"].toString()); + MicroTimestamp mergeTime = parseTs(events[mergeIter]["ts"].toString()); for (; origIter >= 0; origIter--) { - QDateTime origTime = m_rootItem->Child(origIter)->Data(COL::Time).toDateTime(); + QVariant timeData = m_rootItem->Child(origIter)->Data(COL::Time); + MicroTimestamp origTime; + if (timeData.canConvert()) { + origTime = timeData.value(); + } else { + origTime = MicroTimestamp(timeData.toDateTime()); + } + if (mergeTime >= origTime) { InsertChild(origIter + 1, events[mergeIter]); @@ -505,7 +483,7 @@ void TreeModel::SetupChild(TreeItem *child, const QJsonObject & event) { child->SetData(COL::ID, event["idx"].toInt()); child->SetData(COL::File, event["file"].toString()); - child->SetData(COL::Time, parseTs(event["ts"].toString())); + child->SetData(COL::Time, QVariant::fromValue(parseTs(event["ts"].toString()))); child->SetData(COL::PID, event["pid"].toInt()); child->SetData(COL::TID, event["tid"].toString()); child->SetData(COL::Severity, event["sev"].toString()); From e6e8ece541fb5765d14a004890602074c51bb9df Mon Sep 17 00:00:00 2001 From: Stefan Steiner Date: Thu, 2 Oct 2025 19:08:47 -0700 Subject: [PATCH 3/5] Added a space between the three digits of the millisecond and the last three digits of microsecond but remove the space when copying. --- src/logtab.cpp | 12 +++++++++--- src/microtimestamp.cpp | 29 +++++++++++++++++++++++++++++ src/microtimestamp.h | 1 + src/tableau-log-viewer.pro | 7 ++++--- src/treemodel.cpp | 21 +++++++++++++++++++++ 5 files changed, 64 insertions(+), 6 deletions(-) diff --git a/src/logtab.cpp b/src/logtab.cpp index 2d6da73..24bd98d 100644 --- a/src/logtab.cpp +++ b/src/logtab.cpp @@ -764,9 +764,15 @@ void LogTab::CopyItemDetails(bool textOnly, bool normalized) const if (!(ui->treeView->isColumnHidden(i)) && (!normalized || (i == COL::Key) || (i == COL::Value))) { auto idx_info = item_model->index(idx.row(), i, idx.parent()); - QString info = (i == COL::Value) ? - m_treeModel->GetValueFullString(idx, true).replace("\t", " ") : - idx_info.data().toString(); + QString info; + if (i == COL::Time) { + // Use copy-friendly format for timestamps (without space separator) + info = idx_info.data(Qt::UserRole + 1).toString(); + } else if (i == COL::Value) { + info = m_treeModel->GetValueFullString(idx, true).replace("\t", " "); + } else { + info = idx_info.data().toString(); + } if (info.length() > 0) { diff --git a/src/microtimestamp.cpp b/src/microtimestamp.cpp index ba6ced0..525dfbe 100644 --- a/src/microtimestamp.cpp +++ b/src/microtimestamp.cpp @@ -176,6 +176,35 @@ QString MicroTimestamp::toDisplayString(bool includeDate) const } } +QString MicroTimestamp::toCopyString(bool includeDate) const +{ + if (!m_valid) { + return QString(); + } + + if (m_hasMicros) { + // Format for copying without space separator + QString dateStr = m_dateTime.date().toString("MM/dd/yyyy"); + QString timeStr = m_dateTime.time().toString("hh:mm:ss"); + QString microStr = QString("%1%2") + .arg(m_dateTime.time().msec(), 3, 10, QChar('0')) + .arg(m_microseconds, 3, 10, QChar('0')); + + if (includeDate) { + return QString("%1 - %2.%3").arg(dateStr, timeStr, microStr); + } else { + return QString("%1.%2").arg(timeStr, microStr); + } + } else { + // Standard formatting for millisecond display + if (includeDate) { + return m_dateTime.toString("MM/dd/yyyy - hh:mm:ss.zzz"); + } else { + return m_dateTime.toString("hh:mm:ss.zzz"); + } + } +} + QDateTime MicroTimestamp::toDateTime() const { return m_dateTime; diff --git a/src/microtimestamp.h b/src/microtimestamp.h index f4d0128..0000086 100644 --- a/src/microtimestamp.h +++ b/src/microtimestamp.h @@ -23,6 +23,7 @@ class MicroTimestamp // Display formatting QString toString(const QString &format) const; QString toDisplayString(bool includeDate = true) const; + QString toCopyString(bool includeDate = true) const; // For copying without space separator // Conversion functions QDateTime toDateTime() const; diff --git a/src/tableau-log-viewer.pro b/src/tableau-log-viewer.pro index a751b8f..3f4e0bf 100644 --- a/src/tableau-log-viewer.pro +++ b/src/tableau-log-viewer.pro @@ -14,7 +14,7 @@ FORMS = \ mainwindow.ui \ optionsdlg.ui \ savefilterdialog.ui \ - valuedlg.ui + valuedlg.ui HEADERS = \ colorlibrary.h \ @@ -25,6 +25,7 @@ HEADERS = \ highlightoptions.h \ logtab.h \ mainwindow.h \ + microtimestamp.h \ options.h \ optionsdlg.h \ pathhelper.h \ @@ -50,6 +51,7 @@ SOURCES = \ logtab.cpp \ main.cpp \ mainwindow.cpp \ + microtimestamp.cpp \ options.cpp \ optionsdlg.cpp \ pathhelper.cpp \ @@ -73,9 +75,8 @@ win32:RC_ICONS += ../resources/images/tlv.ico ICON = ../resources/images/tlv.icns CONFIG += c++17 -CONFIG += x86_64 -QMAKE_APPLE_DEVICE_ARCHS = x86_64 arm64 +QMAKE_APPLE_DEVICE_ARCHS = arm64 VERSION = 1.3.0 DEFINES += APP_VERSION=\\\"$$VERSION\\\" diff --git a/src/treemodel.cpp b/src/treemodel.cpp index 0ddf216..232dc6c 100644 --- a/src/treemodel.cpp +++ b/src/treemodel.cpp @@ -79,6 +79,27 @@ QVariant TreeModel::data(const QModelIndex &index, int role) const TreeItem* item = GetItem(index); return item->Data(col); } + case Qt::UserRole + 1: // Copy role - provides data without display formatting + { + TreeItem* item = GetItem(index); + if (col == COL::Time) { + QVariant data = item->Data(col); + if (data.canConvert()) { + MicroTimestamp microTs = data.value(); + if (!microTs.isValid()) + return ""; + return microTs.toCopyString(m_timeMode == TimeMode::GlobalDateTime); + } else { + QDateTime dateTime = data.toDateTime(); + if (!dateTime.isValid()) + return ""; + QString format = (m_timeMode == TimeMode::GlobalDateTime) ? + "MM/dd/yyyy - hh:mm:ss.zzz" : "hh:mm:ss.zzz"; + return dateTime.toString(format); + } + } + return item->Data(col); + } case Qt::DisplayRole: { TreeItem* item = GetItem(index); From 07cdde15dc8ef3d16c30683ff717c2b848b99022 Mon Sep 17 00:00:00 2001 From: Stefan Steiner Date: Thu, 2 Oct 2025 19:23:47 -0700 Subject: [PATCH 4/5] Revert compiler change --- src/tableau-log-viewer.pro | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tableau-log-viewer.pro b/src/tableau-log-viewer.pro index 3f4e0bf..5b39f8f 100644 --- a/src/tableau-log-viewer.pro +++ b/src/tableau-log-viewer.pro @@ -75,8 +75,9 @@ win32:RC_ICONS += ../resources/images/tlv.ico ICON = ../resources/images/tlv.icns CONFIG += c++17 +CONFIG += x86_64 -QMAKE_APPLE_DEVICE_ARCHS = arm64 +QMAKE_APPLE_DEVICE_ARCHS = x86_64 arm64 VERSION = 1.3.0 DEFINES += APP_VERSION=\\\"$$VERSION\\\" From 67e50f814d23624264cff3a61803789b3bad9d98 Mon Sep 17 00:00:00 2001 From: Stefan Steiner Date: Thu, 9 Oct 2025 13:55:20 -0700 Subject: [PATCH 5/5] Only create the QRegularExpression::microsecondRegex object once and reuse it. Also eliminated two unused functions --- src/microtimestamp.cpp | 82 +++++++++++++----------------------------- 1 file changed, 24 insertions(+), 58 deletions(-) diff --git a/src/microtimestamp.cpp b/src/microtimestamp.cpp index 525dfbe..8a2395e 100644 --- a/src/microtimestamp.cpp +++ b/src/microtimestamp.cpp @@ -2,6 +2,9 @@ #include #include +// Global static regex for microsecond timestamp parsing +Q_GLOBAL_STATIC(QRegularExpression, microsecondRegex, (R"(^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.(\d{6})$)")); + MicroTimestamp::MicroTimestamp() : m_microseconds(0), m_valid(false), m_hasMicros(false) { @@ -24,13 +27,12 @@ void MicroTimestamp::parseTimestamp(const QString ×tampString) m_valid = false; return; } - + // Check for microsecond format: yyyy-MM-ddTHH:mm:ss.zzzzzz (26 chars) if (timestampString.length() == 26) { // Parse the microsecond timestamp manually - QRegularExpression regex(R"(^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.(\d{6})$)"); - QRegularExpressionMatch match = regex.match(timestampString); - + QRegularExpressionMatch match = microsecondRegex()->match(timestampString); + if (match.hasMatch()) { int year = match.captured(1).toInt(); int month = match.captured(2).toInt(); @@ -39,21 +41,21 @@ void MicroTimestamp::parseTimestamp(const QString ×tampString) int minute = match.captured(5).toInt(); int second = match.captured(6).toInt(); QString microStr = match.captured(7); - + // Create QDateTime with millisecond precision int milliseconds = microStr.left(3).toInt(); m_microseconds = microStr.right(3).toInt(); // Additional microseconds - + QDate date(year, month, day); QTime time(hour, minute, second, milliseconds); m_dateTime = QDateTime(date, time); - + m_valid = m_dateTime.isValid(); m_hasMicros = true; return; } } - + // Try standard millisecond format: yyyy-MM-ddTHH:mm:ss.zzz (23 chars) if (timestampString.length() == 23) { m_dateTime = QDateTime::fromString(timestampString, "yyyy-MM-ddTHH:mm:ss.zzz"); @@ -62,14 +64,14 @@ void MicroTimestamp::parseTimestamp(const QString ×tampString) m_hasMicros = false; return; } - + // Try other common formats QStringList formats = { "yyyy-MM-ddTHH:mm:ss", "yyyy-MM-dd HH:mm:ss.zzz", "yyyy-MM-dd HH:mm:ss" }; - + for (const QString &format : formats) { m_dateTime = QDateTime::fromString(timestampString, format); if (m_dateTime.isValid()) { @@ -79,7 +81,7 @@ void MicroTimestamp::parseTimestamp(const QString ×tampString) return; } } - + m_valid = false; } @@ -88,11 +90,11 @@ bool MicroTimestamp::operator<(const MicroTimestamp &other) const if (!m_valid || !other.m_valid) { return !m_valid && other.m_valid; // Invalid timestamps sort first } - + if (m_dateTime != other.m_dateTime) { return m_dateTime < other.m_dateTime; } - + // If QDateTime is equal, compare microseconds return m_microseconds < other.m_microseconds; } @@ -117,7 +119,7 @@ bool MicroTimestamp::operator==(const MicroTimestamp &other) const if (!m_valid || !other.m_valid) { return m_valid == other.m_valid; } - + return m_dateTime == other.m_dateTime && m_microseconds == other.m_microseconds; } @@ -131,9 +133,9 @@ QString MicroTimestamp::toString(const QString &format) const if (!m_valid) { return QString(); } - + QString result = m_dateTime.toString(format); - + // If format contains microsecond placeholder and we have microseconds if (m_hasMicros && format.contains("zzzzzz")) { // Replace the millisecond part with full microsecond precision @@ -141,7 +143,7 @@ QString MicroTimestamp::toString(const QString &format) const QString microStr = QString("%1%2").arg(msStr).arg(m_microseconds, 3, 10, QChar('0')); result.replace(msStr, microStr); } - + return result; } @@ -150,17 +152,17 @@ QString MicroTimestamp::toDisplayString(bool includeDate) const if (!m_valid) { return QString(); } - + if (m_hasMicros) { // Custom formatting for microsecond display with half space separator QString dateStr = m_dateTime.date().toString("MM/dd/yyyy"); QString timeStr = m_dateTime.time().toString("hh:mm:ss"); QString millisStr = QString("%1").arg(m_dateTime.time().msec(), 3, 10, QChar('0')); QString microsStr = QString("%1").arg(m_microseconds, 3, 10, QChar('0')); - + // Use regular space to separate milliseconds from microseconds QString microStr = QString("%1 %2").arg(millisStr, microsStr); - + if (includeDate) { return QString("%1 - %2.%3").arg(dateStr, timeStr, microStr); } else { @@ -181,7 +183,7 @@ QString MicroTimestamp::toCopyString(bool includeDate) const if (!m_valid) { return QString(); } - + if (m_hasMicros) { // Format for copying without space separator QString dateStr = m_dateTime.date().toString("MM/dd/yyyy"); @@ -189,7 +191,7 @@ QString MicroTimestamp::toCopyString(bool includeDate) const QString microStr = QString("%1%2") .arg(m_dateTime.time().msec(), 3, 10, QChar('0')) .arg(m_microseconds, 3, 10, QChar('0')); - + if (includeDate) { return QString("%1 - %2.%3").arg(dateStr, timeStr, microStr); } else { @@ -241,42 +243,6 @@ void MicroTimestamp::registerMetaType() qRegisterMetaType("MicroTimestamp"); } -// QVariant conversion functions -namespace { - bool convertToMicroTimestamp(const QVariant &from, MicroTimestamp *to) - { - if (from.canConvert()) { - *to = from.value(); - return true; - } - if (from.canConvert()) { - *to = MicroTimestamp(from.toDateTime()); - return true; - } - if (from.canConvert()) { - *to = MicroTimestamp(from.toString()); - return true; - } - return false; - } - - bool convertFromMicroTimestamp(const MicroTimestamp &from, QVariant *to, int targetType) - { - switch (targetType) { - case QMetaType::QDateTime: - *to = from.toDateTime(); - return true; - case QMetaType::QString: - *to = from.toDisplayString(true); - return true; - case QMetaType::LongLong: - *to = from.toMicroSecsSinceEpoch(); - return true; - default: - return false; - } - } -} static bool microTimestampRegistered = []() { MicroTimestamp::registerMetaType();