moment / luxon

⏱ A library for working with dates and times in JS
https://moment.github.io/luxon
MIT License
15.05k stars 728 forks source link

Duration.toFormat does not display milliseconds correctly #1571

Closed GravlLift closed 5 months ago

GravlLift commented 5 months ago

Describe the bug When using Duration.toFormat with S tokens, milliseconds will always render with all their digits, regardless of how many S tokens are used in the format string, thus making it impossible to render durations as tenths or hundreds of a second.

To Reproduce

Duration.fromObject({milliseconds: 100}).toFormat("mm:ss.S") // '00:00.100'
Duration.fromObject({milliseconds: 25}).toFormat("mm:ss.S") // '00:00.25'
Duration.fromObject({milliseconds: 0}).toFormat("mm:ss.S") // '00:00.0'

Actual vs Expected behavior A clear and concise description of what you expected to happen.

Desktop (please complete the following information):

Additional context I'm guessing this is because milliseconds is the only unit you'd want rounded in this way (cutting off precision).

icambron commented 5 months ago

The number of S's determines the padding on the string, nothing more:

Duration.fromObject({milliseconds: 25}).toFormat("S") //=> '25'
Duration.fromObject({milliseconds: 25}).toFormat("SS") //=> '25'
Duration.fromObject({milliseconds: 25}).toFormat("SSS") //=> '025'
Duration.fromObject({milliseconds: 25}).toFormat("SSSS") //=> '0025'

It is not meant to round to the same number of digits as there are S's, and that would be confusing, because then they wouldn't be milliseconds anymore. So there's no bug here. The idea is that if you're putting them after a decimal point, you want SSS; but it also might be just standalone, like "'that took' s 'seconds,' S 'milliseconds'", so you might just want the minimum digits.

But on to what you're trying to accomplish. We don't have format tokens for deciseconds or centiseconds. I'm hesitant to add them; that would immediately bring up questions of whether it should use Math.floor(), or Math.round(), or round toward zero (durations can be negative), or simply leave the decimals in so they can be displayed too. And padding too: for centiseconds you want to pad because you're using it after a decimal point but "'that took' K 'hundredths of a second'" would not want padding. That's a lot of options to represent in format tokens.

There are a few options for how to accomplish what you're looking for. Personally I like this one:

function myFormat(duration) {
   return duration
     .mapUnits((val, unit) => unit === "milliseconds" ? Math.round(val / 10) : val) // use whatever computation you want here
     .toFormat( "mm:ss.SS"); // replace with your desired format
}

myFormat(Duration.fromObject({ milliseconds: 25 })) //=> '00:00.03'

That seems simple enough and is super flexible, so I'm inclined to leave it there.