Schemas

Dates and times

Typed dates, datetimes and times of day with chrono, jiff or time; strict grammar, offsets, ranges and past/future checks.

Date schemas parse strings into the types of a date library. Each library is an additive feature with its own module:

FeatureModuledate() outputdatetime() outputtime() output
chronovld::chronochrono::NaiveDatechrono::DateTime<Utc>chrono::NaiveTime
jiffvld::jiffjiff::civil::Datejiff::Timestampjiff::civil::Time
timevld::timetime::Datetime::OffsetDateTime (UTC)time::Time

Each module exports date(), datetime(), time() and the ZDate, ZDateTime, ZTime aliases.

// features = ["chrono"]
use vld::prelude::*;

let birthday = vld::chrono::date().min("1900-01-01").past();
let d = birthday.parse(r#""1990-06-15""#).unwrap();
assert_eq!(d, chrono::NaiveDate::from_ymd_opt(1990, 6, 15).unwrap());

Shortcuts with one backend

When exactly one date feature is enabled, vld::date(), vld::datetime(), vld::time_of_day() and the prelude types ZDate, ZDateTime, ZTime point to it. They disappear as soon as a second backend is enabled, for example by another crate in your dependency tree through feature unification.

Libraries should always call the backend module (vld::chrono::date()) so they keep compiling when an application enables another backend.

Grammar

All backends share one strict grammar:

SchemaAcceptsExamples
date()YYYY-MM-DD with a real calendar day2024-02-29
datetime()YYYY-MM-DDTHH:MM:SS[.fraction] plus Z or ±HH:MM; T / t and Z / z2024-01-15T10:30:00Z, 2024-01-15T10:30:00.123+03:00
time()HH:MM:SS[.fraction], no offset09:30:00, 23:59:59.5

Seconds are required in datetimes and times. The output of datetime() is always normalized to UTC.

Naive datetimes and offsets

By default datetime() also accepts naive input without an offset (2024-01-15T10:30:00) and reads it as UTC.

MethodEffect
naive_allowed(false) / with_timezone_only()require an offset (RFC 3339)
naive_timezone_offset(secs)read naive input at this offset instead of UTC; panics if abs(secs) >= 86400
timezone_offset_only(secs)require an explicit offset equal to secs (naive input is rejected too); invalid_timezone_offset otherwise
// Moscow clocks: naive input is UTC+3, output is UTC.
let local = vld::chrono::datetime().naive_timezone_offset(3 * 3600);
let utc = local.parse(r#""2024-01-15T10:30:00""#).unwrap();
assert_eq!(utc.to_rfc3339(), "2024-01-15T07:30:00+00:00");

let strict = vld::chrono::datetime().with_timezone_only();
assert!(strict.parse(r#""2024-01-15T10:30:00""#).is_err());

Ranges, past and future

Methoddate()datetime()time()
min(&str) / max(&str)"YYYY-MM-DD"RFC 3339 with offset"HH:MM:SS"
typed boundsmin_date / max_datemin_datetime / max_datetimemin_time / max_time
past() / future()before / after today (UTC)before / after now—
past_msg(..) / future_msg(..)with a custom messagewith a custom message—

The string forms of min / max panic on an invalid literal, the same way a bad regex would: they are meant for constants. Out-of-range values report too_small / too_big with origin date, datetime or time; past/future failures report not_in_past / not_in_future.

let opening = vld::chrono::time().min("09:00:00").max("18:00:00");
assert!(opening.parse(r#""12:15:00""#).is_ok());
assert!(opening.parse(r#""20:00:00""#).is_err());

Dates as plain strings

If you want to keep the value a String and only check its shape, use the string formats iso_date(), iso_datetime() and iso_time(). iso_datetime() uses the same grammar as datetime().

JSON Schema

date() emits {"type": "string", "format": "date"}. datetime() emits format: date-time when an offset is required; by default (naive input allowed) it emits an anyOf of date-time and a pattern for the naive form. time() emits a pattern (format: time would require an offset). min / max become formatMinimum / formatMaximum; past() / future() become x-past / x-future.