79

I am working on a form widget for users to enter a time of day into a text input (for a calendar application). Using JavaScript (we are using jQuery FWIW), I want to find the best way to parse the text that the user enters into a JavaScript Date() object so I can easily perform comparisons and other things on it.

I tried the parse() method and it is a little too picky for my needs. I would expect it to be able to successfully parse the following example input times (in addition to other logically similar time formats) as the same Date() object:

  • 1:00 pm
  • 1:00 p.m.
  • 1:00 p
  • 1:00pm
  • 1:00p.m.
  • 1:00p
  • 1 pm
  • 1 p.m.
  • 1 p
  • 1pm
  • 1p.m.
  • 1p
  • 13:00
  • 13

I am thinking that I might use regular expressions to split up the input and extract the information I want to use to create my Date() object. What is the best way to do this?

Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
Joe Lencioni
  • 10,231
  • 18
  • 55
  • 66

24 Answers24

78

A quick solution which works on the input that you've specified:

function parseTime( t ) {
   var d = new Date();
   var time = t.match( /(\d+)(?::(\d\d))?\s*(p?)/ );
   d.setHours( parseInt( time[1]) + (time[3] ? 12 : 0) );
   d.setMinutes( parseInt( time[2]) || 0 );
   return d;
}

var tests = [
  '1:00 pm','1:00 p.m.','1:00 p','1:00pm','1:00p.m.','1:00p','1 pm',
  '1 p.m.','1 p','1pm','1p.m.', '1p', '13:00','13', '1a', '12', '12a', '12p', '12am', '12pm', '2400am', '2400pm', '2400', 
  '1000', '100', '123', '2459', '2359', '2359am', '1100', '123p',
  '1234', '1', '9', '99', '999', '9999', '99999', '0000', '0011', '-1', 'mioaw' ];

for ( var i = 0; i < tests.length; i++ ) {
  console.log( tests[i].padStart( 9, ' ' ) + " = " + parseTime(tests[i]) );
}

It should work for a few other varieties as well (even if a.m. is used, it'll still work - for example). Obviously this is pretty crude but it's also pretty lightweight (much cheaper to use that than a full library, for example).

Warning: The code doe not work with 12:00 AM, etc.

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
John Resig
  • 35,521
  • 3
  • 29
  • 19
  • 8
    After working with this, I noticed that it doesn't properly parse variants of the time "12 pm" because it adds 12 to the hours number. To fix, I changed the d.setHours line to read: d.setHours( parseInt(time[1]) + ( ( parseInt(time[1]) < 12 && time[3] ) ? 12 : 0) ); – Joe Lencioni Oct 29 '08 at 21:38
  • 3
    I also noticed that parseInt was choking on strings like ':30' or ':00' so I changed the regex to capture the minutes without the colon – Joe Lencioni Oct 31 '08 at 14:11
  • 1
    You'd better hope d doesn't fall on a day where a daylight savings change takes effect. This also assumes English conventions. – peller Dec 10 '09 at 17:03
  • 8
    The calls to ParseInt need a radix of 10 because JS assumes a radix of 8 when there is a leading 0, resulting in the hour being interpreted as 0 if it is greater than 8 and has a leading 0 (because 08 isn't a valid base 8 number). Also, changing "p?" to "[pP]?" will make it work when AM/PM are upper case. All in all, unless you're *really* sure this approach will work for you, you should use a library. Remember, time hates us all. – Benji York Aug 07 '10 at 15:05
  • 5
    An alternative to using "[pP]" would be to append "i" to the end of the literal. That would make the match case-insensitive. – Chris Miller May 19 '11 at 15:16
  • 1
    And now also available in function form, for the lazy (like me): **var timeParser = function(stringTime) { var d = new Date(); var time = stringTime.match(/(\d+)(?::(\d\d))?\s*(p?)/); d.setHours( parseInt(time[1]) + (time[3] ? 12 : 0) ); d.setMinutes( parseInt(time[2]) || 0 ); //console.log( d ); return d; }** – Michahell Mar 24 '13 at 20:24
  • 3
    A word of advice to anyone who has found this through Google like I did. Don't use it. It seems to work, but it's wrong for times around 12am. The comments/edits don't solve this. Nathan's solution is more complete. – braks Sep 30 '15 at 00:01
  • This one has issues with 12:30am etc. https://stackoverflow.com/users/264837/nathan-villaescusa answer is better. – Ralph Yozzo May 16 '21 at 14:59
55

All of the examples provided fail to work for times from 12:00 am to 12:59 am. They also throw an error if the regex does not match a time. The following handles this:

function parseTime(timeString) { 
 if (timeString == '') return null;
 
 var time = timeString.match(/(\d+)(:(\d\d))?\s*(p?)/i); 
 if (time == null) return null;
 
 var hours = parseInt(time[1],10);  
 if (hours == 12 && !time[4]) {
    hours = 0;
 }
 else {
  hours += (hours < 12 && time[4])? 12 : 0;
 } 
 var d = new Date();          
 d.setHours(hours);
 d.setMinutes(parseInt(time[3],10) || 0);
 d.setSeconds(0, 0);  
 return d;
}


var tests = [
  '1:00 pm','1:00 p.m.','1:00 p','1:00pm','1:00p.m.','1:00p','1 pm',
  '1 p.m.','1 p','1pm','1p.m.', '1p', '13:00','13', '1a', '12', '12a', '12p', '12am', '12pm', '2400am', '2400pm', '2400', 
  '1000', '100', '123', '2459', '2359', '2359am', '1100', '123p',
  '1234', '1', '9', '99', '999', '9999', '99999', '0000', '0011', '-1', 'mioaw' ];

for ( var i = 0; i < tests.length; i++ ) {
  console.log( tests[i].padStart( 9, ' ' ) + " = " + parseTime(tests[i]) );
}

This will work for strings which contain a time anywhere inside them. So "abcde12:00pmdef" would be parsed and return 12 pm. If the desired outcome is that it only returns a time when the string only contains a time in them the following regular expression can be used provided you replace "time[4]" with "time[6]".

/^(\d+)(:(\d\d))?\s*((a|(p))m?)?$/i
Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
Nathan Villaescusa
  • 17,331
  • 4
  • 53
  • 56
32

Don't bother doing it yourself, just use datejs.

Jim
  • 72,985
  • 14
  • 101
  • 108
  • 39
    25KB just to do dates?!?! I mean, nice library no doubt, and if I had to have psycho date handling functionality, it would be the one. But 25KB is larger than all of the core of jQuery!!! – Jason Bunting Sep 26 '08 at 19:30
  • 4
    Given the range of input you want to accept, I would go for datejs as well. It seems to handle most of them, apart from the one which is just a number, which it takes as the day of the month. – Jonny Buchanan Sep 26 '08 at 19:39
  • Yeah, I might just go ahead and use datejs. I can get around the single number input being regarded as a month by prepending '1/1/2000 ' to the string when I parse the time. – Joe Lencioni Sep 26 '08 at 19:43
  • 1
    Huge +1! As for the size - actually it is 25KB per locale. But at least it supports locales! Instead of writing you own procedures, use what is available (or write a better library and share it). Also, while whitespace is stripped from JS, it doesn't look minified to me, so you might be able to save some bytes there. If it matters that much to you. – johndodo Apr 20 '12 at 07:59
  • Tried it at http://www.datejs.com/ (under "Mad Skillz…") "12 wed 2020" ==> "Monday, December 03, 2012 12:00:00 AM" -- wat? 2012?? – Jonas N Oct 05 '12 at 22:48
  • 3
    I'm a human and I don't know what "12 wed 2020" means. There are multiple ways to interpret that. If I were to guess what DateJS was doing, I would say that it interpreted "12" as the day of the month and looked for the next Wednesday that fell on the 12th, which is in December. The only thing that surprised me was that it threw away "2020" instead of interpreting it as 8:20pm. – Jim Oct 07 '12 at 16:22
  • 1
    @SebastianMach Even at Venezuelan speeds, 25KB only takes 1/8 of a second. – jwg Mar 27 '17 at 07:30
  • @Jim: I am just talking potential features. Never say never, especially with ambitious product owners. – Sebastian Mach Apr 08 '17 at 05:34
  • 1
    This is actually a bad answer, because it doesn't show *how* to use the library to perform the task in question. The library is great, but the answer sucks. – Heretic Monkey Apr 07 '21 at 13:25
16

Most of the regex solutions here throw errors when the string can't be parsed, and not many of them account for strings like 1330 or 130pm. Even though these formats weren't specified by the OP, I find them critical for parsing dates input by humans.

All of this got me to thinking that using a regular expression might not be the best approach for this.

My solution is a function that not only parses the time, but also allows you to specify an output format and a step (interval) at which to round minutes to. At about 70 lines, it's still lightweight and parses all of the aforementioned formats as well as ones without colons.

function parseTime(time, format, step) {
 
 var hour, minute, stepMinute,
  defaultFormat = 'g:ia',
  pm = time.match(/p/i) !== null,
  num = time.replace(/[^0-9]/g, '');
 
 // Parse for hour and minute
 switch(num.length) {
  case 4:
   hour = parseInt(num[0] + num[1], 10);
   minute = parseInt(num[2] + num[3], 10);
   break;
  case 3:
   hour = parseInt(num[0], 10);
   minute = parseInt(num[1] + num[2], 10);
   break;
  case 2:
  case 1:
   hour = parseInt(num[0] + (num[1] || ''), 10);
   minute = 0;
   break;
  default:
   return '';
 }
 
 // Make sure hour is in 24 hour format
 if( pm === true && hour > 0 && hour < 12 ) hour += 12;
 
 // Force pm for hours between 13:00 and 23:00
 if( hour >= 13 && hour <= 23 ) pm = true;
 
 // Handle step
 if( step ) {
  // Step to the nearest hour requires 60, not 0
  if( step === 0 ) step = 60;
  // Round to nearest step
  stepMinute = (Math.round(minute / step) * step) % 60;
  // Do we need to round the hour up?
  if( stepMinute === 0 && minute >= 30 ) {
   hour++;
   // Do we need to switch am/pm?
   if( hour === 12 || hour === 24 ) pm = !pm;
  }
  minute = stepMinute;
 }
 
 // Keep within range
 if( hour <= 0 || hour >= 24 ) hour = 0;
 if( minute < 0 || minute > 59 ) minute = 0;

 // Format output
 return (format || defaultFormat)
  // 12 hour without leading 0
        .replace(/g/g, hour === 0 ? '12' : 'g')
  .replace(/g/g, hour > 12 ? hour - 12 : hour)
  // 24 hour without leading 0
  .replace(/G/g, hour)
  // 12 hour with leading 0
  .replace(/h/g, hour.toString().length > 1 ? (hour > 12 ? hour - 12 : hour) : '0' + (hour > 12 ? hour - 12 : hour))
  // 24 hour with leading 0
  .replace(/H/g, hour.toString().length > 1 ? hour : '0' + hour)
  // minutes with leading zero
  .replace(/i/g, minute.toString().length > 1 ? minute : '0' + minute)
  // simulate seconds
  .replace(/s/g, '00')
  // lowercase am/pm
  .replace(/a/g, pm ? 'pm' : 'am')
  // lowercase am/pm
  .replace(/A/g, pm ? 'PM' : 'AM');
}

var tests = [
  '1:00 pm','1:00 p.m.','1:00 p','1:00pm','1:00p.m.','1:00p','1 pm',
  '1 p.m.','1 p','1pm','1p.m.', '1p', '13:00','13', '1a', '12', '12a', '12p', '12am', '12pm', '2400am', '2400pm', '2400', 
  '1000', '100', '123', '2459', '2359', '2359am', '1100', '123p',
  '1234', '1', '9', '99', '999', '9999', '99999', '0000', '0011', '-1', 'mioaw' ];

for ( var i = 0; i < tests.length; i++ ) {
  console.log( tests[i].padStart( 9, ' ' ) + " = " + parseTime(tests[i]) );
}
Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
claviska
  • 12,410
  • 2
  • 27
  • 50
  • 1
    I had to mark this up. It's the only really *useful* answer when size matters. Most of the time I use momentjs but that's enormous compared to this solution. – Peter Wone May 09 '14 at 09:26
  • 3
    I needed that, and so went ahead and fixed that with a dirty hack: http://codepen.io/anon/pen/EjrVqq there should be a better solution, but I couldn't put my finger on it yet. – Nomenator Aug 04 '15 at 19:46
12

Here's an improvement on Joe's version. Feel free to edit it further.

function parseTime(timeString)
{
  if (timeString == '') return null;
  var d = new Date();
  var time = timeString.match(/(\d+)(:(\d\d))?\s*(p?)/i);
  d.setHours( parseInt(time[1],10) + ( ( parseInt(time[1],10) < 12 && time[4] ) ? 12 : 0) );
  d.setMinutes( parseInt(time[3],10) || 0 );
  d.setSeconds(0, 0);
  return d;
}

var tests = [
  '1:00 pm','1:00 p.m.','1:00 p','1:00pm','1:00p.m.','1:00p','1 pm',
  '1 p.m.','1 p','1pm','1p.m.', '1p', '13:00','13', '1a', '12', '12a', '12p', '12am', '12pm', '2400am', '2400pm', '2400', 
  '1000', '100', '123', '2459', '2359', '2359am', '1100', '123p',
  '1234', '1', '9', '99', '999', '9999', '99999', '0000', '0011', '-1', 'mioaw' ];

for ( var i = 0; i < tests.length; i++ ) {
  console.log( tests[i].padStart( 9, ' ' ) + " = " + parseTime(tests[i]) );
}

Changes:

  • Added radix parameter to the parseInt() calls (so jslint won't complain).
  • Made the regex case-insenstive so "2:23 PM" works like "2:23 pm"
Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
Patrick McElhaney
  • 57,901
  • 40
  • 134
  • 167
3

I came across a couple of kinks in implementing John Resig's solution. Here is the modified function that I have been using based on his answer:

function parseTime(timeString)
{
  if (timeString == '') return null;
  var d = new Date();
  var time = timeString.match(/(\d+)(:(\d\d))?\s*(p?)/);
  d.setHours( parseInt(time[1]) + ( ( parseInt(time[1]) < 12 && time[4] ) ? 12 : 0) );
  d.setMinutes( parseInt(time[3]) || 0 );
  d.setSeconds(0, 0);
  return d;
} // parseTime()

var tests = [
  '1:00 pm','1:00 p.m.','1:00 p','1:00pm','1:00p.m.','1:00p','1 pm',
  '1 p.m.','1 p','1pm','1p.m.', '1p', '13:00','13', '1a', '12', '12a', '12p', '12am', '12pm', '2400am', '2400pm', '2400', 
  '1000', '100', '123', '2459', '2359', '2359am', '1100', '123p',
  '1234', '1', '9', '99', '999', '9999', '99999', '0000', '0011', '-1', 'mioaw' ];

for ( var i = 0; i < tests.length; i++ ) {
  console.log( tests[i].padStart( 9, ' ' ) + " = " + parseTime(tests[i]) );
}
Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
Joe Lencioni
  • 10,231
  • 18
  • 55
  • 66
3

Here's a solution more for all of those who are using a 24h clock that supports:

  • 0820 -> 08:20
  • 32 -> 03:02
  • 124 -> 12:04

function parseTime(text) {
  var time = text.match(/(\d?\d):?(\d?\d?)/);
 var h = parseInt(time[1], 10);
 var m = parseInt(time[2], 10) || 0;
 
 if (h > 24) {
        // try a different format
  time = text.match(/(\d)(\d?\d?)/);
  h = parseInt(time[1], 10);
  m = parseInt(time[2], 10) || 0;
 } 
 
  var d = new Date();
  d.setHours(h);
  d.setMinutes(m);
  return d;  
}

var tests = [
  '1:00 pm','1:00 p.m.','1:00 p','1:00pm','1:00p.m.','1:00p','1 pm',
  '1 p.m.','1 p','1pm','1p.m.', '1p', '13:00','13', '1a', '12', '12a', '12p', '12am', '12pm', '2400am', '2400pm', '2400', 
  '1000', '100', '123', '2459', '2359', '2359am', '1100', '123p',
  '1234', '1', '9', '99', '999', '9999', '99999', '0000', '0011', '-1', 'mioaw' ];

for ( var i = 0; i < tests.length; i++ ) {
  console.log( tests[i].padStart( 9, ' ' ) + " = " + parseTime(tests[i]) );
}
Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
Stefan Haberl
  • 9,812
  • 7
  • 72
  • 81
3

Compilation table of other answers

First of all, I can't believe that there is not a built-in functionality or even a robust third-party library that can handle this. Actually, it's web development so I can believe it.

Trying to test all edge cases with all these different algorithms was making my head spin, so I took the liberty of compiling all the answers and tests in this thread into a handy table.

The code (and resulting table) is pointlessly large to include inline, so I've made a JSFiddle:

http://jsfiddle.net/jLv16ydb/4/show

// heres some filler code of the functions I included in the test,
// because StackOverfleaux wont let me have a jsfiddle link without code
Functions = [
    JohnResig,
    Qwertie,
    PatrickMcElhaney,
    Brad,
    NathanVillaescusa,
    DaveJarvis,
    AndrewCetinic,
    StefanHaberl,
    PieterDeZwart,
    JoeLencioni,
    Claviska,
    RobG,
    DateJS,
    MomentJS
];
// I didn't include `date-fns`, because it seems to have even more
// limited parsing than MomentJS or DateJS

Please feel free to fork my fiddle and add more algorithms and test cases

I didn't add any comparisons between the result and the "expected" output, because there are cases where the "expected" output could be debated (eg, should 12 be interpreted as 12:00am or 12:00pm?). You will have to go through the table and see which algorithm makes the most sense for you.

Note: The colors do not necessarily indicate quality or "expectedness" of output, they only indicate the type of output:

  • red = js error thrown

  • yellow = "falsy" value (undefined, null, NaN, "", "invalid date")

  • green = js Date() object

  • light green = everything else

Where a Date() object is the output, I convert it to 24 hr HH:mm format for ease of comparison.

V. Rubinetti
  • 1,324
  • 13
  • 21
  • Interesting... I suggest people pay special attention to how libraries respond to inputs like `mioaw`, `999`, `aaa12:34aaa` and `2400`, as such edge cases are the main point of disagreement, though there is also a little disagreement on not-so-corner cases like `1pm`. – Qwertie Jun 16 '23 at 13:43
2

This is a more rugged approach that takes into account how users intend to use this type of input. For example, if a user entered "12", they would expect it to be 12pm (noon), and not 12am. The below function handles all of this. It is also available here: http://blog.de-zwart.net/2010-02/javascript-parse-time/

/**
 * Parse a string that looks like time and return a date object.
 * @return  Date object on success, false on error.
 */
String.prototype.parseTime = function() {
    // trim it and reverse it so that the minutes will always be greedy first:
    var value = this.trim().reverse();

    // We need to reverse the string to match the minutes in greedy first, then hours
    var timeParts = value.match(/(a|p)?\s*((\d{2})?:?)(\d{1,2})/i);

    // This didnt match something we know
    if (!timeParts) {
        return false;
    }

    // reverse it:
    timeParts = timeParts.reverse();

    // Reverse the internal parts:
    for( var i = 0; i < timeParts.length; i++ ) {
        timeParts[i] = timeParts[i] === undefined ? '' : timeParts[i].reverse();
    }

    // Parse out the sections:
    var minutes = parseInt(timeParts[1], 10) || 0;
    var hours = parseInt(timeParts[0], 10);
    var afternoon = timeParts[3].toLowerCase() == 'p' ? true : false;

    // If meridian not set, and hours is 12, then assume afternoon.
    afternoon = !timeParts[3] && hours == 12 ? true : afternoon;
    // Anytime the hours are greater than 12, they mean afternoon
    afternoon = hours > 12 ? true : afternoon;
    // Make hours be between 0 and 12:
    hours -= hours > 12 ? 12 : 0;
    // Add 12 if its PM but not noon
    hours += afternoon && hours != 12 ? 12 : 0;
    // Remove 12 for midnight:
    hours -= !afternoon && hours == 12 ? 12 : 0;

    // Check number sanity:
    if( minutes >= 60 || hours >= 24 ) {
        return false;
    }

    // Return a date object with these values set.
    var d = new Date();
    d.setHours(hours);
    d.setMinutes(minutes);
    return d;
}

var tests = [
  '1:00 pm','1:00 p.m.','1:00 p','1:00pm','1:00p.m.','1:00p','1 pm',
  '1 p.m.','1 p','1pm','1p.m.', '1p', '13:00','13', '1a', '12', '12a', '12p', '12am', '12pm', '2400am', '2400pm', '2400', 
  '1000', '100', '123', '2459', '2359', '2359am', '1100', '123p',
  '1234', '1', '9', '99', '999', '9999', '99999', '0000', '0011', '-1', 'mioaw' ];

for ( var i = 0; i < tests.length; i++ ) {
  console.log( tests[i].padStart( 9, ' ' ) + " = " + tests[i].parseTime() );
}

This is a string prototype, so you can use it like so:

var str = '12am';
var date = str.parseTime();
Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
2

Lots of answers so one more won't hurt.

/**
 * Parse a time in nearly any format
 * @param {string} time - Anything like 1 p, 13, 1:05 p.m., etc.
 * @returns {Date} - Date object for the current date and time set to parsed time
*/
function parseTime(time) {
  var b = time.match(/\d+/g);
  
  // return undefined if no matches
  if (!b) return;
  
  var d = new Date();
  d.setHours(b[0]>12? b[0] : b[0]%12 + (/p/i.test(time)? 12 : 0), // hours
             /\d/.test(b[1])? b[1] : 0,     // minutes
             /\d/.test(b[2])? b[2] : 0);    // seconds
  return d;
}

var tests = [
  '1:00 pm','1:00 p.m.','1:00 p','1:00pm','1:00p.m.','1:00p','1 pm',
  '1 p.m.','1 p','1pm','1p.m.', '1p', '13:00','13', '1a', '12', '2400', 
  '1000', '100', '123', '2459', '2359', '2359am', '1100', '123p',
  '1234', '1', '9', '99', '999', '9999', '99999', '0000', '0011', '-1', 'mioaw' ];

for ( var i = 0; i < tests.length; i++ ) {
  console.log( tests[i].padStart( 9, ' ' ) + " = " + parseTime(tests[i]) );
}

To be properly robust, it should check that each value is within range of allowed values, e.g if am/pm hours must be 1 to 12 inclusive, otherwise 0 to 24 inclusive, etc.

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
RobG
  • 142,382
  • 31
  • 172
  • 209
2

The time package is 0.9kbs in size. Available with NPM and bower package managers.

Here's an example straight from the README.md:

var t = Time('2p');
t.hours();             // 2
t.minutes();           // 0
t.period();            // 'pm'
t.toString();          // '2:00 pm'
t.nextDate();          // Sep 10 2:00 (assuming it is 1 o'clock Sep 10)
t.format('hh:mm AM')   // '02:00 PM'
t.isValid();           // true
Time.isValid('99:12'); // false
Sgnl
  • 1,808
  • 22
  • 30
  • 3
    This package doesn't support 24-hour time, which may or may not be a significant limitation. – gruppler Nov 14 '17 at 17:16
  • @gruppler thanks for that very important note. FWIW, there is a Pull Request with work done to support the 24-hour format. The PR was submitted in 2014. Anyway, here's a link to that Pull Request: https://github.com/zackdever/time/pull/7 – Sgnl Nov 15 '17 at 20:32
  • 2
    This package doesn't support seconds or milliseconds. (Most of the answers here don't either, but I tend to expect more from a "package" than a code snippet in an SO answer.) – Qwertie Jun 08 '18 at 19:43
2

Here's another approach that covers the original answer, any reasonable number of digits, data entry by cats, and logical fallacies. The algorithm follows:

  1. Determine whether meridian is post meridiem.
  2. Convert input digits to an integer value.
  3. Time between 0 and 24: hour is the o'clock, no minutes (hours 12 is PM).
  4. Time between 100 and 2359: hours div 100 is the o'clock, minutes mod 100 remainder.
  5. Time from 2400 on: hours is midnight, with minutes remainder.
  6. When hours exceeds 12, subtract 12 and force post meridiem true.
  7. When minutes exceeds 59, force to 59.

Converting the hours, minutes, and post meridiem to a Date object is an exercise for the reader (numerous other answers show how to do this).

"use strict";

String.prototype.toTime = function () {
  var time = this;
  var post_meridiem = false;
  var ante_meridiem = false;
  var hours = 0;
  var minutes = 0;

  if( time != null ) {
    post_meridiem = time.match( /p/i ) !== null;
    ante_meridiem = time.match( /a/i ) !== null;

    // Preserve 2400h time by changing leading zeros to 24.
    time = time.replace( /^00/, '24' );

    // Strip the string down to digits and convert to a number.
    time = parseInt( time.replace( /\D/g, '' ) );
  }
  else {
    time = 0;
  }

  if( time > 0 && time < 24 ) {
    // 1 through 23 become hours, no minutes.
    hours = time;
  }
  else if( time >= 100 && time <= 2359 ) {
    // 100 through 2359 become hours and two-digit minutes.
    hours = ~~(time / 100);
    minutes = time % 100;
  }
  else if( time >= 2400 ) {
    // After 2400, it's midnight again.
    minutes = (time % 100);
    post_meridiem = false;
  }

  if( hours == 12 && ante_meridiem === false ) {
    post_meridiem = true;
  }

  if( hours > 12 ) {
    post_meridiem = true;
    hours -= 12;
  }

  if( minutes > 59 ) {
    minutes = 59;
  }

  var result =
    (""+hours).padStart( 2, "0" ) + ":" + (""+minutes).padStart( 2, "0" ) +
    (post_meridiem ? "PM" : "AM");

  return result;
};

var tests = [
  '1:00 pm','1:00 p.m.','1:00 p','1:00pm','1:00p.m.','1:00p','1 pm',
  '1 p.m.','1 p','1pm','1p.m.', '1p', '13:00','13', '1a', '12', '12a', '12p', '12am', '12pm', '2400am', '2400pm', '2400', 
  '1000', '100', '123', '2459', '2359', '2359am', '1100', '123p',
  '1234', '1', '9', '99', '999', '9999', '99999', '0000', '0011', '-1', 'mioaw' ];

for ( var i = 0; i < tests.length; i++ ) {
  console.log( tests[i].padStart( 9, ' ' ) + " = " + tests[i].toTime() );
}

With jQuery, the newly defined String prototype is used as follows:

  <input type="text" class="time" />
  $(".time").change( function() {
    var $this = $(this);
    $(this).val( time.toTime() );
  });
Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
2

I wasn't happy with the other answers so I made yet another one. This version:

  • Recognizes seconds and milliseconds
  • Returns undefined on invalid input such as "13:00pm" or "11:65"
  • Returns a local time if you provide a localDate parameter, otherwise returns a UTC time on the Unix epoch (Jan 1, 1970).
  • Supports military time like 1330 (to disable, make the first ':' required in the regex)
  • Allows an hour by itself, with 24-hour time (i.e. "7" means 7am).
  • Allows hour 24 as a synonym for hour 0, but hour 25 is not allowed.
  • Requires the time to be at the beginning of the string (to disable, remove ^\s* in the regex)
  • Has test code that actually detects when the output is incorrect.

Edit: it's now a package including a timeToString formatter: npm i simplertime


/**
 * Parses a string into a Date. Supports several formats: "12", "1234",
 * "12:34", "12:34pm", "12:34 PM", "12:34:56 pm", and "12:34:56.789".
 * The time must be at the beginning of the string but can have leading spaces.
 * Anything is allowed after the time as long as the time itself appears to
 * be valid, e.g. "12:34*Z" is OK but "12345" is not.
 * @param {string} t Time string, e.g. "1435" or "2:35 PM" or "14:35:00.0"
 * @param {Date|undefined} localDate If this parameter is provided, setHours
 *        is called on it. Otherwise, setUTCHours is called on 1970/1/1.
 * @returns {Date|undefined} The parsed date, if parsing succeeded.
 */
function parseTime(t, localDate) {
  // ?: means non-capturing group and ?! is zero-width negative lookahead
  var time = t.match(/^\s*(\d\d?)(?::?(\d\d))?(?::(\d\d))?(?!\d)(\.\d+)?\s*(pm?|am?)?/i);
  if (time) {
    var hour = parseInt(time[1]), pm = (time[5] || ' ')[0].toUpperCase();
    var min = time[2] ? parseInt(time[2]) : 0;
    var sec = time[3] ? parseInt(time[3]) : 0;
    var ms = (time[4] ? parseFloat(time[4]) * 1000 : 0);
    if (pm !== ' ' && (hour == 0 || hour > 12) || hour > 24 || min >= 60 || sec >= 60)
      return undefined;
    if (pm === 'A' && hour === 12) hour = 0;
    if (pm === 'P' && hour !== 12) hour += 12;
    if (hour === 24) hour = 0;
    var date = new Date(localDate!==undefined ? localDate.valueOf() : 0);
    var set = (localDate!==undefined ? date.setHours : date.setUTCHours);
    set.call(date, hour, min, sec, ms);
    return date;
  }
  return undefined;
}

var testSuite = {
  '1300':  ['1:00 pm','1:00 P.M.','1:00 p','1:00pm','1:00p.m.','1:00p','1 pm',
            '1 p.m.','1 p','1pm','1p.m.', '1p', '13:00','13', '1:00:00PM', '1300', '13'],
  '1100':  ['11:00am', '11:00 AM', '11:00', '11:00:00', '1100'],
  '1359':  ['1:59 PM', '13:59', '13:59:00', '1359', '1359:00', '0159pm'],
  '100':   ['1:00am', '1:00 am', '0100', '1', '1a', '1 am'],
  '0':     ['00:00', '24:00', '12:00am', '12am', '12:00:00 AM', '0000', '1200 AM'],
  '30':    ['0:30', '00:30', '24:30', '00:30:00', '12:30:00 am', '0030', '1230am'],
  '1435':  ["2:35 PM", "14:35:00.0", "1435"],
  '715.5': ["7:15:30", "7:15:30am"],
  '109':   ['109'], // Three-digit numbers work (I wasn't sure if they would)
  '':      ['12:60', '11:59:99', '-12:00', 'foo', '0660', '12345', '25:00'],
};

var passed = 0;
for (var key in testSuite) {
  let num = parseFloat(key), h = num / 100 | 0;
  let m = num % 100 | 0, s = (num % 1) * 60;
  let expected = Date.UTC(1970, 0, 1, h, m, s); // Month is zero-based
  let strings = testSuite[key];
  for (let i = 0; i < strings.length; i++) {
    var result = parseTime(strings[i]);
    if (result === undefined ? key !== '' : key === '' || expected !== result.valueOf()) {
      console.log(`Test failed at ${key}:"${strings[i]}" with result ${result ? result.toUTCString() : 'undefined'}`);
    } else {
      passed++;
    }
  }
}
console.log(passed + ' tests passed.');
Qwertie
  • 16,354
  • 20
  • 105
  • 148
1

AnyTime.Converter can parse dates/times in many different formats:

http://www.ama3.com/anytime/

Andrew M. Andrews III
  • 1,989
  • 18
  • 23
1

I have made some modifications to the function above to support a few more formats.

  • 1400 -> 2:00 PM
  • 1.30 -> 1:30 PM
  • 1:30a -> 1:30 AM
  • 100 -> 1:00 AM

Ain't cleaned it up yet but works for everything I can think of.

function parseTime(timeString) {
    if (timeString == '') return null;

    var time = timeString.match(/^(\d+)([:\.](\d\d))?\s*((a|(p))m?)?$/i);

    if (time == null) return null;

    var m = parseInt(time[3], 10) || 0;
    var hours = parseInt(time[1], 10);

    if (time[4]) time[4] = time[4].toLowerCase();

    // 12 hour time
    if (hours == 12 && !time[4]) {
        hours = 12;
    }
    else if (hours == 12 && (time[4] == "am" || time[4] == "a")) {
        hours += 12;
    }
    else if (hours < 12 && (time[4] != "am" && time[4] != "a")) {
        hours += 12;
    }
    // 24 hour time
    else if(hours > 24 && hours.toString().length >= 3) {
        if(hours.toString().length == 3) {
           m = parseInt(hours.toString().substring(1,3), 10);
           hours = parseInt(hours.toString().charAt(0), 10);
        }
        else if(hours.toString().length == 4) {
           m = parseInt(hours.toString().substring(2,4), 10);
           hours = parseInt(hours.toString().substring(0,2), 10);
        }
    }

    var d = new Date();
    d.setHours(hours);
    d.setMinutes(m);
    d.setSeconds(0, 0);
    return d;
}

var tests = [
  '1:00 pm','1:00 p.m.','1:00 p','1:00pm','1:00p.m.','1:00p','1 pm',
  '1 p.m.','1 p','1pm','1p.m.', '1p', '13:00','13', '1a', '12', '12a', '12p', '12am', '12pm', '2400am', '2400pm', '2400', 
  '1000', '100', '123', '2459', '2359', '2359am', '1100', '123p',
  '1234', '1', '9', '99', '999', '9999', '99999', '0000', '0011', '-1', 'mioaw' ];

for ( var i = 0; i < tests.length; i++ ) {
  console.log( tests[i].padStart( 9, ' ' ) + " = " + parseTime(tests[i]) );
}
Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
Andrew Cetinic
  • 2,805
  • 29
  • 44
0

Why not use validation to narrow down what a user can put in and simplify the list to only include formats that can be parsed (or parsed after some tweaking).

I don't think it's asking too much to require a user to put a time in a supported format.

dd:dd A(m)/P(m)

dd A(m)/P(m)

dd

Wayne
  • 38,646
  • 4
  • 37
  • 49
  • You are right, it really is not asking too much. It is, however, a bit of a hurdle for the user and I want to make this particular form as easy to use as is reasonable. Ideally, the input will be flexible enough to interpret what they typed in and reformat it to a standard format. – Joe Lencioni Sep 26 '08 at 19:46
0

An improvement to Patrick McElhaney's solution (his does not handle 12am correctly)

function parseTime( timeString ) {
var d = new Date();
var time = timeString.match(/(\d+)(:(\d\d))?\s*([pP]?)/i);
var h = parseInt(time[1], 10);
if (time[4])
{
    if (h < 12)
        h += 12;
}
else if (h == 12)
    h = 0;
d.setHours(h);
d.setMinutes(parseInt(time[3], 10) || 0);
d.setSeconds(0, 0);
return d;
}

var tests = [
  '1:00 pm','1:00 p.m.','1:00 p','1:00pm','1:00p.m.','1:00p','1 pm',
  '1 p.m.','1 p','1pm','1p.m.', '1p', '13:00','13', '1a', '12', '2400', 
  '1000', '100', '123', '2459', '2359', '2359am', '1100', '123p',
  '1234', '1', '9', '99', '999', '9999', '99999', '0000', '0011', '-1', 'mioaw' ];

for ( var i = 0; i < tests.length; i++ ) {
  console.log( tests[i].padStart( 9, ' ' ) + " = " + parseTime(tests[i]) );
}
Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
Brad
  • 1,360
  • 4
  • 18
  • 27
0
/(\d+)(?::(\d\d))(?::(\d\d))?\s*([pP]?)/ 

// added test for p or P
// added seconds

d.setHours( parseInt(time[1]) + (time[4] ? 12 : 0) ); // care with new indexes
d.setMinutes( parseInt(time[2]) || 0 );
d.setSeconds( parseInt(time[3]) || 0 );

thanks

BNL
  • 7,085
  • 4
  • 27
  • 32
0

If you only want seconds here is a one liner

const toSeconds = s => s.split(':').map(v => parseInt(v)).reverse().reduce((acc,e,i) => acc + e * Math.pow(60,i))
Souradeep Nanda
  • 3,116
  • 2
  • 30
  • 44
0

After thoroughly testing and investigating through my other compilation answer, I concluded that @Dave Jarvis's solution was the closest to what I felt were reasonable outputs and edge-case-handling. For reference, I looked at what Google Calendar's time inputs reformatted the time to after exiting the text box.

Even still, I saw that it didn't handle some (albeit weird) edge cases that Google Calendar did. So I reworked it from the ground up and this is what I came up with. I also added it to my compilation answer.

// attempt to parse string as time. return js date object
function parseTime(string) {
  string = String(string);

  var am = null;

  // check if "apm" or "pm" explicitly specified, otherwise null
  if (string.toLowerCase().includes("p")) am = false;
  else if (string.toLowerCase().includes("a")) am = true;

  string = string.replace(/\D/g, ""); // remove non-digit characters
  string = string.substring(0, 4); // take only first 4 digits
  if (string.length === 3) string = "0" + string; // consider eg "030" as "0030"
  string = string.replace(/^00/, "24"); // add 24 hours to preserve eg "0012" as "00:12" instead of "12:00", since will be converted to integer

  var time = parseInt(string); // convert to integer
  // default time if all else fails
  var hours = 12,
    minutes = 0;

  // if able to parse as int
  if (Number.isInteger(time)) {
    // treat eg "4" as "4:00pm" (or "4:00am" if "am" explicitly specified)
    if (time >= 0 && time <= 12) {
      hours = time;
      minutes = 0;
      // if "am" or "pm" not specified, establish from number
      if (am === null) {
        if (hours >= 1 && hours <= 12) am = false;
        else am = true;
      }
    }
    // treat eg "20" as "8:00pm"
    else if (time >= 13 && time <= 99) {
      hours = time % 24;
      minutes = 0;
      // if "am" or "pm" not specified, force "am"
      if (am === null) am = true;
    }
    // treat eg "52:95" as 52 hours 95 minutes 
    else if (time >= 100) {
      hours = Math.floor(time / 100); // take first two digits as hour
      minutes = time % 100; // take last two digits as minute
      // if "am" or "pm" not specified, establish from number
      if (am === null) {
        if (hours >= 1 && hours <= 12) am = false;
        else am = true;
      }
    }

    // add 12 hours if "pm"
    if (am === false && hours !== 12) hours += 12;
    // sub 12 hours if "12:00am" (midnight), making "00:00"
    if (am === true && hours === 12) hours = 0;

    // keep hours within 24 and minutes within 60
    // eg 52 hours 95 minutes becomes 4 hours 35 minutes
    hours = hours % 24;
    minutes = minutes % 60;
  }

  // convert to js date object
  var date = new Date();
  date.setHours(hours);
  date.setMinutes(minutes);
  date.setSeconds(0);
  return date;
}

var tests = [
  '1:00 pm','1:00 p.m.','1:00 p','1:00pm','1:00p.m.','1:00p','1 pm',
  '1 p.m.','1 p','1pm','1p.m.', '1p', '13:00','13', '1a', '12', '12a', '12p', '12am', '12pm', '2400am', '2400pm', '2400', 
  '1000', '100', '123', '2459', '2359', '2359am', '1100', '123p',
  '1234', '1', '9', '99', '999', '9999', '99999', '0000', '0011', '-1', 'mioaw' ];

for ( var i = 0; i < tests.length; i++ ) {
  console.log( tests[i].padStart( 9, ' ' ) + " = " + parseTime(tests[i]) );
}

I feel that this is the closest I can get for my needs, but suggestions are welcome. Note: This is American-centric in that it defaults to am/pm for certain patterns:

  • 1 => 13:00 (1:00pm)
  • 1100 => 23:00 (11:00pm)
  • 456 => 16:56 (4:56pm)
Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
V. Rubinetti
  • 1,324
  • 13
  • 21
  • Note that for time-tracking applications, `1100` would want to resolve as `11:00am`. (I'd wager that most human activities that we enter as data take place during the day.) If you can recall the edge cases where my solution was lacking, a comment to my answer would be appreciated. Nice summary! – Dave Jarvis Apr 06 '20 at 21:37
  • As a minor nit, `string = String(string);` can be `string = String(string.toLowerCase())` to avoid a redundant call. There are some other simplifications around the boolean logic that can be made.. – Dave Jarvis Apr 06 '20 at 21:40
  • A few other points: the `99*` tests give inconsistent results, `1000` could mean `10:00am`, and `-1` as `1:00pm` is good. However, returning a Date object seems to go a bit beyond the use case. (It assumes that the current Date is also desired, which isn't necessarily true for any particular time being entered.) – Dave Jarvis Apr 06 '20 at 21:54
  • It was a long time ago that I wrote this answer, but note that I wrote this code for a Google Calendar extension, so I made it mimic that behavior, even if the behavior was questionable. For example, `1100` maps to 11pm in GCal. Re: `99*`, I don't know. It could be debated what `999`, `9999`, etc should interpreted as, but my comments say my assumptions. GCal in that case doesn't even accept eg `52:95`. Finally, re: `Date`. It provides some other nice `get` and `format` functions. Also some libraries need things in `Date` format, which was my case iirc. – V. Rubinetti Apr 07 '20 at 00:12
0

I've needed a time parser function and based on some of the answers i ended up with this function

 function parse(time){
  let post_meridiem = time.match(/p/i) !== null;
  let result;
  time = time.replace(/[^\d:-]/g, '');
  let hours = 0;
  let minutes = 0;
  if (!time) return;
  let parts = time.split(':');
  if (parts.length > 2) time = parts[0] + ':' + parts[1];
  if (parts[0] > 59 && parts.length === 2) time = parts[0];
  if (!parts[0] && parts[1] < 60) minutes = parts[1];
  else if (!parts[0] && parts[1] >= 60) return;
  time = time.replace(/^00/, '24');
  time = parseInt(time.replace(/\D/g, ''));
  if (time >= 2500) return;
  if (time > 0 && time < 24 && parts.length === 1) hours = time;
  else if (time < 59) minutes = time;
  else if (time >= 60 && time <= 99 && parts[0]) {
    hours = ('' + time)[0];
    minutes = ('' + time)[1];
  } else if (time >= 100 && time <= 2359) {
    hours = ~~(time / 100);
    minutes = time % 100;
  } else if (time >= 2400) {
    hours = ~~(time / 100) - 24;
    minutes = time % 100;
    post_meridiem = false;
  }
  if (hours > 59 || minutes > 59) return;
  if (post_meridiem && hours !== 0) hours += 12;
  if (minutes > 59) minutes = 59;
  if (hours > 23) hours = 0;
  result = ('' + hours).padStart(2, '0') + ':' + ('' + minutes).padStart(2, '0');
  return result;
}
 var tests = [
   '1:00 pm','1:00 p.m.','1:00 p','1:00pm','1:00p.m.','1:00p','1 pm',
  '1 p.m.','1 p','1pm','1p.m.', '1p', '13:00','13', '1a', '12', '12a', '12p', '12am', '12pm', '2400am', '2400pm', '2400', 
  '1000', '100', '123', '2459', '2359', '2359am', '1100', '123p',
  '1234', '1', '9', '99', '999', '9999', '0000', '0011', '-1', 'mioaw',
  "0820",
  "32",
  "124",
  "1330",
  "130pm",
  "456",
  ":40",
  ":90",
  "12:69",
  "50:90",
  "aaa12:34aaa",
  "aaa50:00aaa",
 ];

    for ( var i = 0; i < tests.length; i++ ) {
      console.log( tests[i].padStart( 9, ' ' ) + " = " + parse(tests[i]) );
    }
also it's on Compilation table of other answers here is a fork Compilation table of other answers
Dharman
  • 30,962
  • 25
  • 85
  • 135
Gabriel
  • 670
  • 1
  • 8
  • 24
0

The main upvoted and selected answers were causing trouble for me and outputting ridiculous results. Below is my stab at it which seems to solve all the issues most people were having, including mine. An added functionality to mine is the ability to specify 'am' or 'pm' as a time of day to default to should the user input not specify (e.g. 1:00). By default, it's set to 'pm'.

One thing to note is this function assumes the user wants to (and attempted to) provide a string representing a time input. Because of this, the "input validation and sanitation" only goes so far as to rule out anything that would cause an error, not anything that doesn't necessarily look like a time. This is best represented by the final three test entries in the array towards the bottom of the code snippet.

const parseTime = (timeString, assumedTimeOfDay = "pm") => {
  // Validate timeString input
  if (!timeString) return null

  const regex = /(\d{1,2})(\d{2})?([a|p]m?)?/
  const noOfDigits = timeString.replace(/[^\d]/g, "").length

  if (noOfDigits === 0) return null

  // Seconds are unsupported (rare use case in my eyes, feel free to edit)
  if (noOfDigits > 4) return null

  // Add a leading 0 to prevent bad regex match (i.e. 100 = 1hr 00min, not 10hr 0min)
  const sanitized = `${noOfDigits === 3 ? "0" : ""}${timeString}`
    .toLowerCase()
    .replace(/[^\dapm]/g, "")
  const parsed = sanitized.match(regex)

  if (!parsed) return null

  // Clean up and name parsed data
  const {
    input,
    hours,
    minutes,
    meridian
  } = {
    input: parsed[0],
    hours: Number(parsed[1] || 0),
    minutes: Number(parsed[2] || 0),
    // Defaults to pm if user provided assumedTimeOfDay is not am or pm
    meridian: /am/.test(`${parsed[3] || assumedTimeOfDay.toLowerCase()}m`) ?
      "am" : "pm",
  }

  // Quick check for valid numbers
  if (hours < 0 || hours >= 24 || minutes < 0 || minutes >= 60) return null

  // Convert hours to 24hr format
  const timeOfDay = hours >= 13 ? "pm" : meridian
  const newHours =
    hours >= 13 ?
    hours :
    hours === 12 && timeOfDay === "am" ?
    0 :
    (hours === 12 && timeOfDay === "pm") || timeOfDay === "am" ?
    hours :
    hours + 12

  // Convert data to Date object and return
  return new Date(new Date().setHours(newHours, minutes, 0))
}

const times = [
  '12',
  '12p',
  '12pm',
  '12p.m.',
  '12 p',
  '12 pm',
  '12 p.m.',
  '12:00',
  '12:00p',
  '12:00pm',
  '12:00p.m.',
  '12:00 p',
  '12:00 pm',
  '12:00 p.m.',
  '12:00',
  '12:00p',
  '12:00pm',
  '12:00p.m.',
  '12:00 p',
  '12:00 pm',
  '12:00 p.m.',
  '1200',
  '1200p',
  '1200pm',
  '1200p.m.',
  '1200 p',
  '1200 pm',
  '1200 p.m.',
  '12',
  '1200',
  '12:00',
  '1',
  '1p',
  '1pm',
  '1p.m.',
  '1 p',
  '1 pm',
  '1 p.m.',
  '1:00',
  '1:00p',
  '1:00pm',
  '1:00p.m.',
  '1:00 p',
  '1:00 pm',
  '1:00 p.m.',
  '01:00',
  '01:00p',
  '01:00pm',
  '01:00p.m.',
  '01:00 p',
  '01:00 pm',
  '01:00 p.m.',
  '0100',
  '0100p',
  '0100pm',
  '0100p.m.',
  '0100 p',
  '0100 pm',
  '0100 p.m.',
  '13',
  '1300',
  '13:00',
  'random',
  '092fsd9)*(U243',
  '092fsd9)*(U'
]

times.map(t => {
  const parsed = parseTime(t)

  if (parsed) {
    console.log(`${parsed.toLocaleTimeString()} from ${t}`)
  } else {
    console.log(`Invalid Time (${t})`)
  }
})

Although I've tested this quite a bit, I'm sure I tunnel-visioned on something. If someone is able to break it (in a reasonable way), please comment and I'll see about updating!

Jacob Lockett
  • 485
  • 1
  • 7
  • 17
0

While some of the others are compact and elegant, here's a function that accounts for all the cases and allows several kinds of delimiters.

//parse user entry and return time as [h:number, m: number], else null
const parse = (entry: string | null): string | null => {
  //get up to 2 match groups for digits, delimiter, digits
  if (!entry) return null;
  var segments = entry.match(/^(\d+)[ :/.]?(\d+)?/i);
  if (!segments) return null;
  const digits1 = segments[1] ?? '';
  const digits2 = segments[2] ?? '0';
  const isAM = entry.includes('a') || entry.includes('A');
  const isPM = entry.includes('p') || entry.includes('P');

  //interpret various formats
  let h = 0,
    m = 0;
  if (digits1.length === 3) {
    //ignore digits2 and interpret '425' as 04:25
    h = parseInt(digits1.substring(0, 1), 10);
    m = parseInt(digits1.substring(1, 3), 10);
  } else if (digits1.length === 4) {
    //ignore digits2 and interpret '1425' as 14:25
    h = parseInt(digits1.substring(0, 2), 10);
    m = parseInt(digits1.substring(2, 4), 10);
  } else if (digits2.length) {
    //interpret '1', '2' as 01:02; or '3' as 03:00
    h = parseInt(digits1, 10);
    m = parseInt(digits2, 10);
  } else return null;
  if (isNaN(h)) h = 0;
  if (isNaN(m)) m = 0;
  if (h > 23 || h < 0 || m > 59 || m < 0) return null;

  //handle manually entered AM/PM
  if (h < 12 && isPM) h += 12;
  if (h === 12 && isAM) h = 0;

  return [h, m];
};
Star Ford
  • 68
  • 5
-1

If you made it this far, this is my first ever posting of an answer and, while the following is not suitable for the OP, it seems to work for me and was created using help from this thread so I thought I should share.

It's for calculating the decimal hours between two field type 'time' inputs, which have validation on them to ensure they are 24hr format:

function hours() {
            
//set variables to 0 in case times are changed
let startTime = 0, endTime = 0, startHours = 0,  startMins = 0, endHours = 0,  endMins = 0, totalTime = 0;
            
//check that the start time and end time have been entered by the user
startTime = document.getElementById("start-time").value;
endTime = document.getElementById("end-time").value;
if (startTime == '' || endTime == '') return null;

//get the full hour from the first and second numbers
startHours = parseInt(startTime[0]+startTime[1],10);     
endHours = parseInt(endTime[0]+endTime[1],10);   
            
//get the minutes from the fourth and fifth numbers and divide by 60 to decimalise
startMins = (parseInt(startTime[3]+startTime[4],10)/60 || 0);               
endMins = (parseInt(endTime[3]+endTime[4],10)/60 || 0);  
            
totalTime = (endHours + endMins) - (startHours + startMins);
document.getElementById("total-hours").value = totalTime;

//for testing       
console.log("totaltime: " + totalTime + " startHours: " + startHours +" startMins: " + startMins +" endHours: " + endHours +" endMins: " + endMins);
}

The total-hours field has onclick="hours();"

Michelle
  • 15
  • 8