Formatting, Sorting & Filtering Dates in ag-Grid

In my first blog about value formatting in ag-Grid, I explained how to format numbers, strings and currencies and gave three examples for that. Whilst we can all agree that I did an excellent job, I missed one very important use case: Dates.

Since dates are a part of everyday life, and ironically, their layout is not standardised, they're a perfect candidate for formatting. In this blog I'll be running through not only how to format them, but also how a valueFormatter can be used in conjunction with sorting and filtering dates as well.

I've built a sample to illustrate the approaches I'll be describing in this post. See it in action below:

In this blog, we'll be going over:

For those of you who were wondering why this article took so long: I made a video about all the subjects discussed in this and the previous blog. So, if you like to consume your media audiovisually, check it out!

Watch On YouTube

Formatting Dates

For this example, I decided to generate the dates as a string because it allows me to show you how easy it is to change between the formats for different applications.

// DATA GENERATION
const _randNum = size => Math.random() * size;
const _zeroPad = num => (num < 10 ? '0' + num : num);

const _generateDate = () => {
  var day = Math.round(_randNum(27)) + 1;
  var month = Math.round(_randNum(11)) + 1;
  var year = Math.round(_randNum(70)) + 1950;
  return `${_zeroPad(day)}/${_zeroPad(month)}/${year}`;
};
With a little help from some helper functions

Now, let's look at the column definitions of the Date and Date Formatted columns:

    {
      field: 'date',
    },
    {
      headerName: 'Date Formatted',
      field: 'date',
      valueFormatter: dateFormatter,
      comparator: dateComparator,
      filter: 'agDateColumnFilter',
      floatingFilter: true,
      floatingFilterComponentParams: {
        suppressFilterButton: true,
      },
      filterParams: {
        debounceMs: 500,
        suppressAndOrCondition: true,
        comparator: function(filterLocalDateAtMidnight, cellValue) {
          if (cellValue == null) {
            return 0;
          }
          var dateParts = cellValue.split('/');
          var year = Number(dateParts[2]);
          var month = Number(dateParts[1]) - 1;
          var day = Number(dateParts[0]);
          var cellDate = new Date(year, month, day);

          if (cellDate < filterLocalDateAtMidnight) {
            return -1;
          } else if (cellDate > filterLocalDateAtMidnight) {
            return 1;
          } else {
            return 0;
          }
        },
      },
    },
Date & Date Formatted column definitions

This looks like a lot of code, but don't worry, it's actually quite simple. For now let's focus on everything above the comparator. Just like in the previous blog, we have a reference to an external valueFormatter, seen below:

function dateFormatter(params) {
  var dateAsString = params.data.date;
  var dateParts = dateAsString.split('/');
  return `${dateParts[0]} - ${dateParts[1]} - ${dateParts[2]}`;
}
Date formatter function

What's going on here is params.data.date is being split at every instance of the "/" character, giving us an array with three elements, day as dateParts[0], month as dateParts[1] and year as datePart[2]. These parts are then being re-combined together with a "-"  character as separators to give us our formatted data:

Changing "07/02/1980" to "07 - 02 - 1980"

Sorting Formatted Dates

Next, we'll look into the comparator. For those of you that don't know, a comparator is, I won't go too far into it, suffice to say, it's a tool for sorting things, numerically, alphabetically —in any way you want.

If the date data provided was already a JS date object, we wouldn't need to provide a custom comparator, ag-Grid already has this feature built in, But when given a string like "06/02/2017", you need to tell the grid how to split it and for instance, whether '02' is a month or a day.

// DATE COMPARATOR FOR SORTING
function dateComparator(date1, date2) {
  var date1Number = _monthToNum(date1);
  var date2Number = _monthToNum(date2);

  if (date1Number === null && date2Number === null) {
    return 0;
  }
  if (date1Number === null) {
    return -1;
  }
  if (date2Number === null) {
    return 1;
  }

  return date1Number - date2Number;
}

// HELPER FOR DATE COMPARISON
function _monthToNum(date) {
  if (date === undefined || date === null || date.length !== 10) {
    return null;
  }

  var yearNumber = date.substring(6, 10);
  var monthNumber = date.substring(3, 5);
  var dayNumber = date.substring(0, 2);

  var result = yearNumber * 10000 + monthNumber * 100 + dayNumber;
  // 29/08/2004 => 20040829
  return result;
}
Comparator & helper functions for Date Formatted

The comparator, along with its _monthToNum() helper, change the date string and convert that string to a number that can then be compared to other dates.

And that's how we achieve sorting!

Filtering Formatted Dates

Finally, let's take a look at filtering and the filtering params. We can think of filtering as similar to a sort, because it's also a comparison operation, we're checking the data against an input.

Filtering in action

From the above gif, you'll note a slight delay before filtering occurs, this is down to a filterParams option called debounceMS which allows us to set a slight debounce giving us more time to type what we want to filter.

Furthermore, the AND/OR conditions of the filter have also been suppressed, giving us a clean and simple filtering experience which only returns exact matches.

One last UX addition was the floating filter which just makes so we don't have to go through the hamburger menu to get filtering.

To achieve the above effect, I used the grid's built in agDateColumnFilter. However, in order to make our data work with it, we first need to convert the data into a JavaScript date format. Below, you'll note that this is done by once again splitting the string at every "/"  and creating a cellDate like so:

filterParams: {
        debounceMs: 500,
        suppressAndOrCondition: true,
        comparator: function(filterLocalDateAtMidnight, cellValue) {
          if (cellValue == null) {
            return 0;
          }
          var dateParts = cellValue.split('/');
          var year = Number(dateParts[2]);
          var month = Number(dateParts[1]) - 1;
          var day = Number(dateParts[0]);
          var cellDate = new Date(year, month, day);
          if (cellDate < filterLocalDateAtMidnight) {
            return -1;
          } else if (cellDate > filterLocalDateAtMidnight) {
            return 1;
          } else {
            return 0;
          }
        },
The filterParams for the Date Formatted column

Now that we have JavaScript dates, we can rely on the language to perform greater-than comparisons with the user input filterLocalDateAtMidnight. This input — converted into a JS date object by the filter — is then provided to the our custom comparator along with the cell value which we'll be using for the comparison.

Summary

And there we go, that's formatting with dates. The great and mysterious, actually quite simple when broken down. But don't go thinking this is it, like I mentioned in the article, the format is up to you, you can do this with date objects, strings, even integers. What's important is how the data is manipulated and compared.

To find out more about valueForamtters and to get an in-depth understanding of how they work, check out our example filled documentation here. You can also find our documentation for sorting & filtering on the main ag-Grid site as well.

Lastly if you're new to ag-Grid and want to see what all the hubbub is about, why not try it out — for free — by checking out our getting started guides.