jQuery DataTables:是否有一种自动检测日期格式的方法?

4

我已经建立并运行了一个数据表格,目前我手动定义日期格式,通过添加

$.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”,并使用此格式进行进一步的排序。
或者如果值是“04/29/2019”,则应将其识别为美国日期格式,使用“mm/dd/yyyy”进行排序。
目前我不知道应用程序支持多少种不同的日期格式。我猜可能会有5种或更多。但在单个表中,只使用一种格式。

你在问题中提到的这两种格式是唯一的两种日期格式吗?另外,以下两种情况哪一种适用于您的情况?可能性1:一个表格可以显示不同格式的日期,但是在单个表格中,只使用一种格式。可能性2:一个表格可以显示不同格式的日期,并且单个表格可以同时包含多种格式。所以一行将使用美国格式,下一行将使用德国格式。您应该编辑您的问题以澄清所有这些。 - Louis
@Louis:你说得对,这很重要...在我的情况下,可能性2是正确的选择。一个表格可以显示不同格式的日期,但在单个表格中,只会使用一种格式。目前我不知道应用程序将支持多少种不同的格式。我猜测至少有5种。我已经编辑了上面的问题。 - JonSnow
@JonSnow:为了避免混淆,Louis所提到的(顺便说一下,我在一个星期前的答案中也提到了)你可以使用我在上一个评论中分享的日期格式解析器。该脚本大多数情况下可以根据上下文(两者的所有可能值)区分天数和月份。 - Yevhen Horbunkov
6个回答

6
有人建议将格式数组传递给$.fn.dataTable.moment(...),但只有当数据匹配的格式不超过一个时才有效。除非你可以保证这一点,否则传递格式数组不是解决方案。
DD.MM.YYYYMM/DD/YYYY 为例。一个日期只会匹配其中一个格式,而不会同时匹配两个格式,因为如果它有句点分隔符,则与第一个格式匹配,但不与第二个格式匹配;如果它有斜杠分隔符,则与第二个格式匹配,但不与第一个格式匹配。然而,通常情况下,如果你有来自美国或德国之外的日期,你将遇到模棱两可的情况。Matt Johnson提到了一个日期,如“01/04/2019”,它既符合MM/DD/YYYY的格式,又可以被解释为“2019年1月4日”,也符合DD/MM/YYYY的格式,可以被解释为“2019年4月1日”。
如果您可以使用DD/MM/YYYYMM/DD/YYYY格式的日期,并调用$.fn.dataTable.moment(["DD/MM/YYYY", "MM/DD/YYYY"]),则有时会得到不正确的结果。问题在于实现您调用的函数的插件将每个单元格视为独立的。
表1:假设一个表格要使用DD/MM/YYYY格式的日期,具有以下单元格:
  1. 21/2/2019
  2. 1/4/2019
  3. 24/12/2019
表2:假设一个表格要使用MM/DD/YYYY格式的日期,具有以下单元格:
  1. 2/21/2019
  2. 4/1/2019
  3. 12/24/2019
这两个表格实际上包含相同的日期,只是表示方式不同。
假设您使用$.fn.dataTable.moment(["DD/MM/YYYY", "MM/DD/YYYY"])配置了表格。表格1将被正确解释。然而,表格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"])。现在第二个表格将正常显示,但是第一个表格将发生相同的问题。

好的,那接下来呢?

如果后端已经包含了UTC时间戳,那么我会将这些时间戳发送到前端,而不是发送本地化的值。在渲染包含日期的单元格时,我会让前端将UTC日期转换为对用户有意义的格式。Datatable将根据UTC值进行排序,这些值可以无歧义地进行比较。

如果后端没有将其日期存储为UTC时间戳,我会重新设计它以使其支持,并执行我在上一段中描述的操作。

否则,在Datatables尝试呈现和排序表格之前,可能有一种方法在前端对您的表格进行统计分析。因此,您可以发现使用的格式,然后将其提供给Datatables。但是,这对我来说仍然似乎很脆弱。如果表格使用服务器端协议,则每次只有一小部分数据可用。如果您仅对服务器的第一个响应进行分析,则稍后覆盖表格的后面部分的响应可能会驳斥最初的假设。此外,可能存在所有日期在datatable中都是模糊的情况。在大型未过滤的数据集上,这可能不太可能,但是一旦允许用户过滤数据集以仅显示子集,他们可能以导致特定子集中所有日期都不明确的方式过滤它。我不会部署希望永远不会发生这种情况的应用程序。

非常好的描述...现在我没有像希望的那样有一个解决方案,但是这给我和后端同事们进行讨论提供了一个很好的基础。最终你的解决方案将使前端代码更易读(我相信也更快)。感谢您所投入的时间。 - JonSnow

4
假设您已经使用date sorting plug-in,只要您需要的格式在指定范围内,就不需要担心任何事情:
$.fn.dataTable.moment(['MM/DD/YYYY', 'DD-MM-YYYY', 'DD-MMM-YYYY']);

您需要遵守moment.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范围内的日和月,并且'DD/MM/YYYY'和'MM/DD/YYYY'都是有效的,则可能会出现意外行为。因此,我想使用预格式化的日期(使用常见格式)和相应的$.fn.dataTable.moment()设置更安全。

如果有人需要实际检测日期格式而不是冗长的客户端解释,可以使用以下自制格式解析器:

//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;
}


抱歉,您的回答并没有帮助到我,因为这正是问题所在。我不想显式设置格式,我希望它能自动检测到。 - JonSnow
现在你已经编辑了你的回答,它更有帮助了,非常感谢! - JonSnow

2
如果值为“04/29/2019”,则应将其识别为英国日期格式,使用mm/dd/yyyy进行排序。
这是美国的格式。英国日期使用dd/mm/yyyy格式。
因此,您所要求的是不可能的。考虑“01/04/2019”。那是1月4日吗?还是4月1日?仅从字符串无法知道。您必须自己提供区域设置上下文。
另请参见维基百科上的按国家分类的日期格式

你说得对。关于美国格式,以及关于意外行为的问题。但我知道我的数据表中很少只有一两个日期,更常见的是50个或更多。那么应该可以检测到吧? - JonSnow
虽然这是可能的,但我不建议这样做。就个人而言,在我的任何项目中都不会使用这种方法。 - Will Jones

2
你可以像momentjs建议的那样使用数组来处理多种格式,具体请看这里。"Original Answer"翻译成"最初的回答"。
$.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>


这听起来很有前途!我会尝试一下,稍后再回来分享结果。 - JonSnow
@JonSnow 我想知道,你是否正在使用JSON数据?并且你是否对后端有控制权? - User863
没有我能控制的,我只是按照数据原样获取。 - JonSnow
如果您能获取毫秒级别的时间戳,那么这将非常有用。 - User863

1
作为我理解,您想要设置默认值?默认位置?要做到这一点,需要这样做:该函数定义为$ .fn.dataTable.moment = function (format, locale)。将您的位置添加为第二个参数。在您的情况下,使用** moment.js **。默认情况下,Moment.js带有英语(美国)语言环境字符串。如果您需要其他位置,则可以将它们加载到Moment.js中以供以后使用。
要加载语言环境,请将键和字符串值传递给moment.locale。
有关每个语言包部分的更多详细信息,请参阅定制部分

1

如果样本大小不够大,准确检测给定日期的日期格式可能从简单到不可能。

您需要将使用的语言环境传递给任何格式化或解析函数,以获得任何准确性,但请注意,如果不同用户同时使用多种格式,则可能会发生什么。

我建议使用UTC格式(YYYY-MM-DD)进行数据存储,因为它是完全明确的,并在输入/显示时转换为用户语言环境。这样,最终用户可以更改显示格式,而不会对存储的数据完整性产生任何不利影响。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接