NAME
    DateTime::Lite - Lightweight, low-dependency drop-in replacement for
    DateTime

SYNOPSIS
        use DateTime::Lite;

        my $dt = DateTime::Lite->new(
            year       => 2026,
            month      => 4,
            day        => 10,
            hour       => 6,
            minute     => 10,
            second     => 30,
            nanosecond => 0,
            time_zone  => 'Asia/Tokyo',
            locale     => 'ja-JP',
        ) || die( DateTime::Lite->error );

        my $now   = DateTime::Lite->now( time_zone => 'UTC' );
        my $today = DateTime::Lite->today( time_zone => 'Asia/Tokyo' );

        # Timezone from GPS coordinates (nearest IANA zone by haversine distance)
        use DateTime::Lite::TimeZone;
        my $tz = DateTime::Lite::TimeZone->new(
            latitude  => 35.658581,
            longitude => 139.745433,   # Tokyo Tower
        );
        my $dt_local = DateTime::Lite->now( time_zone => $tz );
        say $dt_local->time_zone_long_name;  # Asia/Tokyo

        # BCP47 -u-tz- locale extension: timezone inferred from locale tag
        my $dt_bcp47 = DateTime::Lite->now( locale => 'he-IL-u-ca-hebrew-tz-jeruslm' );
        say $dt_bcp47->time_zone_long_name;  # Asia/Jerusalem

        my $from_epoch = DateTime::Lite->from_epoch( epoch => time() );
        my $from_doy   = DateTime::Lite->from_day_of_year(
            year        => 2026,
            day_of_year => 100,
            time_zone   => 'UTC',
        );
        my $eom = DateTime::Lite->last_day_of_month( year => 2026, month => 2 );

        # Cloning (using XS)
        my $copy = $dt->clone;

        # Accessors
        $dt->year;          # 2026
        $dt->month;         # 4 (can be 1-12)
        # alias
        $dt->mon;
        $dt->day;           # 10 (can be 1-31)
        # alias
        $dt->day_of_month
        $dt->hour;          # 6 (can be 0-23)
        $dt->minute;        # 10 (can be 0-59)
        $dt->second;        # 30 (can be 0-61 only on leap-second days)
        $dt->nanosecond;    # 0 (can be 0-999_999_999)

        $dt->day_of_week;   # 5 (1=Mon .. 7=Sun)
        $dt->day_of_year;   # 99 (1-366)
        $dt->day_abbr;      # "金" (but would be "Fri" if the locale were 'en-US')
        $dt->day_name;      # "金曜日" (but would be "Friday" if the locale were 'en-US')
        $dt->month_0;       # 3 (can be 0-11)
        # alias
        $dt->mon_0;
        $dt->month_abbr;    # "4月" (but would be "Apr" if the locale were 'en-US')
        $dt->month_name;    # "4月" (but would be "April" if the locale were 'en-US')
        $dt->quarter;       # 2 (can be 1-4)
        $dt->week;          # ( 2026, 15 ) ($week_year, $week_number)
        $dt->week_number;   # 15 (can be 1-53)
        $dt->week_year;     # 2026 (ISO week year)

        $dt->epoch;         # 1775769030 (Unix timestamp; integer)
        $dt->hires_epoch;   # 1775769030 (floating-point epoch; IEEE 754 double, ~microsecond precision)
        # hires_epoch: limited to ~microsecond precision by IEEE 754 double
        # For full nanosecond precision, combine epoch() and nanosecond() manually:
        say sprintf "%d.%09d", $dt->epoch, $dt->nanosecond;  # 1775769030.000000005
        # or
        # use Math::BigFloat;
        # say Math::BigFloat->new( $dt->epoch ) + Math::BigFloat->new( $dt->nanosecond ) / 1_000_000_000
        # -> 1775769030.0000001
        $dt->jd;            # 2461140.38229167 (Julian Day Number)
        $dt->mjd;           # 61139.8822916667 (Modified Julian Day)

        $dt->offset;                # 32400 (UTC offset in seconds)
        $dt->time_zone;             # "Asia/Tokyo" (DateTime::Lite::TimeZone object)
        $dt->time_zone_long_name;   # "Asia/Tokyo"
        $dt->time_zone_short_name;  # "JST"
        $dt->locale;                # ja-JP (DateTime::Locale::FromCLDR object)
        $dt->is_dst;                # 1 or 0
        $dt->is_leap_year;          # 1 or 0
        $dt->is_finite;             # 1 for normal objects
        $dt->is_infinite;           # 0 for normal objects

        # Internal Rata Die representation
        my( $days, $secs, $ns ) = $dt->utc_rd_values;         # 739715, 76230, 0
        my $rd_secs             = $dt->utc_rd_as_seconds;     # 63911452230
        my( $ld, $ls, $lns )    = $dt->local_rd_values;       # 739716, 22230, 0
        my $local_secs          = $dt->local_rd_as_seconds;   # 63911484630
        my $utc_y               = $dt->utc_year;              # 2027

        # Formatting
        $dt->iso8601;                        # "2026-04-10T06:10:30"
        # alias
        $dt->datetime;
        $dt->ymd;                            # "2026-04-10"
        $dt->ymd('/');                       # "2026/04/10"
        $dt->hms;                            # "06:10:30"
        $dt->dmy('.');                       # "10.04.2026"
        $dt->mdy('-');                       # "10-04-2026"
        $dt->rfc3339;                        # "2026-04-10T06:10:30+09:00"
        $dt->strftime('%Y-%m-%d %H:%M:%S');  # "2026-04-10 06:10:30"
        $dt->format_cldr('yyyy/MM/dd');      # "2026/04/10" (Unicode CLDR pattern)
        "$dt";                               # stringify via iso8601 (or formatter)

        # Arithmetic
        $dt->add( years => 1, months  => 2, days    => 3,
                  hours => 4, minutes => 5, seconds => 6 );
        $dt->subtract( weeks => 2 );

        my $dur = DateTime::Lite::Duration->new( months => 6 );
        $dt->add_duration( $dur );
        $dt->subtract_duration( $dur );

        my $diff     = $dt->subtract_datetime( $other );           # Duration
        my $abs_diff = $dt->subtract_datetime_absolute( $other );  # clock-only Duration
        my $dd       = $dt->delta_days( $other );
        my $dmd      = $dt->delta_md( $other );
        my $dms      = $dt->delta_ms( $other );

        # Mutators
        $dt->set( year => 2027, month => 1, day => 1 );
        $dt->set_year(2027);
        $dt->set_month(1);
        $dt->set_day(1);
        $dt->set_hour(0);
        $dt->set_minute(0);
        $dt->set_second(0);
        $dt->set_nanosecond(0);
        $dt->set_time_zone('America/New_York');
        $dt->set_locale('en-US');  # sets a new DateTime::Locale::FromCLDR object
        $dt->set_formatter( $formatter );
        $dt->truncate( to => 'day' );   # 'year','month','week','day','hour','minute','second'

        # Works for second, minute, hour, day, week, local_week, month, quarter,
        # year, decade, century
        $dt->end_of( 'month' );
        say $dt;  # 2026-04-30T23:59:59.999999999
        $dt->start_of( 'month' );
        say $dt;  # 2026-04-01T00:00:00

        # Comparison
        my @sorted = sort { $a <=> $b } @datetimes;  # overloaded <=>
        DateTime::Lite->compare( $dt1, $dt2 );       # -1, 0, 1
        DateTime::Lite->compare_ignore_floating( $dt1, $dt2 );
        $dt->is_between( $lower, $upper );

        # Class-level settings
        DateTime::Lite->DefaultLocale('fr-FR');
        my $class = $dt->duration_class;  # 'DateTime::Lite::Duration'

        # Constants
        DateTime::Lite::INFINITY();        # +Inf
        DateTime::Lite::NEG_INFINITY();    # -Inf
        DateTime::Lite::NAN();             # NaN
        DateTime::Lite::MAX_NANOSECONDS(); # 1_000_000_000
        DateTime::Lite::SECONDS_PER_DAY(); # 86400

        # Error handling
        my $dt2 = DateTime::Lite->new( %bad_args ) ||
            die( DateTime::Lite->error );
        # Chaining: bad calls return a NullObject so the chain continues safely;
        # check the return value of the last call in the chain.
        my $result = $dt->some_method->another_method ||
            die( $dt->error );

VERSION
        v0.6.1

DESCRIPTION
    "DateTime::Lite" is a lightweight, memory-efficient, drop-in replacement
    for DateTime with the following design goals:

    Low dependency footprint
        Runtime dependencies are limited to: DateTime::Lite::TimeZone
        (bundled SQLite timezone data, with automatic fallback to
        DateTime::TimeZone if DBD::SQLite is unavailable),
        DateTime::Locale::FromCLDR (locale data via Locale::Unicode::Data's
        SQLite backend), Locale::Unicode, and core modules.

        The heavy Specio, Params::ValidationCompiler, Try::Tiny, and
        "namespace::autoclean" are eliminated entirely.

    Low memory footprint
        "DateTime" loads a cascade of modules which inflates %INC
        significantly. "DateTime::Lite" avoids this via selective lazy
        loading.

    Accurate timezone data from TZif binaries
        "DateTime::TimeZone" derives its zone data from the IANA Olson
        *source* files ("africa", "northamerica", etc.) via a custom text
        parser ("DateTime::TimeZone::OlsonDB"), then pre-generates one ".pm"
        file per zone at distribution build time. This introduces an extra
        parsing step that is not part of the official IANA toolchain.

        "DateTime::Lite::TimeZone" instead compiles the IANA source files
        with zic(1), which is the official IANA compiler, and reads the
        resulting TZif binary files directly, following RFC 9636
        <https://www.rfc-editor.org/rfc/rfc9636> (TZif versions 1 through
        4). Timestamps are stored as signed 64-bit integers, giving a range
        of roughly "+/-" 292 billion years.

        Crucially, the POSIX footer TZ string embedded in every TZif v2+
        file, such as "EST5EDT,M3.2.0,M11.1.0", is extracted and stored in
        the SQLite database.

        This string encodes the recurring DST rule for all dates beyond the
        last explicit transition. At runtime, "DateTime::Lite::TimeZone"
        evaluates the footer rule via an XS implementation of the IANA
        "tzcode" reference algorithm (see "dtl_posix.h", derived from
        "tzcode2026a/localtime.c", public domain), ensuring correct timezone
        calculations for any date in the future without expanding the full
        transition table.

    XS-accelerated hot paths
        The XS layer covers all CPU-intensive calendar arithmetic
        ("_rd2ymd", "_ymd2rd", "_seconds_as_components", all leap-second
        helpers), plus new functions not in the original: "_rd_to_epoch",
        "_epoch_to_rd", "_normalize_nanoseconds", and "_compare_rd".

    Compatible API
        The public API mirrors DateTime as closely as possible, so existing
        code using "DateTime" should work with "DateTime::Lite" as a drop-in
        replacement.

    Full Unicode CLDR / BCP 47 locale support
        "DateTime" is limited to the set of pre-generated
        "DateTime::Locale::*" modules, one per locale. "DateTime::Lite"
        accepts any valid Unicode CLDR / BCP 47 locale tag, including
        complex forms with Unicode extensions ("-u-"), transform extensions
        ("-t-"), and script subtags.

            my $dt = DateTime::Lite->now( locale => 'en' );    # simple form
            my $dt = DateTime::Lite->now( locale => 'en-GB' ); # simple form
            # And more complex forms too
            my $dt = DateTime::Lite->now( locale => 'he-IL-u-ca-hebrew-tz-jeruslm' );
            my $dt = DateTime::Lite->now( locale => 'ja-Kana-t-it' );
            my $dt = DateTime::Lite->now( locale => 'ar-SA-u-nu-latn' );

        Locale data is resolved dynamically by DateTime::Locale::FromCLDR
        via Locale::Unicode::Data, so tags like
        "he-IL-u-ca-hebrew-tz-jeruslm" or "ja-Kana-t-it" work transparently
        without any additional installed modules.

        Additionally, if the locale tag carries a Unicode timezone extension
        ("-u-tz-"), and no explicit "time_zone" argument is provided to the
        constructor, "DateTime::Lite" will automatically resolve the
        corresponding IANA canonical timezone name from it:

            # time_zone is inferred as 'Asia/Jerusalem' from the -u-tz-jeruslm extension
            my $dt = DateTime::Lite->now( locale => 'he-IL-u-ca-hebrew-tz-jeruslm' );
            say $dt->time_zone;            # Asia/Jerusalem
            say $dt->time_zone_long_name;  # Asia/Jerusalem

        An explicit "time_zone" argument always takes priority over the
        locale extension.

    No die() in normal operation
        Following the Module::Generic / Locale::Unicode error-handling
        philosophy, "DateTime::Lite" never calls "die()" in normal error
        paths.

        Instead it sets a DateTime::Lite::Exception object and returns
        "undef" in scalar context, or an empty list in list context.

        However, if you really want this module to "die" upon error, you can
        pass the "fatal" option with a true value upon object instantiation.

KNOWN DIFFERENCES FROM DateTime
    Validation
        "DateTime" uses Specio / Params::ValidationCompiler for constructor
        validation. "DateTime::Lite" performs equivalent checks manually.
        Error messages are similar but not identical.

    No warnings::register abuse
        "DateTime::Lite" uses "warnings::enabled" consistently and does not
        depend on the "warnings::register" mechanism for user-facing output.

METHODS NOT IMPLEMENTED
    None at this time. If you encounter a method missing from the DateTime
    API, please file a report.

CONSTRUCTORS
  new
    Accepted parameters are:

    *   "year" (required)

    *   "month"

    *   "day"

    *   "hour"

    *   "minute"

    *   "second"

    *   "nanosecond"

    *   "time_zone"

        The time zone for the datetime. Accepts a zone name, such as
        "Asia/Tokyo"), a fixed-offset string, such as "+09:00", a
        DateTime::Lite::TimeZone object, "UTC", "floating", or "local".

        If omitted, and the "locale" argument carries a BCP47 "-u-tz-"
        extension, such as "he-IL-u-ca-hebrew-tz-jeruslm", the corresponding
        IANA canonical timezone is resolved automatically. If neither is
        provided, the default floating timezone is used (or
        $ENV{PERL_DATETIME_DEFAULT_TZ} if set).

    *   "locale"

        Any valid locale as defined by the Unicode CLDR (Common Locale Data
        Repository), and BCP47. See Locale::Unicode

    *   "formatter"

    *   "fatal"

    Returns the new object upon success, or sets an error and returns
    "undef" in scalar context, or an empty list in list context. In chaining
    (object context), it returns a dummy object ("DateTime::Lite::Null") to
    avoid the typical "Can't call method '%s' on an undefined value"

  from_day_of_year
        my $dt2 = DateTime::Lite->from_day_of_year(
            year        => 2026,
            day_of_year => 100,
            time_zone   => 'UTC',
            locale      => 'fr-FR',
        );

    Constructs from a year and day-of-year (1-366).

    Returns the new object upon success, or sets an error and returns
    "undef" in scalar context, or an empty list in list context. In chaining
    (object context), it returns a dummy object ("DateTime::Lite::Null") to
    avoid the typical "Can't call method '%s' on an undefined value"

  from_epoch
        my $dt = DateTime::Lite->from_epoch(
            epoch     => 1775769030,
            time_zone => 'Asia/Tokyo',
            locale    => 'ja-JP',
            formatter => $formatter,
        );

    Constructs from a Unix epoch value (integer or float). Non-integer
    values are rounded to the nearest microsecond.

    It accepts the "time_zone", "locale", and "formatter" parameters.

    The returned object will be in the "UTC" time zone.

    If you provide the "time_zone" argument, it will be applied *after* the
    object is instantiated. Thus, the epoch value provided will always be
    set in the UTC time zone.

    For example:

        my $dt = DateTime->from_epoch(
            epoch     => 0,
            time_zone => 'Asia/Tokyo'
        );
        say $dt; # Prints 1970-01-01T09:00:00 as Asia/Tokyo is +09:00 from UTC.
        $dt->set_time_zone('UTC');
        say $dt; # Prints 1970-01-01T00:00:00

    Returns the new object upon success, or sets an error and returns
    "undef" in scalar context, or an empty list in list context. In chaining
    (object context), it returns a dummy object ("DateTime::Lite::Null") to
    avoid the typical "Can't call method '%s' on an undefined value"

  from_object
        my $dt1 = DateTime->new;
        my $dt = DateTime::Lite->from_object(
            object    => $dt1,
            time_zone => 'Asia/Tokyo',
            locale    => 'ja-JP'
        );

    Converts any object implementing "utc_rd_values()" to a "DateTime::Lite"
    instance.

    Returns the new object upon success, or sets an error and returns
    "undef" in scalar context, or an empty list in list context. In chaining
    (object context), it returns a dummy object ("DateTime::Lite::Null") to
    avoid the typical "Can't call method '%s' on an undefined value"

  last_day_of_month
        my $dt = DateTime::Lite->last_day_of_month(
            year  => 2026,
            month => 4,
        );
        say $dt;  # 2026-04-30T00:00:00

        my $dt = DateTime::Lite->last_day_of_month(
            year      => 2026,
            month     => 4,
            time_zone => 'Asia/Tokyo',
            locale    => 'ja-JP',
            hour      => 6,
            minute    => 10,
            second    => 30
        );
        say $dt;  # 2026-04-30T06:10:30

    Constructs on the last day of the given month.

    Returns the new object upon success, or sets an error and returns
    "undef" in scalar context, or an empty list in list context. In chaining
    (object context), it returns a dummy object ("DateTime::Lite::Null") to
    avoid the typical "Can't call method '%s' on an undefined value"

  now
        my $now = DateTime::Lite->now(
            time_zone => 'Asia/Tokyo',
            locale    => 'ja-JP'
        );

        # time_zone inferred from the -u-tz- BCP47 extension:
        my $now2 = DateTime::Lite->now( locale => 'he-IL-u-ca-hebrew-tz-jeruslm' );
        say $now2->time_zone;            # Asia/Jerusalem
        say $now2->time_zone_long_name;  # Asia/Jerusalem

    Returns the current datetime (calls "from_epoch( epoch =" time )>).

    If "time_zone" is omitted and the "locale" carries a BCP47 "-u-tz-"
    extension, as in the example above, the timezone is inferred
    automatically. See "new" for the full priority rules.

    Returns the new object upon success, or sets an error and returns
    "undef" in scalar context, or an empty list in list context. In chaining
    (object context), it returns a dummy object ("DateTime::Lite::Null") to
    avoid the typical "Can't call method '%s' on an undefined value"

  today
        my $dt = DateTime::Lite->today(
            time_zone => 'Asia/Tokyo',
            locale    => 'ja-JP'
        );

    Returns the current date truncated to midnight.

    This is equivalent to:

        DateTime::Lite->now( @_ )->truncate( to => 'day' );

    Returns the new object upon success, or sets an error and returns
    "undef" in scalar context, or an empty list in list context. In chaining
    (object context), it returns a dummy object ("DateTime::Lite::Null") to
    avoid the typical "Can't call method '%s' on an undefined value"

  clone
        my $copy = $dt->clone;
        $copy->set_time_zone( 'Asia/Tokyo' );  # does not affect $dt

    Returns a new "DateTime::Lite" object that is an independent deep copy
    of the invocant. All scalar fields are duplicated, and nested objects
    ("tz" and "locale") are also independently copied, so mutating the clone
    does not affect the original.

ACCESSORS
  year
        my $year = $dt->year;  # e.g. 2026

    Returns the year component of the datetime.

  month
        my $m = $dt->month;  # 1..12

    Returns the month as a number from 1 (January) to 12 (December).

  mon
    Alias for "month".

  day
        my $d = $dt->day;

    Returns the day of the month (1-31).

  day_of_month
    Alias for "day".

  hour
        my $h = $dt->hour;

    Returns the hour (0-23).

  minute
        my $min = $dt->minute;

    Returns the minute (0-59).

  second
        my $s = $dt->second;

    Returns the second (0-59, or 60 on a leap second).

  second
        my $s = $dt->second;

    Returns the second component of the datetime. The range is normally
    0-59, but may be 60 or 61 in exceptional cases:

    60  A positive leap second. The IERS (International Earth Rotation and
        Reference Systems Service) occasionally inserts an extra second at
        the end of a UTC day to keep atomic time aligned with the Earth's
        rotation. When that happens, the clock reads "23:59:60" before
        rolling over to midnight. Since 1972, all leap seconds have been
        positive (seconds have been added, never removed).

    61  Reserved by the POSIX standard for a hypothetical double leap
        second. This has never occurred in practice and is considered
        extremely unlikely, but the upper bound of 61 is preserved for full
        standards compliance.

    In practice, the vast majority of datetime objects will always return a
    value in 0..59. The constructor accepts values up to 61 and will return
    an error for anything higher.

  nanosecond
        my $ns = $dt->nanosecond;

    Returns the fractional-second component in nanoseconds (0-999_999_999).

  day_of_week
        my $dow = $dt->day_of_week;  # 1=Mon .. 7=Sun

    Returns the day of week as a number from 1 (Monday) to 7 (Sunday),
    following the ISO 8601 convention.

  day_of_year
        my $doy = $dt->day_of_year;

    Returns the day of the year (1-366).

  day_abbr
        my $abbr = $dt->day_abbr;  # e.g. "Mon"

    Returns the abbreviated weekday name for the current locale.

  day_name
        my $name = $dt->day_name;  # e.g. "Monday"

    Returns the full weekday name for the current locale.

  month_0
        my $m0 = $dt->month_0;  # 0=Jan .. 11=Dec

    Returns the month as a zero-based number (0-11).

  mon_0
    Alias for "month_0".

  month_abbr
        my $abbr = $dt->month_abbr;  # e.g. "Jan"

    Returns the abbreviated month name for the current locale.

  month_name
        my $name = $dt->month_name;  # e.g. "January"

    Returns the full month name for the current locale.

  week
        my( $wy, $wn ) = $dt->week;

    Returns a two-element list "( $week_year, $week_number )" according to
    ISO 8601 week numbering.

  week_number
        my $wn = $dt->week_number;

    Returns the ISO 8601 week number (1-53).

  week_year
        my $wy = $dt->week_year;

    Returns the year that the ISO 8601 week belongs to. This may differ from
    "year" for days near the start or end of the calendar year.

  quarter
        my $q = $dt->quarter;

    Returns the quarter of the year (1-4).

  epoch
        my $ts = $dt->epoch;

    Returns the Unix timestamp (seconds since 1970-01-01T00:00:00 UTC) as an
    integer.

  hires_epoch
        my $ts = $dt->hires_epoch;

    Returns the Unix timestamp as a floating-point number (IEEE 754 double)
    that includes sub-second precision.

    Precision caveat: a 64-bit double has ~15-16 significant decimal digits.
    A Unix timestamp around 2026 already consumes 10 digits for the integer
    part, leaving only ~6 digits for the fractional part. This means
    precision is effectively limited to the microsecond range (~1 µs);
    nanosecond values smaller than a few hundred nanoseconds will be lost in
    floating-point rounding.

    For full nanosecond precision, combine "epoch" and "nanosecond"
    directly:

        printf "%d.%09d\n", $dt->epoch, $dt->nanosecond;

  jd
        my $jd = $dt->jd;

    Returns the Julian Day Number as a floating-point number.

  mjd
        my $mjd = $dt->mjd;

    Returns the Modified Julian Day (Julian Day minus 2,400,000.5).

  offset
        my $off = $dt->offset;

    Returns the UTC offset in seconds for the current datetime, such as
    32400 for "+09:00".

  time_zone
        my $tz = $dt->time_zone;

    Returns the DateTime::Lite::TimeZone object associated with this
    datetime.

  time_zone_long_name
        my $name = $dt->time_zone_long_name;

    Returns the long name of the time zone, such as "America/New_York".

  time_zone_short_name
        my $abbr = $dt->time_zone_short_name;

    Returns the short abbreviation of the time zone in effect at this
    datetime (e.g. "EST" or "EDT").

  locale
        my $loc = $dt->locale;

    Returns the DateTime::Locale::FromCLDR object associated with this
    datetime.

  is_leap_year
        if( $dt->is_leap_year ) { ... }

    Returns true if the year of this datetime is a leap year.

  is_dst
        if( $dt->is_dst ) { ... }

    Returns true if daylight saving time is in effect at this datetime.

  is_finite
    Returns true (always, for non-infinite objects). See
    DateTime::Lite::Infinite for the infinite case.

  is_infinite
    Returns false (always, for non-infinite objects).

  stringify
        my $str = $dt->stringify;
        print "$dt";   # same thing

    Returns the string representation of this datetime. If a formatter has
    been set via "set_formatter", it delegates to
    "$formatter->format_datetime( $self )"; otherwise it returns the
    "iso8601" string.

    This method is also called by the "" overloading operator.

  utc_rd_values
        my( $days, $secs, $ns ) = $dt->utc_rd_values;

    Returns a three-element list "( $utc_rd_days, $utc_rd_secs, $rd_nanosecs
    )", the internal UTC Rata Die representation.

  utc_rd_as_seconds
        my $rd_secs = $dt->utc_rd_as_seconds;

    Returns the internal UTC representation as a single integer:
    "utc_rd_days * 86400 + utc_rd_secs".

  utc_year
        my $uy = $dt->utc_year;

    Returns an internal approximation initialised to "year + 1" to break the
    circular dependency that arises when computing the UTC offset (you need
    an approximate year to look up the timezone offset, but you need the
    offset to know the exact UTC year). The stored value is deliberately
    equal to or greater than the real UTC year, so it is not suitable for
    direct use in application code. To obtain the actual UTC year, use:

        $dt->clone->set_time_zone('UTC')->year;

  local_rd_values
        my( $days, $secs, $ns ) = $dt->local_rd_values;

    Returns a three-element list "( $local_rd_days, $local_rd_secs,
    $rd_nanosecs )", the internal local-time Rata Die representation.

  local_rd_as_seconds
        my $rd_secs = $dt->local_rd_as_seconds;

    Returns the internal local-time representation as a single integer:
    "local_rd_days * 86400 + local_rd_secs".

  duration_class
        my $class = $dt->duration_class;

    Returns the string "DateTime::Lite::Duration", the class used to
    construct duration objects.

  DefaultLocale
        # Read the current default
        my $loc = DateTime::Lite->DefaultLocale;

        # Change the default to French
        DateTime::Lite->DefaultLocale( 'fr-FR' );

    Class method. Gets or sets the default locale used when constructing new
    "DateTime::Lite" objects that do not specify an explicit locale.

    The argument must be a valid CLDR locale tag, such as "en-US", "ja-JP",
    "fr-FR", or even "ja-Kana-t-it", or "he-IL-u-ca-hebrew-tz-jeruslm". The
    initial default is "en-US".

CONSTANTS
    The following constants are exported as zero-argument subs. They are
    used internally and exposed for completeness.

  INFINITY
        use DateTime::Lite qw();
        my $inf = DateTime::Lite::INFINITY();

    Returns positive infinity ("100**100**100**100").

  NEG_INFINITY
        my $neg = DateTime::Lite::NEG_INFINITY();

    Returns negative infinity ("-INFINITY").

  NAN
        my $nan = DateTime::Lite::NAN();

    Returns Not-a-Number ("INFINITY - INFINITY").

  MAX_NANOSECONDS
        my $max_ns = DateTime::Lite::MAX_NANOSECONDS();

    Returns "1_000_000_000" (10^9), the number of nanoseconds in one second.

  SECONDS_PER_DAY
        my $spd = DateTime::Lite::SECONDS_PER_DAY();

    Returns 86400, the number of seconds in one day (excluding leap
    seconds).

FORMATTING
  strftime( @patterns )
    POSIX-style formatting. Supports all standard %x specifiers plus
    "%{method_name}" and %NNN for nanoseconds.

  format_cldr( @patterns )
    CLDR / Unicode date format patterns (as used in DateTime). Supports all
    standard CLDR symbols.

  iso8601
        my $str = $dt->iso8601;

    Returns the datetime as an ISO 8601 string, such as
    "2026-04-09T12:34:56".

  datetime
    Alias for "iso8601".

  ymd( [$sep] )
        my $date = $dt->ymd;          # "2026-04-09"
        my $date = $dt->ymd( '/' );   # "2026/04/09"

    Returns the date portion as "YYYY-MM-DD" (default separator "-").

  hms( [$sep] )
        my $time = $dt->hms;          # "12:34:56"
        my $time = $dt->hms( '.' );   # "12.34.56"

    Returns the time portion as "HH:MM:SS" (default separator ":">).

  dmy( [$sep] )
        my $dmy = $dt->dmy;           # "09-04-2026"

    Returns the date as "DD-MM-YYYY".

  mdy( [$sep] )
        my $mdy = $dt->mdy;           # "04-09-2026"

    Returns the date as "MM-DD-YYYY".

  rfc3339
        my $str = $dt->rfc3339;       # "2026-04-09T12:34:56+09:00"

    Returns an RFC 3339 string. For a UTC datetime this is the same as
    "iso8601" with a "Z" suffix; for other timezones it appends the numeric
    offset.

ARITHMETIC
  add( %args )
        $dt->add( years => 1, months => 3 );
        $dt->add( hours => 2, minutes => 30 );

    Adds a duration to the datetime in-place (mutates $self). Accepts the
    same keys as "new" in DateTime::Lite::Duration: "years", "months",
    "weeks", "days", "hours", "minutes", "seconds", "nanoseconds".

    Returns $self to allow chaining.

  subtract( %args )
        $dt->subtract( days => 7 );

    Subtracts a duration from the datetime in-place (mutates $self).
    Equivalent to "$dt->add" with all values negated.

  add_duration( $dur )
        my $dur = DateTime::Lite::Duration->new( months => 2 );
        $dt->add_duration( $dur );

    Adds a DateTime::Lite::Duration object to the datetime in-place (mutates
    $self).

    Returns $self to allow chaining.

  subtract_duration( $dur )
        $dt->subtract_duration( $dur );

    Subtracts a DateTime::Lite::Duration object from the datetime in-place
    (mutates $self). Equivalent to "$dt->add_duration( $dur->inverse )".

  subtract_datetime( $dt )
    Returns a DateTime::Lite::Duration representing the difference between
    two "DateTime::Lite" objects (calendar-aware).

  subtract_datetime_absolute( $dt )
    Returns a DateTime::Lite::Duration representing the absolute UTC
    difference in seconds/nanoseconds.

  delta_days( $dt )
        my $dur = $dt1->delta_days( $dt2 );
        printf "%d days apart\n", $dur->days;

    Returns a DateTime::Lite::Duration containing only a "days" component
    representing the number of whole days between $self and $dt.

  delta_md( $dt )
        my $dur = $dt1->delta_md( $dt2 );

    Returns a DateTime::Lite::Duration with "months" and "days" components
    (calendar-aware difference).

  delta_ms( $dt )
        my $dur = $dt1->delta_ms( $dt2 );

    Returns a DateTime::Lite::Duration with "minutes" and "seconds"
    components (absolute clock difference).

SETTERS
  set
        $dt->set( hour => 0, minute => 0, second => 0 );

    Sets one or more datetime components in-place. Accepted keys are any of
    "year", "month", "day", "hour", "minute", "second", "nanosecond".
    Returns $self.

  set_year
        $dt->set_year(2030);

    Sets the year component. Returns $self.

  set_month
        $dt->set_month(12);

    Sets the month (1-12). Returns $self.

  set_day
        $dt->set_month(31);

    Sets the day of the month. Returns $self.

  set_hour
        $dt->set_hour(14);

    Sets the hour (0-23). Returns $self.

  set_minute
        $dt->set_minute(40);

    Sets the minute (0-59). Returns $self.

  set_second
        $dt->set_second(30);

    Sets the second (0-59). Returns $self.

  set_nanosecond
        $dt->set_nanosecond(1000);

    Sets the nanosecond component (0-999_999_999). Returns $self.

  set_locale
        $dt->set_locale( 'zh-TW' );

    Sets the locale. Accepts a CLDR locale string, such as "fr-FR", or a
    DateTime::Locale::FromCLDR object. Returns $self.

  set_formatter
        $dt->set_formatter( $my_formatter );

    Sets the formatter object used by "stringify". Must respond to
    "format_datetime". Pass "undef" to revert to the default ISO 8601
    representation.

  set_time_zone
        $dt->set_time_zone( 'Asia/Tokyo' );

    Changes the time zone of the datetime in-place. Accepts a time zone name
    string, such as "America/New_York", or a DateTime::Lite::TimeZone
    object. Returns $self.

  end_of
        my $dt = DateTime::Lite->new(
            year      => 2026,
            month     => 4,
            day       => 15,
            hour      => 14,
            minute    => 32,
            second    => 47,
            time_zone => 'UTC',
        );
        $dt->end_of( 'month' );
        say $dt;  # 2026-04-30T23:59:59.999999999

    Modifies the object in place to represent the last instant of the given
    unit. Supported units are: "second", "minute", "hour", "day", "week",
    "local_week", "month", "quarter", "year", "decade", "century".

    The result is the last nanosecond before the start of the next unit, so
    the timezone and variable-length units such as months and years are
    handled correctly without hardcoding boundary values.

    Returns the modified object on success, or sets an error object and
    returns "undef" in scalar context, or an empty list in list context. In
    chaining (object context), it returns a dummy object
    ("DateTime::Lite::Null") to avoid the typical "Can't call method '%s' on
    an undefined value"

    See also "start_of" and "truncate".

  start_of
        my $dt = DateTime::Lite->new(
            year      => 2026,
            month     => 4,
            day       => 15,
            hour      => 14,
            minute    => 32,
            second    => 47,
            time_zone => 'UTC',
        );
        $dt->start_of( 'month' );
        say $dt;  # 2026-04-01T00:00:00

    Modifies the object in place to represent the first instant of the given
    unit. Supported units are: "second", "minute", "hour", "day", "week",
    "local_week", "month", "quarter", "year", "decade", "century".

    For most units this delegates to "truncate". "decade" and "century" are
    handled independently: "start_of('decade')" for 2026 returns 2020-01-01,
    and "start_of('century')" returns 2001-01-01.

    Returns the modified object on success, or sets an error object and
    returns "undef" in scalar context, or an empty list in list context. In
    chaining (object context), it returns a dummy object
    ("DateTime::Lite::Null") to avoid the typical "Can't call method '%s' on
    an undefined value"

    See also "end_of" and "truncate".

  truncate
        $dt->truncate( to => 'day' );   # sets h/m/s/ns to zero

    Truncates the datetime to the given precision level. Accepted values for
    "to": "year", "month", "week", "local_week", "day", "hour", "minute",
    "second".

COMPARISON
  compare( $dt1, $dt2 )
    Class or instance method. Compares two "DateTime::Lite" objects. Returns
    -1 if $dt1 is earlier, 0 if equal, 1 if later.

    Uses the XS "_compare_rd()" fast-path when the XS layer is loaded.

        my $cmp = DateTime::Lite->compare( $dt1, $dt2 );

    Can also be used via the overloaded "<=>" and "cmp" operators:

        my @sorted = sort { $a <=> $b } @datetimes;

  compare_ignore_floating( $dt1, $dt2 )
    Like "compare", but treats floating-timezone datetimes as if they share
    the same UTC offset as the other operand. Useful when comparing local
    wall-clock times regardless of timezone.

  is_between( $lower, $upper )
    Returns true if $self is strictly between the two boundaries.

  error
        my $dt = DateTime::Lite->new( %bad_args );
        if( !defined( $dt ) )
        {
            my $err = DateTime::Lite->error;
            warn "Error: $err";
        }

    Instance and class method. When called with a message, constructs a
    DateTime::Lite::Exception object, stores it internally, and either warns
    (if "fatal" mode is off) or "die"s (if "fatal" mode is on). Returns
    "undef" in scalar context, an empty list in list context.

    When called without arguments, returns the most recent error object (or
    "undef" if no error has occurred).

  pass_error
        sub my_method
        {
            my $self = shift( @_ );
            my $tz = DateTime::Lite::TimeZone->new( name => 'Invalid' ) ||
                return( $self->pass_error );
            ...
        }

    Propagates the error stored in another object (or the class-level error)
    into the current object's error slot, without constructing a new
    exception. Used internally when a lower-level call fails and the caller
    wants to surface the same error to its own caller.

LOW-LEVEL XS UTILITIES
  posix_tz_lookup
        my $r = DateTime::Lite->posix_tz_lookup( 1775769030, 'EST5EDT,M3.2.0,M11.1.0' );
        my $r = $dt->posix_tz_lookup( 1775769030, 'EST5EDT,M3.2.0,M11.1.0' );
        if( defined( $r ) )
        {
            say $r->{offset};     # -14400  (seconds east of UTC)
            say $r->{is_dst};     # 1
            say $r->{short_name}; # "EDT"
        }

    Given a Unix timestamp (signed 64-bit integer, representing the number
    of seconds since 1970-01-01T00:00:00 UTC), and a POSIX TZ footer string,
    it parses the footer string, and resolves the UTC offset, DST flag, and
    timezone abbreviation for the given Unix timestamp.

    This is the low-level function used internally by
    DateTime::Lite::TimeZone to handle dates beyond the last explicit
    transition stored in the TZif database.

    The first argument may be either a class name or an instance; both forms
    are accepted.

    The POSIX TZ footer string comes from the TZif v2+ files, and looks like
    "EST5EDT,M3.2.0,M11.1.0" or "JST-9" or "<+0545>-5:45".

    The implementation is in C via dtl_posix.h, derived from the IANA
    "tzcode" reference implementation (public domain). It handles all POSIX
    TZ string rule forms ("Jn" julian day, "n" zero-based julian day, and
    "Mm.w.d" month/week/day), as well as the RFC 9636
    <https://www.rfc-editor.org/rfc/rfc9636.html> extensions for TZif v3+:

    "Jn"
        Julian day (1-365, no leap day)

    "n" zero-based Julian day (0-365, counts leap day)

    "Mm.w.d"
        month/week/day rule (e.g. "M3.2.0" = second Sunday of March)

    It also handles quoted angle-bracket abbreviations such as "<+0545>",
    fractional offsets, negative and greater-than-24-hour transition times
    (RFC 9636 section 3.3.2
    <https://www.rfc-editor.org/rfc/rfc9636.html#name-tz-string-extension>
    extensions for TZif v3+), and southern-hemisphere DST where the DST
    period wraps around the year boundary (start > end).

    Returns a hashref with three keys on success:

    "offset"
        The UTC offset in seconds east of UTC (negative for zones west of
        UTC, such as -18000 for EST).

    "is_dst"
        1 if the timestamp falls within a DST period, 0 otherwise.

    "short_name"
        The timezone abbreviation, such as "EDT", "JST", or +0545.

    Returns "undef" if $tz_string cannot be parsed.

    This method is primarily intended for advanced use cases, such as
    building custom timezone libraries on top of "DateTime::Lite::TimeZone".
    Most users will not need to call it directly.

SERIALISATION
    "STORABLE_freeze" and "STORABLE_thaw" are implemented, compatible with
    Storable.

    "FREEZE" and "THAW" are also implemented compatible with Sereal or CBOR

ERROR HANDLING
    On error, this class methods set an exception object, and return "undef"
    in scalar context, or an empty list in list context. The exception
    object is accessible via:

        my $err = DateTime::Lite->error;   # class method
        my $err = $dt->error;              # instance method

    The exception object stringifies to a human-readable message including
    file and line number.

    "error" detects the context is chaining, or object, and thus instead of
    returning "undef", it will return a dummy instance of
    "DateTime::Lite::Null" to avoid the typical perl error "Can't call
    method '%s' on an undefined value".

    So for example:

        $dt->now( %bad_arguments )->subtract( %params );

    If there was an error in "now", the chain will execute, but the last
    one, "subtract" in this example, will return "undef", so you can and
    even should check the return value:

        $dt->now( %bad_arguments )->subtract( %params ) ||
            die( $dt->error );

PERFORMANCE
    This section compares "DateTime::Lite" with the reference implementation
    DateTime 1.66 on four axes: module footprint, load time, memory, and CPU
    throughput. The figures below were recorded on an "aarch64" machine
    running Perl 5.36.1. Run "scripts/benchmark.pl" (bundled in this
    distribution) to reproduce them on your own hardware.

    The goal of this comparison is not to disparage DateTime, which is a
    mature, feature-complete, and battle-tested library, but to make the
    trade-offs explicit so you can choose the right tool for your context.

  Module footprint
    The number of files loaded into %INC, directly and indirectly through
    dependencies, when "use DateTime" or "use DateTime::Lite" is evaluated:

                              DateTime 1.66   DateTime::Lite
        -------               -------------   --------------
        use Module                      137               67
        TimeZone class alone            105               47
        Runtime prereqs (META)           23               11

    "DateTime" depends on Specio, Params::ValidationCompiler,
    namespace::autoclean, and several supporting modules that collectively
    account for the extra overhead. "DateTime::Lite" replaces this
    validation layer with lightweight hand-written checks and uses
    DateTime::Locale::FromCLDR instead of the heavier DateTime::Locale
    stack.

    "DateTime::TimeZone" loads 105 modules because it ships one ".pm" file
    per IANA zone, such as "DateTime::TimeZone::America::New_York", all
    loaded on the first "new()" call. "DateTime::Lite::TimeZone" loads,
    directly and indirectly, 47 modules and stores all zone data in a single
    SQLite file instead.

  Load time
    Measured as "time()" around a cold "require" (modules not yet in %INC):

                                   DateTime 1.66   DateTime::Lite
        -------                    -------------   --------------
        require Module                     48 ms            32 ms
        require TimeZone standalone       180 ms           100 ms

    Startup time matters in short-lived scripts (cron jobs, CLI tools, CGI)
    where the process initialisation is a significant fraction of total
    runtime. For a long-running Apache2/mod_perl2, Plack, or Mojolicious
    service, this cost is paid once and amortised over millions of requests.

  Memory (RSS after loading)
    Measured in a clean Perl process immediately after "use Module":

                              DateTime 1.66   DateTime::Lite
        -------               -------------   --------------
        use Module (~28 MB)        ~28 MB           ~37 MB
        TimeZone class only        ~19 MB           ~16 MB

    The "use Module" row is somewhat misleading on its own: "DateTime::Lite"
    loads "DBD::SQLite", which embeds a complete compiled SQLite engine (~14
    MB of native code) regardless of how many timezone objects you create.
    When measuring the "TimeZone" class in isolation, the component that
    actually handles date arithmetic, "DateTime::Lite::TimeZone" is lighter
    (~16 MB vs ~19 MB) because it does not pre-load all Olson zone data into
    RAM.

    "DateTime::TimeZone" pre-loads all IANA Olson definitions into memory on
    the first "new()" call (roughly 3-4 MB of compiled Perl structures on
    top of the module overhead). "DateTime::Lite::TimeZone" queries a
    compact SQLite database on demand and keeps those structures on disk.

  CPU throughput (10,000 iterations, µs per call)
                                            DateTime 1.66   DateTime::Lite
        -------                             -------------   --------------
        new( UTC )                                 ~13 µs          ~10 µs
        new( named zone, string )                  ~25 µs          ~64 µs  (*)
        new( named zone, all caches enabled )      ~25 µs          ~14 µs
        now( UTC )                                 ~11 µs          ~10 µs
        year + month + day + epoch                ~0.5 µs         ~0.4 µs
        clone + add( days + hours )                ~35 µs          ~25 µs
        strftime                                  ~3.5 µs         ~3.6 µs
        TimeZone->new (warm, no mem cache)          ~2 µs          ~19 µs  (*)
        TimeZone->new (mem cache enabled)           ~2 µs         ~0.4 µs

    Rows marked "(*)" reflect the default behaviour without the memory
    cache. With "DateTime::Lite::TimeZone->enable_mem_cache" active,
    "TimeZone-"new> drops to ~0.4 µs and "new(named zone)" drops to ~14 µs,
    which is faster than "DateTime" (~25 µs). See "TimeZone caching model"
    for the full explanation.

    For UTC construction, "now()", accessors, arithmetic, and formatting,
    "DateTime::Lite" is equivalent or faster. The XS-accelerated clone and
    the lighter validation layer account for the gain in arithmetic.

  TimeZone caching model
    This is the single most important trade-off to understand.

    DateTime::TimeZone loads the complete set of IANA time zone rules into
    RAM the first time any named zone is constructed (~180 ms startup, ~4 MB
    of in-memory hash structures). Every subsequent
    "DateTime::TimeZone->new( name => $name )" call is served from that hash
    in about 4 µs. If you construct thousands of "DateTime" objects per
    second in a long-lived process, this model is very fast after the
    initial warm-up.

    DateTime::Lite::TimeZone stores the same IANA data in a compact SQLite
    database ("tz.sqlite3", included in the distribution). The first call
    for a given zone name runs a query (~22 ms) and populates a per-instance
    cache; subsequent calls for the same zone use a cached "DBD::SQLite"
    prepared statement and return in ~130 µs. There is no process-wide
    singleton by default, so two calls with the same name each incur the 130
    µs cost.

    Optional memory cache: "DateTime::Lite::TimeZone" also provides an
    opt-in process-level memory cache that matches or beats
    "DateTime::TimeZone" on per-call speed:

        # Enable once at application start-up:
        DateTime::Lite::TimeZone->enable_mem_cache;

        # Or per call:
        my $tz = DateTime::Lite::TimeZone->new(
            name          => 'America/New_York',
            use_cache_mem => 1,
        );

    With the memory cache active, repeated "new()" calls for the same zone
    return the cached object from a plain hash lookup in about 0.8 µs:

                                  DateTime::TimeZone   DateTime::Lite::TimeZone
        ------                    -----------------   ------------------------
        Cold first call                    ~225 ms                      ~22 ms
        Warm (no mem cache)                  ~2 µs                      ~19 µs
        Warm (mem cache only)                ~2 µs                      ~0.4 µs
        Warm (mem+span+footer cache)         ~2 µs                      ~0.4 µs
        new(named zone, all caches)         ~25 µs                      ~14 µs

    Practical guidance:

    *   For long-lived services constructing datetime objects with named
        zones, call "DateTime::Lite::TimeZone->enable_mem_cache" once at
        startup. This activates three layers of caching:

        1. the object cache (avoids SQLite construction);
        2. the span cache (avoids the UTC offset query); and
        3. the footer cache (avoids the POSIX DST rule calculation).

        With all layers warm, "new(named zone)" costs ~14 µs, which is
        faster than "DateTime" (~25 µs).

    *   If you prefer explicit control, pass "use_cache_mem => 1" on each
        individual "new()" call, or construct one "TimeZone" object and
        reuse it:

            my $tz = DateTime::Lite::TimeZone->new( name => 'America/New_York' );
            my $dt = DateTime::Lite->new( ..., time_zone => $tz );

    *   For batch processing (log parsing, ETL, report generation) where
        timezone construction is a small fraction of total I/O time, the
        difference is imperceptible regardless of which option you choose.

    *   For short-lived scripts and command-line tools, "DateTime::Lite"
        wins on both startup time (~120 ms vs ~320 ms) and memory (~19 MB vs
        ~28 MB).

  Running the benchmark
    A self-contained benchmark script is included in the distribution:

        cd DateTime-Lite-vX.X.X
        perl Makefile.PL && make  # make sure the XS code is compiled
        perl -Iblib/lib -Iblib/arch scripts/benchmark.pl

        # More iterations for stable numbers:
        perl -Iblib/lib -Iblib/arch scripts/benchmark.pl --iterations 50000

        # Machine-readable CSV output:
        perl -Iblib/lib -Iblib/arch scripts/benchmark.pl --csv > results.csv

USAGE
  0-based Versus 1-based Numbers
    "DateTime::Lite" follows a simple rule for 0-based vs. 1-based numbers.

    Month, day of month, day of week, and day of year are 1-based. Every
    1-based method also has a "_0" variant. For example, "day_of_week"
    returns 1 (Monday) through 7 (Sunday), while "day_of_week_0" returns 0
    through 6.

    All *time*-related values (hour, minute, second) are 0-based.

    Years are neither, as they can be positive or negative. There is a year
    0.

    There is no "quarter_0" method.

  Floating DateTimes
    The default time zone for new "DateTime::Lite" objects (except where
    stated otherwise) is the "floating" time zone. This concept comes from
    the iCal standard. A floating datetime is not anchored to any particular
    time zone and does not include leap seconds, since those require a real
    time zone to apply.

    Date math and comparison between a floating datetime and one with a real
    time zone produce results of limited validity, because one includes leap
    seconds and the other does not.

    If you plan to use objects with a real time zone, it is strongly
    recommended that you do not mix them with floating datetimes.

  Determining the Local Time Zone Can Be Slow
    If $ENV{TZ} is not set, looking up the local time zone may involve
    reading several files in /etc. If you know the local time zone will not
    change during your program's lifetime and you need many objects for that
    zone, cache it once:

        my $local_tz = DateTime::Lite::TimeZone->new( name => 'local' );

        my $dt = DateTime::Lite->new( ..., time_zone => $local_tz );

    "DateTime::Lite::TimeZone" also provides a process-level cache that
    eliminates this cost entirely:

        DateTime::Lite::TimeZone->enable_mem_cache;
        my $dt = DateTime::Lite->new( ..., time_zone => 'local' );

  Far Future DST
    For dates very far in the future (thousands of years from now),
    "DateTime" with named time zones can consume large amounts of memory
    because "DateTime::TimeZone" pre-computes all DST transitions from the
    present to that date.

    "DateTime::Lite" is not affected by this problem.
    "DateTime::Lite::TimeZone" uses a compact SQLite database and a POSIX
    footer TZ string to derive the correct offset for any future date
    without expanding the full transition table.

  Globally Setting a Default Time Zone
    Warning: this is very dangerous. Use at your own risk.

    You can force "DateTime::Lite" to use a specific default time zone by
    setting:

        $ENV{PERL_DATETIME_DEFAULT_TZ} = 'America/New_York';

    This affects all code that creates a "DateTime::Lite" object, including
    any CPAN modules you use. Audit your dependencies before using this in
    production.

  Upper and Lower Bounds
    Internally, dates are stored as the number of days before or after
    "0001-01-01", held in a Perl integer. The usable range depends on your
    platform's integer size ($Config{ivsize}):

    *   32-bit Perl: approximately year "+/-1,469,903"

    *   64-bit Perl: approximately year "+/-12,626,367,463,883,278"

  Overloading
    "DateTime::Lite" overloads the following operators:

    *   "+" - adds a DateTime::Lite::Duration to a datetime, returning a new
        datetime.

    *   "-" - either subtracts a duration from a datetime (returning a new
        datetime), or subtracts two datetimes (returning a
        DateTime::Lite::Duration).

    *   "<=>" and "cmp" - numeric and string comparison, for use with "sort"
        and comparison operators.

    *   "" (stringification) - calls stringify, which delegates to the
        formatter if set, otherwise returns the iso8601 string.

    *   "bool" - always true for finite objects.

    The "fallback" parameter is set, so derived operators ("+=", "-=", etc.)
    work as expected. Do not expect "++" or "--" to be useful.

        my $dt2 = $dt + $duration;  # new datetime
        my $dt3 = $dt - $duration;  # new datetime
        my $dur = $dt - $other_dt;  # Duration

        for my $dt ( sort @datetimes ) { ... }  # uses <=>

  Formatters And Stringification
    You can supply a "formatter" object to control how a datetime is
    stringified. Any constructor accepts a "formatter" argument:

        my $fmt = DateTime::Format::Unicode->new( locale => 'fr-FR' );
        my $dt  = DateTime::Lite->new( year => 2026, formatter => $fmt );

    Or set it afterwards:

        $dt->set_formatter( $fmt );
        my $current_fmt = $dt->formatter;

    Once set, $dt will call "$fmt->format_datetime($dt)" instead of iso8601.
    Pass "undef" to revert to the default.

    A formatter must implement a "format_datetime($dt)" method. The
    DateTime::Format::Unicode module (available separately on CPAN) provides
    a full-featured CLDR formatter with support for date/time intervals and
    additional pattern tokens not covered by format_cldr.

CLDR PATTERNS
    The CLDR (Unicode Common Locale Data Repository) pattern language is
    more powerful and more complex than strftime. Unlike strftime, patterns
    are plain letters with no prefix, so any literal text must be quoted.

  Quoting and escaping
    Surround literal ASCII letters with single quotes ("'"). To include a
    literal single quote, write two consecutive single quotes (''). Spaces
    and non-letter characters are always passed through unchanged.

        my $p1 = q{'Today is ' EEEE};           # "Today is Thursday"
        my $p2 = q{'It is now' h 'o''clock' a}; # "It is now 9 o'clock AM"

  Pattern length and padding
    Most patterns pad with leading zeroes when the specifier is longer than
    one character. For example, "h" gives 9 but "hh" gives 09. The exception
    is that five of a letter usually means the narrow form, such as "EEEEE"
    gives "T" for Thursday, not a five-character wide value.

  Format vs. stand-alone forms
    Many tokens have a *format* form (used inside a larger string) and a
    *stand-alone* form (used alone, such as in a calendar header). They are
    distinguished by case: "M" is format, "L" is stand-alone for months;
    "E"/"e" is format, "c" is stand-alone for weekdays.

  Token reference
        Era
          G{1,3}   abbreviated era (BC, AD)
          GGGG     wide era (Before Christ, Anno Domini)
          GGGGG    narrow era

        Year
          y        year, zero-padded as needed
          yy       two-digit year (special case)
          Y{1,}    week-of-year calendar year (from week_year)
          u{1,}    same as y, but yy is not special

        Quarter
          Q{1,2}   quarter as number (1-4)
          QQQ      abbreviated format quarter
          QQQQ     wide format quarter
          q{1,2}   quarter as number (stand-alone)
          qqq      abbreviated stand-alone quarter
          qqqq     wide stand-alone quarter

        Month
          M{1,2}   numerical month (format)
          MMM      abbreviated format month name
          MMMM     wide format month name
          MMMMM    narrow format month name
          L{1,2}   numerical month (stand-alone)
          LLL      abbreviated stand-alone month name
          LLLL     wide stand-alone month name
          LLLLL    narrow stand-alone month name

        Week
          w{1,2}   week of year (from week_number)
          W        week of month (from week_of_month)

        Day
          d{1,2}   day of month
          D{1,3}   day of year
          F        day of week in month (from weekday_of_month)
          g{1,}    modified Julian day (from mjd)

        Weekday
          E{1,3}   abbreviated format weekday
          EEEE     wide format weekday
          EEEEE    narrow format weekday
          e{1,2}   locale-based numeric weekday (1 = first day of week for locale)
          eee      abbreviated format weekday (same as E{1,3})
          eeee     wide format weekday
          eeeee    narrow format weekday
          c        numeric weekday, Monday = 1 (stand-alone)
          ccc      abbreviated stand-alone weekday
          cccc     wide stand-alone weekday
          ccccc    narrow stand-alone weekday

        Period
          a        AM or PM (localized)

        Hour
          h{1,2}   hour 1-12
          H{1,2}   hour 0-23
          K{1,2}   hour 0-11
          k{1,2}   hour 1-24
          j{1,2}   locale-preferred hour (12h or 24h)

        Minute / Second
          m{1,2}   minute
          s{1,2}   second
          S{1,}    fractional seconds (without decimal point)
          A{1,}    millisecond of day

        Time zone
          z{1,3}   short time zone name
          zzzz     long time zone name
          Z{1,3}   time zone offset (e.g. -0500)
          ZZZZ     short name + offset (e.g. CDT-0500)
          ZZZZZ    sexagesimal offset (e.g. -05:00)
          v{1,3}   short time zone name
          vvvv     long time zone name
          V{1,3}   short time zone name
          VVVV     long time zone name

    The following tokens are not supported by "format_cldr()" but are
    supported by DateTime::Format::Unicode:

    *   "b" / "B" - period and flexible period of day ("noon", "at
        night"...)

    *   "O" / "OOOO" - localized GMT format ("GMT-8", "GMT-08:00")

    *   "r" - related Gregorian year

    *   "x"/"X" - ISO 8601 timezone offsets with optional "Z"

  CLDR Available Formats
    The CLDR data includes locale-specific pre-defined format skeletons. A
    skeleton is a pattern key that maps to a locale-appropriate rendering
    pattern. For example, the skeleton "MMMd" maps to "MMM d" in "en-US"
    (giving "Apr 9") and to "d MMM" in "fr-FR" (giving "9 avr.").

    Retrieve the locale-specific pattern via the locale object and pass it
    to "format_cldr":

        say $dt->format_cldr( $dt->locale->available_format('MMMd') );
        say $dt->format_cldr( $dt->locale->available_format('yQQQ') );
        say $dt->format_cldr( $dt->locale->available_format('hm') );

    See "available_formats" in DateTime::Locale::FromCLDR for the full list
    of skeletons for any given locale.

  DateTime::Format::Unicode
    For more advanced formatting, including features not covered by
    "format_cldr()", use DateTime::Format::Unicode (available separately on
    CPAN). It provides:

    *   Support for the additional tokens listed above ("b", "B", "O", "r",
        "x", "X")

    *   Formatting of datetime intervals, such as "Apr 9 - 12, 2026"

    *   Full CLDR number system support (Arabic-Indic numerals, etc.)

    *   Any CLDR locale, including complex tags such as
        "es-419-u-ca-gregory"

        use DateTime::Format::Unicode;

        my $fmt = DateTime::Format::Unicode->new(
            locale  => 'ja-JP',
            pattern => 'GGGGy年M月d日（EEEE）',
        ) || die( DateTime::Format::Unicode->error );

        say $fmt->format_datetime( $dt );

        # Interval formatting:
        my $fmt2 = DateTime::Format::Unicode->new(
            locale  => 'en',
            pattern => 'GyMMMd',
        );
        say $fmt2->format_interval( $dt1, $dt2 );  # e.g. "Apr 9 - 12, 2026"

HOW DATETIME MATH WORKS
    Date math in "DateTime::Lite" follows the same model as DateTime. The
    key distinction is between *calendar units* (months, days) and *clock
    units* (minutes, seconds, nanoseconds). Understanding this distinction
    is essential for correct results.

  Duration buckets
    A DateTime::Lite::Duration stores its components in five independent
    *buckets*: months, days, minutes, seconds, nanoseconds. Each bucket is
    kept as a signed integer. The buckets are not normalised against each
    other: a duration of "{ months => 1, days => 31 }" is distinct from "{
    months => 2, days => 0 }" because the number of days in a month varies.

  Calendar vs. clock units
    *Calendar units* (months, days) are relative: their real duration
    depends on the datetime to which they are applied. *Clock units*
    (minutes, seconds, nanoseconds) are absolute.

    When add applies a duration, calendar units are applied first, then
    clock units:

        $dt->add( months => 1, hours => 2 );
        # Step 1: advance by 1 month  (calendar)
        # Step 2: advance by 2 hours  (clock)

  End-of-month handling
    Adding months to a date whose day is beyond the end of the target month
    requires a policy decision. DateTime::Lite::Duration supports three
    "end_of_month" modes:

    *   "wrap" (default) - wrap into the next month. January 31 + 1 month =
        March 3 (or 2 in leap years).

    *   "limit" - clamp to the last day of the target month. January 31 + 1
        month = February 28 (or 29 in leap years).

    *   "preserve" - like "limit", but remember that the original day was at
        the end of month, so a further addition of one month will also land
        on the last day.

  Subtraction
    "$dt1->subtract_datetime( $dt2 )" returns a duration representing the
    difference. The calendar part is computed in months and days (from the
    local dates), and the clock part in seconds and nanoseconds (from the
    UTC representations). This is the most commonly useful result.

    "$dt1->subtract_datetime_absolute( $dt2 )" returns a duration in pure
    clock units (seconds and nanoseconds), based on the UTC epoch
    difference. This is useful when you need an exact elapsed time
    independent of DST changes.

  Leap seconds
    "DateTime::Lite" handles leap seconds when the time zone is not
    floating. Adding a duration in clock units across a leap second boundary
    will correctly account for the extra second.

SEE ALSO
    DateTime, DateTime::Lite::Duration, DateTime::Lite::Exception,
    DateTime::Lite::Infinite, DateTime::Locale::FromCLDR,
    Locale::Unicode::Data, DateTime::Format::Unicode

CREDITS
    Credits to the original author of DateTime, Dave Rolsky and all the
    contributors for their great work on which this module DateTime::Lite is
    derived.

AUTHOR
    Jacques Deguest <jack@deguest.jp>

COPYRIGHT & LICENSE
    Copyright(c) 2026 DEGUEST Pte. Ltd.

    All rights reserved

    This program is free software; you can redistribute it and/or modify it
    under the same terms as Perl itself.

