Кросс-браузер и перспективный метод извлечения даты из формата ISO 8601?

Мы используем API Facebook для получения некоторых сообщений и соответствующих им дат и отображения их на созданном нами веб-сайте. API facebook возвращает дату в следующем формате:

2016-05-22T10:38:38+0000

Я определил, что это формат ISO 8601. До недавнего времени мы использовали код, аналогичный следующему, для извлечения удобочитаемой даты в формате мм/дд/гггг:

//created_time = '2016-05-22T10:38:38+0000'
var date = new Date(created_time).toLocaleDateString();

Однако мы заметили, что это не работает в iOS Safari, и все решения включают в себя разбиение строки и ее синтаксический анализ с помощью функции Date().

Я думал, что что-то простое и грязное вроде этого должно работать:

var getDate = function(date) {
    var day   = date.substr(8, 2);
    var month = date.substr(5, 2);
    var year  = date.substr(0, 4);
    return [day, month, year].join('/');
}

Но у моего коллеги есть сомнения по поводу его устойчивости к будущему. Что, если Facebook решит изменить формат, в котором возвращается его дата? Совершенно справедливое беспокойство. Итак, может ли кто-нибудь порекомендовать программный кросс-браузерный метод возврата читаемой даты в формате dd/mm/yyyy из этой строки? Спасибо.


person styke    schedule 06.06.2016    source источник
comment
Разве Date.parse не является явным способом анализа iso 8601 в js? Вы пробовали это? Оговорки вашего коллеги действительны просто на том основании, что весь спектр iso 8601 не охвачен этой функцией getDate.   -  person Crescent Fresh    schedule 06.06.2016
comment
@CrescentFresh — нет. Ни Date.parse, ни конструктор Date не следует использовать для анализа строк даты, поскольку они несовместимы или правильно реализованы в разных браузерах.   -  person RobG    schedule 07.06.2016
comment
@RobG: спасибо за разъяснение!   -  person Crescent Fresh    schedule 07.06.2016


Ответы (1)


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

Следующее будет надежно анализировать строку даты и времени в расширенном формате ISO 8601.

/* Parse ISO date string in format yyyy-mm-ddThh:mm:ss.sss+hh:mm or Z
**
** @param (string} s - string to parse in ISO 8601 extended format
**                     yyyy-mm-ddThh:mm:ss.sss+/-hh:mm or z
**                     Ttime zone separator can be omitted,
**                       e.g. +05:30 or +0530
**                     Date and time separator "T" can be replaced by space
**                       e.g. 2016-04-23 12:00:00-0700
**                     Date only form (2016-04-30) is treated as local (per ISO 8601,
**                     but ECMA-262 requires as GMT (Z or 0 offset).
**
** @returns {Date}   - returns a Date object. If any value out of range,
**                     returns an invalid date.
*/
function parseISO(s) {
  // Create base Date object
  var date = new Date();
  // Object to return if invalid date
  var invalidDate = new Date(NaN);
  // Set some defaults
  var sign = -1, tzMins = 0;
  var tzHr, tzMin;
  // Trim leading and trailing whitespace
  s = s.replace(/^\s*|\s*$/g,'').toUpperCase();
  // Get parts of string and split into numbers
  var d  = (s.match(/^\d+(-\d+){0,2}/)             || [''])[0].split(/\D/);
  var t  = (s.match(/[\sT]\d+(:\d+){0,2}(\.\d+)?/) || [''])[0].split(/\D/);
  var tz = (s.match(/Z|[+\-]\d\d:?\d\d$/)          || [''])[0];

  // Resolve timezone to minutes, may be Z, +hh:mm or +hhmm
  // substr is old school but more compatible than slice
  // Don't need to split into parts but makes validation easier
  if (tz) {
    sign  = /^-/.test(tz)? 1 : -1;
    tzHr  = tz == 'Z'? 0 : tz.substr(1,2);
    tzMin = tz == 'Z'? 0 : tz.substr(tz.length - 2, 2)*1;
    tzMins = sign * (tzHr*60 + tzMin);
  }

  // Validation
  function isLeap(year){return year % 4 != 0 || year % 100 == 0 && year % 400 != 0}
  // Check number of date parts and month is valid
  if (d.length > 3 || d[1] < 1 || d[1] > 12) return invalidDate;
  // Test day is valid
  var monthDays = [,31,28,31,30,31,30,31,31,30,31,30,31];
  var monthMax = isLeap(d[0]) && d[1] == 2? 29 : monthDays[d[1]];
  if (d[2] < 1 || d[1] > monthMax) return invalidDate;
  // Test time parts
  if (t.length > 5 || t[1] > 23 || t[2] > 59 || t[3] > 59 || t[4] > 999) return invalidDate;
  // Test tz within bounds
  if (tzHr > 12 || tzMin > 59) return invalidDate;

  // If there's a timezone, use UTC methods, otherwise local
  var method = tz? 'UTC' : '';
  
  // Set date values
  date['set' + method + 'FullYear'](d[0], (d[1]? d[1]-1 : 0), d[2]||1);
  // Set time values - first member is '' from separator \s or T
  date['set' + method + 'Hours'](t[1] || 0, (+t[2]||0) + tzMins, t[3]||0, t[4]||0);

  return date;
}

['2016-04-12T04:31:56.200+05:30','2016-02-01','2016-01-01 17:51:12-0700','2016-01-01T17:51:12Z'].forEach(function (s) {
  var d = parseISO(s);
  document.write('String: ' + s + '<br>' +
                 'Local time: ' + d + '<br>' +
                 'toISOString: ' + d.toISOString() + '<br><br>'
                );
 });

Вышеупомянутое можно сократить до гораздо меньшего количества кода, его легко читать и поддерживать, а большее количество кода не обязательно означает, что оно медленнее, чем более короткая версия (хотя кэширование регулярных выражений может помочь, если производительность является проблемой).

person RobG    schedule 07.06.2016