mwsimple/datetimepicker-bundle
MWSImple DateTimePickerBundle is a Symfony bundle that adds date and time picker support to forms, making it easier to render and handle date/time inputs in web applications with minimal configuration.
Read this first: the package is not a Laravel package. mwsimple/datetimepicker-bundle is a Symfony bundle (Resources/config/services.xml, Form/Type/*, Resources/views/Form/*) that registers a form type wrapping the Eonasdan/Bootstrap-DateTimePicker widget. On a Laravel codebase you have two realistic options, and you should decide before writing any code:
Resources/public/ into public/vendor/datetimepicker/, and re-implement the ~40 lines of the bundle's form type as a Blade component. This gets you the identical UI with zero Symfony runtime.composer require mwsimple/datetimepicker-bundle. This only works if it drags in symfony/framework-bundle and the bundle's own dependency constraints still allow your PHP version — check composer.json before running it. Pulling a Symfony framework into a Laravel app to render a date input is almost never worth it.Where to look, in order:
Resources/views/Form/ — the Twig block that renders the input-group markup. This is what you port to Blade.Form/Type/ — the class that calls $builder->add('field', DatetimepickerType::class, ['format' => ...]). It shows which options the bundle exposes and which it hardcodes.Resources/public/ — the picker JS/CSS (and any bundled moment.js build).DependencyInjection/ — any configurable defaults worth mirroring in a Laravel config file.First use case: an appointment/event scheduling field on an admin form. Minimal Laravel version:
{{-- resources/views/components/datetimepicker.blade.php --}}
@props(['name', 'value' => null, 'id' => null])
@php $id ??= 'dtp-'.\Illuminate\Support\Str::random(8); @endphp
<div class="input-group date" id="{{ $id }}" data-target-input="nearest">
<input type="text" name="{{ $name }}" id="{{ $id }}-input"
class="form-control" value="{{ old($name, $value) }}" autocomplete="off">
<div class="input-group-append" data-target="#{{ $id }}" data-toggle="datetimepicker">
<span class="input-group-text"><i class="far fa-calendar"></i></span>
</div>
</div>
@push('scripts')
<script>
$(function () {
$('#{{ $id }}').datetimepicker({ format: 'YYYY-MM-DD HH:mm:ss', locale: '{{ app()->getLocale() }}' });
});
</script>
@endpush
Single source of truth for the format. The most common bug in this integration is a mismatch between the JS format string (YYYY-MM-DD HH:mm:ss, moment tokens) and the PHP validation/serialization format (Y-m-d H:i:s, Carbon tokens). Define both in one config entry so they can never drift:
// config/datetimepicker.php
return [
'js_format' => 'YYYY-MM-DD HH:mm:ss',
'php_format' => 'Y-m-d H:i:s',
'locale' => env('APP_LOCALE', 'en'),
'side_by_side' => true,
];
Emit js_format into the component, use php_format in Rule::date()->format(...) and in Carbon::createFromFormat() on save.
Blade component + validation rule as a pair. Treat the component and a matching DateTimeInput rule (or a prepareForValidation() hook) as one unit. Because the widget posts a string, normalize in the FormRequest:
protected function prepareForValidation(): void
{
if (blank($this->input('starts_at'))) {
$this->merge(['starts_at' => null]);
}
}
Livewire. Wrap the picker in wire:ignore and re-init after DOM morphs; otherwise Livewire replaces the input node and the picker silently stops opening:
<div wire:ignore>
<x-datetimepicker name="starts_at" :value="$starts_at" />
</div>
If you need the value back into Livewire, add an onchange that dispatches: $('#id-input').on('change.datetimepicker', e => @this.set('starts_at', e.target.value)).
Multiple pickers / dynamic rows. Never hardcode the initializer to one selector. The component generates a unique ID (as above) and pushes per-instance JS. For dynamically added rows, re-run the initializer inside the "row added" callback and guard with if (!$('#id').data('DateTimePicker')).
Where the Symfony bundle actually helps you (if you run it under Symfony). Use the bundle's form type directly with $builder->add('starts_at', DatetimepickerType::class) and set defaults through the bundle config in config/packages/; you get the widget wired to Symfony's form theme. Don't mix that with hand-rolled jQuery init on the same field — double initialization produces two overlapping calendars.
Asset pipeline. If you use Vite, import the picker CSS/JS in resources/js/app.js rather than <script> tags, and import moment with only the locales you need (import 'moment/locale/fr') to keep the bundle small. Import order matters: moment → bootstrap-datetimepicker.
tempusdominus-bootstrap-5 (same API lineage) is a cleaner drop-in and lets you keep your Blade component unchanged.symfony/framework-bundle version pins from the Symfony 2/3 era often conflict with a modern Laravel/Symfony stack. Run composer require --dry-run first and read the solver output before touching composer.json.moment/locale/{locale}.js actually 200s, and call moment.locale(...) before initializing.Carbon::createFromFormat(config('datetimepicker.php_format'), $value, config('app.timezone'))->utc(). Do not let strtotime()/implicit parsing guess.'', not null. SQL will happily store an empty string into a datetime column on permissive drivers, or throw on strict ones. Normalize in prepareForValidation() (see above) and make the column nullable..datetimepicker({...}) twice on the same element stacks handlers and produces flicker or duplicated popups. Guard with if ($el.data('DateTimePicker')) { $el.data('DateTimePicker').destroy(); } before re-init — essential when options change (e.g. switching a "side-by-side" toggle).$('#id').data('DateTimePicker') exposes the live instance (date(), format(), options()); $('#id').on('error.datetimepicker', ...) catches malformed input silently rejected by the picker. If the calendar opens in the wrong place, it's almost always a position: relative ancestor or a overflow: hidden modal — use widgetPositioning: { vertical: 'bottom' }.format, language/locale, sideBySide, minDate/maxDate, and daysOfWeekDisabled options. Surface these as component props so call sites don't reach into raw JS, and forward only the non-null ones into the datetimepicker({...}) call.shown.bs.tab / shown.bs.modal instead of DOMContentLoaded.How can I help you explore Laravel packages today?