1/*
2 This file is part of the KDE Frameworks
3
4 SPDX-FileCopyrightText: 2013 Alex Merry <alex.merry@kdemail.net>
5 SPDX-FileCopyrightText: 2013 John Layt <jlayt@kde.org>
6 SPDX-FileCopyrightText: 2010 Michael Leupold <lemma@confuego.org>
7 SPDX-FileCopyrightText: 2009 Michael Pyne <mpyne@kde.org>
8 SPDX-FileCopyrightText: 2008 Albert Astals Cid <aacid@kde.org>
9
10 SPDX-License-Identifier: LGPL-2.0-or-later
11*/
12
13#include "kformatprivate_p.h"
14
15#include <QDateTime>
16#include <QSettings>
17#include <QStandardPaths>
18
19#include <math.h>
20
21KFormatPrivate::KFormatPrivate(const QLocale &locale)
22 : m_locale(locale)
23{
24}
25
26KFormatPrivate::~KFormatPrivate()
27{
28}
29
30constexpr double bpow(int exp)
31{
32 return (exp > 0) ? 2.0 * bpow(exp: exp - 1) : (exp < 0) ? 0.5 * bpow(exp: exp + 1) : 1.0;
33}
34
35QString KFormatPrivate::formatValue(double value,
36 KFormat::Unit unit,
37 QString unitString,
38 int precision,
39 KFormat::UnitPrefix prefix,
40 KFormat::BinaryUnitDialect dialect) const
41{
42 if (dialect <= KFormat::DefaultBinaryDialect || dialect > KFormat::LastBinaryDialect) {
43 dialect = KFormat::IECBinaryDialect;
44 }
45
46 if (static_cast<int>(prefix) < static_cast<int>(KFormat::UnitPrefix::Yocto) || static_cast<int>(prefix) > static_cast<int>(KFormat::UnitPrefix::Yotta)) {
47 prefix = KFormat::UnitPrefix::AutoAdjust;
48 }
49
50 double multiplier = 1024.0;
51 if (dialect == KFormat::MetricBinaryDialect) {
52 multiplier = 1000.0;
53 }
54
55 if (prefix == KFormat::UnitPrefix::AutoAdjust) {
56 int power = 0;
57 double adjustValue = qAbs(t: value);
58 while (adjustValue >= multiplier) {
59 adjustValue /= multiplier;
60 power += 1;
61 }
62 while (adjustValue && adjustValue < 1.0) {
63 adjustValue *= multiplier;
64 power -= 1;
65 }
66 const KFormat::UnitPrefix map[] = {
67 KFormat::UnitPrefix::Yocto, // -8
68 KFormat::UnitPrefix::Zepto,
69 KFormat::UnitPrefix::Atto,
70 KFormat::UnitPrefix::Femto,
71 KFormat::UnitPrefix::Pico,
72 KFormat::UnitPrefix::Nano,
73 KFormat::UnitPrefix::Micro,
74 KFormat::UnitPrefix::Milli,
75 KFormat::UnitPrefix::Unity, // 0
76 KFormat::UnitPrefix::Kilo,
77 KFormat::UnitPrefix::Mega,
78 KFormat::UnitPrefix::Giga,
79 KFormat::UnitPrefix::Tera,
80 KFormat::UnitPrefix::Peta,
81 KFormat::UnitPrefix::Exa,
82 KFormat::UnitPrefix::Zetta,
83 KFormat::UnitPrefix::Yotta, // 8
84 };
85 power = std::max(a: -8, b: std::min(a: 8, b: power));
86 prefix = map[power + 8];
87 }
88
89 if (prefix == KFormat::UnitPrefix::Unity && unit == KFormat::Unit::Byte) {
90 precision = 0;
91 }
92
93 struct PrefixMapEntry {
94 KFormat::UnitPrefix prefix;
95 double decimalFactor;
96 double binaryFactor;
97 QString prefixCharSI;
98 QString prefixCharIEC;
99 };
100
101 const PrefixMapEntry map[] = {
102 {.prefix: KFormat::UnitPrefix::Yocto, .decimalFactor: 1e-24, .binaryFactor: bpow(exp: -80), .prefixCharSI: tr(sourceText: "y", disambiguation: "SI prefix for 10^⁻24"), .prefixCharIEC: QString()},
103 {.prefix: KFormat::UnitPrefix::Zepto, .decimalFactor: 1e-21, .binaryFactor: bpow(exp: -70), .prefixCharSI: tr(sourceText: "z", disambiguation: "SI prefix for 10^⁻21"), .prefixCharIEC: QString()},
104 {.prefix: KFormat::UnitPrefix::Atto, .decimalFactor: 1e-18, .binaryFactor: bpow(exp: -60), .prefixCharSI: tr(sourceText: "a", disambiguation: "SI prefix for 10^⁻18"), .prefixCharIEC: QString()},
105 {.prefix: KFormat::UnitPrefix::Femto, .decimalFactor: 1e-15, .binaryFactor: bpow(exp: -50), .prefixCharSI: tr(sourceText: "f", disambiguation: "SI prefix for 10^⁻15"), .prefixCharIEC: QString()},
106 {.prefix: KFormat::UnitPrefix::Pico, .decimalFactor: 1e-12, .binaryFactor: bpow(exp: -40), .prefixCharSI: tr(sourceText: "p", disambiguation: "SI prefix for 10^⁻12"), .prefixCharIEC: QString()},
107 {.prefix: KFormat::UnitPrefix::Nano, .decimalFactor: 1e-9, .binaryFactor: bpow(exp: -30), .prefixCharSI: tr(sourceText: "n", disambiguation: "SI prefix for 10^⁻9"), .prefixCharIEC: QString()},
108 {.prefix: KFormat::UnitPrefix::Micro, .decimalFactor: 1e-6, .binaryFactor: bpow(exp: -20), .prefixCharSI: tr(sourceText: "µ", disambiguation: "SI prefix for 10^⁻6"), .prefixCharIEC: QString()},
109 {.prefix: KFormat::UnitPrefix::Milli, .decimalFactor: 1e-3, .binaryFactor: bpow(exp: -10), .prefixCharSI: tr(sourceText: "m", disambiguation: "SI prefix for 10^⁻3"), .prefixCharIEC: QString()},
110 {.prefix: KFormat::UnitPrefix::Unity, .decimalFactor: 1.0, .binaryFactor: 1.0, .prefixCharSI: QString(), .prefixCharIEC: QString()},
111 {.prefix: KFormat::UnitPrefix::Kilo, .decimalFactor: 1e3, .binaryFactor: bpow(exp: 10), .prefixCharSI: tr(sourceText: "k", disambiguation: "SI prefix for 10^3"), .prefixCharIEC: tr(sourceText: "Ki", disambiguation: "IEC binary prefix for 2^10")},
112 {.prefix: KFormat::UnitPrefix::Mega, .decimalFactor: 1e6, .binaryFactor: bpow(exp: 20), .prefixCharSI: tr(sourceText: "M", disambiguation: "SI prefix for 10^6"), .prefixCharIEC: tr(sourceText: "Mi", disambiguation: "IEC binary prefix for 2^20")},
113 {.prefix: KFormat::UnitPrefix::Giga, .decimalFactor: 1e9, .binaryFactor: bpow(exp: 30), .prefixCharSI: tr(sourceText: "G", disambiguation: "SI prefix for 10^9"), .prefixCharIEC: tr(sourceText: "Gi", disambiguation: "IEC binary prefix for 2^30")},
114 {.prefix: KFormat::UnitPrefix::Tera, .decimalFactor: 1e12, .binaryFactor: bpow(exp: 40), .prefixCharSI: tr(sourceText: "T", disambiguation: "SI prefix for 10^12"), .prefixCharIEC: tr(sourceText: "Ti", disambiguation: "IEC binary prefix for 2^40")},
115 {.prefix: KFormat::UnitPrefix::Peta, .decimalFactor: 1e15, .binaryFactor: bpow(exp: 50), .prefixCharSI: tr(sourceText: "P", disambiguation: "SI prefix for 10^15"), .prefixCharIEC: tr(sourceText: "Pi", disambiguation: "IEC binary prefix for 2^50")},
116 {.prefix: KFormat::UnitPrefix::Exa, .decimalFactor: 1e18, .binaryFactor: bpow(exp: 60), .prefixCharSI: tr(sourceText: "E", disambiguation: "SI prefix for 10^18"), .prefixCharIEC: tr(sourceText: "Ei", disambiguation: "IEC binary prefix for 2^60")},
117 {.prefix: KFormat::UnitPrefix::Zetta, .decimalFactor: 1e21, .binaryFactor: bpow(exp: 70), .prefixCharSI: tr(sourceText: "Z", disambiguation: "SI prefix for 10^21"), .prefixCharIEC: tr(sourceText: "Zi", disambiguation: "IEC binary prefix for 2^70")},
118 {.prefix: KFormat::UnitPrefix::Yotta, .decimalFactor: 1e24, .binaryFactor: bpow(exp: 80), .prefixCharSI: tr(sourceText: "Y", disambiguation: "SI prefix for 10^24"), .prefixCharIEC: tr(sourceText: "Yi", disambiguation: "IEC binary prefix for 2^80")},
119 };
120
121 auto entry = std::find_if(first: std::begin(arr: map), last: std::end(arr: map), pred: [prefix](const PrefixMapEntry &e) {
122 return e.prefix == prefix;
123 });
124
125 switch (unit) {
126 case KFormat::Unit::Bit:
127 unitString = tr(sourceText: "bit", disambiguation: "Symbol of binary digit");
128 break;
129 case KFormat::Unit::Byte:
130 unitString = tr(sourceText: "B", disambiguation: "Symbol of byte");
131 break;
132 case KFormat::Unit::Meter:
133 unitString = tr(sourceText: "m", disambiguation: "Symbol of meter");
134 break;
135 case KFormat::Unit::Hertz:
136 unitString = tr(sourceText: "Hz", disambiguation: "Symbol of hertz");
137 break;
138 case KFormat::Unit::Other:
139 break;
140 }
141
142 if (prefix == KFormat::UnitPrefix::Unity) {
143 QString numString = m_locale.toString(f: value, format: 'f', precision);
144 //: value without prefix, format "<val> <unit>"
145 return tr(sourceText: "%1 %2", disambiguation: "no Prefix").arg(args&: numString, args&: unitString);
146 }
147
148 QString prefixString;
149 if (dialect == KFormat::MetricBinaryDialect) {
150 value /= entry->decimalFactor;
151 prefixString = entry->prefixCharSI;
152 } else {
153 value /= entry->binaryFactor;
154 if (dialect == KFormat::IECBinaryDialect) {
155 prefixString = entry->prefixCharIEC;
156 } else {
157 prefixString = entry->prefixCharSI.toUpper();
158 }
159 }
160
161 QString numString = m_locale.toString(f: value, format: 'f', precision);
162
163 //: value with prefix, format "<val> <prefix><unit>"
164 return tr(sourceText: "%1 %2%3", disambiguation: "MetricBinaryDialect").arg(args&: numString, args&: prefixString, args&: unitString);
165}
166
167QString KFormatPrivate::formatByteSize(double size, int precision, KFormat::BinaryUnitDialect dialect, KFormat::BinarySizeUnits units) const
168{
169 // Current KDE default is IECBinaryDialect
170 const auto fallbackDialect = KFormat::IECBinaryDialect;
171
172 if (dialect <= KFormat::DefaultBinaryDialect || dialect > KFormat::LastBinaryDialect) {
173 const auto kdeglobals = QStandardPaths::locate(type: QStandardPaths::GenericConfigLocation, QStringLiteral("kdeglobals"));
174 QSettings settings(kdeglobals, QSettings::IniFormat);
175 dialect = static_cast<KFormat::BinaryUnitDialect>(settings.value(key: "Locale/BinaryUnitDialect", defaultValue: fallbackDialect).toInt());
176 }
177
178 // Current KDE default is to auto-adjust so the size falls in the range 0 to 1000/1024
179 if (units < KFormat::DefaultBinaryUnits || units > KFormat::UnitLastUnit) {
180 units = KFormat::DefaultBinaryUnits;
181 }
182
183 int unit = 0; // Selects what unit to use
184 double multiplier = 1024.0;
185
186 if (dialect == KFormat::MetricBinaryDialect) {
187 multiplier = 1000.0;
188 }
189
190 // If a specific unit conversion is given, use it directly. Otherwise
191 // search until the result is in [0, multiplier] (or out of our range).
192 if (units == KFormat::DefaultBinaryUnits) {
193 while (qAbs(t: size) >= multiplier && unit < int(KFormat::UnitYottaByte)) {
194 size /= multiplier;
195 ++unit;
196 }
197 } else {
198 // A specific unit is in use
199 unit = static_cast<int>(units);
200 if (unit > 0) {
201 size /= pow(x: multiplier, y: unit);
202 }
203 }
204
205 // Bytes, no rounding
206 if (unit == 0) {
207 precision = 0;
208 }
209
210 QString numString = m_locale.toString(f: size, format: 'f', precision);
211
212 // Do not remove "//:" comments below, they are used by the translators.
213 // NB: we cannot pass pluralization arguments, as the size may be negative
214 if (dialect == KFormat::MetricBinaryDialect) {
215 switch (unit) {
216 case KFormat::UnitByte:
217 //: MetricBinaryDialect size in bytes
218 return tr(sourceText: "%1 B", disambiguation: "MetricBinaryDialect").arg(a: numString);
219 case KFormat::UnitKiloByte:
220 //: MetricBinaryDialect size in 1000 bytes
221 return tr(sourceText: "%1 kB", disambiguation: "MetricBinaryDialect").arg(a: numString);
222 case KFormat::UnitMegaByte:
223 //: MetricBinaryDialect size in 10^6 bytes
224 return tr(sourceText: "%1 MB", disambiguation: "MetricBinaryDialect").arg(a: numString);
225 case KFormat::UnitGigaByte:
226 //: MetricBinaryDialect size in 10^9 bytes
227 return tr(sourceText: "%1 GB", disambiguation: "MetricBinaryDialect").arg(a: numString);
228 case KFormat::UnitTeraByte:
229 //: MetricBinaryDialect size in 10^12 bytes
230 return tr(sourceText: "%1 TB", disambiguation: "MetricBinaryDialect").arg(a: numString);
231 case KFormat::UnitPetaByte:
232 //: MetricBinaryDialect size in 10^15 bytes
233 return tr(sourceText: "%1 PB", disambiguation: "MetricBinaryDialect").arg(a: numString);
234 case KFormat::UnitExaByte:
235 //: MetricBinaryDialect size in 10^18 byte
236 return tr(sourceText: "%1 EB", disambiguation: "MetricBinaryDialect").arg(a: numString);
237 case KFormat::UnitZettaByte:
238 //: MetricBinaryDialect size in 10^21 bytes
239 return tr(sourceText: "%1 ZB", disambiguation: "MetricBinaryDialect").arg(a: numString);
240 case KFormat::UnitYottaByte:
241 //: MetricBinaryDialect size in 10^24 bytes
242 return tr(sourceText: "%1 YB", disambiguation: "MetricBinaryDialect").arg(a: numString);
243 }
244 } else if (dialect == KFormat::JEDECBinaryDialect) {
245 switch (unit) {
246 case KFormat::UnitByte:
247 //: JEDECBinaryDialect memory size in bytes
248 return tr(sourceText: "%1 B", disambiguation: "JEDECBinaryDialect").arg(a: numString);
249 case KFormat::UnitKiloByte:
250 //: JEDECBinaryDialect memory size in 1024 bytes
251 return tr(sourceText: "%1 KB", disambiguation: "JEDECBinaryDialect").arg(a: numString);
252 case KFormat::UnitMegaByte:
253 //: JEDECBinaryDialect memory size in 10^20 bytes
254 return tr(sourceText: "%1 MB", disambiguation: "JEDECBinaryDialect").arg(a: numString);
255 case KFormat::UnitGigaByte:
256 //: JEDECBinaryDialect memory size in 10^30 bytes
257 return tr(sourceText: "%1 GB", disambiguation: "JEDECBinaryDialect").arg(a: numString);
258 case KFormat::UnitTeraByte:
259 //: JEDECBinaryDialect memory size in 10^40 bytes
260 return tr(sourceText: "%1 TB", disambiguation: "JEDECBinaryDialect").arg(a: numString);
261 case KFormat::UnitPetaByte:
262 //: JEDECBinaryDialect memory size in 10^50 bytes
263 return tr(sourceText: "%1 PB", disambiguation: "JEDECBinaryDialect").arg(a: numString);
264 case KFormat::UnitExaByte:
265 //: JEDECBinaryDialect memory size in 10^60 bytes
266 return tr(sourceText: "%1 EB", disambiguation: "JEDECBinaryDialect").arg(a: numString);
267 case KFormat::UnitZettaByte:
268 //: JEDECBinaryDialect memory size in 10^70 bytes
269 return tr(sourceText: "%1 ZB", disambiguation: "JEDECBinaryDialect").arg(a: numString);
270 case KFormat::UnitYottaByte:
271 //: JEDECBinaryDialect memory size in 10^80 bytes
272 return tr(sourceText: "%1 YB", disambiguation: "JEDECBinaryDialect").arg(a: numString);
273 }
274 } else { // KFormat::IECBinaryDialect, KFormat::DefaultBinaryDialect
275 switch (unit) {
276 case KFormat::UnitByte:
277 //: IECBinaryDialect size in bytes
278 return tr(sourceText: "%1 B", disambiguation: "IECBinaryDialect").arg(a: numString);
279 case KFormat::UnitKiloByte:
280 //: IECBinaryDialect size in 1024 bytes
281 return tr(sourceText: "%1 KiB", disambiguation: "IECBinaryDialect").arg(a: numString);
282 case KFormat::UnitMegaByte:
283 //: IECBinaryDialect size in 10^20 bytes
284 return tr(sourceText: "%1 MiB", disambiguation: "IECBinaryDialect").arg(a: numString);
285 case KFormat::UnitGigaByte:
286 //: IECBinaryDialect size in 10^30 bytes
287 return tr(sourceText: "%1 GiB", disambiguation: "IECBinaryDialect").arg(a: numString);
288 case KFormat::UnitTeraByte:
289 //: IECBinaryDialect size in 10^40 bytes
290 return tr(sourceText: "%1 TiB", disambiguation: "IECBinaryDialect").arg(a: numString);
291 case KFormat::UnitPetaByte:
292 //: IECBinaryDialect size in 10^50 bytes
293 return tr(sourceText: "%1 PiB", disambiguation: "IECBinaryDialect").arg(a: numString);
294 case KFormat::UnitExaByte:
295 //: IECBinaryDialect size in 10^60 bytes
296 return tr(sourceText: "%1 EiB", disambiguation: "IECBinaryDialect").arg(a: numString);
297 case KFormat::UnitZettaByte:
298 //: IECBinaryDialect size in 10^70 bytes
299 return tr(sourceText: "%1 ZiB", disambiguation: "IECBinaryDialect").arg(a: numString);
300 case KFormat::UnitYottaByte:
301 //: IECBinaryDialect size in 10^80 bytes
302 return tr(sourceText: "%1 YiB", disambiguation: "IECBinaryDialect").arg(a: numString);
303 }
304 }
305
306 // Should never reach here
307 Q_ASSERT(false);
308 return numString;
309}
310
311enum TimeConstants {
312 MSecsInDay = 86400000,
313 MSecsInHour = 3600000,
314 MSecsInMinute = 60000,
315 MSecsInSecond = 1000,
316};
317
318QString KFormatPrivate::formatDuration(quint64 msecs, KFormat::DurationFormatOptions options) const
319{
320 quint64 ms = msecs;
321 if (options & KFormat::HideSeconds) {
322 // round to nearest minute
323 ms = qRound64(d: ms / (qreal)MSecsInMinute) * MSecsInMinute;
324 } else if (!(options & KFormat::ShowMilliseconds)) {
325 // round to nearest second
326 ms = qRound64(d: ms / (qreal)MSecsInSecond) * MSecsInSecond;
327 }
328
329 int hours = ms / MSecsInHour;
330 ms = ms % MSecsInHour;
331 int minutes = ms / MSecsInMinute;
332 ms = ms % MSecsInMinute;
333 int seconds = ms / MSecsInSecond;
334 ms = ms % MSecsInSecond;
335
336 if ((options & KFormat::InitialDuration) == KFormat::InitialDuration) {
337 if ((options & KFormat::FoldHours) == KFormat::FoldHours && (options & KFormat::ShowMilliseconds) == KFormat::ShowMilliseconds) {
338 //: @item:intext Duration format minutes, seconds and milliseconds
339 return tr(sourceText: "%1m%2.%3s").arg(a: hours * 60 + minutes, fieldWidth: 1, base: 10, fillChar: QLatin1Char('0')).arg(a: seconds, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0')).arg(a: ms, fieldwidth: 3, base: 10, fillChar: QLatin1Char('0'));
340 } else if ((options & KFormat::FoldHours) == KFormat::FoldHours) {
341 //: @item:intext Duration format minutes and seconds
342 return tr(sourceText: "%1m%2s").arg(a: hours * 60 + minutes, fieldWidth: 1, base: 10, fillChar: QLatin1Char('0')).arg(a: seconds, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'));
343 } else if ((options & KFormat::HideSeconds) == KFormat::HideSeconds) {
344 //: @item:intext Duration format hours and minutes
345 return tr(sourceText: "%1h%2m").arg(a: hours, fieldWidth: 1, base: 10, fillChar: QLatin1Char('0')).arg(a: minutes, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'));
346 } else if ((options & KFormat::ShowMilliseconds) == KFormat::ShowMilliseconds) {
347 //: @item:intext Duration format hours, minutes, seconds, milliseconds
348 return tr(sourceText: "%1h%2m%3.%4s")
349 .arg(a: hours, fieldWidth: 1, base: 10, fillChar: QLatin1Char('0'))
350 .arg(a: minutes, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'))
351 .arg(a: seconds, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'))
352 .arg(a: ms, fieldwidth: 3, base: 10, fillChar: QLatin1Char('0'));
353 } else { // Default
354 //: @item:intext Duration format hours, minutes, seconds
355 return tr(sourceText: "%1h%2m%3s").arg(a: hours, fieldWidth: 1, base: 10, fillChar: QLatin1Char('0')).arg(a: minutes, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0')).arg(a: seconds, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'));
356 }
357
358 } else {
359 if ((options & KFormat::FoldHours) == KFormat::FoldHours && (options & KFormat::ShowMilliseconds) == KFormat::ShowMilliseconds) {
360 //: @item:intext Duration format minutes, seconds and milliseconds
361 return tr(sourceText: "%1:%2.%3").arg(a: hours * 60 + minutes, fieldWidth: 1, base: 10, fillChar: QLatin1Char('0')).arg(a: seconds, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0')).arg(a: ms, fieldwidth: 3, base: 10, fillChar: QLatin1Char('0'));
362 } else if ((options & KFormat::FoldHours) == KFormat::FoldHours) {
363 //: @item:intext Duration format minutes and seconds
364 return tr(sourceText: "%1:%2").arg(a: hours * 60 + minutes, fieldWidth: 1, base: 10, fillChar: QLatin1Char('0')).arg(a: seconds, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'));
365 } else if ((options & KFormat::HideSeconds) == KFormat::HideSeconds) {
366 //: @item:intext Duration format hours and minutes
367 return tr(sourceText: "%1:%2").arg(a: hours, fieldWidth: 1, base: 10, fillChar: QLatin1Char('0')).arg(a: minutes, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'));
368 } else if ((options & KFormat::ShowMilliseconds) == KFormat::ShowMilliseconds) {
369 //: @item:intext Duration format hours, minutes, seconds, milliseconds
370 return tr(sourceText: "%1:%2:%3.%4")
371 .arg(a: hours, fieldWidth: 1, base: 10, fillChar: QLatin1Char('0'))
372 .arg(a: minutes, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'))
373 .arg(a: seconds, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'))
374 .arg(a: ms, fieldwidth: 3, base: 10, fillChar: QLatin1Char('0'));
375 } else { // Default
376 //: @item:intext Duration format hours, minutes, seconds
377 return tr(sourceText: "%1:%2:%3").arg(a: hours, fieldWidth: 1, base: 10, fillChar: QLatin1Char('0')).arg(a: minutes, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0')).arg(a: seconds, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'));
378 }
379 }
380
381 Q_UNREACHABLE();
382 return QString();
383}
384
385QString KFormatPrivate::formatDecimalDuration(quint64 msecs, int decimalPlaces) const
386{
387 if (msecs >= MSecsInDay) {
388 //: @item:intext %1 is a real number, e.g. 1.23 days
389 return tr(sourceText: "%1 days").arg(a: m_locale.toString(f: msecs / (+MSecsInDay * 1.0), format: 'f', precision: decimalPlaces));
390 } else if (msecs >= MSecsInHour) {
391 //: @item:intext %1 is a real number, e.g. 1.23 hours
392 return tr(sourceText: "%1 hours").arg(a: m_locale.toString(f: msecs / (+MSecsInHour * 1.0), format: 'f', precision: decimalPlaces));
393 } else if (msecs >= MSecsInMinute) {
394 //: @item:intext %1 is a real number, e.g. 1.23 minutes
395 return tr(sourceText: "%1 minutes").arg(a: m_locale.toString(f: msecs / (+MSecsInMinute * 1.0), format: 'f', precision: decimalPlaces));
396 } else if (msecs >= MSecsInSecond) {
397 //: @item:intext %1 is a real number, e.g. 1.23 seconds
398 return tr(sourceText: "%1 seconds").arg(a: m_locale.toString(f: msecs / (+MSecsInSecond * 1.0), format: 'f', precision: decimalPlaces));
399 }
400 //: @item:intext %1 is a whole number
401 //~ singular %n millisecond
402 //~ plural %n milliseconds
403 return tr(sourceText: "%n millisecond(s)", disambiguation: nullptr, n: msecs);
404}
405
406enum DurationUnits {
407 Days = 0,
408 Hours,
409 Minutes,
410 Seconds,
411};
412
413static QString formatSingleDuration(DurationUnits units, int n)
414{
415 // NB: n is guaranteed to be non-negative
416 switch (units) {
417 case Days:
418 //: @item:intext %n is a whole number
419 //~ singular %n day
420 //~ plural %n days
421 return KFormatPrivate::tr(sourceText: "%n day(s)", disambiguation: nullptr, n);
422 case Hours:
423 //: @item:intext %n is a whole number
424 //~ singular %n hour
425 //~ plural %n hours
426 return KFormatPrivate::tr(sourceText: "%n hour(s)", disambiguation: nullptr, n);
427 case Minutes:
428 //: @item:intext %n is a whole number
429 //~ singular %n minute
430 //~ plural %n minutes
431 return KFormatPrivate::tr(sourceText: "%n minute(s)", disambiguation: nullptr, n);
432 case Seconds:
433 //: @item:intext %n is a whole number
434 //~ singular %n second
435 //~ plural %n seconds
436 return KFormatPrivate::tr(sourceText: "%n second(s)", disambiguation: nullptr, n);
437 }
438 Q_ASSERT(false);
439 return QString();
440}
441
442QString KFormatPrivate::formatSpelloutDuration(quint64 msecs) const
443{
444 quint64 ms = msecs;
445 int days = ms / MSecsInDay;
446 ms = ms % (MSecsInDay);
447 int hours = ms / MSecsInHour;
448 ms = ms % MSecsInHour;
449 int minutes = ms / MSecsInMinute;
450 ms = ms % MSecsInMinute;
451 int seconds = qRound(d: ms / 1000.0);
452
453 // Handle correctly problematic case #1 (look at KFormatTest::prettyFormatDuration())
454 if (seconds == 60) {
455 return formatSpelloutDuration(msecs: msecs - ms + MSecsInMinute);
456 }
457
458 if (days && hours) {
459 /*: @item:intext days and hours. This uses the previous item:intext messages.
460 If this does not fit the grammar of your language please contact the i18n team to solve the problem */
461 return tr(sourceText: "%1 and %2").arg(args: formatSingleDuration(units: Days, n: days), args: formatSingleDuration(units: Hours, n: hours));
462 } else if (days) {
463 return formatSingleDuration(units: Days, n: days);
464 } else if (hours && minutes) {
465 /*: @item:intext hours and minutes. This uses the previous item:intext messages.
466 If this does not fit the grammar of your language please contact the i18n team to solve the problem */
467 return tr(sourceText: "%1 and %2").arg(args: formatSingleDuration(units: Hours, n: hours), args: formatSingleDuration(units: Minutes, n: minutes));
468 } else if (hours) {
469 return formatSingleDuration(units: Hours, n: hours);
470 } else if (minutes && seconds) {
471 /*: @item:intext minutes and seconds. This uses the previous item:intext messages.
472 If this does not fit the grammar of your language please contact the i18n team to solve the problem */
473 return tr(sourceText: "%1 and %2").arg(args: formatSingleDuration(units: Minutes, n: minutes), args: formatSingleDuration(units: Seconds, n: seconds));
474 } else if (minutes) {
475 return formatSingleDuration(units: Minutes, n: minutes);
476 } else {
477 return formatSingleDuration(units: Seconds, n: seconds);
478 }
479}
480
481QString KFormatPrivate::formatRelativeDate(const QDate &date, QLocale::FormatType format) const
482{
483 if (!date.isValid()) {
484 return tr(sourceText: "Invalid date", disambiguation: "used when a relative date string can't be generated because the date is invalid");
485 }
486
487 const qint64 daysTo = QDate::currentDate().daysTo(d: date);
488 if (daysTo > 2 || daysTo < -2) {
489 return m_locale.toString(date, format);
490 }
491
492 switch (daysTo) {
493 case 2:
494 return tr(sourceText: "In two days");
495 case 1:
496 return tr(sourceText: "Tomorrow");
497 case 0:
498 return tr(sourceText: "Today");
499 case -1:
500 return tr(sourceText: "Yesterday");
501 case -2:
502 return tr(sourceText: "Two days ago");
503 }
504 Q_UNREACHABLE();
505}
506
507QString KFormatPrivate::formatRelativeDateTime(const QDateTime &dateTime, QLocale::FormatType format) const
508{
509 const QDateTime now = QDateTime::currentDateTime();
510
511 const auto secsToNow = dateTime.secsTo(now);
512 constexpr int secsInAHour = 60 * 60;
513 if (secsToNow >= 0 && secsToNow < secsInAHour) {
514 const int minutesToNow = secsToNow / 60;
515 if (minutesToNow <= 1) {
516 return tr(sourceText: "Just now");
517 } else {
518 //: @item:intext %1 is a whole number
519 //~ singular %n minute ago
520 //~ plural %n minutes ago
521 return tr(sourceText: "%n minute(s) ago", disambiguation: nullptr, n: minutesToNow);
522 }
523 }
524
525 const auto timeFormatType = format == QLocale::FormatType::LongFormat ? QLocale::FormatType::ShortFormat : format;
526 const qint64 daysToNow = dateTime.daysTo(now);
527 QString dateString;
528 if (daysToNow < 2 && daysToNow > -2) {
529 dateString = formatRelativeDate(date: dateTime.date(), format);
530 } else {
531 dateString = m_locale.toString(date: dateTime.date(), format);
532 }
533
534 /*: relative datetime with %1 result of QLocale.toString(date, format) or formatRelativeDate
535 and %2 result of QLocale.toString(time, timeformatType)
536 If this does not fit the grammar of your language please contact the i18n team to solve the problem */
537 QString formattedDate = tr(sourceText: "%1 at %2").arg(args&: dateString, args: m_locale.toString(time: dateTime.time(), format: timeFormatType));
538
539 return formattedDate.replace(i: 0, len: 1, after: formattedDate.at(i: 0).toUpper());
540}
541

source code of kcoreaddons/src/lib/util/kformatprivate.cpp