]> git.ipfire.org Git - thirdparty/freeswitch.git/commitdiff
[contrib/timezone-gen] Fix timezone gen (#2215)
authorDouglas Vought <doug@vought.co>
Tue, 5 Sep 2023 20:11:01 +0000 (16:11 -0400)
committerGitHub <noreply@github.com>
Tue, 5 Sep 2023 20:11:01 +0000 (23:11 +0300)
* [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 [new file with mode: 0755]
scripts/perl/timezones/data/.gitignore [new file with mode: 0644]
scripts/perl/timezones/fix-tzstr.pl [new file with mode: 0644]
scripts/perl/timezones/tests.pl [new file with mode: 0644]
scripts/perl/timezones/timezone-gen.pl [moved from scripts/perl/timezone-gen.pl with 90% similarity]

diff --git a/scripts/perl/timezones/build-zonedata.pl b/scripts/perl/timezones/build-zonedata.pl
new file mode 100755 (executable)
index 0000000..347a743
--- /dev/null
@@ -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, "<data/version" ) {
+    $local_version = do { local $/; <$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 (file)
index 0000000..144983b
--- /dev/null
@@ -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 (file)
index 0000000..224c9a5
--- /dev/null
@@ -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 =~ /(<(?<std>[^>]+)>)([^<]+)(?<dst><.+>)?(?<rest>.+)?/ ) {
+        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 (file)
index 0000000..3aec76f
--- /dev/null
@@ -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
similarity index 90%
rename from scripts/perl/timezone-gen.pl
rename to scripts/perl/timezones/timezone-gen.pl
index e812023ef0bdff4438ac9157449843f7c3a87e4d..86822cc5539c25d8c993f1c5d0b29822838fadf2 100755 (executable)
@@ -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<zone name=\"$zone\" value=\"$str\" />\n";
+    print $out " " x 8, "<zone name=\"$zone\" value=\"$str\" />\n";
 }
 print $out " " x 4, "</timezones>\n";
 print $out "</configuration>\n";