Dates - Not Just A Delicious Fruit


In this video we are going to take a look at Symfony's DateType form field type, which will act as a foundation for understanding the DateTimeType in the next video.

Being a form field that specialises in dates, you can imagine that there are a variety of options to configure the days, months, and years.

Each of these options have usable defaults. These would be:

  • days 1-31
  • months listing all twelve months in text format
  • years shows 5 years before, and 5 years after the current year

These are customisable by passing in an array as needed, e.g.:

use Symfony\Component\Form\Extension\Core\Type\DateType;

$builder->add('yourDateField', DateType::class, [
    'widget' => 'choice',
    'years'  => [2011,2014,2017,2023]
]);

And often you will see the use of PHP's range function used with these options, e.g.:

use Symfony\Component\Form\Extension\Core\Type\DateType;

$builder->add('yourDateField', DateType::class, [
    'widget' => 'choice',
    'years'  => range(2000,2030)
]);

This being short hand for creating an array containing every number between 2000 and 2030 without having to manually type them all out.

The thing is, there's a few not-so-immediately-obvious caveats to using the year, month, and day options.

Firstly, they only apply if you use the widget type of choice. This is to say that they have no bearing on validation or stopping any other year being submitted as has been demonstrated in other videos in this series. So be careful of that.

The other problem is that if you had a situation where you only wanted to show the days after a certain day - for example, let's say we are on the 15th of the current month. We write some 'clever' bit of PHP like:

$now = (new \DateTime('now'))->format('d');
// 't' gives the last day in the current month
$endOfTheCurrentMonth = (new \DateTime('now'))->format('t');

$builder->add('yourDateField', DateType::class, [
    'widget' => 'choice',
    'days'  => range($now, $endOfTheCurrentMonth),
]);

Then yes, we get an array of days going from 15-31 (assuming a 31 day month), but then all the months in our drop downs can only be given a date starting 15th or greater. D'oh.

So you end up doing some JavaScript / clientside tomfoolery to hack around this problem.

Validating Dates

If you want ensure only certain dates are valid - maybe any date that is greater than or equal to 'today', then you will likely want to add in some validation / assertions to your entities.

Unusually you wouldn't use a date assertion for this, but rather a 'comparison' constraint:

// src/AppBundle/Entity/Order.php
namespace AppBundle\Entity;

use Symfony\Component\Validator\Constraints as Assert;

class Order
{
    /**
     * @Assert\GreaterThanOrEqual("today")
     */
    protected $deliveryDate;
}

Note that this example is taken directly from the Symfony documentation.

Standard And Custom Date Formats

There are three configurable ways of accepting a date when using the DateType, these are:

  • choice
  • text
  • single_text

With choice being the default. You can change which input method you want using the widget option.

use Symfony\Component\Form\Extension\Core\Type\DateType;

$builder->add('yourDateField', DateType::class, [
    'widget' => 'choice',
]);

In my experience, the choice and single_text options are the most common. The immediate question I had was why is there text and single_text? Seems a little redundant.

Well, setting your widget option to text is going to render out three individual text boxes for input - one for day, one for month, and one for year.

The main reason I don't like this - aside from the default formatting - is that it's not immediately obvious to the end user how to use these fields. Do they need to enter the day name, the day number, should it have a leading zero, is it month name or number... two or four digit year? So many questions, which inevitably leads to confusion and as they say in ecommerce-land, form abandonment.

Instead, I would primarily opt for the choice type, which thankfully is the default.

The choice type renders out three preconfigured dropdowns with the months, days, and years preconfigured. There's still some room for error - as in selecting February 31st is entirely possible.

Thankfully, as mentioned above, there are validation rules around this, and you could add in some JavaScript to handle this client side in the first instance also. But that's all extra work on your part.

Then there is single_text, which renders out a single text input field which allows you to be as customisable as you like.

One cool thing about single_text is that you will get a HTML5 date picker thrown in for free.

This may very well be 'good enough' that you do not need a third party library / plugin / extra dependency.

However, this field is also useful if you do want to use your own custom library for your date picker. To do so, you just need to turn off the HTML5 date picker by setting html5 to false:

use Symfony\Component\Form\Extension\Core\Type\DateType;

$builder->add('yourDateField', DateType::class, [
    'widget' => 'single_text',
    'html5'  => false,
]);

You may also need to pass in a custom format for accepting dates. This is less likely with the DateType than it is with the DateTimeType that we will see in the next video, but be aware of this.

Once you pass in a custom format option you can accept any kind of date you like. This is by far the most useful, but also the most tricky to get working.

Imagine for some reason that you needed to set up the date in the format of 'day/Month Year', which is going to confuse the heck out of Symfony unless we help it along:

use Symfony\Component\Form\Extension\Core\Type\DateType;

$builder->add('yourDateField', DateType::class, [
    'widget' => 'single_text',
    'html5'  => false,
    'format' => 'dd/MM yyyy',
]);

Honestly, I found the format option amongst the most confusing, head-scratching part of working with the DateType and especially the DateTimeType. There is some method to the madness, but it's really not helped by PHP.net's really beginner-unfriendly documentation.

Some more examples to help you along:

// easiest:
'format' => 'dd/MM/yyyy HH:mm:ss'

// JavaScript ISO8601 date friendly
'format' => 'yyyy-MM-dd'T'HH:mm:ss.SSSZ',

Given these, figuring out your own should be much easier than going purely off the documentation. At least I hope it will be :)

Code For This Course

Get the code for this course.

Episodes