From 07f192ca03b609ab6b498490e29d849c2bf4946a Mon Sep 17 00:00:00 2001 From: Douglas Vought Date: Tue, 5 Sep 2023 16:11:01 -0400 Subject: [PATCH] [contrib/timezone-gen] Fix timezone gen (#2215) * [contrib/timezone-gen] Move timezone-gen.pl to own folder * [contrib/timezone-gen] Add fixTzstr * [contrib/timezone-gen] Add tests and zone data getter - tests.pl can be used to verify that the generated timezone conf will produce the correct datetimes by testing them against what the system's `date` says - build-zonedata.pl will download the latest tzdb data and build the posix timezone data files. It only builds what is needed rather than adding extraneous "right/" and "posix/" timezones. FreeSWITCH doesn't seem to be able to use the "right/" timezone files. - data/ is where the various files needed to generate the timezones gets stored --- scripts/perl/timezones/build-zonedata.pl | 28 +++++++++ scripts/perl/timezones/data/.gitignore | 4 ++ scripts/perl/timezones/fix-tzstr.pl | 61 ++++++++++++++++++ scripts/perl/timezones/tests.pl | 65 ++++++++++++++++++++ scripts/perl/{ => timezones}/timezone-gen.pl | 10 ++- 5 files changed, 165 insertions(+), 3 deletions(-) create mode 100755 scripts/perl/timezones/build-zonedata.pl create mode 100644 scripts/perl/timezones/data/.gitignore create mode 100644 scripts/perl/timezones/fix-tzstr.pl create mode 100644 scripts/perl/timezones/tests.pl rename scripts/perl/{ => timezones}/timezone-gen.pl (90%) diff --git a/scripts/perl/timezones/build-zonedata.pl b/scripts/perl/timezones/build-zonedata.pl new file mode 100755 index 0000000000..347a743dae --- /dev/null +++ b/scripts/perl/timezones/build-zonedata.pl @@ -0,0 +1,28 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +my $remote_version = `wget --quiet https://data.iana.org/time-zones/tzdb/version --output-document -` =~ s/\n//r; +my $local_version; + +if ( open my $in, " }; + close $in; +} + +my $up_to_date = defined($local_version) && $local_version eq $remote_version; + +if ( ! $up_to_date ) { + open my $out, ">data/version"; + print $out $remote_version; + close $out; +} + +$local_version = $remote_version; + +`wget --quiet --timestamping --directory-prefix=data https://data.iana.org/time-zones/tzdb-latest.tar.lz`; +`tar --extract --file=data/tzdb-latest.tar.lz --directory=data`; +`make DESTDIR=../ TZDIR=zones-$local_version --directory=data/tzdb-$local_version posix_only`; + +print("Yay. Now you can run\n ./timezone-gen.pl --base=data/zones-$local_version --output=timezones-$local_version.conf.xml") \ No newline at end of file diff --git a/scripts/perl/timezones/data/.gitignore b/scripts/perl/timezones/data/.gitignore new file mode 100644 index 0000000000..144983b728 --- /dev/null +++ b/scripts/perl/timezones/data/.gitignore @@ -0,0 +1,4 @@ +tzdb-* +zones-* +version +tzdb-latest.tar.lz \ No newline at end of file diff --git a/scripts/perl/timezones/fix-tzstr.pl b/scripts/perl/timezones/fix-tzstr.pl new file mode 100644 index 0000000000..224c9a550e --- /dev/null +++ b/scripts/perl/timezones/fix-tzstr.pl @@ -0,0 +1,61 @@ +#!/usr/bin/perl + +sub fixTzstr { + # switch_time.c expects POSIX-style TZ rule, but it won't process quoted TZ + # rules that look like this: <-04>4 or <-04>4<-03> + # See https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_03 + + # Instead it defaults to UTC for these values. Here we process the quoted + # values and convert them into letters. If the zone name has "GMT", we use + # that as the replacement prefix, otherwise a default "STD" is used. Zones + # that have a quoted suffix have their suffix replaced with "DST". + + my ($tzstr, $name) = @_; + + if ( $tzstr =~ /(<(?[^>]+)>)([^<]+)(?<.+>)?(?.+)?/ ) { + my ($tzprefix, $tzsuffix, $tzrest, $offset, $offsetprefix) = ("") x 5; + + if ( defined($+{std}) ) { + my $std = $+{std}; + + if ( lc($name) =~ m/gmt/) { + $tzprefix = "GMT"; + } else { + $tzprefix = "STD"; + } + + if ( $std =~ m/\+/ ) { + $offset = sprintf "%d", $std =~ s/\+//r; + $offsetprefix = "-"; + } else { + $offset = sprintf "%d", $std =~ s/\-//r; + } + + my @chars = split(//, $offset); + if ( @chars > 2 ) { + my $hours = $chars[-3]; + if ( defined( $chars[-4] ) ) { + $hours = $chars[-4].$hours; + } + + $offset = $hours.":".$chars[-2].$chars[-1]; + } + + $offset = $offsetprefix.$offset; + } + + if ( defined($+{dst}) ) { + $tzsuffix = "DST"; + } + + if ( defined($+{rest}) ) { + $tzrest = $+{rest}; + } + + return $tzprefix.$offset.$tzsuffix.$tzrest; + } + + return $tzstr; +} + +1; \ No newline at end of file diff --git a/scripts/perl/timezones/tests.pl b/scripts/perl/timezones/tests.pl new file mode 100644 index 0000000000..3aec76ff68 --- /dev/null +++ b/scripts/perl/timezones/tests.pl @@ -0,0 +1,65 @@ +#!/usr/bin/perl +=pod +Tests to verify that the provided modifications to timezone formats produce +the correct results. The first set of tests verify the fixTzstr subroutine +converts the quoted values to something that won't make FreeSWITCH default to +UTC. + +The second set of tests confirms that those timezone changes actually produce +the correct timestamps. + +Make sure FreeSWITCH already has already loaded the timezones.conf.xml that you +want to test. + +To run tests: + +TIMEZONES_XML_PATH=path/to/timezones.conf.xml prove tests.pl +=cut + +use strict; +use warnings; +use Test::More; +use ESL; +use XML::LibXML::Reader; + +require "./fix-tzstr.pl"; + +use Env qw(TIMEZONES_XML_PATH); +die "The TIMEZONES_XML_PATH environment variable must be set to test timezones." unless ( defined($TIMEZONES_XML_PATH) ); + +ok( fixTzstr("<-02>2", "doesntmatterhere") eq "STD2" ); +ok( fixTzstr("EST5EDT,M3.2.0,M11.1.0", "US/Eastern") eq "EST5EDT,M3.2.0,M11.1.0" ); +ok( fixTzstr("<+11>-11", "GMT-11") eq "GMT-11" ); +ok( fixTzstr("<-02>2<-01>,M3.5.0/-1,M10.5.0/0", "America/Godthab") eq "STD2DST,M3.5.0/-1,M10.5.0/0" ); + +my $test_count = 4; + +my $tz_fmt = "%Y-%m-%d %H:%M:%S"; +my $c = new ESL::ESLconnection("127.0.0.1", "8021", "ClueCon"); +$c->api("reloadxml")->getBody(); +my $epoch = $c->api("strepoch")->getBody(); +run_tests($epoch); +run_tests("1699613236"); # testing DST, add more epochs as needed + +sub run_tests { + my $epoch = shift; + my $reader = XML::LibXML::Reader->new(location => $TIMEZONES_XML_PATH); + while ($reader->read) { + my $tag = $reader; + if ( $tag->name eq "zone" && $tag->hasAttributes() ) { + my $zn = $tag->getAttribute("name"); + + my $cmd = `TZ='$zn' date +'$tz_fmt' --date='\@$epoch'`; + my $sys_time = $cmd =~ s/\n//r; + my $fs_time = $c->api("strftime_tz $zn $epoch|$tz_fmt")->getBody(); + + ok ( $sys_time eq $fs_time, $zn ) or diag( + " (sys) $sys_time\t(fs) $fs_time" + ); + + $test_count++; + } + } +} + +done_testing($test_count); \ No newline at end of file diff --git a/scripts/perl/timezone-gen.pl b/scripts/perl/timezones/timezone-gen.pl similarity index 90% rename from scripts/perl/timezone-gen.pl rename to scripts/perl/timezones/timezone-gen.pl index e812023ef0..86822cc553 100755 --- a/scripts/perl/timezone-gen.pl +++ b/scripts/perl/timezones/timezone-gen.pl @@ -1,10 +1,12 @@ #!/usr/bin/perl use strict; +use warnings; use Getopt::Long; use XML::Entities; use HTML::Entities; +require "./fix-tzstr.pl"; my $base = "/usr/share/zoneinfo"; my $output = "timezones.conf.xml"; @@ -18,7 +20,7 @@ my $res = GetOptions( "base=s" => \$base, "debug+" => \$debug, "help" => \$help, - "output" => \$output + "output=s" => \$output ); if ( !$res || $help ) { print "$0 [--base=/usr/share/zoneinfo] [--output=timezones.conf.xml] [--debug] [--help]\n"; @@ -64,7 +66,9 @@ foreach my $name ( sort( keys(%name_to_file) ) ) { next; } - $zones{$name} = pop(@strings); + my $tzstr = fixTzstr( pop(@strings), $name ); + + $zones{$name} = $tzstr; } open( my $out, ">$output" ); @@ -83,7 +87,7 @@ foreach my $zone ( sort( keys(%zones) ) ) { } $lastprefix = $newprefix; - print $out "\t\n"; + print $out " " x 8, "\n"; } print $out " " x 4, "\n"; print $out "\n";