jQuery tablesorter custom sort based on time datetime

For data heavy web projects with data displayed in tables, I frequently use the jQuery tablesorter plugin to allow users to easily sort data without writing a lot of custom code or relying on server-side processing. The plugin autodetects many data types, including dates, but I wasn’t entirely happy with the built-in date processing on a project I recently worked on. In the end I wrote a custom parser to work with the <time> HTML element, and thought I would write it up for future reference.

Dates are always fiddly due to the differences between locales (it still boggles my mind that the USA default is mm/dd/yyyy!). The jQuery tablesorter plugin is quite clever in its date detection, but it doesn’t seem to make any use of information in <time> tags. These were introduced to the HTML spec to help simplify the discrepancy between the ways computers process dates vs how humans prefer to read them, which to my mind makes them perfect candidates for automatic detection and sorting regardless of locale.

In my application, I am displaying dates using HTML similar to <time datetime="yyyy-mm-dd">Human readable version</time>. When viewed in a browser, they show the localised version of the date. The datetime attribute is intended to be processed by the code. Complicating matters in my particular application, dates aren’t always available for some data types, in which case they are replaced by a string explaining why the date is missing. Alternatively, sometimes the date is present and accompanied by an explanatory string, but when this occurs I want to ignore the string and sort using the date only.

Here’s the custom parser I wrote:

/**
 * Sort date columns based on <time datetime='x'>...</time> element in cell
 *
 * This allows dates to be displayed in different formats while still sorting
 * consistently and not needing to change sorter to match display format.
 *
 * https://mottie.github.io/tablesorter/docs/example-parsers.html
 */
$.tablesorter.addParser( {
    id: 'time-datetime',
    is: function( s, table, cell, $cell ) {
        // we can't auto-detect because tablesorter only checks the first row,
        // and not every cell in a date-related column is guaranteed to have a
        // valid date in it because dates are allowed to be NULL in some cases.
        return false;
    },
    format: function( s, table, cell, cellIndex ) {
        if( typeof s !== 'undefined' && typeof cell !== 'undefined' ) {
            // hack to set default sort order:
            // dates will always start with the year '20xx'.
            // cells without date should be sorted by text value,
            // but apparently tablesorter sorts alphabetic characters before numeric ones.
            // if we set the sort string to begin with '9' then it will sort after dates,
            // and by appending its contents we can still sort by the original text.
            let output = '9' + s;
            let cell_contents = $.parseHTML( cell.innerHTML );
            $.each( cell_contents, function( i, element ) {
                if( element.nodeName.toUpperCase() === 'TIME' ) {
                    output = element.getAttribute( 'datetime' );
                }
            } );
            return output;
        }
        else {
            return '9z';
        }
    },
    // parsed: false, // default value
    type: 'text'
} );

To apply it to a column, simply add the attribute data-sorter="time-datetime" to the table header. If you know for certain that every cell will contain a valid <time> element, you can simplify the format function, and could also put in proper autodetection for the is function. Based on my tests, it appears that jQuery tablesorter just checks the first data row in the table in order to guess the data type of each column. Therefore auto-detection only reliably works for columns where you can guarantee that there will always be valid data in the expected format.

Add new comment