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"