gradio · difficulty ◆◆
gradio DateTime — one picker for dates, times, and time windows
gr.DateTime hands your function the string sitting in its field — pick the type, then respect the one format it will parse.
That little calendar icon is a decoy. gr.DateTime is a text field first, and the placeholder it shows you is the entire contract.
$ gr.DateTime()What it does
gr.DateTime is the library's only date-and-time input. gr.Date and gr.Time are not in the namespace any more (hasattr(gr, 'Date') is False on 6.18.0). Three constructor switches decide everything: include_time=True|False adds or drops the time part of the field, type='timestamp'|'datetime'|'string' decides what your Python function receives — epoch seconds as a float, a real datetime, or the untouched string — and timezone= localises and formats. The widget itself is a plain <input class="time"> whose placeholder reads YYYY-MM-DD HH:MM:SS, next to a calendar button that opens a month grid, h/m/s number fields, an AM/PM toggle and Clear / Now / Done. There is no format parameter, no min/max and no locale setting.
Why it matters
Every dashboard ends up asking the same question: since when? Before DateTime you made users type a timestamp into a Textbox and regexed it into submission. Now the picker writes a canonical string and the component parses it for you — with a single strptime against exactly one pattern. Knowing that pattern is the difference between a demo that also works from gradio_client or curl, and one that dies the first time a colleague sends ISO-8601 with a T in it.
Example
$ import gradio as gr
with gr.Blocks(title="Shift Scheduler") as demo:
dt = gr.DateTime(label="Shift start", include_time=True, type="string",
info="Local plant time")
btn = gr.Button("Commit shift", variant="primary")
out = gr.Markdown()
@btn.click(inputs=dt, outputs=out)
def commit(when):
return f"Shift starts **{when}**"
demo.launch(prevent_thread_lock=True)* Running on local URL: http://127.0.0.1:7938
Component tree from /config: ['markdown', 'datetime', 'button', 'markdown', 'form']
The field is literally <input class="time" placeholder="YYYY-MM-DD HH:MM:SS"> beside <button class="calendar"> (a calendar glyph).
Clicking the icon opens: a month grid of <button class="day"> cells (classes .other-month, .selected on today), ‹ / › month nav, three <input type="number"> fields pre-filled with the current wall-clock h/m/s, an AM/PM toggle, and Clear / Now / Done.
Picking the 20th at 09:34 wrote 2026-10-20 09:34 into the field (date + time-of-day; there is no preset clock time).
"Commit shift" POSTed {"data":["2026-10-20 09:34:11"],"fn_index":0,"trigger_id":2,...} to /gradio_api/queue/join.
Output rendered: Shift starts 2026-10-20 09:34:11Verified on gradio 6.18.0 (Python 3.13) with headless Chromium driving the widget: a day was clicked in the real panel and the outgoing request body captured. type="string" is the least surprising choice — what you see in the field is what your function receives.
$ import gradio as gr
with gr.Blocks(title="Maintenance Window") as demo:
day = gr.DateTime(label="Window opens", include_time=False, type="datetime")
b = gr.Button("Plan window", variant="primary")
o = gr.Markdown()
@b.click(inputs=day, outputs=o)
def plan(d):
return f"{d} · that is a {d.strftime('%A')}"
demo.launch(prevent_thread_lock=True)* Running on local URL: http://127.0.0.1:7939
/gradio_api/info declares the parameter as {"type": "string", "description": "Formatted as YYYY-MM-DD"}; the field's placeholder drops the time part.
Calling the event API by hand (POST /gradio_api/call/plan, then reading the SSE stream):
"2026-10-11" -> complete; the function got datetime(2026, 10, 11, 0, 0)
"2026-10-11 09:30:00" -> error; ValueError: unconverted data remains: 09:30:00
"2026-10-11T09:30:00" -> error; ValueError: unconverted data remains: T09:30:00
1760000000.0 -> error; AttributeError: 'float' object has no attribute 'strip'
"now" -> complete; datetime.now() at full precision: 2026-10-11 09:33:43.113586
Component props in /config: include_time false, type "datetime", min_width 160include_time=False changes what the parser accepts, not just what the UI shows: preprocess switches to strptime('%Y-%m-%d'), so a payload carrying a time is rejected. Send the date-only string from your own code and it works; send an ISO timestamp and you get an anonymous 'event: error' in the browser.
$ import datetime as dt
import gradio as gr
LOGS = [
("2026-10-11 07:59:00", "INFO boot ok"),
("2026-10-11 09:00:12", "WARN disk 91% on /var"),
("2026-10-11 09:04:44", "ERROR postgres connection refused"),
("2026-10-11 09:41:03", "INFO retry succeeded"),
("2026-10-11 12:15:00", "INFO cron finished"),
]
with gr.Blocks(title="Log Window") as demo:
since = gr.DateTime(label="Show logs since", type="timestamp",
include_time=True, value="now - 30m")
run = gr.Button("Filter logs", variant="primary")
table = gr.Dataframe(headers=["time", "message"], label="Matching lines")
@run.click(inputs=since, outputs=table)
def filt(epoch_seconds):
cut = dt.datetime.fromtimestamp(epoch_seconds)
return [[s[11:19], m] for s, m in LOGS
if dt.datetime.strptime(s, "%Y-%m-%d %H:%M:%S") >= cut]
demo.launch(prevent_thread_lock=True)* Running on local URL: http://127.0.0.1:7940
Component tree: ['datetime', 'button', 'dataframe', 'form']
value="now - 30m" is echoed in /config as {"value": "now - 30m", "include_time": true, "type": "timestamp", ...}
Calling the handler with a cut at "2026-10-11 09:30:00": the function received the float 1791703800.0
-> ["09:41:03", "INFO retry succeeded"], ["12:15:00", "INFO cron finished"]
Calling it with "2026-10-11 00:00:00": all five rows come back.
The Dataframe event stream returns {"headers": ["time", "message"], "data": [[...]], "metadata": null}.type="timestamp" is the default and the friendliest for arithmetic: your function gets epoch seconds as a float, so datetime.fromtimestamp() and >= comparisons need no parsing library. Note the two formats in play — strptime in preprocess and strptime on the log line are separate parses; keep them aligned.
$ import gradio as gr
with gr.Blocks(title="Region Dashboard") as demo:
window = gr.DateTime(label="Window start", type="string",
value="now - 2h", timezone="Asia/Riyadh")
btn = gr.Button("Apply", variant="primary")
out = gr.Markdown(label="Window")
@btn.click(inputs=window, outputs=out)
def apply(w):
return f"window = `{w}`"
demo.launch(prevent_thread_lock=True)* Running on local URL: http://127.0.0.1:7941
/gradio_api/call/apply round trips (server wall clock was 09:34:49 CEST):
"now - 2h" -> complete; window = `2026-10-11 07:34:49`
"now - 45s" -> complete; window = `2026-10-11 09:34:04`
"now" -> complete; window = `2026-10-11 09:34:49`
"now + 1d" -> error; ValueError: Invalid 'now' time format
"2026-10-11 09:30:00" -> complete; window = `2026-10-11 09:30:00` (string type: passed through untouched)
"" -> complete; window = `None`
Relative parsing on the component itself: 'now - 30m' -> 1791702274.013, 'now - 1d' -> 1791617674.013, 'now - 2h' -> 1791696874.013, 'now-2d' -> 1791531274.013, 'tomorrow' -> ValueError: time data 'tomorrow' does not match format '%Y-%m-%d %H:%M:%S'The relative shortcut is the fastest 'last N minutes' control in Gradio — but it only subtracts, and timezone='Asia/Riyadh' does not shift that maths: the branch is a naive datetime.now() minus your delta, so a preset follows the server clock, not the timezone you passed. For everything else, timezone= does the work: postprocess(1791703800.0) gives '2026-10-11 09:30:00' with tz=None, '09:30:00' with Europe/Vienna and '10:30:00' with Asia/Riyadh.
Common flags
- type="timestamp" | "datetime" | "string"
- What your function receives: epoch float (default), a real datetime, or the raw string. The single most consequential argument.
- include_time=False
- Drops the time from the field AND from the parser — the accepted format changes from '%Y-%m-%d %H:%M:%S' to '%Y-%m-%d'.
- timezone="Asia/Riyadh"
- Localises a parsed datetime with pytz and formats epoch->string in that zone on the way out. Does not affect the 'now' branch.
- value="now - 30m"
- Relative presets live in the constructor. Only 'now' and 'now - N[d|h|m|s]' are accepted; a future offset raises.
- info="Local plant time"
- The small grey hint under the field — use it to state which clock you mean, because this component has no timezone-aware UI.
- min_width=160
- The default is narrow; in a Row it will not stretch unless you hand it scale= or widen min_width.
- .change / .submit
- The only two events declared (EVENTS == ['change', 'submit']). No .input, no .select — bind one of these to react to a pick.
History
A late arrival (2024-07-12)
DateTime is young. In the gradio-app repo the file gradio/components/datetime.py does not exist at tag 4.37.1 and does exist at tag 4.38.0, and PyPI dates gradio 4.38.0 to 2024-07-12. The widgets it superseded — the separate gr.Date and gr.Time — are no longer exposed: hasattr(gr, 'Date') and hasattr(gr, 'Time') are both False on 6.18.0.
One format, spelled out in the placeholder
The design decision from day one: no `format` parameter. The class carries a time_format ('%Y-%m-%d %H:%M:%S', or '%Y-%m-%d' when include_time=False) and preprocess runs datetime.strptime(payload, self.time_format). That is why the placeholder is the contract, and why a value that merely looks like a date — 2026-10-11 or ISO with a T — is an opaque error on a time-enabled picker. Gradio has been patching this path since: 'use system timezone in gr.DateTime with include_time=False' landed in 5.4.0 (2024-10-25) and the whitespace/parse fix plus a comprehensive DateTime test suite in 6.12.0 (2026-04-10).
Fun facts
Pros & cons
pros
- + One component covers date-only, full timestamps and past windows — no extra dependency, no custom JS.
- + type="timestamp" gives epoch floats, so comparisons, pandas filtering and numpy arithmetic need no parsing at all.
- + value="now - 30m" plus a .click handler is a complete 'last N minutes' control in two lines.
cons
- − Parsing is a single strict format: ISO-8601 with a T, a date-only string on a time-enabled picker, or a numeric epoch all raise, and the browser only sees 'event: error'.
- − No format, min/max, locale or step: you cannot bound a picker to 'no future dates' or force a specific display locale.
- − timezone= does not shift relative 'now' values — that branch is naive datetime.now(), so a Riyadh dashboard reads the server's clock.
Takeaways
- 1Choose the type first: timestamp for maths, datetime for time-aware libraries, string when you are just echoing the user's choice back.
- 2Send the exact 'YYYY-MM-DD HH:MM:SS' string, and flip include_time=False in the same commit that drops the visible time — the accepted format changes with it.
- 3Put value="now - 30m" on a DateTime, bind .click, and you have a working time-window filter before you write any client code.
- 4Bind .change or .submit yourself: the component fires nothing on its own when a day is picked, and a bare 'event: error' in the browser usually means a format mismatch, not a bug in your function.