jQuery DataTables: есть ли способ автоматически определять формат даты? - PullRequest
3 голосов
/ 01 апреля 2019

У меня есть готовая дата-база, и на данный момент я определил формат даты вручную, добавив

$.fn.dataTable.moment("DD.MM.YYYY");

перед тем, как сам определить свою таблицу данных:

var myTable = $('#authors').DataTable({
    "paging": false,
    "ordering": true,
    "order": [2, "desc"],
    "info": false,
    "stateSave": true,
    "responsive": true,
    "columnDefs": [
        { targets: "noSort", orderable: false }
    ]       
});

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

Есть ли способ автоматически определить значение данного столбца даты, чтобы я мог правильно отсортировать этот столбец?Или я всегда буду определять формат даты вручную?

Я хочу иметь динамическую версию строки

$.fn.dataTable.moment("DD.MM.YYYY");

Она должна обнаружить: «О, значение в этом столбце равно« 29 .04.2019 », этонемецкий формат даты, определенный как dd.mm.yyyy ", и используйте этот формат для дальнейшей сортировки.

Или, если вместо этого используется значение «29.04.2009», это следует признать в формате даты США, используя «мм / дд / гггг» для сортировки.

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

Ответы [ 6 ]

5 голосов
/ 08 апреля 2019

Было предложено передать массив для форматов в $.fn.dataTable.moment(...), но это работает тогда и только тогда, когда никогда не случится, что данные соответствуют более чем одному формату в массиве. Если вы не можете гарантировать это, передача множества форматов не является решением.

Вы начали с примера DD.MM.YYYY и MM/DD/YYYY. Дата будет соответствовать одному или другому формату, но не обоим, потому что, если она имеет разделители периода, то она соответствует 1-му формату, но не 2-му, и если она имеет разделители косой черты, она соответствует 2-му формату, но не 1-му. Тем не менее, в целом, если у вас есть даты не из США или Германии, вы столкнетесь с неоднозначными случаями. Мэтт Джонсон упомянул, например, такую ​​дату, как "01/04/2019", которая может соответствовать формату MM/DD/YYYY и интерпретироваться как "4 января 2019", или соответствовать формату DD/MM/YYYY и интерпретироваться как «1 апреля 2019 года».

Если у вас могут быть даты в формате DD/MM/YYYY или MM/DD/YYYY и вы вызываете $.fn.dataTable.moment(["DD/MM/YYYY", "MM/DD/YYYY"]), то иногда вы получите неправильные результаты. Проблема в том, что плагин, реализующий функцию, которую вы ' Вызывающий смотрит на каждую ячейку в изоляции .

Таблица 1

Предположим, что таблица предназначена для использования дат в формате DD/MM/YYYY со следующими ячейками:

  1. 21/2/2019 * +1027 * * 1 028 * 1/4/2019
  2. 24/12/2019

Таблица 2

Предположим, что таблица предназначена для использования дат в формате MM/DD/YYYY со следующими ячейками:

  1. 4/1/2019
  2. 12/24/2019

Две таблицы на самом деле содержат одинаковые даты. Они просто представлены по-разному.

Предположим, вы настроили свою таблицу с $.fn.dataTable.moment(["DD/MM/YYYY", "MM/DD/YYYY"]). Таблица 1 будет интерпретирована правильно. Однако строка 2 в таблице 2 будет интерпретирована неправильно. Дата 4/1/2019 соответствует первому формату в массиве (DD/MM/YYYY), и именно так moment будет ее интерпретировать. Неважно, сколько других ячеек не может уместиться DD/MM/YYYY, потому что плагин, который вызывает moment, не выполняет статистический анализ. Он смотрит на каждую клетку изолированно. Вот соответствующий код (с удалением нескольких пустых строк):

$.fn.dataTable.moment = function ( format, locale, reverseEmpties ) {
    var types = $.fn.dataTable.ext.type;

    // Add type detection
    types.detect.unshift( function ( d ) {
        if ( d ) {
            // Strip HTML tags and newline characters if possible
            if ( d.replace ) {
                d = d.replace(/(<.*?>)|(\r?\n|\r)/g, '');
            }

            // Strip out surrounding white space
            d = $.trim( d );
        }

        // Null and empty values are acceptable
        if ( d === '' || d === null ) {
            return 'moment-'+format;
        }

        return moment( d, format, locale, true ).isValid() ?
            'moment-'+format :
            null;
    } );

    // Add sorting method - use an integer for the sorting
    types.order[ 'moment-'+format+'-pre' ] = function ( d ) {
        if ( d ) {
            // Strip HTML tags and newline characters if possible
            if ( d.replace ) {
                d = d.replace(/(<.*?>)|(\r?\n|\r)/g, '');
            }

            // Strip out surrounding white space
            d = $.trim( d );
        }

        return !moment(d, format, locale, true).isValid() ?
            (reverseEmpties ? -Infinity : Infinity) :
            parseInt( moment( d, format, locale, true ).format( 'x' ), 10 );
    };
};

Вы могли бы перевернуть аргументы и вызвать $.fn.dataTable.moment(["MM/DD/YYYY", "DD/MM/YYYY"]). Теперь со 2-й таблицей все будет в порядке, но та же проблема возникнет в 1-й таблице.


Хорошо, что тогда?

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

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

В противном случае может быть способом сделать во внешнем интерфейсе статистический анализ вашей таблицы до того, как Datatables попытается отрендерить и упорядочить ее. Таким образом, вы можете узнать, какой формат используется, а затем передать его в Datatables. Тем не менее, это все еще кажется мне хрупким. Если в таблице используется серверный протокол, то одновременно доступна только небольшая часть данных. Если вы выполняете анализ только первого ответа от сервера, более поздний ответ, охватывающий более позднюю часть таблицы, может опровергнуть первоначальное предположение. Более того, могут быть случаи, когда все даты в датированных данных неоднозначны. В большом и нефильтрованном наборе данных это может быть маловероятным, но как только пользователям будет разрешено фильтровать набор данных для отображения только подмножества, они могут отфильтровать его таким образом, что все даты в конкретном подмножестве будут неоднозначными. Я бы не стал развертывать приложение в надежде, что этого никогда не произойдет.

2 голосов
/ 03 апреля 2019

Или, если вместо этого установлено значение «29.04.2009», это должно быть распознано как британский формат даты с использованием мм / дд / гггг для сортировки.

Это форматирование США. Британские даты используют форматирование дд / мм / гггг.

Из-за этого то, что вы спрашиваете, невозможно. Рассмотрим "01/04/2019". Это 4 января? Или это 1 апреля? Там есть способ узнать из одной строки. Вы должны сами указать контекст локали.

См. Также: Форматы дат по странам в Википедии.

2 голосов
/ 03 апреля 2019

Вы также можете использовать массив для нескольких форматов, как предлагает момент здесь

$.fn.dataTable.moment(['MM/DD/YYYY', 'MM-DD-YYYY', 'MM.DD.YYYY']);

const srcData = [{"name":"Freda Rasmussen","date":"03-01-2015","date2":"03.01.2015","date3":"03/01/2015"},{"name":"Ramsey Blackwell","date":"08-22-2016","date2":"08.22.2016","date3":"08/22/2016"},{"name":"Cameron Leach","date":"11-01-2015","date2":"11.01.2015","date3":"11/01/2015"},{"name":"Foley Porter","date":"04-26-2014","date2":"04.26.2014","date3":"04/26/2014"},{"name":"Corrine Wiggins","date":"04-18-2018","date2":"04.18.2018","date3":"04/18/2018"}]

$.fn.dataTable.moment(['MM/DD/YYYY', 'MM-DD-YYYY', 'MM.DD.YYYY']);


const dataTable = $('#mytable').DataTable({
  data: srcData,
  columns: [{
      title: 'name',
      data: 'name'
    },
    {
      title: 'date',
      data: 'date'
    },
    {
      title: 'date',
      data: 'date2'
    },
    {
      title: 'date',
      data: 'date3'
    }
  ]

});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script type="application/javascript" src="https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
<script type="application/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.8.4/moment.min.js"></script>
<script type="application/javascript" src="https://cdn.datatables.net/plug-ins/1.10.19/sorting/datetime-moment.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css">

<table id="mytable"></table>
1 голос
/ 10 апреля 2019

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

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

Я рекомендую использовать формат UTC (ГГГГ-ММ-DD) для хранения данных, так как они полностью однозначны и конвертируются из / в пользовательский языковой стандарт при вводе / отображении.Таким образом, отображаемый формат может быть изменен конечным пользователем без какого-либо ущерба для целостности хранимых данных.

1 голос
/ 07 апреля 2019

как я понял вы хотите установить что-то по умолчанию?Местоположение по умолчанию?Для этого сделайте это так: функция определена как $ .fn.dataTable.moment = function (format, locale).Вы добавляете свое местоположение в качестве второго параметра.В вашем случае используется ** moment.js **.По умолчанию Moment.js поставляется с английскими (США) языковыми строками.Если вам нужны другие местоположения, вы можете загрузить их в Moment.js для дальнейшего использования.

Чтобы загрузить языковой стандарт, передайте значения ключа и строки в moment.locale.

Подробнеена каждой части языкового пакета можно найти в настройках раздел .

1 голос
/ 03 апреля 2019

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

$.fn.dataTable.moment(['MM/DD/YYYY', 'DD-MM-YYYY', 'DD-MMM-YYYY']);

После правила , продиктованные моментом.js, на который опирается ваш плагин, должны соблюдаться.

Ниже вы можете найти работающую демонстрацию :

//table source
const srcData = [
	{name: 'Til Schweiger', date: '19-12-1963'},
	{name: 'Jan Joseph Liefers', date: '08/08/1964'},
	{name: 'Moritz Bleibtreu', date: '13-Aug-1971'},
	{name: 'Thomas Jahn', date: '07/08/1965'}
		
];

$.fn.dataTable.moment(['MM/DD/YYYY', 'DD-MM-YYYY', 'DD-MMM-YYYY']);

//DataTable init
const dataTable = $('#mytable').DataTable({
	dom: 't',
	data: srcData,
	columns: [
		{title: 'name', data: 'name'},
		{title: 'date', data: 'date'}
	]
	
});
<!doctype html>
<html>
<head>
  <script type="application/javascript" src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
  <script type="application/javascript" src="https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
  <script type="application/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.8.4/moment.min.js"></script>
  <script type="application/javascript" src="https://cdn.datatables.net/plug-ins/1.10.19/sorting/datetime-moment.js"></script>
  <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css">
</head>
<body>
<table id="mytable"></table>
</body>
</html>

Однако может произойти неожиданное поведение, если у вас есть даты, когда оба дня и месяцы находятся в диапазоне 0-12, а оба «ДД / ММ / ГГГГ» и «ММ / ДД / ГГГГ 'действительны, поэтому я предполагаю, что было бы безопаснее использовать предварительно отформатированные даты (используя общий формат) и соответствующие настройки $.fn.dataTable.moment() для этого формата.

Если кто-то, в отличие отOP, может потребоваться фактический способ определения формата даты, а не скучное обоснование того, почему не следует делать это на стороне клиента, может пригодиться следующий анализатор запеченных форматов:

//sample source data
const test1 = ['01-05-2015', '21-06-1982', '13-08-1982', '05-06-2018'];
const test2 = ['05/01/2015', '06/21/1982', '08/13/1982', '06/05/2018'];
const test3 = ['01/05/2015', '21/06/1982', '13/08/1982', '05/06/2018'];
const test4 = ['1-May-2015', '21-Jun-1982', '13-Aug-1982', '5-Jun-2018'];

const dateFormatRecognition = dateArr => {
	//split each date string into parts, delimited by either of ('.', '-', '/')
	let dateParts = dateArr.map(testdate => testdate.split(/[\/\.\-]/));
	//regroup parts so, first, second and third parts values groupped within corresponding array
	dateParts = dateParts[0].map((entry, colindex) => dateParts.map(col => col[colindex]));
	//check each part values against the set of criteria and figure out possible options
	const partsFormat = dateParts.map(parts => ({
		//check whether each part values could be day, month, year
		daysNum: parts.every(part => /^\d+$/.test(part) && part > 0 && part < 32),
		monthAlpha: parts.every(part => ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'].indexOf(part.toLowerCase()) > -1),
		monthNum: parts.every(part => /^\d+$/.test(part) && part > 0 && part < 13),
		yearsNum: parts.every(part => /^\d+$/.test(part) && part > 31),
		//grap min parts length
		minLen: parts.reduce((min,part) => part.length < min ? part.length : min, parts[0].length),
	}));
	//grab first of possible delimiters ('.', '-', '/') and check if those are the same across all values
	const delimiter = dateArr.every(dateEntry => dateEntry.match(/[\.\-\/]/)[0] == dateArr[0].match(/[\.\-\/]/)[0]) ? dateArr[0].match(/[\.\-\/]/)[0] : null;
	//decision making about parts roles
	return partsFormat.reduce((format, partProps) => {
		format.push(partProps.yearsNum ? 'YYYY' :
		partProps.monthNum && format[0] != 'MM' && partProps.minLen == 2 ? 'MM' :
		partProps.monthNum && format[0] != 'MM' && partProps.minLen == 1 ? 'M' :
		partProps.monthAlpha ? 'MMM' :
		partProps.daysNum && partProps.minLen == 2 ? 'DD' :
		partProps.daysNum && partProps.minLen == 1 ? 'D' : null);
		return format;
	}, []).join(delimiter);
};
//output test array formats
console.log(dateFormatRecognition(test1));
console.log(dateFormatRecognition(test2));
console.log(dateFormatRecognition(test3));
console.log(dateFormatRecognition(test4));
.as-console-wrapper {
  max-height: 100% !important;
  top: 0;
}
...