如何从 JavaScript 中的用户输入将时间解析为 Date 对象?

IT技术 javascript datetime parsing date time
2021-01-12 12:36:33

我正在开发一个表单小部件,供用户在文本输入中输入一天中的时间(对于日历应用程序)。使用 JavaScript(我们使用 jQuery FWIW),我想找到解析用户输入 JavaScriptDate()对象的文本的最佳方法,以便我可以轻松地对其进行比较和其他操作。

我尝试了该parse()方法,但它对我的需求来说有点太挑剔了。我希望它能够将以下示例输入时间(除了其他逻辑上相似的时间格式)成功解析为同一Date()对象:

  • 1:00 PM
  • 1:00 PM
  • 1:00 下午
  • 1:00 PM
  • 1:00 PM。
  • 1:00p
  • 下午 1 点
  • 下午 1 点
  • 1 人
  • 下午1点
  • 下午 1 点
  • 1p
  • 13:00
  • 13

我想我可能会使用正则表达式来拆分输入并提取我想用来创建Date()对象的信息。做这个的最好方式是什么?

6个回答

适用于您指定的输入的快速解决方案:

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]) );
}

它也应该适用于其他一些品种(即使使用了 am,它仍然可以工作 - 例如)。显然这很粗糙,但它也很轻量级(例如,使用它比使用完整库便宜得多)。

警告:该代码不适用于上午 12:00 等。

我还注意到 parseInt 被诸如 ':30' 或 ':00' 之类的字符串阻塞,所以我更改了正则表达式以捕获没有冒号的分钟
2021-03-15 12:36:33
处理完这个之后,我注意到它没有正确解析时间“12 pm”的变体,因为它在小时数上加了 12。为了修复,我将 d.setHours 行更改为: d.setHours( parseInt(time[1]) + ( ( parseInt(time[1]) < 12 && time[3] ) ? 12 : 0) );
2021-03-23 12:36:33
给像我一样通过谷歌找到这个的任何人的建议。不要使用它。它似乎有效,但在凌晨 12 点左右出现错误。评论/编辑不能解决这个问题。Nathan的解决方案更完整。
2021-03-29 12:36:33
使用“[pP]”的替代方法是将“i”附加到文字的末尾。这将使匹配不区分大小写。
2021-03-31 12:36:33
对 ParseInt 的调用需要 10 的基数,因为当有前导 0 时 JS 假定基数为 8,导致小时被解释为 0 如果它大于 8 并且有前导 0(因为 08 不是有效的)基数为 8 的数字)。另外,改变“p?” 到“[pP]?” 当 AM/PM 为大写时将使其工作。总而言之,除非您真的确定这种方法对您有用,否则您应该使用库。记住,时间讨厌我们所有人。
2021-04-10 12:36:33

提供的所有示例都无法在上午 12:00 到上午 12:59 的时间内工作。如果正则表达式与时间不匹配,它们也会抛出错误。下面处理这个:

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]) );
}

这适用于在其中任何地方包含时间的字符串。所以“abcde12:00pmdef”将被解析并返回 12 pm。如果期望的结果是它只返回字符串中仅包含时间的时间,则可以使用以下正则表达式,前提是您将“time[4]”替换为“time[6]”。

/^(\d+)(:(\d\d))?\s*((a|(p))m?)?$/i

不要费心自己做,只需使用datejs

25KB 只是为了约会?!?!我的意思是,毫无疑问,不错的库,如果我必须拥有心理日期处理功能,那就是它。但是 25KB 比 jQuery 的所有核心都要大!!!
2021-03-25 12:36:33
鉴于您想要接受的输入范围,我也会选择 datejs。它似乎处理了大多数,除了一个只是一个数字,它作为一个月的第几天。
2021-03-28 12:36:33
我是一个人类,我不知道“2020 年 12 月 12 日”是什么意思。有多种解释方式。如果让我猜 DateJS 在做什么,我会说它将“12”解释为一个月中的第几天,并寻找下一个落在 12 日(即 12 月)的星期三。唯一让我感到惊讶的是它扔掉了“2020”,而不是将其解释为晚上 8 点 20 分。
2021-03-29 12:36:33
巨大的+1!至于大小 - 实际上每个语言环境是 25KB。但至少它支持语言环境!与其编写自己的程序,不如使用可用的程序(或编写一个更好的库并共享它)。此外,虽然从 JS 中删除了空格,但在我看来它并没有缩小,因此您可以在那里保存一些字节。如果这对你来说很重要。
2021-03-31 12:36:33
@SebastianMach 即使在委内瑞拉的速度下,25KB 也只需要 1/8 秒。
2021-04-11 12:36:33

当无法解析字符串时,此处的大多数正则表达式解决方案都会引发错误,并且其中没有多少会考虑像1330或 之类的字符串130pm尽管这些格式不是由 OP 指定的,但我发现它们对于解析人类输入的日期至关重要。

所有这些让我想到使用正则表达式可能不是最好的方法。

我的解决方案是一个函数,它不仅可以解析时间,而且还允许您指定输出格式和将分钟舍入到的步长(间隔)。大约 70 行,它仍然是轻量级的,可以解析所有上述格式以及没有冒号的格式。

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]) );
}

我必须把它标记出来。当尺寸很重要时,这是唯一真正有用的答案。大多数时候我使用momentjs,但与这个解决方案相比,这是巨大的。
2021-03-15 12:36:33
我需要那个,所以继续用一个肮脏的黑客来解决这个问题codepen.io/anon/pen/EjrVqq应该有一个更好的解决方案,但我还不能解决它。
2021-03-19 12:36:33

这是对Joe 版本的改进随意进一步编辑它。

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]) );
}

变化:

  • 向 parseInt() 调用添加了基数参数(因此 jslint 不会抱怨)。
  • 使正则表达式不区分大小写,因此“2:23 PM”的工作方式类似于“2:23 pm”