ad_page_contract { This page loads the linux time zones and time zone rules into $timezones_table and $timezone_rules_table @author pedroliska.com @creation-date 2007-04-13 @cvs-id $Id: load-timezones.tcl,v 1.3 2007/04/27 23:05:01 pliska Exp $ } ################################################################# # PLEASE SET THE FOLLOWING PARAMETERS: # action can be one of: dat_file, db_insert # - dat_file will generate dat files that can be loded by the # ref-timezone *.ctl scripts. See NOTE below. # - db_insert will insert the timezones directly into # an the DB set action "dat_file" # if action == dat_file, this param is needed. The timezones.dat and # timezone-rules.dat files will be placed in that directory set dat_file_directory [acs_root_dir]/packages/ref-timezones/sql/common # if action == db_insert, this param is needed set timezones_table timezones set timezone_rules_table timezone_rules set debug_p 0 # NOTE: # My goal was to get the current *.ctl files in ref-timezones package # to be able to load my .dat files but I ran into a problem with # generating the timezones-rules.dat file. I was not able to get Tcl's # clock procs to format the date to "Mon DD YYYY HH:MI:SS" format. So # you have to modify it's .ctl file to load it. The date is in the # following format: "Dy Mon DD HH24:MI:SS YYYY". ################################################################# # You should not need to modify anything below this line proc get_tz_names {} { set timezone_list [list] set line_list [split [exec -- find /usr/share/zoneinfo/ -type f -print | grep -v /right/ | grep -v /posix] \n] foreach line $line_list { regexp {^/usr/share/zoneinfo/(.*)$} $line match timezone lappend timezone_list $timezone } return $timezone_list } # Returns the timezone rules for tz_name proc get_linux_tz_rules {tz_name} { return [split [exec -- zdump -v /usr/share/zoneinfo/$tz_name] \n] } # Returns a Tcl list of Linux timezone rules where each rule is a Tcl list containing: # - The UTC time # - The Local Time # - Is it daylight savings time (0,1) # - The GMT offset proc get_tz_rules {tz_name} { set tz_rules [list] with_catch errmsg { # On a couple of rules, I got a warning which made the proc break. The warnings were all # like this: zdump: warning: zone "/usr/share/zoneinfo/iso3166.tab" abbreviation "/usr/share/zoneinfo/iso" differs from POSIX standard set linux_rules [get_linux_tz_rules $tz_name] } { ns_log notice "get_tz_rules_no_cache: Rules ignored for $tz_name due to the following error: $errmsg" set linux_rules "" } foreach line $linux_rules { # Sometimes the abbrev has strange values like 'Local time zone must be set--see zic manual page', # so I can't assume it's only one word. This is why I have to rely on the local datetime to be # 24 chars in length (which is quite safe to assume) if [regexp "^/usr/share/zoneinfo/$tz_name +\(.+\) UTC = \(.{24}\) \(.+\) isdst=\(.+\) gmtoff=\(.+\)\$" $line match utc_datetime local_datetime abbrev isdst gmtoff] { lappend tz_rules [list $utc_datetime $local_datetime $abbrev $isdst $gmtoff] #ns_write "$utc_datetime|$local_datetime|$abbrev|$isdst|$gmtoff\n" } } return $tz_rules } proc secs_to_hhmiss {secs} { if {$secs < 0} { set sign "-" } else { set sign "+" } set secs [expr abs($secs)] set hours [format %02d [expr $secs / 3600]] set minutes [format %02d [expr ($secs % 3600) / 60]] set seconds [format %02d [expr ($secs % 3600) % 60]] return $sign$hours$minutes$seconds } ################################################################# ReturnHeaders text/html ns_write "
\n" # action can be one of: dat_file, db_insert switch $action { dat_file { set tz_filename "$dat_file_directory/timezones.dat" set tz_file [open $tz_filename w] set tz_rules_filename "$dat_file_directory/timezone-rules.dat" set tz_rules_file [open $tz_rules_filename w] ns_write "Inserting timezone and timezone_rules into the following files:\n" ns_write " $tz_filename\n" ns_write " $tz_rules_filename\n\n" } db_insert { ns_write "Deleting ALL from the $timezones_table table\n" db_dml "delete timezones" "delete from $timezones_table" ns_write "Deleting ALL from the $timezone_rules_table table\n" db_dml "delete timezone rules" "delete from $timezone_rules_table" ns_write "Inserting timezone and timezone_rules into the DB\n" } default { error "action '$action' is not supported" } } set tz_id 0 foreach tz_name [get_tz_names] { # Get the lines of that rule set rules [get_tz_rules $tz_name] set rule_count [llength $rules] if {$rule_count <= 0} { # If there are no rules, let's not insert that timezone in the DB. # There were several GMT+N timezones that had no rules continue } # There are some rules, like the ones for the iso3166.tab timezone, that # have weird abbreviations. I'll just ignore those set weird_abbrev_found 0 foreach rule $rules { set abbrev [lindex $rule 2] if {[string length $abbrev] > 10} { set weird_abbrev_found 1 break } } if $weird_abbrev_found { continue } # get the gmt_offset (hhmiss) from the last rule line where isdst=0 set isdst 1 set last_non_dst_line $rule_count while {$isdst == 1} { set isdst [lindex [lindex $rules [incr last_non_dst_line -1] ] 3] } set gmt_offset_in_seconds [lindex [lindex $rules $last_non_dst_line] 4] # the linux gmtoff values are in seconds so convert it to hhmiss for the DB set gmt_offset_for_db [secs_to_hhmiss $gmt_offset_in_seconds] ns_write " Time zone: $tz_name. Linux rule count: $rule_count\n" incr tz_id # action can be one of: dat_file, db_insert switch $action { dat_file { puts $tz_file "$tz_id,$tz_name,$gmt_offset_for_db" } db_insert { db_dml "insert the timezone" " insert into $timezones_table (tz_id, tz, gmt_offset) values (:tz_id,:tz_name,:gmt_offset_for_db)" } } # ignore the first and last lines, they just specify the lowest and highest # possible time values # All the date ranges begin in odd-indexed lines # the date ranges end in the date/time specified by the subsequent line # This algorith automatically ignores the first line (index = 0) but we # also have to ignore the last line. for {set i 1} {$i < [expr $rule_count -1]} {incr i 2} { set current_rule [lindex $rules $i] set next_rule [lindex $rules [expr $i + 1]] if $debug_p { ns_write " $current_rule\n" ns_write " $next_rule\n" } set utc_start [lindex $current_rule 0] set utc_end [lindex $next_rule 0] set local_start [lindex $current_rule 1] set local_end [lindex $next_rule 1] set abbrev [lindex $current_rule 2] set isdst [ad_decode [lindex $current_rule 3] 1 t f] set gmt_offset [lindex $current_rule 4] if $debug_p { ns_write " $utc_start\n" ns_write " $utc_end\n" ns_write " $local_start\n" ns_write " $local_end\n" ns_write " $abbrev\n" ns_write " $isdst\n" ns_write " $gmt_offset\n" } # action can be one of: dat_file, db_insert switch $action { dat_file { # Tcl is not able to format the beginning (1901) and ending (2038) # date ranges. It's probably an off-by-one problem So I decided to # modify the format strings in the .ctl files # set datetime_format "%h %d %Y %H:%M:%S" # with_catch errmsg { # set utc_start [clock format [clock scan $utc_start -gmt 1] -format $datetime_format -gmt 1] # set utc_end [clock format [clock scan $utc_end -gmt 1] -format $datetime_format -gmt 1] # set local_start [clock format [clock scan $local_start -gmt 1] -format $datetime_format -gmt 1] # set local_end [clock format [clock scan $local_end -gmt 1] -format $datetime_format -gmt 1] # } { # # One of the dates could not be formatted by Tcl. This usually happens when the # # date is too far in the past or in the future. Since these rules are at the ends # # of the supported date ranges, ignoring the rule is safe. # if $debug_p { # ns_write " ERROR: $errmsg\n" # } # continue # } puts $tz_rules_file "$tz_id,$abbrev,$utc_start,$utc_end,$local_start,$local_end,$gmt_offset,$isdst" } db_insert { db_dml "insert the timezone rules" " insert into $timezone_rules_table ( tz_id, abbrev, utc_start, utc_end, local_start, local_end, gmt_offset, isdst ) values ( :tz_id, :abbrev, to_date(:utc_start,'Dy Mon DD HH24:MI:SS YYYY'), to_date(:utc_end,'Dy Mon DD HH24:MI:SS YYYY'), to_date(:local_start,'Dy Mon DD HH24:MI:SS YYYY'), to_date(:local_end,'Dy Mon DD HH24:MI:SS YYYY'), :gmt_offset, :isdst)" } } } } # action can be one of: dat_file, db_insert switch $action { dat_file { close $tz_file close $tz_rules_file ns_write "Timezones written to files: $tz_id\n" } db_insert { ns_write "Time zones inserted in the DB: $tz_id\n" } } ns_write "\n"