timespec

CLI-oriented time specification parser for MoonBit

time
datetime
duration
range
span
cli
parser
moon add kawaz/timespec@0.2.4
Download zip
Author
Version
0.2.4
License
MIT
Last updated
last month
Downloads
221
README

#kawaz/timespec

English | 日本語

CLI-oriented time specification parser for MoonBit.

Parses flexible time expressions like 5m, @1h30m, 2026-03-15T12:00:00Z+5h, 3 minutes ago into structured TimeSpec values for --since / --until style CLI options.

#Install

moon add kawaz/timespec

#Quick Start

// Duration parsing
let d = @timespec.parse_duration("1h30m") // Duration(5400000)

// TimeSpec parsing (relative)
let ts = @timespec.parse_timespec("5m", default_sign=Minus)
// Relative(EpochTime(now - 300000), Duration(-300000))

// TimeSpec parsing (absolute with @)
let ts = @timespec.parse_timespec("@5m", default_sign=Minus)
// Absolute(EpochTime(now - 300000), Duration(-300000))

// Time range
let r = @timespec.parse_range(since="5m", until="3m", default_sign=Minus)
// { since: Some(Relative(...)), until: Some(Relative(...)) }

// Re-serialization
ts.to_cli_string()
// Relative → "+5m" / Absolute → "2026-03-15T12:00:00Z"

#Supported Expressions

#Duration (parse_duration)

InputParsed as
5m5 minutes
1h30m1 hour 30 minutes
1.5h1.5 hours (= 90 minutes)
500ms500 milliseconds
3_600_000msunderscore separators
2d12h2 days 12 hours
5 minutes ago-5 minutes (ago reverses sign)
+1h -30m1 hour minus 30 minutes (= 30 minutes)

Units: w/week(s), d/day(s), h/hour(s), m/min/minute(s), s/sec/second(s), ms/millisecond(s)

#TimeSpec (parse_timespec)

The @ marker pins a time expression as Absolute (re-serializes to ISO 8601). Without @, duration-only inputs are Relative (re-serializes to signed duration like +5m).

Examples with default_sign=Minus:

InputKindDescription
5mRelative5 minutes ago from now
+5mRelative5 minutes from now
@5mAbsolute5 minutes ago, pinned
-5h@Absolute@ position is flexible
2026-03-15T12:00:00ZAbsoluteISO 8601 datetime
2026-03-15T21:00:00+09:00Absolutewith timezone offset
30m 2026-03-15T12:00:00ZAbsolutedatetime - 30 minutes
2026-03-15T12:00:00Z +5h30mAbsolutedatetime + 5h30m
@10:30Absolutetoday at 10:30 (time-of-day reset)
@10:30+09:00Absolutetoday at 10:30 JST
@1704110400000Absoluteraw epoch ms (like date -d @EPOCH)
3 minutes agoRelativeEnglish-style modifier

#TimeRange (parse_range)

Two-argument style (recommended):

sinceuntilDescription
5m3mlast 5 min to last 3 min
5m(empty)from 5 minutes ago
(empty)3muntil 3 minutes ago
@5m3mabsolute since, relative until anchored to since
5m+3mexplicit + for positive offset

Tilde-delimited style (single string):

inputEquivalent
5m~3msince=5m, until=3m
5d~since=5d, until=_(none)_
~3msince=_(none)_, until=3m

#Timezone Offset (parse_tz_offset)

InputResult
Z, UTC, GMTUtc
9, +09, +09:00, +0900Hour(9)
GMT+9, UTC+09:00Hour(9)
+5:30, UTC+5:30Min(330)
-5h, +9h30mduration-style offset
localLocal (resolved at runtime)

#Re-serialization

TimeSpec::to_cli_string() preserves intent:

VariantOutputUse case
Relative"-5m", "+1h30m"Reproduces the same relative offset
Absolute"2026-03-15T12:00:00Z"Reproduces the exact point in time

#Pluggable Design

All parse functions accept optional labeled parameters with sensible defaults:

  • now — custom clock source (default: system time)
  • epoch — custom epoch for Snowflake IDs, Performance API, etc.
  • default_signMinus (for --since), Plus, or Reject
  • default_tz_offset — timezone for TZ-less datetime inputs
  • parse_datetime — replace ISO 8601 parser with locale-aware parser

#Multi-target

TargetLocal TZNotes
NativeC FFI (localtime_r)Full support
JSDate.getTimezoneOffset()Full support
WASMFalls back to UTCWASI has no TZ API

#License

MIT License - Yoshiaki Kawazu (@kawaz)

#
ParseError

pub(all) suberror ParseError {
ParseError(String)
} derive(Eq,
Debug
)

#
Duration

pub(all) struct Duration(Int64) derive(Compare, Eq,
Debug
)

impl Add for Duration
impl Neg for Duration
impl Sub for Duration

#
Duration::scale

fn Duration::scale(self : Duration, n : Int64) -> Duration

#
Duration::to_ms

fn Duration::to_ms(self : Duration) -> Int64

#
EpochTime

pub(all) struct EpochTime(Int64) derive(Compare, Eq,
Debug
)

#
EpochTime::add_duration

fn EpochTime::add_duration(self : EpochTime, d : Duration) -> EpochTime

#
EpochTime::to_ms

fn EpochTime::to_ms(self : EpochTime) -> Int64

#
Sign

pub(all) enum Sign {
Minus
Plus
Reject
} derive(Eq,
Debug
)

#
TimeRange

pub(all) struct TimeRange {
since : TimeSpec?
until : TimeSpec?
} derive(Eq,
Debug
)

#
TimeSpec

pub(all) enum TimeSpec {
Absolute(EpochTime, Duration)
Relative(EpochTime, Duration)
} derive(Eq,
Debug
)

#
TimeSpec::duration

fn TimeSpec::duration(self : TimeSpec) -> Duration

#
TimeSpec::epoch

fn TimeSpec::epoch(self : TimeSpec) -> EpochTime

#
TimeSpec::is_absolute

fn TimeSpec::is_absolute(self : TimeSpec) -> Bool

#
TimeSpec::to_cli_string

fn TimeSpec::to_cli_string(self : TimeSpec, epoch? : EpochTime) -> String

#
TimeSpec::to_epoch_ms

fn TimeSpec::to_epoch_ms(self : TimeSpec) -> Int64

#
TzOffset

pub(all) enum TzOffset {
Utc
Local
Hour(Int)
Min(Int)
} derive(Eq,
Debug
)

#
TzOffset::equal_offset

fn TzOffset::equal_offset(self : TzOffset, other : TzOffset) -> Bool

#
day

let day : Duration

#
default_parse_datetime

fn default_parse_datetime(input : String) -> Int64?

#
epoch_to_iso8601

fn epoch_to_iso8601(epoch_ms : Int64, tz_offset? : TzOffset) -> String

#
hour

let hour : Duration

#
local_tz_offset

fn local_tz_offset() -> TzOffset

#
make_parse_datetime

fn make_parse_datetime(default_tz_offset? : TzOffset) -> ((String) -> Int64?)

#
millisecond

let millisecond : Duration

#
minute

let minute : Duration

#
parse_duration

fn parse_duration(input : String, default_sign? : Sign) -> Duration raise ParseError

#
parse_range

fn parse_range(input? : String, since? : String, until? : String, epoch? : EpochTime, default_sign? : Sign, default_tz_offset? : TzOffset, now? : () -> UInt64, swap? : Bool, parse_datetime? : (String) -> Int64?) -> TimeRange raise ParseError

#
parse_timespec

fn parse_timespec(input : String, epoch? : EpochTime, default_sign? : Sign, default_tz_offset? : TzOffset, now? : () -> UInt64, parse_datetime? : (String) -> Int64?) -> TimeSpec? raise ParseError

#
parse_tz_offset

fn parse_tz_offset(input : String) -> TzOffset raise ParseError

#
second

let second : Duration

#
week

let week : Duration