#!/usr/bin/perl
###########################################
#
# Simple Time Zone Converter (c) Arjun Roychowdhury, arjunrc@gmail.com
#
# This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
#################################################
# compilation for executable: 
# pp -p tz.pl -c -o tz.par
# open par with 7zip, paste in perl\site\lib
# pp tz.par -o tz.cgi

use DateTime;
use Date::Parse;
use DateTime::TimeZone;
use Text::Capitalize;
use DateTime::Locale;
use File::Slurp;

my %cities; # will be populated with convenience names from file
my %tzmaps; # will contain full timezone and shortname hash
my %revtzmaps; 

$glob_tzlist ="tzlist.read";
$glob_tzmap="tzmap.read";
$glob_revtzmap = "revtzmap.read";

#-------------------------------------------------------------------------------#
# Simple Date/Time and Timezone converer
#
# I needed a flexible no-nonsense Date/Time TZ converter that 
# allowed me to enter free form text. Did not find any simple 
# enough to use, so wrote this.
#
#							- Arjun Roychowdhury
# ------------------------------------------------------------------------------#
#
# CHANGELOG:
# April 23 2008: Version 1.3
#	- used file::slurp to decrease starup time
#	- moved tzmaps, revtzmaps to files for faster processing
# Mar 30 2008: Version 1.2
# 	- added common convenience names 
# 	- added option to edit convenience names at runtime
# 	- hashed tz/shot name list for faster processing (but slower start)
#  Mar 08 2008: Version 1.1
#		- added comma separated dest
# Nov 29/2006: Version 1.0
# 		- original version
#
 
#---------------------------------------------------------
# Error handler for browser environment
#---------------------------------------------------------
 use CGI;
 use CGI::Carp qw(fatalsToBrowser set_message);
 BEGIN
 {
	 sub handle_errors
	 {
		 my $msg = shift;
		 print "<p style='color:white; background-color:red'>ERROR: $msg</p>";
	 }
	 set_message(\&handle_errors);
}

# if the user decides to select a TZ from the dropdown list,
 # the timezone text input is automatically updated
 $JSCRIPT=<<EOF;
	function updateVar(elem,x)
	 {	
		var v = elem.options[elem.selectedIndex].text;
		if (x == '1')
		{
			document.timezone.form_stz.value = v;
		}
		else
		{
			document.timezone.form_dtz.value = v;
		}
	}	
EOF
;
#---------------------------------------------------------
# returns version
#---------------------------------------------------------
sub version()
{
	return "1.3";
}

#---------------------------------------------------------
# Debug printing
#---------------------------------------------------------
sub print_dbg {
    print "<small><font color=blue><i>DBG::", @_, "</i></font></small><br>\n" if ($x_dbg);
}

#---------------------------------------------------------
# remove leading and trailing ws
#---------------------------------------------------------
sub trim($)
{
	my $string = shift;
	$string =~ s/^\s+//;
	$string =~ s/\s+$//;
	return $string;
}

#---------------------------------------------------------
# Given a convenience name, converts to 
# proper TZ value
#---------------------------------------------------------
sub convenience_convert($)
{
	my $sstring = shift;
	return $sstring if (!$sstring);
	$sstring=lc($sstring);
	my $conv = "";
	$conv = $cities{$sstring};
		
	$conv = $sstring if (!$conv);
print_dbg ("Converted ".$sstring. " to ".$conv) if ($sstring ne $conv);
	return $conv;
}

#---------------------------------------------------------
# MAIN 
#---------------------------------------------------------
$glob_first=0;
#grab all well formatted timezones
	@tzs=DateTime::TimeZone->all_names;
	unshift(@tzs,""); # add a blank entry on top - just for form display

if (-r $glob_tzmap) 
{
	my $text = read_file($glob_tzmap);
	%tzmaps = $text =~ /^(.+)=(.+)$/mg ;

	my $text2 = read_file($glob_revtzmap);
	%revtzmaps = $text2 =~ /^(.+)=(.+)$/mg ;
	#while (@temp=each(%tzmaps)) {print "KEY:VALUE is @temp\n";}
	#while (@temp2=each(%revtzmaps)) {print "REVKEY:VALUE is @temp2\n";}
	#exit;
}
else
{
	my $ddt = DateTime->now;
	#populate tz/shortname hash for faster search
	open (FH1,">$glob_tzmap");
	open (FH2,">$glob_revtzmap");
	for my $ndx (@tzs)
	{
		next if (!$ndx);
		$ddt->set_time_zone($ndx);
		my $sname = $ddt->time_zone_short_name;
		$tzmaps{$sname}=$ndx;
		#user can enter long names in mixed cases, so store key as all ucase so i can compare with uc
		$revtzmaps{uc($ndx)}=uc($sname);
		print FH1 "$sname=$ndx\n";
		$rsname=uc($sname);
		$rndx=uc($ndx);
		print FH2 "$rndx=$rsname\n";
	}
	close(FH1);
	close(FH2);
	$glob_first++;
}

#exit if ($glob_first);

# read convenience mappings before each query
		open (FH,"<mycities.inc") || die "Cannot find convenience mappings";
		@cdd = ();
		while (<FH>)
		{
			 s/#.*//; 
			 next if /^(\s)*$/;
			chomp;
			($key,$value)=split(':',$_);
			$key = trim ($key);
			$value = trim ($value);
			$cities{$key}=$value;
			push (@cdd, join(' is mapped to ',$key, $value));
		}	
#		print_dbg("A total of ".keys(%cities)." convenience mappings have been detected in mycities.inc");


$ver=version();
 $q = new CGI;
 print $q->header(),
       $q->start_html(-title=>'No-Nonsense TimeZoneConverter',-script=>$JSCRIPT,-style=>{-src=>'/tz.css'},),
       $q->h3('No-Nonsense TimeZone Converter v'.$ver),

       # start defining the form that will ask for values
       $q->start_form(-name=>'timezone'),
       "Time (free form text) ",$q->textfield('form_time','',50), "<a href='/tzhelp.html' target='_blank'> help</a>",$q->br,
       "<small><i> enter time in whole or part, like 'nov 17 4:45p paris to india,london,oulu' or '12:20a ist to america/new_york' etc.</i></small>"
	   ,$q->p,

	"<span id='arc_note'>List of convenience mappings you can use above:", $q->popup_menu(-name=>'conv_dropdown', -values=>\@cdd),
		$q->br,"To add to the list of convenience mappings, click <a href='/cgi-bin/cv.cgi'>here</a></span>",
$q->p,
"<a href='#' onclick=\"document.getElementById('arc_options').style.display='inline';\">Show</a>",
	" or <a href='#' onclick=\"document.getElementById('arc_options').style.display='none';\">Hide</a> more options",
$q->br,
"<span id='arc_options'>",


	"Optional: (use this only if you have not entered all the information above)",$q->p,
	   "Source Timezone ",$q->textfield('form_stz','',20)," or ",
		$q->popup_menu(-name=>'stz_dropdown',-values=>\@tzs,  -onChange=>"updateVar(this,1)"), 
		"<small><i>either select from a list or type it in - short forms work too, like cst,est...</i></small>",$q->br(),
       "Target Timezone ",$q->textfield('form_dtz','',20)," or ",
		$q->popup_menu(-name=>'dtz_dropdown',-values=>\@tzs,-onChange=>"updateVar(this,2)"), 
		"<small><i>in simplest form, leave everything blank and just fill in dst</i></small>",
		$q->p,
	  "</span>",
		$q->p,
		$q->submit('submit','submit'),
		$q->defaults('reset'), $q->p,
		$q->checkbox('form_dbg',1,1,'debug'),
		$q->end_form,
		"<p><small><i>Tried many world times,none did what I needed easily...</i></small>",
		$q->hr,"\n";
  
    if ($q->param()) 
    {
	
		my $s_time = $q->param('form_time');
		$x_dbg = $q->param('form_dbg'); # if checked, this will display useful debug output
		($a,$b) = split (' to ',$s_time); # if user typed ' to ' that means the time box has both source and destination inputs
	    $a=trim($a);
	    $b=trim($b);

	    		
		# now see if user has specified multiple :0
	    # timezones

		
	    @destinations=split(',',$b);
		my $list_dest = $q->param('form_dtz');
		push @destinations, $list_dest if ($list_dest); # if there is a dest in the dropdown, add it
		$original_a = $a;
		
	    foreach $b (@destinations)
	    {
			$a = $original_a; # since we strip tz from $a, we need the original back for a comma separated list
			$b=trim($b);
			my @atz=($q->param('form_stz'), $q->param('form_dtz'));
            # resolve EST ambiguity - bias towards US/EST here - since EST is also used for other timezones in the world
	  	   if (lc($atz[0]) eq "est")    {$atz[0]="EST5EDT";print_dbg("Source:Converted EST to EST5EDT (I assumed you meant EST of USA)");}
		   if (lc($atz[1]) eq "est") {$atz[1]="EST5EDT";print_dbg("Dest:Converted EST to EST5EDT (I assumed you meant EST of USA)");}
		   
		   $b = convenience_convert ($b);
		    
		   if ($b)
		   {
			print_dbg("Found all values in Time box..");
		   $atz[1]=$b;
		   } #if b
		   else
		   {
			   print_dbg("looks like you did not specify destination in time box - so I will check the other boxes...");
		   }
				
			# now we need to check if $a also has TZ
			# logic is we check for last word. If it ends with 't' and does not
			# begin with 'a', it is a timezone, since otherwise it may be august
			# Alternately, if it had a '/' then it is also a timezone

			$olda=$a;
			$a =~s/(\S+)$//; #get last word in $1, remove last word from time
			$etz=$1;

				

			# now that we have extracted the last word, let us see if it is really a timezone
			#if ( ((lc(substr($etz,-1,1)) eq "t") && (lc(substr($etz,0,1)) ne "a")) || ($etz =~ m:/:) || ($etz ne convenience_convert($etz)))
			#{
				$s_time = $a;
				$atz[0] = $etz;
			#print_dbg ("IT IT S ATZ");
			#}
			#else # last word was not a timezone
			#{
			#	$s_time=$olda; # so, put it back to where it belongs
			#	print_dbg ("Last word not FQTZ reverting back to $s_time");
			#}

		   	if (!$atz[0]) 
			{
				$atz[0]="America/New_York"; 
				print_dbg("You did not specify a source timezone, so I am defaulting to America/New_York");
			}
		   	if (!$atz[1]) 
			{
				$atz[1]="America/New_York"; 
				print_dbg("You did not specify a destination timezone, so I am defaulting to America/New_York");
			}

			if ((!($atz[0]=~m:/:)) || (!($atz[1]=~m:/:)))
			{
				print_dbg("You are using shortcodes in timezones. Remember that the same shortcode can represent different time zones");
				print_dbg("So I am going to try a best match. If it is not what you want, I suggest you use the full Timezone name from the dropdown-list");
			}

			$atz[0] = convenience_convert($atz[0]);
			$atz[1] = convenience_convert($atz[1]);
		     # flexible parser for freeform date/time entries
		   ($xss,$xmin,$xhr,$xday,$xmonth,$xyear,) = strptime($s_time);

			
		 

		   # in windows, perl could not figure out my local time with 'local', 
		   # did not bother investigating...
	       
			   my $oopsie=0;
			   foreach $elem (@atz)
			   {
				    if   ($tzmaps{uc($elem)})
				   {
					   print_dbg("Found ". uc($elem).", replacing with".$tzmaps{uc($elem)}
					   ."  (which supposedly also uses a timezone shortcode of ".uc($elem).")");
					   $elem = $tzmaps{uc($elem)};
					}
					elsif (!$revtzmaps{uc($elem)}) 
					
					{
						
							print "<p style='color:black; background-color:yellow'>Oops. I don't recognize '$elem'..</p>";					
							$oopsie=1;
						
					}
			   }	
		   next if $oopsie;
		   my $s_tz = $atz[0];
		   my $d_tz = $atz[1];
			
		 
		   $s_tz=capitalize($s_tz);
		   $d_tz=capitalize($d_tz);

		   # the above does not capitalize the character after _ so let us do it manually
		   # otherwise set_time_zone barfs - it needs exact capitalization

		   $s_tz =~ s/_(.)/"_".uc($1)/eg;
		   $d_tz =~ s/_(.)/"_".uc($1)/eg;
		   print_dbg("Final values: STZ=$s_tz and DTZ=$d_tz");

	       my $source = DateTime->now->set_time_zone($s_tz);
		   # we start with current time, and then replace with whatever the user enters.
		   if ($s_time)
		   {

			$source->set_hour($xhr) if (defined $xhr);
			$source->set_minute($xmin) if (defined $xmin);
			$source->set_day($xday) if (defined $xday);
			$source->set_month($xmonth+1) if (defined $xmonth);
			$source->set_year($xyear+1900) if (defined $xyear);
		   }

		   # make a copy with the dest. tz
		   my $result = $source->clone()
	                    ->set_time_zone($d_tz);
		   print "<h3>",$source->strftime(" %a, %b %d %Y: "),"<font color=blue>",$source->strftime("%I:%M%P %Z"), 
		   "</font> is ". $result->strftime(" %a, %b %d %Y: ")."<font color=red>", 
	           $result->strftime("%I:%M%P %Z"),"</font></h3>";

	  } # end foreach dest
    } #q->param
#    print "<img src=\"../GMT2.jpg\" />"; # just a pretty picture..
print $q->end_html;