DateTime-Lite

 view release on metacpan or  search on metacpan

README.md  view on Meta::CPAN

    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](https://metacpan.org/pod/DateTime) 1.66 on four axes: module footprint, load time, memory, and CPU throughput. The figures below were recorded on an `aarch64` machine...

The goal of this comparison is not to disparage [DateTime](https://metacpan.org/pod/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](https://metacpan.org/pod/Specio), [Params::ValidationCompiler](https://metacpan.org/pod/Params%3A%3AValidationCompiler), [namespace::autoclean](https://metacpan.org/pod/namespace%3A%3Aautoclean), and several supporting ...

`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 sto...

## 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 amor...

## 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` cla...

`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 kee...

## 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"](#timezone-caching-model) for the full explan...

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 ser...

**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 ...

**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 s...

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}`):



( run in 0.543 second using v1.01-cache-2.11-cpan-71847e10f99 )