How to parse a time into a Date object from user input in JavaScript?
Solution 1
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.
Solution 2
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
Solution 3
Don't bother doing it yourself, just use datejs.
Solution 4
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]) );
}
Solution 5
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"
MAAAAANI
Updated on July 05, 2022Comments
-
MAAAAANI almost 2 years
I'm using node.js with socketstream..I'm getting following error while running my
"appln.Cannot find module '/entry' [Break On This Error]
throw new Error("Cannot find module '" + x + "'")
NewNodeProject client -code -app -libs -css -libs static templates views -app.html node_modules -express -socketstream server -rpc -user.js -app.js
-
paulbjensen over 11 yearsHi Maaaaani, did you generate that project using 'socketstream new <project_name>'? It looks like you're missing an entry.js file at /client/code/app/entry.js
-
MAAAAANI over 11 years@paulbjensen : no i didn't generate the project using 'socketstream new <project_name>'.ok i will try that.
-
MAAAAANI over 11 years@paulbjensen:thanks.now my application is working fine..
-
-
Jason Bunting over 15 years25KB 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!!!
-
serverpunk over 15 yearsGiven 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.
-
Joe Lencioni over 15 yearsYeah, 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 over 15 yearsYou 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 over 15 yearsAfter 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 over 15 yearsI also noticed that parseInt was choking on strings like ':30' or ':00' so I changed the regex to capture the minutes without the colon
-
peller over 14 yearsYou'd better hope d doesn't fall on a day where a daylight savings change takes effect. This also assumes English conventions.
-
Andrew M. Andrews III about 14 yearsI should add: skip past the picker stuff at the top 3/4 of that page and look at the section on converting: ama3.com/anytime/#converting
-
Benji York almost 14 yearsThe 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 almost 14 yearsThis has the same bugs I noted in the highest-voted answer above.
-
Lucas Gabriel Sánchez almost 13 yearsAn alternative to using "[pP]" would be to append "i" to the end of the literal. That would make the match case-insensitive.
-
johndodo about 12 yearsHuge +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.
-
Jonas N over 11 yearsTried it at datejs.com (under "Mad Skillz…") "12 wed 2020" ==> "Monday, December 03, 2012 12:00:00 AM" -- wat? 2012??
-
Lomithrani over 11 yearsI'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.
-
Michael Trouw about 11 yearsAnd 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; }
-
Peter Wone almost 10 yearsI 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.
-
Nomenator almost 9 yearsI needed that, and so went ahead and fixed that with a dirty hack: codepen.io/anon/pen/EjrVqq there should be a better solution, but I couldn't put my finger on it yet.
-
braks over 8 yearsA 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.
-
jwg about 7 years@SebastianMach Even at Venezuelan speeds, 25KB only takes 1/8 of a second.
-
Sebastian Mach about 7 years@Jim: I am just talking potential features. Never say never, especially with ambitious product owners.
-
gruppler over 6 yearsThis package doesn't support 24-hour time, which may or may not be a significant limitation.
-
Sgnl over 6 years@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: github.com/zackdever/time/pull/7
-
Qwertie almost 6 yearsThis 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.)
-
Dave Jarvis about 4 yearsNote that for time-tracking applications,
1100
would want to resolve as11: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 about 4 yearsAs a minor nit,
string = String(string);
can bestring = String(string.toLowerCase())
to avoid a redundant call. There are some other simplifications around the boolean logic that can be made.. -
Dave Jarvis about 4 yearsA few other points: the
99*
tests give inconsistent results,1000
could mean10:00am
, and-1
as1: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.) -
V. Rubinetti about 4 yearsIt 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 what999
,9999
, etc should interpreted as, but my comments say my assumptions. GCal in that case doesn't even accept eg52:95
. Finally, re:Date
. It provides some other niceget
andformat
functions. Also some libraries need things inDate
format, which was my case iirc. -
Heretic Monkey about 3 yearsThis 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.
-
Ralph Yozzo almost 3 yearsThis one has issues with 12:30am etc. stackoverflow.com/users/264837/nathan-villaescusa answer is better.