https://github.com/sam0x17/timelang
A Rust-based DSL (Domain Specific Language) and grammar for parsing and rendering human-readable date/time and duration values.
https://github.com/sam0x17/timelang
Last synced: 8 months ago
JSON representation
A Rust-based DSL (Domain Specific Language) and grammar for parsing and rendering human-readable date/time and duration values.
- Host: GitHub
- URL: https://github.com/sam0x17/timelang
- Owner: sam0x17
- License: mit
- Created: 2023-11-20T14:19:27.000Z (about 2 years ago)
- Default Branch: main
- Last Pushed: 2023-12-05T07:12:01.000Z (about 2 years ago)
- Last Synced: 2025-03-14T14:21:16.371Z (10 months ago)
- Language: Rust
- Size: 67.4 KB
- Stars: 2
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# 🕗 Timelang
[](https://crates.io/crates/timelang)
[](https://docs.rs/timelang/latest/timelang/)
[](https://github.com/sam0x17/timelang/actions/workflows/ci.yaml?query=branch%3Amain)
[](https://github.com/sam0x17/timelang/blob/main/LICENSE)
Timelang is a simple DSL (Domain Specific Language) for representing human-readable
time-related expressions including specific date/times, relative expressions like "3 hours from
now", time ranges, and durations.
## Getting Started
To use timelang, you should take a look at [TimeExpression], which is the top-level entry point
of the AST, or some of the more specific types like [Duration], [PointInTime], and [TimeRange].
All nodes in timelang impl [FromStr] as well as [syn::parse::Parse] which is used for the
internal parsing logic. The standard [Display] impl is used on all node types as the preferred
means of outputting them to a string.
Note that for the moment, only years, months, weeks, days, hours, and minutes are supported in
timelang, but seconds and more might be added later. Generally better than minute resolution is
not needed in many of the common use-cases for timelang.
## Examples
The following are all examples of valid expressions in timelang:
- `now`
- `tomorrow`
- `next tuesday`
- `day after tomorrow`
- `the day before yesterday`
- `20/4/2021`
- `11:21 AM`
- `15/6/2022 at 3:58 PM`
- `2 hours, 37 minutes`
- `5 years, 2 months, 3 weeks and 11 minutes`
- `7 days ago`
- `2 years and 10 minutes from now`
- `5 days, 3 weeks, 6 minutes after 15/4/2025 at 9:27 AM`
- `from 1/1/2023 at 14:07 to 15/1/2023`
- `from 19/3/2024 at 10:07 AM to 3 months 2 days after 3/9/2027 at 5:27 PM`
- `2 days and 14 hours after the day after tomorrow`
- `11 days before the day before yesterday`
- `5 days after next tuesday`
Specific Date:
```rust
use timelang::*;
assert_eq!(
"20/4/2021".parse::().unwrap(),
TimeExpression::Specific(PointInTime::Absolute(AbsoluteTime::Date(Date(
Month::April,
DayOfMonth(20),
Year(2021)
))))
);
```
Specific DateTime:
```rust
use timelang::*;
assert_eq!(
"15/6/2022 at 14:00".parse::().unwrap(),
AbsoluteTime::DateTime(DateTime(
Date(Month::June, DayOfMonth(15), Year(2022)),
Time(Hour::Hour24(14), Minute(0))
))
);
```
Time Range:
```rust
use timelang::*;
assert_eq!(
"from 1/1/2023 to 15/1/2023"
.parse::()
.unwrap(),
TimeExpression::Range(TimeRange(
PointInTime::Absolute(AbsoluteTime::Date(Date(
Month::January,
DayOfMonth(1),
Year(2023)
))),
PointInTime::Absolute(AbsoluteTime::Date(Date(
Month::January,
DayOfMonth(15),
Year(2023)
)))
))
);
```
Duration (multiple units with comma):
```rust
use timelang::*;
assert_eq!(
"2 hours, 30 minutes".parse::().unwrap(),
TimeExpression::Duration(Duration {
hours: Number(2),
minutes: Number(30),
days: Number(0),
weeks: Number(0),
months: Number(0),
years: Number(0)
})
);
```
Duration (multiple units with `and`):
```rust
use timelang::*;
assert_eq!(
"1 year and 6 months".parse::().unwrap(),
TimeExpression::Duration(Duration {
years: Number(1),
months: Number(6),
days: Number(0),
weeks: Number(0),
hours: Number(0),
minutes: Number(0)
})
);
```
Relative Time (using `ago`):
```rust
use timelang::*;
assert_eq!(
"3 days ago".parse::().unwrap(),
TimeExpression::Specific(PointInTime::Relative(RelativeTime::Directional {
duration: Duration {
days: Number(3),
minutes: Number(0),
hours: Number(0),
weeks: Number(0),
months: Number(0),
years: Number(0)
},
dir: TimeDirection::Ago
}))
);
```
Relative Time (using `from now`):
```rust
use timelang::*;
assert_eq!(
"5 days, 10 hours, and 35 minutes from now"
.parse::()
.unwrap(),
TimeExpression::Specific(PointInTime::Relative(RelativeTime::Directional {
duration: Duration {
minutes: Number(35),
hours: Number(10),
days: Number(5),
weeks: Number(0),
months: Number(0),
years: Number(0)
},
dir: TimeDirection::FromNow
}))
);
```
Relative Time (`after` a specific date):
```rust
use timelang::*;
assert_eq!(
"2 hours, 3 minutes after 10/10/2022"
.parse::()
.unwrap(),
TimeExpression::Specific(PointInTime::Relative(RelativeTime::Directional {
duration: Duration {
hours: Number(2),
minutes: Number(3),
days: Number(0),
weeks: Number(0),
months: Number(0),
years: Number(0)
},
dir: TimeDirection::AfterAbsolute(AbsoluteTime::Date(Date(
Month::October,
DayOfMonth(10),
Year(2022)
)))
}))
);
```
Relative Time (`before` a specific date/time):
```rust
use timelang::*;
assert_eq!(
"1 day before 31/12/2023 at 11:13 PM"
.parse::()
.unwrap(),
TimeExpression::Specific(PointInTime::Relative(RelativeTime::Directional {
duration: Duration {
days: Number(1),
minutes: Number(0),
hours: Number(0),
weeks: Number(0),
months: Number(0),
years: Number(0)
},
dir: TimeDirection::BeforeAbsolute(AbsoluteTime::DateTime(DateTime(
Date(Month::December, DayOfMonth(31), Year(2023)),
Time(Hour::Hour12(11, AmPm::PM), Minute(13))
)))
}))
);
```
Time Range (with specific date/times):
```rust
use timelang::*;
assert_eq!(
"from 1/1/2024 at 10:00 to 2/1/2024 at 15:30"
.parse::()
.unwrap(),
TimeExpression::Range(TimeRange(
PointInTime::Absolute(AbsoluteTime::DateTime(DateTime(
Date(Month::January, DayOfMonth(1), Year(2024)),
Time(Hour::Hour24(10), Minute(0))
))),
PointInTime::Absolute(AbsoluteTime::DateTime(DateTime(
Date(Month::January, DayOfMonth(2), Year(2024)),
Time(Hour::Hour24(15), Minute(30))
)))
))
);
```
Relative Time:
```rust
use timelang::*;
assert_eq!("now".parse::().unwrap(), RelativeTime::Named(NamedRelativeTime::Now));
assert_eq!(
"tomorrow".parse::().unwrap(),
RelativeTime::Named(NamedRelativeTime::Tomorrow)
);
assert_eq!(
"yesterday".parse::().unwrap(),
RelativeTime::Named(NamedRelativeTime::Yesterday)
);
assert_eq!(
"day before yesterday".parse::().unwrap(),
RelativeTime::Named(NamedRelativeTime::DayBeforeYesterday)
);
// note the optional `the`
assert_eq!(
"the day after tomorrow".parse::().unwrap(),
RelativeTime::Named(NamedRelativeTime::DayAfterTomorrow)
);
assert_eq!(
"next tuesday".parse::().unwrap(),
RelativeTime::Next(RelativeTimeUnit::Tuesday)
);
assert_eq!(
"last wednesday".parse::().unwrap(),
RelativeTime::Last(RelativeTimeUnit::Wednesday)
);
assert_eq!(
"3 days before yesterday".parse::().unwrap(),
RelativeTime::Directional {
duration: Duration {
minutes: Number(0),
hours: Number(0),
days: Number(3),
weeks: Number(0),
months: Number(0),
years: Number(0)
},
dir: TimeDirection::BeforeNamed(NamedRelativeTime::Yesterday)
}
);
assert_eq!(
"2 days and 14 hours after the day after tomorrow".parse::().unwrap(),
RelativeTime::Directional {
duration: Duration {
minutes: Number(0),
hours: Number(14),
days: Number(2),
weeks: Number(0),
months: Number(0),
years: Number(0)
},
dir: TimeDirection::AfterNamed(NamedRelativeTime::DayAfterTomorrow)
}
);
assert_eq!(
"2 weeks before last sunday".parse::().unwrap(),
RelativeTime::Directional {
duration: Duration {
minutes: Number(0),
hours: Number(0),
days: Number(0),
weeks: Number(2),
months: Number(0),
years: Number(0)
},
dir: TimeDirection::BeforeLast(RelativeTimeUnit::Sunday)
}
);
assert_eq!(
"3 years, 2 weeks after next thursday".parse::().unwrap(),
RelativeTime::Directional {
duration: Duration {
minutes: Number(0),
hours: Number(0),
days: Number(0),
weeks: Number(2),
months: Number(0),
years: Number(3)
},
dir: TimeDirection::AfterNext(RelativeTimeUnit::Thursday)
}
);
```
## Notes
* At the moment [syn](https://crates.io/crates/syn) is used for parsing. This will likely be
swapped out for a TBD parsing crate, but it was easy to quickly get this off the ground using
syn. Whatever new crate we use will hopefully allow us to make timelang compatible with no
std.
* Timelang is unambiguous, meaning there is exactly one tree representation for all possible
timelang sentences. If you can come up with an ambiguous sentence, please let us know by
submitting a GitHub issue!