DateTime-Lite

 view release on metacpan or  search on metacpan

README  view on Meta::CPAN

    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.



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