DateTime-Lite

 view release on metacpan or  search on metacpan

lib/DateTime/Lite.pm  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.

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

So for example:

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

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

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

=head1 PERFORMANCE

This section compares C<DateTime::Lite> with the reference implementation L<DateTime> 1.66 on four axes: module footprint, load time, memory, and CPU throughput. The figures below were recorded on an C<aarch64> machine running Perl 5.36.1. Run C<scri...

The goal of this comparison is not to disparage L<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.

=head2 Module footprint

The number of files loaded into C<%INC>, directly and indirectly through dependencies, when C<use DateTime> or C<use DateTime::Lite> is evaluated:

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

C<DateTime> depends on L<Specio>, L<Params::ValidationCompiler>, L<namespace::autoclean>, and several supporting modules that collectively account for the extra overhead. C<DateTime::Lite> replaces this validation layer with lightweight hand-written ...

C<DateTime::TimeZone> loads 105 modules because it ships one C<.pm> file per IANA zone, such as C<DateTime::TimeZone::America::New_York>, all loaded on the first C<new()> call. C<DateTime::Lite::TimeZone> loads, directly and indirectly, 47 modules an...

=head2 Load time

Measured as C<time()> around a cold C<require> (modules not yet in C<%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 amort...

=head2 Memory (RSS after loading)

Measured in a clean Perl process immediately after C<use Module>:

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

The C<use Module> row is somewhat misleading on its own: C<DateTime::Lite> loads C<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 C<TimeZone>...

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

=head2 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 C<(*)> reflect the default behaviour without the memory cache.
With C<< DateTime::Lite::TimeZone->enable_mem_cache >> active, C<TimeZone->new> drops to ~0.4 µs and C<new(named zone)> drops to ~14 µs, which is faster than C<DateTime> (~25 µs). See L</TimeZone caching model> for the full explanation.

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

=head2 TimeZone caching model

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

B<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 C<< DateTime::TimeZone->new( name => $name ) >> call is...

B<DateTime::Lite::TimeZone> stores the same IANA data in a compact SQLite database (C<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 ...

B<Optional memory cache:>  C<DateTime::Lite::TimeZone> also provides an opt-in process-level memory cache that matches or beats C<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 C<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:

=over 4

=item *

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

=over 4

=item 1. the object cache (avoids SQLite construction);

=item 2. the span cache (avoids the UTC offset query); and

=item 3. the footer cache (avoids the POSIX DST rule calculation).

=back

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

=item *

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

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

=item *

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.

=item *

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

=back

=head2 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

=head1 USAGE

=head2 0-based Versus 1-based Numbers

C<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 B<1-based>. Every 1-based method also has a C<_0> variant. For example, C<day_of_week> returns 1 (Monday) through 7 (Sunday), while C<day_of_week_0> returns 0 through 6.

All I<time>-related values (hour, minute, second) are B<0-based>.

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

There is no C<quarter_0> method.

=head2 Floating DateTimes

The default time zone for new C<DateTime::Lite> objects (except where stated otherwise) is the C<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...

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 B<do not> mix them with floating datetimes.

=head2 Determining the Local Time Zone Can Be Slow

If C<$ENV{TZ}> is not set, looking up the local time zone may involve reading several files in F</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 );

C<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' );

=head2 Far Future DST

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

C<DateTime::Lite> is not affected by this problem. C<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.

=head2 Globally Setting a Default Time Zone

B<Warning: this is very dangerous. Use at your own risk.>

You can force C<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 C<DateTime::Lite> object, including any CPAN modules you use. Audit your dependencies before using this in production.

=head2 Upper and Lower Bounds

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



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