2

Input: seconds, Output: grammatically correct, formatted time (with words spelled out).

So far I managed to get as far as going from say 40044373 to 1 year, 98 days, 5 hours, 37 minutes, 1 second - note the correct plurals and commas.

What I'm missing is an "and" that is added instead of the last comma (of course only when there's enough inputs). For example 1 year and 1 second for input 31556953 instead of my current 1 year, 1 second in that case.

function formatDuration (seconds) {

var numyears = Math.floor(seconds / 31556952);
if (numyears > 1) {var pluryears = " years, "} else {var pluryears = " year, "};
if (numyears > 0) {var printyears = numyears + pluryears;} else {var printyears = ''};

var numdays = Math.floor((seconds % 31556952) / 86400);
if (numdays > 1) {var plurdays = " days, "} else {var plurdays = " day, "};
if (numdays > 0) {var printdays = numdays + plurdays;} else {var printdays = ''};

var numhours = Math.floor(((seconds % 31556952) % 86400) / 3600);
if (numhours > 1) {var plurhours = " hours, "} else {var plurhours = " hour, "};
if (numhours > 0) {var printhours = numhours + plurhours;} else {var printhours = ''};

var numminutes = Math.floor((((seconds % 31556952) % 86400) % 3600) / 60);
if (numminutes > 1) {var plurminutes = " minutes, "} else {var plurminutes = " minute, "};
if (numminutes > 0) {var printminutes = numminutes + plurminutes;} else {var printminutes = ''};

var numseconds = (((seconds % 31556952) % 86400) % 3600) % 60;
if (numseconds > 1) {var plurseconds = " seconds"} else {var plurseconds = " second"};
if (numseconds > 0) {var printseconds = numseconds + plurseconds;} else {var printseconds = ''};

return(printyears + printdays + printhours + printminutes + printseconds)
}

formatDuration(31556953);
Julix
  • 598
  • 1
  • 9
  • 20

5 Answers5

3

A bit more compact solution that outputs the time string as intended:

function formatDuration (seconds) {
  var values = {
    years: Math.floor(seconds / 31556952),
    days: Math.floor((seconds % 31556952) / 86400),
    hours: Math.floor(((seconds % 31556952) % 86400) / 3600),
    minutes: Math.floor((((seconds % 31556952) % 86400) % 3600) / 60),
    seconds: (((seconds % 31556952) % 86400) % 3600) % 60,
  };
  var withUnits = Object.keys(values)
  .filter(function(unit) { return values[unit] > 0; })
  .map(function (unit) {
    var value = values[unit];
    return value + ' ' + (value === 1 ? unit.slice(0, -1) : unit);
  });
  return (withUnits.length > 1 ? withUnits.slice(0, -1).join(', ') + ' and ' : '') + withUnits.pop();
}

console.log(formatDuration(40044373));
Oliver
  • 507
  • 6
  • 12
  • Ultimately this is nicer than my solution (although I'd format it differently ;) – Dave Newton Aug 16 '16 at 20:47
  • I'm still very new to JS, so I'll double-check that I'm understanding this right: var x = {a: foo, b: fuu, c: fuh[no trailing comma here, right?]} is a shorthand for setting up an array, right? The stuff that happens after I don't recognize at all yet. https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/keys pretty cool! - Thanks! – Julix Aug 16 '16 at 20:52
  • (I think. It does a lot of slicing and dicing that I'm not sure is overly-clear.) – Dave Newton Aug 16 '16 at 20:53
  • @Julix An object, not an array. – Dave Newton Aug 16 '16 at 20:53
  • Neat. Well I guess I have a lot more reading to do for understanding that kind of thing... .map? - didn't know you could .anything without something in front of the dot. – Julix Aug 16 '16 at 20:56
  • ran it. for input 1 I got "and 1 second" -- also with different input "and 1 day" - so the and isn't quite right yet. – Julix Aug 16 '16 at 21:08
  • Yup, passes all tests now. :) – Julix Aug 16 '16 at 21:15
  • If this works for you, you may consider marking my post as the answer to your question. :) – Oliver Aug 17 '16 at 22:22
1

Just a little regex that replaces the last comma:

return (printyears + printdays + printhours + printminutes + printseconds)
    .replace(/, (.*)$/, " and $1");
baao
  • 71,625
  • 17
  • 143
  • 203
  • That helps, but if there aren't any seconds (say in the case of 2 minutes) this replaces the last comma and leaves me with "2 minutes and" for input 120. – Julix Aug 16 '16 at 20:37
1

Unrelated, but the code as presented is pretty difficult to think about.

I'd extract the pluralization bit into its own method, roughly:

function pluralize(dur, s) {
  var ret = false;

  if (dur > 0) {
    ret = dur + ' ' + s;

    if (dur > 1) {
      ret += 's';
    }
  }

  return ret;
}

Then the mainline code, instead of doing everything manually, push onto an array, e.g.,

function formatDuration(seconds) {
  var segments = [];

  var years  = Math.floor(seconds / 31556952)
    , sYears = pluralize(years, 'year')
    ;

  if (sYears) { 
    segments.push(sYears); 
  }

  // etc.

You only need to special-case the seconds value:

  var seconds = (((seconds % 31556952) % 86400) % 3600) % 60
    , sSeconds = pluralize(seconds, 'second')
    ;

  var tmp = segments.join(', ');
  if (!sSeconds) {
    return tmp;
  }

  return tmp + ' and ' + sSeconds;
}

This could be cleaned up a little further, but produces output like the following:

1 year, 2 hours, 46 minutes and 43 seconds
1 year and 3 seconds

(Although I prefer Oxford Commas.)

Things I'd still do:

  • Stop re-computing everything; keep a count of the number of seconds remaining.
  • Pass the array into the function and avoid manual labor in the mainline code.
    • (Or wrap that up some other way.)

https://gist.github.com/davelnewton/a1371867527c5f1530498e1555e2fb0a

Dave Newton
  • 158,873
  • 26
  • 254
  • 302
  • Copied from github and ran it. Gave me "and 1 second" for duration 1. – Julix Aug 16 '16 at 21:08
  • @Julix So fix it, it's trivial. SO isn't a code-writing service, you know. – Dave Newton Aug 16 '16 at 21:41
  • Sorry, my comment before that didn't send (had it open in a different tab and closed that I guess) - I really like your answer a lot. I understand more of what's going on in here and I'm grateful for you posting it. However, it doesn't solve my problem of not knowing how to get rid of the and in those cases. The other solution by Oliver does do that now, but it uses more things that I don't understand than this solution does, which is why I'm curious how you would attempt to get rid of the and in all cases but when needed. – Julix Aug 16 '16 at 21:50
  • Didn't mean to make it sound like I'm entitled to get that curiosity satisfied, just saying I'm still interested. – Julix Aug 16 '16 at 21:51
  • @Julix Just check for an empty array :) – Dave Newton Aug 16 '16 at 21:52
1

I wrote a small library for precisely this purpose:

https://github.com/adamshaylor/compound-subject

In your case, you’d use it like:

var formattedTimeString = compoundSubject([
    numyears + 'years',
    numdays + 'days',
    numhours + 'hours',
    numminutes + 'minutes',
    numseconds + 'seconds'
]).make();

Yielding something like:

2 years, 5 days, 3 hours, 10 minutes and 2 seconds

You can also add an Oxford comma by calling delimitAll().

Adam
  • 696
  • 6
  • 16
0

You just need to add the 'and' in your return.

return(printyears + printdays + printhours + printminutes + 'and' + printseconds)

And remove the comma after the 'minute, ' on this line

if (numminutes > 1) {var plurminutes = " minutes "} else {var plurminutes = " minute "};
Radec
  • 65
  • 9
  • Unless I'm missing something this would print an and even if you have only 1 second, right? - That's not good enough. – Julix Aug 16 '16 at 20:33
  • Also would lead to things like 1 year, and 1 second (instead of 1 year and 1 second, which I want) – Julix Aug 16 '16 at 20:34
  • You are correct. In this case, you can put the commas before the next "element". Like this: instead of "year, ", use ", day". – Radec Aug 16 '16 at 20:40
  • you can't use ", day" because the numday still has to go in front of day. – Julix Aug 16 '16 at 21:53
  • Yes, you are right. Sorry for the mistake. In this case, you can put the variable between the comma and "day". – Radec Aug 17 '16 at 00:38
  • That was my logic originally as well, but when I started thinking about the whether or not to include comma and/or and logic it became really messy, so I came here looking for help, which I found in some of the answers above. - Thanks for trying though. – Julix Aug 17 '16 at 01:19
  • No problem. Thank you for posting your question. – Radec Aug 17 '16 at 01:26