stephanecollot/datetimepicker-bundle
Symfony bundle that integrates the Bootstrap DateTimePicker widget into Symfony forms, providing a configurable date/time picker form type and the required front-end assets so you can add polished date and time selection with minimal setup.
Install the bundle and register it. It's a Symfony‑2/3‑era bundle, so the registration mechanics depend on your Symfony version.
composer require stephanecollot/datetimepicker-bundle
Symfony ≤3 (app/AppKernel.php):
new Collot\DateTimePickerBundle\CollotDateTimePickerBundle(),
Symfony 4+ (config/bundles.php):
Collot\DateTimePickerBundle\CollotDateTimePickerBundle::class => ['all' => true],
This single step is the first thing to check when anything looks wrong — the bundle does nothing until it is enabled and its form theme is registered. Add the theme globally (recommended) in config/packages/twig.yaml:
twig:
form_themes:
- '@CollotDateTimePickerBundle/Form/fields.html.twig'
# older path style: 'CollotDateTimePickerBundle:Form:fields.html.twig'
Then publish the bundle assets (Symfony 4+) and pull in the front‑end dependencies yourself — the bundle renders the input and the inline init script, but you are responsible for jQuery, Moment.js and the Bootstrap 3 datetimepicker CSS/JS:
php bin/console assets:install --symlink public
First use case: swap a plain DateTimeType on an admin form for a calendar popup. Nothing else in your entity or controller changes.
use Collot\DateTimePickerBundle\Form\Type\DateTimePickerType;
$builder->add('publishedAt', DateTimePickerType::class, [
'pickerOptions' => [
'format' => 'DD/MM/YYYY HH:mm',
'pickTime' => true,
'showTodayButton' => true,
'sideBySide' => true,
],
]);
Render the form as usual — the theme supplies the widget block that wires the JS.
Set a sane application default once. Because pickerOptions is a plain array option, every field you add will otherwise repeat the same five lines. Either create a thin project type that extends DateTimePickerType and injects defaults in configureOptions(), or use a FormTypeExtension so all pickers inherit your house style:
class DateTimePickerDefaultsExtension extends AbstractTypeExtension
{
public static function getExtendedTypes(): iterable
{
return [DateTimePickerType::class];
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefault('pickerOptions', [
'format' => 'DD/MM/YYYY HH:mm',
'locale' => 'fr',
'useCurrent' => false,
'ignoreReadonly' => true,
'allowInputToggle' => true,
]);
}
}
Note that setDefault only applies when the caller omits the option entirely; per-field pickerOptions arrays replace rather than merge. If you need merge semantics, do it in the extension with a normalizer.
Separate the two "format" worlds explicitly. You have three formats that must agree:
| Concern | Option | Token style | Example |
|---|---|---|---|
| What the user sees in the JS picker | pickerOptions['format'] |
Moment.js | DD/MM/YYYY HH:mm |
| What Symfony parses on submit | format |
ICU/IntlDateFormatter |
dd/MM/yyyy HH:mm |
What the PHP DateTime becomes |
model_timezone / with_seconds |
— | — |
Date-only fields. The type parents to DateTimeType, so it always yields a DateTime. For a birthdate-style field, disable time picking and make the formats line up:
$builder->add('birthDate', DateTimePickerType::class, [
'format' => 'dd/MM/yyyy',
'pickerOptions' => ['pickTime' => false, 'format' => 'DD/MM/YYYY'],
]);
Bounded ranges. Keep the picker bounds and the validator bounds in sync — the picker is UX only and is trivially bypassed:
$builder->add('startsAt', DateTimePickerType::class, [
'pickerOptions' => ['minDate' => 'now', 'maxDate' => '+1 year'],
'constraints' => [new Assert\GreaterThanOrEqual('today')],
]);
Integrating with AJAX / dynamic collections. The init script runs inline per widget. Rows inserted after page load need re-initialisation; the pragmatic pattern is to opt out of the generated inline script (override the widget block) and instead add a marker class via attr, then initialise in your own delegated handler:
'attr' => ['class' => 'js-datetimepicker', 'data-format' => 'DD/MM/YYYY HH:mm']
Extension points worth knowing. The Twig block (collot_datetimepicker_widget / its script block) can be overridden per project — copy it into templates/form/, adjust to emit data-* attributes instead of inline JS, and register yours after the bundle theme. That is the cleanest way to add aria attributes, custom buttons, or a picker icon.
12/03/2024 14:30 but Symfony's format is yyyy-MM-dd, submit fails with "This value is not valid." Remember moment uses YYYY/DD/HH, Symfony uses yyyy/dd/HH. YYYY vs yyyy (week-year vs year) and DD vs dd (day-of-year vs day-of-month) are the traps.getName() / block prefix drift. On Symfony 4.3+ the block prefix is derived correctly, but if you see the widget render as a bare text input, the form theme silently wasn't applied — check twig.form_themes and that the template path is right for your Symfony version (the Bundle:Dir:file.html.twig notation is deprecated/removed for bundles outside templates/).defer/async or scripts in <head>, you get $(...).datetimepicker is not a function in the console — the field still renders, so it looks like a styling issue.Resources/public is only served after assets:install. In Symfony 5.2+ with the new asset mapper, the public/bundles/... copies may need to be referenced by the asset() function with the full bundle path.model_timezone/view_timezone. If users report times "off by an hour" around DST, set model_timezone explicitly and store UTC.pickerOptions['locale'] picks the Moment locale file (which must actually be loaded), while the Symfony locale governs server-side parsing. Mismatched month names are the symptom.data-target wiring — ensure prototype placeholders are replaced.pickerOptions API in your own form type that extends plain DateTimeType with widget => single_text and a handful of lines of JS — the bundle is thin enough that forking or inlining is cheaper than fighting it.data-*/inline options JSON match, (5) do the three formats agree.How can I help you explore Laravel packages today?