# Period
A human-friendly date and time library for Rust.
[](https://www.rust-lang.org)
[](LICENSE)
[](https://github.com/hallucinations/period/actions/workflows/ci.yml)
[](https://crates.io/crates/period)
---
## 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.
```rust
use period::{today, yesterday, days_ago, weeks_from_now, months_ago, humanize};
use period::now;
let today = today();
let yesterday = yesterday();
let past = days_ago(3)?;
let future = weeks_from_now(2)?;
let earlier = months_ago(6)?;
let label = humanize(now()); // "just now"
```
---
## Installation
Add Period to your `Cargo.toml`:
```toml
[dependencies]
period = "0.1"
```
---
## API Reference
### Current time
```rust
use period::{now, today};
let datetime = now(); // DateTime<Local>
let date = today(); // NaiveDate
```
### Named days
```rust
use period::{yesterday, tomorrow};
let yesterday = yesterday(); // NaiveDate
let tomorrow = tomorrow(); // NaiveDate
```
### Relative dates
All relative functions return `Result<NaiveDate, PeriodError>` and reject negative values with a helpful error message.
```rust
use period::{days_ago, days_from_now, weeks_ago, weeks_from_now,
months_ago, months_from_now, years_ago, years_from_now};
let d = days_ago(7)?; // 7 days in the past
let d = days_from_now(30)?; // 30 days in the future
let d = weeks_ago(2)?; // 2 weeks in the past
let d = weeks_from_now(4)?; // 4 weeks in the future
let d = months_ago(3)?; // 3 calendar months in the past
let d = months_from_now(6)?; // 6 calendar months in the future
let d = years_ago(1)?; // 1 year in the past
let d = years_from_now(5)?; // 5 years in the future
```
### Relative times
These return `Result<DateTime<Local>, PeriodError>`.
```rust
use period::{seconds_ago, seconds_from_now, minutes_ago, minutes_from_now,
hours_ago, hours_from_now};
let t = seconds_ago(30)?; // 30 seconds in the past
let t = seconds_from_now(10)?; // 10 seconds in the future
let t = minutes_ago(15)?; // 15 minutes in the past
let t = minutes_from_now(45)?; // 45 minutes in the future
let t = hours_ago(2)?; // 2 hours in the past
let t = hours_from_now(8)?; // 8 hours in the future
```
### Humanize
Convert any `DateTime<Local>` into a human-readable relative string.
```rust
use period::humanize;
use chrono::Local;
let dt = Local::now() - chrono::Duration::minutes(35);
println!("{}", humanize(dt)); // "35 minutes ago"
let dt = Local::now() + chrono::Duration::hours(2);
println!("{}", humanize(dt)); // "in 2 hours"
```
| < 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"` |
---
## Error handling
All fallible functions return `Result<T, PeriodError>`. The error type is inspectable — you can match on specific variants:
```rust
use period::{days_ago, PeriodError};
match days_ago(-5) {
Err(PeriodError::NegativeValue { suggestion, value, .. }) => {
println!("Did you mean {}({})?", suggestion, value);
}
Err(PeriodError::Overflow { unit, value }) => {
println!("{} value {} is too large", unit, value);
}
Ok(date) => println!("{}", date),
}
```
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 str` fields
- **Composable** — `PeriodError` implements `std::error::Error` for easy integration with the broader Rust ecosystem
---
## License
MIT