kmail.at
← learning

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.

2026-10-11 · 5 min read

$ 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:11

Verified 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 160

include_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

  1. 1Choose the type first: timestamp for maths, datetime for time-aware libraries, string when you are just echoing the user's choice back.
  2. 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.
  3. 3Put value="now - 30m" on a DateTime, bind .click, and you have a working time-window filter before you write any client code.
  4. 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.

Related commands

← all learning