skip to Main Content

If this seems similar to the other question "toLocaleTimeString() always showing leading zero", that’s because it is.

I want to format a time string to mm:ss without a leading zero for minutes, like 9:09. However, I’m only getting 09:09 when using Date.toLocaleTimeString().

The difference with the question that I linked above is that I’m already using minute: "numeric" in my DateTimeFormat options:

const date = new Date(Date.now());
date.setMinutes(9,9); // so we're sure to catch leading zeros

const timeOptions = {
  minute: "numeric", // without leading zero
  second: "2-digit", // with leading zero
}

const dateString = date.toLocaleTimeString("en-US", timeOptions)

// ❌ I expect "9:09", I get "09:09"
console.log(dateString);

Interestingly, when changing options to get only numeric minutes (no seconds), it works as expected:

const date = new Date(Date.now());
date.setMinutes(9,9); // so we're sure to catch leading zeros

const timeOptions = {
  minute: "numeric", // without leading zero
  // second: "2-digit", // 👈 without seconds this time!
}

const dateString = date.toLocaleTimeString("en-US", timeOptions)

// ✅ I expect "9", I get "9"
console.log(dateString);

I’ve been through the full MDN docs on DateTimeFormat and couldn’t find anything to explain this. I’ve also tried this on the latest Chrome and Firefox, with the same results.

So my question(s):

  • is it just me? (e.g. could it be a localization issue? I’m explicitly setting the locale to en-US in my example to try to avoid this, but who knows)
  • how can I get this to work?

Thank you!

2

Answers


  1. Chosen as BEST ANSWER

    (Note: I'm accepting Kooilnc's answer since it was correct and shared a helpful library to help format dates in a more predictable way. This is meant to add some more detailed findings about and Date and localization.)


    DateTimeFormat options work in mysterious ways

    The first thing I missed when asking this question is that I misunderstood DateTimeFormat options. The day, hour, minute and second options accept the following values (example for the day option):

    day

    The representation of the day. Possible values are:

    • "numeric" (e.g., 1)
    • "2-digit" (e.g., 01)

    As a result, I assumed that to format minutes and seconds without a leading 0 for minutes, the following would work (snippet copied from the question):

    const date = new Date(Date.now());
    date.setMinutes(9,9); // so we're sure to catch leading zeros
    
    const timeOptions = {
      minute: "numeric", // without leading zero
      second: "2-digit", // with leading zero
    }
    
    const dateString = date.toLocaleTimeString("en-US", timeOptions)
    
    // ❌ I expect "9:09", I get "09:09"
    console.log(dateString);

    This does not work, and that's because these properties (day, hour, minute, second) don't behave consistently, especially when grouped in specific subsets.

    For example:

    const date = new Date(Date.now());
    date.setHours(9,9,9); // so we're sure to catch leading zeros
    
    // consistent options
    const minuteAndSecond = {
      minute: "numeric",
      second: "2-digit",
    }
    const hourAndMinute = {
      hour: "numeric",
      minute: "2-digit",
    }
    
    // inconsistent formatting (even in the same locale)
    console.log(date.toLocaleTimeString("en-US", minuteAndSecond)); // 👉 09:09
    console.log(date.toLocaleTimeString("en-US", hourAndMinute)); // 👉 9:09 AM

    So as Kooilnc wrote, while this can seem inconsistent, this is just how DateTimeFormat options work.


    A side note about locale

    When I stumbled on the similar SO question that I linked to, I was surprised to see that the accepted solution wasn't working for me:

    const date = new Date(Date.now());
    date.setHours(9,9); // so we're sure to catch leading zeros
    
    const timeOptions = {
      hour: "numeric", // without leading zero
      minute: "2-digit", // with leading zero
    }
    
    const dateString = date.toLocaleTimeString([], timeOptions)
    
    // 🤔 the question said this would be "9:09 AM", I was getting "09:09"
    console.log(dateString);

    I had missed a couple important things:

    • first, as mentioned above, options for formatting hours and minutes don't behave like options for formatting minutes and seconds. So the other question's solution doesn't apply to my question
    • in the other question's case, the reason that I was getting a leading zero was indeed because of locale. The snippet passes [] as Date.toLocaleTimeString()'s first parameter, so the browser uses the default locale. I won't go into details here, but the tl;dr is: my browser's locale resolved to en-DE (not en-US), therefore I was seeing hours and minutes formatted differently. Iff you're interested, I did a few formatting experiments in a CodeSandbox.

    So: locale is an important factor for formatting hours and minutes, but it didn't affect my case of formatting minutes and seconds (although it might have, with yet other locales).


    In closing (where do we go from here)

    I've personally decided to not remove the leading zero from the formatted time after all. Attempting to force a localized time string to look a certain way seems at odds with the concept of localization itself.

    My recommendation to others would be to treat the output of Date.toLocaleDateTime() like a random string (i.e. don't try to predict its format) and to trust the browser to format it in a way that makes the most sense for users. If you absolutely need to format a Date in a specific way, you're probably better off using something like Date.toISOString() (which outputs a predictable ISO format), and transforming the result at will.


  2. is it just me? (e.g. could it be a localization issue? I’m explicitly
    setting the locale to en-US in my example to try to avoid this, but
    who knows)

    I don’t think it’s the localization. It just works that way.

    how can I get this to work?

    Maybe it’s overkill, but not long ago I created a small library to format dates using Intl.DateTimeFormat. It does the trick because it utilizes the parts of formatToParts, see snippet.

    <script type="module">
      const dtFormat = (await 
        import(`//cdn.jsdelivr.net/npm/intl-dateformatter@latest/index.min.js`))
        .default;
      const date2Format = new Date();
      date2Format.setHours(9);
      date2Format.setMinutes(9);
      console.log( dtFormat(date2Format, `h:mmi`, `l:en-US`) );
    </script>

    To be more clear: here’s what formatToParts returns.

    const now = new Date();
    now.setHours(9);
    now.setMinutes(9);
    const opts = {hour: `numeric`, minute: `2-digit`};
    const formatter = new Intl.DateTimeFormat('en-US', opts);
    console.log(formatter.formatToParts(now));

    Check some experiments with the library @Stackblitz.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search