#pragma once

#include "systime.h"

#include <util/str_stl.h>
#include <util/system/platform.h>
#include <util/system/datetime.h>
#include <util/generic/string.h>
#include <util/generic/strbuf.h>
#include <util/generic/ylimits.h>
#include <util/generic/utility.h>
#include <util/generic/typetraits.h>
#include <util/generic/yexception.h>

#include <chrono>

#if defined(__cpp_lib_three_way_comparison)
    #include <compare>
#endif

#include <ctime>
#include <cstdio>
#include <ratio>

#include <time.h>

#ifdef _MSC_VER
    #pragma warning(push)
    #pragma warning(disable : 4244) // conversion from 'time_t' to 'long', possible loss of data
#endif                              // _MSC_VER

// Microseconds since epoch
class TInstant;

// Duration is microseconds. Could be used to store timeouts, for example.
class TDuration;

/// Current time
static inline TInstant Now() noexcept;

/// Use Now() method to obtain current time instead of *Seconds() unless you understand what are you doing.

class TDateTimeParseException: public yexception {
};

const int DATE_BUF_LEN = 4 + 2 + 2 + 1; // [YYYYMMDD*]

constexpr long seconds(const struct tm& theTm) {
    return 60 * (60 * theTm.tm_hour + theTm.tm_min) + theTm.tm_sec;
}

void sprint_gm_date(char* buf, time_t when, long* sec = nullptr);
bool sscan_date(const char* date, struct tm& theTm);

const int DATE_8601_LEN = 21; // strlen("YYYY-MM-DDThh:mm:ssZ") = 20 + '\0'

size_t FormatDate8601(char* buf, size_t len, time_t when);

inline void sprint_date8601(char* buf, time_t when) {
    buf[FormatDate8601(buf, 64, when)] = 0;
}

bool ParseISO8601DateTimeDeprecated(const char* date, time_t& utcTime);
bool ParseISO8601DateTimeDeprecated(const char* date, size_t dateLen, time_t& utcTime);
bool ParseRFC822DateTimeDeprecated(const char* date, time_t& utcTime);
bool ParseRFC822DateTimeDeprecated(const char* date, size_t dateLen, time_t& utcTime);
bool ParseHTTPDateTimeDeprecated(const char* date, time_t& utcTime);
bool ParseHTTPDateTimeDeprecated(const char* date, size_t dateLen, time_t& utcTime);
bool ParseX509ValidityDateTimeDeprecated(const char* date, time_t& utcTime);
bool ParseX509ValidityDateTimeDeprecated(const char* date, size_t dateLen, time_t& utcTime);

bool ParseISO8601DateTime(const char* date, time_t& utcTime);
bool ParseISO8601DateTime(const char* date, size_t dateLen, time_t& utcTime);
bool ParseRFC822DateTime(const char* date, time_t& utcTime);
bool ParseRFC822DateTime(const char* date, size_t dateLen, time_t& utcTime);
bool ParseHTTPDateTime(const char* date, time_t& utcTime);
bool ParseHTTPDateTime(const char* date, size_t dateLen, time_t& utcTime);
bool ParseX509ValidityDateTime(const char* date, time_t& utcTime);
bool ParseX509ValidityDateTime(const char* date, size_t dateLen, time_t& utcTime);

constexpr long TVdiff(timeval r1, timeval r2) {
    return (1000000 * (r2.tv_sec - r1.tv_sec) + (r2.tv_usec - r1.tv_usec));
}

TString Strftime(const char* format, const struct tm* tm);

// Use functions below instead of sprint_date (check IGNIETFERRO-892 for details)
void DateToString(char* buf, const struct tm& theTm);
void DateToString(char* buf, time_t when, long* sec = nullptr);
TString DateToString(const struct tm& theTm);
TString DateToString(time_t when, long* sec = nullptr);
// Year in format "YYYY", throws an exception if year not in range [0, 9999]
TString YearToString(const struct tm& theTm);
TString YearToString(time_t when);

template <class S>
class TTimeBase {
public:
    using TValue = ui64;

protected:
    constexpr TTimeBase(const TValue& value) noexcept
        : Value_(value)
    {
    }

public:
    constexpr TTimeBase() noexcept
        : Value_(0)
    {
    }

    constexpr TTimeBase(const struct timeval& tv) noexcept
        : Value_(tv.tv_sec * (ui64)1000000 + tv.tv_usec)
    {
    }

    constexpr TValue GetValue() const noexcept {
        return Value_;
    }

    constexpr double SecondsFloat() const noexcept {
        return Value_ * (1 / 1000000.0);
    }

    constexpr double MillisecondsFloat() const noexcept {
        return Value_ * (1 / 1000.0);
    }

    constexpr TValue MicroSeconds() const noexcept {
        return Value_;
    }

    constexpr TValue MilliSeconds() const noexcept {
        return MicroSeconds() / 1000;
    }

    constexpr TValue Seconds() const noexcept {
        return MilliSeconds() / 1000;
    }

    constexpr TValue Minutes() const noexcept {
        return Seconds() / 60;
    }

    constexpr TValue Hours() const noexcept {
        return Minutes() / 60;
    }

    constexpr TValue Days() const noexcept {
        return Hours() / 24;
    }

    constexpr TValue NanoSeconds() const noexcept {
        return MicroSeconds() >= (Max<TValue>() / (TValue)1000) ? Max<TValue>() : MicroSeconds() * (TValue)1000;
    }

    constexpr ui32 MicroSecondsOfSecond() const noexcept {
        return MicroSeconds() % (TValue)1000000;
    }

    constexpr ui32 MilliSecondsOfSecond() const noexcept {
        return MicroSecondsOfSecond() / (TValue)1000;
    }

    constexpr ui32 NanoSecondsOfSecond() const noexcept {
        return MicroSecondsOfSecond() * (TValue)1000;
    }

    constexpr explicit operator bool() const noexcept {
        return Value_;
    }

protected:
    TValue Value_; // microseconds count
};

namespace NDateTimeHelpers {
    template <typename T>
    struct TPrecisionHelper {
        using THighPrecision = ui64;
    };

    template <>
    struct TPrecisionHelper<float> {
        using THighPrecision = double;
    };

    template <>
    struct TPrecisionHelper<double> {
        using THighPrecision = double;
    };
}

class TDuration: public TTimeBase<TDuration> {
    using TBase = TTimeBase<TDuration>;

private:
    /**
     * private construct from microseconds
     */
    constexpr explicit TDuration(TValue value) noexcept
        : TBase(value)
    {
    }

public:
    constexpr TDuration() noexcept {
    }

    constexpr TDuration(const struct timeval& tv) noexcept
        : TBase(tv)
    {
    }

    /**
     * TDuration is compatible with std::chrono::duration:
     *   it can be constructed and compared with std::chrono::duration.
     * But there is two significant and dangerous differencies between them:
     *   1) TDuration is never negative and use saturation between 0 and maximum value.
     *      std::chrono::duration can be negative and can overflow.
     *   2) TDuration uses integer number of microseconds.
     *      std::chrono::duration is flexible, can be integer of floating point,
     *      can have different precisions.
     * So when casted from std::chrono::duration to TDuration value is clamped and rounded.
     * In arithmethic operations std::chrono::duration argument is only rounded,
     *   result is TDuration and it clamped and rounded.
     * In comparisons std::chrono::duration argument is rounded.
     */
    template <typename T, typename TRatio>
    constexpr TDuration(std::chrono::duration<T, TRatio> duration) noexcept {
        static_assert(
            std::ratio_greater_equal<TRatio, std::micro>::value &&
                (!std::is_floating_point<T>::value || std::ratio_greater<TRatio, std::micro>::value),
            "Extremely likely it is loss of precision, because TDuration stores microseconds. "
            "Cast you duration explicitly to microseconds if you really need it.");

        if (duration.count() < 0) {
            *this = TDuration::Zero(); // clamp from the bottom
        } else {
            if
#if !defined(__NVCC__)
                constexpr
#endif
                /* if [constexpr] */ (std::ratio_greater<TRatio, std::micro>::value || std::is_floating_point<T>::value) {
                // clamp from the top
                using TCommonDuration = std::chrono::duration<typename std::common_type<T, TValue>::type, TRatio>;
                constexpr auto maxDuration = std::chrono::duration<TValue, std::micro>(::Max<TValue>());
                if (std::chrono::duration_cast<TCommonDuration>(duration) >= std::chrono::duration_cast<TCommonDuration>(maxDuration)) {
                    *this = TDuration::Max();
                    return;
                }
            }
            const TValue us = std::chrono::duration_cast<std::chrono::duration<TValue, std::micro>>(duration).count();
            *this = TDuration::MicroSeconds(us);
        }
    }

    static constexpr TDuration FromValue(TValue value) noexcept {
        return TDuration(value);
    }

    static constexpr TDuration MicroSeconds(ui64 us) noexcept {
        return TDuration(us);
    }

    /* noexcept(false) as conversion from T might throw, for example FromString("abc") */
    template <typename T>
    static constexpr TDuration MilliSeconds(T ms) noexcept(false) {
        return MicroSeconds((ui64)(typename NDateTimeHelpers::TPrecisionHelper<T>::THighPrecision(ms) * 1000));
    }

    using TBase::Days;
    using TBase::Hours;
    using TBase::MicroSeconds;
    using TBase::MilliSeconds;
    using TBase::Minutes;
    using TBase::Seconds;

    /// DeadLineFromTimeOut
    inline TInstant ToDeadLine() const;
    constexpr TInstant ToDeadLine(TInstant now) const;

    static constexpr TDuration Max() noexcept {
        return TDuration(::Max<TValue>());
    }

    static constexpr TDuration Zero() noexcept {
        return TDuration();
    }

    /* noexcept(false) as conversion from T might throw, for example FromString("abc") */
    template <typename T>
    static constexpr TDuration Seconds(T s) noexcept(false) {
        return MilliSeconds(typename NDateTimeHelpers::TPrecisionHelper<T>::THighPrecision(s) * 1000);
    }

    static constexpr TDuration Minutes(ui64 m) noexcept {
        return Seconds(m * 60);
    }

    static constexpr TDuration Hours(ui64 h) noexcept {
        return Minutes(h * 60);
    }

    static constexpr TDuration Days(ui64 d) noexcept {
        return Hours(d * 24);
    }

    /// parses strings like 10s, 15ms, 15.05s, 20us, or just 25 (s). See parser_ut.cpp for details
    static TDuration Parse(const TStringBuf input);

    static bool TryParse(const TStringBuf input, TDuration& result);

    // note global Out method is defined for TDuration, so it could be written to IOutputStream as text

    template <class T>
    inline TDuration& operator+=(const T& t) noexcept {
        return (*this = (*this + t));
    }

    template <class T>
    inline TDuration& operator-=(const T& t) noexcept {
        return (*this = (*this - t));
    }

    template <class T>
    inline TDuration& operator*=(const T& t) noexcept {
        return (*this = (*this * t));
    }

    template <class T>
    inline TDuration& operator/=(const T& t) noexcept {
        return (*this = (*this / t));
    }

    TString ToString() const;
};

Y_DECLARE_PODTYPE(TDuration);

template <>
struct THash<TDuration> {
    size_t operator()(const TDuration& key) const {
        return THash<TDuration::TValue>()(key.GetValue());
    }
};

/// TInstant and TDuration are guaranteed to have same precision
class TInstant: public TTimeBase<TInstant> {
    using TBase = TTimeBase<TInstant>;

private:
    /**
     * private construct from microseconds since epoch
     */
    constexpr explicit TInstant(TValue value) noexcept
        : TBase(value)
    {
    }

public:
    constexpr TInstant() noexcept {
    }

    constexpr TInstant(const struct timeval& tv) noexcept
        : TBase(tv)
    {
    }

    static constexpr TInstant FromValue(TValue value) noexcept {
        return TInstant(value);
    }

    static inline TInstant Now() {
        return TInstant::MicroSeconds(::MicroSeconds());
    }

    using TBase::Days;
    using TBase::Hours;
    using TBase::MicroSeconds;
    using TBase::MilliSeconds;
    using TBase::Minutes;
    using TBase::Seconds;

    static constexpr TInstant Max() noexcept {
        return TInstant(::Max<TValue>());
    }

    static constexpr TInstant Zero() noexcept {
        return TInstant();
    }

    /// us since epoch
    static constexpr TInstant MicroSeconds(ui64 us) noexcept {
        return TInstant(us);
    }

    /// ms since epoch
    static constexpr TInstant MilliSeconds(ui64 ms) noexcept {
        return MicroSeconds(ms * 1000);
    }

    /// seconds since epoch
    static constexpr TInstant Seconds(ui64 s) noexcept {
        return MilliSeconds(s * 1000);
    }

    /// minutes since epoch
    static constexpr TInstant Minutes(ui64 m) noexcept {
        return Seconds(m * 60);
    }

    /// hours since epoch
    static constexpr TInstant Hours(ui64 h) noexcept {
        return Minutes(h * 60);
    }

    /// days since epoch
    static constexpr TInstant Days(ui64 d) noexcept {
        return Hours(d * 24);
    }

    constexpr time_t TimeT() const noexcept {
        return (time_t)Seconds();
    }

    inline struct timeval TimeVal() const noexcept {
        struct timeval tv;
        ::Zero(tv);
        tv.tv_sec = TimeT();
        tv.tv_usec = MicroSecondsOfSecond();
        return tv;
    }

    inline struct tm* LocalTime(struct tm* tm) const noexcept {
        time_t clock = Seconds();
        return localtime_r(&clock, tm);
    }

    inline struct tm* GmTime(struct tm* tm) const noexcept {
        time_t clock = Seconds();
        return GmTimeR(&clock, tm);
    }

    /**
     * Formats the instant using the UTC time zone, with microsecond precision.
     *
     * @returns An ISO 8601 formatted string, e.g. '2015-11-21T23:30:27.991669Z'.
     * @note Global Out method is defined to TInstant, so it can be written as text to IOutputStream.
     */
    TString ToString() const;

    /**
     * Formats the instant using the UTC time zone.
     *
     * @returns An RFC822 formatted string, e.g. 'Sun, 06 Nov 1994 08:49:37 GMT'.
     */
    TString ToRfc822String() const;

    /**
     * Formats the instant using the UTC time zone, with second precision.
     *
     * @returns An ISO 8601 formatted string, e.g. '2015-11-21T23:30:27Z'.
     */
    TString ToStringUpToSeconds() const;

    /**
     * Formats the instant using the system time zone, with microsecond precision.
     *
     * @returns An ISO 8601 / RFC 3339 formatted string,
     * e.g. '2015-11-22T04:30:27.991669+05:00'.
     */
    TString ToIsoStringLocal() const;

    /**
     * Formats the instant using the system time zone, with microsecond precision.
     *
     * @returns A semi-ISO 8601 formatted string with timezone without colon,
     * e.g. '2015-11-22T04:30:27.991669+0500'.
     */
    TString ToStringLocal() const;

    /**
     * Formats the instant using the system time zone.
     *
     * @returns An RFC822 formatted string, e.g. 'Sun, 06 Nov 1994 08:49:37 MSK'.
     */
    TString ToRfc822StringLocal() const;

    /**
     * Formats the instant using the system time zone, with second precision.
     *
     * @returns An ISO 8601 / RFC 3339 formatted string,
     * e.g. '2015-11-22T04:30:27+05:00'.
     */
    TString ToIsoStringLocalUpToSeconds() const;

    /**
     * Formats the instant using the system time zone, with second precision.
     *
     * @returns A semi-ISO 8601 formatted string with timezone without colon,
     * e.g. '2015-11-22T04:30:27+0500'.
     */
    TString ToStringLocalUpToSeconds() const;

    TString FormatLocalTime(const char* format) const noexcept;
    TString FormatGmTime(const char* format) const noexcept;

    /// See #TryParseIso8601.
    static TInstant ParseIso8601(TStringBuf);
    /// See #TryParseRfc822.
    static TInstant ParseRfc822(TStringBuf);
    /// See #TryParseHttp.
    static TInstant ParseHttp(TStringBuf);
    /// See #TryParseX509.
    static TInstant ParseX509Validity(TStringBuf);

    /// ISO 8601 Representation of Dates and Times
    ///
    /// @link https://www.iso.org/standard/40874.html Description of format.
    static bool TryParseIso8601(TStringBuf input, TInstant& instant);

    /// RFC 822 Date and Time specification
    ///
    /// @link https://tools.ietf.org/html/rfc822#section-5 Description of format.
    static bool TryParseRfc822(TStringBuf input, TInstant& instant);

    /// RFC 2616 3.3.1 Full Date
    ///
    /// @link https://tools.ietf.org/html/rfc2616#section-3.3.1 Description of format.
    static bool TryParseHttp(TStringBuf input, TInstant& instant);

    /// X.509 certificate validity time (see rfc5280 4.1.2.5.*)
    ///
    /// @link https://tools.ietf.org/html/rfc5280#section-4.1.2.5 Description of format.
    static bool TryParseX509(TStringBuf input, TInstant& instant);

    static TInstant ParseIso8601Deprecated(TStringBuf);
    static TInstant ParseRfc822Deprecated(TStringBuf);
    static TInstant ParseHttpDeprecated(TStringBuf);
    static TInstant ParseX509ValidityDeprecated(TStringBuf);

    static bool TryParseIso8601Deprecated(TStringBuf input, TInstant& instant);
    static bool TryParseRfc822Deprecated(TStringBuf input, TInstant& instant);
    static bool TryParseHttpDeprecated(TStringBuf input, TInstant& instant);
    static bool TryParseX509Deprecated(TStringBuf input, TInstant& instant);

    template <class T>
    inline TInstant& operator+=(const T& t) noexcept {
        return (*this = (*this + t));
    }

    template <class T>
    inline TInstant& operator-=(const T& t) noexcept {
        return (*this = (*this - t));
    }
};

Y_DECLARE_PODTYPE(TInstant);

template <>
struct THash<TInstant> {
    size_t operator()(const TInstant& key) const {
        return THash<TInstant::TValue>()(key.GetValue());
    }
};

namespace NPrivate {
    template <bool PrintUpToSeconds, bool iso>
    struct TPrintableLocalTime {
        TInstant MomentToPrint;

        constexpr explicit TPrintableLocalTime(TInstant momentToPrint)
            : MomentToPrint(momentToPrint)
        {
        }
    };
}

/** @name Helpers for printing local times to `IOutputStream`s.
 *        The FormatLocal* functions create an opaque object that, when written to
 *        a `IOutputStream`, outputs this instant as an ISO 8601 formatted string
 *        using the system time zone.
 *
 *  @note The only reason behind this set of functions is to avoid excessive
 *        allocations when you directly print the local time to a stream.
 *
 *        If you need something beyond just printing the value or your code
 *        is not performance-critical, feel free to use the corresponding
 *        TInstant::ToString*() functions.
 */
///@{
/// @see TInstant::ToIsoStringLocal()
::NPrivate::TPrintableLocalTime<false, true> FormatIsoLocal(TInstant instant);
/// @see TInstant::ToStringLocal()
::NPrivate::TPrintableLocalTime<false, false> FormatLocal(TInstant instant);
/// @see TInstant::ToIsoStringLocalUpToSeconds()
::NPrivate::TPrintableLocalTime<true, true> FormatIsoLocalUpToSeconds(TInstant instant);
/// @see TInstant::ToStringLocalUpToSeconds()
::NPrivate::TPrintableLocalTime<true, false> FormatLocalUpToSeconds(TInstant instant);
///@}

template <class S>
static constexpr bool operator<(const TTimeBase<S>& l, const TTimeBase<S>& r) noexcept {
    return l.GetValue() < r.GetValue();
}

template <class S>
static constexpr bool operator<=(const TTimeBase<S>& l, const TTimeBase<S>& r) noexcept {
    return l.GetValue() <= r.GetValue();
}

template <class S>
static constexpr bool operator==(const TTimeBase<S>& l, const TTimeBase<S>& r) noexcept {
    return l.GetValue() == r.GetValue();
}

template <class S>
static constexpr bool operator!=(const TTimeBase<S>& l, const TTimeBase<S>& r) noexcept {
    return l.GetValue() != r.GetValue();
}

template <class S>
static constexpr bool operator>(const TTimeBase<S>& l, const TTimeBase<S>& r) noexcept {
    return l.GetValue() > r.GetValue();
}

template <class S>
static constexpr bool operator>=(const TTimeBase<S>& l, const TTimeBase<S>& r) noexcept {
    return l.GetValue() >= r.GetValue();
}

namespace NDateTimeHelpers {
    template <typename T>
    static constexpr T SumWithSaturation(T a, T b) {
        static_assert(!std::numeric_limits<T>::is_signed, "expect !std::numeric_limits<T>::is_signed");

        return Max<T>() - a < b ? Max<T>() : a + b;
    }

    template <typename T>
    static constexpr T DiffWithSaturation(T a, T b) {
        static_assert(!std::numeric_limits<T>::is_signed, "expect !std::numeric_limits<T>::is_signed");

        return a < b ? 0 : a - b;
    }
}

constexpr TDuration operator-(const TInstant& l, const TInstant& r) noexcept {
    return TDuration::FromValue(::NDateTimeHelpers::DiffWithSaturation(l.GetValue(), r.GetValue()));
}

constexpr TInstant operator+(const TInstant& i, const TDuration& d) noexcept {
    return TInstant::FromValue(::NDateTimeHelpers::SumWithSaturation(i.GetValue(), d.GetValue()));
}

constexpr TInstant operator-(const TInstant& i, const TDuration& d) noexcept {
    return TInstant::FromValue(::NDateTimeHelpers::DiffWithSaturation(i.GetValue(), d.GetValue()));
}

constexpr TDuration operator-(const TDuration& l, const TDuration& r) noexcept {
    return TDuration::FromValue(::NDateTimeHelpers::DiffWithSaturation(l.GetValue(), r.GetValue()));
}

constexpr TDuration operator+(const TDuration& l, const TDuration& r) noexcept {
    return TDuration::FromValue(::NDateTimeHelpers::SumWithSaturation(l.GetValue(), r.GetValue()));
}

template <typename T, typename TRatio>
constexpr bool operator==(const TDuration& l, const std::chrono::duration<T, TRatio>& r) noexcept {
    return r.count() >= 0 && l == TDuration(r);
}

#if defined(__cpp_lib_three_way_comparison)

template <typename T, typename TRatio>
constexpr std::strong_ordering operator<=>(const TDuration& l, const std::chrono::duration<T, TRatio>& r) noexcept {
    if (r.count() < 0) {
        return std::strong_ordering::greater;
    }
    return l.GetValue() <=> TDuration(r).GetValue();
}

#else

template <typename T, typename TRatio>
constexpr bool operator<(const TDuration& l, const std::chrono::duration<T, TRatio>& r) noexcept {
    return r.count() >= 0 && l < TDuration(r);
}

template <typename T, typename TRatio>
constexpr bool operator<=(const TDuration& l, const std::chrono::duration<T, TRatio>& r) noexcept {
    return r.count() >= 0 && l <= TDuration(r);
}

template <typename T, typename TRatio>
constexpr bool operator!=(const TDuration& l, const std::chrono::duration<T, TRatio>& r) noexcept {
    return !(l == r);
}

template <typename T, typename TRatio>
constexpr bool operator>(const TDuration& l, const std::chrono::duration<T, TRatio>& r) noexcept {
    return r.count() < 0 || l > TDuration(r);
}

template <typename T, typename TRatio>
constexpr bool operator>=(const TDuration& l, const std::chrono::duration<T, TRatio>& r) noexcept {
    return r.count() < 0 || l >= TDuration(r);
}

template <typename T, typename TRatio>
constexpr bool operator<(const std::chrono::duration<T, TRatio>& l, const TDuration& r) noexcept {
    return r > l;
}

template <typename T, typename TRatio>
constexpr bool operator<=(const std::chrono::duration<T, TRatio>& l, const TDuration& r) noexcept {
    return r >= l;
}

template <typename T, typename TRatio>
constexpr bool operator==(const std::chrono::duration<T, TRatio>& l, const TDuration& r) noexcept {
    return r == l;
}

template <typename T, typename TRatio>
constexpr bool operator!=(const std::chrono::duration<T, TRatio>& l, const TDuration& r) noexcept {
    return r != l;
}

template <typename T, typename TRatio>
constexpr bool operator>(const std::chrono::duration<T, TRatio>& l, const TDuration& r) noexcept {
    return r < l;
}

template <typename T, typename TRatio>
constexpr bool operator>=(const std::chrono::duration<T, TRatio>& l, const TDuration& r) noexcept {
    return r >= l;
}

#endif

template <typename T, typename TRatio>
constexpr TDuration operator+(const TDuration& l, const std::chrono::duration<T, TRatio>& r) noexcept {
    return r < r.zero() ? l - TDuration(-r) : l + TDuration(r);
}

template <typename T, typename TRatio>
constexpr TDuration operator+(const std::chrono::duration<T, TRatio>& l, const TDuration& r) noexcept {
    return r + l;
}

template <typename T, typename TRatio>
constexpr TDuration operator-(const TDuration& l, const std::chrono::duration<T, TRatio>& r) noexcept {
    return l + (-r);
}

template <typename T, typename TRatio>
constexpr TDuration operator-(const std::chrono::duration<T, TRatio>& l, const TDuration& r) noexcept {
    return TDuration(l) - r;
}

template <typename T, typename TRatio>
constexpr TInstant operator+(const TInstant& l, const std::chrono::duration<T, TRatio>& r) noexcept {
    return r < r.zero() ? l - TDuration(-r) : l + TDuration(r);
}

template <typename T, typename TRatio>
constexpr TInstant operator-(const TInstant& l, const std::chrono::duration<T, TRatio>& r) noexcept {
    return l + (-r);
}

template <class T>
inline TDuration operator*(TDuration d, T t) noexcept {
    Y_ASSERT(t >= T());
    Y_ASSERT(t == T() || Max<TDuration::TValue>() / t >= d.GetValue());
    return TDuration::FromValue(d.GetValue() * t);
}

template <>
inline TDuration operator*(TDuration d, double t) noexcept {
    Y_ASSERT(t >= 0 && MaxFloor<TDuration::TValue>() >= d.GetValue() * t);
    return TDuration::FromValue(d.GetValue() * t);
}

template <>
inline TDuration operator*(TDuration d, float t) noexcept {
    return d * static_cast<double>(t);
}

template <class T>
inline TDuration operator*(T t, TDuration d) noexcept {
    return d * t;
}

template <class T, std::enable_if_t<!std::is_same<TDuration, T>::value, int> = 0>
constexpr TDuration operator/(const TDuration& d, const T& t) noexcept {
    return TDuration::FromValue(d.GetValue() / t);
}

constexpr double operator/(const TDuration& x, const TDuration& y) noexcept {
    return static_cast<double>(x.GetValue()) / static_cast<double>(y.GetValue());
}

inline TInstant TDuration::ToDeadLine() const {
    return ToDeadLine(TInstant::Now());
}

constexpr TInstant TDuration::ToDeadLine(TInstant now) const {
    return now + *this;
}

void Sleep(TDuration duration);
void SleepUntil(TInstant instant);

static inline TInstant Now() noexcept {
    return TInstant::Now();
}

#ifdef _MSC_VER
    #pragma warning(pop)
#endif // _MSC_VER