From 42cd2e65e626ca803deb897a76d33d86563842b0 Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Sun, 27 Jun 2021 03:09:53 +0300 Subject: [PATCH] Change Duration.toString format KT-42851 --- .../samples/test/samples/time/durations.kt | 12 +-- libraries/stdlib/src/kotlin/time/Duration.kt | 76 ++++++++++++------- libraries/stdlib/test/time/DurationTest.kt | 63 +++++++-------- 3 files changed, 89 insertions(+), 62 deletions(-) diff --git a/libraries/stdlib/samples/test/samples/time/durations.kt b/libraries/stdlib/samples/test/samples/time/durations.kt index 26794728a37..58042f4f122 100644 --- a/libraries/stdlib/samples/test/samples/time/durations.kt +++ b/libraries/stdlib/samples/test/samples/time/durations.kt @@ -25,12 +25,12 @@ class Durations { @Sample fun toStringDefault() { - assertPrints(Duration.days(45), "45.0d") - assertPrints(Duration.days(1.5), "36.0h") - assertPrints(Duration.minutes(1230), "20.5h") - assertPrints(Duration.minutes(920), "920m") - assertPrints(Duration.seconds(1.546), "1.55s") - assertPrints(Duration.milliseconds(25.12), "25.1ms") + assertPrints(Duration.days(45), "1080h") + assertPrints(Duration.days(1.5), "36h") + assertPrints(Duration.minutes(1230), "20h 30m") + assertPrints(Duration.minutes(920), "15h 20m") + assertPrints(Duration.seconds(1.546), "1.546s") + assertPrints(Duration.milliseconds(25.12), "0.025120s") } @Sample diff --git a/libraries/stdlib/src/kotlin/time/Duration.kt b/libraries/stdlib/src/kotlin/time/Duration.kt index 3092c0b7a99..c74ff74fdf9 100644 --- a/libraries/stdlib/src/kotlin/time/Duration.kt +++ b/libraries/stdlib/src/kotlin/time/Duration.kt @@ -639,15 +639,25 @@ public value class Duration internal constructor(private val rawValue: Long) : C public fun toLongMilliseconds(): Long = inWholeMilliseconds /** - * Returns a string representation of this duration value expressed in the unit which yields the most compact and readable number value. + * Returns a string representation of this duration value + * expressed as a combination of numeric components, each in its own unit. + * + * Each component is a number followed by the unit abbreviated name: `d`, `h`, `m`, `s`: + * `5h`, `1d 12h`, `1h 0m 30.3340s`. + * The last component, usually seconds, can be a number with a fractional part. + * + * If the duration is less than a second, it is represented as a single number + * with one of sub-second units: `ms` (milliseconds), `us` (microseconds), or `ns` (nanoseconds): + * `140.884ms`, `500us`, `24ns`. + * + * A negative duration is prefixed with `-` sign and, if it consists of multiple components, surrounded with parentheses: + * `-12m` and `-(1h 30m)`. * * Special cases: - * - zero duration is formatted as `"0s"` - * - the infinite duration is formatted as `"Infinity"` without unit - * - very small durations (less than 1e-15 s) are expressed in seconds and formatted in scientific notation - * - very big durations (more than 1e+7 days) are expressed in days and formatted in scientific notation + * - an infinite duration is formatted as `"Infinity"` or `"-Infinity"` without a unit. * - * @return the value of duration in the automatically determined unit followed by that unit abbreviated name: `d`, `h`, `m`, `s`, `ms`, `us`, or `ns`. + * It's recommended to use [toIsoString] that uses more strict ISO-8601 format instead of this `toString` + * when you want to convert a duration to a string in cases of serialization, interchange, etc. * * @sample samples.time.Durations.toStringDefault */ @@ -656,27 +666,41 @@ public value class Duration internal constructor(private val rawValue: Long) : C INFINITE.rawValue -> "Infinity" NEG_INFINITE.rawValue -> "-Infinity" else -> { - val absNs = absoluteValue.toDouble(DurationUnit.NANOSECONDS) - var scientific = false - var maxDecimals = 0 - val unit = when { - absNs < 1e-6 -> DurationUnit.SECONDS.also { scientific = true } - absNs < 1 -> DurationUnit.NANOSECONDS.also { maxDecimals = 7 } - absNs < 1e3 -> DurationUnit.NANOSECONDS - absNs < 1e6 -> DurationUnit.MICROSECONDS - absNs < 1e9 -> DurationUnit.MILLISECONDS - absNs < 1000e9 -> DurationUnit.SECONDS - absNs < 60_000e9 -> DurationUnit.MINUTES - absNs < 3600_000e9 -> DurationUnit.HOURS - absNs < 86400e9 * 1e7 -> DurationUnit.DAYS - else -> DurationUnit.DAYS.also { scientific = true } + buildString { + if (isNegative()) append('-') + absoluteValue.run { + toComponents { _, minutes, seconds, nanoseconds -> + val hours = inWholeHours + val hasHours = hours != 0L + val hasSeconds = seconds != 0 || nanoseconds != 0 + if (hasHours) { + append(hours) + append('h') + } + if (minutes != 0 || (hasSeconds && hasHours)) { + if (length > 1) append(' ') + append(minutes) + append('m') + } + if (hasSeconds) { + if (length > 1) append(' ') + append(seconds) + if (nanoseconds != 0) { + append('.') + when { + (nanoseconds % 1_000_000 == 0) -> + append((nanoseconds / 1_000_000).toString().padStart(3, '0')) + (nanoseconds % 1_000 == 0) -> + append((nanoseconds / 1_000).toString().padStart(6, '0')) + else -> + append(nanoseconds.toString().padStart(9, '0')) + } + } + append('s') + } + } + } } - val value = toDouble(unit) - when { - scientific -> formatScientific(value) - maxDecimals > 0 -> formatUpToDecimals(value, maxDecimals) - else -> formatToExactDecimals(value, precision(abs(value))) - } + unit.shortName() } } diff --git a/libraries/stdlib/test/time/DurationTest.kt b/libraries/stdlib/test/time/DurationTest.kt index 6a85ce18284..0ac24b03b78 100644 --- a/libraries/stdlib/test/time/DurationTest.kt +++ b/libraries/stdlib/test/time/DurationTest.kt @@ -543,40 +543,43 @@ class DurationTest { assertEquals("-$actual", (-duration).toString()) } - test(Duration.days(101), "101d") - test(Duration.days(45.3), "45.3d") - test(Duration.days(45), "45.0d") + test(Duration.days(101), "2424h") + test(Duration.days(45.3), "1087h 12m") // 1080h + 7.2h + test(Duration.days(45), "1080h") test(Duration.days(40.5), "972h") - test(Duration.hours(40) + Duration.minutes(15), "40.3h", "40.2h") - test(Duration.hours(40), "40.0h") + test(Duration.hours(40) + Duration.minutes(15), "40h 15m") + test(Duration.hours(40), "40h") - test(Duration.hours(12.5), "750m") - test(Duration.minutes(30), "30.0m") - test(Duration.minutes(17.5), "17.5m") + test(Duration.hours(12.5), "12h 30m") + test(Duration.hours(12) + Duration.seconds(15), "12h 0m 15s") + test(Duration.hours(12) + Duration.nanoseconds(1), "12h 0m 0.000000001s") + test(Duration.minutes(30), "30m") + test(Duration.minutes(17.5), "17m 30s") - test(Duration.minutes(16.5), "990s") - test(Duration.seconds(90.36), "90.4s") - test(Duration.seconds(50), "50.0s") - test(Duration.seconds(1.3), "1.30s") - test(Duration.seconds(1), "1.00s") + test(Duration.minutes(16.5), "16m 30s") + test(Duration.seconds(1097.1), "18m 17.099999999s") // Double to Long ns truncation! + test(Duration.seconds(90.36), "1m 30.360s") + test(Duration.seconds(50), "50s") + test(Duration.seconds(1.3), "1.300s") + test(Duration.seconds(1), "1s") - test(Duration.seconds(0.5), "500ms") - test(Duration.milliseconds(40.2), "40.2ms") - test(Duration.milliseconds(4.225), "4.23ms", "4.22ms") - test(Duration.milliseconds(4.245), "4.25ms") - test(Duration.milliseconds(1), "1.00ms") + test(Duration.seconds(0.5), "0.500s") + test(Duration.milliseconds(40.2), "0.040200s") + test(Duration.milliseconds(4.225), "0.004225s") + test(Duration.milliseconds(4.245), "0.004245s") + test(Duration.milliseconds(1), "0.001s") - test(Duration.milliseconds(0.75), "750us") - test(Duration.microseconds(75.35), "75.4us", "75.3us") - test(Duration.microseconds(7.25), "7.25us") - test(Duration.microseconds(1.035), "1.04us", "1.03us") - test(Duration.microseconds(1.005), "1.01us", "1.00us") + test(Duration.milliseconds(0.75), "0.000750s") + test(Duration.microseconds(75.35), "0.000075350s") + test(Duration.microseconds(7.25), "0.000007250s") + test(Duration.microseconds(1.035), "0.000001035s") + test(Duration.microseconds(1.005), "0.000001004s") // Double to Long ns truncation! - test(Duration.nanoseconds(950.5), "951ns", "950ns") - test(Duration.nanoseconds(85.23), "85.0ns") - test(Duration.nanoseconds(8.235), "8.00ns") - test(Duration.nanoseconds(1.3), "1.00ns") + test(Duration.nanoseconds(950.5), "0.000000950s") + test(Duration.nanoseconds(85.23), "0.000000085s") + test(Duration.nanoseconds(8.235), "0.000000008s") + test(Duration.nanoseconds(1.3), "0.000000001s") // equal to zero // test(Duration.nanoseconds(0.75), "0.75ns") @@ -586,9 +589,9 @@ class DurationTest { // test(Duration.nanoseconds(0.0000035), "0.0000035ns") test(Duration.ZERO, "0s") - test(Duration.days(365) * 10000, "3650000d") - test(Duration.days(300) * 100000, "3.00e+7d") - test(Duration.days(365) * 100000, "3.65e+7d") + test(Duration.days(365) * 10000, "87600000h") + test(Duration.days(300) * 100000, "720000000h") + test(Duration.days(365) * 100000, "876000000h") // all infinite // val universeAge = Duration.days(365.25) * 13.799e9