DateTime-Lite
view release on metacpan or search on metacpan
scripts/build_tz_database.pl view on Meta::CPAN
#!/usr/bin/env perl
##----------------------------------------------------------------------------
## DateTime::Lite::TimeZone - ~/scripts/build_tz_database.pl
## Version v0.4.0
## Copyright(c) 2026 DEGUEST Pte. Ltd.
## Author: Jacques Deguest <jack@deguest.jp>
## Created 2026/04/03
## Modified 2026/04/07
## All rights reserved
##
## This program is free software; you can redistribute it and/or modify it
## under the same terms as Perl itself.
##----------------------------------------------------------------------------
# SYNOPSIS
# # Fetch latest tzdata from IANA, compile, build database:
# perl scripts/build_tz_database.pl [--verbose|--debug 3]
#
# # Use a specific version (fetched from IANA if not cached):
# perl scripts/build_tz_database.pl --tz-version 2026a [--verbose|--debug 3]
#
# # Use already-downloaded tarball:
# perl scripts/build_tz_database.pl --tarball /path/to/tzdata2026a.tar.gz
#
# # Use system zoneinfo directory (no download):
# perl scripts/build_tz_database.pl --zoneinfo /usr/share/zoneinfo
#
# DESCRIPTION
# Builds the SQLite timezone database bundled with DateTime::Lite::TimeZone.
#
# Primary mode: downloads the latest (or specified) tzdata release from
# IANA (https://ftp.iana.org/tz/releases/), verifies the GPG signature,
# compiles the Olson source files with zic(1), then reads the resulting
# TZif binary files (RFC 8536). Downloaded tarballs are cached under
# ~/.cache/dtl-tzdata/ to avoid redundant downloads.
#
# Fallback mode (--zoneinfo): reads TZif files from a local zoneinfo
# directory instead of downloading. Useful when IANA is not reachable.
#
# REQUIREMENTS
# Always: zic(1), Perl 5.10.1+, DBD::SQLite >= 1.27, DBI >= 1.611
# IANA mode: HTTP::Promise or Net::FTP depending on --proto (default to 'http')
# Recommended: gpg(1) for signature verification
# Optional: rdfind(1), symlinks(1) for zoneinfo deduplication
#
# SEE ALSO
# Repository on Github: <https://github.com/eggert/tz>
##----------------------------------------------------------------------------
use v5.10.1;
use strict;
use warnings;
use Config;
use Data::Pretty qw( dump );
use DBI ();
use Encode ();
use File::Which qw( which );
use Getopt::Class;
use JSON;
use Module::Generic::File qw( cwd file stdout stderr tempdir );
use Pod::Usage;
use POSIX qw( strftime );
use Term::ANSIColor::Simple;
our $VERSION = 'v0.4.0';
our $LOG_LEVEL = 0;
our $DEBUG = 0;
our $VERBOSE = 0;
# NOTE: Constants
use constant NEG_INF_SENTINEL => -9_223_372_036_854_775_807;
use constant POS_INF_SENTINEL => 9_223_372_036_854_775_807;
# Seconds from Rata Die epoch (0001-01-01) to Unix epoch (1970-01-01)
# Verified: DateTime->new(year=>1970,month=>1,day=>1,time_zone=>'UTC')->utc_rd_as_seconds
use constant UNIX_TO_RD => 62_135_683_200;
use constant IANA_RELEASES => 'https://ftp.iana.org/tz/releases';
# We need both code and data to compile the binaries and tzdata.zi
use constant IANA_LATEST_CODE => 'https://ftp.iana.org/tz/tzcode-latest.tar.gz';
use constant IANA_LATEST_DATA => 'https://ftp.iana.org/tz/tzdata-latest.tar.gz';
# Olson source files to compile with zic, in the conventional order
use constant OLSON_FILES => [qw(
africa
antarctica
asia
australasia
europe
northamerica
southamerica
etcetera
factory
backward
)];
use constant TZINFO_EXTRA_FILES => [qw(
iso3166.tab
zone1970.tab
zonenow.tab
zone.tab
tzdata.zi
)];
our $HAS_NATIVE_I64 = 0;
scripts/build_tz_database.pl view on Meta::CPAN
$json->encode( $ref );
} || die( "Unable to encode array to JSON for array values @$ref: $@" );
return( $encoded );
}
}
sub _tzif_data_block_size
{
my( $h, $time_size ) = @_;
return(
$h->{timecnt} * $time_size +
$h->{timecnt} +
$h->{typecnt} * 6 +
$h->{charcnt} +
$h->{leapcnt} * ( $time_size + 4 ) +
$h->{isstdcnt} +
$h->{isutcnt}
);
}
sub _u8
{
my( $s ) = @_;
return( unpack( 'C', $s ) );
}
sub _u32be
{
my( $s ) = @_;
return( unpack( 'N', $s ) );
}
# _version_from_tarball( $tarball )
# Extracts the tzdata version string from the Makefile inside the tarball.
sub _version_from_tarball
{
my $tarball = file( shift( @_ ) );
my $tar = _has_tool('tar') ||
die( "Unable to find tar on your system." );
# VERSION= line in the Makefile
my $makefile = qx( $tar -xOf "$tarball" Makefile 2>/dev/null ) // '';
if( $makefile =~ /^VERSION[[:blank:]\h]*=[[:blank:]\h]*(\S+)/m )
{
return( $1 );
}
# Fallback: parse from the tarball filename
my $basename = $tarball->basename;
if( $basename =~ /tzdata(\d{4}[a-z]+)\.tar\.gz/ )
{
return( $1 );
}
die( "Cannot determine tzdata version from '$tarball'.\n" );
}
# _verify_signature( $tarball )
# Verifies the GPG signature $tarball.asc against $tarball.
# Non-fatal if gpg is absent or the key cannot be fetched.
sub _verify_signature
{
my $tarball = shift( @_ );
my $asc = file( "$tarball.asc" );
_message( 2, "Verifying tarball signature with <green>$asc</>" );
my $gpg = _has_tool('gpg');
unless( $gpg )
{
warn( " gpg not found; skipping signature verification.\n" );
return;
}
unless( $asc->exists )
{
warn( " Signature file '$asc' not found; skipping verification.\n" );
return;
}
# Try to ensure the IANA signing key is available.
# Paul Eggert's current key: ED97E90E62AA7E34
my $key_id = 'ED97E90E62AA7E34';
unless( qx( gpg --list-keys "$key_id" 2>/dev/null ) =~ /$key_id/i )
{
_message( 1, " Importing IANA signing key $key_id..." );
system( $gpg,
'--keyserver', 'keys.openpgp.org',
'--recv-keys', $key_id
) == 0 || warn( "Warning only: error calling $gpg to import IANA public key: exit ", ( $? >> 8 ) );
# Non-fatal: key servers may be unreachable
}
_message( 1, " Verifying GPG signature with: $gpg --verify $asc $tarball" );
if( system( $gpg, '--verify', $asc, $tarball ) )
{
warn( " GPG signature verification FAILED for '$tarball'.\n"
. " Proceeding; verify manually if this concerns you.\n" );
}
else
{
_message( 1, " Signature OK." );
}
}
__END__
=encoding utf8
=head1 NAME
build_tz_database.pl - Build the DateTime::Lite::TimeZone SQLite database
=head1 SYNOPSIS
# Fetch latest tzdata from IANA, compile, build database:
perl scripts/build_tz_database.pl [--verbose]
# Specific version:
perl scripts/build_tz_database.pl --tz-version 2026a
# Already-downloaded tarball:
perl scripts/build_tz_database.pl --tarball /path/to/tzdata2026a.tar.gz
# Use system zoneinfo (no download):
perl scripts/build_tz_database.pl --zoneinfo /usr/share/zoneinfo
=head1 DESCRIPTION
Builds the SQLite timezone database bundled with L<DateTime::Lite::TimeZone>.
B<Primary mode> downloads the latest (or specified) tzdata release directly from IANA (L<https://ftp.iana.org/tz/releases/>), verifies the GPG signature, compiles the Olson source files with C<zic(1)>, and parses the resulting TZif binary files (RFC ...
B<Fallback mode> (C<--zoneinfo>) reads TZif files from a local compiled zoneinfo directory instead of downloading. Useful when IANA is unreachable or for quick local rebuilds from the installed system timezone data.
Run this script whenever a new tzdata release is available, then commit the updated C<lib/DateTime/Lite/tz.sqlite3>.
=head1 OPTIONS
=over 4
=item C<--tz-version> I<version>
Target a specific tzdata version such as C<2026a>. Defaults to the latest version found on the IANA releases page.
=item C<--tarball> I<file>
Use an already-downloaded C<tzdata*.tar.gz> file, skipping the download.
=item C<--zoneinfo> I<directory>
Use a local compiled zoneinfo directory instead of downloading from IANA.
=item C<--db> I<file>
Output database path. Defaults to C<lib/DateTime/Lite/tz.sqlite3> relative to the distribution root.
=item C<--cache-dir> I<directory>
Where to store cached tarballs. Defaults to C<~/.cache/dtl-tzdata/>.
=item C<--skip-verify>
Skip GPG signature verification. Not recommended for production.
=item C<--verbose>
Print one line per timezone as it is processed.
=back
=head1 REQUIREMENTS
Always required: C<zic(1)> (from the C<tzdata> or C<tz-utils> system package), L<DBI>, L<DBD::SQLite> >= 1.27.
For primary mode: C<curl(1)> or C<wget(1)>.
Recommended: C<gpg(1)> for signature verification.
Optional (non-fatal if absent): C<rdfind(1)> for deduplication, C<symlinks(1)> for relative symlink conversion.
=head1 EPOCH CONVERSION
TZif transition times are stored as seconds since the Unix epoch (1970-01-01T00:00:00 UTC). L<DateTime> uses seconds since the Rata Die epoch (0001-01-01T00:00:00). The constant C<UNIX_TO_RD = 62_135_683_200> converts between them.
=head1 AUTHOR
Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
=head1 COPYRIGHT & LICENSE
Copyright(c) 2026 DEGUEST Pte. Ltd.
All rights reserved
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
=cut
( run in 0.626 second using v1.01-cache-2.11-cpan-e1769b4cff6 )