How to set format in datetime-local field in CakePHP 4?

Hey guys!

I’ve started converting my CakePHP2 application into a CakePHP4 application. Back in time, this was how I formatted my dates in my input fields:

<?php

echo $this->Form->create('CoasterCms.Article');

echo $this->Form->inputs(array(
    'legend' => false,
    'published' => array(
        'label' => 'Datum',
        'dateFormat' => 'DMY',
        'timeFormat' => 24,
        'separator' => false,
        'minYear' => date('Y') - 10,
        'maxYear' => date('Y')
    )
));

echo $this->Form->end();

?>

This is my current CakePHP 4 form:

<?= $this->Form->create() ?>
<?= $this->Form->controls(
    [
        'published' => [
            'class' => 'Form-inputDatetimeLocal',
            'label' => [
                'class' => 'Form-label',
                'text' => __('Date publication')
            ],
            'type' => 'datetime-local',
            'dateFormat' => 'd-m-Y\TH:i'
        ],
        'name' => [
            'class' => 'Form-inputText',
            'label' => [
                'class' => 'Form-label',
                'text' => __('Title')
            ],
            'type' => 'text'
        ],
        'content' => [
            'class' => 'Form-textarea Form-textarea--redactor',
            'label' => [
                'class' => 'Form-label',
                'text' => __('Content')
            ],
            'rows' => 10,
            'type' => 'textarea'
        ],
        'meta_description' => [
            'class' => 'Form-inputText',
            'label' => [
                'class' => 'Form-label',
                'text' => __('Meta description')
            ],
            'type' => 'text'
        ],
        'show' => [
            'label' => [
                'class' => 'Form-label',
                'text' => __('Show on website')
            ],
            'options' => [
                'Y' => __('Yes'),
                'N' => __('No')
            ],
            'type' => 'radio'
        ]
    ],
    [
        'fieldset' => [
            'class' => 'Form-fieldset'
        ],
        'legend' => false
    ]
)
?>
<?= $this->Form->submit(
    __('Save'),
    [
        'class' => 'Button Button--default'
    ]
)
?>
<?= $this->Form->end() ?>

It seems that I can’t use the parameter ‘dateFormat’ anymore. I’ve printed the whole cookbook here and I can’t find a clue in the viewhelper section :s

Can somebody explain me how to format dates in input fields please?
I’ve added a screenshot to make things more clear :wink:

Thanks!

Its possible what you’re looking for is no longer handled by a viewhelper.

Check this thread, which is sort of on topic: -

as it may have the answer for you. Note that’s for display, not sure if input draws from the same settings.

(If you are checking config/app.php also check config/app_local.php as that has precedence.)

Thanks for your reply Jawfin! So there’s a chance that every used date in the application is set in a config file? I ‘yes’, I must admit that I have no idea where it should be set. Here’s my app.php with defaultLocale set:

<?php

use Cake\Cache\Engine\FileEngine;
use Cake\Database\Connection;
use Cake\Database\Driver\Mysql;
use Cake\Error\ExceptionRenderer;
use Cake\Log\Engine\FileLog;
use Cake\Mailer\Transport\MailTransport;

return [
    /*
     * Debug Level:
     *
     * Production Mode:
     * false: No error messages, errors, or warnings shown.
     *
     * Development Mode:
     * true: Errors and warnings shown.
     */
    'debug' => filter_var(env('DEBUG', false), FILTER_VALIDATE_BOOLEAN),

    /*
     * Configure basic information about the application.
     *
     * - namespace - The namespace to find app classes under.
     * - defaultLocale - The default locale for translation, formatting currencies and numbers, date and time.
     * - encoding - The encoding used for HTML + database connections.
     * - base - The base directory the app resides in. If false this
     *   will be auto detected.
     * - dir - Name of app directory.
     * - webroot - The webroot directory.
     * - wwwRoot - The file path to webroot.
     * - baseUrl - To configure CakePHP to *not* use mod_rewrite and to
     *   use CakePHP pretty URLs, remove these .htaccess
     *   files:
     *      /.htaccess
     *      /webroot/.htaccess
     *   And uncomment the baseUrl key below.
     * - fullBaseUrl - A base URL to use for absolute links. When set to false (default)
     *   CakePHP generates required value based on `HTTP_HOST` environment variable.
     *   However, you can define it manually to optimize performance or if you
     *   are concerned about people manipulating the `Host` header.
     * - imageBaseUrl - Web path to the public images directory under webroot.
     * - cssBaseUrl - Web path to the public css directory under webroot.
     * - jsBaseUrl - Web path to the public js directory under webroot.
     * - paths - Configure paths for non class based resources. Supports the
     *   `plugins`, `templates`, `locales` subkeys, which allow the definition of
     *   paths for plugins, view templates and locale files respectively.
     */
    'App' => [
        'namespace' => 'App',
        'encoding' => env('APP_ENCODING', 'UTF-8'),
        'defaultLocale' => env('APP_DEFAULT_LOCALE', 'nl'),
        'defaultTimezone' => env('APP_DEFAULT_TIMEZONE', 'UTC'),
        'base' => false,
        'dir' => 'src',
        'webroot' => 'webroot',
        'wwwRoot' => WWW_ROOT,
        //'baseUrl' => env('SCRIPT_NAME'),
        'fullBaseUrl' => false,
        'imageBaseUrl' => 'img/',
        'cssBaseUrl' => 'css/',
        'jsBaseUrl' => 'js/',
        'paths' => [
            'plugins' => [ROOT . DS . 'plugins' . DS],
            'templates' => [ROOT . DS . 'templates' . DS],
            'locales' => [RESOURCES . 'locales' . DS],
        ],
    ],

    /*
     * Security and encryption configuration
     *
     * - salt - A random string used in security hashing methods.
     *   The salt value is also used as the encryption key.
     *   You should treat it as extremely sensitive data.
     */
    'Security' => [
        'salt' => env('SECURITY_SALT'),
    ],

    /*
     * Apply timestamps with the last modified time to static assets (js, css, images).
     * Will append a querystring parameter containing the time the file was modified.
     * This is useful for busting browser caches.
     *
     * Set to true to apply timestamps when debug is true. Set to 'force' to always
     * enable timestamping regardless of debug value.
     */
    'Asset' => [
        //'timestamp' => true,
        // 'cacheTime' => '+1 year'
    ],

    /*
     * Configure the cache adapters.
     */
    'Cache' => [
        'default' => [
            'className' => FileEngine::class,
            'path' => CACHE,
            'url' => env('CACHE_DEFAULT_URL', null),
        ],

        /*
         * Configure the cache used for general framework caching.
         * Translation cache files are stored with this configuration.
         * Duration will be set to '+2 minutes' in bootstrap.php when debug = true
         * If you set 'className' => 'Null' core cache will be disabled.
         */
        '_cake_core_' => [
            'className' => FileEngine::class,
            'prefix' => 'myapp_cake_core_',
            'path' => CACHE . 'persistent' . DS,
            'serialize' => true,
            'duration' => '+1 years',
            'url' => env('CACHE_CAKECORE_URL', null),
        ],

        /*
         * Configure the cache for model and datasource caches. This cache
         * configuration is used to store schema descriptions, and table listings
         * in connections.
         * Duration will be set to '+2 minutes' in bootstrap.php when debug = true
         */
        '_cake_model_' => [
            'className' => FileEngine::class,
            'prefix' => 'myapp_cake_model_',
            'path' => CACHE . 'models' . DS,
            'serialize' => true,
            'duration' => '+1 years',
            'url' => env('CACHE_CAKEMODEL_URL', null),
        ],

        /*
         * Configure the cache for routes. The cached routes collection is built the
         * first time the routes are processed through `config/routes.php`.
         * Duration will be set to '+2 seconds' in bootstrap.php when debug = true
         */
        '_cake_routes_' => [
            'className' => FileEngine::class,
            'prefix' => 'myapp_cake_routes_',
            'path' => CACHE,
            'serialize' => true,
            'duration' => '+1 years',
            'url' => env('CACHE_CAKEROUTES_URL', null),
        ],
    ],

    /*
     * Configure the Error and Exception handlers used by your application.
     *
     * By default errors are displayed using Debugger, when debug is true and logged
     * by Cake\Log\Log when debug is false.
     *
     * In CLI environments exceptions will be printed to stderr with a backtrace.
     * In web environments an HTML page will be displayed for the exception.
     * With debug true, framework errors like Missing Controller will be displayed.
     * When debug is false, framework errors will be coerced into generic HTTP errors.
     *
     * Options:
     *
     * - `errorLevel` - int - The level of errors you are interested in capturing.
     * - `trace` - boolean - Whether or not backtraces should be included in
     *   logged errors/exceptions.
     * - `log` - boolean - Whether or not you want exceptions logged.
     * - `exceptionRenderer` - string - The class responsible for rendering
     *   uncaught exceptions. If you choose a custom class you should place
     *   the file for that class in src/Error. This class needs to implement a
     *   render method.
     * - `skipLog` - array - List of exceptions to skip for logging. Exceptions that
     *   extend one of the listed exceptions will also be skipped for logging.
     *   E.g.:
     *   `'skipLog' => ['Cake\Http\Exception\NotFoundException', 'Cake\Http\Exception\UnauthorizedException']`
     * - `extraFatalErrorMemory` - int - The number of megabytes to increase
     *   the memory limit by when a fatal error is encountered. This allows
     *   breathing room to complete logging or error handling.
     * - `ignoredDeprecationPaths` - array - A list of glob compatible file paths that deprecations
     *   should be ignored in. Use this to ignore deprecations for plugins or parts of
     *   your application that still emit deprecations.
     */
    'Error' => [
        'errorLevel' => E_ALL,
        'exceptionRenderer' => ExceptionRenderer::class,
        'skipLog' => [],
        'log' => true,
        'trace' => true,
        'ignoredDeprecationPaths' => [],
    ],

    /*
     * Debugger configuration
     *
     * Define development error values for Cake\Error\Debugger
     *
     * - `editor` Set the editor URL format you want to use.
     *   By default atom, emacs, macvim, phpstorm, sublime, textmate, and vscode are
     *   available. You can add additional editor link formats using
     *   `Debugger::addEditor()` during your application bootstrap.
     * - `outputMask` A mapping of `key` to `replacement` values that
     *   `Debugger` should replace in dumped data and logs generated by `Debugger`.
     */
    'Debugger' => [
        'editor' => 'phpstorm',
    ],

    /*
     * Email configuration.
     *
     * By defining transports separately from delivery profiles you can easily
     * re-use transport configuration across multiple profiles.
     *
     * You can specify multiple configurations for production, development and
     * testing.
     *
     * Each transport needs a `className`. Valid options are as follows:
     *
     *  Mail   - Send using PHP mail function
     *  Smtp   - Send using SMTP
     *  Debug  - Do not send the email, just return the result
     *
     * You can add custom transports (or override existing transports) by adding the
     * appropriate file to src/Mailer/Transport. Transports should be named
     * 'YourTransport.php', where 'Your' is the name of the transport.
     */
    'EmailTransport' => [
        'default' => [
            'className' => MailTransport::class,
            /*
             * The keys host, port, timeout, username, password, client and tls
             * are used in SMTP transports
             */
            'host' => 'localhost',
            'port' => 25,
            'timeout' => 30,
            /*
             * It is recommended to set these options through your environment or app_local.php
             */
            //'username' => null,
            //'password' => null,
            'client' => null,
            'tls' => false,
            'url' => env('EMAIL_TRANSPORT_DEFAULT_URL', null),
        ],
    ],

    /*
     * Email delivery profiles
     *
     * Delivery profiles allow you to predefine various properties about email
     * messages from your application and give the settings a name. This saves
     * duplication across your application and makes maintenance and development
     * easier. Each profile accepts a number of keys. See `Cake\Mailer\Email`
     * for more information.
     */
    'Email' => [
        'default' => [
            'transport' => 'default',
            'from' => 'you@localhost',
            /*
             * Will by default be set to config value of App.encoding, if that exists otherwise to UTF-8.
             */
            //'charset' => 'utf-8',
            //'headerCharset' => 'utf-8',
        ],
    ],

    /*
     * Connection information used by the ORM to connect
     * to your application's datastores.
     *
     * ### Notes
     * - Drivers include Mysql Postgres Sqlite Sqlserver
     *   See vendor\cakephp\cakephp\src\Database\Driver for complete list
     * - Do not use periods in database name - it may lead to error.
     *   See https://github.com/cakephp/cakephp/issues/6471 for details.
     * - 'encoding' is recommended to be set to full UTF-8 4-Byte support.
     *   E.g set it to 'utf8mb4' in MariaDB and MySQL and 'utf8' for any
     *   other RDBMS.
     */
    'Datasources' => [
        /*
         * These configurations should contain permanent settings used
         * by all environments.
         *
         * The values in app_local.php will override any values set here
         * and should be used for local and per-environment configurations.
         *
         * Environment variable based configurations can be loaded here or
         * in app_local.php depending on the applications needs.
         */
        'default' => [
            'className' => Connection::class,
            'driver' => Mysql::class,
            'persistent' => false,
            'timezone' => 'UTC',

            /*
             * For MariaDB/MySQL the internal default changed from utf8 to utf8mb4, aka full utf-8 support, in CakePHP 3.6
             */
            //'encoding' => 'utf8mb4',

            /*
             * If your MySQL server is configured with `skip-character-set-client-handshake`
             * then you MUST use the `flags` config to set your charset encoding.
             * For e.g. `'flags' => [\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4']`
             */
            'flags' => [],
            'cacheMetadata' => true,
            'log' => false,

            /*
             * Set identifier quoting to true if you are using reserved words or
             * special characters in your table or column names. Enabling this
             * setting will result in queries built using the Query Builder having
             * identifiers quoted when creating SQL. It should be noted that this
             * decreases performance because each query needs to be traversed and
             * manipulated before being executed.
             */
            'quoteIdentifiers' => false,

            /*
             * During development, if using MySQL < 5.6, uncommenting the
             * following line could boost the speed at which schema metadata is
             * fetched from the database. It can also be set directly with the
             * mysql configuration directive 'innodb_stats_on_metadata = 0'
             * which is the recommended value in production environments
             */
            //'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'],
        ],

        /*
         * The test connection is used during the test suite.
         */
        'test' => [
            'className' => Connection::class,
            'driver' => Mysql::class,
            'persistent' => false,
            'timezone' => 'UTC',
            //'encoding' => 'utf8mb4',
            'flags' => [],
            'cacheMetadata' => true,
            'quoteIdentifiers' => false,
            'log' => false,
            //'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'],
        ],
    ],

    /*
     * Configures logging options
     */
    'Log' => [
        'debug' => [
            'className' => FileLog::class,
            'path' => LOGS,
            'file' => 'debug',
            'url' => env('LOG_DEBUG_URL', null),
            'scopes' => false,
            'levels' => ['notice', 'info', 'debug'],
        ],
        'error' => [
            'className' => FileLog::class,
            'path' => LOGS,
            'file' => 'error',
            'url' => env('LOG_ERROR_URL', null),
            'scopes' => false,
            'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'],
        ],
        // To enable this dedicated query log, you need set your datasource's log flag to true
        'queries' => [
            'className' => FileLog::class,
            'path' => LOGS,
            'file' => 'queries',
            'url' => env('LOG_QUERIES_URL', null),
            'scopes' => ['queriesLog'],
        ],
    ],

    /*
     * Session configuration.
     *
     * Contains an array of settings to use for session configuration. The
     * `defaults` key is used to define a default preset to use for sessions, any
     * settings declared here will override the settings of the default config.
     *
     * ## Options
     *
     * - `cookie` - The name of the cookie to use. Defaults to value set for `session.name` php.ini config.
     *    Avoid using `.` in cookie names, as PHP will drop sessions from cookies with `.` in the name.
     * - `cookiePath` - The url path for which session cookie is set. Maps to the
     *   `session.cookie_path` php.ini config. Defaults to base path of app.
     * - `timeout` - The time in minutes the session should be valid for.
     *    Pass 0 to disable checking timeout.
     *    Please note that php.ini's session.gc_maxlifetime must be equal to or greater
     *    than the largest Session['timeout'] in all served websites for it to have the
     *    desired effect.
     * - `defaults` - The default configuration set to use as a basis for your session.
     *    There are four built-in options: php, cake, cache, database.
     * - `handler` - Can be used to enable a custom session handler. Expects an
     *    array with at least the `engine` key, being the name of the Session engine
     *    class to use for managing the session. CakePHP bundles the `CacheSession`
     *    and `DatabaseSession` engines.
     * - `ini` - An associative array of additional ini values to set.
     *
     * The built-in `defaults` options are:
     *
     * - 'php' - Uses settings defined in your php.ini.
     * - 'cake' - Saves session files in CakePHP's /tmp directory.
     * - 'database' - Uses CakePHP's database sessions.
     * - 'cache' - Use the Cache class to save sessions.
     *
     * To define a custom session handler, save it at src/Network/Session/<name>.php.
     * Make sure the class implements PHP's `SessionHandlerInterface` and set
     * Session.handler to <name>
     *
     * To use database sessions, load the SQL file located at config/schema/sessions.sql
     */
    'Session' => [
        'defaults' => 'php',
    ],
];

Or 1: should there something be set in config/bootstrap.php?
Or 2: should I convert every datetime to another format in the model?

First I must disclaim that I have no knowledge of CakePHP history, only what I have read about in posts like yours.

Looking more closely at what I was doing (in an effort to extract a sample) I noticed it wasn’t working for me either!! So I had a play. I know this is wrong, so waiting to be corrected, but this is what I needed to do to make it work for me. I didn’t need to touch the bootstrap, but in src/Controller/AppController.php in the beforeFilter() function I added: -

        Time::setDefaultLocale('en-AU'); // For any mutable DateTime
        FrozenTime::setDefaultLocale('en-AU'); // For any immutable DateTime

which requires the uses clauses: -

use Cake\I18n\Time;
use Cake\I18n\FrozenTime;

(I also do I18n::setLocale('en'); which requires the uses: use Cake\I18n\I18n;)

Note this is a specific internal bespoke app for an Australian client, thus I can hard-code [for now] my locale. If I don’t like the locale date formatting itself I can replace setDefaultLocale() with setToStringFormat() like setToStringFormat('DD/MM/YY');

Which all means, those lovely settings in the config/app.php are now ignored, which is why I think my answer is wrong :stuck_out_tongue:

I may also add, I have date fields in an AJAX call which are not handled by this - after-all its in my beforeFilter(), and I accept that if I am pulling in data after Cake has done its job, then its my responsibility to format those dates as needed.

Thanks for the effort again Jawfin! I think it’s good that you know what you are doing with your approach, so I don’t think it’s a ‘wrong’ answer :wink:

This morning, I tried something else that works for me: changing the type for this field to ‘datetime’ (instead of ‘datetime-local’). Here’s my new code, I hope it can inspire others:

<?php
/**
 * @var \App\View\AppView $this
 * @var \Cake\Datasource\EntityInterface $article
 */
?>
<h1><?= __('Edit article'); ?></h1>
<?= $this->Form->create(
    $article,
    [
        'class' => 'Form',
        'novalidate' => true,
        'templates' => 'cms-form-templates',
        'type' => 'file'
    ]
)
?>
<?= $this->Form->controls(
    [
        'published' => [
            'class' => 'Form-inputDatetime',
            'label' => [
                'class' => 'Form-label',
                'text' => __('Date publication')
            ],
            //'type' => 'datetime-local'
            'type' => 'datetime'
        ],
        'name' => [
            'class' => 'Form-inputText',
            'label' => [
                'class' => 'Form-label',
                'text' => __('Title')
            ],
            'type' => 'text'
        ],
        'content' => [
            'class' => 'Form-textarea Form-textarea--redactor',
            'label' => [
                'class' => 'Form-label',
                'text' => __('Content')
            ],
            'rows' => 10,
            'type' => 'textarea'
        ],
        'meta_description' => [
            'class' => 'Form-inputText',
            'label' => [
                'class' => 'Form-label',
                'text' => __('Meta description')
            ],
            'type' => 'text'
        ],
        'show' => [
            'label' => [
                'class' => 'Form-label',
                'text' => __('Show on website')
            ],
            'options' => [
                'Y' => __('Yes'),
                'N' => __('No')
            ],
            'type' => 'radio'
        ]
    ],
    [
        'fieldset' => [
            'class' => 'Form-fieldset'
        ],
        'legend' => false
    ]
)
?>
<?= $this->Form->button(
    '<span class="Button-text">' . __('Save') . '</span><i class="Button-icon Button-icon--right fas fa-download"></i>',
    [
        'class' => 'Button Button--default',
        'escapeTitle' => false,
        'typ' => 'submit'
    ]
)
?>
<?= $this->Form->end() ?>

If you’re looking for consistent date formatting throughout your app, you can configure this in your bootstrap.php file. Here’s what mine looks like. I have my dates formatted MM/dd/yyyy:

// format chronos. We want 4-digit years to display throughout the app
\Cake\I18n\Time::setToStringFormat('HH:mm');
\Cake\I18n\Date::setToStringFormat('MM/dd/yyyy'); 
\Cake\I18n\FrozenTime::setToStringFormat('MM/dd/yyyy HH:mm:ss');
\Cake\I18n\FrozenDate::setToStringFormat('MM/dd/yyyy');

If I need to set a date value in a model or controller, I just use date('Y-m-d') in my code (assuming a datetime data type in your database).

1 Like