Skip to content

Commit ae73280

Browse files
committed
AK+Tests: Add a formatter for Duration
This will format Duration as seconds, with as much decimal precision as necessary to fully represent its value. The alternate format specifier can be used to make it print the units on the end, i.e. "1.23s".
1 parent c8958c9 commit ae73280

File tree

3 files changed

+147
-0
lines changed

3 files changed

+147
-0
lines changed

AK/Time.cpp

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,113 @@ Duration Duration::from_half_sanitized(i64 seconds, i32 extra_seconds, u32 nanos
218218
return Duration { seconds + extra_seconds, nanoseconds };
219219
}
220220

221+
ErrorOr<void> Formatter<Duration>::format(FormatBuilder& builder, Duration value)
222+
{
223+
if (value.m_nanoseconds >= 1'000'000'000)
224+
return builder.put_string("{ INVALID }"sv);
225+
226+
auto align = m_align;
227+
if (align == FormatBuilder::Align::Default)
228+
align = FormatBuilder::Align::Right;
229+
230+
auto sign_mode = m_sign_mode;
231+
if (sign_mode == FormatBuilder::SignMode::Default)
232+
sign_mode = FormatBuilder::SignMode::OnlyIfNeeded;
233+
234+
auto align_width = m_width.value_or(0);
235+
236+
u8 base;
237+
bool upper_case = false;
238+
if (m_mode == Mode::Default || m_mode == Mode::FixedPoint) {
239+
base = 10;
240+
} else if (m_mode == Mode::Hexfloat) {
241+
base = 16;
242+
} else if (m_mode == Mode::HexfloatUppercase) {
243+
base = 16;
244+
upper_case = true;
245+
} else if (m_mode == Mode::Binary) {
246+
base = 2;
247+
} else if (m_mode == Mode::BinaryUppercase) {
248+
base = 2;
249+
upper_case = true;
250+
} else if (m_mode == Mode::Octal) {
251+
base = 8;
252+
} else {
253+
VERIFY_NOT_REACHED();
254+
}
255+
256+
auto is_negative = value.m_seconds < 0;
257+
auto seconds = is_negative ? 0 - static_cast<u64>(value.m_seconds) : static_cast<u64>(value.m_seconds);
258+
auto nanoseconds = value.m_nanoseconds;
259+
if (is_negative && nanoseconds > 0) {
260+
seconds--;
261+
nanoseconds = 1'000'000'000 - nanoseconds;
262+
}
263+
264+
VERIFY(nanoseconds < 1'000'000'000);
265+
266+
size_t integer_width = 1;
267+
if (seconds != 0) {
268+
auto remaining_seconds = seconds / 10;
269+
while (remaining_seconds != 0) {
270+
remaining_seconds /= base;
271+
integer_width++;
272+
}
273+
}
274+
if (sign_mode != FormatBuilder::SignMode::OnlyIfNeeded)
275+
integer_width++;
276+
277+
constexpr size_t nanoseconds_length = 9;
278+
size_t precision = 0;
279+
u64 nanoseconds_to_precision = nanoseconds;
280+
if (m_precision.has_value()) {
281+
precision = min(m_precision.value(), nanoseconds_length);
282+
for (size_t i = nanoseconds_length; i > precision; i--)
283+
nanoseconds_to_precision /= base;
284+
} else if (nanoseconds_to_precision != 0) {
285+
auto trailing_zeroes = 0;
286+
while ((nanoseconds_to_precision % base) == 0) {
287+
nanoseconds_to_precision /= base;
288+
trailing_zeroes++;
289+
}
290+
precision = nanoseconds_length - trailing_zeroes;
291+
}
292+
293+
size_t non_integer_width = 0;
294+
if (precision != 0)
295+
non_integer_width = precision + 1;
296+
if (m_alternative_form)
297+
non_integer_width++;
298+
299+
auto total_width = integer_width + non_integer_width;
300+
301+
size_t integer_align_width = 0;
302+
if (align == FormatBuilder::Align::Right)
303+
integer_align_width = Checked<size_t>::saturating_sub(align_width, non_integer_width);
304+
else if (align == FormatBuilder::Align::Center)
305+
integer_align_width = integer_width + Checked<size_t>::saturating_sub(align_width, total_width) / 2;
306+
TRY(builder.put_u64(seconds, base, false, upper_case, m_zero_pad, m_use_separator, FormatBuilder::Align::Right, integer_align_width, m_fill, m_sign_mode, is_negative));
307+
308+
if (nanoseconds_to_precision != 0) {
309+
TRY(builder.builder().try_append('.'));
310+
TRY(builder.put_u64(nanoseconds_to_precision, base, false, upper_case, true, m_use_separator, FormatBuilder::Align::Right, precision));
311+
if (m_precision.has_value() && m_precision.value() > nanoseconds_length) {
312+
auto zeroes = m_precision.value() - nanoseconds_length;
313+
TRY(builder.put_padding('0', zeroes));
314+
}
315+
}
316+
317+
if (m_alternative_form)
318+
TRY(builder.builder().try_append('s'));
319+
320+
if (align_width > 0 && align != FormatBuilder::Align::Right) {
321+
auto padding_width = Checked<size_t>::saturating_sub(align_width, max(integer_width, integer_align_width) + non_integer_width);
322+
TRY(builder.builder().try_append_repeated(m_fill, padding_width));
323+
}
324+
325+
return {};
326+
}
327+
221328
namespace {
222329

223330
#if defined(AK_OS_WINDOWS)

AK/Time.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,8 @@ class Duration {
353353
}
354354

355355
private:
356+
friend struct Formatter<Duration>;
357+
356358
constexpr explicit Duration(i64 seconds, u32 nanoseconds)
357359
: m_seconds(seconds)
358360
, m_nanoseconds(nanoseconds)
@@ -365,6 +367,11 @@ class Duration {
365367
u32 m_nanoseconds { 0 }; // Always less than 1'000'000'000
366368
};
367369

370+
template<>
371+
struct Formatter<Duration> : StandardFormatter {
372+
ErrorOr<void> format(FormatBuilder&, Duration);
373+
};
374+
368375
namespace Detail {
369376

370377
// Common base class for all unaware time types.

Tests/AK/TestFormat.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include <AK/ByteString.h>
1010
#include <AK/StringBuilder.h>
11+
#include <AK/Time.h>
1112
#include <AK/Vector.h>
1213

1314
#ifdef AK_OS_WINDOWS
@@ -479,3 +480,35 @@ TEST_CASE(format_checked)
479480
EXPECT_EQ(ByteString::formatted("{}", c), "{ OVERFLOW }");
480481
}
481482
}
483+
484+
TEST_CASE(format_duration)
485+
{
486+
EXPECT_EQ(ByteString::formatted("{}", AK::Duration::from_seconds(0)), "0");
487+
EXPECT_EQ(ByteString::formatted("{}", AK::Duration::from_seconds(NumericLimits<i64>::max())), "9223372036854775807");
488+
EXPECT_EQ(ByteString::formatted("{}", AK::Duration::from_seconds(NumericLimits<i64>::min())), "-9223372036854775808");
489+
490+
EXPECT_EQ(ByteString::formatted("{}", AK::Duration::from_microseconds(6'500)), "0.0065");
491+
EXPECT_EQ(ByteString::formatted("{}", AK::Duration::from_microseconds(-6'500)), "-0.0065");
492+
EXPECT_EQ(ByteString::formatted("{}", AK::Duration::from_milliseconds(-1'500)), "-1.5");
493+
494+
EXPECT_EQ(ByteString::formatted("{}", AK::Duration::from_nanoseconds(1)), "0.000000001");
495+
EXPECT_EQ(ByteString::formatted("{}", AK::Duration::from_nanoseconds(999'999'999)), "0.999999999");
496+
EXPECT_EQ(ByteString::formatted("{}", AK::Duration::from_nanoseconds(-999'999'999)), "-0.999999999");
497+
498+
EXPECT_EQ(ByteString::formatted("{:05}", AK::Duration::from_seconds(1)), "00001");
499+
EXPECT_EQ(ByteString::formatted("{:8}", AK::Duration::from_milliseconds(1'250)), " 1.25");
500+
EXPECT_EQ(ByteString::formatted("{:|>4}", AK::Duration::from_seconds(1)), "|||1");
501+
EXPECT_EQ(ByteString::formatted("{:.<20}", AK::Duration::from_nanoseconds(1'050'250'000'001)), "1050.250000001......");
502+
EXPECT_EQ(ByteString::formatted("{:^5}", AK::Duration::from_milliseconds(1'500)), " 1.5 ");
503+
EXPECT_EQ(ByteString::formatted("{:^+6}", AK::Duration::from_milliseconds(1'500)), " +1.5 ");
504+
505+
EXPECT_EQ(ByteString::formatted("{:.10}", AK::Duration::from_milliseconds(100'000'500)), "100000.5000000000");
506+
EXPECT_EQ(ByteString::formatted("{:.0}", AK::Duration::from_milliseconds(67'500)), "67");
507+
EXPECT_EQ(ByteString::formatted("{:.0}", AK::Duration::from_nanoseconds(123'456'789)), "0");
508+
EXPECT_EQ(ByteString::formatted("{:.3}", AK::Duration::from_nanoseconds(123'456'789)), "0.123");
509+
EXPECT_EQ(ByteString::formatted("{:.9}", AK::Duration::from_milliseconds(500)), "0.500000000");
510+
EXPECT_EQ(ByteString::formatted("{:.21}", AK::Duration::from_milliseconds(500)), "0.500000000000000000000");
511+
512+
EXPECT_EQ(ByteString::formatted("{:#}", AK::Duration::from_milliseconds(12'054)), "12.054s");
513+
EXPECT_EQ(ByteString::formatted("{:^#8}", AK::Duration::from_milliseconds(1'512)), " 1.512s ");
514+
}

0 commit comments

Comments
 (0)