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 | |
21 | KFormatPrivate::KFormatPrivate(const QLocale &locale) |
22 | : m_locale(locale) |
23 | { |
24 | } |
25 | |
26 | KFormatPrivate::~KFormatPrivate() |
27 | { |
28 | } |
29 | |
30 | constexpr 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 | |
35 | QString 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 | |
167 | QString 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 | |
311 | enum TimeConstants { |
312 | MSecsInDay = 86400000, |
313 | MSecsInHour = 3600000, |
314 | MSecsInMinute = 60000, |
315 | MSecsInSecond = 1000, |
316 | }; |
317 | |
318 | QString 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 | |
385 | QString 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 | |
406 | enum DurationUnits { |
407 | Days = 0, |
408 | Hours, |
409 | Minutes, |
410 | Seconds, |
411 | }; |
412 | |
413 | static 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 | |
442 | QString 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 | |
481 | QString 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 | |
507 | QString 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 | |