Crate fundu

Source
Expand description

§Overview

Parse a rust string into a crate::Duration.

fundu is a configurable, precise and blazingly fast duration parser

  • with the flexibility to customize TimeUnits, or even create own time units with a CustomTimeUnit (the custom feature is needed)
  • without floating point calculations. What you put in is what you get out.
  • with sound limit handling. Infinity and numbers larger than Duration::MAX evaluate to Duration::MAX. Numbers x with abs(x) < 1e-18 evaluate to Duration::ZERO.
  • with many options to customize the number format and other aspects of the parsing process like parsing negative durations
  • and with meaningful error messages

§Fundu’s Duration

This crate::Duration is returned by the parser of this library and can be converted to a std::time::Duration and if the feature is activated into a time::Duration respectively chrono::Duration. This crates duration is a superset of the aforementioned durations ranging from -Duration::MAX to +Duration::MAX with Duration::MAX having u64::MAX seconds and 999_999_999 nano seconds. Converting to fundu’s duration from any of the above durations with From or Into is lossless. Converting from crate::Duration to any of the other durations can overflow or can’t be negative, so conversions must be done with TryFrom or TryInto. Additionally, fundu’s duration implements SaturatingInto for the above durations, so conversions saturate at the maximum or minimum of these durations.

§Features

§standard

The standard feature exposes the DurationParser and DurationParserBuilder structs with the a predefined small set of time units. The set of time units can be customized but the identifier for each TimeUnit is fixed.

§custom

The custom feature provides a CustomDurationParser and CustomDurationParserBuilder with fully customizable identifiers for each TimeUnit. With the CustomDurationParser it is also possible to define completely new time units, a CustomTimeUnit.

§base

The base feature exports the basic Parser and the Config on which the standard and custom features are built. It may lack the convenience of the other features but provides greater freedom. To be able to use this Parser an implementation of TimeUnitsLike is needed for time units and optionally for time keywords. Optionally, NumbersLike implementations are supported, too. For fixed sets of time units and time keywords this is usually a simple and straightforward process. See the documentation of Parser, TimeUnitsLike and NumbersLike for examples.

§chrono and time

The chrono feature activates methods of Duration to convert from and to a chrono::Duration. The time feature activates methods of Duration to convert from and to a time::Duration. Both of these durations allow negative durations. Parsing negative numbers can be enabled with DurationParser::allow_negative or CustomDurationParser::allow_negative independently of the chrono or time feature.

§serde

Some structs and enums can be serialized and deserialized with serde if the feature is activated.

§Configuration and Format

The standard parser can be configured to accept strings with a default set of time units DurationParser::new, with all time units DurationParser::with_all_time_units or without DurationParser::without_time_units. A custom set of time units is also possible with DurationParser::with_time_units. All these parsers accept strings such as

  • 1.41
  • 42
  • 2e-8, 2e+8 (or likewise 2.0e8)
  • .5 or likewise 0.5
  • 3. or likewise 3.0
  • inf, +inf, infinity, +infinity

All alphabetic characters are matched case-insensitive, so InFINity or 2E8 are valid input strings. Additionally, depending on the chosen set of time units one of the following time units (the first column) is accepted.

TimeUnitdefault idis default time unit
Nanosecondnsyes
MicrosecondMsyes
Millisecondmsyes
Secondsyes
Minutemyes
Hourhyes
Daydyes
Weekwyes
MonthMno
Yearyno

If no time unit is given and not specified otherwise with DurationParser::default_unit then s (= Second) is assumed. Some accepted strings with time units

  • 31.2s
  • 200000Ms
  • 3.14e8w

Per default there is no whitespace allowed between the number and the TimeUnit, but this behavior can be changed with DurationParser::allow_time_unit_delimiter.

§Format specification

The TimeUnits and every Char is case-sensitive, all other alphabetic characters are case-insensitive

Durations ::= Duration [ DurationStartingWithDigit
            | ( Delimiter+ ( Duration | Conjunction ))
            ]* ;
Conjunction ::= ConjunctionWord (( Delimiter+ Duration ) | DurationStartingWithDigit ) ;
ConjunctionWord ::= Char+ ;
Duration ::= Sign? ( 'inf' | 'infinity'
            | TimeKeyword
            | Number [ TimeUnit [ Delimiter+ 'ago' ]]
            ) ;
DurationStartingWithDigit ::=
            ( Digit+ | Digit+ '.' Digit* ) Exp? [ TimeUnit [ Delimiter+ 'ago' ]] ;
TimeUnit ::= ns | Ms | ms | s | m | h | d | w | M | y | CustomTimeUnit ;
CustomTimeUnit ::= Char+ ;
TimeKeyword ::= Char+ ;
Number   ::= ( Digits Exp? ) | Exp ;
Digits   ::= Digit+ | Digit+ '.' Digit* | Digit* '.' Digit+
Exp      ::= 'e' Sign? Digit+ ;
Sign     ::= [+-] ;
Digit    ::= [0-9] ;
Char     ::= ? a valid UTF-8 character ? ;
Delimiter ::= ? a closure with the signature u8 -> bool ? ;

Special cases which are not displayed in the specification:

  • Parsing multiple Durations must be enabled with parse_multiple. The Delimiter and ConjunctionWords can also be defined with the parse_multiple method. Multiple Durations are summed up following the saturation rule below
  • A negative Duration (Sign == -), including negative infinity is not allowed as long as the allow_negative option is not enabled. For exceptions see the next point.
  • Numbers x (positive and negative) close to 0 (abs(x) < 1e-18) are treated as 0
  • Positive infinity and numbers exceeding Duration::MAX saturate at Duration::MAX. If the allow_negative option is enabled, negative infinity and numbers falling below Duration::MIN saturate at Duration::MIN.
  • The exponent must be in the range -32768 <= Exp <= 32767
  • If allow_time_unit_delimiter is set then any Delimiter is allowed between the Number and TimeUnit.
  • If number_is_optional is enabled then the Number is optional but the TimeUnit must be present instead.
  • The ago keyword must be enabled in the parser with allow_ago
  • TimeKeyword is a custom feature which must be enabled by adding a TimeKeyword to the CustomDurationParser
  • CustomTimeUnit is a custom feature which lets you define own time units

§Examples

If only the default configuration is required once, the parse_duration method can be used.

use std::time::Duration;

use fundu::parse_duration;

let input = "1.0e2s";
assert_eq!(parse_duration(input).unwrap(), Duration::new(100, 0));

When a customization of the accepted TimeUnits is required, then DurationParser can be used.

use fundu::{Duration, DurationParser};

let input = "3m";
assert_eq!(
    DurationParser::with_all_time_units().parse(input).unwrap(),
    Duration::positive(180, 0)
);

When no time units are configured, seconds is assumed.

use fundu::{Duration, DurationParser};

let input = "1.0e2";
assert_eq!(
    DurationParser::without_time_units().parse(input).unwrap(),
    Duration::positive(100, 0)
);

However, the following will return an error because y (Years) is not a default time unit:

use fundu::DurationParser;

let input = "3y";
assert!(DurationParser::new().parse(input).is_err());

The parser is reusable and the set of time units is fully customizable

use fundu::TimeUnit::*;
use fundu::{Duration, DurationParser};

let parser = DurationParser::with_time_units(&[NanoSecond, Minute, Hour]);

assert_eq!(parser.parse("9e3ns").unwrap(), Duration::positive(0, 9000));
assert_eq!(parser.parse("10m").unwrap(), Duration::positive(600, 0));
assert_eq!(parser.parse("1.1h").unwrap(), Duration::positive(3960, 0));
assert_eq!(parser.parse("7").unwrap(), Duration::positive(7, 0));

Setting the default time unit (if no time unit is given in the input string) to something different than seconds is also easily possible

use fundu::TimeUnit::*;
use fundu::{Duration, DurationParser};

assert_eq!(
    DurationParser::without_time_units()
        .default_unit(MilliSecond)
        .parse("1000")
        .unwrap(),
    Duration::positive(1, 0)
);

The identifiers for time units can be fully customized with any number of valid utf-8 sequences if the custom feature is activated:

use fundu::TimeUnit::*;
use fundu::{CustomTimeUnit, CustomDurationParser, Duration};

let parser = CustomDurationParser::with_time_units(&[
    CustomTimeUnit::with_default(MilliSecond, &["χιλιοστό του δευτερολέπτου"]),
    CustomTimeUnit::with_default(Second, &["s", "secs"]),
    CustomTimeUnit::with_default(Hour, &["⏳"]),
]);

assert_eq!(parser.parse(".3χιλιοστό του δευτερολέπτου"), Ok(Duration::positive(0, 300_000)));
assert_eq!(parser.parse("1e3secs"), Ok(Duration::positive(1000, 0)));
assert_eq!(parser.parse("1.1⏳"), Ok(Duration::positive(3960, 0)));

The custom feature can be used to customize a lot more. See the documentation of the exported items of the custom feature (like CustomTimeUnit, TimeKeyword) for more information.

Also, fundu tries to give informative error messages

use fundu::DurationParser;

assert_eq!(
    DurationParser::without_time_units()
        .parse("1y")
        .unwrap_err()
        .to_string(),
    "Time unit error: No time units allowed but found: 'y' at column 1"
);

The number format can be easily adjusted to your needs. For example to allow numbers being optional, allow some ascii whitespace between the number and the time unit and restrict the number format to whole numbers, without fractional part and an exponent:

use fundu::TimeUnit::*;
use fundu::{Duration, DurationParser, ParseError};

const PARSER: DurationParser = DurationParser::builder()
    .time_units(&[NanoSecond])
    .allow_time_unit_delimiter()
    .number_is_optional()
    .disable_fraction()
    .disable_exponent()
    .build();

assert_eq!(PARSER.parse("ns").unwrap(), Duration::positive(0, 1));
assert_eq!(
    PARSER.parse("1000\t\n\r ns").unwrap(),
    Duration::positive(0, 1000)
);

assert_eq!(
    PARSER.parse("1.0ns").unwrap_err(),
    ParseError::Syntax(1, "No fraction allowed".to_string())
);
assert_eq!(
    PARSER.parse("1e9ns").unwrap_err(),
    ParseError::Syntax(1, "No exponent allowed".to_string())
);

Structs§

Configbase
The structure containing all options for the crate::parse::Parser
ConfigBuilderbase
A builder to create a Config
CustomDurationParsercustom
A parser with a customizable set of TimeUnits and customizable identifiers.
CustomDurationParserBuildercustom
Like crate::DurationParserBuilder for crate::DurationParser, this is a builder for a CustomDurationParser.
CustomTimeUnitcustom
A CustomTimeUnit is a completely customizable TimeUnit using an additional Multiplier.
Duration
The duration which is returned by the parser
DurationParserstandard
A parser with a customizable set of TimeUnits with default identifiers.
DurationParserBuilderstandard
An ergonomic builder for a DurationParser.
Multiplier
The multiplier of a TimeUnit.
Numeralcustom
A Numeral can occur where numbers usually occur in the source string
Parserbase
The core duration parser to parse strings into a crate::time::Duration
TimeKeywordcustom
A TimeKeyword represents a complete duration without the need for a number

Enums§

ParseError
Error type emitted during the parsing
TimeUnit
The time units used to define possible time units in the input string
TryFromDurationError
This error may occur when converting a crate::time::Duration to a different duration like std::time::Duration

Constants§

DEFAULT_ALL_TIME_UNITScustom
All identifiers taken from the standard feature with Month and Year
DEFAULT_ID_DAY
The default identifier of TimeUnit::Day
DEFAULT_ID_HOUR
The default identifier of TimeUnit::Hour
DEFAULT_ID_MICRO_SECOND
The default identifier of TimeUnit::MicroSecond
DEFAULT_ID_MILLI_SECOND
The default identifier of TimeUnit::MicroSecond
DEFAULT_ID_MINUTE
The default identifier of TimeUnit::Minute
DEFAULT_ID_MONTH
The default identifier of TimeUnit::Month
DEFAULT_ID_NANO_SECOND
The default identifier of TimeUnit::NanoSecond
DEFAULT_ID_SECOND
The default identifier of TimeUnit::Second
DEFAULT_ID_WEEK
The default identifier of TimeUnit::Week
DEFAULT_ID_YEAR
The default identifier of TimeUnit::Year
DEFAULT_TIME_UNITScustom
The default identifiers taken from the standard feature (without Month and Year)
SYSTEMD_TIME_UNITScustom
The identifiers as defined in systemd.time

Traits§

NumbersLikebase
NumbersLike strings can occur where usually a number would occur in the source string
SaturatingInto
Conversion which saturates at the maximum or maximum instead of overflowing
TimeUnitsLikebase
To be able to use the basic crate::parse::Parser this trait needs to be implemented

Functions§

parse_durationstandard
Parse a string into a std::time::Duration by accepting a string similar to floating point with the default set of time units.

Type Aliases§

Delimiter
An ascii delimiter defined as closure.