#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <ctime>
#include <numeric>

#include <util/datetime/parser.h>
#include <util/generic/ymath.h>


%%{

machine DateTimeParserCommon;

sp  = ' ';

action clear_int {
    I = 0;
    Dc = 0;
}

action update_int {
    I = I * 10 + (fc - '0');
    ++Dc;
}

int = (digit+)
    >clear_int
    $update_int;

int1 = digit
    >clear_int
    $update_int;

int2 = (digit digit)
    >clear_int
    $update_int;

int3 = (digit digit digit)
    >clear_int
    $update_int;

int4 = (digit digit digit digit)
    >clear_int
    $update_int;

int12 = (digit digit?)
    >clear_int
    $update_int;

int24 = ( digit digit ( digit digit )? )
    >clear_int
    $update_int;

# According to both RFC2822 and RFC2616 dates MUST be case-sensitive,
# but Andrey fomichev@ wants relaxed parser

month3 =
      'Jan'i %{ DateTimeFields.Month =  1; }
    | 'Feb'i %{ DateTimeFields.Month =  2; }
    | 'Mar'i %{ DateTimeFields.Month =  3; }
    | 'Apr'i %{ DateTimeFields.Month =  4; }
    | 'May'i %{ DateTimeFields.Month =  5; }
    | 'Jun'i %{ DateTimeFields.Month =  6; }
    | 'Jul'i %{ DateTimeFields.Month =  7; }
    | 'Aug'i %{ DateTimeFields.Month =  8; }
    | 'Sep'i %{ DateTimeFields.Month =  9; }
    | 'Oct'i %{ DateTimeFields.Month = 10; }
    | 'Nov'i %{ DateTimeFields.Month = 11; }
    | 'Dec'i %{ DateTimeFields.Month = 12; };

wkday   = 'Mon'i | 'Tue'i | 'Wed'i | 'Thu'i | 'Fri'i | 'Sat'i | 'Sun'i;
weekday = 'Monday'i | 'Tuesday'i | 'Wednesday'i | 'Thursday'i
        | 'Friday'i | 'Saturday'i | 'Sunday'i;

action set_second   { DateTimeFields.Second = I; }
action set_minute   { DateTimeFields.Minute = I; }
action set_hour     { DateTimeFields.Hour = I; }
action set_day      { DateTimeFields.Day = I; }
action set_month    { DateTimeFields.Month = I; }
action set_year     { DateTimeFields.SetLooseYear(I); }
action set_zone_utc { DateTimeFields.ZoneOffsetMinutes = 0; }

}%%

%%{

machine RFC822DateParser;

################# RFC 2822 3.3 Full Date ###################

include DateTimeParserCommon;

ws1 = (space+);
ws0 = (space*);
dow_spec = ( wkday ',' )?;

day  = int12 %set_day;
year = int24 %set_year;

# actually it must be from 0 to 23
hour = int2 %set_hour;

# actually it must be from 0 to 59
min  = int2 %set_minute;

# actually it must be from 0 to 59
sec  = int2 %set_second;

sec_spec  = ( ':' . sec )?;

# so called "military zone offset". I hardly believe someone uses it now, but we MUST respect RFc822
action set_mil_offset {
    char c = (char)toupper(fc);
    if (c == 'Z')
        DateTimeFields.ZoneOffsetMinutes = 0;
    else {
        if (c <= 'M') {
            // ['A'..'M'] \ 'J'
            if (c < 'J')
                DateTimeFields.ZoneOffsetMinutes = (i32)TDuration::Hours(c - 'A' + 1).Minutes();
            else
                DateTimeFields.ZoneOffsetMinutes = (i32)TDuration::Hours(c - 'A').Minutes();
        } else {
            // ['N'..'Y']
            DateTimeFields.ZoneOffsetMinutes = -(i32)TDuration::Hours(c - 'N' + 1).Minutes();
        }
    }
}

action set_digit_offset {
    DateTimeFields.ZoneOffsetMinutes = Sign * (i32)(TDuration::Hours(I / 100) + TDuration::Minutes(I % 100)).Minutes();
}

mil_zone = /[A-IK-Za-ik-z]/ $set_mil_offset;

# actions % were replaced with @ (when the script was migrated to ragel 5.24)
# because ragel 5.24 does not call to the % action if it  be called at the very end of string.
# it is a bug in ragel 5 because ragel 6.2 works correctly with % at the end of string.
# see http://www.complang.org/ragel/ChangeLog.

zone = 'UT' @{ DateTimeFields.ZoneOffsetMinutes = 0; }
    | 'GMT' @{ DateTimeFields.ZoneOffsetMinutes = 0; }
    | 'EST' @{ DateTimeFields.ZoneOffsetMinutes = -(i32)TDuration::Hours(5).Minutes();}
    | 'EDT' @{ DateTimeFields.ZoneOffsetMinutes = -(i32)TDuration::Hours(4).Minutes(); }
    | 'CST' @{ DateTimeFields.ZoneOffsetMinutes = -(i32)TDuration::Hours(6).Minutes();}
    | 'CDT' @{ DateTimeFields.ZoneOffsetMinutes = -(i32)TDuration::Hours(5).Minutes(); }
    | 'MST' @{ DateTimeFields.ZoneOffsetMinutes = -(i32)TDuration::Hours(7).Minutes();}
    | 'MDT' @{ DateTimeFields.ZoneOffsetMinutes = -(i32)TDuration::Hours(6).Minutes(); }
    | 'PST' @{ DateTimeFields.ZoneOffsetMinutes = -(i32)TDuration::Hours(8).Minutes();}
    | 'PDT' @{ DateTimeFields.ZoneOffsetMinutes = -(i32)TDuration::Hours(7).Minutes(); };

digit_offset = ('+' | '-') > { Sign = fc == '+' ? 1 : -1; } . int4 @set_digit_offset;

offset = ( zone | mil_zone | digit_offset );

rfc822datetime = ws0 . dow_spec . ws0 . day . ws1 . month3 . ws1 . year . ws1 . hour . ':' . min . sec_spec . ws1 . offset . ws0;

main := rfc822datetime;

write data noerror;

}%%

TRfc822DateTimeParserDeprecated::TRfc822DateTimeParserDeprecated() {
    %% write init;
}

bool TRfc822DateTimeParserDeprecated::ParsePart(const char* input, size_t len) {
    const char* p = input;
    const char* pe = input + len;

    %% write exec;
    return cs != %%{ write error; }%%;
}

TRfc822DateTimeParser::TRfc822DateTimeParser() {
    %% write init;
}

bool TRfc822DateTimeParser::ParsePart(const char* input, size_t len) {
    const char* p = input;
    const char* pe = input + len;

    %% write exec;
    return cs != %%{ write error; }%%;
}

%%{

machine ISO8601DateTimeParser;

include DateTimeParserCommon;

year = int4 @set_year;
month = int2 @set_month;
day = int2 @set_day;
hour = int2 @set_hour;
minute = int2 @set_minute;
second = int2 @set_second;
secondFrac = digit {1,6} >clear_int $update_int @{
    ui32 us = I;
    for (int k = Dc; k < 6; ++k) {
        us *= 10;
    }
    DateTimeFields.MicroSecond = us;
};
secondFracTail = (digit*);

zoneZ = [Zz] @set_zone_utc;
zoneOffset = space? . ('+' | '-') >{ Sign = fc == '+' ? 1 : -1; } . int2 @{ DateTimeFields.ZoneOffsetMinutes = Sign * (i32)TDuration::Hours(I).Minutes(); } . (':')? . (int2 @{ DateTimeFields.ZoneOffsetMinutes += I * Sign; })?;
zone = zoneZ | zoneOffset;

iso8601date = (year . '-' . month . '-' . day) | (year . month . day);
iso8601time = (hour . ':' . minute . (':' . second ('.' secondFrac secondFracTail)?)?) | (hour . minute . second?);

iso8601datetime = iso8601date . ([Tt ] . iso8601time . zone?)?;

main := iso8601datetime;

write data noerror;

}%%

TIso8601DateTimeParserDeprecated::TIso8601DateTimeParserDeprecated() {
    %% write init;
}

bool TIso8601DateTimeParserDeprecated::ParsePart(const char* input, size_t len) {
    const char* p = input;
    const char* pe = input + len;

    %% write exec;
    return cs != %%{ write error; }%%;
}

TIso8601DateTimeParser::TIso8601DateTimeParser() {
    %% write init;
}

bool TIso8601DateTimeParser::ParsePart(const char* input, size_t len) {
    const char* p = input;
    const char* pe = input + len;

    %% write exec;
    return cs != %%{ write error; }%%;
}

%%{

machine HttpDateTimeParser;

include DateTimeParserCommon;

################# RFC 2616 3.3.1 Full Date #################

time            = int2 %set_hour ':' int2 %set_minute ':' int2 %set_second;
date1           = int2 %set_day ' ' month3 ' ' int4 %set_year;
date2           = int2 %set_day '-' month3 '-' int2 %set_year;
date3           = month3 sp (int2 | sp int1) %set_day;

rfc1123_date    = wkday ',' sp date1 sp time sp 'GMT'i;
rfc850_date     = weekday ',' sp date2 sp time sp 'GMT'i;
asctime_date    = wkday sp date3 sp time sp int4 @set_year;
http_date       = (rfc1123_date | rfc850_date | asctime_date) @set_zone_utc;

}%%

%%{

machine HttpDateTimeParserStandalone;

include HttpDateTimeParser;

main := http_date;

write data noerror;

}%%

THttpDateTimeParserDeprecated::THttpDateTimeParserDeprecated() {
    %% write init;
}

bool THttpDateTimeParserDeprecated::ParsePart(const char* input, size_t len) {
    const char* p = input;
    const char* pe = input + len;

    %% write exec;
    return cs != %%{ write error; }%%;
}

THttpDateTimeParser::THttpDateTimeParser() {
    %% write init;
}

bool THttpDateTimeParser::ParsePart(const char* input, size_t len) {
    const char* p = input;
    const char* pe = input + len;

    %% write exec;
    return cs != %%{ write error; }%%;
}

%%{

machine X509ValidityDateTimeParser;

include DateTimeParserCommon;

################# X.509 certificate validity time (see rfc5280 4.1.2.5.*) #################

year = int2 @{ DateTimeFields.Year = (I < 50 ? I + 2000 : I + 1900); };
month = int2 @set_month;
day = int2 @set_day;
hour = int2 @set_hour;
minute = int2 @set_minute;
second = int2 @set_second;
zone = 'Z' @set_zone_utc;

main := year . month . day . hour . minute . second . zone;

write data noerror;

}%%

TX509ValidityDateTimeParserDeprecated::TX509ValidityDateTimeParserDeprecated() {
    %% write init;
}

bool TX509ValidityDateTimeParserDeprecated::ParsePart(const char *input, size_t len) {
    const char *p = input;
    const char *pe = input + len;

    %% write exec;
    return cs != %%{ write error; }%%;
}

TX509ValidityDateTimeParser::TX509ValidityDateTimeParser() {
    %% write init;
}

bool TX509ValidityDateTimeParser::ParsePart(const char *input, size_t len) {
    const char *p = input;
    const char *pe = input + len;

    %% write exec;
    return cs != %%{ write error; }%%;
}

%%{

machine X509Validity4yDateTimeParser;

include DateTimeParserCommon;

year = int4 @{ DateTimeFields.Year = I; };
month = int2 @set_month;
day = int2 @set_day;
hour = int2 @set_hour;
minute = int2 @set_minute;
second = int2 @set_second;
zone = 'Z' @set_zone_utc;


main := year . month . day . hour . minute . second . zone;

write data noerror;

}%%

TX509Validity4yDateTimeParserDeprecated::TX509Validity4yDateTimeParserDeprecated() {
    %% write init;
}

bool TX509Validity4yDateTimeParserDeprecated::ParsePart(const char *input, size_t len) {
    const char *p = input;
    const char *pe = input + len;

    %% write exec;
    return cs != %%{ write error; }%%;
}

TX509Validity4yDateTimeParser::TX509Validity4yDateTimeParser() {
    %% write init;
}

bool TX509Validity4yDateTimeParser::ParsePart(const char *input, size_t len) {
    const char *p = input;
    const char *pe = input + len;

    %% write exec;
    return cs != %%{ write error; }%%;
}

TInstant TIso8601DateTimeParserDeprecated::GetResult(TInstant defaultValue) const {
    Y_UNUSED(ISO8601DateTimeParser_en_main);
    return TDateTimeParserBaseDeprecated::GetResult(ISO8601DateTimeParser_first_final, defaultValue);
}

TInstant TRfc822DateTimeParserDeprecated::GetResult(TInstant defaultValue) const {
    Y_UNUSED(RFC822DateParser_en_main);
    return TDateTimeParserBaseDeprecated::GetResult(RFC822DateParser_first_final, defaultValue);
}

TInstant THttpDateTimeParserDeprecated::GetResult(TInstant defaultValue) const {
    Y_UNUSED(HttpDateTimeParserStandalone_en_main);
    return TDateTimeParserBaseDeprecated::GetResult(HttpDateTimeParserStandalone_first_final, defaultValue);
}

TInstant TX509ValidityDateTimeParserDeprecated::GetResult(TInstant defaultValue) const {
    Y_UNUSED(X509ValidityDateTimeParser_en_main);
    return TDateTimeParserBaseDeprecated::GetResult(X509ValidityDateTimeParser_first_final, defaultValue);
}

TInstant TX509Validity4yDateTimeParserDeprecated::GetResult(TInstant defaultValue) const {
    Y_UNUSED(X509Validity4yDateTimeParser_en_main);
    return TDateTimeParserBaseDeprecated::GetResult(X509Validity4yDateTimeParser_first_final, defaultValue);
}

TInstant TIso8601DateTimeParser::GetResult(TInstant defaultValue) const {
    Y_UNUSED(ISO8601DateTimeParser_en_main);
    return TDateTimeParserBase::GetResult(ISO8601DateTimeParser_first_final, defaultValue);
}

TInstant TRfc822DateTimeParser::GetResult(TInstant defaultValue) const {
    Y_UNUSED(RFC822DateParser_en_main);
    return TDateTimeParserBase::GetResult(RFC822DateParser_first_final, defaultValue);
}

TInstant THttpDateTimeParser::GetResult(TInstant defaultValue) const {
    Y_UNUSED(HttpDateTimeParserStandalone_en_main);
    return TDateTimeParserBase::GetResult(HttpDateTimeParserStandalone_first_final, defaultValue);
}

TInstant TX509ValidityDateTimeParser::GetResult(TInstant defaultValue) const {
    Y_UNUSED(X509ValidityDateTimeParser_en_main);
    return TDateTimeParserBase::GetResult(X509ValidityDateTimeParser_first_final, defaultValue);
}

TInstant TX509Validity4yDateTimeParser::GetResult(TInstant defaultValue) const {
    Y_UNUSED(X509Validity4yDateTimeParser_en_main);
    return TDateTimeParserBase::GetResult(X509Validity4yDateTimeParser_first_final, defaultValue);
}

template<class TParser, class TResult>
static inline TResult Parse(const char* input, size_t len, TResult defaultValue) {
    TParser parser;
    if (!parser.ParsePart(input, len))
        return defaultValue;
    return parser.GetResult(defaultValue);
}

template<class TParser, class TResult, bool ThrowExceptionOnFailure = true>
static inline TResult ParseUnsafe(const char* input, size_t len) {
    TResult r = Parse<TParser, TResult>(input, len, TResult::Max());
    if (ThrowExceptionOnFailure && r == TResult::Max())
        ythrow TDateTimeParseException() << "error in datetime parsing. Input data: " << TStringBuf(input, len);
    return r;
}

TInstant TInstant::ParseIso8601Deprecated(const TStringBuf input) {
    return ParseUnsafe<TIso8601DateTimeParserDeprecated, TInstant>(input.data(), input.size());
}

TInstant TInstant::ParseRfc822Deprecated(const TStringBuf input) {
    return ParseUnsafe<TRfc822DateTimeParserDeprecated, TInstant>(input.data(), input.size());
}

TInstant TInstant::ParseHttpDeprecated(const TStringBuf input) {
    return ParseUnsafe<THttpDateTimeParserDeprecated, TInstant>(input.data(), input.size());
}

TInstant TInstant::ParseX509ValidityDeprecated(const TStringBuf input) {
    switch (input.size()) {
    case 13:
        return ParseUnsafe<TX509ValidityDateTimeParserDeprecated, TInstant>(input.data(), 13);
    case 15:
        return ParseUnsafe<TX509Validity4yDateTimeParserDeprecated, TInstant>(input.data(), 15);
    default:
        ythrow TDateTimeParseException();
    }
}

bool TInstant::TryParseIso8601Deprecated(const TStringBuf input, TInstant& instant) {
    const auto parsed = ParseUnsafe<TIso8601DateTimeParserDeprecated, TInstant, false>(input.data(), input.size());
    if (TInstant::Max() == parsed) {
        return false;
    }
    instant = parsed;
    return true;
}

bool TInstant::TryParseRfc822Deprecated(const TStringBuf input, TInstant& instant) {
    const auto parsed = ParseUnsafe<TRfc822DateTimeParserDeprecated, TInstant, false>(input.data(), input.size());
    if (TInstant::Max() == parsed) {
        return false;
    }
    instant = parsed;
    return true;
}

bool TInstant::TryParseHttpDeprecated(const TStringBuf input, TInstant& instant) {
    const auto parsed = ParseUnsafe<THttpDateTimeParserDeprecated, TInstant, false>(input.data(), input.size());
    if (TInstant::Max() == parsed) {
        return false;
    }
    instant = parsed;
    return true;
}

bool TInstant::TryParseX509Deprecated(const TStringBuf input, TInstant& instant) {
    TInstant parsed;
    switch (input.size()) {
        case 13:
            parsed = ParseUnsafe<TX509ValidityDateTimeParserDeprecated, TInstant, false>(input.data(), 13);
            break;
        case 15:
            parsed = ParseUnsafe<TX509Validity4yDateTimeParserDeprecated, TInstant, false>(input.data(), 15);
            break;
        default:
            return false;
    }
    if (TInstant::Max() == parsed) {
        return false;
    }
    instant = parsed;
    return true;
}

TInstant TInstant::ParseIso8601(const TStringBuf input) {
    return ParseUnsafe<TIso8601DateTimeParser, TInstant>(input.data(), input.size());
}

TInstant TInstant::ParseRfc822(const TStringBuf input) {
    return ParseUnsafe<TRfc822DateTimeParser, TInstant>(input.data(), input.size());
}

TInstant TInstant::ParseHttp(const TStringBuf input) {
    return ParseUnsafe<THttpDateTimeParser, TInstant>(input.data(), input.size());
}

TInstant TInstant::ParseX509Validity(const TStringBuf input) {
    switch (input.size()) {
    case 13:
        return ParseUnsafe<TX509ValidityDateTimeParser, TInstant>(input.data(), 13);
    case 15:
        return ParseUnsafe<TX509Validity4yDateTimeParser, TInstant>(input.data(), 15);
    default:
        ythrow TDateTimeParseException();
    }
}

bool TInstant::TryParseIso8601(const TStringBuf input, TInstant& instant) {
    const auto parsed = ParseUnsafe<TIso8601DateTimeParser, TInstant, false>(input.data(), input.size());
    if (TInstant::Max() == parsed) {
        return false;
    }
    instant = parsed;
    return true;
}

bool TInstant::TryParseRfc822(const TStringBuf input, TInstant& instant) {
    const auto parsed = ParseUnsafe<TRfc822DateTimeParser, TInstant, false>(input.data(), input.size());
    if (TInstant::Max() == parsed) {
        return false;
    }
    instant = parsed;
    return true;
}

bool TInstant::TryParseHttp(const TStringBuf input, TInstant& instant) {
    const auto parsed = ParseUnsafe<THttpDateTimeParser, TInstant, false>(input.data(), input.size());
    if (TInstant::Max() == parsed) {
        return false;
    }
    instant = parsed;
    return true;
}

bool TInstant::TryParseX509(const TStringBuf input, TInstant& instant) {
    TInstant parsed;
    switch (input.size()) {
        case 13:
            parsed = ParseUnsafe<TX509ValidityDateTimeParser, TInstant, false>(input.data(), 13);
            break;
        case 15:
            parsed = ParseUnsafe<TX509Validity4yDateTimeParser, TInstant, false>(input.data(), 15);
            break;
        default:
            return false;
    }
    if (TInstant::Max() == parsed) {
        return false;
    }
    instant = parsed;
    return true;
}

bool ParseRFC822DateTimeDeprecated(const char* input, time_t& utcTime) {
    return ParseRFC822DateTimeDeprecated(input, strlen(input), utcTime);
}

bool ParseISO8601DateTimeDeprecated(const char* input, time_t& utcTime) {
    return ParseISO8601DateTimeDeprecated(input, strlen(input), utcTime);
}

bool ParseHTTPDateTimeDeprecated(const char* input, time_t& utcTime) {
    return ParseHTTPDateTimeDeprecated(input, strlen(input), utcTime);
}

bool ParseX509ValidityDateTimeDeprecated(const char* input, time_t& utcTime) {
    return ParseX509ValidityDateTimeDeprecated(input, strlen(input), utcTime);
}

bool ParseRFC822DateTimeDeprecated(const char* input, size_t inputLen, time_t& utcTime) {
    try {
        utcTime = ParseUnsafe<TRfc822DateTimeParserDeprecated, TInstant>(input, inputLen).TimeT();
        return true;
    } catch (const TDateTimeParseException&) {
        return false;
    }
}

bool ParseISO8601DateTimeDeprecated(const char* input, size_t inputLen, time_t& utcTime) {
    try {
        utcTime = ParseUnsafe<TIso8601DateTimeParserDeprecated, TInstant>(input, inputLen).TimeT();
        return true;
    } catch (const TDateTimeParseException&) {
        return false;
    }
}

bool ParseHTTPDateTimeDeprecated(const char* input, size_t inputLen, time_t& utcTime) {
    try {
        utcTime = ParseUnsafe<THttpDateTimeParserDeprecated, TInstant>(input, inputLen).TimeT();
        return true;
    } catch (const TDateTimeParseException&) {
        return false;
    }
}

bool ParseX509ValidityDateTimeDeprecated(const char* input, size_t inputLen, time_t& utcTime) {
    TInstant r;
    switch (inputLen) {
    case 13:
        r = Parse<TX509ValidityDateTimeParserDeprecated, TInstant>(input, 13, TInstant::Max());
        break;
    case 15:
        r = Parse<TX509Validity4yDateTimeParserDeprecated, TInstant>(input, 15, TInstant::Max());
        break;
    default:
        return false;
    }
    if (r == TInstant::Max())
        return false;
    utcTime = r.TimeT();
    return true;
}

bool ParseRFC822DateTime(const char* input, time_t& utcTime) {
    return ParseRFC822DateTime(input, strlen(input), utcTime);
}

bool ParseISO8601DateTime(const char* input, time_t& utcTime) {
    return ParseISO8601DateTime(input, strlen(input), utcTime);
}

bool ParseHTTPDateTime(const char* input, time_t& utcTime) {
    return ParseHTTPDateTime(input, strlen(input), utcTime);
}

bool ParseX509ValidityDateTime(const char* input, time_t& utcTime) {
    return ParseX509ValidityDateTime(input, strlen(input), utcTime);
}

bool ParseRFC822DateTime(const char* input, size_t inputLen, time_t& utcTime) {
    try {
        utcTime = ParseUnsafe<TRfc822DateTimeParser, TInstant>(input, inputLen).TimeT();
        return true;
    } catch (const TDateTimeParseException&) {
        return false;
    }
}

bool ParseISO8601DateTime(const char* input, size_t inputLen, time_t& utcTime) {
    try {
        utcTime = ParseUnsafe<TIso8601DateTimeParser, TInstant>(input, inputLen).TimeT();
        return true;
    } catch (const TDateTimeParseException&) {
        return false;
    }
}

bool ParseHTTPDateTime(const char* input, size_t inputLen, time_t& utcTime) {
    try {
        utcTime = ParseUnsafe<THttpDateTimeParser, TInstant>(input, inputLen).TimeT();
        return true;
    } catch (const TDateTimeParseException&) {
        return false;
    }
}

bool ParseX509ValidityDateTime(const char* input, size_t inputLen, time_t& utcTime) {
    TInstant r;
    switch (inputLen) {
    case 13:
        r = Parse<TX509ValidityDateTimeParser, TInstant>(input, 13, TInstant::Max());
        break;
    case 15:
        r = Parse<TX509Validity4yDateTimeParser, TInstant>(input, 15, TInstant::Max());
        break;
    default:
        return false;
    }
    if (r == TInstant::Max())
        return false;
    utcTime = r.TimeT();
    return true;
}

%%{

machine TDurationParser;

include DateTimeParserCommon;


multiplier
    = '' #   >{ MultiplierPower =  6; } # work around Ragel bugs
    | 'w'  @{ MultiplierPower =  6; Multiplier = 604800; }
    | 'd'  @{ MultiplierPower =  6; Multiplier = 86400; }
    | 'h'  @{ MultiplierPower =  6; Multiplier = 3600; }
    | 'm'  @{ MultiplierPower =  6; Multiplier = 60; }
    | 's'  @{ MultiplierPower =  6; Multiplier = 1; }
    | 'ms' @{ MultiplierPower =  3; Multiplier = 1; }
    | 'us' @{ MultiplierPower =  0; Multiplier = 1; }
    | 'ns' @{ MultiplierPower = -3; Multiplier = 1; }
    ;

integer = int @{ IntegerPart = I; };

fraction = '.' digit {1,6} >clear_int $update_int @{ FractionPart = I; FractionDigits = Dc; } digit*;

duration = integer fraction? multiplier;

main := duration;

write data noerror;

}%%

TDurationParser::TDurationParser()
    : cs(0)
    , I(0)
    , Dc(0)
    , MultiplierPower(6)
    , Multiplier(1)
    , IntegerPart(0)
    , FractionPart(0)
    , FractionDigits(0)
{
    Y_UNUSED(TDurationParser_en_main);
    %% write init;
}

bool TDurationParser::ParsePart(const char* input, size_t len) {
    const char* p = input;
    const char* pe = input + len;

    %% write exec;
    return cs != %%{ write error; }%%;
}

static inline ui64 DecPower(ui64 part, i32 power) {
    if (power >= 0)
        return part * Power(10, power);
    return part / Power(10, -power);
}

TDuration TDurationParser::GetResult(TDuration defaultValue) const {
    if (cs < TDurationParser_first_final)
        return defaultValue;
    ui64 us = 0;
    us += Multiplier * DecPower(IntegerPart, MultiplierPower);
    us += Multiplier * DecPower(FractionPart, MultiplierPower - FractionDigits);
    return TDuration::MicroSeconds(us);
}

bool TDuration::TryParse(const TStringBuf input, TDuration& result) {
    TDuration r = ::Parse<TDurationParser, TDuration>(input.data(), input.size(), TDuration::Max());
    if (r == TDuration::Max())
        return false;
    result = r;
    return true;
}

TDuration TDuration::Parse(const TStringBuf input) {
    return ParseUnsafe<TDurationParser, TDuration>(input.data(), input.size());
}