Skip to content

0k/kal-time

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

kal-time Repository

Overview

kal-time is a tiny Rust library for parsing human-friendly time and timespan expressions into chrono::DateTime<FixedOffset>. It supports relative parsing against a caller-provided reference moment and gracefully fills missing segments (date, time, or offset) using either zeroes or the supplied reference.

Use it when you want a CLI or automation tool to accept terse inputs such as "9h..10h" for “today between 9 and 10” or "30m" to fix the minute field to 00:30 while reusing the current day/hour, without forcing users to type full ISO timestamps.

This is more a tiny piece of code I use between many different project. It has no ambition to become anything big, and the quality is alpha level.

Supported Formats

The library accepts a variety of date/time formats, listed here from most specific to least specific. Formats are tried in order until one matches.

All examples below use this reference: 2024-03-20T11:45:30+05:00

When the input lacks an explicit timezone, the offset from the reference is used. This makes parsing deterministic regardless of the system’s local timezone.

ISO 8601 formats (with timezone)

Full ISO 8601 compliance with explicit timezone offset or UTC marker. The provided timezone is preserved in the output (reference ignored):

2025-01-12T14:30:00+01:00   => 2025-01-12 14:30:00 +01:00
2025-01-12T14:30:00Z        => 2025-01-12 14:30:00 +00:00
2025-01-12T14:30+01:00      => 2025-01-12 14:30:00 +01:00
2025-01-12T14:30Z           => 2025-01-12 14:30:00 +00:00
2025-01-12 14:30:00+01:00   => 2025-01-12 14:30:00 +01:00
2025-01-12 14:30:00Z        => 2025-01-12 14:30:00 +00:00

ISO 8601 formats (no timezone)

Standard ISO formats without timezone—offset comes from reference (+05:00):

2025-01-12T14:30:00         => 2025-01-12 14:30:00 +05:00
2025-01-12T14:30            => 2025-01-12 14:30:00 +05:00
2025-01-12 14:30:00         => 2025-01-12 14:30:00 +05:00
2025-01-12 14:30            => 2025-01-12 14:30:00 +05:00
2025-01-12                  => 2025-01-12 00:00:00 +05:00

Partial date formats

Missing fields filled from reference (year=2024, month=03, day=20, offset=+05:00):

01-12                       => 2024-01-12 00:00:00 +05:00  (year, offset from ref)
01/12                       => 2024-01-12 00:00:00 +05:00  (year, offset from ref)
01-12 14:30                 => 2024-01-12 14:30:00 +05:00  (year, offset from ref)
01-12 14:30:45              => 2024-01-12 14:30:45 +05:00  (year, offset from ref)
15 14:30                    => 2024-03-15 14:30:00 +05:00  (year+month, offset from ref)

Time-only formats

Date (2024-03-20) and offset (+05:00) come from reference:

14:30:59                    => 2024-03-20 14:30:59 +05:00
14:30                       => 2024-03-20 14:30:00 +05:00

Terse/human-friendly formats

Missing fields filled from reference (hour=11, minute=45, offset=+05:00):

14h30                       => 2024-03-20 14:30:00 +05:00  (date, offset from ref)
9h                          => 2024-03-20 09:00:00 +05:00  (date, offset from ref)
30m                         => 2024-03-20 11:30:00 +05:00  (date+hour, offset from ref)
15 14h30                    => 2024-03-15 14:30:00 +05:00  (year+month, offset from ref)
15 9h                       => 2024-03-15 09:00:00 +05:00  (year+month, offset from ref)

Unix timestamp

Prefix with @ for epoch seconds (always interpreted as UTC, reference ignored):

@1736692200                 => 2025-01-12 14:30:00 +00:00

Natural language (via chrono-english)

English expressions for relative dates and times (offset +05:00 from ref):

yesterday                   => 2024-03-19 11:45:30 +05:00  (1 day before ref)
tomorrow                    => 2024-03-21 11:45:30 +05:00  (1 day after ref)
friday                      => 2024-03-22 00:00:00 +05:00  (this friday)
last friday                 => 2024-03-15 00:00:00 +05:00
next monday                 => 2024-04-01 00:00:00 +05:00
2 days ago                  => 2024-03-18 11:45:30 +05:00
3 hours ago                 => 2024-03-20 08:45:30 +05:00
1 hour                      => 2024-03-20 12:45:30 +05:00  (1 hour from ref)
friday 8pm                  => 2024-03-22 20:00:00 +05:00
april 1                     => 2024-04-01 00:00:00 +05:00
1 april                     => 2024-04-01 00:00:00 +05:00

Shorthand intervals are also supported: 3h, 2d, 15m, 1y.

Reference Types and Offset Behavior

The library distinguishes between two types of references:

Fixed offset reference (DateTime<FixedOffset> or DateTime<Utc>)

When the reference has an explicit fixed offset, that offset is used for all parsed times (unless the input itself specifies a timezone). No DST checking is performed since the offset is explicit.

use chrono::FixedOffset;
use kal_time::parse_with_reference;

let offset = FixedOffset::east_opt(5 * 3600).unwrap(); // +05:00
let reference = offset.with_ymd_and_hms(2024, 3, 20, 12, 0, 0).unwrap();
let parsed = parse_with_reference("2025-07-15 14:30", &reference)?;
// => 2025-07-15 14:30:00 +05:00 (offset from reference, no DST check)

Local reference (DateTime<Local>)

When the reference is a DateTime<Local>, the library uses the system timezone (TZ environment variable) to determine the correct offset for the target date. This enables DST-aware parsing.

use chrono::Local;
use kal_time::parse_with_reference;

let reference = Local::now();
let parsed = parse_with_reference("2025-07-15 14:30", &reference)?;
// => 2025-07-15 14:30:00 +02:00 (in Europe/Paris summer time)

DST Transition Handling

When using a DateTime<Local> reference (no explicit offset), the library checks for problematic times during Daylight Saving Time transitions. Two cases are detected:

Ambiguous times (fall back)

In regions that observe DST, clocks “fall back” once a year, causing a range of local times to occur twice.

For example, in Europe/Paris on the last Sunday of October, clocks move from 03:00 CEST (+02:00) back to 02:00 CET (+01:00). This means times between 02:00:00 and 03:00:00 are ambiguous—they could refer to either the CEST or CET instance.

$ TZ=Europe/Paris kt-parse time "2025-10-26 02:30:00"
Error: Ambiguous time during DST transition: 2025-10-26 02:30:00 could be
       2025-10-26 02:30:00 +0200 or 2025-10-26 02:30:00 +0100

Non-existent times (spring forward)

Conversely, clocks “spring forward” once a year, skipping a range of local times entirely.

For example, in Europe/Paris on the last Sunday of March, clocks jump from 02:00 CET (+01:00) to 03:00 CEST (+02:00). Times between 02:00:00 and 03:00:00 simply do not exist.

$ TZ=Europe/Paris kt-parse time "2025-03-30 02:30:00"
Error: Non-existent time during DST transition: 2025-03-30 02:30:00 does not exist
       (clocks skip forward)

Resolving DST issues

To resolve ambiguity or specify a non-existent time, use an explicit timezone offset:

$ kt-parse time "2025-10-26T02:30:00+02:00"   # CEST (before fallback)
$ kt-parse time "2025-10-26T02:30:00+01:00"   # CET (after fallback)

Times outside the problematic windows parse normally:

$ TZ=Europe/Paris kt-parse time "2025-10-26 01:59:59"  # Before fall-back window
$ TZ=Europe/Paris kt-parse time "2025-10-26 03:00:01"  # After fall-back window
$ TZ=Europe/Paris kt-parse time "2025-03-30 01:59:59"  # Before spring-forward gap
$ TZ=Europe/Paris kt-parse time "2025-03-30 03:00:00"  # After spring-forward gap

Build & Test

  • cargo build — compile the library and surface warnings.
  • cargo test — execute unit tests embedded alongside the modules.
  • cargo fmt and cargo clippy — enforce formatting and linting prior to review.

Usage Examples

Each snippet shows how a public helper parses input and what kind of timestamp it produces.

Relative offset from a reference

Use parse_with_reference to interpret partial or relative expressions against a known moment.

use chrono::TimeZone;
use chrono::Utc;
use kal_time::parse_with_reference;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let reference = Utc.with_ymd_and_hms(2025, 10, 22, 9, 10, 11).unwrap();
    let parsed = parse_with_reference("30m", &reference)?;

    println!("{}", parsed);
    // => 2025-10-22 09:30:00 +00:00
    Ok(())
}

Parsing absolute time with local defaults

parse assumes the local clock when fields are missing; full timestamps stay in the caller’s local offset.

use kal_time::parse;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let parsed = parse("2025-10-22 14:30")?;

    println!("{}", parsed);
    // => 2025-10-22 14:30:00 +<local offset>
    Ok(())
}

UTC parsing shortcut

parse_utc mirrors parse but always anchors missing pieces to the current UTC reference.

use kal_time::parse_utc;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let parsed = parse_utc("9h")?;

    println!("{}", parsed);
    // => <today’s date> 09:00:00 +00:00
    Ok(())
}

Parsing timespans

parse_timespan expands a range like start..end into start/stop instants, defaulting to a 1-day window when no end is supplied.

use kal_time::parse_timespan;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let (start, stop) = parse_timespan("2025-10-20..2025-10-22 12:00")?;

    println!("start: {}", start);
    println!("stop:  {}", stop);
    // => start: 2025-10-20 00:00:00 +<local offset>
    // => stop:  2025-10-22 12:00:00 +<local offset>
    Ok(())
}

Command-line Utility

kt-parse is a thin wrapper around the library, useful in scripts and shell pipelines.

Run kt-parse --help for a quick usage reminder.

Parse a time with the current clock

$ kt-parse time 9h
1761104400 2025-10-22 09:00:00 +00:00

Parse a time with an explicit reference

You can supply a fully specified reference (RFC3339 or similar) when you need deterministic results regardless of the machine clock.

$ kt-parse time 30m 2025-10-22T09:10:11+00:00
1761105011 2025-10-22 09:30:11 +00:00

Parse a timespan

Timespans print two lines: start then end. Relative fields reuse the reference on a per-field basis.

$ kt-parse timespan 9h..10h 2025-10-22T09:10:11+00:00
1761104400 2025-10-22 09:00:00 +00:00
1761108000 2025-10-22 10:00:00 +00:00

Missing fields in the end segment now borrow the fully resolved start instant (commit 1741734), so terse ranges stay on the expected day.

$ kt-parse timespan 10:15..30 2025-10-27T09:00:00+00:00
1761560100 2025-10-27 10:15:00 +00:00
1761561000 2025-10-27 10:30:00 +00:00

$ kt-parse timespan '2025-10-27 10:30..11:30' 2025-10-01T00:00:00+00:00
1761561000 2025-10-27 10:30:00 +00:00
1761564600 2025-10-27 11:30:00 +00:00

$ kt-parse timespan '2025-10-27 10:00:00..01:30' 2025-10-01T00:00:00+00:00
1761559200 2025-10-27 10:00:00 +00:00
1761559290 2025-10-27 10:01:30 +00:00

About

Small rust time parsing helper

Resources

License

Stars

Watchers

Forks

Packages

No packages published