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

Posted

技术标签:

【中文标题】如何从 JavaScript 中的用户输入将时间解析为 Date 对象?【英文标题】:How to parse a time into a Date object from user input in JavaScript? 【发布时间】:2010-09-13 13:41:23 【问题描述】:

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

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

下午 1:00 下午 1:00 1:00 p 下午 1:00 下午 1:00 1:00p 下午 1 点 下午 1 点 1 个人 下午 1 点 下午 1 点 1p 13:00 13

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

【问题讨论】:

【参考方案1】:

我需要一个时间解析器函数,根据一些答案,我最终得到了这个函数

 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]) );
    
它也在其他答案的编译表上,这里是一个叉子 Compilation table of other answers

【讨论】:

【参考方案2】:

在通过my other compilation answer 进行彻底测试和调查后,我得出结论,@Dave Jarvis 的解决方案最接近我认为的合理输出和极端情况处理。作为参考,我查看了退出文本框后 Google 日历的时间输入将时间重新格式化的内容。

即便如此,我发现它并没有像 Google 日历那样处理一些(尽管很奇怪)边缘情况。所以我从头开始重新设计它,这就是我想出的。我也添加到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]) );

我觉得这是最能满足我需求的方法,但欢迎提出建议。 注意:这是以美国为中心的,因为它对于某些模式默认为 am/pm:

1 => 13:00 (1:00pm) 1100 => 23:00 (11:00pm) 456 => 16:56 (4:56pm)

【讨论】:

请注意,对于时间跟踪应用程序,1100 需要解析为 11:00am。 (我敢打赌,我们作为数据输入的大多数人类活动都发生在白天。)如果您能回忆起我的解决方案缺乏的边缘情况,我们将不胜感激。不错的总结! 作为一个小细节,string = String(string); 可以是string = String(string.toLowerCase()) 以避免冗余调用。可以对布尔逻辑进行其他一些简化.. 其他几点:99* 测试给出的结果不一致,1000 可能意味着10:00am,而-1 因为1:00pm 是好的。但是,返回 Date 对象似乎超出了用例。 (它假设当前的日期也是需要的,这对于输入的任何特定时间都不一定正确。) 很久以前我写了这个答案,但请注意,我为谷歌日历扩展编写了这段代码,所以我让它模仿了这种行为,即使行为是有问题的。例如,1100 映射到 GCal 中的晚上 11 点。回复:99*,我不知道。可能会争论9999999 等应该解释为什么,但我的 cmets 说出了我的假设。在这种情况下,GCal 甚至不接受例如52:95。最后,回复:Date。它提供了一些其他不错的 getformat 函数。还有一些图书馆需要Date 格式的东西,这就是我的情况iirc。【参考方案3】:

其他答案汇总表

首先,我不敢相信没有内置功能,甚至没有强大的第三方库可以处理这个问题。实际上,这是网络开发,所以我可以相信。

试图用所有这些不同的算法测试所有边缘情况让我头晕目眩,所以我冒昧地将这个线程中的所有答案和测试编译到一个方便的表格中。

代码(和结果表)太大而无法包含内联,所以我做了一个 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

请随时 fork 我的小提琴并添加更多算法和测试用例

我没有在结果和“预期”输出之间添加任何比较,因为在某些情况下“预期”输出可能会引起争议(例如,应该将 12 解释为 12:00am12:00pm ?)。您必须仔细阅读表格,看看哪种算法对您最有意义。

注意:颜色不一定表示输出的质量或“预期”,它们仅表示输出的类型:

red = js 错误抛出

yellow = “假”值(undefinednullNaN"""invalid date"

green = js Date() 对象

light green = 其他所有内容

如果Date() 对象是输出,我将其转换为24 小时HH:mm 格式以便于比较。

【讨论】:

【参考方案4】:

如果你只想要几秒钟,这里是一条线

const toSeconds = s => s.split(':').map(v => parseInt(v)).reverse().reduce((acc,e,i) => acc + e * Math.pow(60,i))

【讨论】:

【参考方案5】:

我对其他答案不满意,所以我又做了一个。这个版本:

识别秒和毫秒 在“13:00pm”或“11:65”等无效输入时返回undefined 如果您提供 localDate 参数,则返回本地时间,否则返回 Unix 纪元(1970 年 1 月 1 日)的 UTC 时间。 支持像1330 这样的军事时间(要禁用,请在正则表达式中设置第一个':') 单独允许一个小时,24 小时制(即“7”表示早上 7 点)。 允许 24 小时作为 0 小时的同义词,但不允许 25 小时。 要求时间在字符串的开头(要禁用,删除正则表达式中的^\s*) 具有实际检测输出不正确的测试代码。

编辑:现在是 package,包括 timeToString 格式化程序: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.');

【讨论】:

【参考方案6】:

这是对Joe's version 的改进。随意进一步编辑它。

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() 调用中添加了 radix 参数(因此 jslint 不会抱怨)。 使正则表达式不区分大小写,因此“2:23 PM”的工作方式类似于“2:23 pm”

【讨论】:

【参考方案7】:

Patrick McElhaney 的解决方案的改进(他不能正确处理上午 12 点)

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

【讨论】:

【参考方案8】:

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

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

它也应该适用于其他几个品种(例如,即使使用 a.m.,它仍然可以工作)。显然这很粗糙,但也很轻巧(例如,使用它比使用完整的库便宜得多)。

警告:代码不适用于中午 12:00 等

【讨论】:

在处理完这个之后,我注意到它没有正确解析时间“12 pm”的变体,因为它在小时数上增加了 12。为了解决这个问题,我将 d.setHours 行改为: d.setHours( parseInt(time[1]) + ( ( parseInt(time[1]) 我还注意到 parseInt 被 ':30' 或 ':00' 之类的字符串阻塞,所以我更改了正则表达式以捕获不带冒号的分钟 对 ParseInt 的调用需要 10 的基数,因为 JS 在有前导 0 时假定基数为 8,导致如果小时大于 8 并且有前导 0,则将其解释为 0 (因为 08 不是有效的以 8 为基数的数字)。另外,改变“p?”到“[pP]?”当 AM/PM 为大写时将使其工作。总而言之,除非您真的确定这种方法对您有用,否则您应该使用库。请记住,时间憎恨我们所有人。 使用“[pP]”的替代方法是将“i”附加到文字的末尾。这将使匹配不区分大小写。 对像我一样通过 Google 找到此内容的任何人的忠告。不要使用它。它似乎有效,但在上午 12 点左右的时间是错误的。 cmets/edits 不能解决这个问题。 Nathan 的解决方案更完善。【参考方案9】:

提供的所有示例在上午 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 点。如果期望的结果是它只返回字符串中只包含时间的时间,则可以使用以下正则表达式,前提是您将“time[4]”替换为“time[6]”。

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

【讨论】:

【参考方案10】:

这是另一种方法,涵盖原始答案、任何合理的位数、猫的数据输入和逻辑谬误。算法如下:

    判断子午线是否为post meridiem。 将输入数字转换为整数值。 0 到 24 之间的时间:小时是点,没有分钟(12 小时是下午)。 100 到 2359 之间的时间:小时 div 100 是点,分钟模 100 余数。 从 2400 开始的时间:小时是午夜,还有分钟。 当小时数超过 12 时,减去 12 并强制 meridiem 为真。 当分钟超过 59 时,强制为 59。

将小时、分钟和子午线转换为 Date 对象是读者的练习(许多其他答案显示了如何做到这一点)。

"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() );

使用jQuery,新定义的String原型使用如下:

  <input type="text" class="time" />
  $(".time").change( function() 
    var $this = $(this);
    $(this).val( time.toTime() );
  );

【讨论】:

【参考方案11】:

我对上面的函数做了一些修改,以支持更多的格式。

1400 -> 下午 2:00 1.30 -> 下午 1:30 1:30a -> 1:30 AM 100 -> 凌晨 1:00

尚未清理它,但适用于我能想到的一切。

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

【讨论】:

【参考方案12】:

对于所有使用支持 24 小时制时钟的人来说,这里有一个解决方案:

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

【讨论】:

【参考方案13】:

这是一种更可靠的方法,它考虑了用户打算如何使用这种类型的输入。例如,如果用户输入“12”,他们会期望它是中午 12 点(中午),而不是上午 12 点。下面的函数处理所有这些。也可以在这里找到: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*((\d2)?:?)(\d1,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() );

这是一个字符串原型,所以你可以这样使用它:

var str = '12am';
var date = str.parseTime();

【讨论】:

【参考方案14】:

我在实施 John Resig 的解决方案时遇到了一些问题。这是我根据他的回答一直在使用的修改后的功能:

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

【讨论】:

这与我在上面投票最高的答案中提到的相同。【参考方案15】:

这里的大多数正则表达式解决方案在无法解析字符串时都会抛出错误,并且其中没有多少会考虑到像 1330130pm 这样的字符串。尽管 OP 没有指定这些格式,但我发现它们对于解析人类输入的日期至关重要。

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

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

演示:http://jsfiddle.net/HwwzS/1/ 代码:https://gist.github.com/claviska/4744736

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,但与这个解决方案相比,这是巨大的。 我需要它,所以继续用肮脏的 hack 解决了这个问题:codepen.io/anon/pen/EjrVqq 应该有更好的解决方案,但我还不能确定。【参考方案16】:

很多答案,所以再多一个就不会受到伤害。

/**
 * 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]) );

为了适当的稳健,它应该检查每个值是否在允许值的范围内,例如,如果上午/下午时间必须是 1 到 12(含),否则是 0 到 24(含)等。

【讨论】:

【参考方案17】:

time 包大小为 0.9kbs。可用于 NPM 和 bower 包管理器。

这是直接来自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

【讨论】:

此软件包不支持 24 小时制,这可能是也可能不是重大限制。 @grupler 感谢您的重要说明。 FWIW,有一个 Pull Request 完成了支持 24 小时格式的工作。 PR 是在 2014 年提交的。无论如何,这里是那个 Pull Request 的链接:github.com/zackdever/time/pull/7 这个包不支持秒或毫秒。 (这里的大多数答案也没有,但我倾向于从“包”中得到更多的期望,而不是 SO 答案中的代码 sn-p。)【参考方案18】:
/(\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 );

谢谢

【讨论】:

【参考方案19】:

AnyTime.Converter 可以解析多种不同格式的日期/时间:

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

【讨论】:

我应该补充一点:跳过该页面顶部 3/4 的选择器内容,然后查看转换部分:ama3.com/anytime/#converting【参考方案20】:

为什么不使用验证来缩小用户可以输入的范围并简化列表以仅包含可以解析(或经过一些调整后解析)的格式。

我认为要求用户以支持的格式输入时间并不过分。

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

dd A(m)/P(m)

dd

【讨论】:

你说得对,真的不是要求太多。然而,这对用户来说是一个障碍,我想让这个特定的表单尽可能地易于使用。理想情况下,输入将足够灵活,可以解释他们输入的内容并将其重新格式化为标准格式。【参考方案21】:

不用自己动手,用datejs就行了。

【讨论】:

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

以上是关于如何从 JavaScript 中的用户输入将时间解析为 Date 对象?的主要内容,如果未能解决你的问题,请参考以下文章

如何重新排列javascript循环中的数字?

如何将pdf中的数据输入网站

如何从 JavaScript 中的无线电输入中获取“未检查”事件?

如何通过单独的联系表单将 HTML 输入从页面发送给用户

如何将从输入字段获取的画布名称与Javascript中的画布合并?

如何从目标C中的stringByEvaluatingJavaScriptFromString:方法执行长javascript?