Displaying Date-Ranges

Recently a new issue popped up on the joind.in issue-tracker which called for nicer date-ranges. As I did something like that a few weeks prior I grabbed the issue and starded working.

The output is a small library that enables you to display a date-range like 12. – 13. March 2015 instead of 12. March 2015 – 13. March 2015.

Simple thing.

It also displays March 12th – March 13th 2015 if you want to.

Not that easy any more.

I won’t bother you with the details on how it’s done (it seemed more difficult than it actually was) as you can see that in the source-code if you are interested.

How does it work?

The main thing is to create an instance of the DateRangeFormatter and feed it with the format you would like to have printed when both dates are the same. You can also set the separator that shall be printed between the differing date-parts. By default it’s ” – “.

use Org_HeiglDateRangeDateRangeFormatter;

$formatter = new DateRangeFormatter();
$formatter->setFormat('dS of M Y');
$formatter->setSeparator(' until ');

Now the formatter is set up for further action.

echo $formatter->format(
    new DateTime('12.3.2015'), 
    new DateTime('13.3.2015')

This will output 12th until 13th of March 2015.

Nice, eh?

But what happens, when both dates are the same? What will echo $formatter->format(new \DateTime('12.3.2015'), new \DateTime('12.3.2015')); output? As you’d expect it will be just 12th of March 2015.

What now?

You can now add this to your favourite templating engine. I’ve implemented it in twig for joind.in in no time. The code is as follows:

// $env instanceof Twig_Environment
    new Twig_SimpleFunction('dateRange', function ($start, $end, $format = 'd.m.Y', $separator = ' - ') use ($app) {
        $formatter = new Org_HeiglDateRangeDateRangeFormatter();
        if (! $start instanceof DateTimeInterface) {
            $start = new DateTime($start);
        if (! $end instanceof DateTimeInterface) {
            $end = new DateTime($end);
        return $formatter->getDateRange($start, $end);

That way you can call {% dateRange(new DateTime('12.3.2015'), new DateRange('13.3.2015')) %} to get 12. – 13.03.2015 as output. Adaptions (inject a pre-prepared instance f.i.) or Implementation in other template-engines should be easy enough.

How to get it?

You can add the lib to your project using

$> composer require org_heigl/daterange
$> composer update org_heigl/daterange   

and then get going.


Well a short time after release a request came up to be able to remove the dot after the first day in 12. – 13.03.2015 so that it reads 12 – 13.03.2015.

It took me a while to get a solution but it seemed rather easy. You just add a filter.

But where?

There are four different parts that can be filtered.

  • The format that will be applied to a single-day range (DateRangeFormatter::FILTER_COMPLETE),
  • the part of the format that will be applied to the start-date part of the differing part of the date-range (DateRangeFormatter::FILTER_FIRST_DIFF)
  • The part of the format that will be applied to the end-date part of the differing part of the date-range (DateRangeFormatter::FILTER_SECOND_DIFF)
  • The part of the format that will be applied to the part that is equal for both start- and end-date (DateRangeFormatter::FILTER_SAME)

There are two filters ready for use.

  • RemoveEverythingAfterLastDateStringFilter – which removes – as the name already suggests – everything after the last date-character in the part it is set for. So for dS \o\f it would remove the \o\f-part.
  • TrimFilter – which applies the trim-function to the part it is set for.

Just add it using the DateRangeFormatter::addFilter()-method like this:

    new Org_HeiglDateRangeFilterRemoveEverythingAfterLastDateStringFilter(),

Now the RemoveEverythingAfterLastDateStringFilter will be applied to the start-date part of the differing part of the date-range. So instead of 12. – 13.03.2015 it will now read 12 – 13.03.2015.

CAVEAT: If you would apply it to the DateRangeFormatter::FILTER_SECOND_DIFF it would read 12. – 1303.2015. Seems not what you wanted.

Where to go from here?