Period
A human-friendly date and time library for Rust.
Note: Period is a learning project, built to explore the Rust ecosystem and the process of authoring Ruby extensions from Rust. Its public API is intentionally expressive, but the implementation is a thin layer over chrono with no additional performance guarantees, hardening, or production-level validation. If you need reliable, well-tested date and time handling in production, use
chronodirectly or a library with a longer track record. That said, if the API style appeals to you, you are welcome to use it — just do so with that context in mind.
Overview
Period provides an expressive, readable API for common date and time operations. Instead of wrestling with offsets and arithmetic, you write code that reads like English.
Every relative function returns a Relative value — a thin wrapper around DateTime<Local> — which you convert to whichever representation you need:
use ;
let today = today; // NaiveDate
let yesterday = yesterday; // NaiveDate
let past = days_ago?.as_date; // NaiveDate
let future = weeks_from_now?.as_date; // NaiveDate
let earlier = months_ago?.as_datetime; // DateTime<Local>
let time_of_day = hours_ago?.as_time; // NaiveTime (time-of-day only)
let label = humanize; // "just now"
Installation
Add Period to your Cargo.toml:
[]
= "0.6.0-alpha.1"
This is a pre-release. Pin the exact version — no stability guarantees are made between alpha releases.
API Reference
Current time
use ;
let datetime = now; // DateTime<Local>
let date = today; // NaiveDate
Named days
use ;
let yesterday = yesterday; // NaiveDate
let tomorrow = tomorrow; // NaiveDate
Relative values
All relative functions return Result<Relative, PeriodError>. Call .as_date(), .as_datetime(), or .as_time() on the result to extract the representation you need.
use ;
// Dates
let d = days_ago?.as_date; // NaiveDate — 7 days in the past
let d = days_from_now?.as_date; // NaiveDate — 30 days in the future
let d = weeks_ago?.as_date; // NaiveDate — 2 weeks in the past
let d = weeks_from_now?.as_date; // NaiveDate — 4 weeks in the future
let d = months_ago?.as_date; // NaiveDate — 3 calendar months in the past
let d = months_from_now?.as_date; // NaiveDate — 6 calendar months in the future
let d = years_ago?.as_date; // NaiveDate — 1 year in the past
let d = years_from_now?.as_date; // NaiveDate — 5 years in the future
// DateTimes
let t = seconds_ago?.as_datetime; // DateTime<Local> — 30 seconds ago
let t = seconds_from_now?.as_datetime; // DateTime<Local> — 10 seconds from now
let t = minutes_ago?.as_datetime; // DateTime<Local> — 15 minutes ago
let t = minutes_from_now?.as_datetime; // DateTime<Local> — 45 minutes from now
let t = hours_ago?.as_datetime; // DateTime<Local> — 2 hours ago
let t = hours_from_now?.as_datetime; // DateTime<Local> — 8 hours from now
// Time-of-day only
let t = hours_ago?.as_time; // NaiveTime
The Relative type
Relative is a Copy wrapper around DateTime<Local> with three conversion methods:
| Method | Returns | Description |
|---|---|---|
.as_datetime() |
DateTime<Local> |
Full date and time with timezone |
.as_date() |
NaiveDate |
Calendar date only |
.as_time() |
NaiveTime |
Time-of-day only |
From<Relative> is implemented for all three types, so you can use .into() or type inference in assignments:
use days_ago;
use NaiveDate;
let date: NaiveDate = days_ago?.into;
Humanize
Convert any DateTime<Local> into a human-readable relative string.
use humanize;
use Local;
let dt = now - minutes;
println!; // "35 minutes ago"
let dt = now + hours;
println!; // "in 2 hours"
You can also pass the inner datetime from a Relative value:
use ;
let label = humanize; // "3 hours ago"
| Absolute delta | Past | Future |
|---|---|---|
| < 30 s | "just now" |
"just now" |
| < 90 s | "a minute ago" |
"in a minute" |
| < 45 min | "N minutes ago" |
"in N minutes" |
| < 90 min | "an hour ago" |
"in an hour" |
| < 22 h | "N hours ago" |
"in N hours" |
| < 36 h | "yesterday" |
"tomorrow" |
| < 25 days | "N days ago" |
"in N days" |
| < 45 days | "a month ago" |
"in a month" |
| < 10 months | "N months ago" |
"in N months" |
| < 18 months | "a year ago" |
"in a year" |
| ≥ 18 months | "N years ago" |
"in N years" |
Note: The
< 90 sbucket uses the article form ("a minute ago"). Values in the< 45 minbucket that round ton = 1produce"1 minute ago", so the transition at 90 s is"a minute ago"→"1 minute ago"→"2 minutes ago".Note:
"yesterday"/"tomorrow"are triggered by elapsed seconds (22–35 h), not by calendar-day boundaries. Months are approximated as 30 days.
Formatting
Convert dates and datetimes to common string formats.
use ;
use ;
let date = from_ymd_opt.unwrap;
to_date_string; // "2026-02-22"
to_long_date; // "February 22, 2026"
to_short_date; // "Feb 22, 2026"
let tz = east_opt.unwrap; // UTC+5:30
let dt = tz.with_ymd_and_hms.single.unwrap;
to_iso8601; // "2026-02-22T14:30:00+05:30"
to_rfc2822; // "Sun, 22 Feb 2026 14:30:00 +0530"
to_iso8601 and to_rfc2822 accept any timezone — Local, Utc, FixedOffset, etc.
Note:
to_long_dateandto_short_datedo not pad single-digit days —"February 5, 2026"and"Feb 5, 2026"respectively.
Error handling
All fallible functions return Result<_, PeriodError>. The error type is inspectable — you can match on specific variants:
use ;
match days_ago
Passing a negative value produces a descriptive error with a suggestion:
days must be positive. Did you mean days_from_now(5)?
PeriodError implements both std::fmt::Display and std::error::Error, making it compatible with ? in functions returning Box<dyn Error> or anyhow::Error.
Design principles
- Human-readable — function names read like natural language
- Explicit over implicit — negative values are rejected with actionable error messages rather than silently producing unexpected results
- Zero heap allocation on the error path — error variants use
&'static strfields - Composable —
PeriodErrorimplementsstd::error::Errorfor easy integration with the broader Rust ecosystem
License
MIT