It's kinda hard to make it "more elegant", but it can certainly be made more correct.
Since you use Math.round
, you can get a time like "3:60 PM"
if you pass it 15.999
. And I'm preeeetty sure there's no such thing.
There's also another bug. If you pass a value like 0.5
, you should get "12:30 AM"
. But you don't. You get "0:30 AM"
.
And some nitpicking: First off, info
is not a very descriptive name. It looks like you're trying to format a number of hours, with minutes as the decimals, so hours
would be a better name. (Edit: or better yet fractionalHours
as rolfl suggests, and which I forgot to credit.)
Also, instead of using parseInt
(which is meant to turn a string into an integer), use Math.floor
, which is what you actually mean: The number without any decimals. For the minutes, you could use the modulo operator (%
) to get the remainder of dividing by 1 (i.e. the decimals) instead of subtracting. But either one works.
By the way, even if your input is a string like "12.3231"
JavaScript's type coercion will treat it as a number, so you don't really need Number()
anywhere, but it's fine to add it.
You can also do something slightly tricky by using modulo 12 on the hours, and default to 12 if the result it zero. That fixes the 0/12 bug mentioned above.
Also, fewer lines do not make things better/faster/more elegant. In this case, it just hurts readability. So use some linebreaks instead of having a line like if (hrs >12) { hrs = hrs - 12; suffix='PM'; }
.
Lastly, use some indentation. It may just be a copy/paste thing when you created the question, but still.
I might do something like this, first converting everything to minutes and rounding that, then breaking it into hours and minutes afterward:
function convertToHHMM(fractionalHours) {
var totalMinutes = Math.round(fractionalHours * 60) % (24 * 60),
hours = Math.floor(totalMinutes / 60) % 12 || 12,
minutes = totalMinutes % 60,
suffix = totalMinutes >= 12 * 60 ? 'PM' : 'AM';
if(minutes < 10) minutes = '0' + minutes;
return hours + ':' + minutes + ' ' + suffix;
}
totalMinutes
rolls over after rounding, so passing in 23.999
is equivalent to passing in 0
, and 27
becomes 3
, etc.
hours
also roll over but start at 12 instead of zero, then 1, 2, 3 and so on.
minutes
should be straight-forward, and
suffix
basically looks at whether the totalMinutes
are greater than or equal to noon-in-minutes.
Snippet with some test cases below (format borrowed from rolfl).
function original(info) {
var suffix='AM';
var hrs = parseInt(Number(info));
var min = Math.round((Number(info)-hrs) * 60);
if (hrs >12) { hrs = hrs - 12; suffix='PM'; }
if (min < 10) {min='0'+min;}
return hrs + ':' + min + ' ' + suffix;
}
function reviewed(fractionalHours) {
var totalMinutes = Math.round(fractionalHours * 60) % (24 * 60),
hours = Math.floor(totalMinutes / 60) % 12 || 12,
minutes = totalMinutes % 60,
suffix = totalMinutes >= 12 * 60 ? 'PM' : 'AM';
if(minutes < 10) minutes = '0' + minutes;
return hours + ':' + minutes + ' ' + suffix;
}
var testCases = [
["12:00 AM", 0], // midnight
["12:30 AM", 0.5],
["1:00 AM", 0.99999999],
["11:30 AM", 11.5],
["12:00 PM", 11.9999999], // noon
["12:00 PM", 12],
["11:30 PM", 23.5],
["12:00 AM", 23.9999999],
["12:00 AM", 24],
["1:00 AM", 25]
];
var data = "<table>";
data += "<tr><th>Input</th><th>Expected</th><th>Review func</th><th>Original func</th></tr>";
for(var i = 0, l = testCases.length ; i < l ; i++) {
data += "<tr>";
data += "<td>" + testCases[i][1] + "</td>";
data += "<td>" + testCases[i][0] + "</td>";
data += "<td>" + reviewed(testCases[i][1]) + "</td>";
data += "<td>" + original(testCases[i][1]) + "</td>";
data += "</tr>";
}
data += "</table>";
document.body.innerHTML = data;
body { font-family: monospace }
table { border-collapse: collapse }
td, th {
padding: 0.2em 0.5em;
border: 1px solid #ccc
}