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 }