DateTime Input Fields
The datetime-local input field combines date and time selection in a single control. It provides an integrated picker and submits data in YYYY-MM-DDTHH:MM format.
Browser Display vs Submitted Format: Browsers display datetime-local inputs according to user locale, but always submit in consistent ISO 8601 format (YYYY-MM-DDTHH:MM) that your PHP code can rely on.
Basic Usage
Use to create a datetime input field:
echo form_datetime_local('event_datetime');
// Output: <input type="datetime-local" name="event_datetime">Demonstration
Below is the native HTML5 date and time input rendered by form_datetime_local():
Function Signature
function form_datetime_local(string $name, ?string $value = null, array $attributes = []): stringParameters
| Parameter | Type | Description | Default |
|---|---|---|---|
$name |
string | The name attribute for the input (required) | N/A |
$value |
string|null | The value in YYYY-MM-DDTHH:MM format | null |
$attributes |
array | Additional HTML attributes | [] |
With a Default Value
echo form_datetime_local('appointment', '2025-12-27T14:30');
// Output: <input type="datetime-local" name="appointment" value="2025-12-27T14:30">Format Note: The value must include the literal "T" character between date and time: 2025-12-27T14:30. This is the ISO 8601 format for datetime-local.
With Attributes
$attributes = [
'min' => '2025-01-01T09:00',
'max' => '2025-12-31T17:00',
'required' => true
];
echo form_datetime_local('deadline', '', $attributes);Output:
<input type="datetime-local" name="deadline" value="" min="2025-01-01T09:00" max="2025-12-31T17:00" required>Boolean Attributes: Pass true for boolean attributes like required, readonly, or disabled. This generates clean HTML5 syntax: <input required>
Downloadable Demo: Want to see the function in action? You've got it! Check out the complete Events module example on GitHub:
https://github.com/trongate/Trongate-v2-Events-Module
This GitHub repository demonstrates a full-featured CRUD (Create, Read, Update, Delete) application for scheduling events with precise date and time tracking. It includes bidirectional datetime conversion between ISO 8601 and MySQL DATETIME formats. Check it out and learn by exploring a full working example.
Common Attributes
| Attribute | Purpose | Example |
|---|---|---|
min |
Earliest selectable datetime (YYYY-MM-DDTHH:MM) | ['min' => '2025-01-01T00:00'] |
max |
Latest selectable datetime (YYYY-MM-DDTHH:MM) | ['max' => '2025-12-31T23:59'] |
step |
Time interval in seconds | ['step' => 900] (15 min) |
required |
Field must have a value | ['required' => true] |
readonly |
Prevent user editing | ['readonly' => true] |
disabled |
Disable the input | ['disabled' => true] |
Setting Current DateTime as Default
$now = date('Y-m-d\TH:i');
echo form_datetime_local('scheduled_at', $now);Important: Notice the escaped \T in the date format string. This ensures PHP outputs the literal "T" character required by the datetime-local format.
How to Repopulate Forms After Validation Errors
Controller Example
public function create(): void {
// Fetch posted value (returns empty string if form hasn't been submitted)
$data['event_datetime'] = post('event_datetime', true);
$data['deadline'] = post('deadline', true);
// Pass data to view
$this->view('event_form', $data);
}View Example
<?php
echo form_open('events/submit');
echo form_label('Event Date & Time');
echo form_datetime_local('event_datetime', $event_datetime);
echo form_label('Registration Deadline');
echo form_datetime_local('deadline', $deadline);
echo form_submit('submit', 'Save');
echo form_close();
?>Form Repopulation Pattern: Always use post('field_name', true) in the controller to fetch submitted values. The true parameter trims whitespace, ensuring clean data.
Real-World Example: Event Scheduling
Controller:
public function create(): void {
$data['headline'] = 'Schedule Event';
$data['event_start'] = post('event_start', true);
$data['event_end'] = post('event_end', true);
$data['form_location'] = BASE_URL . 'events/submit';
$this->templates->admin($data);
}
public function submit(): void {
$this->validation->set_rules('event_start', 'event start', 'required|valid_datetime_local');
$this->validation->set_rules('event_end', 'event end', 'required|valid_datetime_local|callback_end_after_start');
if ($this->validation->run() === true) {
// Convert from YYYY-MM-DDTHH:MM to MySQL DATETIME format
$data['event_start'] = str_replace('T', ' ', post('event_start', true)) . ':00';
$data['event_end'] = str_replace('T', ' ', post('event_end', true)) . ':00';
$new_id = $this->db->insert($data, 'events');
set_flashdata('Event successfully scheduled');
redirect('events/show/' . $new_id);
} else {
$this->create();
}
}
public function callback_end_after_start($event_end): string|bool {
$event_start = post('event_start', true);
if ($event_start === '' || $event_end === '') {
return true;
}
if (strtotime($event_end) <= strtotime($event_start)) {
return 'The {label} must be after the event start time.';
}
return true;
}View:
<h1><?= $headline ?></h1>
<?= validation_errors() ?>
<?php
echo form_open($form_location);
echo form_label('Event Start');
$start_attrs = ['required' => true, 'step' => 900];
echo form_datetime_local('event_start', $event_start, $start_attrs);
echo form_label('Event End');
$end_attrs = ['required' => true, 'step' => 900];
echo form_datetime_local('event_end', $event_end, $end_attrs);
echo form_submit('submit', 'Schedule Event');
echo form_close();
?>Database Conversion
The datetime-local input submits data in YYYY-MM-DDTHH:MM format, but MySQL's DATETIME type expects YYYY-MM-DD HH:MM:SS format. You need to convert between these formats.
Table Schema
CREATE TABLE events (
id INT PRIMARY KEY AUTO_INCREMENT,
event_title VARCHAR(255),
event_start DATETIME,
event_end DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);Converting for Database Storage
public function submit(): void {
$this->validation->set_rules('event_start', 'event start', 'required|valid_datetime_local');
if ($this->validation->run() === true) {
// From: 2025-12-27T14:30
// To: 2025-12-27 14:30:00
$datetime_submitted = post('event_start', true);
$datetime_for_db = str_replace('T', ' ', $datetime_submitted) . ':00';
$data['event_title'] = post('event_title', true);
$data['event_start'] = $datetime_for_db;
$this->db->insert($data, 'events');
redirect('events/manage');
} else {
$this->create();
}
}Converting for Form Display
public function create(): void {
$update_id = segment(3, 'int');
if ($update_id > 0 && REQUEST_TYPE === 'GET') {
// From database: 2025-12-27 14:30:00
// To form: 2025-12-27T14:30
$record = $this->db->get_where($update_id, 'events');
$datetime_from_db = $record->event_start;
// Remove seconds and replace space with T
$datetime_for_form = substr($datetime_from_db, 0, 16);
$datetime_for_form = str_replace(' ', 'T', $datetime_for_form);
$data['event_start'] = $datetime_for_form;
} else {
$data['event_start'] = post('event_start', true);
}
$this->view('event_form', $data);
}Helper Function Idea: Consider creating helper functions in your model for these conversions:
// In model
private function datetime_to_form(string $db_datetime): string {
return str_replace(' ', 'T', substr($db_datetime, 0, 16));
}
private function datetime_to_db(string $form_datetime): string {
return str_replace('T', ' ', $form_datetime) . ':00';
}Working with the Create/Update Pattern
public function create(): void {
$update_id = segment(3, 'int');
if ($update_id > 0 && REQUEST_TYPE === 'GET') {
// Editing - load and convert from database
$record = $this->db->get_where($update_id, 'appointments');
$data['scheduled_at'] = str_replace(' ', 'T', substr($record->scheduled_at, 0, 16));
} else {
// New or validation error - use POST
$data['scheduled_at'] = post('scheduled_at', true);
}
$data['form_location'] = BASE_URL . 'appointments/submit/' . $update_id;
$this->view('appointment_form', $data);
}
public function submit(): void {
$this->validation->set_rules('scheduled_at', 'appointment time', 'required|valid_datetime_local');
if ($this->validation->run() === true) {
$update_id = segment(3, 'int');
// Convert for database
$data['scheduled_at'] = str_replace('T', ' ', post('scheduled_at', true)) . ':00';
if ($update_id > 0) {
$this->db->update($update_id, $data, 'appointments');
set_flashdata('Appointment updated');
} else {
$this->db->insert($data, 'appointments');
set_flashdata('Appointment scheduled');
}
redirect('appointments/manage');
} else {
$this->create();
}
}Type-Casting Segments: Always use segment(3, 'int') when expecting numeric IDs. This prevents type-related bugs and improves code clarity.
Formatting DateTime for Display
// From database: 2025-12-27 14:30:00
$datetime = new DateTime('2025-12-27 14:30:00');
// Various formats
echo $datetime->format('F j, Y \a\t g:i A'); // December 27, 2025 at 2:30 PM
echo $datetime->format('M d, Y - H:i'); // Dec 27, 2025 - 14:30
echo $datetime->format('Y-m-d H:i'); // 2025-12-27 14:30
echo $datetime->format('l, F j, Y \a\t g:i A'); // Friday, December 27, 2025 at 2:30 PM
// Locale-aware
$formatter = new IntlDateFormatter(
'en_US',
IntlDateFormatter::LONG,
IntlDateFormatter::SHORT
);
echo $formatter->format($datetime); // December 27, 2025, 2:30 PM
// French
$formatter = new IntlDateFormatter(
'fr_FR',
IntlDateFormatter::LONG,
IntlDateFormatter::SHORT
);
echo $formatter->format($datetime); // 27 décembre 2025 à 14:30
// Japanese
$formatter = new IntlDateFormatter(
'ja_JP',
IntlDateFormatter::LONG,
IntlDateFormatter::SHORT
);
echo $formatter->format($datetime); // 2025年12月27日 14:30Validation
Use the valid_datetime_local validation rule:
$this->validation->set_rules('event_datetime', 'event date and time', 'required|valid_datetime_local');This validates that the value matches YYYY-MM-DDTHH:MM format and represents a valid date and time.
Client vs Server Validation: While browsers validate datetime formats client-side, always use server-side validation with valid_datetime_local. Client validation can be bypassed and doesn't protect your database.
See the Validating Date and Time Data chapter for complete validation rule documentation.
Restricting to Business Hours
Example: Only allow appointments during business hours (9 AM - 5 PM, Monday-Friday):
public function book_appointment(): void {
$data['appointment_time'] = post('appointment_time', true);
// Set min to tomorrow at 9 AM
$tomorrow = new DateTime('tomorrow');
$tomorrow->setTime(9, 0);
$data['min_datetime'] = $tomorrow->format('Y-m-d\TH:i');
// Set max to 30 days from now at 5 PM
$max_date = new DateTime('+30 days');
$max_date->setTime(17, 0);
$data['max_datetime'] = $max_date->format('Y-m-d\TH:i');
$this->view('booking_form', $data);
}
public function submit_appointment(): void {
$this->validation->set_rules('appointment_time', 'appointment time', 'required|valid_datetime_local|callback_during_business_hours');
if ($this->validation->run() === true) {
// Process booking
$data['appointment_time'] = str_replace('T', ' ', post('appointment_time', true)) . ':00';
$this->db->insert($data, 'appointments');
set_flashdata('Appointment booked successfully');
redirect('appointments/confirm');
} else {
$this->book_appointment();
}
}
public function callback_during_business_hours($datetime): string|bool {
$dt = new DateTime($datetime);
$hour = (int) $dt->format('H');
$day_of_week = (int) $dt->format('N'); // 1=Monday, 7=Sunday
// Check if weekend
if ($day_of_week >= 6) {
return 'Appointments are only available Monday through Friday.';
}
// Check if outside business hours
if ($hour < 9 || $hour >= 17) {
return 'Appointments must be between 9:00 AM and 5:00 PM.';
}
return true;
}// View
<?php
$attrs = [
'min' => $min_datetime,
'max' => $max_datetime,
'step' => 1800, // 30-minute intervals
'required' => true
];
echo form_datetime_local('appointment_time', $appointment_time, $attrs);
?>Common Patterns
Event Scheduling (Future Dates Only)
$now = date('Y-m-d\TH:i');
$attributes = [
'min' => $now,
'required' => true,
'step' => 900 // 15-minute intervals
];
echo form_datetime_local('event_start', $event_start, $attributes);Deadline Setting (Next 90 Days)
$now = date('Y-m-d\TH:i');
$max = date('Y-m-d\TH:i', strtotime('+90 days'));
$attributes = [
'min' => $now,
'max' => $max,
'required' => true
];
echo form_datetime_local('deadline', $deadline, $attributes);Log Entry (Past Dates Only)
$now = date('Y-m-d\TH:i');
$attributes = [
'max' => $now,
'required' => true
];
echo form_datetime_local('occurred_at', $occurred_at, $attributes);Timezone Considerations
The datetime-local input type intentionally does not include timezone information. It represents "local time" - the date and time as displayed on a calendar and clock.
Important Timezone Behavior:
- The value submitted is exactly what the user sees and enters
- No timezone conversion happens automatically
- The server receives the datetime "as entered" by the user
- This is perfect for: appointments, deadlines, event times, scheduled tasks
When This Works Well
// User in New York books appointment for 2:00 PM
// Form submits: 2025-12-27T14:00
// Database stores: 2025-12-27 14:00:00
// Display shows: December 27, 2025 at 2:00 PM
// ✓ This is correct - the appointment is at 2 PM local timeWhen You Need Timezone Awareness
If your application needs to coordinate times across timezones (like a global conference call), you'll need additional handling:
// Capture timezone separately
echo form_datetime_local('meeting_time', $meeting_time);
echo form_dropdown('timezone', $timezone_options, $selected_timezone);
// Or detect timezone with JavaScript and submit as hidden field
// Or store user's timezone in their profileBrowser Rendering
Different browsers provide different datetime picker interfaces:
- Chrome/Edge: Combined date and time picker with calendar popup
- Firefox: Separate date and time controls in one input
- Safari: Native datetime picker with smooth scrolling interface
- Mobile: Native datetime picker optimized for touch
Datetime-Local vs Separate Date and Time Fields
| Aspect | datetime-local | Separate date + time |
|---|---|---|
| User Experience | Single control, faster input | Two controls, more explicit |
| Data Format | YYYY-MM-DDTHH:MM (one value) | Two separate values to combine |
| Database Conversion | Replace T with space, add seconds | Concatenate date + time |
| Validation | One rule: valid_datetime_local | Two rules: valid_date + valid_time |
| Best For | Appointments, deadlines, events | More control, optional times |
Complete Model Example with Conversions
class Events_model extends Model {
/**
* Convert database datetime to form format
*/
private function db_to_form_datetime(?string $db_datetime): string {
if (empty($db_datetime) || $db_datetime === '0000-00-00 00:00:00') {
return '';
}
// From: 2025-12-27 14:30:00
// To: 2025-12-27T14:30
return str_replace(' ', 'T', substr($db_datetime, 0, 16));
}
/**
* Convert form datetime to database format
*/
private function form_to_db_datetime(string $form_datetime): string {
if (empty($form_datetime)) {
return '';
}
// From: 2025-12-27T14:30
// To: 2025-12-27 14:30:00
return str_replace('T', ' ', $form_datetime) . ':00';
}
/**
* Get form data for create/update
*/
public function get_form_data(int $update_id): array {
if ($update_id > 0 && REQUEST_TYPE === 'GET') {
$record = $this->db->get_where($update_id, 'events');
return [
'event_title' => $record->event_title,
'event_start' => $this->db_to_form_datetime($record->event_start),
'event_end' => $this->db_to_form_datetime($record->event_end)
];
}
return [
'event_title' => post('event_title', true),
'event_start' => post('event_start', true),
'event_end' => post('event_end', true)
];
}
/**
* Prepare POST data for database
*/
public function get_post_data_for_database(): array {
return [
'event_title' => post('event_title', true),
'event_start' => $this->form_to_db_datetime(post('event_start', true)),
'event_end' => $this->form_to_db_datetime(post('event_end', true))
];
}
}Important Notes
- Value format must include literal "T":
YYYY-MM-DDTHH:MM - Database conversion required: replace T with space, add :00 for seconds
- When loading from database: remove seconds, replace space with T
- Represents "local time" - no timezone information included
- Use MySQL's
DATETIMEtype for storage - Step attribute works same as time input (seconds)
- Browser handles display format automatically
- Server-side validation still required despite browser validation
Best Practice: Create helper methods in your model for datetime conversions. This keeps your controllers clean and makes conversions consistent across your application.
We're continually improving the Trongate documentation. If anything is incorrect, unclear, incomplete, or could be better, we'd genuinely appreciate your input.
Share your thoughts in the Documentation Feedback.