Slint Kit Documentation

Times and zones

Zoned Date and Time Field for Slint

KitZonedDateTimeField · Slint Kit 0.3.0

Local date and time resolved in an IANA time zone.

Local date and time resolved in an IANA time zone.
Native app built from the example below.

Try the component

Download the complete project, extract it and run cargo run --locked in its folder. Rust 1.92+; Slint Kit 0.3.0 from crates.io. You can also copy the four files below.

Enter YYYY-MM-DD, 24-hour HH:MM and an IANA time zone, then Resolve time zone. A repeated local time requires an explicit earlier or later occurrence.

ui/app.slint

export { KitFormValidation, HtmlInputKind, NumberInputFormat, KitInputConstraints, KitValidationResult, KitValidityState } from "@kit/kit.slint";
import {KitZonedDateTimeField,KitButton,KitThemes,CivilDateView,LocalTimeView,LocalDateTimeView,LocalDateTimeDraft,ZonedDateTimeDraft,ZonedTimePresentation,AmbiguousTimeChoice} from "@kit/kit.slint";
export {CivilDateView,LocalTimeView,LocalDateTimeView,LocalDateTimeDraft,ZonedDateTimeDraft,ZonedTimePresentation,AmbiguousTimeChoice}
export component App inherits Window {
 title:"zoned-date-time-field";preferred-width:560px;preferred-height:660px;max-width:100000px;max-height:100000px;background:KitThemes.light.canvas;
 in-out property <string> date-text:"2026-10-17";in-out property <string> time-text:"09:30";in-out property <string> zone-text:"America/Toronto";
 in property <LocalDateTimeView> accepted-local;in property <string> error;in property <string> accepted;
 in property <ZonedTimePresentation> resolution;callback commit(ZonedDateTimeDraft);callback edit();callback choose(AmbiguousTimeChoice);callback cancel();
 VerticalLayout {padding:24px;spacing:16px;alignment:start;
 control:=KitZonedDateTimeField{label:"Appointment";date-text<=>root.date-text;time-text<=>root.time-text;date-error:root.error;
 zone-text<=>root.zone-text;accepted-local:root.accepted-local;resolution:root.resolution;resolution-message:root.error;resolve-requested(draft)=>{root.commit(draft);}ambiguity-chosen(choice)=>{root.choose(choice);}
 edited(draft)=>{root.edit();}cancelled=>{root.cancel();}
 }
 KitButton{text:"Resolve time zone";activated=>{root.commit({date:root.date-text,time:root.time-text,zone:root.zone-text});}}
 Text{text:"Accepted: "+root.accepted;color:KitThemes.light.text-primary;wrap:word-wrap;}
 }
}

src/main.rs

use slint::ComponentHandle;
use slint_kit::{
    CivilDate, DraftValue, Instant, LocalDateTime, LocalTime, TimePrecision, TimeZoneId,
    ZonedResolution,
};
use std::{cell::RefCell, rc::Rc};
mod ui {
    slint::include_modules!();
}
fn local(date: &str, time: &str) -> Option<LocalDateTime> {
    match (
        CivilDate::parse_iso(date),
        LocalTime::parse(time, TimePrecision::Minute),
    ) {
        (DraftValue::Parsed(date), DraftValue::Parsed(time)) => {
            Some(LocalDateTime::new(date, time))
        }
        _ => None,
    }
}
fn view(value: LocalDateTime) -> ui::LocalDateTimeView {
    ui::LocalDateTimeView {
        date: ui::CivilDateView {
            year: value.date.year(),
            month: value.date.month() as i32,
            day: value.date.day() as i32,
        },
        time: ui::LocalTimeView {
            hour: value.time.hour() as i32,
            minute: value.time.minute() as i32,
            second: value.time.second() as i32,
        },
    }
}
fn main() -> Result<(), slint::PlatformError> {
    let app = ui::App::new()?;
    slint_kit::init_form_validation!(ui, app);
    let initial = local("2026-10-17", "09:30").unwrap();
    app.set_accepted_local(view(initial));
    app.set_accepted("Not resolved yet".into());
    let state = Rc::new(RefCell::new((
        initial,
        None::<Instant>,
        None::<(ui::ZonedDateTimeDraft, Instant, Instant)>,
        TimeZoneId::new("America/Toronto").unwrap(),
    )));
    let weak = app.as_weak();
    let shared = state.clone();
    app.on_commit(move |draft| {
        if let Some(app) = weak.upgrade() {
            let Some(value) = local(&draft.date, &draft.time) else {
                app.set_error("Use a valid YYYY-MM-DD date and HH:mm time".into());
                return;
            };
            shared.borrow_mut().2 = None;
            let Ok(zone) = TimeZoneId::new(&draft.zone) else {
                app.set_resolution(ui::ZonedTimePresentation::InvalidZone);
                app.set_error("Use an IANA zone such as America/Toronto".into());
                return;
            };
            match zone.resolve(value) {
                ZonedResolution::Unique(instant) => {
                    let mut state = shared.borrow_mut();
                    state.0 = value;
                    state.1 = Some(instant);
                    state.3 = zone;
                    app.set_accepted_local(view(value));
                    app.set_resolution(ui::ZonedTimePresentation::Unique);
                    app.set_error("".into());
                    app.set_accepted(
                        format!("{} seconds since Unix epoch", instant.unix_seconds()).into(),
                    );
                }
                ZonedResolution::Nonexistent => {
                    app.set_resolution(ui::ZonedTimePresentation::Nonexistent);
                    app.set_error("This local time does not exist; choose another time".into());
                }
                ZonedResolution::Ambiguous { earlier, later } => {
                    shared.borrow_mut().2 = Some((draft, earlier, later));
                    app.set_resolution(ui::ZonedTimePresentation::Ambiguous);
                    app.set_error("Choose the earlier or later occurrence explicitly".into());
                }
            }
        }
    });
    let weak = app.as_weak();
    let shared = state.clone();
    app.on_edit(move || {
        if let Some(app) = weak.upgrade() {
            app.set_error("".into());
            shared.borrow_mut().2 = None;
            app.set_resolution(ui::ZonedTimePresentation::Unresolved);
        }
    });
    let weak = app.as_weak();
    let shared = state.clone();
    app.on_cancel(move || {
        if let Some(app) = weak.upgrade() {
            let mut state = shared.borrow_mut();
            state.2 = None;
            app.set_zone_text(state.3.name().into());
            app.set_date_text(state.0.date.iso_string().into());
            app.set_time_text(
                state
                    .0
                    .time
                    .format(
                        TimePrecision::Minute,
                        slint_kit::ClockFormat::TwentyFourHour,
                    )
                    .into(),
            );
            app.set_resolution(ui::ZonedTimePresentation::Unresolved);
            app.set_error("".into());
        }
    });
    let weak = app.as_weak();
    let shared = state.clone();
    app.on_choose(move |choice| {
        if let Some(app) = weak.upgrade() {
            let mut state = shared.borrow_mut();
            let Some((draft, earlier, later)) = state.2.take() else {
                return;
            };
            if draft.date != app.get_date_text()
                || draft.time != app.get_time_text()
                || draft.zone != app.get_zone_text()
            {
                return;
            }
            let instant = if choice == ui::AmbiguousTimeChoice::Earlier {
                earlier
            } else {
                later
            };
            state.1 = Some(instant);
            state.3 = TimeZoneId::new(&draft.zone).unwrap();
            state.0 = local(&draft.date, &draft.time).unwrap();
            app.set_accepted_local(view(state.0));
            app.set_resolution(ui::ZonedTimePresentation::Unique);
            app.set_error("".into());
            app.set_accepted(format!("{} seconds since Unix epoch", instant.unix_seconds()).into());
        }
    });
    app.run()
}

Cargo.toml and build.rs

Cargo.toml

[package]
name = "slint-kit-zoned-date-time-field-example"
version = "0.1.0"
edition = "2024"
rust-version = "1.92"
license = "GPL-3.0-or-later"
publish = false

[dependencies]
slint-kit = "=0.3.0"
slint = { version = "=1.18.1", default-features = false, features = ["std", "compat-1-18", "backend-winit", "renderer-skia", "accessibility"] }

[build-dependencies]
slint-kit-build = "=0.3.0"

[workspace]

build.rs

fn main() {
    slint_kit_build::compile("ui/app.slint").expect("compile component example");
}

Files: Cargo.toml · build.rs · ui/app.slint · src/main.rs

Parse YYYY-MM-DD and 24-hour HH:MM, validate TimeZoneId, then resolve through the typed time-zone adapter.

A nonexistent time is rejected. A repeated time requires an explicit earlier/later choice; editing invalidates that pending choice.

Choose between built-in options and custom composition

Shared options

Visual composition

The file below produces the screenshot. For controller-driven components, it shows the displayed state without supplying the Rust wiring. Create a project.

Show the screenshot source
Local date and time resolved in an IANA time zone.
Native screenshot of the same example, rendered with Skia.
// SPDX-License-Identifier: GPL-3.0-or-later
// Display projections are fixtures. Validate accepted domain values in Rust.
import { KitTheme, KitThemes, KitZonedDateTimeField, ZonedTimePresentation } from "@kit/kit.slint";

export component Example inherits Window {
    title: "KitZonedDateTimeField example";
    preferred-width: 480px;
    preferred-height: 390px;
    max-width: 100000px;
    max-height: 100000px;
    background: root.theme.canvas;
    in-out property <KitTheme> theme: KitThemes.light;
    in-out property <string> status;
    VerticalLayout {
        padding: 24px; spacing: 16px; alignment: start;
        Text { text: "Zoned Date Time Field"; color: root.theme.text-primary; font-size: 20px * root.theme.text-scale; font-weight: 600; }
        KitZonedDateTimeField { theme: root.theme; label: "Meeting"; date-text: "2026-10-17"; time-text: "09:30"; zone-text: "America/Toronto"; resolution: ZonedTimePresentation.unique; resolution-message: "UTC−04:00"; }
        if root.status != "": Text { text: root.status; color: root.theme.text-secondary; font-size: 12px * root.theme.text-scale; wrap: word-wrap; }
    }
}

Download this example

Public API

Inherits Rectangle.

Properties

Name Type Direction
required bool in
min string in
max string in
step string in
custom-validity string in-out
invalid-zone-message string in
validity KitValidityState out
will-validate bool out
validation-message string out
theme KitTheme in
control-size ControlSize in
access FieldAccess in
accepted-local LocalDateTimeView in
clock-format ClockFormat in
precision TimePrecision in
label string in
date-placeholder string in
date-description string in
time-description string in
date-label string in
time-label string in
zone-label string in
zone-placeholder string in
zone-description string in
earlier-label string in
later-label string in
date-error string in
time-error string in
zone-error string in
resolution-message string in
resolution ZonedTimePresentation in
date-text string in-out
time-text string in-out
zone-text string in-out
editor-focused bool out

Types and variants

Type Declaration
enum AmbiguousTimeChoice earlier, later
struct CivilDateView year: int, month: int, day: int
enum ClockFormat twenty-four-hour, twelve-hour
enum ControlSize compact, medium, comfortable
enum FieldAccess editable, read-only, disabled
struct KitValidityState valid: bool, value-missing: bool, type-mismatch: bool, pattern-mismatch: bool, too-long: bool, too-short: bool, range-underflow: bool, range-overflow: bool, step-mismatch: bool, bad-input: bool, custom-error: bool,
struct LocalDateTimeView date: CivilDateView, time: LocalTimeView
struct LocalTimeView hour: int, minute: int, second: int
enum TimePrecision minute, second
struct ZonedDateTimeDraft date: string, time: string, zone: string
enum ZonedTimePresentation unresolved, unique, ambiguous, nonexistent, invalid-zone

Callbacks

Functions

See also