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#include <QTimeZone>
19
20#include <math.h>
21
22using namespace Qt::Literals;
23
24KFormatPrivate::KFormatPrivate(const QLocale &locale)
25 : m_locale(locale)
26{
27}
28
29KFormatPrivate::~KFormatPrivate() = default;
30
31constexpr double bpow(int exp)
32{
33 return (exp > 0) ? 2.0 * bpow(exp: exp - 1) : (exp < 0) ? 0.5 * bpow(exp: exp + 1) : 1.0;
34}
35
36QString KFormatPrivate::formatValue(double value,
37 KFormat::Unit unit,
38 QString unitString,
39 int precision,
40 KFormat::UnitPrefix prefix,
41 KFormat::BinaryUnitDialect dialect) const
42{
43 if (dialect <= KFormat::DefaultBinaryDialect || dialect > KFormat::LastBinaryDialect) {
44 dialect = KFormat::IECBinaryDialect;
45 }
46
47 if (static_cast<int>(prefix) < static_cast<int>(KFormat::UnitPrefix::Yocto) || static_cast<int>(prefix) > static_cast<int>(KFormat::UnitPrefix::Yotta)) {
48 prefix = KFormat::UnitPrefix::AutoAdjust;
49 }
50
51 double multiplier = 1024.0;
52 if (dialect == KFormat::MetricBinaryDialect) {
53 multiplier = 1000.0;
54 }
55
56 if (prefix == KFormat::UnitPrefix::AutoAdjust) {
57 int power = 0;
58 double adjustValue = qAbs(t: value);
59 while (adjustValue >= multiplier) {
60 adjustValue /= multiplier;
61 power += 1;
62 }
63 while (adjustValue && adjustValue < 1.0) {
64 adjustValue *= multiplier;
65 power -= 1;
66 }
67 const KFormat::UnitPrefix map[] = {
68 KFormat::UnitPrefix::Yocto, // -8
69 KFormat::UnitPrefix::Zepto,
70 KFormat::UnitPrefix::Atto,
71 KFormat::UnitPrefix::Femto,
72 KFormat::UnitPrefix::Pico,
73 KFormat::UnitPrefix::Nano,
74 KFormat::UnitPrefix::Micro,
75 KFormat::UnitPrefix::Milli,
76 KFormat::UnitPrefix::Unity, // 0
77 KFormat::UnitPrefix::Kilo,
78 KFormat::UnitPrefix::Mega,
79 KFormat::UnitPrefix::Giga,
80 KFormat::UnitPrefix::Tera,
81 KFormat::UnitPrefix::Peta,
82 KFormat::UnitPrefix::Exa,
83 KFormat::UnitPrefix::Zetta,
84 KFormat::UnitPrefix::Yotta, // 8
85 };
86 power = std::max(a: -8, b: std::min(a: 8, b: power));
87 prefix = map[power + 8];
88 }
89
90 if (prefix == KFormat::UnitPrefix::Unity && unit == KFormat::Unit::Byte) {
91 precision = 0;
92 }
93
94 struct PrefixMapEntry {
95 KFormat::UnitPrefix prefix;
96 double decimalFactor;
97 double binaryFactor;
98 QString prefixCharSI;
99 QString prefixCharIEC;
100 };
101
102 const PrefixMapEntry map[] = {
103 {.prefix: KFormat::UnitPrefix::Yocto, .decimalFactor: 1e-24, .binaryFactor: bpow(exp: -80), .prefixCharSI: tr(sourceText: "y", disambiguation: "SI prefix for 10^⁻24"), .prefixCharIEC: QString()},
104 {.prefix: KFormat::UnitPrefix::Zepto, .decimalFactor: 1e-21, .binaryFactor: bpow(exp: -70), .prefixCharSI: tr(sourceText: "z", disambiguation: "SI prefix for 10^⁻21"), .prefixCharIEC: QString()},
105 {.prefix: KFormat::UnitPrefix::Atto, .decimalFactor: 1e-18, .binaryFactor: bpow(exp: -60), .prefixCharSI: tr(sourceText: "a", disambiguation: "SI prefix for 10^⁻18"), .prefixCharIEC: QString()},
106 {.prefix: KFormat::UnitPrefix::Femto, .decimalFactor: 1e-15, .binaryFactor: bpow(exp: -50), .prefixCharSI: tr(sourceText: "f", disambiguation: "SI prefix for 10^⁻15"), .prefixCharIEC: QString()},
107 {.prefix: KFormat::UnitPrefix::Pico, .decimalFactor: 1e-12, .binaryFactor: bpow(exp: -40), .prefixCharSI: tr(sourceText: "p", disambiguation: "SI prefix for 10^⁻12"), .prefixCharIEC: QString()},
108 {.prefix: KFormat::UnitPrefix::Nano, .decimalFactor: 1e-9, .binaryFactor: bpow(exp: -30), .prefixCharSI: tr(sourceText: "n", disambiguation: "SI prefix for 10^⁻9"), .prefixCharIEC: QString()},
109 {.prefix: KFormat::UnitPrefix::Micro, .decimalFactor: 1e-6, .binaryFactor: bpow(exp: -20), .prefixCharSI: tr(sourceText: "µ", disambiguation: "SI prefix for 10^⁻6"), .prefixCharIEC: QString()},
110 {.prefix: KFormat::UnitPrefix::Milli, .decimalFactor: 1e-3, .binaryFactor: bpow(exp: -10), .prefixCharSI: tr(sourceText: "m", disambiguation: "SI prefix for 10^⁻3"), .prefixCharIEC: QString()},
111 {.prefix: KFormat::UnitPrefix::Unity, .decimalFactor: 1.0, .binaryFactor: 1.0, .prefixCharSI: QString(), .prefixCharIEC: QString()},
112 {.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")},
113 {.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")},
114 {.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")},
115 {.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")},
116 {.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")},
117 {.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")},
118 {.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")},
119 {.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")},
120 };
121
122 auto entry = std::find_if(first: std::begin(arr: map), last: std::end(arr: map), pred: [prefix](const PrefixMapEntry &e) {
123 return e.prefix == prefix;
124 });
125
126 switch (unit) {
127 case KFormat::Unit::Bit:
128 unitString = tr(sourceText: "bit", disambiguation: "Symbol of binary digit");
129 break;
130 case KFormat::Unit::Byte:
131 unitString = tr(sourceText: "B", disambiguation: "Symbol of byte");
132 break;
133 case KFormat::Unit::Meter:
134 unitString = tr(sourceText: "m", disambiguation: "Symbol of meter");
135 break;
136 case KFormat::Unit::Hertz:
137 unitString = tr(sourceText: "Hz", disambiguation: "Symbol of hertz");
138 break;
139 case KFormat::Unit::Other:
140 break;
141 }
142
143 if (prefix == KFormat::UnitPrefix::Unity) {
144 QString numString = m_locale.toString(f: value, format: 'f', precision);
145 //: value without prefix, format "<val> <unit>"
146 return tr(sourceText: "%1 %2", disambiguation: "no Prefix").arg(args&: numString, args&: unitString);
147 }
148
149 QString prefixString;
150 if (dialect == KFormat::MetricBinaryDialect) {
151 value /= entry->decimalFactor;
152 prefixString = entry->prefixCharSI;
153 } else {
154 value /= entry->binaryFactor;
155 if (dialect == KFormat::IECBinaryDialect) {
156 prefixString = entry->prefixCharIEC;
157 } else {
158 prefixString = entry->prefixCharSI.toUpper();
159 }
160 }
161
162 QString numString = m_locale.toString(f: value, format: 'f', precision);
163
164 //: value with prefix, format "<val> <prefix><unit>"
165 return tr(sourceText: "%1 %2%3", disambiguation: "MetricBinaryDialect").arg(args&: numString, args&: prefixString, args&: unitString);
166}
167
168QString KFormatPrivate::formatByteSize(double size, int precision, KFormat::BinaryUnitDialect dialect, KFormat::BinarySizeUnits units) const
169{
170 // Current KDE default is IECBinaryDialect
171 const auto fallbackDialect = KFormat::IECBinaryDialect;
172
173 if (dialect <= KFormat::DefaultBinaryDialect || dialect > KFormat::LastBinaryDialect) {
174 const auto kdeglobals = QStandardPaths::locate(type: QStandardPaths::GenericConfigLocation, QStringLiteral("kdeglobals"));
175 QSettings settings(kdeglobals, QSettings::IniFormat);
176 dialect = static_cast<KFormat::BinaryUnitDialect>(settings.value(key: "Locale/BinaryUnitDialect", defaultValue: fallbackDialect).toInt());
177 }
178
179 // Current KDE default is to auto-adjust so the size falls in the range 0 to 1000/1024
180 if (units < KFormat::DefaultBinaryUnits || units > KFormat::UnitLastUnit) {
181 units = KFormat::DefaultBinaryUnits;
182 }
183
184 int unit = 0; // Selects what unit to use
185 double multiplier = 1024.0;
186
187 if (dialect == KFormat::MetricBinaryDialect) {
188 multiplier = 1000.0;
189 }
190
191 // If a specific unit conversion is given, use it directly. Otherwise
192 // search until the result is in [0, multiplier] (or out of our range).
193 if (units == KFormat::DefaultBinaryUnits) {
194 while (qAbs(t: size) >= multiplier && unit < int(KFormat::UnitYottaByte)) {
195 size /= multiplier;
196 ++unit;
197 }
198 } else {
199 // A specific unit is in use
200 unit = static_cast<int>(units);
201 if (unit > 0) {
202 size /= pow(x: multiplier, y: unit);
203 }
204 }
205
206 // Bytes, no rounding
207 if (unit == 0) {
208 precision = 0;
209 }
210
211 QString numString = m_locale.toString(f: size, format: 'f', precision);
212
213 // Do not remove "//:" comments below, they are used by the translators.
214 // NB: we cannot pass pluralization arguments, as the size may be negative
215 if (dialect == KFormat::MetricBinaryDialect) {
216 switch (unit) {
217 case KFormat::UnitByte:
218 //: MetricBinaryDialect size in bytes
219 return tr(sourceText: "%1 B", disambiguation: "MetricBinaryDialect").arg(a: numString);
220 case KFormat::UnitKiloByte:
221 //: MetricBinaryDialect size in 1000 bytes
222 return tr(sourceText: "%1 kB", disambiguation: "MetricBinaryDialect").arg(a: numString);
223 case KFormat::UnitMegaByte:
224 //: MetricBinaryDialect size in 10^6 bytes
225 return tr(sourceText: "%1 MB", disambiguation: "MetricBinaryDialect").arg(a: numString);
226 case KFormat::UnitGigaByte:
227 //: MetricBinaryDialect size in 10^9 bytes
228 return tr(sourceText: "%1 GB", disambiguation: "MetricBinaryDialect").arg(a: numString);
229 case KFormat::UnitTeraByte:
230 //: MetricBinaryDialect size in 10^12 bytes
231 return tr(sourceText: "%1 TB", disambiguation: "MetricBinaryDialect").arg(a: numString);
232 case KFormat::UnitPetaByte:
233 //: MetricBinaryDialect size in 10^15 bytes
234 return tr(sourceText: "%1 PB", disambiguation: "MetricBinaryDialect").arg(a: numString);
235 case KFormat::UnitExaByte:
236 //: MetricBinaryDialect size in 10^18 byte
237 return tr(sourceText: "%1 EB", disambiguation: "MetricBinaryDialect").arg(a: numString);
238 case KFormat::UnitZettaByte:
239 //: MetricBinaryDialect size in 10^21 bytes
240 return tr(sourceText: "%1 ZB", disambiguation: "MetricBinaryDialect").arg(a: numString);
241 case KFormat::UnitYottaByte:
242 //: MetricBinaryDialect size in 10^24 bytes
243 return tr(sourceText: "%1 YB", disambiguation: "MetricBinaryDialect").arg(a: numString);
244 }
245 } else if (dialect == KFormat::JEDECBinaryDialect) {
246 switch (unit) {
247 case KFormat::UnitByte:
248 //: JEDECBinaryDialect memory size in bytes
249 return tr(sourceText: "%1 B", disambiguation: "JEDECBinaryDialect").arg(a: numString);
250 case KFormat::UnitKiloByte:
251 //: JEDECBinaryDialect memory size in 1024 bytes
252 return tr(sourceText: "%1 KB", disambiguation: "JEDECBinaryDialect").arg(a: numString);
253 case KFormat::UnitMegaByte:
254 //: JEDECBinaryDialect memory size in 10^20 bytes
255 return tr(sourceText: "%1 MB", disambiguation: "JEDECBinaryDialect").arg(a: numString);
256 case KFormat::UnitGigaByte:
257 //: JEDECBinaryDialect memory size in 10^30 bytes
258 return tr(sourceText: "%1 GB", disambiguation: "JEDECBinaryDialect").arg(a: numString);
259 case KFormat::UnitTeraByte:
260 //: JEDECBinaryDialect memory size in 10^40 bytes
261 return tr(sourceText: "%1 TB", disambiguation: "JEDECBinaryDialect").arg(a: numString);
262 case KFormat::UnitPetaByte:
263 //: JEDECBinaryDialect memory size in 10^50 bytes
264 return tr(sourceText: "%1 PB", disambiguation: "JEDECBinaryDialect").arg(a: numString);
265 case KFormat::UnitExaByte:
266 //: JEDECBinaryDialect memory size in 10^60 bytes
267 return tr(sourceText: "%1 EB", disambiguation: "JEDECBinaryDialect").arg(a: numString);
268 case KFormat::UnitZettaByte:
269 //: JEDECBinaryDialect memory size in 10^70 bytes
270 return tr(sourceText: "%1 ZB", disambiguation: "JEDECBinaryDialect").arg(a: numString);
271 case KFormat::UnitYottaByte:
272 //: JEDECBinaryDialect memory size in 10^80 bytes
273 return tr(sourceText: "%1 YB", disambiguation: "JEDECBinaryDialect").arg(a: numString);
274 }
275 } else { // KFormat::IECBinaryDialect, KFormat::DefaultBinaryDialect
276 switch (unit) {
277 case KFormat::UnitByte:
278 //: IECBinaryDialect size in bytes
279 return tr(sourceText: "%1 B", disambiguation: "IECBinaryDialect").arg(a: numString);
280 case KFormat::UnitKiloByte:
281 //: IECBinaryDialect size in 1024 bytes
282 return tr(sourceText: "%1 KiB", disambiguation: "IECBinaryDialect").arg(a: numString);
283 case KFormat::UnitMegaByte:
284 //: IECBinaryDialect size in 10^20 bytes
285 return tr(sourceText: "%1 MiB", disambiguation: "IECBinaryDialect").arg(a: numString);
286 case KFormat::UnitGigaByte:
287 //: IECBinaryDialect size in 10^30 bytes
288 return tr(sourceText: "%1 GiB", disambiguation: "IECBinaryDialect").arg(a: numString);
289 case KFormat::UnitTeraByte:
290 //: IECBinaryDialect size in 10^40 bytes
291 return tr(sourceText: "%1 TiB", disambiguation: "IECBinaryDialect").arg(a: numString);
292 case KFormat::UnitPetaByte:
293 //: IECBinaryDialect size in 10^50 bytes
294 return tr(sourceText: "%1 PiB", disambiguation: "IECBinaryDialect").arg(a: numString);
295 case KFormat::UnitExaByte:
296 //: IECBinaryDialect size in 10^60 bytes
297 return tr(sourceText: "%1 EiB", disambiguation: "IECBinaryDialect").arg(a: numString);
298 case KFormat::UnitZettaByte:
299 //: IECBinaryDialect size in 10^70 bytes
300 return tr(sourceText: "%1 ZiB", disambiguation: "IECBinaryDialect").arg(a: numString);
301 case KFormat::UnitYottaByte:
302 //: IECBinaryDialect size in 10^80 bytes
303 return tr(sourceText: "%1 YiB", disambiguation: "IECBinaryDialect").arg(a: numString);
304 }
305 }
306
307 // Should never reach here
308 Q_ASSERT(false);
309 return numString;
310}
311
312constexpr quint64 MSecsInYear = (quint64)1000 * 60 * 60 * 24 * 365; // approximation of a year
313constexpr quint64 MSecsInDay = 1000 * 60 * 60 * 24;
314constexpr quint64 MSecsInHour = 3600000;
315constexpr quint64 MSecsInMinute = 60000;
316constexpr quint64 MSecsInSecond = 1000;
317
318enum DurationUnits {
319 Years = 0,
320 Days,
321 Hours,
322 Minutes,
323 Seconds,
324};
325
326static QString formatSingleAbbreviatedDuration(DurationUnits units, int n)
327{
328 switch (units) {
329 case Years:
330 //: @item:intext abbreviated amount of years
331 //~ singular %n yr
332 //~ plural %n yr
333 return KFormatPrivate::tr(sourceText: "%n yr", disambiguation: nullptr, n);
334 case Days:
335 //: @item:intext abbreviated amount of days
336 //~ singular %n d
337 //~ plural %n d
338 return KFormatPrivate::tr(sourceText: "%n d", disambiguation: nullptr, n);
339 case Hours:
340 //: @item:intext abbreviated amount of hours
341 //~ singular %n hr
342 //~ plural %n hr
343 return KFormatPrivate::tr(sourceText: "%n hr", disambiguation: nullptr, n);
344 case Minutes:
345 //: @item:intext abbreviated amount of minutes
346 //~ singular %n min
347 //~ plural %n min
348 return KFormatPrivate::tr(sourceText: "%n min", disambiguation: nullptr, n);
349 case Seconds:
350 //: @item:intext abbreviated amount of seconds
351 //~ singular %n sec
352 //~ plural %n sec
353 return KFormatPrivate::tr(sourceText: "%n sec", disambiguation: nullptr, n);
354 }
355 Q_ASSERT(false);
356 return QString();
357}
358QString KFormatPrivate::formatDuration(quint64 msecs, KFormat::DurationFormatOptions options) const
359{
360 quint64 ms = msecs;
361 if (options & KFormat::HideSeconds) {
362 // round to nearest minute
363 ms = qRound64(d: ms / (qreal)MSecsInMinute) * MSecsInMinute;
364 } else if (!(options & KFormat::ShowMilliseconds)) {
365 // round to nearest second
366 ms = qRound64(d: ms / (qreal)MSecsInSecond) * MSecsInSecond;
367 }
368
369 if ((options & KFormat::InitialDuration) == KFormat::InitialDuration) {
370 int hours = ms / MSecsInHour;
371 ms = ms % MSecsInHour;
372 int minutes = ms / MSecsInMinute;
373 ms = ms % MSecsInMinute;
374 int seconds = ms / MSecsInSecond;
375 ms = ms % MSecsInSecond;
376
377 if ((options & KFormat::FoldHours) == KFormat::FoldHours && (options & KFormat::ShowMilliseconds) == KFormat::ShowMilliseconds) {
378 //: @item:intext Duration format minutes, seconds and milliseconds
379 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'));
380 } else if ((options & KFormat::FoldHours) == KFormat::FoldHours) {
381 //: @item:intext Duration format minutes and seconds
382 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'));
383 } else if ((options & KFormat::HideSeconds) == KFormat::HideSeconds) {
384 //: @item:intext Duration format hours and minutes
385 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'));
386 } else if ((options & KFormat::ShowMilliseconds) == KFormat::ShowMilliseconds) {
387 //: @item:intext Duration format hours, minutes, seconds, milliseconds
388 return tr(sourceText: "%1h%2m%3.%4s")
389 .arg(a: hours, fieldWidth: 1, base: 10, fillChar: QLatin1Char('0'))
390 .arg(a: minutes, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'))
391 .arg(a: seconds, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'))
392 .arg(a: ms, fieldwidth: 3, base: 10, fillChar: QLatin1Char('0'));
393 }
394
395 //: @item:intext Duration format hours, minutes, seconds
396 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'));
397 }
398
399 int days = 0;
400 int years = 0;
401 if (options & KFormat::AbbreviatedDuration) {
402 years = ms / MSecsInYear;
403 ms = ms % MSecsInYear;
404 days = ms / MSecsInDay;
405 ms = ms % MSecsInDay;
406 }
407 int hours = ms / MSecsInHour;
408 ms = ms % MSecsInHour;
409 int minutes = ms / MSecsInMinute;
410 ms = ms % MSecsInMinute;
411 int seconds = ms / MSecsInSecond;
412 ms = ms % MSecsInSecond;
413
414 if (options & KFormat::AbbreviatedDuration) {
415 if (options & KFormat::FoldHours) {
416 minutes += 60 * hours + 60 * 24 * days + 365 * 60 * 24 * years;
417 hours = 0;
418 days = 0;
419 years = 0;
420 }
421
422 if (days == 0 && hours == 0 && minutes == 0) {
423 return (options & KFormat::HideSeconds) ? formatSingleAbbreviatedDuration(units: Minutes, n: minutes) : formatSingleAbbreviatedDuration(units: Seconds, n: seconds);
424 }
425
426 if (years != 0) {
427 if (days == 0) {
428 return formatSingleAbbreviatedDuration(units: Years, n: years);
429 }
430
431 //: @item:intext abbreviated amount of years and abbreviated amount of days
432 return tr(sourceText: "%1 %2").arg(args: formatSingleAbbreviatedDuration(units: Years, n: years), args: formatSingleAbbreviatedDuration(units: Days, n: days));
433 }
434
435 if (days != 0) {
436 if (hours == 0) {
437 return formatSingleAbbreviatedDuration(units: Days, n: days);
438 }
439
440 //: @item:intext abbreviated amount of days and abbreviated amount of hours
441 return tr(sourceText: "%1 %2").arg(args: formatSingleAbbreviatedDuration(units: Days, n: days), args: formatSingleAbbreviatedDuration(units: Hours, n: hours));
442 }
443
444 if (hours != 0) {
445 //: @item:intext abbreviated amount of hours and abbreviated amount of minutes
446 return tr(sourceText: "%1 %2").arg(args: formatSingleAbbreviatedDuration(units: Hours, n: hours), args: formatSingleAbbreviatedDuration(units: Minutes, n: minutes));
447 }
448
449 if (options & KFormat::HideSeconds) {
450 return formatSingleAbbreviatedDuration(units: Minutes, n: minutes);
451 }
452
453 //: @item:intext abbreviated amount of minutes and abbreviated amount of seconds
454 return tr(sourceText: "%1 %2").arg(args: formatSingleAbbreviatedDuration(units: Minutes, n: minutes), args: formatSingleAbbreviatedDuration(units: Seconds, n: seconds));
455 }
456
457 if ((options & KFormat::FoldHours) == KFormat::FoldHours && (options & KFormat::ShowMilliseconds) == KFormat::ShowMilliseconds) {
458 //: @item:intext Duration format minutes, seconds and milliseconds
459 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'));
460 } else if ((options & KFormat::FoldHours) == KFormat::FoldHours) {
461 //: @item:intext Duration format minutes and seconds
462 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'));
463 } else if ((options & KFormat::HideSeconds) == KFormat::HideSeconds) {
464 //: @item:intext Duration format hours and minutes
465 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'));
466 } else if ((options & KFormat::ShowMilliseconds) == KFormat::ShowMilliseconds) {
467 //: @item:intext Duration format hours, minutes, seconds, milliseconds
468 return tr(sourceText: "%1:%2:%3.%4")
469 .arg(a: hours, fieldWidth: 1, base: 10, fillChar: QLatin1Char('0'))
470 .arg(a: minutes, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'))
471 .arg(a: seconds, fieldWidth: 2, base: 10, fillChar: QLatin1Char('0'))
472 .arg(a: ms, fieldwidth: 3, base: 10, fillChar: QLatin1Char('0'));
473 }
474
475 //: @item:intext Duration format hours, minutes, seconds
476 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'));
477}
478
479QString KFormatPrivate::formatDecimalDuration(quint64 msecs, int decimalPlaces) const
480{
481 if (msecs >= MSecsInDay) {
482 //: @item:intext %1 is a real number, e.g. 1.23 days
483 return tr(sourceText: "%1 days").arg(a: m_locale.toString(f: msecs / (+MSecsInDay * 1.0), format: 'f', precision: decimalPlaces));
484 } else if (msecs >= MSecsInHour) {
485 //: @item:intext %1 is a real number, e.g. 1.23 hours
486 return tr(sourceText: "%1 hours").arg(a: m_locale.toString(f: msecs / (+MSecsInHour * 1.0), format: 'f', precision: decimalPlaces));
487 } else if (msecs >= MSecsInMinute) {
488 //: @item:intext %1 is a real number, e.g. 1.23 minutes
489 return tr(sourceText: "%1 minutes").arg(a: m_locale.toString(f: msecs / (+MSecsInMinute * 1.0), format: 'f', precision: decimalPlaces));
490 } else if (msecs >= MSecsInSecond) {
491 //: @item:intext %1 is a real number, e.g. 1.23 seconds
492 return tr(sourceText: "%1 seconds").arg(a: m_locale.toString(f: msecs / (+MSecsInSecond * 1.0), format: 'f', precision: decimalPlaces));
493 }
494 //: @item:intext %1 is a whole number
495 //~ singular %n millisecond
496 //~ plural %n milliseconds
497 return tr(sourceText: "%n millisecond(s)", disambiguation: nullptr, n: msecs);
498}
499
500static QString formatSingleDuration(DurationUnits units, int n)
501{
502 // NB: n is guaranteed to be non-negative
503 switch (units) {
504 case Years:
505 //: @item:intext %n is a whole number
506 //~ singular %n year
507 //~ plural %n years
508 return KFormatPrivate::tr(sourceText: "%n year(s)", disambiguation: nullptr, n);
509 case Days:
510 //: @item:intext %n is a whole number
511 //~ singular %n day
512 //~ plural %n days
513 return KFormatPrivate::tr(sourceText: "%n day(s)", disambiguation: nullptr, n);
514 case Hours:
515 //: @item:intext %n is a whole number
516 //~ singular %n hour
517 //~ plural %n hours
518 return KFormatPrivate::tr(sourceText: "%n hour(s)", disambiguation: nullptr, n);
519 case Minutes:
520 //: @item:intext %n is a whole number
521 //~ singular %n minute
522 //~ plural %n minutes
523 return KFormatPrivate::tr(sourceText: "%n minute(s)", disambiguation: nullptr, n);
524 case Seconds:
525 //: @item:intext %n is a whole number
526 //~ singular %n second
527 //~ plural %n seconds
528 return KFormatPrivate::tr(sourceText: "%n second(s)", disambiguation: nullptr, n);
529 }
530 Q_ASSERT(false);
531 return QString();
532}
533
534QString KFormatPrivate::formatSpelloutDuration(quint64 msecs) const
535{
536 quint64 ms = msecs;
537 int days = ms / MSecsInDay;
538 ms = ms % (MSecsInDay);
539 int hours = ms / MSecsInHour;
540 ms = ms % MSecsInHour;
541 int minutes = ms / MSecsInMinute;
542 ms = ms % MSecsInMinute;
543 int seconds = qRound(d: ms / 1000.0);
544
545 // Handle correctly problematic case #1 (look at KFormatTest::prettyFormatDuration())
546 if (seconds == 60) {
547 return formatSpelloutDuration(msecs: msecs - ms + MSecsInMinute);
548 }
549
550 if (days && hours) {
551 /*: @item:intext days and hours. This uses the previous item:intext messages.
552 If this does not fit the grammar of your language please contact the i18n team to solve the problem */
553 return tr(sourceText: "%1 and %2").arg(args: formatSingleDuration(units: Days, n: days), args: formatSingleDuration(units: Hours, n: hours));
554 } else if (days) {
555 return formatSingleDuration(units: Days, n: days);
556 } else if (hours && minutes) {
557 /*: @item:intext hours and minutes. This uses the previous item:intext messages.
558 If this does not fit the grammar of your language please contact the i18n team to solve the problem */
559 return tr(sourceText: "%1 and %2").arg(args: formatSingleDuration(units: Hours, n: hours), args: formatSingleDuration(units: Minutes, n: minutes));
560 } else if (hours) {
561 return formatSingleDuration(units: Hours, n: hours);
562 } else if (minutes && seconds) {
563 /*: @item:intext minutes and seconds. This uses the previous item:intext messages.
564 If this does not fit the grammar of your language please contact the i18n team to solve the problem */
565 return tr(sourceText: "%1 and %2").arg(args: formatSingleDuration(units: Minutes, n: minutes), args: formatSingleDuration(units: Seconds, n: seconds));
566 } else if (minutes) {
567 return formatSingleDuration(units: Minutes, n: minutes);
568 } else {
569 return formatSingleDuration(units: Seconds, n: seconds);
570 }
571}
572
573QString KFormatPrivate::formatRelativeDate(const QDate &date, QLocale::FormatType format) const
574{
575 if (!date.isValid()) {
576 return tr(sourceText: "Invalid date", disambiguation: "used when a relative date string can't be generated because the date is invalid");
577 }
578
579 const qint64 daysTo = QDate::currentDate().daysTo(d: date);
580 if (daysTo > 2 || daysTo < -2) {
581 return m_locale.toString(date, format);
582 }
583
584 switch (daysTo) {
585 case 2:
586 return tr(sourceText: "In two days");
587 case 1:
588 return tr(sourceText: "Tomorrow");
589 case 0:
590 return tr(sourceText: "Today");
591 case -1:
592 return tr(sourceText: "Yesterday");
593 case -2:
594 return tr(sourceText: "Two days ago");
595 }
596 Q_UNREACHABLE();
597}
598
599QString KFormatPrivate::formatRelativeDateTime(const QDateTime &dateTime, QLocale::FormatType format) const
600{
601 const QDateTime now = QDateTime::currentDateTime();
602
603 const auto secsToNow = dateTime.secsTo(now);
604 constexpr int secsInAHour = 60 * 60;
605 if (secsToNow >= 0 && secsToNow < secsInAHour) {
606 const int minutesToNow = secsToNow / 60;
607 if (minutesToNow <= 1) {
608 return tr(sourceText: "Just now");
609 } else if (format == QLocale::NarrowFormat) {
610 //: @item:intext %1 is a whole number, abbreviate minute value
611 //~ singular %n min ago
612 //~ plural %n mins ago
613 return tr(sourceText: "%n min(s) ago", disambiguation: nullptr, n: minutesToNow);
614 }
615 //: @item:intext %1 is a whole number
616 //~ singular %n minute ago
617 //~ plural %n minutes ago
618 return tr(sourceText: "%n minute(s) ago", disambiguation: nullptr, n: minutesToNow);
619 }
620 if (secsToNow <= 0 && -secsToNow < secsInAHour) {
621 const int minutesFromNow = -secsToNow / 60;
622 if (minutesFromNow < 1) {
623 return tr(sourceText: "Now");
624 } else if (format == QLocale::NarrowFormat) {
625 //: @item:intext %1 is a whole number, abbreviate minute value
626 //~ singular %n min
627 //~ plural %n mins
628 return tr(sourceText: "%n min(s)", disambiguation: nullptr, n: minutesFromNow);
629 }
630 //: @item:intext %1 is a whole number
631 //~ singular In %n minute
632 //~ plural In %n minutes
633 return tr(sourceText: "In %n minute(s)", disambiguation: nullptr, n: minutesFromNow);
634 }
635
636 const auto timeFormatType = format == QLocale::FormatType::LongFormat ? QLocale::FormatType::ShortFormat : format;
637 const qint64 daysToNow = dateTime.daysTo(now);
638 QString dateString;
639 if (daysToNow < 2 && daysToNow > -2) {
640 dateString = formatRelativeDate(date: dateTime.date(), format);
641 } else {
642 dateString = m_locale.toString(date: dateTime.date(), format);
643 }
644
645 /*: relative datetime with %1 result of QLocale.toString(date, format) or formatRelativeDate
646 and %2 result of QLocale.toString(time, timeformatType)
647 If this does not fit the grammar of your language please contact the i18n team to solve the problem */
648 QString formattedDate = tr(sourceText: "%1 at %2").arg(args&: dateString, args: m_locale.toString(time: dateTime.time(), format: timeFormatType));
649
650 return formattedDate.replace(i: 0, len: 1, after: formattedDate.at(i: 0).toUpper());
651}
652
653[[nodiscard]] static bool needsTimeZone(const QDateTime &dt)
654{
655 if (dt.timeSpec() == Qt::LocalTime) {
656 return false;
657 }
658 return dt.timeZone().offsetFromUtc(atDateTime: dt) != QTimeZone::systemTimeZone().offsetFromUtc(atDateTime: dt);
659}
660
661QString KFormatPrivate::formatTime(const QDateTime &dateTime, QLocale::FormatType format, KFormat::TimeFormatOptions options) const
662{
663 auto output = m_locale.toString(time: dateTime.time(), format);
664 if (options == KFormat::DoNotAddTimeZone || dateTime.timeSpec() == Qt::LocalTime
665 || ((options & KFormat::AddTimezoneAbbreviationIfNeeded) && !needsTimeZone(dt: dateTime))) {
666 return output;
667 }
668
669 QString tzAbbr;
670 const auto tz = dateTime.timeZone();
671 if (tz.hasDaylightTime()) {
672 tzAbbr = tz.displayName(timeType: tz.isDaylightTime(atDateTime: dateTime) ? QTimeZone::DaylightTime : QTimeZone::StandardTime, nameType: QTimeZone::ShortName, locale: m_locale);
673 } else {
674 tzAbbr = tz.displayName(timeType: QTimeZone::GenericTime, nameType: QTimeZone::ShortName, locale: m_locale);
675 }
676 if (tzAbbr.isEmpty()) {
677 return output;
678 }
679 /*: %1 is a formatted time (from QLocale.toString), %2 is a localized timezone abbreviation (from QTimeZone::displayName(QTimeZone::ShortName)). */
680 return tr(sourceText: "%1 %2").arg(args&: output, args&: tzAbbr);
681}
682
683QString KFormatPrivate::formatDistance(double distance, KFormat::DistanceFormatOptions options) const
684{
685 if (((options & KFormat::MetricDistanceUnits) == 0)
686 && (m_locale.measurementSystem() == QLocale::ImperialUSSystem || m_locale.measurementSystem() == QLocale::ImperialUSSystem)) {
687 return formatImperialDistance(distance);
688 }
689 return formatMetricDistance(distance);
690}
691
692[[nodiscard]] QString KFormatPrivate::formatImperialDistance(double distance) const
693{
694 const auto feet = distance / 0.3048;
695 if (feet < 500.0) {
696 return KFormatPrivate::tr(sourceText: "%1 ft", disambiguation: "distance in feet").arg(a: m_locale.toString(i: (int)std::round(x: feet)));
697 }
698 const auto miles = distance / 1609.344;
699 if (miles < 10.0) {
700 return KFormatPrivate::tr(sourceText: "%1 mi", disambiguation: "distance in miles").arg(a: m_locale.toString(f: (int)(std::round(x: miles * 10.0)) / 10.0));
701 }
702 return KFormatPrivate::tr(sourceText: "%1 mi", disambiguation: "distance in miles").arg(a: m_locale.toString(i: (int)std::round(x: miles)));
703}
704
705[[nodiscard]] QString KFormatPrivate::formatMetricDistance(double distance) const
706{
707 if (distance < 1000.0) {
708 return KFormatPrivate::tr(sourceText: "%1 m", disambiguation: "distance in meter").arg(a: m_locale.toString(i: (int)std::round(x: distance)));
709 }
710 if (distance < 10000.0) {
711 return KFormatPrivate::tr(sourceText: "%1 km", disambiguation: "distance in kilometer").arg(a: m_locale.toString(f: (int)(std::round(x: distance / 100.0)) / 10.0));
712 }
713 return KFormatPrivate::tr(sourceText: "%1 km", disambiguation: "distance in kilometer").arg(a: m_locale.toString(i: (int)std::round(x: distance / 1000.0)));
714}
715

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