feat: add tooltips with follow/sub durations to usercard (#6384)
This commit is contained in:
@@ -24,6 +24,7 @@
|
|||||||
- Minor: Add a setting under Moderation -> Logs to customize the timestamp used for chat logs. (#6338)
|
- Minor: Add a setting under Moderation -> Logs to customize the timestamp used for chat logs. (#6338)
|
||||||
- Minor: Add a setting under Moderation -> Logs to use server timestamp from the message instead of the local clock time for logging. (#6346)
|
- Minor: Add a setting under Moderation -> Logs to use server timestamp from the message instead of the local clock time for logging. (#6346)
|
||||||
- Minor: Add feature to search for only zero-width emotes when prepended by `:~`. (#6362)
|
- Minor: Add feature to search for only zero-width emotes when prepended by `:~`. (#6362)
|
||||||
|
- Minor: The follow and sub dates now show the duration in a tooltip. (#6384)
|
||||||
- Minor: Usercards now show a live indicator if the user is currently streaming. (#6383)
|
- Minor: Usercards now show a live indicator if the user is currently streaming. (#6383)
|
||||||
- Bugfix: Commands are no longer tab-completable in the middle of messages. (#6273)
|
- Bugfix: Commands are no longer tab-completable in the middle of messages. (#6273)
|
||||||
- Bugfix: Automatic streamer mode detection now works from Flatpak. (#6250)
|
- Bugfix: Automatic streamer mode detection now works from Flatpak. (#6250)
|
||||||
|
|||||||
+168
-5
@@ -1,5 +1,10 @@
|
|||||||
#include "util/FormatTime.hpp"
|
#include "util/FormatTime.hpp"
|
||||||
|
|
||||||
|
#include "common/QLogging.hpp"
|
||||||
|
|
||||||
|
#include <boost/date_time/gregorian/gregorian.hpp>
|
||||||
|
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
@@ -7,7 +12,9 @@ namespace chatterino {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
void appendDuration(int count, QChar &&suffix, QString &out)
|
using namespace Qt::Literals;
|
||||||
|
|
||||||
|
void appendShortDuration(int count, QChar suffix, QString &out)
|
||||||
{
|
{
|
||||||
if (!out.isEmpty())
|
if (!out.isEmpty())
|
||||||
{
|
{
|
||||||
@@ -17,6 +24,101 @@ void appendDuration(int count, QChar &&suffix, QString &out)
|
|||||||
out.append(suffix);
|
out.append(suffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::pair<uint16_t, boost::gregorian::date> yearsBetween(
|
||||||
|
boost::gregorian::date first, boost::gregorian::date second)
|
||||||
|
{
|
||||||
|
uint16_t years = 0;
|
||||||
|
boost::gregorian::date lastOk = first;
|
||||||
|
boost::gregorian::year_iterator yit(first);
|
||||||
|
++yit; // skip one year
|
||||||
|
for (; *yit <= second; ++yit)
|
||||||
|
{
|
||||||
|
years++;
|
||||||
|
lastOk = *yit;
|
||||||
|
}
|
||||||
|
return {years, lastOk};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<uint16_t, boost::gregorian::date> monthsBetween(
|
||||||
|
boost::gregorian::date first, boost::gregorian::date second)
|
||||||
|
{
|
||||||
|
uint16_t months = 0;
|
||||||
|
boost::gregorian::date lastOk = first;
|
||||||
|
boost::gregorian::month_iterator mit(first);
|
||||||
|
++mit; // skip one month
|
||||||
|
for (; *mit <= second; ++mit)
|
||||||
|
{
|
||||||
|
months++;
|
||||||
|
lastOk = *mit;
|
||||||
|
}
|
||||||
|
return {months, lastOk};
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BalancedDuration {
|
||||||
|
uint16_t years = 0;
|
||||||
|
uint16_t months = 0;
|
||||||
|
uint16_t days = 0;
|
||||||
|
uint16_t hours = 0;
|
||||||
|
uint16_t minutes = 0;
|
||||||
|
uint16_t seconds = 0;
|
||||||
|
|
||||||
|
using Component = std::pair<uint16_t BalancedDuration::*, QStringView>;
|
||||||
|
constexpr static std::array<Component, 6> COMPONENTS{
|
||||||
|
std::pair{&BalancedDuration::years, u"year"},
|
||||||
|
{&BalancedDuration::months, u"month"},
|
||||||
|
{&BalancedDuration::days, u"day"},
|
||||||
|
{&BalancedDuration::hours, u"hour"},
|
||||||
|
{&BalancedDuration::minutes, u"minute"},
|
||||||
|
{&BalancedDuration::seconds, u"second"},
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t components() const
|
||||||
|
{
|
||||||
|
uint8_t n = 0;
|
||||||
|
for (const auto &[ptr, _name] : COMPONENTS)
|
||||||
|
{
|
||||||
|
if (this->*ptr != 0)
|
||||||
|
{
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BalancedDuration durationBetween(const QDateTime &a, const QDateTime &b)
|
||||||
|
{
|
||||||
|
auto fromDT = boost::posix_time::from_time_t(a.toSecsSinceEpoch());
|
||||||
|
auto toDT = boost::posix_time::from_time_t(b.toSecsSinceEpoch());
|
||||||
|
if (fromDT > toDT)
|
||||||
|
{
|
||||||
|
std::swap(fromDT, toDT);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto fromD = fromDT.date();
|
||||||
|
auto toD = toDT.date();
|
||||||
|
|
||||||
|
if (toDT.time_of_day() < fromDT.time_of_day())
|
||||||
|
{
|
||||||
|
toD -= boost::gregorian::date_duration(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto [years, yd] = yearsBetween(fromD, toD);
|
||||||
|
auto [months, md] = monthsBetween(yd, toD);
|
||||||
|
|
||||||
|
auto rem = toDT - boost::posix_time::ptime(md, fromDT.time_of_day());
|
||||||
|
auto hoursAndDays = rem.hours();
|
||||||
|
|
||||||
|
return {
|
||||||
|
.years = years,
|
||||||
|
.months = months,
|
||||||
|
.days = static_cast<uint16_t>(hoursAndDays / 24),
|
||||||
|
.hours = static_cast<uint16_t>(hoursAndDays % 24),
|
||||||
|
.minutes = static_cast<uint16_t>(rem.minutes() % 60),
|
||||||
|
.seconds = static_cast<uint16_t>(rem.seconds() % 60),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
QString formatTime(int totalSeconds)
|
QString formatTime(int totalSeconds)
|
||||||
@@ -31,19 +133,19 @@ QString formatTime(int totalSeconds)
|
|||||||
int days = timeoutHours / 24;
|
int days = timeoutHours / 24;
|
||||||
if (days > 0)
|
if (days > 0)
|
||||||
{
|
{
|
||||||
appendDuration(days, 'd', res);
|
appendShortDuration(days, 'd', res);
|
||||||
}
|
}
|
||||||
if (hours > 0)
|
if (hours > 0)
|
||||||
{
|
{
|
||||||
appendDuration(hours, 'h', res);
|
appendShortDuration(hours, 'h', res);
|
||||||
}
|
}
|
||||||
if (minutes > 0)
|
if (minutes > 0)
|
||||||
{
|
{
|
||||||
appendDuration(minutes, 'm', res);
|
appendShortDuration(minutes, 'm', res);
|
||||||
}
|
}
|
||||||
if (seconds > 0)
|
if (seconds > 0)
|
||||||
{
|
{
|
||||||
appendDuration(seconds, 's', res);
|
appendShortDuration(seconds, 's', res);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@@ -71,4 +173,65 @@ QString formatTime(std::chrono::seconds totalSeconds)
|
|||||||
std::numeric_limits<int>::max()))));
|
std::numeric_limits<int>::max()))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString formatLongFriendlyDuration(const QDateTime &from, const QDateTime &to)
|
||||||
|
{
|
||||||
|
if (!from.isValid() || !to.isValid())
|
||||||
|
{
|
||||||
|
qCWarning(chatterinoHelper)
|
||||||
|
<< "Invalid arguments to formatLongFriendlyDuration - from:" << from
|
||||||
|
<< "to:" << to;
|
||||||
|
return u"n/a"_s;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto bd = durationBetween(from, to);
|
||||||
|
auto remaining = std::min<uint8_t>(bd.components(), 4);
|
||||||
|
const auto total = remaining;
|
||||||
|
if (remaining == 0)
|
||||||
|
{
|
||||||
|
return u"0 seconds"_s;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString out;
|
||||||
|
for (const auto &[ptr, name] : BalancedDuration::COMPONENTS)
|
||||||
|
{
|
||||||
|
uint16_t n = bd.*ptr;
|
||||||
|
if (n == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!out.isEmpty())
|
||||||
|
{
|
||||||
|
if (remaining == 1)
|
||||||
|
{
|
||||||
|
if (total > 2)
|
||||||
|
{
|
||||||
|
out += ',';
|
||||||
|
}
|
||||||
|
out += u" and ";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
out += u", ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out += QString::number(n);
|
||||||
|
out += u' ';
|
||||||
|
out += name;
|
||||||
|
if (n != 1)
|
||||||
|
{
|
||||||
|
out += u's';
|
||||||
|
}
|
||||||
|
|
||||||
|
--remaining;
|
||||||
|
if (remaining == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
@@ -11,4 +12,10 @@ QString formatTime(int totalSeconds);
|
|||||||
QString formatTime(const QString &totalSecondsString);
|
QString formatTime(const QString &totalSecondsString);
|
||||||
QString formatTime(std::chrono::seconds totalSeconds);
|
QString formatTime(std::chrono::seconds totalSeconds);
|
||||||
|
|
||||||
|
/// Formats a duration that's expected to be long (sevreal months or years) like
|
||||||
|
/// "4 years, 5 days and 8 hours".
|
||||||
|
///
|
||||||
|
/// This includes the components year, month, day, and hour.
|
||||||
|
QString formatLongFriendlyDuration(const QDateTime &from, const QDateTime &to);
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include "singletons/Theme.hpp"
|
#include "singletons/Theme.hpp"
|
||||||
#include "singletons/WindowManager.hpp"
|
#include "singletons/WindowManager.hpp"
|
||||||
#include "util/Clipboard.hpp"
|
#include "util/Clipboard.hpp"
|
||||||
|
#include "util/FormatTime.hpp"
|
||||||
#include "util/Helpers.hpp"
|
#include "util/Helpers.hpp"
|
||||||
#include "util/LayoutCreator.hpp"
|
#include "util/LayoutCreator.hpp"
|
||||||
#include "util/PostToThread.hpp"
|
#include "util/PostToThread.hpp"
|
||||||
@@ -955,6 +956,12 @@ void UserInfoPopup::updateUserData()
|
|||||||
user.displayName, this->underlyingChannel_->getName()));
|
user.displayName, this->underlyingChannel_->getName()));
|
||||||
this->ui_.createdDateLabel->setText(
|
this->ui_.createdDateLabel->setText(
|
||||||
TEXT_CREATED.arg(user.createdAt.section("T", 0, 0)));
|
TEXT_CREATED.arg(user.createdAt.section("T", 0, 0)));
|
||||||
|
this->ui_.createdDateLabel->setToolTip(
|
||||||
|
formatLongFriendlyDuration(
|
||||||
|
QDateTime::fromString(user.createdAt, Qt::ISODateWithMs),
|
||||||
|
QDateTime::currentDateTimeUtc()) +
|
||||||
|
u" ago"_s);
|
||||||
|
this->ui_.createdDateLabel->setMouseTracking(true);
|
||||||
this->ui_.userIDLabel->setText(TEXT_USER_ID % user.id);
|
this->ui_.userIDLabel->setText(TEXT_USER_ID % user.id);
|
||||||
this->ui_.userIDLabel->setProperty("copy-text", user.id);
|
this->ui_.userIDLabel->setProperty("copy-text", user.id);
|
||||||
|
|
||||||
@@ -1054,6 +1061,11 @@ void UserInfoPopup::updateUserData()
|
|||||||
followedAt.toString("yyyy-MM-dd");
|
followedAt.toString("yyyy-MM-dd");
|
||||||
this->ui_.followageLabel->setText("❤ Following since " +
|
this->ui_.followageLabel->setText("❤ Following since " +
|
||||||
followingSince);
|
followingSince);
|
||||||
|
this->ui_.followageLabel->setToolTip(
|
||||||
|
formatLongFriendlyDuration(
|
||||||
|
followedAt, QDateTime::currentDateTimeUtc()) +
|
||||||
|
u" ago"_s);
|
||||||
|
this->ui_.followageLabel->setMouseTracking(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subageInfo.isSubHidden)
|
if (subageInfo.isSubHidden)
|
||||||
|
|||||||
@@ -205,3 +205,288 @@ TEST(FormatTime, chrono)
|
|||||||
<< actual << " did not match expected value " << expected;
|
<< actual << " did not match expected value " << expected;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(FormatTime, formatLongFriendlyDuration)
|
||||||
|
{
|
||||||
|
struct Case {
|
||||||
|
QString from;
|
||||||
|
QString to;
|
||||||
|
QString dur;
|
||||||
|
};
|
||||||
|
|
||||||
|
// You can verify this with Temporal in your browser:
|
||||||
|
// function humanDuration(from, to) {
|
||||||
|
// return Temporal.Instant.from(to)
|
||||||
|
// .since(Temporal.Instant.from(from))
|
||||||
|
// .round({
|
||||||
|
// smallestUnit: "seconds",
|
||||||
|
// largestUnit: "years",
|
||||||
|
// relativeTo: Temporal.Instant.from(from).toZonedDateTimeISO("UTC"),
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// > humanDuration("2018-07-04T06:34:24Z", "2025-08-10T13:03:50Z")
|
||||||
|
// > P7Y1M6DT6H29M26S
|
||||||
|
std::array cases{
|
||||||
|
// basic
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2025-08-10T13:03:50Z",
|
||||||
|
.dur = "7 years, 1 month, 6 days, and 6 hours",
|
||||||
|
},
|
||||||
|
// eq
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2018-07-04T06:34:24Z",
|
||||||
|
.dur = "0 seconds",
|
||||||
|
},
|
||||||
|
// one
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2018-07-04T06:34:25Z",
|
||||||
|
.dur = "1 second",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2018-07-04T06:35:24Z",
|
||||||
|
.dur = "1 minute",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2018-07-04T06:35:25Z",
|
||||||
|
.dur = "1 minute and 1 second",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2018-07-04T07:34:24Z",
|
||||||
|
.dur = "1 hour",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2018-07-05T06:34:24Z",
|
||||||
|
.dur = "1 day",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2018-07-05T07:34:24Z",
|
||||||
|
.dur = "1 day and 1 hour",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2018-08-04T06:34:24Z",
|
||||||
|
.dur = "1 month",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2018-08-05T06:34:24Z",
|
||||||
|
.dur = "1 month and 1 day",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2018-08-05T07:34:24Z",
|
||||||
|
.dur = "1 month, 1 day, and 1 hour",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2018-08-04T07:34:24Z",
|
||||||
|
.dur = "1 month and 1 hour",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2019-07-04T06:34:24Z",
|
||||||
|
.dur = "1 year",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2019-07-04T07:34:24Z",
|
||||||
|
.dur = "1 year and 1 hour",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2019-07-05T06:34:24Z",
|
||||||
|
.dur = "1 year and 1 day",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2019-08-04T06:34:24Z",
|
||||||
|
.dur = "1 year and 1 month",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2019-07-05T07:34:24Z",
|
||||||
|
.dur = "1 year, 1 day, and 1 hour",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2019-08-05T06:34:24Z",
|
||||||
|
.dur = "1 year, 1 month, and 1 day",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2019-08-04T07:34:24Z",
|
||||||
|
.dur = "1 year, 1 month, and 1 hour",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2019-08-05T07:34:24Z",
|
||||||
|
.dur = "1 year, 1 month, 1 day, and 1 hour",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2019-07-05T07:35:24Z",
|
||||||
|
.dur = "1 year, 1 day, 1 hour, and 1 minute",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2019-07-04T07:35:25Z",
|
||||||
|
.dur = "1 year, 1 hour, 1 minute, and 1 second",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2018-07-04T07:35:25Z",
|
||||||
|
.dur = "1 hour, 1 minute, and 1 second",
|
||||||
|
},
|
||||||
|
// two
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2018-07-04T06:34:26Z",
|
||||||
|
.dur = "2 seconds",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2018-07-04T06:36:24Z",
|
||||||
|
.dur = "2 minutes",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2018-07-04T06:36:26Z",
|
||||||
|
.dur = "2 minutes and 2 seconds",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2018-07-04T08:34:24Z",
|
||||||
|
.dur = "2 hours",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2018-07-06T06:34:24Z",
|
||||||
|
.dur = "2 days",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2018-07-06T08:34:24Z",
|
||||||
|
.dur = "2 days and 2 hours",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2018-09-04T06:34:24Z",
|
||||||
|
.dur = "2 months",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2018-09-06T06:34:24Z",
|
||||||
|
.dur = "2 months and 2 days",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2018-09-06T08:34:24Z",
|
||||||
|
.dur = "2 months, 2 days, and 2 hours",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2020-07-04T06:34:24Z",
|
||||||
|
.dur = "2 years",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2020-09-04T06:34:24Z",
|
||||||
|
.dur = "2 years and 2 months",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2020-09-06T06:34:24Z",
|
||||||
|
.dur = "2 years, 2 months, and 2 days",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2020-09-06T08:34:24Z",
|
||||||
|
.dur = "2 years, 2 months, 2 days, and 2 hours",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2020-07-06T08:36:24Z",
|
||||||
|
.dur = "2 years, 2 days, 2 hours, and 2 minutes",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2020-07-04T08:36:26Z",
|
||||||
|
.dur = "2 years, 2 hours, 2 minutes, and 2 seconds",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2018-07-04T08:36:26Z",
|
||||||
|
.dur = "2 hours, 2 minutes, and 2 seconds",
|
||||||
|
},
|
||||||
|
// more units
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2020-09-06T08:36:26Z",
|
||||||
|
.dur = "2 years, 2 months, 2 days, and 2 hours",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T06:34:24Z",
|
||||||
|
.to = "2020-07-06T08:36:26Z",
|
||||||
|
.dur = "2 years, 2 days, 2 hours, and 2 minutes",
|
||||||
|
},
|
||||||
|
// swapped
|
||||||
|
Case{
|
||||||
|
.from = "2020-09-06T08:34:24Z",
|
||||||
|
.to = "2018-07-04T06:34:24Z",
|
||||||
|
.dur = "2 years, 2 months, 2 days, and 2 hours",
|
||||||
|
},
|
||||||
|
// lower time
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T13:34:24Z",
|
||||||
|
.to = "2025-08-04T10:03:50Z",
|
||||||
|
.dur = "7 years, 30 days, 20 hours, and 29 minutes",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T23:50:50Z",
|
||||||
|
.to = "2018-07-05T00:03:00Z",
|
||||||
|
.dur = "12 minutes and 10 seconds",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T23:50:50Z",
|
||||||
|
.to = "2018-07-05T00:00:00Z",
|
||||||
|
.dur = "9 minutes and 10 seconds",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T23:50:50Z",
|
||||||
|
.to = "2018-07-06T00:03:00Z",
|
||||||
|
.dur = "1 day, 12 minutes, and 10 seconds",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T23:50:50Z",
|
||||||
|
.to = "2018-07-07T00:03:00Z",
|
||||||
|
.dur = "2 days, 12 minutes, and 10 seconds",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T23:50:50Z",
|
||||||
|
.to = "2018-08-04T00:03:00Z",
|
||||||
|
.dur = "30 days, 12 minutes, and 10 seconds",
|
||||||
|
},
|
||||||
|
Case{
|
||||||
|
.from = "2018-07-04T23:50:50Z",
|
||||||
|
.to = "2018-09-04T00:03:00Z",
|
||||||
|
.dur = "1 month, 30 days, 12 minutes, and 10 seconds",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto &c : cases)
|
||||||
|
{
|
||||||
|
auto d = formatLongFriendlyDuration(
|
||||||
|
QDateTime::fromString(c.from, Qt::ISODate),
|
||||||
|
QDateTime::fromString(c.to, Qt::ISODate));
|
||||||
|
ASSERT_EQ(d, c.dur)
|
||||||
|
<< "from=" << c.from << " to=" << c.to << " expected=" << c.dur;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"boost-asio",
|
"boost-asio",
|
||||||
"boost-beast",
|
"boost-beast",
|
||||||
"boost-circular-buffer",
|
"boost-circular-buffer",
|
||||||
|
"boost-date-time",
|
||||||
"boost-foreach",
|
"boost-foreach",
|
||||||
"boost-interprocess",
|
"boost-interprocess",
|
||||||
"boost-json",
|
"boost-json",
|
||||||
|
|||||||
Reference in New Issue
Block a user