Я работаю над виджетом формы, чтобы пользователи могли вводить время дня в текстовый ввод (для календарного приложения). Используя JavaScript (мы используем jQuery FWIW), я хочу найти лучший способ проанализировать текст, который пользователь вводит в объект JavaScript Date(), чтобы я мог легко выполнять с ним сравнения и другие вещи.
Я попробовал метод parse(), и он слишком разборчив для моих нужд. Я ожидал, что он сможет успешно проанализировать следующие примеры времени ввода (в дополнение к другим логически схожим форматам времени) как тот же объект Date():
Я думаю, что могу использовать регулярные выражения для разделения ввода и извлечения информации, которую я хочу использовать для создания моего объекта Date(). Как лучше всего это сделать?



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Не трудитесь делать это самостоятельно, просто используйте datejs.
Учитывая диапазон ввода, который вы хотите принять, я бы тоже выбрал datejs. Кажется, он обрабатывает большинство из них, за исключением того, которое представляет собой просто число, которое он принимает как день месяца.
Да, я мог бы просто использовать datejs. Я могу обойти ввод одного числа, который рассматривается как месяц, добавив к строке «1/1/2000», когда я анализирую время.
Огромный +1! Что касается размера - на самом деле это 25 КБ на локаль. Но по крайней мере он поддерживает локали! Вместо того, чтобы писать собственные процедуры, используйте то, что доступно (или напишите лучшую библиотеку и поделитесь ею). Кроме того, хотя пробелы удаляются из JS, они не кажутся мне минимизированными, поэтому вы можете сохранить там несколько байтов. Если это так важно для тебя.
Пробовал на datejs.com (в разделе «Mad Skillz…») «12 ср 2020» ==> «Понедельник, 3 декабря 2012 г., 12:00:00» - ват? 2012 ??
Я человек и не знаю, что означает «12 ср 2020». Есть несколько способов интерпретировать это. Если бы я угадал, что делает DateJS, я бы сказал, что он интерпретировал «12» как день месяца и искал следующую среду, выпавшую на 12-е число, то есть в декабре. Единственное, что меня удивило, это то, что он выбросил «2020» вместо того, чтобы интерпретировать его как 20:20.
@SebastianMach Даже на скорости Венесуэлы 25 КБ занимает всего 1/8 секунды.
@ Джим: Я просто говорю о потенциальных возможностях. Никогда не говори никогда, особенно с амбициозными владельцами продукта.
Почему бы не использовать проверку, чтобы сузить то, что пользователь может ввести, и упростить список, включив только форматы, которые можно анализировать (или анализировать после некоторой настройки).
Я не думаю, что требуется слишком много, чтобы пользователь указывал время в поддерживаемом формате.
dd: dd A (м) / P (м)
dd A (м) / P (м)
дд
Вы правы, это действительно не так уж много. Однако для пользователя это немного затруднительно, и я хочу сделать эту конкретную форму настолько простой в использовании, насколько это разумно. В идеале ввод должен быть достаточно гибким, чтобы интерпретировать то, что они вводят, и переформатировать его в стандартный формат.
Быстрое решение, которое работает с указанным вами вводом:
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.info( tests[i].padStart( 9, ' ' ) + " = " + parseTime(tests[i]) );
}Он должен работать и для некоторых других разновидностей (например, даже если используется утренний час, он все равно будет работать). Очевидно, это довольно грубо, но также довольно легковесно (намного дешевле в использовании, чем, например, полная библиотека).
Warning: The code doe not work with 12:00 AM, etc.
Поработав с этим, я заметил, что он неправильно анализирует варианты времени «12 часов дня», потому что он добавляет 12 к числу часов. Чтобы исправить это, я изменил строку d.setHours следующим образом: d.setHours (parseInt (time [1]) + ((parseInt (time [1]) <12 && time [3])? 12: 0));
Я также заметил, что parseInt задыхается от строк типа ': 30' или ': 00', поэтому я изменил регулярное выражение, чтобы фиксировать минуты без двоеточия.
Лучше надеяться, что d не выпадет на день, когда вступает в силу переход на летнее время. Это также предполагает английские соглашения.
Для вызовов ParseInt требуется основание системы счисления 10, потому что JS предполагает основание системы счисления 8, когда есть начальный 0, в результате чего час интерпретируется как 0, если он больше 8 и имеет начальный 0 (поскольку 08 не является допустимым число по основанию 8). Кроме того, изменение "p?" на "[pP]?" заставит его работать, когда AM / PM в верхнем регистре. В общем, если вы В самом деле не уверены, что этот подход сработает для вас, вам следует использовать библиотеку. Помните, время нас всех ненавидит.
Альтернативой использованию «[pP]» было бы добавление «i» в конец литерала. Это сделало бы совпадение нечувствительным к регистру.
И теперь также доступен в форме функции для ленивых (вроде меня): var timeParser = функция (stringTime) {var d = new Date (); var time = stringTime.match (/ (\ d +) (? :: (\ d \ d))? \ s * (p?) /); d.setHours (parseInt (время [1]) + (время [3]? 12: 0)); d.setMinutes (parseInt (время [2]) || 0); //console.info (d); return d; }
Небольшой совет всем, кто нашел это через Google, как и я. Не используйте это. Кажется, это работает, но это неправильно для времени около 12 часов утра. Комментарии / правки этого не решают. Решение Натана более полное.
При реализации решения Джона Ресига я столкнулся с парой изломов. Вот модифицированная функция, которую я использовал на основе его ответа:
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.info( tests[i].padStart( 9, ' ' ) + " = " + parseTime(tests[i]) );
}Это те же ошибки, которые я отметил в ответе с наибольшим количеством голосов выше.
Вот улучшение Версия Джо. Не стесняйтесь редактировать его дальше.
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.info( tests[i].padStart( 9, ' ' ) + " = " + parseTime(tests[i]) );
}Изменения:
/(\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 );
Благодарность
Все приведенные примеры не работают с 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.info( tests[i].padStart( 9, ' ' ) + " = " + parseTime(tests[i]) );
}Это будет работать для строк, которые содержат время где угодно внутри себя. Таким образом, "abcde12: 00pmdef" будет проанализирован и вернет 12 часов вечера. Если желаемый результат состоит в том, что он возвращает только время, когда строка содержит только время, можно использовать следующее регулярное выражение при условии, что вы замените «time [4]» на «time [6]».
/^(\d+)(:(\d\d))?\s*((a|(p))m?)?$/i
Это более надежный подход, который учитывает то, как пользователи намереваются использовать этот тип ввода. Например, если пользователь ввел «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*((\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.info( tests[i].padStart( 9, ' ' ) + " = " + tests[i].parseTime() );
}Это строковый прототип, поэтому вы можете использовать его так:
var str = '12am';
var date = str.parseTime();
AnyTime.Converter может анализировать дату / время во многих различных форматах:
Я должен добавить: пропустите средство выбора в верхней 3/4 этой страницы и посмотрите раздел о преобразовании: ama3.com/anytime/#converting
Улучшение решения Патрика МакЭлхейни (он неправильно обрабатывает 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.info( tests[i].padStart( 9, ' ' ) + " = " + parseTime(tests[i]) );
}Вот еще одно решение для всех тех, кто использует 24-часовой формат, который поддерживает:
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.info( tests[i].padStart( 9, ' ' ) + " = " + parseTime(tests[i]) );
}Я внес некоторые изменения в указанную выше функцию для поддержки еще нескольких форматов.
Еще не почистил, но работает для всего, что я могу придумать.
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.info( tests[i].padStart( 9, ' ' ) + " = " + parseTime(tests[i]) );
}Большинство решений с регулярными выражениями здесь выдают ошибки, когда строка не может быть проанализирована, и не многие из них учитывают такие строки, как 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.info( tests[i].padStart( 9, ' ' ) + " = " + parseTime(tests[i]) );
}Я должен был это отметить. Это единственный действительно ответ полезный, когда размер имеет значение. В большинстве случаев я использую momentjs, но это огромно по сравнению с этим решением.
Мне это было нужно, поэтому я пошел дальше и исправил это с помощью грязного хака: codepen.io/anon/pen/EjrVqq должно быть лучшее решение, но я пока не мог понять его.
Ответов много, так что еще один не повредит.
/**
* 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.info( tests[i].padStart( 9, ' ' ) + " = " + parseTime(tests[i]) );
}Чтобы быть должным образом устойчивым, он должен проверять, что каждое значение находится в пределах допустимого диапазона, например, если часы am / pm должны быть от 1 до 12 включительно, в противном случае от 0 до 24 включительно и т. д.
Размер пакета время составляет 0,9 КБ. Доступно с менеджерами пакетов 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-часовое время, что может быть, а может и не быть значительным ограничением.
@gruppler спасибо за это очень важное замечание. FWIW, есть Pull Request с проделанной работой для поддержки 24-часового формата. PR был представлен в 2014 году. В любом случае, вот ссылка на этот запрос на слияние: github.com/zackdever/time/pull/7
Этот пакет не поддерживает секунды и миллисекунды. (В большинстве ответов здесь тоже нет, но я склонен ожидать большего от «пакета», чем от фрагмента кода в ответе SO.)
Вот еще один подход, который охватывает исходный ответ, любое разумное количество цифр, ввод данных кошками и логические ошибки. Алгоритм следующий:
Преобразование часов, минут и меридием сообщений в объект 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.info( tests[i].padStart( 9, ' ' ) + " = " + tests[i].toTime() );
}В jQuery вновь определенный прототип String используется следующим образом:
<input type = "text" class = "time" />
$(".time").change( function() {
var $this = $(this);
$(this).val( time.toTime() );
});
Мне не понравились другие ответы, поэтому я сделал еще один. Эта версия:
undefined при недопустимом вводе, таком как «13:00 pm» или «11:65».localDate, в противном случае возвращает время в формате UTC для эпохи Unix (1 января 1970 г.).1330 (чтобы отключить, сделайте первый ":" обязательным в регулярном выражении)^\s* в регулярном выражении)Обновлено: теперь это упаковка, включая форматтер 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.info(`Test failed at ${key}:"${strings[i]}" with result ${result ? result.toUTCString() : 'undefined'}`);
} else {
passed++;
}
}
}
console.info(passed + ' tests passed.');
Если вам нужны только секунды, вот один лайнер
const toSeconds = s => s.split(':').map(v => parseInt(v)).reverse().reduce((acc,e,i) => acc + e * Math.pow(60,i))
Составительная таблица других ответов
Прежде всего, я не могу поверить, что нет встроенной функциональности или даже надежной сторонней библиотеки, которая могла бы справиться с этим. На самом деле, это веб-разработка, так что я могу в это поверить.
Попытка протестировать все крайние случаи с помощью всех этих различных алгоритмов кружила мне голову, поэтому я взял на себя смелость скомпилировать все ответы и тесты в этом потоке в удобную таблицу.
Код (и результирующая таблица) бессмысленно велики для включения в строку, поэтому я сделал 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
Пожалуйста, не стесняйтесь разветвлять мою скрипку и добавлять больше алгоритмов и тестовых примеров
Я не добавлял никаких сравнений между результатом и «ожидаемым» выводом, потому что бывают случаи, когда «ожидаемый» вывод может быть предметом обсуждения (например, следует ли интерпретировать 12 как 12:00am или 12:00pm?). Вам нужно будет просмотреть таблицу и посмотреть, какой алгоритм наиболее подходит для вас.
Примечание: Цвета не обязательно указывают на качество или «ожидаемость» вывода, они только указывают тип вывода:
red = ошибка js
yellow = "ложное" значение (undefined, null, NaN, "", "invalid date")
green = объект js Date()
light green = все остальное
Если на выходе объект Date(), я конвертирую его в 24-часовой формат HH:mm для простоты сравнения.
После тщательного тестирования и исследования с помощью мой другой ответ компиляции я пришел к выводу, что решение @Dave Jarvis было наиболее близким к тому, что я считал разумными выходами и обработкой крайних случаев. Для справки, я посмотрел, какие временные поля Google Calendar переформатировали время после выхода из текстового поля.
Тем не менее, я заметил, что он не справлялся с некоторыми (хотя и странными) крайними случаями, как Google Calendar. Так что я переработал его с нуля, и это то, что я придумал. Тоже добавил в мой ответ компиляции.
// 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.info( 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, похоже, выходит за рамки варианта использования. (Предполагается, что также желательна текущая дата, что не обязательно верно для любого конкретного вводимого времени.)
Я написал этот ответ очень давно, но обратите внимание, что я написал этот код для расширения Google Calendar, поэтому я сделал его имитирующим это поведение, даже если поведение было сомнительным. Например, 1100 соответствует 23:00 в GCal. Re: 99*, не знаю. Можно спорить, как следует интерпретировать 999, 9999 и т. д., Но мои комментарии говорят о моих предположениях. GCal в этом случае даже не принимает, например, 52:95. Наконец, re: Date. Он предоставляет некоторые другие приятные функции get и format. Также некоторым библиотекам нужны вещи в формате Date, в моем случае это был iirc.
Мне нужна была функция синтаксического анализа времени, и на основе некоторых ответов я получил эту функцию
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.info( tests[i].padStart( 9, ' ' ) + " = " + parse(tests[i]) );
}
25КБ только на свидания?!?! Я имею в виду, без сомнения, хорошая библиотека, и если бы мне понадобилась функция обработки психологических дат, она была бы единственной. Но 25 КБ больше, чем все ядро jQuery !!!