Каков наилучший способ для анализа времени в объект Date из пользовательского ввода в Javascript? - PullRequest
62 голосов
/ 26 сентября 2008

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

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

  • 13:00
  • 13:00
  • 1: 00 р
  • 1: 00 pm
  • 1: 00 p.m.
  • 1: 00p
  • 13:00
  • 1 час.
  • 1 р
  • 1027 * 1 вечер *
  • 1029 * 1 вечер *
  • 13: 00
  • 13

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

Ответы [ 20 ]

1 голос
/ 09 марта 2018

Вот еще один подход, который охватывает исходный ответ, любое разумное количество цифр, ввод данных кошками и логические ошибки. Алгоритм следующий:

  1. Определите, является ли меридиан после меридиема .
  2. Преобразование входных цифр в целочисленное значение.
  3. Время от 0 до 24: часы - это часы, без минут (часы 12 - это вечера).
  4. Время между 100 и 2359: часы делятся на 100 - это часы, минуты - остаток 100.
  5. Время с 24:00: часы - полночь, оставшиеся минуты.
  6. Когда часы превышают 12, вычтите 12 и выполните принудительное меридием true.
  7. Когда количество минут превышает 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() );
  });
1 голос
/ 06 декабря 2011

Я внес некоторые изменения в вышеуказанную функцию для поддержки еще нескольких форматов.

  • 1400 -> 14:00
  • 1,30 -> 13: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]) );
}
0 голосов
/ 13 декабря 2018

После тщательного тестирования и изучения с помощью моего другого ответа по компиляции , я пришел к выводу, что решение @Dave Jarvis было наиболее близким к тому, что я чувствовал, были разумные результаты и обработка крайних случаев. Для справки я посмотрел, во сколько времени в календаре Google переформатировалось время после выхода из текстового поля.

Тем не менее, я видел, что он не обрабатывал некоторые (хотя и странные) крайние случаи, которые делал Календарь Google. Так что я переработал его с нуля, и это то, что я придумал. Я также добавил его в мой компиляционный ответ .

// attempt to parse string as time. return js date object
static 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;
}

Я чувствую, что это самое близкое, что я могу получить для своих нужд, но предложения приветствуются. Примечание: Это ориентировано на американцев в том смысле, что по умолчанию для некоторых моделей используется значение am / pm:

  • 1 => 13:00 (1:00pm)
  • 1100 => 23:00 (11:00pm)
  • 456 => 16:56 (4:56pm)
0 голосов
/ 12 декабря 2018

Таблица составления других ответов

Прежде всего, я не могу поверить , что нет встроенной функциональности или даже надежной сторонней библиотеки, способной справиться с этим. На самом деле, это веб-разработка, поэтому я могу в это поверить.

Попытка протестировать все граничные случаи с помощью всех этих различных алгоритмов заставляла меня вращаться, поэтому я позволил себе собрать все ответы и тесты в этой теме в удобную таблицу.

Код (и результирующая таблица) бессмысленно велик, чтобы включать встроенный код, поэтому я создал 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 для удобства сравнения.

0 голосов
/ 22 августа 2018

Если вам нужны только секунды, это один вкладыш

const toSeconds = s => s.split(':').map(v => parseInt(v)).reverse().reduce((acc,e,i) => acc + e * Math.pow(60,i))
0 голосов
/ 09 июня 2018

Я не был доволен другими ответами, поэтому сделал еще один. Эта версия:

  • Распознает секунды и миллисекунды
  • Возвращает undefined при неправильном вводе, таком как «13:00 pm» или «11:65»
  • Возвращает местное время, если вы указали параметр localDate, в противном случае возвращает время UTC в эпоху Unix (1 января 1970 г.).
  • Поддерживает военное время, например 1330 (чтобы отключить, введите в регулярном выражении первый ':')
  • Разрешает один час с 24-часовым временем (т. Е. "7" означает 7:00).
  • Позволяет использовать 24-й час как синоним 0-го часа, но 25-й не допускается.
  • Требуется время, которое должно быть в начале строки (чтобы отключить, удалите ^\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.log(`Test failed at ${key}:"${strings[i]}" with result ${result ? result.toUTCString() : 'undefined'}`);
    } else {
      passed++;
    }
  }
}
console.log(passed + ' tests passed.');
0 голосов
/ 05 января 2015

Множество ответов, так что еще один не повредит.

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

Чтобы быть должным образом устойчивым, он должен проверить, что каждое значение находится в пределах диапазона допустимых значений, например, если часы am / pm должны быть от 1 до 12 включительно, в противном случае от 0 до 24 включительно и т.д.

0 голосов
/ 11 января 2009
/(\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 );

спасибо

0 голосов
/ 26 сентября 2008

Почему бы не использовать проверку для сужения того, что пользователь может вставить, и для упрощения списка, включающего только форматы, которые можно анализировать (или анализировать после некоторой настройки).

Не думаю, что он слишком много требует, чтобы пользователь указывал время в поддерживаемом формате.

дд: дд A (м) / P (м)

дд А (м) / Р (м)

дд

0 голосов
/ 22 октября 2010

Улучшение в решении Патрика МакЭлхани (его неправильно обрабатывает 12:00)

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]) );
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...