node.jsdate-fns

Strange format duration with date-fns for simple case


I have a strange format case with date-fns and formatting duration. If somebody have an idea what I'm doing wrong:

function formatDuration(duration) {
  return duration >= 60000 * 60
    ? format(new Date(duration), "hh 'h', mm 'min', ss 'sec'")
    : duration >= 60000
      ? format(new Date(duration), "mm 'min', ss 'sec'")
      : format(new Date(duration), "ss 'sec'");
}

What I expect:

formatDuration((59 * 60 + 59) * 1000); // '59 min, 59 sec'
formatDuration((60 * 60) * 1000); // '01 h 00 min, 00 sec'

What I get:

formatDuration((59 * 60 + 59) * 1000); // '59 min, 59 sec'
formatDuration((60 * 60) * 1000); // '02 h 00 min, 00 sec'

In the second case, there is an extra hour appearing from nowhere. I don't understand where the problem is. Any idea? Thanks!


Solution

  • Some of you were true that using Date was not a good idea. Manipulating number was more successfull. Here is my solution to my problem:

    function formatDuration(duration) {
      if (!duration) {
        return '';
      }
    
      const durationSeconds = duration / 1000;
    
      const hours = Math.floor(durationSeconds / 3600);
    
      const restHours = durationSeconds % 3600;
    
      const minutes = Math.floor(restHours / 60);
    
      const seconds = restHours % 60;
    
      const hoursPart = hours > 0 ? `${hours} h, ` : '';
    
      let minutesPart;
      if (hours > 0) {
        if (minutes > 0) {
          minutesPart = `${minutes.toString().padStart(2, '0')} min, `;
        } else {
          minutesPart = '00 min, ';
        }
          } else {
        if (minutes > 0) {
          minutesPart = `${minutes} min, `;
        } else {
          minutesPart = '';
        }
      }
    
      let secondsPart;
      if (hours > 0 || minutes > 0) {
        if (seconds > 0) {
          secondsPart = `${seconds.toString().padStart(2, '0')} sec`;
        } else {
          secondsPart = '00 sec';
        }
      } else {
        if (seconds > 0) {
          secondsPart = `${seconds} sec`;
        } else {
          secondsPart = '';
        }
      }
    
      return `${hoursPart}${minutesPart}${secondsPart}`;
    }
    

    Maybe there is a more simple solution than this one but its working well and it's ok for all my cases.

    Tested with this, all green:

    describe('duration_format', () => {
      const cases = [
        [2, '2 sec'],
        [9, '9 sec'],
        [14, '14 sec'],
        [23, '23 sec'],
        [36, '36 sec'],
        [47, '47 sec'],
        [51, '51 sec'],
        [59, '59 sec'],
        [60, '1 min, 00 sec'],
        [61, '1 min, 01 sec'],
        [82, '1 min, 22 sec'],
        [154, '2 min, 34 sec'],
        [887, '14 min, 47 sec'],
        [30 * 60 + 22, '30 min, 22 sec'],
        [59 * 60 + 22, '59 min, 22 sec'],
        [59 * 60 + 59, '59 min, 59 sec'],
        [60 * 60, '1 h, 00 min, 00 sec'],
        [12 * 60 * 60 + 45 * 60 + 22, '12 h, 45 min, 22 sec'],
      ];
    
      test.each(cases)('%s to %s', (duration, humanReadable) => {
        expect(formatDuration(duration * 1000)).toBe(humanReadable);
      });
    });