File indexing completed on 2024-05-19 03:56:24

0001 /*
0002     This file is part of the KDE Frameworks
0003 
0004     SPDX-FileCopyrightText: 2013 Alex Merry <alex.merry@kdemail.net>
0005     SPDX-FileCopyrightText: 2013 John Layt <jlayt@kde.org>
0006     SPDX-FileCopyrightText: 2010 Michael Leupold <lemma@confuego.org>
0007     SPDX-FileCopyrightText: 2009 Michael Pyne <mpyne@kde.org>
0008     SPDX-FileCopyrightText: 2008 Albert Astals Cid <aacid@kde.org>
0009 
0010     SPDX-License-Identifier: LGPL-2.0-or-later
0011 */
0012 
0013 #include "kformatprivate_p.h"
0014 
0015 #include <QDateTime>
0016 #include <QSettings>
0017 #include <QStandardPaths>
0018 
0019 #include <math.h>
0020 
0021 KFormatPrivate::KFormatPrivate(const QLocale &locale)
0022     : m_locale(locale)
0023 {
0024 }
0025 
0026 KFormatPrivate::~KFormatPrivate()
0027 {
0028 }
0029 
0030 constexpr double bpow(int exp)
0031 {
0032     return (exp > 0) ? 2.0 * bpow(exp - 1) : (exp < 0) ? 0.5 * bpow(exp + 1) : 1.0;
0033 }
0034 
0035 QString KFormatPrivate::formatValue(double value,
0036                                     KFormat::Unit unit,
0037                                     QString unitString,
0038                                     int precision,
0039                                     KFormat::UnitPrefix prefix,
0040                                     KFormat::BinaryUnitDialect dialect) const
0041 {
0042     if (dialect <= KFormat::DefaultBinaryDialect || dialect > KFormat::LastBinaryDialect) {
0043         dialect = KFormat::IECBinaryDialect;
0044     }
0045 
0046     if (static_cast<int>(prefix) < static_cast<int>(KFormat::UnitPrefix::Yocto) || static_cast<int>(prefix) > static_cast<int>(KFormat::UnitPrefix::Yotta)) {
0047         prefix = KFormat::UnitPrefix::AutoAdjust;
0048     }
0049 
0050     double multiplier = 1024.0;
0051     if (dialect == KFormat::MetricBinaryDialect) {
0052         multiplier = 1000.0;
0053     }
0054 
0055     if (prefix == KFormat::UnitPrefix::AutoAdjust) {
0056         int power = 0;
0057         double adjustValue = qAbs(value);
0058         while (adjustValue >= multiplier) {
0059             adjustValue /= multiplier;
0060             power += 1;
0061         }
0062         while (adjustValue && adjustValue < 1.0) {
0063             adjustValue *= multiplier;
0064             power -= 1;
0065         }
0066         const KFormat::UnitPrefix map[] = {
0067             KFormat::UnitPrefix::Yocto, // -8
0068             KFormat::UnitPrefix::Zepto,
0069             KFormat::UnitPrefix::Atto,
0070             KFormat::UnitPrefix::Femto,
0071             KFormat::UnitPrefix::Pico,
0072             KFormat::UnitPrefix::Nano,
0073             KFormat::UnitPrefix::Micro,
0074             KFormat::UnitPrefix::Milli,
0075             KFormat::UnitPrefix::Unity, // 0
0076             KFormat::UnitPrefix::Kilo,
0077             KFormat::UnitPrefix::Mega,
0078             KFormat::UnitPrefix::Giga,
0079             KFormat::UnitPrefix::Tera,
0080             KFormat::UnitPrefix::Peta,
0081             KFormat::UnitPrefix::Exa,
0082             KFormat::UnitPrefix::Zetta,
0083             KFormat::UnitPrefix::Yotta, // 8
0084         };
0085         power = std::max(-8, std::min(8, power));
0086         prefix = map[power + 8];
0087     }
0088 
0089     if (prefix == KFormat::UnitPrefix::Unity && unit == KFormat::Unit::Byte) {
0090         precision = 0;
0091     }
0092 
0093     struct PrefixMapEntry {
0094         KFormat::UnitPrefix prefix;
0095         double decimalFactor;
0096         double binaryFactor;
0097         QString prefixCharSI;
0098         QString prefixCharIEC;
0099     };
0100 
0101     const PrefixMapEntry map[] = {
0102         {KFormat::UnitPrefix::Yocto, 1e-24, bpow(-80), tr("y", "SI prefix for 10^⁻24"), QString()},
0103         {KFormat::UnitPrefix::Zepto, 1e-21, bpow(-70), tr("z", "SI prefix for 10^⁻21"), QString()},
0104         {KFormat::UnitPrefix::Atto, 1e-18, bpow(-60), tr("a", "SI prefix for 10^⁻18"), QString()},
0105         {KFormat::UnitPrefix::Femto, 1e-15, bpow(-50), tr("f", "SI prefix for 10^⁻15"), QString()},
0106         {KFormat::UnitPrefix::Pico, 1e-12, bpow(-40), tr("p", "SI prefix for 10^⁻12"), QString()},
0107         {KFormat::UnitPrefix::Nano, 1e-9, bpow(-30), tr("n", "SI prefix for 10^⁻9"), QString()},
0108         {KFormat::UnitPrefix::Micro, 1e-6, bpow(-20), tr("µ", "SI prefix for 10^⁻6"), QString()},
0109         {KFormat::UnitPrefix::Milli, 1e-3, bpow(-10), tr("m", "SI prefix for 10^⁻3"), QString()},
0110         {KFormat::UnitPrefix::Unity, 1.0, 1.0, QString(), QString()},
0111         {KFormat::UnitPrefix::Kilo, 1e3, bpow(10), tr("k", "SI prefix for 10^3"), tr("Ki", "IEC binary prefix for 2^10")},
0112         {KFormat::UnitPrefix::Mega, 1e6, bpow(20), tr("M", "SI prefix for 10^6"), tr("Mi", "IEC binary prefix for 2^20")},
0113         {KFormat::UnitPrefix::Giga, 1e9, bpow(30), tr("G", "SI prefix for 10^9"), tr("Gi", "IEC binary prefix for 2^30")},
0114         {KFormat::UnitPrefix::Tera, 1e12, bpow(40), tr("T", "SI prefix for 10^12"), tr("Ti", "IEC binary prefix for 2^40")},
0115         {KFormat::UnitPrefix::Peta, 1e15, bpow(50), tr("P", "SI prefix for 10^15"), tr("Pi", "IEC binary prefix for 2^50")},
0116         {KFormat::UnitPrefix::Exa, 1e18, bpow(60), tr("E", "SI prefix for 10^18"), tr("Ei", "IEC binary prefix for 2^60")},
0117         {KFormat::UnitPrefix::Zetta, 1e21, bpow(70), tr("Z", "SI prefix for 10^21"), tr("Zi", "IEC binary prefix for 2^70")},
0118         {KFormat::UnitPrefix::Yotta, 1e24, bpow(80), tr("Y", "SI prefix for 10^24"), tr("Yi", "IEC binary prefix for 2^80")},
0119     };
0120 
0121     auto entry = std::find_if(std::begin(map), std::end(map), [prefix](const PrefixMapEntry &e) {
0122         return e.prefix == prefix;
0123     });
0124 
0125     switch (unit) {
0126     case KFormat::Unit::Bit:
0127         unitString = tr("bit", "Symbol of binary digit");
0128         break;
0129     case KFormat::Unit::Byte:
0130         unitString = tr("B", "Symbol of byte");
0131         break;
0132     case KFormat::Unit::Meter:
0133         unitString = tr("m", "Symbol of meter");
0134         break;
0135     case KFormat::Unit::Hertz:
0136         unitString = tr("Hz", "Symbol of hertz");
0137         break;
0138     case KFormat::Unit::Other:
0139         break;
0140     }
0141 
0142     if (prefix == KFormat::UnitPrefix::Unity) {
0143         QString numString = m_locale.toString(value, 'f', precision);
0144         //: value without prefix, format "<val> <unit>"
0145         return tr("%1 %2", "no Prefix").arg(numString, unitString);
0146     }
0147 
0148     QString prefixString;
0149     if (dialect == KFormat::MetricBinaryDialect) {
0150         value /= entry->decimalFactor;
0151         prefixString = entry->prefixCharSI;
0152     } else {
0153         value /= entry->binaryFactor;
0154         if (dialect == KFormat::IECBinaryDialect) {
0155             prefixString = entry->prefixCharIEC;
0156         } else {
0157             prefixString = entry->prefixCharSI.toUpper();
0158         }
0159     }
0160 
0161     QString numString = m_locale.toString(value, 'f', precision);
0162 
0163     //: value with prefix, format "<val> <prefix><unit>"
0164     return tr("%1 %2%3", "MetricBinaryDialect").arg(numString, prefixString, unitString);
0165 }
0166 
0167 QString KFormatPrivate::formatByteSize(double size, int precision, KFormat::BinaryUnitDialect dialect, KFormat::BinarySizeUnits units) const
0168 {
0169     // Current KDE default is IECBinaryDialect
0170     const auto fallbackDialect = KFormat::IECBinaryDialect;
0171 
0172     if (dialect <= KFormat::DefaultBinaryDialect || dialect > KFormat::LastBinaryDialect) {
0173         const auto kdeglobals = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QStringLiteral("kdeglobals"));
0174         QSettings settings(kdeglobals, QSettings::IniFormat);
0175         dialect = static_cast<KFormat::BinaryUnitDialect>(settings.value("Locale/BinaryUnitDialect", fallbackDialect).toInt());
0176     }
0177 
0178     // Current KDE default is to auto-adjust so the size falls in the range 0 to 1000/1024
0179     if (units < KFormat::DefaultBinaryUnits || units > KFormat::UnitLastUnit) {
0180         units = KFormat::DefaultBinaryUnits;
0181     }
0182 
0183     int unit = 0; // Selects what unit to use
0184     double multiplier = 1024.0;
0185 
0186     if (dialect == KFormat::MetricBinaryDialect) {
0187         multiplier = 1000.0;
0188     }
0189 
0190     // If a specific unit conversion is given, use it directly.  Otherwise
0191     // search until the result is in [0, multiplier] (or out of our range).
0192     if (units == KFormat::DefaultBinaryUnits) {
0193         while (qAbs(size) >= multiplier && unit < int(KFormat::UnitYottaByte)) {
0194             size /= multiplier;
0195             ++unit;
0196         }
0197     } else {
0198         // A specific unit is in use
0199         unit = static_cast<int>(units);
0200         if (unit > 0) {
0201             size /= pow(multiplier, unit);
0202         }
0203     }
0204 
0205     // Bytes, no rounding
0206     if (unit == 0) {
0207         precision = 0;
0208     }
0209 
0210     QString numString = m_locale.toString(size, 'f', precision);
0211 
0212     // Do not remove "//:" comments below, they are used by the translators.
0213     // NB: we cannot pass pluralization arguments, as the size may be negative
0214     if (dialect == KFormat::MetricBinaryDialect) {
0215         switch (unit) {
0216         case KFormat::UnitByte:
0217             //: MetricBinaryDialect size in bytes
0218             return tr("%1 B", "MetricBinaryDialect").arg(numString);
0219         case KFormat::UnitKiloByte:
0220             //: MetricBinaryDialect size in 1000 bytes
0221             return tr("%1 kB", "MetricBinaryDialect").arg(numString);
0222         case KFormat::UnitMegaByte:
0223             //: MetricBinaryDialect size in 10^6 bytes
0224             return tr("%1 MB", "MetricBinaryDialect").arg(numString);
0225         case KFormat::UnitGigaByte:
0226             //: MetricBinaryDialect size in 10^9 bytes
0227             return tr("%1 GB", "MetricBinaryDialect").arg(numString);
0228         case KFormat::UnitTeraByte:
0229             //: MetricBinaryDialect size in 10^12 bytes
0230             return tr("%1 TB", "MetricBinaryDialect").arg(numString);
0231         case KFormat::UnitPetaByte:
0232             //: MetricBinaryDialect size in 10^15 bytes
0233             return tr("%1 PB", "MetricBinaryDialect").arg(numString);
0234         case KFormat::UnitExaByte:
0235             //: MetricBinaryDialect size in 10^18 byte
0236             return tr("%1 EB", "MetricBinaryDialect").arg(numString);
0237         case KFormat::UnitZettaByte:
0238             //: MetricBinaryDialect size in 10^21 bytes
0239             return tr("%1 ZB", "MetricBinaryDialect").arg(numString);
0240         case KFormat::UnitYottaByte:
0241             //: MetricBinaryDialect size in 10^24 bytes
0242             return tr("%1 YB", "MetricBinaryDialect").arg(numString);
0243         }
0244     } else if (dialect == KFormat::JEDECBinaryDialect) {
0245         switch (unit) {
0246         case KFormat::UnitByte:
0247             //: JEDECBinaryDialect memory size in bytes
0248             return tr("%1 B", "JEDECBinaryDialect").arg(numString);
0249         case KFormat::UnitKiloByte:
0250             //: JEDECBinaryDialect memory size in 1024 bytes
0251             return tr("%1 KB", "JEDECBinaryDialect").arg(numString);
0252         case KFormat::UnitMegaByte:
0253             //: JEDECBinaryDialect memory size in 10^20 bytes
0254             return tr("%1 MB", "JEDECBinaryDialect").arg(numString);
0255         case KFormat::UnitGigaByte:
0256             //: JEDECBinaryDialect memory size in 10^30 bytes
0257             return tr("%1 GB", "JEDECBinaryDialect").arg(numString);
0258         case KFormat::UnitTeraByte:
0259             //: JEDECBinaryDialect memory size in 10^40 bytes
0260             return tr("%1 TB", "JEDECBinaryDialect").arg(numString);
0261         case KFormat::UnitPetaByte:
0262             //: JEDECBinaryDialect memory size in 10^50 bytes
0263             return tr("%1 PB", "JEDECBinaryDialect").arg(numString);
0264         case KFormat::UnitExaByte:
0265             //: JEDECBinaryDialect memory size in 10^60 bytes
0266             return tr("%1 EB", "JEDECBinaryDialect").arg(numString);
0267         case KFormat::UnitZettaByte:
0268             //: JEDECBinaryDialect memory size in 10^70 bytes
0269             return tr("%1 ZB", "JEDECBinaryDialect").arg(numString);
0270         case KFormat::UnitYottaByte:
0271             //: JEDECBinaryDialect memory size in 10^80 bytes
0272             return tr("%1 YB", "JEDECBinaryDialect").arg(numString);
0273         }
0274     } else { // KFormat::IECBinaryDialect, KFormat::DefaultBinaryDialect
0275         switch (unit) {
0276         case KFormat::UnitByte:
0277             //: IECBinaryDialect size in bytes
0278             return tr("%1 B", "IECBinaryDialect").arg(numString);
0279         case KFormat::UnitKiloByte:
0280             //: IECBinaryDialect size in 1024 bytes
0281             return tr("%1 KiB", "IECBinaryDialect").arg(numString);
0282         case KFormat::UnitMegaByte:
0283             //: IECBinaryDialect size in 10^20 bytes
0284             return tr("%1 MiB", "IECBinaryDialect").arg(numString);
0285         case KFormat::UnitGigaByte:
0286             //: IECBinaryDialect size in 10^30 bytes
0287             return tr("%1 GiB", "IECBinaryDialect").arg(numString);
0288         case KFormat::UnitTeraByte:
0289             //: IECBinaryDialect size in 10^40 bytes
0290             return tr("%1 TiB", "IECBinaryDialect").arg(numString);
0291         case KFormat::UnitPetaByte:
0292             //: IECBinaryDialect size in 10^50 bytes
0293             return tr("%1 PiB", "IECBinaryDialect").arg(numString);
0294         case KFormat::UnitExaByte:
0295             //: IECBinaryDialect size in 10^60 bytes
0296             return tr("%1 EiB", "IECBinaryDialect").arg(numString);
0297         case KFormat::UnitZettaByte:
0298             //: IECBinaryDialect size in 10^70 bytes
0299             return tr("%1 ZiB", "IECBinaryDialect").arg(numString);
0300         case KFormat::UnitYottaByte:
0301             //: IECBinaryDialect size in 10^80 bytes
0302             return tr("%1 YiB", "IECBinaryDialect").arg(numString);
0303         }
0304     }
0305 
0306     // Should never reach here
0307     Q_ASSERT(false);
0308     return numString;
0309 }
0310 
0311 enum TimeConstants {
0312     MSecsInDay = 86400000,
0313     MSecsInHour = 3600000,
0314     MSecsInMinute = 60000,
0315     MSecsInSecond = 1000,
0316 };
0317 
0318 QString KFormatPrivate::formatDuration(quint64 msecs, KFormat::DurationFormatOptions options) const
0319 {
0320     quint64 ms = msecs;
0321     if (options & KFormat::HideSeconds) {
0322         // round to nearest minute
0323         ms = qRound64(ms / (qreal)MSecsInMinute) * MSecsInMinute;
0324     } else if (!(options & KFormat::ShowMilliseconds)) {
0325         // round to nearest second
0326         ms = qRound64(ms / (qreal)MSecsInSecond) * MSecsInSecond;
0327     }
0328 
0329     int hours = ms / MSecsInHour;
0330     ms = ms % MSecsInHour;
0331     int minutes = ms / MSecsInMinute;
0332     ms = ms % MSecsInMinute;
0333     int seconds = ms / MSecsInSecond;
0334     ms = ms % MSecsInSecond;
0335 
0336     if ((options & KFormat::InitialDuration) == KFormat::InitialDuration) {
0337         if ((options & KFormat::FoldHours) == KFormat::FoldHours && (options & KFormat::ShowMilliseconds) == KFormat::ShowMilliseconds) {
0338             //: @item:intext Duration format minutes, seconds and milliseconds
0339             return tr("%1m%2.%3s").arg(hours * 60 + minutes, 1, 10, QLatin1Char('0')).arg(seconds, 2, 10, QLatin1Char('0')).arg(ms, 3, 10, QLatin1Char('0'));
0340         } else if ((options & KFormat::FoldHours) == KFormat::FoldHours) {
0341             //: @item:intext Duration format minutes and seconds
0342             return tr("%1m%2s").arg(hours * 60 + minutes, 1, 10, QLatin1Char('0')).arg(seconds, 2, 10, QLatin1Char('0'));
0343         } else if ((options & KFormat::HideSeconds) == KFormat::HideSeconds) {
0344             //: @item:intext Duration format hours and minutes
0345             return tr("%1h%2m").arg(hours, 1, 10, QLatin1Char('0')).arg(minutes, 2, 10, QLatin1Char('0'));
0346         } else if ((options & KFormat::ShowMilliseconds) == KFormat::ShowMilliseconds) {
0347             //: @item:intext Duration format hours, minutes, seconds, milliseconds
0348             return tr("%1h%2m%3.%4s")
0349                 .arg(hours, 1, 10, QLatin1Char('0'))
0350                 .arg(minutes, 2, 10, QLatin1Char('0'))
0351                 .arg(seconds, 2, 10, QLatin1Char('0'))
0352                 .arg(ms, 3, 10, QLatin1Char('0'));
0353         } else { // Default
0354             //: @item:intext Duration format hours, minutes, seconds
0355             return tr("%1h%2m%3s").arg(hours, 1, 10, QLatin1Char('0')).arg(minutes, 2, 10, QLatin1Char('0')).arg(seconds, 2, 10, QLatin1Char('0'));
0356         }
0357 
0358     } else {
0359         if ((options & KFormat::FoldHours) == KFormat::FoldHours && (options & KFormat::ShowMilliseconds) == KFormat::ShowMilliseconds) {
0360             //: @item:intext Duration format minutes, seconds and milliseconds
0361             return tr("%1:%2.%3").arg(hours * 60 + minutes, 1, 10, QLatin1Char('0')).arg(seconds, 2, 10, QLatin1Char('0')).arg(ms, 3, 10, QLatin1Char('0'));
0362         } else if ((options & KFormat::FoldHours) == KFormat::FoldHours) {
0363             //: @item:intext Duration format minutes and seconds
0364             return tr("%1:%2").arg(hours * 60 + minutes, 1, 10, QLatin1Char('0')).arg(seconds, 2, 10, QLatin1Char('0'));
0365         } else if ((options & KFormat::HideSeconds) == KFormat::HideSeconds) {
0366             //: @item:intext Duration format hours and minutes
0367             return tr("%1:%2").arg(hours, 1, 10, QLatin1Char('0')).arg(minutes, 2, 10, QLatin1Char('0'));
0368         } else if ((options & KFormat::ShowMilliseconds) == KFormat::ShowMilliseconds) {
0369             //: @item:intext Duration format hours, minutes, seconds, milliseconds
0370             return tr("%1:%2:%3.%4")
0371                 .arg(hours, 1, 10, QLatin1Char('0'))
0372                 .arg(minutes, 2, 10, QLatin1Char('0'))
0373                 .arg(seconds, 2, 10, QLatin1Char('0'))
0374                 .arg(ms, 3, 10, QLatin1Char('0'));
0375         } else { // Default
0376             //: @item:intext Duration format hours, minutes, seconds
0377             return tr("%1:%2:%3").arg(hours, 1, 10, QLatin1Char('0')).arg(minutes, 2, 10, QLatin1Char('0')).arg(seconds, 2, 10, QLatin1Char('0'));
0378         }
0379     }
0380 
0381     Q_UNREACHABLE();
0382     return QString();
0383 }
0384 
0385 QString KFormatPrivate::formatDecimalDuration(quint64 msecs, int decimalPlaces) const
0386 {
0387     if (msecs >= MSecsInDay) {
0388         //: @item:intext %1 is a real number, e.g. 1.23 days
0389         return tr("%1 days").arg(m_locale.toString(msecs / (MSecsInDay * 1.0), 'f', decimalPlaces));
0390     } else if (msecs >= MSecsInHour) {
0391         //: @item:intext %1 is a real number, e.g. 1.23 hours
0392         return tr("%1 hours").arg(m_locale.toString(msecs / (MSecsInHour * 1.0), 'f', decimalPlaces));
0393     } else if (msecs >= MSecsInMinute) {
0394         //: @item:intext %1 is a real number, e.g. 1.23 minutes
0395         return tr("%1 minutes").arg(m_locale.toString(msecs / (MSecsInMinute * 1.0), 'f', decimalPlaces));
0396     } else if (msecs >= MSecsInSecond) {
0397         //: @item:intext %1 is a real number, e.g. 1.23 seconds
0398         return tr("%1 seconds").arg(m_locale.toString(msecs / (MSecsInSecond * 1.0), 'f', decimalPlaces));
0399     }
0400     //: @item:intext %1 is a whole number
0401     //~ singular %n millisecond
0402     //~ plural %n milliseconds
0403     return tr("%n millisecond(s)", nullptr, msecs);
0404 }
0405 
0406 enum DurationUnits {
0407     Days = 0,
0408     Hours,
0409     Minutes,
0410     Seconds,
0411 };
0412 
0413 static QString formatSingleDuration(DurationUnits units, int n)
0414 {
0415     // NB: n is guaranteed to be non-negative
0416     switch (units) {
0417     case Days:
0418         //: @item:intext %n is a whole number
0419         //~ singular %n day
0420         //~ plural %n days
0421         return KFormatPrivate::tr("%n day(s)", nullptr, n);
0422     case Hours:
0423         //: @item:intext %n is a whole number
0424         //~ singular %n hour
0425         //~ plural %n hours
0426         return KFormatPrivate::tr("%n hour(s)", nullptr, n);
0427     case Minutes:
0428         //: @item:intext %n is a whole number
0429         //~ singular %n minute
0430         //~ plural %n minutes
0431         return KFormatPrivate::tr("%n minute(s)", nullptr, n);
0432     case Seconds:
0433         //: @item:intext %n is a whole number
0434         //~ singular %n second
0435         //~ plural %n seconds
0436         return KFormatPrivate::tr("%n second(s)", nullptr, n);
0437     }
0438     Q_ASSERT(false);
0439     return QString();
0440 }
0441 
0442 QString KFormatPrivate::formatSpelloutDuration(quint64 msecs) const
0443 {
0444     quint64 ms = msecs;
0445     int days = ms / MSecsInDay;
0446     ms = ms % (MSecsInDay);
0447     int hours = ms / MSecsInHour;
0448     ms = ms % MSecsInHour;
0449     int minutes = ms / MSecsInMinute;
0450     ms = ms % MSecsInMinute;
0451     int seconds = qRound(ms / 1000.0);
0452 
0453     // Handle correctly problematic case #1 (look at KFormatTest::prettyFormatDuration())
0454     if (seconds == 60) {
0455         return formatSpelloutDuration(msecs - ms + MSecsInMinute);
0456     }
0457 
0458     if (days && hours) {
0459         /*: @item:intext days and hours. This uses the previous item:intext messages.
0460             If this does not fit the grammar of your language please contact the i18n team to solve the problem */
0461         return tr("%1 and %2").arg(formatSingleDuration(Days, days), formatSingleDuration(Hours, hours));
0462     } else if (days) {
0463         return formatSingleDuration(Days, days);
0464     } else if (hours && minutes) {
0465         /*: @item:intext hours and minutes. This uses the previous item:intext messages.
0466             If this does not fit the grammar of your language please contact the i18n team to solve the problem */
0467         return tr("%1 and %2").arg(formatSingleDuration(Hours, hours), formatSingleDuration(Minutes, minutes));
0468     } else if (hours) {
0469         return formatSingleDuration(Hours, hours);
0470     } else if (minutes && seconds) {
0471         /*: @item:intext minutes and seconds. This uses the previous item:intext messages.
0472             If this does not fit the grammar of your language please contact the i18n team to solve the problem */
0473         return tr("%1 and %2").arg(formatSingleDuration(Minutes, minutes), formatSingleDuration(Seconds, seconds));
0474     } else if (minutes) {
0475         return formatSingleDuration(Minutes, minutes);
0476     } else {
0477         return formatSingleDuration(Seconds, seconds);
0478     }
0479 }
0480 
0481 QString KFormatPrivate::formatRelativeDate(const QDate &date, QLocale::FormatType format) const
0482 {
0483     if (!date.isValid()) {
0484         return tr("Invalid date", "used when a relative date string can't be generated because the date is invalid");
0485     }
0486 
0487     const qint64 daysTo = QDate::currentDate().daysTo(date);
0488     if (daysTo > 2 || daysTo < -2) {
0489         return m_locale.toString(date, format);
0490     }
0491 
0492     switch (daysTo) {
0493     case 2:
0494         return tr("In two days");
0495     case 1:
0496         return tr("Tomorrow");
0497     case 0:
0498         return tr("Today");
0499     case -1:
0500         return tr("Yesterday");
0501     case -2:
0502         return tr("Two days ago");
0503     }
0504     Q_UNREACHABLE();
0505 }
0506 
0507 QString KFormatPrivate::formatRelativeDateTime(const QDateTime &dateTime, QLocale::FormatType format) const
0508 {
0509     const QDateTime now = QDateTime::currentDateTime();
0510 
0511     const auto secsToNow = dateTime.secsTo(now);
0512     constexpr int secsInAHour = 60 * 60;
0513     if (secsToNow >= 0 && secsToNow < secsInAHour) {
0514         const int minutesToNow = secsToNow / 60;
0515         if (minutesToNow <= 1) {
0516             return tr("Just now");
0517         } else {
0518             //: @item:intext %1 is a whole number
0519             //~ singular %n minute ago
0520             //~ plural %n minutes ago
0521             return tr("%n minute(s) ago", nullptr, minutesToNow);
0522         }
0523     }
0524 
0525     const auto timeFormatType = format == QLocale::FormatType::LongFormat ? QLocale::FormatType::ShortFormat : format;
0526     const qint64 daysToNow = dateTime.daysTo(now);
0527     QString dateString;
0528     if (daysToNow < 2 && daysToNow > -2) {
0529         dateString = formatRelativeDate(dateTime.date(), format);
0530     } else {
0531         dateString = m_locale.toString(dateTime.date(), format);
0532     }
0533 
0534     /*: relative datetime with %1 result of QLocale.toString(date, format) or formatRelativeDate
0535         and %2 result of QLocale.toString(time, timeformatType)
0536         If this does not fit the grammar of your language please contact the i18n team to solve the problem */
0537     QString formattedDate = tr("%1 at %2").arg(dateString, m_locale.toString(dateTime.time(), timeFormatType));
0538 
0539     return formattedDate.replace(0, 1, formattedDate.at(0).toUpper());
0540 }