Dates and times
Date schemas parse strings into the types of a date library. Each library is an additive feature with its own module:
| Feature | Module | date() output | datetime() output | time() output |
|---|---|---|---|---|
chrono | vld::chrono | chrono::NaiveDate | chrono::DateTime<Utc> | chrono::NaiveTime |
jiff | vld::jiff | jiff::civil::Date | jiff::Timestamp | jiff::civil::Time |
time | vld::time | time::Date | time::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.
vld::chrono::date()) so they keep compiling when an application enables another backend.Grammar
All backends share one strict grammar:
| Schema | Accepts | Examples |
|---|---|---|
date() | YYYY-MM-DD with a real calendar day | 2024-02-29 |
datetime() | YYYY-MM-DDTHH:MM:SS[.fraction] plus Z or ±HH:MM; T / t and Z / z | 2024-01-15T10:30:00Z, 2024-01-15T10:30:00.123+03:00 |
time() | HH:MM:SS[.fraction], no offset | 09: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.
| Method | Effect |
|---|---|
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
| Method | date() | datetime() | time() |
|---|---|---|---|
min(&str) / max(&str) | "YYYY-MM-DD" | RFC 3339 with offset | "HH:MM:SS" |
| typed bounds | min_date / max_date | min_datetime / max_datetime | min_time / max_time |
past() / future() | before / after today (UTC) | before / after now | — |
past_msg(..) / future_msg(..) | with a custom message | with 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.