Dealing With Dates and Times


In this video we are going to expand on the knowledge we gained during learning about the DateType Symfony form field type, by including both dates and times with the DateTimeType.

Firstly, as we need to include both date and time it is important to update our entity to include this information:

// src/AppBundle/Entity/Product.php

     /**
      * @ORM\Column(type="datetime")
      */
     protected $yourDateTime;

Also you would add in the associated get'ter and set'ter which I have excluded here for brevity. Without these your form is going to have a bad time.

And as long as you use the matching property name in your form field type, this should all work quite splendidly:

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

$builder->add('yourDateTime', DateTimeType::class);

You can go ahead and customise the day, year, month, html5, and all the similar fields just as we did in the previous video. The DateType and the DateTimeType function almost identically.

However, because of the time aspect, the submitted input can vary enough that it is worth covering the DateTimeType in its own video.

Timezones

One of the most painful aspects of dealing with dates and times is when you have users in multiple timezones.

Imagine for a moment that you have a website where users can schedule their posts - somewhat similar to WordPress. They write a post, they can either post now, or they can schedule the post for some time in the future.

Do you publish this post at the time they specify, or at the time that the servers thinks is that time?

Let's say we have a user who wants to post their current post at midnight. The user lives in London. However, the server is set up in Paris - which is one hour ahead. That's assuming they are observing daylight savings time, and don't have their server set to UTC... already you can see (or already know) that dealing with dates and times is a real pain.

Symfony offers some support around this with the model_timezone and the view_timezone, which in my opinion are useful but also quite confusing.

It would be my advice to keep your server in UTC, and submit your date / time values back with the appropriate time offset - EST being UTC-5, or HKT being UTC+8.

This can be achieved by updating your php.ini file with the appropriate timezone information. But if you have no control over this, you can use Symfony's DateTimeType to set the model_timezone to Etc/UTC and force your data model - or the area in which your data is processed - into believing the submitted value should be treated as though the server's timezone is in UTC time.

The only real downside to this is you have to remember to set this value per form. So if at all possible, properly configure your php.ini file.

You can also configure the front end to work with some degree of locality by specifying the view_timezone to whatever timezone value you know the user is operating in.

This is hard to explain with words so I would highly advise watching the video where this is much more easily understood with a visual example.

If you do not specify a model_timezone then your server's timezone value will be used by default.

Custom DateTime Picker

The likelihood is that you want to use a custom datetime picker, rather than work with the defaults given to you by Symfony. As covered in the previous video, it is worthwhile investigating the default HTML5 datetime picker that you get for free with the single_text widget, so be sure to check that if you haven't already.

However, if you do wish to go with a custom widget then much like in the DateType video we will need to switch to using the single_text type, but likely also render our form out manually so we can more easily dictate where the datetime picker should live on the page.

In the video example this would be:

<?php

// /src/AppBundle/Form/Type/ProductType.php

namespace AppBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ProductType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('datetime', DateTimeType::class, [
                'widget' => 'single_text',
                'format' => 'yyyy HH:mm:ss dd-MM',
                'input'  => 'datetime',
//                'view_timezone' => 'Australia/Adelaide',
//                'model_timezone' => 'Etc/UTC'
            ])
            ->add('save', SubmitType::class)
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => 'AppBundle\Entity\Product'
        ]);
    }
}

And on the entity:

<?php

// /src/AppBundle/Entity/Product.php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity
 * @ORM\Table(name="products")
 */
class Product
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @ORM\Column(type="datetime")
     * @ Assert\GreaterThan("today")
     */
    protected $dateTime;

    /**
     * @return mixed
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @return mixed
     */
    public function getDateTime()
    {
        return $this->dateTime;
    }

    /**
     * @param mixed $dateTime
     * @return Product
     */
    public function setDateTime($dateTime)
    {
        $this->dateTime = $dateTime;

        return $this;
    }
}

In the form example template we would have:

<!-- /app/Resources/views/form-example/index.html.twig -->

{% extends '::base.html.twig' %}

{% block body %}

    <h2>Symfony 3 Form Example</h2>

    <hr />

    {{ form_start(myForm) }}

    {{ form_errors(myForm) }}

    <div class="form-group {% if not myForm.datetime.vars.valid %}has-error{% endif %}">
        {{ form_label(myForm.datetime) }}
        <div class="col-sm-10">
            {{ form_widget(myForm.datetime) }}
            {{ form_errors(myForm.datetime) }}
        </div>
    </div>

    {{ form_end(myForm) }}

{% endblock %}

{% block javascripts %}

    {{ parent() }}

    <script type="text/javascript">
        $(function() {
           $('#product_datetime').datetimepicker({
               inline: true,
               sideBySide: true,
               showTodayButton: true,
               toolbarPlacement: 'bottom',
               format: 'YYYY HH:mm:ss DD-MM'
           });
        });
    </script>

{% endblock %}

And that base template would be as follows:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>{% block title %}Code Review Videos{% endblock %}</title>
        <!-- Latest compiled and minified CSS -->
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.37/css/bootstrap-datetimepicker.min.css">
        <link rel="stylesheet" href="{{ asset('css/mystyle.css') }}">
        {% block stylesheets %}{% endblock %}
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.13.0/moment.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.37/js/bootstrap-datetimepicker.min.js"></script>
        <link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}" />
    </head>
    <body>

        <nav class="navbar navbar-default navbar-fixed-top">
            <div class="container">
                <div class="navbar-header">
                    <a class="navbar-brand" href="#">Symfony 3 Tutorials</a>
                </div>
            </div>
        </nav>

        <div class="container">

            <div class="row">
                <div class="col-sm-12">
                    {% include ('::flash-messages.html.twig') %}
                </div>
            </div>

            <div class="row">
                <div class="col-sm-12">
                    {% block body %}
                    {% endblock %}
                </div>
            </div>

        </div><!-- /.container -->

        {% block javascripts %}
            <script src="/js/datetime.js"></script>
        {% endblock %}
    </body>
</html>

Much like in the previous video we are using just the first result back from Google for 'Bootstrap Datetime picker' - and your opinion may vary, which is equally valid. Use the one that makes sense for you.

Code For This Course

Get the code for this course.

Episodes