Revision history for Perl distribution DateTime-Lite

v0.6.1 2026-04-19T08:22:20+0900
    - Fixed _dbh_add_user_defined_functions() in DateTime::Lite::TimeZone to
      correctly handle SQLite installations where pragma_function_list is
      unavailable.
      The previous implementation queried pragma_function_list unconditionally to
      detect whether SQLite's built-in math functions (sqrt, sin, cos, asin) were
      compiled in (with the macro 'SQLITE_ENABLE_MATH_FUNCTIONS'). That table-valued
      pragma is only available since SQLite 3.16.0 (2017-01-02).
      So, older installations raised the error: "no such table: pragma_function_list"
      causing t/14.tz_coordinates.t to fail entirely.
      The detection logic is now version-aware, based on $DBD::SQLite::sqlite_version:
        - SQLite >= 3.35.0 (2021-03-12): pragma_function_list is queried for 'sqrt'.
          The check runs before any UDF is registered, so a hit is guaranteed to be a
          native function. A miss means the build omitted -DSQLITE_ENABLE_MATH_FUNCTIONS;
          Perl UDFs are registered as fallback.
        - SQLite >= 3.16.0 and < 3.35.0: pragma_function_list exists but the math
          functions cannot be present regardless of the build flags;
          Perl UDFs are registered unconditionally without querying the pragma.
        - SQLite < 3.16.0: pragma_function_list is not available as a table-valued
          function, so Perl UDFs are registered unconditionally.
      UDFs via sqlite_create_function() are available on all SQLite >= 3.0.0, so
      coordinate-based timezone resolution now works transparently on all supported
      SQLite versions.
      Reported via CPAN Testers on Perl 5.20.0, 5.22.2 and 5.24.0 on x86_64-linux
      (Debian Wheezy, system SQLite < 3.16.0).
      Thanks to Slaven Rezić for the detailed test reports.
    - Added .gitlab-ci.yml. The pipeline covers Perl 5.10.1 through 5.40 on Linux,
      plus a dedicated job that builds SQLite 3.15.2 from the official autoconf tarball
      (the last release before 3.16.0) to ensure the unconditional UDF registration path
      is exercised in CI.

v0.6.0 2026-04-17T22:30:20+0900
    - Added extended_aliases table to tz.sqlite3 (schema v0.6.0).
      abbreviations not stored as TZif type entries in the IANA database, such as BDT,
      CEST, HAEC, JST, and the full set of NATO military single-letter zones.
      Each abbreviation maps to one or more canonical IANA zone names.
      One row per (abbreviation, zone_id) pair with 'is_primary' marking the most
      commonly accepted canonical zone when multiple candidates exist.
      Two triggers enforce that at most one is_primary = 1 row exists per abbreviation
      (on INSERT and on UPDATE OF is_primary).
      355 timezone abbreviations, 535 (abbreviation, zone) pairs, covering real-world
    - Updated tz.sqlite3 with new schema and latest data (tzdata 2026a).
    - Extended resolve_abbreviation() in DateTime::Lite::TimeZone with an optional
      'extended' boolean argument.
      When true and the abbreviation is absent from the IANA types table, the method
      falls back to querying the new table 'extended_aliases'.
      Extended results carry utc_offset => undef and is_dst => undef (the extended
      alias table maps names only); the new 'is_primary' and 'extended' keys are added
      to each result hashref.
      The ambiguity flag for extended results is based on candidate count and
      'is_primary': 'ambiguous' is set to 0 when exactly one 'is_primary' exists,
      otherwise, 'ambiguous' is set to 1.
      Existing callers are unaffected: without the option 'extended' set to a true value,
      the behaviour is identical to the previous version v0.5.0.
    - Added the property 'extended' set to 0 in all IANA results returned from
      resolve_abbreviation() for consistency with extended alias results.
    - Added cached prepared statement resolve_abbreviation_extended for the
      'extended_aliases' table query, consistent with existing statement caching.
    - Updated POD for resolve_abbreviation() to document the boolean option 'extended',
      'utc_offset', and the result fields 'extended' and 'is_primary', and the 'ambiguity'
      semantics for extended results.
    - Added t/16.extended_aliases.t with 6 subtests covering:
      - IANA abbreviation with the boolean option 'extended' as a no-op (CEST)
      - unambiguous single-zone extended alias (AFT -> Asia/Kabul)
      - multi-zone extended alias with 'is_primary' (AMST)
      - unknown abbreviation with and without the boolean option 'extended'
      - JST IANA offset verification; and
      - round-trip: using the returned 'zone_name' to instantiate a
        DateTime::Lite::TimeZone object.
    - Extended resolve_abbreviation() with period-based filtering and deterministic
      sort order:
      - Results are now sorted by most-recently-used first
        (MAX(trans_time) DESC), so the currently-active or most-recently-active zone
        appears first. The new result field 'last_trans_time' exposes the Unix epoch
        of that most recent transition.
      - Added optional 'period' argument to restrict results to zones whose most recent
        matching transition falls within a given time window.
        Accepts a single value or an array reference of values for multiple AND-combined
        conditions. Each value may be prefixed with a comparison operator: >, >=, <, <=.
        ISO date strings such as '1950-01-01' are converted to Unix epoch via SQLite
        strftime(); plain integers are passed as CAST(? AS INTEGER) to ensure
        arithmetic comparison.
        The special value 'current' returns only zones where the abbreviation is active
        right now, by checking that the most recent matching transition is the zone's
        overall most recent transition and is in the past.
    - Added t/17.resolve_abbreviation_period.t with 10 subtests for abbreviated timezone
      resolution.

v0.5.0 2026-04-16T15:26:07+0900
    - Added the method resolve_abbreviation() in DateTime::Lite::TimeZone
    - Corrected method _nearest_zone() to use $class instead of $self
    - Implemented a check for the availability of SQLite math functions
      (SQLite version >= 3.35.0; March 2021)
    - Added a private method, _dbh_add_user_defined_functions(), to add the missing math
      functions with User Defined Functions if missing. _dbh_add_user_defined_functions()
      is only called by relevant methods, so those math functions are added only when
      necessary.

v0.4.0 2026-04-14T10:27:25+0900
    - Fixed t/12.tz_database.t on Windows (Strawberry Perl): replaced
      POSIX::mktime with Time::Local::timegm for pre-1970 timestamps,
      which POSIX::mktime cannot handle on Windows.
    - Added start_of( $unit ) mutator, which modifies the datetime object in place
      to the first instant of the given unit.
      Supported units are second, minute, hour, day, week, local_week, month, quarter,
      year, decade, and century.
      Delegates to truncate() for most units; decade and century are handled independently.
      Returns the modified object on success.
    - Added end_of( $unit ) mutator: modifies the datetime object in place to
      the last nanosecond of the given unit (i.e. the nanosecond before the
      start of the next unit).
      Supports the same units as start_of().
      Handles variable-length units such as months and leap years correctly without
      hardcoding boundary values.
      Returns the modified object on success.
    - Added test suite t/15.start_end_of.t covering all supported units for both
      start_of() and end_of(), including edge cases such as February in leap and
      non-leap years, quarter boundaries, decade and century boundaries, timezone and
      locale preservation, and invalid unit handling.
    - Added POD documentation for posix_tz_lookup() under a new =head1 LOW-LEVEL XS UTILITIES
      section in DateTime::Lite, documenting the XS function signature, all supported
      POSIX TZ string rule forms (Jn, n, Mm.w.d), the RFC 9636 extensions for TZif v3+,
      and the structure of the returned hashref.

v0.3.0 2026-04-13T07:30:23+0900
    - Added GPS coordinate-based timezone resolution to DateTime::Lite::TimeZone::new().
      Passing C<latitude> and C<longitude> (decimal degrees) instead of C<name>
      finds the nearest canonical IANA timezone using the haversine great-circle
      distance against the reference coordinates stored in the C<zones> table of C<tz.sqlite3> (derived from
      IANA C<zone1970.tab>). No new dependencies.
      Note: this is an approximation based on one representative point per zone;
      it is accurate for most locations but may give incorrect results near timezone
      boundaries or in enclaves. For more boundary-precise resolution, see
      L<Geo::Location::TimeZoneFinder>.
    - Added private method _nearest_zone() to DateTime::Lite::TimeZone, implementing
      the haversine query entirely within SQLite via asin(), sqrt(), cos() and sin()
      functions. Only canonical zones with coordinates are considered.
    - Added input validation for latitude (-90..90) and longitude (-180..180), with
      proper error() returns rather than die().
    - Updated DateTime::Lite::TimeZone POD to document the coordinate-based resolution,
      its approximation caveat, and the valid parameter ranges.
    - Added t/14.tz_coordinates.t with 10 subtests covering: Tokyo, Paris, New York,
      Taipei, Sydney, Buenos Aires (southern hemisphere); integration with
      DateTime::Lite->now(); and input validation for missing, out-of-range, and
      non-numeric parameters.

v0.2.0 2026-04-11T20:48:09+0900
    - BCP47 -u-tz- locale extension support: if the locale tag carries a
      Unicode timezone extension, such as C<he-IL-u-ca-hebrew-tz-jeruslm>,
      and no explicit C<time_zone> argument is supplied to the constructor,
      the corresponding IANA canonical timezone name is resolved automatically
      via C<Locale::Unicode->tz_id2names()>.
      Explicit C<time_zone> always takes priority.
      No new dependencies: the BCP47 timezone to Olson canonical timezone
      resolution uses the static in-memory hash in C<Locale::Unicode>, so no
      SQLite query, and thus super fast.
    - _set_locale() rewritten: now uses C<Scalar::Util::blessed()> instead
      of C<ref()> to distinguish objects from unblessed references; This was
      a carry over from the way DateTime is doing.
      Returns the C<DateTime::Locale::FromCLDR> object instead of C<undef> to allow
      the BCP47 timezone resolution in C<_new()>.
      Unblessed references now return a proper error instead of an undefined behaviour;
      replaced C<die()> with C<pass_error()> for consistency with the no-die philosophy.
    - Added support for 'local' timezone name in DateTime::Lite::TimeZone::new().
      The local timezone is resolved automatically without any external module
      dependency, using OS-specific detection strategies:
        - Unix/Linux/macOS/FreeBSD/OpenBSD/NetBSD/Solaris/AIX/HP-UX/OS2/Cygwin:
          $ENV{TZ}, /etc/localtime symlink or binary match, /etc/timezone,
          /etc/TIMEZONE, /etc/sysconfig/clock, /etc/default/init.
        - Windows (MSWin32, NetWare): $ENV{TZ} then Windows Registry via
          Win32::TieRegistry (optional, not a hard dependency).
        - Android: $ENV{TZ}, getprop persist.sys.timezone, then 'UTC'.
        - VMS: TZ, SYS$TIMEZONE_RULE, SYS$TIMEZONE_NAME, UCX$TZ, TCPIP$TZ.
        - Symbian, EPOC, MS-DOS, Mac OS 9 and earlier: $ENV{TZ} only.
    - Added OS dispatch aliases for darwin, cygwin, freebsd, openbsd, netbsd, solaris,
      aix, hpux, os2, netware, symbian, epoc, dos, macos.
    - Updated DateTime::Lite::TimeZone POD to document 'local' resolution strategy per OS.
    - Added t/10.timezone.t subtest for 'local' timezone resolution via $ENV{TZ}.

v0.1.0 2026-04-10T06:12:47+0900
    - Initial release.
    - Full port of DateTime 1.66 API.
    - Dependencies reduced from ~10 non-core to 3 (DateTime::TimeZone,
      DateTime::Locale::FromCLDR, Locale::Unicode).
    - Specio, Params::ValidationCompiler, Try::Tiny, namespace::autoclean all
      eliminated.
    - XS layer ported from DateTime.xs with 4 new functions:
      _rd_to_epoch, _epoch_to_rd, _normalize_nanoseconds, _compare_rd.
    - Pure-Perl fallback (DateTime::Lite::PP) for environments without a C compiler.
    - No die() in normal error paths: DateTime::Lite::Exception used throughout,
      errors returned as undef in scalar context.
    - DateTime::Lite::Infinite provides singleton Future/Past objects.
    - Locale data via DateTime::Locale::FromCLDR + Locale::Unicode::Data (SQLite backend,
      no generated Perl locale files).
    - Includes DateTime::Lite::TimeZone based thoroughly on the IANA Olson data
      accessible with a SQLite database.
    - Replaced the shallow Perl clone() with an XS deep copy (DateTime-Lite.xs):
      - Root object scalar fields are copied with newSVsv().
      - Nested blessed hashrefs (tz, locale) are copied with a new static
        helper dtl_clone_flat_hv(), then re-blessed into the original stash.
        The RV is created first with newRV_noinc() before calling sv_bless(),
        which requires an RV not a raw HV pointer.
      - The local_c cache (plain hashref) is also deep-copied, eliminating
        any shared state between original and clone.
      - Non-HV references (formatter objects, etc.) are shallow-copied with
        newSVsv(), acceptable since formatters are effectively immutable.
    - Implemented a TZif footer POSIX parser in XS, based on IANA code (public domain).
    - New file dtl_posix.h: self-contained C header containing eight functions
      derived from tzcode localtime.c (is_digit, getzname, getqzname, getnum,
      getsecs, getoffset, getrule, transtime) plus dtl_year_to_jan1() and the
      public entry point dtl_posix_tz_lookup(). All symbols prefixed dtl_;
      no dynamic allocation, no system calls, no global state.
    - New XS function DateTime::Lite::posix_tz_lookup(class_or_self,
      unix_secs, tz_string): parses any POSIX TZ footer and returns
      { offset, is_dst, short_name } or undef. Handles Jn, n, Mm.w.d
      rules; quoted <name> abbreviations; fractional offsets; negative and
      >24 h transition times (RFC 9636 s3.3.2 v3+ extensions); southern-
      hemisphere DST (start > end).
    - DateTime::Lite::TimeZone::_posix_tz_lookup() is now a thin Perl wrapper that
      delegates to the XS function.
    - Implemented process-level memory cache to DateTime::Lite::TimeZone.
      The cache is opt-in and activated either per-call or class-wide:
        use_cache_mem => 1 argument to new() enables it for that call;
        DateTime::Lite::TimeZone->enable_mem_cache enables it globally.
      Three new class methods: enable_mem_cache(), disable_mem_cache(),
      clear_mem_cache(). Cache is keyed by both input name and canonical
      name after alias resolution, so "US/Eastern" and "America/New_York"
      share the same cached object.
    - Implemented three-layer span cache to DateTime::Lite::TimeZone, active
      when use_cache_mem => 1 or enable_mem_cache() is set:
        Layer 1 - object cache: TimeZone->new() returns the cached object
          keyed by zone name and canonical name, bypassing SQLite entirely.
        Layer 2 - span cache: _lookup_span() and _lookup_span_local() store
          the last matched span boundaries per cached TZ object. Subsequent
          calls within the same span return the cached result in ~0.5 us
          instead of ~45 us (SQL query). SQL extended with utc_start/utc_end
          and local_start/local_end columns for range checks.
        Layer 3 - POSIX footer cache: For zones with a footer TZ string
          (e.g. America/New_York), the DST rule calculation result is cached
          by calendar day. Pre-check added BEFORE the SQL query so future
          dates skip the database entirely on subsequent calls.
