HTML Input Type Datetime-local
Some things happen at an exact moment: a webinar, a flight, a delivery slot, an exam start. For these you need both a day and a time, and type="datetime-local" lets the visitor pick both in a single field.
The name has a catch worth understanding right away. "Local" means the value carries no time zone. It says "25 December, half past three" and nothing about where. In this lesson, you will learn how the field works, and how to turn that local value into a real, unambiguous moment on your server.
What You Will Learnβ
- What
type="datetime-local"shows, and the value format it sends - What "local" really means
min,max, andstep- How to set the current time as a default
- How to convert the value into an exact moment (UTC)
- Daylight saving traps, accessibility, and common mistakes
The Basicsβ
- HTML Code
- Browser Output
<label for="start">Event starts</label>
<input type="datetime-local" id="start" name="start" />
The field has parts for day, month, year, hour, and minute (and AM/PM on a 12-hour clock). Clicking it opens a picker in many browsers, and phones show their own native date and time wheels. It looks like a date input and a time input joined together.
The Value Formatβ
yyyy-mm-ddThh:mmA date, the letter T, and a 24-hour time, such as 2026-12-25T15:30. It is the same however the field is displayed.
| What the visitor sees | What is sent |
|---|---|
12/25/2026, 03:30 PM (US) | 2026-12-25T15:30 |
25/12/2026, 15:30 (UK, India) | 2026-12-25T15:30 |
25.12.2026, 15:30 (Germany) | 2026-12-25T15:30 |
| (nothing, or incomplete) | an empty string |
start=2026-12-25T15%3A30
(The : is encoded as %3A in the address, and your server decodes it back.)
If step is under 60 seconds, the value gets seconds too: 2026-12-25T15:30:45.
The starting value in HTML uses the same format. The T is required, and a space will not work:
<input type="datetime-local" name="start" value="2026-12-25T15:30" />
datetime type is goneYears ago, HTML had a type="datetime" that included a time zone. No browser supports it any more, and it was removed. Use datetime-local, and handle the zone yourself, as explained below.
What "Local" Meansβ
The value has no time zone and no offset. It is a "wall-clock" reading: what a clock on the wall would show to the person choosing it.
| Visitor in | Picks | Value sent |
|---|---|---|
| Mumbai | 25 Dec, 15:30 | 2026-12-25T15:30 |
| Chicago | 25 Dec, 15:30 | 2026-12-25T15:30 |
Those are two different moments (Mumbai's is about 11.5 hours earlier), yet the strings are identical. Your server cannot tell them apart without more information.
That makes datetime-local perfect for things that are tied to a local clock: "the doctor sees me at 15:30 at the clinic," or "the gym class starts at 18:00." But when the exact moment matters across places, you need to attach a time zone.
Try It: One Value, Many Momentsβ
Pick a date and time in the field. The panel shows the exact moment that value stands for, if it is read in your own time zone, and how that same moment looks elsewhere.
- HTML Code
- Browser Output
<label for="start">Meeting starts</label>
<input type="datetime-local" id="start" name="start" value="2026-12-25T15:30" />
<ul>
<li>Your zone: <span id="zone"></span></li>
<li>In UTC: <code id="utc"></code></li>
<li>In New York: <span id="ny"></span></li>
<li>In Tokyo: <span id="tokyo"></span></li>
</ul>
const input = document.getElementById("start");
function show(date, zone) {
return new Intl.DateTimeFormat("en-GB", {
dateStyle: "medium",
timeStyle: "short",
timeZone: zone,
}).format(date);
}
function update() {
if (!input.value) return;
const moment = new Date(input.value); // read as LOCAL time
document.getElementById("zone").textContent =
Intl.DateTimeFormat().resolvedOptions().timeZone;
document.getElementById("utc").textContent = moment.toISOString();
document.getElementById("ny").textContent = show(moment, "America/New_York");
document.getElementById("tokyo").textContent = show(moment, "Asia/Tokyo");
}
input.addEventListener("input", update);
update();
The key line is new Date(input.value). A date-time string without an offset is read as the local time of the device. That is exactly what the visitor meant, so it is correct for this case. moment.toISOString() then gives the same moment in UTC, ending in Z.
Turning the Value into a Real Momentβ
Here is the dependable recipe for events that happen at one exact moment worldwide.
Option A: Convert in the Browser (the Visitor's Own Zone)β
When the visitor means "this time in my time zone":
const input = document.getElementById("start");
const utc = new Date(input.value).toISOString(); // "2026-12-25T10:00:00.000Z" (for UTC+05:30)
Send utc to the server (in a hidden field, or in JSON), and store it. Everyone can later convert it to their own zone for display. Store UTC in your database.
Option B: Send the Local Value and the Zone Nameβ
When the time is for a specific place (for example, a clinic), send the local value and the place's zone name. Let the server combine them with a date library:
<label for="when">Appointment</label>
<input type="datetime-local" id="when" name="when" required />
<label for="zone">Time zone</label>
<select id="zone" name="zone">
<option value="Asia/Kolkata">India (Asia/Kolkata)</option>
<option value="Europe/London">UK (Europe/London)</option>
<option value="America/New_York">US Eastern (America/New_York)</option>
</select>
On the server, interpret 2026-12-25T15:30 in Asia/Kolkata, and convert to UTC for storage.
Send Asia/Kolkata or America/Chicago, not +05:30. Offsets change with daylight saving time, and a name lets a library apply the right rules for any date, even years ahead.
Min, Max, and Stepβ
| Attribute | What it does | Example |
|---|---|---|
min | The earliest date and time | min="2026-12-01T09:00" |
max | The latest date and time | max="2026-12-31T17:00" |
step | The gap in seconds (default 60) | step="900" |
<label for="slot">Choose a slot (December, quarter hours)</label>
<input
type="datetime-local"
id="slot"
name="slot"
min="2026-12-01T09:00"
max="2026-12-31T17:00"
step="900"
required
/>
minandmaxuse the fullyyyy-mm-ddThh:mmformat.stepis in seconds, as with time:900is 15 minutes,3600is an hour,1shows seconds.- Unlike
time, amingreater thanmaxis not an overnight range. It is simply an impossible range, and nothing will be valid. - HTML cannot block specific hours inside a day (such as lunch), or days (such as holidays). Check them on the server, and explain clearly.
Setting "Now" as a Defaultβ
Pre-filling the current time is common ("when did this happen?"). HTML cannot know the time when the page was written, so use JavaScript, and build the string from the local parts:
function nowLocal() {
const now = new Date();
const pad = (n) => String(n).padStart(2, "0");
return (
now.getFullYear() + "-" + pad(now.getMonth() + 1) + "-" + pad(now.getDate()) +
"T" + pad(now.getHours()) + ":" + pad(now.getMinutes())
);
}
const input = document.getElementById("start");
input.value = nowLocal();
input.min = nowLocal(); // no moments in the past
toISOString().slice(0, 16)That gives the time in UTC, not the visitor's local clock, so the field would show the wrong hour (and sometimes the wrong day). Build the string from getHours(), getDate(), and the other local getters, as above.
Reading the Value in JavaScriptβ
| Property | What it gives |
|---|---|
input.value | The string yyyy-mm-ddThh:mm |
input.valueAsNumber | Milliseconds, as if the value were UTC |
input.valueAsDate | Not supported for this type (it returns null) |
To get a proper Date, use new Date(input.value), which reads it as the device's local time. Avoid valueAsNumber unless you know you want the "pretend it is UTC" reading. It does not match the visitor's local moment.
To split the parts:
const [day, time] = input.value.split("T"); // "2026-12-25", "15:30"
Daylight Saving Trapsβ
When clocks change, some local times are strange:
- Spring forward: In places that move clocks ahead an hour, a time like
02:30on that day does not exist. - Fall back: When clocks move back an hour, a time like
01:30happens twice.
A datetime-local value cannot say which one the visitor meant. Browsers and date libraries make different guesses. To stay safe:
- Do the conversion on the server with a time zone library, and decide on a rule (usually "move forward to the next valid time").
- Show the result back to the visitor ("Your meeting is at 15:30 IST, Thursday 25 December") so mistakes are caught.
- Store UTC, plus the original zone name, so you can always show the intended local time.
Other Attributesβ
| Attribute | What it does | Example |
|---|---|---|
name, id | Sent key, and label link | name="start" |
value | The starting value | value="2026-12-25T15:30" |
required | Cannot be empty | required |
readonly, disabled | Behavior flags | readonly |
list | Suggestions from a datalist | list="events" |
placeholder, maxlength, minlength, pattern, and size do not apply.
When to Use Date and Time Separatelyβ
| Use | When |
|---|---|
datetime-local | One exact event: a meeting, a flight, a webinar start |
date | Only the day matters: a birthday, a delivery day, a deadline day |
time | Only a time of day matters: opening hours, a daily reminder |
date + select of slots | A calendar day, then fixed time slots that can be marked as taken |
Two separate fields can be easier for some people, especially on desktop, where one combined picker can feel cramped. They also let you control each part's help text and validation.
Styling a Datetime-local Inputβ
input[type="datetime-local"] {
padding: 0.6rem 0.75rem;
border: 1px solid #64748b;
border-radius: 6px;
font: inherit;
font-size: 1rem;
min-height: 2.75rem;
}
input[type="datetime-local"]:focus-visible {
outline: 3px solid #2563eb;
outline-offset: 2px;
}
input[type="datetime-local"]:invalid {
border-color: #b91c1c;
}
- The field is wide, because it holds many parts. Give it room, or it may be cut off on small screens.
- The picker popup cannot be styled.
- Keep a visible focus style, and a comfortable touch size.
Accessibility at a Glanceβ
- Always add a label, such as "Meeting starts."
- Say which time zone applies, in text: "Times are in India Standard Time." This is the most useful hint you can give.
- State the allowed range and step: "Choose a time between 9:00 and 17:00, on the quarter hour."
- Use the native input, which works with keyboards and screen readers better than most custom ones.
- Allow typing. Many people find that quicker than a picker.
- Confirm the result in plain words after submission, such as "Thursday 25 December 2026, 3:30 PM India time."
- Give clear errors: "Choose a time on or after 1 December."
Security and Validation Notesβ
- Re-check on the server. Parse the string strictly, check it is a real date and time, and enforce the range and availability.
- Use the server's clock for rules like "at least 24 hours from now."
- Apply time zones on the server, with a trusted library. Do not trust an offset sent by the browser without checking it.
- Prevent double booking. Two people can pick the same slot at once, so lock or recheck it when saving.
Global Audience Notesβ
- 12-hour and 24-hour clocks and date order follow the visitor's settings. Write help text in a way that avoids ambiguity: "25 December 2026."
- Time zones differ in whole hours, half hours (India, +05:30), and even 45 minutes (Nepal, +05:45). Never assume whole hours.
- Daylight saving is used in many countries, and not in others. Dates when it starts vary.
- Calendars and week starts vary, though the stored value is always in the Gregorian calendar.
- Show times in the viewer's own zone when you display events, and say the zone name.
Common Mistakes to Avoidβ
- Believing the value includes a time zone. It does not.
- Storing the local value as if it were UTC. Convert it, and store UTC plus a zone name.
- Setting
valuewith a space (2026-12-25 15:30) instead ofT. - Using
toISOString().slice(0, 16)for "now," which gives UTC time. - Using
valueAsNumberas a local timestamp. It treats the value as UTC. - Sending an offset (
+05:30) instead of a zone name. - Ignoring daylight saving, where some times do not exist, and others happen twice.
- Forgetting to say which time zone the visitor should assume.
- Using a combined field when only a date or only a time is needed.
- Expecting
mingreater thanmaxto mean overnight. It is not allowed here. - Not rechecking availability and rules on the server.
- Relying on
type="datetime", which no longer exists.
Practice Timeβ
Try these in your editor:
- Build a labeled
datetime-localfield, choose a value, and submit it withmethod="get". Read the address. - Set
value="2026-12-25T15:30", then tryvalue="2026-12-25 15:30"(with a space). - Add
minandmaxin the full format, and open the picker. - Add
step="900"for quarter hours, andstep="1"to show seconds. - Fill the field with the current local time using the
nowLocal()function. - Log
new Date(input.value).toISOString(), and compare it with your own time zone. - Show the same moment in two other zones with
Intl.DateTimeFormatandtimeZone. - Add a hidden field with
Intl.DateTimeFormat().resolvedOptions().timeZone. - Build a version using one
datefield plus a select of time slots. - Bonus: write a short note on what should happen when a visitor picks a time that does not exist because of daylight saving.
Quick Recapβ
Click a question to check your answer.
1. What format is the value of a datetime-local input?
yyyy-mm-ddThh:mm, such as 2026-12-25T15:30, with a 24-hour time. If step is under 60, seconds are included. An incomplete value gives an empty string.
2. What does "local" mean in datetime-local?
The value has no time zone or offset. It is a wall-clock reading, so the same string from different places means different exact moments.
3. How do you get an exact moment from the value?
Convert it using a time zone. In the browser, new Date(input.value).toISOString() reads it in the visitor's own zone and gives UTC. For a specific place, send the local value and a zone name (like Asia/Kolkata), and convert on the server.
4. Why send Asia/Kolkata and not +05:30?
Offsets change with daylight saving time, but a zone name lets a library apply the right rules for any date.
5. How do you set the current local time as a default?
Build the yyyy-mm-ddThh:mm string from the local getters (getFullYear(), getMonth(), getDate(), getHours(), getMinutes()). Do not use toISOString(), which is UTC.
6. What daylight saving problems can a local value hit?
When clocks jump forward, some times do not exist. When they go back, some times happen twice. Resolve this on the server with a time zone library, and show the result back to the visitor.
Key Takeawaysβ
type="datetime-local"picks a date and a time in one field, and sendsyyyy-mm-ddThh:mm.- "Local" means no time zone. The same string can mean different moments in different places.
- To get an exact moment, convert with a time zone: in the browser with
new Date(value).toISOString(), or on the server with a zone name. - Use
min,max, andstep(in seconds), but enforce rules and availability on the server. - Build "now" from local parts, never from
toISOString(). - Tell visitors which time zone applies, confirm the result in plain words, and store UTC plus a zone name.
Next, learn how to pick a whole month, in Month, or return to the Forms overview.