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 )