Skip to content

Commit

Permalink
Better fix for MythDate::formatTime to handle overflows.
Browse files Browse the repository at this point in the history
Fixes #520.
  • Loading branch information
linuxdude42 committed Mar 16, 2022
1 parent 2d7a09e commit 65b9c73
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 20 deletions.
60 changes: 47 additions & 13 deletions mythtv/libs/libmythbase/mythdate.cpp
@@ -1,5 +1,6 @@
#include <QtGlobal>
#include <QCoreApplication>
#include <QRegularExpression>

#include "mythcorecontext.h"
#include "mythdate.h"
Expand Down Expand Up @@ -209,7 +210,11 @@ std::chrono::seconds secsInFuture (const QDateTime& future)
/**
* \brief Format a milliseconds time value
*
* Convert a millisecond time value into a textual representation of the value.
* Convert a millisecond time value into a textual representation of
* the value. QTime can't handle overflow of any of the fields, so the
* formatting needs to be done manually. Think a music playlist of
* more than 24 hours, or a single song of more than 60 minutes
* (e.g. a podcast or something like that).
*
* \param msecs The time value in milliseconds. Since the type of this
* field is std::chrono::duration, any duration of a larger
Expand All @@ -222,18 +227,47 @@ std::chrono::seconds secsInFuture (const QDateTime& future)
*/
QString formatTime(std::chrono::milliseconds msecs, QString fmt)
{
int count = 0;

// QTime can't handle times of more than 24 hours. Do that part
// of the formatting manually, and then let QTime::toString handle
// the rest of the formatting.
while (fmt[count] == QLatin1Char('H'))
count++;
fmt.remove(0, count);

QString result = QString("%1").arg(msecs / 1h, count,10,QLatin1Char('0'));
msecs = msecs % 1h;
return result + QTime::fromMSecsSinceStartOfDay(msecs.count()).toString(fmt);
static const QRegularExpression hRe("H+");
static const QRegularExpression mRe("m+");
static const QRegularExpression sRe("s+");
static const QRegularExpression zRe("z+");

QRegularExpressionMatch match = hRe.match(fmt);
if (match.hasMatch())
{
int width = match.capturedLength();
QString text = QString("%1").arg(msecs / 1h, width,10,QLatin1Char('0'));
fmt.replace(match.capturedStart(), width, text);
msecs = msecs % 1h;
}

match = mRe.match(fmt);
if (match.hasMatch())
{
int width = match.capturedLength();
QString text = QString("%1").arg(msecs / 1min, width,10,QLatin1Char('0'));
fmt.replace(match.capturedStart(), width, text);
msecs = msecs % 1min;
}

match = sRe.match(fmt);
if (match.hasMatch())
{
int width = match.capturedLength();
QString text = QString("%1").arg(msecs / 1s, width,10,QLatin1Char('0'));
fmt.replace(match.capturedStart(), width, text);
}

match = zRe.match(fmt);
if (match.hasMatch())
{
static constexpr std::array<int,4> divisor = {1000, 100, 10, 1};
int width = std::min(3, static_cast<int>(match.capturedLength()));
int value = (msecs % 1s).count() / divisor[width];
QString text = QString("%1").arg(value, width,10,QLatin1Char('0'));
fmt.replace(match.capturedStart(), match.capturedLength(), text);
}
return fmt;
}

}; // namespace MythDate
4 changes: 1 addition & 3 deletions mythtv/libs/libmythbase/mythdate.h
@@ -1,13 +1,11 @@
#ifndef MYTH_DATE_H
#define MYTH_DATE_H

#include <chrono>
using namespace std::chrono_literals;

#include <QDateTime>
#include <QString>

#include "mythbaseexp.h"
#include "mythchrono.h"

namespace MythDate
{
Expand Down
1 change: 1 addition & 0 deletions mythtv/libs/libmythbase/test/test_mythdate/.gitignore
@@ -0,0 +1 @@
test_mythdate
59 changes: 59 additions & 0 deletions mythtv/libs/libmythbase/test/test_mythdate/test_mythdate.cpp
@@ -0,0 +1,59 @@
/*
* Class TestMythDate
*
* Copyright (c) David Hampton 2022
*
* 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
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "test_mythdate.h"

void TestMythDate::formatting_data(void)
{
QTest::addColumn<std::chrono::milliseconds>("msecs");
QTest::addColumn<QString>("fmt");
QTest::addColumn<QString>("expected");

QTest::newRow( "m:ss") << 192876ms << "m:ss" << "3:12";
QTest::newRow( "mm:ss") << 192876ms << "mm:ss" << "03:12";
QTest::newRow( "mm:ss oflow") << 17112876ms << "mm:ss" << "285:12";
QTest::newRow(" H:mm:ss") << 17112876ms << "H:mm:ss" << "4:45:12";
QTest::newRow("HH:mm:ss") << 17112876ms << "HH:mm:ss" << "04:45:12";
QTest::newRow(" H:mm:ss oflow") << 1169112876ms << "H:mm:ss" << "324:45:12";
QTest::newRow("HH:mm:ss oflow") << 1169112876ms << "HH:mm:ss" << "324:45:12";

QTest::newRow("HH:mm:ss.") << 17112876ms << "HH:mm:ss." << "04:45:12.";
QTest::newRow("HH:mm:ss.z") << 17112876ms << "HH:mm:ss.z" << "04:45:12.8";
QTest::newRow("HH:mm:ss.zz") << 17112876ms << "HH:mm:ss.zz" << "04:45:12.87";
QTest::newRow("HH:mm:ss.zzz") << 17112876ms << "HH:mm:ss.zzz" << "04:45:12.876";
QTest::newRow("HH:mm:ss.000") << 17112000ms << "HH:mm:ss.zzz" << "04:45:12.000";
QTest::newRow("HH:mm:ss.05") << 17112050ms << "HH:mm:ss.zz" << "04:45:12.05";

// Precison maxes out at 3. This is a millisecond function.
QTest::newRow("HH:mm:ss.zzzzzz")<< 17112876ms << "HH:mm:ss.zzzzzz" << "04:45:12.876";
QTest::newRow("silly overflow") << 1169112876ms << "HHHHHHHH:mm:ss.z" << "00000324:45:12.8";
QTest::newRow("random chars") << 1169112876ms << "!HH@mm#ss%zzzzzz" << "!324@45#12%876";
}

void TestMythDate::formatting(void)
{
QFETCH(std::chrono::milliseconds, msecs);
QFETCH(QString, fmt);
QFETCH(QString, expected);

QString actual = MythDate::formatTime(std::chrono::milliseconds(msecs), fmt);
QCOMPARE(actual, expected);
}

QTEST_APPLESS_MAIN(TestMythDate)
33 changes: 33 additions & 0 deletions mythtv/libs/libmythbase/test/test_mythdate/test_mythdate.h
@@ -0,0 +1,33 @@
/*
* Class TestMythDate
*
* Copyright (c) David Hampton 2022
*
* 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
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include <QtTest/QtTest>
#include <iostream>

#include "mythdate.h"

class TestMythDate : public QObject
{
Q_OBJECT

private slots:
static void formatting_data(void);
static void formatting(void);
};
20 changes: 20 additions & 0 deletions mythtv/libs/libmythbase/test/test_mythdate/test_mythdate.pro
@@ -0,0 +1,20 @@
include ( ../../../../settings.pro )
include ( ../../../../test.pro )

QT += testlib

TEMPLATE = app
TARGET = test_mythdate
DEPENDPATH += . ../..
INCLUDEPATH += . ../..
LIBS += -L../.. -lmythbase-$$LIBVERSION
LIBS += -Wl,$$_RPATH_$${PWD}/../..

# Input
HEADERS += test_mythdate.h
SOURCES += test_mythdate.cpp

QMAKE_CLEAN += $(TARGET)
QMAKE_CLEAN += ; ( cd $(OBJECTS_DIR) && rm -f *.gcov *.gcda *.gcno )

LIBS += $$EXTRA_LIBS $$LATE_LIBS
3 changes: 1 addition & 2 deletions mythtv/libs/libmythmetadata/lyricsdata.h
Expand Up @@ -38,8 +38,7 @@ class META_PUBLIC LyricsLine
private:
QString formatTime(void) const
{
QString timestr = MythDate::formatTime(m_time,"mm:ss.zzz");
timestr.chop(1); // Chop 1 to return hundredths
QString timestr = MythDate::formatTime(m_time,"mm:ss.zz");
return QString("[%1]").arg(timestr);
}
};
Expand Down
3 changes: 1 addition & 2 deletions mythtv/libs/libmythtv/Bluray/mythbdbuffer.cpp
Expand Up @@ -718,8 +718,7 @@ bool MythBDBuffer::UpdateTitleInfo(void)
m_titlesize = bd_get_title_size(m_bdnav);
uint32_t chapter_count = GetNumChapters();
auto total_msecs = duration_cast<std::chrono::milliseconds>(m_currentTitleLength);
auto duration = MythDate::formatTime(total_msecs, "HH:mm:ss.zzz");
duration.chop(2); // Chop 2 to show tenths
auto duration = MythDate::formatTime(total_msecs, "HH:mm:ss.z");
LOG(VB_GENERAL, LOG_INFO, LOC + QString("New title info: Index %1 Playlist: %2 Duration: %3 ""Chapters: %5")
.arg(m_currentTitle).arg(m_currentTitleInfo->playlist).arg(duration).arg(chapter_count));
LOG(VB_GENERAL, LOG_INFO, LOC + QString("New title info: Clips: %1 Angles: %2 Title Size: %3 Frame Rate %4")
Expand Down

1 comment on commit 65b9c73

@acediac
Copy link

Choose a reason for hiding this comment

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

mythdate.cpp:264:
static constexpr std::array<int,4> divisor = {1000, 100, 10, 1};
is failing to compile with
error: constexpr variable cannot have non-literal type 'const std::array<int, 4>'
(llvm/clang 13.0.1 MacOS 12.1)

Solved by adding
#include <array>
at the top of the file or in mythdate.h

Please sign in to comment.