/src/serenity/AK/NumberFormat.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2020-2022, the SerenityOS developers. |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <AK/Assertions.h> |
8 | | #include <AK/NumberFormat.h> |
9 | | #include <AK/NumericLimits.h> |
10 | | |
11 | | namespace AK { |
12 | | |
13 | | // FIXME: Remove this hackery once printf() supports floats. |
14 | | static String number_string_with_one_decimal(u64 number, u64 unit, StringView suffix, UseThousandsSeparator use_thousands_separator) |
15 | 0 | { |
16 | 0 | constexpr auto max_unit_size = NumericLimits<u64>::max() / 10; |
17 | 0 | VERIFY(unit < max_unit_size); |
18 | | |
19 | 0 | auto integer_part = number / unit; |
20 | 0 | auto decimal_part = (number % unit) * 10 / unit; |
21 | 0 | if (use_thousands_separator == UseThousandsSeparator::Yes) |
22 | 0 | return MUST(String::formatted("{:'d}.{} {}", integer_part, decimal_part, suffix)); |
23 | | |
24 | 0 | return MUST(String::formatted("{}.{} {}", integer_part, decimal_part, suffix)); |
25 | 0 | } |
26 | | |
27 | | String human_readable_quantity(u64 quantity, HumanReadableBasedOn based_on, StringView unit, UseThousandsSeparator use_thousands_separator) |
28 | 0 | { |
29 | 0 | u64 size_of_unit = based_on == HumanReadableBasedOn::Base2 ? 1024 : 1000; |
30 | 0 | constexpr auto unit_prefixes = AK::Array { "", "K", "M", "G", "T", "P", "E" }; |
31 | 0 | auto full_unit_suffix = [&](int index) { |
32 | 0 | auto binary_infix = (based_on == HumanReadableBasedOn::Base2 && index != 0) ? "i"sv : ""sv; |
33 | 0 | return MUST(String::formatted("{}{}{}", |
34 | 0 | unit_prefixes[index], binary_infix, unit)); |
35 | 0 | }; |
36 | |
|
37 | 0 | auto size_of_current_unit = size_of_unit; |
38 | |
|
39 | 0 | if (quantity < size_of_unit) |
40 | 0 | return MUST(String::formatted("{} {}", quantity, full_unit_suffix(0))); |
41 | | |
42 | 0 | for (size_t i = 1; i < unit_prefixes.size() - 1; i++) { |
43 | 0 | auto suffix = full_unit_suffix(i); |
44 | 0 | if (quantity < size_of_unit * size_of_current_unit) { |
45 | 0 | return number_string_with_one_decimal(quantity, size_of_current_unit, suffix, use_thousands_separator); |
46 | 0 | } |
47 | | |
48 | 0 | size_of_current_unit *= size_of_unit; |
49 | 0 | } |
50 | | |
51 | 0 | return number_string_with_one_decimal(quantity, |
52 | 0 | size_of_current_unit, full_unit_suffix(unit_prefixes.size() - 1), use_thousands_separator); |
53 | 0 | } |
54 | | |
55 | | String human_readable_size(u64 size, HumanReadableBasedOn based_on, UseThousandsSeparator use_thousands_separator) |
56 | 0 | { |
57 | 0 | return human_readable_quantity(size, based_on, "B"sv, use_thousands_separator); |
58 | 0 | } |
59 | | |
60 | | String human_readable_size_long(u64 size, UseThousandsSeparator use_thousands_separator) |
61 | 0 | { |
62 | 0 | if (size < 1 * KiB) { |
63 | 0 | if (use_thousands_separator == UseThousandsSeparator::Yes) |
64 | 0 | return MUST(String::formatted("{:'d} bytes", size)); |
65 | | |
66 | 0 | return MUST(String::formatted("{} bytes", size)); |
67 | 0 | } |
68 | | |
69 | 0 | auto human_readable_size_string = human_readable_size(size, HumanReadableBasedOn::Base2, use_thousands_separator); |
70 | 0 | if (use_thousands_separator == UseThousandsSeparator::Yes) |
71 | 0 | return MUST(String::formatted("{} ({:'d} bytes)", human_readable_size_string, size)); |
72 | | |
73 | 0 | return MUST(String::formatted("{} ({} bytes)", human_readable_size_string, size)); |
74 | 0 | } |
75 | | |
76 | | String human_readable_time(i64 time_in_seconds) |
77 | 0 | { |
78 | 0 | auto days = time_in_seconds / 86400; |
79 | 0 | time_in_seconds = time_in_seconds % 86400; |
80 | |
|
81 | 0 | auto hours = time_in_seconds / 3600; |
82 | 0 | time_in_seconds = time_in_seconds % 3600; |
83 | |
|
84 | 0 | auto minutes = time_in_seconds / 60; |
85 | 0 | time_in_seconds = time_in_seconds % 60; |
86 | |
|
87 | 0 | StringBuilder builder; |
88 | |
|
89 | 0 | if (days > 0) |
90 | 0 | builder.appendff("{} day{} ", days, days == 1 ? "" : "s"); |
91 | |
|
92 | 0 | if (hours > 0) |
93 | 0 | builder.appendff("{} hour{} ", hours, hours == 1 ? "" : "s"); |
94 | |
|
95 | 0 | if (minutes > 0) |
96 | 0 | builder.appendff("{} minute{} ", minutes, minutes == 1 ? "" : "s"); |
97 | |
|
98 | 0 | if (time_in_seconds > 0 || days + hours + minutes == 0) |
99 | 0 | builder.appendff("{} second{}", time_in_seconds, time_in_seconds == 1 ? "" : "s"); |
100 | |
|
101 | 0 | return MUST(builder.to_string()); |
102 | 0 | } |
103 | | |
104 | | String human_readable_digital_time(i64 time_in_seconds) |
105 | 0 | { |
106 | 0 | auto hours = time_in_seconds / 3600; |
107 | 0 | time_in_seconds = time_in_seconds % 3600; |
108 | |
|
109 | 0 | auto minutes = time_in_seconds / 60; |
110 | 0 | time_in_seconds = time_in_seconds % 60; |
111 | |
|
112 | 0 | StringBuilder builder; |
113 | |
|
114 | 0 | if (hours > 0) |
115 | 0 | builder.appendff("{:02}:", hours); |
116 | 0 | builder.appendff("{:02}:", minutes); |
117 | 0 | builder.appendff("{:02}", time_in_seconds); |
118 | |
|
119 | 0 | return MUST(builder.to_string()); |
120 | 0 | } |
121 | | |
122 | | } |