#!/usr/bin/perl
# Blosxom Plugin: entries_index_tagged
# Author(s): Eric Sherman <enkidu@primitiveworker.org>
#            Rael Dornfest <rael@oreilly.com> 
# Version: 2003-05-18 (v0.2)
# Documentation: See the bottom of this file or type: perldoc entries_index_tagged
#
# This plugin replaces the default blosxom entries function, and Rael's entries_index plugin.

# TODO make a switch which defaults to off for the additional modules, update the docs in this file

package entries_index_tagged;

# --- Configurable variables -----
# --------------------------------

# the tag that specifies the creation date of this story
$timestamp_tag = "meta-creation_timestamp:" unless defined $timestamp_tag;
# another tag which does the same as the above, except is human writable
# in a format compatible with Time::ParseDate , e.g., meta-creation_date: 8/27/2005 16:23:30
$date_tag = "meta-creation_date:" unless defined $date_tag;

use Carp;
use File::stat;
use File::Find;
use Data::Dumper;
use CGI qw/:standard/;

my %mtable;
my %umult;
my %wdays;
my $y2k;

CONFIG:	{

	%mtable = qw(
		Jan 1	Jan. 1	January 1
		Feb 2	Feb. 2	February 2
		Mar 3	Mar. 3	March 3
		Apr 4	Apr. 4	April 4
		May 5 
		Jun 6	Jun. 6	June 6 
		Jul 7	Jul. 7	July 7 
		Aug 8	Aug. 8	August 8 
		Sep 9	Sep. 9	September 9 
		Oct 10	Oct. 10	October 10 
		Nov 11	Nov. 11	November 11 
		Dec 12	Dec. 12	December 12 );
	%umult = qw(
		sec 1 second 1
		min 60 minute 60
		hour 3600
		day 86400
		week 604800 );
	%wdays = qw(
		sun 0 sunday 0
		mon 1 monday 1
		tue 2 tuesday 2
		wed 3 wednesday 3
		thu 4 thursday 4
		fri 5 friday 5
		sat 6 saturday 6
		);

	$y2k = 946684800; # turn of the century
}

sub start {
	1;
}

sub entries {
	return sub {
		my(%files, %indexes);

		if ( open ENTRIES, "$blosxom::plugin_state_dir/.entries_index_tagged.index" ) {
			my $index = join '', <ENTRIES>;
			close ENTRIES;
			$index =~ /\$VAR1 = \{/ and eval($index) and !$@ and %files = %$VAR1;
		}

		for my $file (keys %files) { -f $file or do { $reindex++; delete $files{$file} }; }

		find(
			sub {
				my $d; 
				my $curr_depth = $File::Find::dir =~ tr[/][]; 
				if ( $blosxom::depth and $curr_depth > $blosxom::depth ) {
					$files{$File::Find::name} and delete $files{$File::Find::name};
					return;
				}

				$File::Find::name =~ m!^$blosxom::datadir/(?:(.*)/)?(.+)\.$blosxom::file_extension$!
					and $2 ne 'index' and $2 !~ /^\./ and (-r $File::Find::name)
				# to show or not to show future entries
					and (
					$blosxom::show_future_entries
						or stat($File::Find::name)->mtime <= time
				) 
					and ( $files{$File::Find::name} || ++$reindex )
					and ( $files{$File::Find::name} = $files{$File::Find::name} || decodeDate($File::Find::name) || stat($File::Find::name)->mtime )
			
				# Static
					and (
					param('-all') 
						or !-f "$blosxom::static_dir/$1/index." . $blosxom::static_flavours[0]
						or stat("$blosxom::static_dir/$1/index." . $blosxom::static_flavours[0])->mtime < stat($File::Find::name)->mtime
				)
					and $indexes{$1} = 1
					and $d = join('/', (blosxom::nice_date($files{$File::Find::name}))[5,2,3])
					and $indexes{$d} = $d
					and $blosxom::static_entries and $indexes{ ($1 ? "$1/" : '') . "$2.$blosxom::file_extension" } = 1;
			}, $blosxom::datadir
		);

		if ( $reindex ) {
			if ( open ENTRIES, "> $blosxom::plugin_state_dir/.entries_index_tagged.index" ) {
				print ENTRIES Dumper \%files;
				close ENTRIES;
			} else {
				warn "couldn't > $blosxom::plugin_state_dir/.entries_index_tagged.index: $!\n";
			}
		}

		return (\%files, \%indexes);
	};

}

# returns a unix-style mtime from a tag within the file
sub decodeDate {
	my ($thisFile) = $_;
	my $mtime = 0;

	open (FILE, $thisFile); 
	my @lines = <FILE>;	 
	close (FILE);

	# read the header and return the first time tag found
	foreach my $thisLine (@lines) {

		# the timestamp tag takes precedence if both are present
 
		# grab the mtime from the timestamp tag if it exists
		if ($thisLine =~ /^$timestamp_tag\s+.*$/) {
			($mtime) = $thisLine =~ m/^$timestamp_tag\s+([0-9]+)$/;
			return $mtime;
		}
		
		# grab the mtime from the date tag if it exists
		if ($thisLine =~ /^$date_tag\s+.*$/) {
			my ($dateString) = $thisLine =~ m/^$date_tag\s+(.*)$/;
			$mtime = parsedate($dateString);
			return $mtime;
		}
		
		# stop reading this file when we get to the end of the header
		# (the first blank line)
		last if $thisLine =~ /^\s*$/;
	}

	# no tag
	return 0;
}
sub parsedate
{
	my ($t, %options) = @_;

	my ($y, $m, $d);	# year, month - 1..12, day
	my ($H, $M, $S);	# hour, minute, second
	my $tz;		 	# timezone
	my $tzo;		# timezone offset
	my ($rd, $rs);		# relative days, relative seconds

	my $rel; 		# time&|date is relative

	my $isspec;
	my $now = $options{NOW} || time;
	my $passes = 0;
	my $uk = defined($options{UK}) ? $options{UK} : 0;

	local $parse = '';  # will be dynamically scoped.

	if ($t =~ s#^   ([ \d]\d) 
			/ (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)
			/ (\d\d\d\d)
			: (\d\d)
			: (\d\d)
			: (\d\d)
			(?:
			 [ ]
			 ([-+] \d\d\d\d)
			  (?: \("?(?:(?:[A-Z]{1,4}[TCW56])|IDLE)\))?
			 )?
			##xi) { #"emacs
		# [ \d]/Mon/yyyy:hh:mm:ss [-+]\d\d\d\d
		# This is the format for www server logging.

		($d, $m, $y, $H, $M, $S, $tzo) = ($1, $mtable{"\u\L$2"}, $3, $4, $5, $6, $7 ? &mkoff($7) : ($tzo || undef));
		$parse .= " ".__LINE__ if $debug;
	} elsif ($t =~ s#^(\d\d)/(\d\d)/(\d\d)\.(\d\d)\:(\d\d)(\s+|$)##) {
		# yy/mm/dd.hh:mm
		# I support this format because it's used by wbak/rbak
		# on Apollo Domain OS.  Silly, but historical.

		($y, $m, $d, $H, $M, $S) = ($1, $2, $3, $4, $5, 0);
		$parse .= " ".__LINE__ if $debug;
	} else {
		while(1) {
			if (! defined $m and ! defined $rd and ! defined $y
				and ! ($passes == 0 and $options{'TIMEFIRST'}))
			{
				# no month defined.
				if (&parse_date_only(\$t, \$y, \$m, \$d, $uk)) {
					$parse .= " ".__LINE__ if $debug;
					next;
				}
			}
			if (! defined $H and ! defined $rs) {
				if (&parse_time_only(\$t, \$H, \$M, \$S, 
					\$tz, %options)) 
				{
					$parse .= " ".__LINE__ if $debug;
					next;
				}
			}
			next if $passes == 0 and $options{'TIMEFIRST'};
			if (! defined $y) {
				if (&parse_year_only(\$t, \$y, $now, %options)) {
					$parse .= " ".__LINE__ if $debug;
					next;
				}
			}
			if (! defined $tz and ! defined $tzo and ! defined $rs 
				and (defined $m or defined $H)) 
			{
				if (&parse_tz_only(\$t, \$tz, \$tzo)) {
					$parse .= " ".__LINE__ if $debug;
					next;
				}
			}
			if (! defined $H and ! defined $rs) {
				if (&parse_time_offset(\$t, \$rs, %options)) {
					$rel = 1;
					$parse .= " ".__LINE__ if $debug;
					next;
				}
			}
			if (! defined $m and ! defined $rd and ! defined $y) {
				if (&parse_date_offset(\$t, $now, \$y, 
					\$m, \$d, \$rd, \$rs, %options)) 
				{
					$rel = 1;
					$parse .= " ".__LINE__ if $debug;
					next;
				}
			}
			if (defined $M or defined $rd) {
				if ($t =~ s/^\s*(?:at|\+)\s*(\s+|$)//x) {
					$rel = 1;
					$parse .= " ".__LINE__ if $debug;
					next;
				}
			}
			last;
		} continue {
			$passes++;
			&debug_display($tz, $tzo, $H, $M, $S, $m, $d, $y, $rs, $rd, $rel, $passes, $parse, $t) if $debug;

		}

		if ($passes == 0) {
			print "nothing matched\n" if $debug;
			return (undef, "no match on time/date") 
				if wantarray();
			return undef;
		}
	}

	&debug_display($tz, $tzo, $H, $M, $S, $m, $d, $y, $rs, $rd, $rel, $passes, $parse, $t) if $debug;

	$t =~ s/^\s+//;

	if ($t ne '') {
		# we didn't manage to eat the string
		print "NOT WHOLE\n" if $debug;
		if ($options{WHOLE}) {
			return (undef, "characters left over after parse")
				if wantarray();
			return undef 
		}
	}

	# define a date if there isn't one already

	if (! defined $y and ! defined $m and ! defined $rd) {
		print "no date defined, trying to find one." if $debug;
		if (defined $rs or defined $H) {
			# we do have a time.
			if ($options{DATE_REQUIRED}) {
				return (undef, "no date specified")
					if wantarray();
				return undef;
			}
			if (defined $rs) {
				print "simple offset: $rs\n" if $debug;
				my $rv = $now + $rs;
				return ($rv, $t) if wantarray();
				return $rv;
			}
			$rd = 0;
		} else {
			print "no time either!\n" if $debug;
			return (undef, "no time specified")
				if wantarray();
			return undef;
		}
	}

	if ($options{TIME_REQUIRED} && ! defined($rs) 
		&& ! defined($H) && ! defined($rd))
	{
		return (undef, "no time found")
			if wantarray();
		return undef;
	}

	my $secs;
	my $jd;

	if (defined $rd) {
		if (defined $rs || ! (defined($H) || defined($M) || defined($S))) {
			print "fully relative\n" if $debug;
			my ($j, $in, $it);
			my $definedrs = defined($rs) ? $rs : 0;
			my ($isdst_now, $isdst_then);
			my $r = $now + $rd * 86400 + $definedrs;
			#
			# It's possible that there was a timezone shift 
			# during the time specified.  If so, keep the
			# hours the "same".
			#
			$isdst_now = (localtime($r))[8];
			$isdst_then = (localtime($now))[8];
			if (($isdst_now == $isdst_then) || $options{GMT})
			{
				return ($r, $t) if wantarray();
				return $r 
			}
				
			print "localtime changed DST during time period!\n" if $debug;
		}

		print "relative date\n" if $debug;
		$jd = local_julian_day($now);
		print "jd($now) = $jd\n" if $debug;
		$jd += $rd;
	} else {
		unless (defined $y) {
			if ($options{PREFER_PAST}) {
				my ($day, $mon011);
				($day, $mon011, $y) = (&righttime($now))[3,4,5];

				print "calc year -past $day-$d $mon011-$m $y\n" if $debug;
				$y -= 1 if ($mon011+1 < $m) || 
					(($mon011+1 == $m) && ($day < $d));
			} elsif ($options{PREFER_FUTURE}) {
				print "calc year -future\n" if $debug;
				my ($day, $mon011);
				($day, $mon011, $y) = (&righttime($now))[3,4,5];
				$y += 1 if ($mon011 >= $m) || 
					(($mon011+1 == $m) && ($day > $d));
			} else {
				print "calc year -this\n" if $debug;
				$y = (localtime($now))[5];
			}
			$y += 1900;
		}

		$y = expand_two_digit_year($y, $now, %options)
			if $y < 100;

		if ($options{VALIDATE}) {
			require Time::DaysInMonth;
			my $dim = Time::DaysInMonth::days_in($y, $m);
			if ($y < 1000 or $m < 1 or $d < 1 
				or $y > 9999 or $m > 12 or $d > $dim)
			{
				return (undef, "illegal YMD: $y, $m, $d")
					if wantarray();
				return undef;
			}
		}
		$jd = julian_day($y, $m, $d);
		print "jd($y, $m, $d) = $jd\n" if $debug;
	}

	# put time into HMS

	if (! defined($H)) {
		if (defined($rd) || defined($rs)) {
			($S, $M, $H) = &righttime($now, %options);
			print "HMS set to $H $M $S\n" if $debug;
		} 
	}

	my $carry;

	print "before ", (defined($rs) ? "$rs" : ""),
		    " $jd $H $M $S\n" 
		if $debug;
	#
	# add in relative seconds.  Do it this way because we want to
	# preserve the localtime across DST changes.
	#

	$S = 0 unless $S; # -w
	$M = 0 unless $M; # -w
	$H = 0 unless $H; # -w

	if ($options{VALIDATE} and
		($S < 0 or $M < 0 or $H < 0 or $S > 59 or $M > 59 or $H > 23)) 
	{
		return (undef, "illegal HMS: $H, $M, $S") if wantarray();
		return undef;
	}

	$S += $rs if defined $rs;
	$carry = int($S / 60);
	my($frac) = $S - int($S);
	$S = int($S);
	$S %= 60;
	$S += $frac;
	$M += $carry;
	$carry = int($M / 60);
	$M %= 60;
	$H += $carry;
	$carry = int($H / 24);
	$H %= 24;
	$jd += $carry;

	print "after rs  $jd $H $M $S\n" if $debug;

	$secs = jd_secondsgm($jd, $H, $M, $S);
	print "jd_secondsgm($jd, $H, $M, $S) = $secs\n" if $debug;

	# 
	# If we see something link 3pm CST then and we want to end
	# up with a GMT seconds, then we convert the 3pm to GMT and
	# subtract in the offset for CST.  We subtract because we
	# are converting from CST to GMT.
	#
	my $tzadj;
	if ($tz) {
		$tzadj = tz_offset($tz, $secs);
		print "adjusting secs for $tz: $tzadj\n" if $debug;
		$tzadj = tz_offset($tz, $secs-$tzadj);
		$secs -= $tzadj;
	} elsif (defined $tzo) {
		print "adjusting time for offset: $tzo\n" if $debug;
		$secs -= $tzo;
	} else {
		unless ($options{GMT}) {
			if ($options{ZONE}) {
				$tzadj = tz_offset($options{ZONE}, $secs);
				$tzadj = tz_offset($options{ZONE}, $secs-$tzadj);
				print "adjusting secs for $options{ZONE}: $tzadj\n" if $debug;
				$secs -= $tzadj;
			} else {
				$tzadj = tz_local_offset($secs);
				print "adjusting secs for local offset: $tzadj\n" if $debug;
				# 
				# Just in case we are very close to a time
				# change...
				#
				$tzadj = tz_local_offset($secs-$tzadj);
				$secs -= $tzadj;
			}
		}
	}

	print "returning $secs.\n" if $debug;

	return ($secs, $t) if wantarray();
	return $secs;
}


sub mkoff
{
	my($offset) = @_;

	if (defined $offset and $offset =~ s#^([-+])(\d\d)(\d\d)$##) {
		return ($1 eq '+' ? 
			  3600 * $2  + 60 * $3
			: -3600 * $2 + -60 * $3 );
	}
	return undef;
}

sub parse_tz_only
{
	my($tr, $tz, $tzo) = @_;

	$$tr =~ s#^\s+##;
	my $o;

	if ($$tr =~ s#^
			([-+]\d\d\d\d)
			\s+
			\(
				"?
				(?:
					(?:
						[A-Z]{1,4}[TCW56]
					)
					|
					IDLE
				)
			\)
			(?:
				\s+
				|
				$ 
			)
			##x) { #"emacs
		$$tzo = &mkoff($1);
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	} elsif ($$tr =~ s#^GMT\s*([-+]\d{1,2})(\s+|$)##x) {
		$o = $1;
		if ($o <= 24 and $o !~ /^0/) {
			# probably hours.
			printf "adjusted at %d. ($o 00)\n", __LINE__ if $debug;
			$o = "${o}00";
		}
		$o =~ s/\b(\d\d\d)/0$1/;
		$$tzo = &mkoff($o);
		printf "matched at %d. ($$tzo, $o)\n", __LINE__ if $debug;
		return 1;
	} elsif ($$tr =~ s#^(?:GMT\s*)?([-+]\d\d\d\d)(\s+|$)##x) {
		$o = $1;
		$$tzo = &mkoff($o);
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	} elsif ($$tr =~ s#^"?((?:[A-Z]{1,4}[TCW56])|IDLE)(?:\s+|$ )##x) { #"
		$$tz = $1;
		$$tz .= " DST" 
			if $$tz eq 'MET' && $$tr =~ s#^DST(?:\s+|$ )##x;
		printf "matched at %d: '$$tz'.\n", __LINE__ if $debug;
		return 1;
	}
	return 0;
}

sub parse_date_only
{
	my ($tr, $yr, $mr, $dr, $uk) = @_;

	$$tr =~ s#^\s+##;

	if ($$tr =~ s#^(\d\d\d\d)([-./])(\d\d?)\2(\d\d?)(\s+|$)##) {
		# yyyy/mm/dd

		($$yr, $$mr, $$dr) = ($1, $3, $4);
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	} elsif ($$tr =~ s#^(\d\d?)([-./])(\d\d?)\2(\d\d\d\d?)(\s+|$)##) {
		# mm/dd/yyyy - is this safe?  No.
		# -- or dd/mm/yyyy! If $1>12, then it's umabiguous.
		# Otherwise check option UK for UK style date.
		if ($uk || $1>12) {
		  ($$yr, $$mr, $$dr) = ($4, $3, $1);
		} else {
		  ($$yr, $$mr, $$dr) = ($4, $1, $3);
		}
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	} elsif ($$tr =~ s#^(\d\d\d\d)/(\d\d?)(?:\s|$ )##x) {
		# yyyy/mm

		($$yr, $$mr, $$dr) = ($1, $2, 1);
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	} elsif ($$tr =~ s#^(?xi)
			(?:
				(?:Mon|Monday|Tue|Tuesday|Wed|Wednesday|
					Thu|Thursday|Fri|Friday|
					Sat|Saturday|Sun|Sunday),?
				\s+
			)?
			(\d\d?)
			(\s+ | - | \. | /)
			(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\.?
			(?:
				\2
				(\d\d (?:\d\d)? )
			)?
			(?:
				\s+
			|
				$
			)
			##) {
		# [Dow,] dd Mon [yy[yy]]
		($$yr, $$mr, $$dr) = ($4, $mtable{"\u\L$3"}, $1);

		printf "%d: %s - %s - %s\n", __LINE__, $1, $2, $3 if $debug;
		print "y undef\n" if ($debug && ! defined($$yr));
		return 1;
	} elsif ($$tr =~ s#^(?xi)
			(?:
				(?:Mon|Monday|Tue|Tuesday|Wed|Wednesday|
					Thu|Thursday|Fri|Friday|
					Sat|Saturday|Sun|Sunday),?
				\s+
			)?
			(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\.?
			((\s)+ | - | \. | /)
				
			(\d\d?)
			(?:
				(?: \2|\3+)
				(\d\d (?: \d\d)?)
			)?
			(?:
				\s+
			|
				$
			)
			##) {
		# [Dow,] Mon dd [yyyy]
		($$yr, $$mr, $$dr) = ($5, $mtable{"\u\L$1"}, $4);
		printf "%d: %s - %s - %s\n", __LINE__, $1, $2, $4 if $debug;
		print "y undef\n" if ($debug && ! defined($$yr));
		return 1;
	} elsif ($$tr =~ s#^(?xi)
			(January|Jan\.?|February|Feb\.?|March|Mar\.?|April|Apr\.?|May|
			    June|Jun\.?|July|Jul\.?|August|Aug\.?|September|Sep\.?|
			    October|Oct\.?|November|Nov\.?|December|Dec\.?)
			\s+
			(\d+)
			(?:st|nd|rd|th)?
			\,?
			(?: 
				\s+
				(?:
					(\d\d\d\d)
					|(?:\' (\d\d))
				)
			)?
			(?:
				\s+
			|
				$
			)
			##) {
		# Month day{st,nd,rd,th}, 'yy
		# Month day{st,nd,rd,th}, year
		($$yr, $$mr, $$dr) = ($3 || $4, $mtable{"\u\L$1"}, $2);
		printf "%d: %s - %s - %s - %s\n", __LINE__, $1, $2, $3, $4 if $debug;
		print "y undef\n" if ($debug && ! defined($$yr));
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	} elsif ($$tr =~ s#^(\d\d?)([-/.])(\d\d?)\2(\d\d?)(\s+|$)##x) {
		if ($1 > 31 || (!$uk && $1 > 12 && $4 < 32)) {
			# yy/mm/dd
			($$yr, $$mr, $$dr) = ($1, $3, $4);
		} elsif ($1 > 12 || $uk) {
			# dd/mm/yy
			($$yr, $$mr, $$dr) = ($4, $3, $1);
		} else {
			# mm/dd/yy
			($$yr, $$mr, $$dr) = ($4, $1, $3);
		}
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	} elsif ($$tr =~ s#^(\d\d?)/(\d\d?)(\s+|$)##x) {
		if ($1 > 31 || (!$uk && $1 > 12)) {
			# yy/mm
			($$yr, $$mr, $$dr) = ($1, $2, 1);
		} elsif ($2 > 31 || ($uk && $2 > 12)) {
			# mm/yy
			($$yr, $$mr, $$dr) = ($2, $1, 1);
		} elsif ($1 > 12 || $uk) {
			# dd/mm
			($$mr, $$dr) = ($2, $1);
		} else {
			# mm/dd
			($$mr, $$dr) = ($1, $2);
		}
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	} elsif ($$tr =~ s#^(\d\d)(\d\d)(\d\d)(\s+|$)##x) {
		if ($1 > 31 || (!$uk && $1 > 12)) {
			# YYMMDD
			($$yr, $$mr, $$dr) = ($1, $2, $3);
		} elsif ($1 > 12 || $uk) {
			# DDMMYY
			($$yr, $$mr, $$dr) = ($3, $2, $1);
		} else {
			# MMDDYY
			($$yr, $$mr, $$dr) = ($3, $1, $2);
		}
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	} elsif ($$tr =~ s#^(?xi)
			(\d{1,2})
			(\s+ | - | \. | /)
			(January|Jan\.?|February|Feb\.?|March|Mar\.?|April|Apr\.?|May|
			    June|Jun\.?|July|Jul\.?|August|Aug\.?|September|Sep\.?|
			    October|Oct\.?|November|Nov\.?|December|Dec\.?)
			(?:
				\2
				(
					\d\d
					(?:\d\d)?
				)
			)
			(:?
				\s+
			|
				$
			)
			##) {
		# dd Month [yr]
		($$yr, $$mr, $$dr) = ($4, $mtable{"\u\L$3"}, $1);
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	} elsif ($$tr =~ s#^(?xi)
			(\d+)
			(?:st|nd|rd|th)?
			\s+
			(January|Jan\.?|February|Feb\.?|March|Mar\.?|April|Apr\.?|May|
			    June|Jun\.?|July|Jul\.?|August|Aug\.?|September|Sep\.?|
			    October|Oct\.?|November|Nov\.?|December|Dec\.?)
			(?: 
				\,?
				\s+
				(\d\d\d\d)
			)?
			(:?
				\s+
			|
				$
			)
			##) {
		# day{st,nd,rd,th}, Month year
		($$yr, $$mr, $$dr) = ($3, $mtable{"\u\L$2"}, $1);
		printf "%d: %s - %s - %s - %s\n", __LINE__, $1, $2, $3, $4 if $debug;
		print "y undef\n" if ($debug && ! defined($$yr));
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	}
	return 0;
}

sub parse_time_only
{
	my ($tr, $hr, $mr, $sr, $tzr, %options) = @_;

	$$tr =~ s#^\s+##;

	if ($$tr =~ s!^(?x)
			(?:
				(?:
					([012]\d)		(?# $1)
					(?:
						([0-5]\d) 	(?# $2)
						(?:
						    ([0-5]\d)	(?# $3)
						)?
					)
					\s*
					([ap]m)?  		(?# $4)
				) | (?:
					(\d{1,2}) 		(?# $5)
					(?:
						\:
						(\d\d)		(?# $6)
						(?:
							\:
							(\d\d)	(?# $7)
								(
									(?# don't barf on database sub-second timings)
									(?:\:|\.)
									\d{1,6}
								)?	(?# $8)
						)?
					)
					\s*
					([apAP][mM])?		(?# $9)
				) | (?:
					(\d{1,2})		(?# $10)
					([apAP][mM])		(?# ${11})
				)
			)
			(?:
				\s+
				"?
				(				(?# ${12})
					(?: [A-Z]{1,4}[TCW56] )
					|
					IDLE
				)	
			)?
			(?:
				\s*
			|
				$
			)
			!!) { #"emacs
		# HH[[:]MM[:SS]]meridan [zone] 
		my $ampm;
		$$hr = $1 || $5 || $10 || 0; # 10 is undef, but 5 is defined..
		$$mr = $2 || $6 || 0;
		$$sr = $3 || $7 || 0;
		if (defined($8) && exists($options{SUBSECOND}) && $options{SUBSECOND}) {
			my($frac) = $8;
			substr($frac,0,1) = '.';
			$$sr += $frac;
		}
		print "S = $$sr\n" if $debug;
		$ampm = $4 || $9 || $11;
		$$tzr = $12;
		$$hr += 12 if $ampm and "\U$ampm" eq "PM" && $$hr != 12;
		$$hr = 0 if $$hr == 12 && "\U$ampm" eq "AM";
		$$hr = 0 if $$hr == 24;
		printf "matched at %d, rem = %s.\n", __LINE__, $$tr if $debug;
		return 1;
	} elsif ($$tr =~ s#noon(?:\s+|$ )##ix) {
		# noon
		($$hr, $$mr, $$sr) = (12, 0, 0);
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	} elsif ($$tr =~ s#midnight(?:\s+|$ )##ix) {
		# midnight
		($$hr, $$mr, $$sr) = (0, 0, 0);
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	}
	return 0;
}

sub parse_time_offset
{
	my ($tr, $rsr, %options) = @_;

	$$tr =~ s/^\s+//;

	return 0 if $options{NO_RELATIVE};

	if ($$tr =~ s#^(?xi)
			([-+]?)
			\s*
			(\d+)
			\s*
			(sec|second|min|minute|hour)s?
			(
				\s+
				ago
			)?
			(?:
				\s+
				|
				$
			)
			##) {
		# count units
		$$rsr = 0 unless defined $$rsr;
		$$rsr += $umult{"\L$3"} * "$1$2";

		$$rsr = -$$rsr if $4 ||
			$$tr =~ /\b(day|mon|month|year)s?\s*ago\b/;
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	} 
	return 0;
}

#
# What to you do with a date that has a two-digit year?
# There's not much that can be done except make a guess.
#
# Some example situations to handle:
#
#	now		year 
#
#	1999		01
#	1999		71
#	2010		71
#	2110		09
#

sub expand_two_digit_year
{
	my ($yr, $now, %options) = @_;

	return $yr if $yr > 100;

	my ($y) = (&righttime($now, %options))[5];
	$y += 1900;
	my $century = int($y / 100) * 100;
	my $within = $y % 100;

	my $r = $yr + $century;

	if ($options{PREFER_PAST}) {
		if ($yr > $within) {
			$r = $yr + $century - 100;
		}
	} elsif ($options{PREFER_FUTURE}) {
		# being strict here would be silly
		if ($yr < $within-20) {
			# it's 2019 and the date is '08'
			$r = $yr + $century + 100;
		}
	} elsif ($options{UNAMBIGUOUS}) {
		# we really shouldn't guess
		return undef;
	} else {
		# prefer the current century in most cases

		if ($within > 80 && $within - $yr > 60) {
			$r = $yr + $century + 100;
		}

		if ($within < 30 && $yr - $within > 59) {
			$r = $yr + $century - 100;
		}
	}
	print "two digit year '$yr' expanded into $r\n" if $debug;
	return $r;
}


sub calc 
{
	my ($rsr, $yr, $mr, $dr, $rdr, $now, $units, $count, %options) = @_;

	confess unless $units;
	$units = "\L$units";
	print "calc based on $units\n" if $debug;

	if ($units eq 'day') {
		$$rdr = $count;
	} elsif ($units eq 'week') {
		$$rdr = $count * 7;
	} elsif ($umult{$units}) {
		$$rsr = $count * $umult{$units};
	} elsif ($units eq 'mon' || $units eq 'month') {
		($$yr, $$mr, $$dr) = &monthoff($now, $count, %options);
		$$rsr = 0 unless $$rsr;
	} elsif ($units eq 'year') {
		($$yr, $$mr, $$dr) = &monthoff($now, $count * 12, %options);
		$$rsr = 0 unless $$rsr;
	} else {
		carp "interal error";
	}
	print "calced rsr $$rsr rdr $$rdr, yr $$yr mr $$mr dr $$dr.\n" if $debug;
}

sub monthoff
{
	my ($now, $months, %options) = @_;

	# months are 0..11
	my ($d, $m11, $y) = (&righttime($now, %options)) [ 3,4,5 ] ;

	$y += 1900;

	print "m11 = $m11 + $months, y = $y\n" if $debug;

	$m11 += $months;

	print "m11 = $m11, y = $y\n" if $debug;
	if ($m11 > 11 || $m11 < 0) {
		$y -= 1 if $m11 < 0 && ($m11 % 12 != 0);
		$y += int($m11/12);

		# this is required to work around a bug in perl 5.003
		no integer;
		$m11 %= 12;
	}
	print "m11 = $m11, y = $y\n" if $debug;

	# 
	# What is "1 month from January 31st?"  
	# I think the answer is February 28th most years.
	#
	# Similarly, what is one year from February 29th, 1980?
	# I think it's February 28th, 1981.
	#
	# If you disagree, change the following code.
	#
	if ($d > 30 or ($d > 28 && $m11 == 1)) {
		require Time::DaysInMonth;
		my $dim = Time::DaysInMonth::days_in($y, $m11+1);
		print "dim($y,$m11+1)= $dim\n" if $debug;
		$d = $dim if $d > $dim;
	}
	return ($y, $m11+1, $d);
}

sub righttime
{
	my ($time, %options) = @_;
	if ($options{GMT}) {
		return gmtime($time);
	} else {
		return localtime($time);
	}
}

sub parse_year_only
{
	my ($tr, $yr, $now, %options) = @_;

	$$tr =~ s#^\s+##;

	if ($$tr =~ s#^(\d\d\d\d)(?:\s+|$)##) {
		$$yr = $1;
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	} elsif ($$tr =~ s#\'(\d\d)(?:\s+|$ )##) {
		$$yr = expand_two_digit_year($1, $now, %options);
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	}
	return 0;
}

sub parse_date_offset
{
	my ($tr, $now, $yr, $mr, $dr, $rdr, $rsr, %options) = @_;

	return 0 if $options{NO_RELATIVE};

	# now - current seconds_since_epoch
	# yr - year return
	# mr - month return
	# dr - day return
	# rdr - relatvie day return
	# rsr - relative second return

	my $j;
	my $wday = (&righttime($now, %options))[6];

	$$tr =~ s#^\s+##;

	if ($$tr =~ s#^(?xi)
			\s*
			(\d+)
			\s*
			(day|week|month|year)s?
			(
				\s+
				ago
			)?
			(?:
				\s+
				|
				$
			)
			##) {
		my $amt = $1 + 0;
		my $units = $2;
		$amt = -$amt if $3 ||
			$$tr =~ m#\b(sec|second|min|minute|hour)s?\s*ago\b#;
		&calc($rsr, $yr, $mr, $dr, $rdr, $now, $units, 
			$amt, %options);
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	} elsif ($$tr =~ s#^(?xi)
			(?:
				(?:
					now
					\s+
				)?
				(\+ | \-)
				\s*
			)?
			(\d+)
			\s*
			(day|week|month|year)s?
			(?:
				\s+
				|
				$
			)
			##) {
		my $one = $1 || '';
		my $two = $2 || '';
		my $amt = "$one$two"+0;
		&calc($rsr, $yr, $mr, $dr, $rdr, $now, $3, 
			$amt, %options);
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	} elsif ($$tr =~ s#^(?xi)
			(Mon|Tue|Wed|Thu|Fri|Sat|Sun|Monday|Tuesday
				|Wednesday|Thursday|Friday|Saturday|Sunday)
			\s+
			after
			\s+
			next
			(?: \s+ | $ )
			##) {
		# Dow "after next"
		$$rdr = $wdays{"\L$1"} - $wday + ( $wdays{"\L$1"} > $wday ? 7 : 14);
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	} elsif ($$tr =~ s#^(?xi)
			next\s+
			(Mon|Tue|Wed|Thu|Fri|Sat|Sun|Monday|Tuesday
				|Wednesday|Thursday|Friday|Saturday|Sunday)
			(?:\s+|$ )
			##) {
		# "next" Dow
		$$rdr = $wdays{"\L$1"} - $wday 
				+ ( $wdays{"\L$1"} > $wday ? 0 : 7);
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	} elsif ($$tr =~ s#^(?xi)
			last\s+
			(Mon|Tue|Wed|Thu|Fri|Sat|Sun|Monday|Tuesday
				|Wednesday|Thursday|Friday|Saturday|Sunday)
			(?:\s+|$ )##) {
		# "last" Dow
		printf "c %d - %d + ( %d < %d ? 0 : -7 \n", $wdays{"\L$1"},  $wday,  $wdays{"\L$1"}, $wday if $debug;
		$$rdr = $wdays{"\L$1"} - $wday + ( $wdays{"\L$1"} < $wday ? 0 : -7);
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	} elsif ($options{PREFER_PAST} and $$tr =~ s#^(?xi)
			(Mon|Tue|Wed|Thu|Fri|Sat|Sun|Monday|Tuesday
				|Wednesday|Thursday|Friday|Saturday|Sunday)
			(?:\s+|$ )##) {
		# Dow
		printf "c %d - %d + ( %d < %d ? 0 : -7 \n", $wdays{"\L$1"},  $wday,  $wdays{"\L$1"}, $wday if $debug;
		$$rdr = $wdays{"\L$1"} - $wday + ( $wdays{"\L$1"} < $wday ? 0 : -7);
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	} elsif ($options{PREFER_FUTURE} and $$tr =~ s#^(?xi)
			(Mon|Tue|Wed|Thu|Fri|Sat|Sun|Monday|Tuesday
				|Wednesday|Thursday|Friday|Saturday|Sunday)
			(?:\s+|$ )
			##) {
		# Dow
		$$rdr = $wdays{"\L$1"} - $wday 
				+ ( $wdays{"\L$1"} > $wday ? 0 : 7);
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	} elsif ($$tr =~ s#^today(?:\s+|$ )##xi) {
		# today
		$$rdr = 0;
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	} elsif ($$tr =~ s#^tomorrow(?:\s+|$ )##xi) {
		$$rdr = 1;
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	} elsif ($$tr =~ s#^yesterday(?:\s+|$ )##xi) {
		$$rdr = -1;
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	} elsif ($$tr =~ s#^last\s+(week|month|year)(?:\s+|$ )##xi) {
		&calc($rsr, $yr, $mr, $dr, $rdr, $now, $1, -1, %options);
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	} elsif ($$tr =~ s#^next\s+(week|month|year)(?:\s+|$ )##xi) {
		&calc($rsr, $yr, $mr, $dr, $rdr, $now, $1, 1, %options);
		printf "matched at %d.\n", __LINE__ if $debug;
		return 1;
	} elsif ($$tr =~ s#^now (?: \s+ | $ )##x) {
		$$rdr = 0;
		return 1;
	}
	return 0;
}

sub debug_display
{
	my ($tz, $tzo, $H, $M, $S, $m, $d, $y, $rs, $rd, $rel, $passes, $parse, $t) = @_;
	print "---------<<\n";
	print defined($tz) ? "tz: $tz.\n" : "no tz\n";
	print defined($tzo) ? "tzo: $tzo.\n" : "no tzo\n";
	print "HMS: ";
	print defined($H) ? "$H, " : "no H, ";
	print defined($M) ? "$M, " : "no M, ";
	print defined($S) ? "$S\n" : "no S.\n";
	print "mdy: ";
	print defined($m) ? "$m, " : "no m, ";
	print defined($d) ? "$d, " : "no d, ";
	print defined($y) ? "$y\n" : "no y.\n";
	print defined($rs) ? "rs: $rs.\n" : "no rs\n";
	print defined($rd) ? "rd: $rd.\n" : "no rd\n";
	print $rel ? "relative\n" : "not relative\n";
	print "passes: $passes\n";
	print "parse:$parse\n";
	print "t: $t.\n";
	print "--------->>\n";
}

#JulianDay.pm
# calculate the julian day, given $year, $month and $day
sub julian_day
{
    my($year, $month, $day) = @_;
    my($tmp);

    use Carp;
#    confess() unless defined $day;

    $tmp = $day - 32075
      + 1461 * ( $year + 4800 - ( 14 - $month ) / 12 )/4
      + 367 * ( $month - 2 + ( ( 14 - $month ) / 12 ) * 12 ) / 12
      - 3 * ( ( $year + 4900 - ( 14 - $month ) / 12 ) / 100 ) / 4
      ;

    return($tmp);

}

sub gm_julian_day
{
    my($secs) = @_;
    my($sec, $min, $hour, $mon, $year, $day, $month);
    ($sec, $min, $hour, $day, $mon, $year) = gmtime($secs);
    $month = $mon + 1;
    $year += 1900;
    return julian_day($year, $month, $day)
}

sub local_julian_day
{
    my($secs) = @_;
    my($sec, $min, $hour, $mon, $year, $day, $month);
    ($sec, $min, $hour, $day, $mon, $year) = localtime($secs);
    $month = $mon + 1;
    $year += 1900;
    return julian_day($year, $month, $day)
}

sub day_of_week
{
	my ($jd) = @_;
        return (($jd + 1) % 7);       # calculate weekday (0=Sun,6=Sat)
}


# The following defines the first day that the Gregorian calendar was used
# in the British Empire (Sep 14, 1752).  The previous day was Sep 2, 1752
# by the Julian Calendar.  The year began at March 25th before this date.

$brit_jd = 2361222;

# Usage:  ($year,$month,$day) = &inverse_julian_day($julian_day)
sub inverse_julian_day
{
        my($jd) = @_;
        my($jdate_tmp);
        my($m,$d,$y);

        carp("warning: julian date $jd pre-dates British use of Gregorian calendar\n")
                if ($jd < $brit_jd);

        $jdate_tmp = $jd - 1721119;
        $y = (4 * $jdate_tmp - 1)/146097;
        $jdate_tmp = 4 * $jdate_tmp - 1 - 146097 * $y;
        $d = $jdate_tmp/4;
        $jdate_tmp = (4 * $d + 3)/1461;
        $d = 4 * $d + 3 - 1461 * $jdate_tmp;
        $d = ($d + 4)/4;
        $m = (5 * $d - 3)/153;
        $d = 5 * $d - 3 - 153 * $m;
        $d = ($d + 5) / 5;
        $y = 100 * $y + $jdate_tmp;
        if($m < 10) {
                $m += 3;
        } else {
                $m -= 9;
                ++$y;
        }
        return ($y, $m, $d);
}

{
	my($sec, $min, $hour, $day, $mon, $year) = gmtime(0);
	$year += 1900;
	if ($year == 1970 && $mon == 0 && $day == 1) {
		# standard unix time format
		$jd_epoch = 2440588;
	} else {
		$jd_epoch = julian_day($year, $mon+1, $day);
	}
	$jd_epoch_remainder = $hour*3600 + $min*60 + $sec;
}

sub jd_secondsgm
{
	my($jd, $hr, $min, $sec) = @_;

	my($r) =  (($jd - $jd_epoch) * 86400 
		+ $hr * 3600 + $min * 60 
		- $jd_epoch_remainder);

	no integer;
	return ($r + $sec);
	use integer;
}

sub jd_secondslocal
{
	my($jd, $hr, $min, $sec) = @_;
	my $jds = jd_secondsgm($jd, $hr, $min, $sec);
	return $jds - tz_local_offset($jds);
}

# this uses a 0-11 month to correctly reverse localtime()
sub jd_timelocal
{
	my ($sec,$min,$hours,$mday,$mon,$year) = @_;
	$year += 1900 unless $year > 1000;
	my $jd = julian_day($year, $mon+1, $mday);
	my $jds = jd_secondsgm($jd, $hours, $min, $sec);
	return $jds - tz_local_offset($jds);
}

# this uses a 0-11 month to correctly reverse gmtime()
sub jd_timegm
{
	my ($sec,$min,$hours,$mday,$mon,$year) = @_;
	$year += 1900 unless $year > 1000;
	my $jd = julian_day($year, $mon+1, $mday);
	return jd_secondsgm($jd, $hours, $min, $sec);
}
# end JulianDay.pm

#Timezone.pm
sub tz2zone
{
	my($TZ, $time, $isdst) = @_;

	use vars qw(%tzn_cache);

	$TZ = defined($ENV{'TZ'}) ? ( $ENV{'TZ'} ? $ENV{'TZ'} : 'GMT' ) : ''
	    unless $TZ;

	# Hack to deal with 'PST8PDT' format of TZ
	# Note that this can't deal with all the esoteric forms, but it
	# does recognize the most common: [:]STDoff[DST[off][,rule]]

	if (! defined $isdst) {
		my $j;
		$time = time() unless $time;
		($j, $j, $j, $j, $j, $j, $j, $j, $isdst) = localtime($time);
	}

	if (defined $tzn_cache{$TZ}->[$isdst]) {
		return $tzn_cache{$TZ}->[$isdst];
	}
      
	if ($TZ =~ /^
		    ( [^:\d+\-,] {3,} )
		    ( [+-] ?
		      \d {1,2}
		      ( : \d {1,2} ) {0,2} 
		    )
		    ( [^\d+\-,] {3,} )?
		    /x
	    ) {
		$TZ = $isdst ? $4 : $1;
		$tzn_cache{$TZ} = [ $1, $4 ];
	} else {
		$tzn_cache{$TZ} = [ $TZ, $TZ ];
	}
	return $TZ;
}

sub tz_local_offset
{
	my ($time) = @_;

	$time = time() unless $time;
	my (@l) = localtime($time);
	my $isdst = $l[8] || 0;
	my $tzenv = defined($ENV{TZ}) ? $ENV{TZ} : "__notz";

	if ($Timezone::tz_local{$tzenv} &&
	    defined($Timezone::tz_local{$tzenv}[$isdst])) {
		return $Timezone::tz_local{$tzenv}[$isdst];
	}

	$Timezone::tz_local{$tzenv}[$isdst] = &calc_off($time);

	return $Timezone::tz_local{$tzenv}[$isdst];
}

sub calc_off
{
	my ($time) = @_;

	my (@l) = localtime($time);
	my (@g) = gmtime($time);

	my $off;

	$off =	   $l[0] - $g[0]
		+ ($l[1] - $g[1]) * 60
		+ ($l[2] - $g[2]) * 3600;

	# subscript 7 is yday.

	if ($l[7] == $g[7]) {
		# done
	} elsif ($l[7] == $g[7] + 1) {
		$off += 86400;
	} elsif ($l[7] == $g[7] - 1) {
		$off -= 86400;
	} elsif ($l[7] < $g[7]) {
		# crossed over a year boundry!
		# localtime is beginning of year, gmt is end
		# therefore local is ahead
		$off += 86400;
	} else {
		$off -= 86400;
	}

	return $off;
}

# constants
# The rest of the file originally comes from Graham Barr <bodg@tiuk.ti.com> 
#
# Some references:
#  http://www.weltzeituhr.com/laender/zeitzonen_e.shtml
#  http://www.worldtimezone.com/wtz-names/timezonenames.html
#  http://www.timegenie.com/timezones.php

CONFIG: {
	use vars qw(%dstZone %zoneOff %dstZoneOff %Zone);

	%dstZone = (
	    "brst" =>	-2*3600,	 # Brazil Summer Time (East Daylight)
	    "adt"  =>	-3*3600,	 # Atlantic Daylight   
	    "edt"  =>	-4*3600,	 # Eastern Daylight
	    "cdt"  =>	-5*3600,	 # Central Daylight
	    "mdt"  =>	-6*3600,	 # Mountain Daylight
	    "pdt"  =>	-7*3600,	 # Pacific Daylight
	    "ydt"  =>	-8*3600,	 # Yukon Daylight
	    "hdt"  =>	-9*3600,	 # Hawaii Daylight
	    "bst"  =>	+1*3600,	 # British Summer   
	    "mest" =>	+2*3600,	 # Middle European Summer   
	    "met dst" => +2*3600,	 # Middle European Summer   
	    "sst"  =>	+2*3600,	 # Swedish Summer
	    "fst"  =>	+2*3600,	 # French Summer
	    "eest" =>	+3*3600,	 # Eastern European Summer
	    "cest" =>	+2*3600,	 # Central European Daylight
	    "wadt" =>	+8*3600,	 # West Australian Daylight
	    "kdt"  =>  +10*3600,	 # Korean Daylight
	#   "cadt" =>  +10*3600+1800,	 # Central Australian Daylight
	    "eadt" =>  +11*3600,	 # Eastern Australian Daylight
	    "nzdt" =>  +13*3600,	 # New Zealand Daylight	  
	);

	# not included due to ambiguity:
	#	IST     Indian Standard Time            +5.5
	#		Ireland Standard Time           0
	#		Israel Standard Time            +2
	#	IDT     Ireland Daylight Time           +1
	#		Israel Daylight Time            +3
	#	AMST    Amazon Standard Time /          -3
	#		Armenia Standard Time           +8
	#	BST	Brazil Standard			-3

	%Zone = (
	    "gmt"	=>   0,		 # Greenwich Mean
	    "ut"	=>   0,		 # Universal (Coordinated)
	    "utc"	=>   0,
	    "wet"	=>   0,		 # Western European
	    "wat"	=>  -1*3600,	 # West Africa
	    "azost"	=>  -1*3600,	 # Azores Standard Time
	    "cvt"	=>  -1*3600,	 # Cape Verde Time
	    "at"	=>  -2*3600,	 # Azores
	    "fnt"	=>  -2*3600,	 # Brazil Time (Extreme East - Fernando Noronha)
	    "ndt" 	=>  -2*3600-1800,# Newfoundland Daylight   
	    "art"	=>  -3*3600,	 # Argentina Time
	# For completeness.  BST is also British Summer, and GST is also Guam Standard.
	#   "gst"	=>  -3*3600,	 # Greenland Standard
	    "nft"	=>  -3*3600-1800,# Newfoundland
	#   "nst"	=>  -3*3600-1800,# Newfoundland Standard
	    "mnt"	=>  -4*3600,	 # Brazil Time (West Standard - Manaus)
	    "ewt"	=>  -4*3600,	 # U.S. Eastern War Time
	    "ast"	=>  -4*3600,	 # Atlantic Standard
	    "bot"	=>  -4*3600,	 # Bolivia Time
	    "vet"	=>  -4*3600,	 # Venezuela Time
	    "est"	=>  -5*3600,	 # Eastern Standard
	    "cot"	=>  -5*3600,	 # Colombia Time
	    "act"	=>  -5*3600,	 # Brazil Time (Extreme West - Acre)
	    "pet"	=>  -5*3600,	 # Peru Time
	    "cst"	=>  -6*3600,	 # Central Standard
	    "cest"	=>  +2*3600,	 # Central European Summer
	    "mst"	=>  -7*3600,	 # Mountain Standard
	    "pst"	=>  -8*3600,	 # Pacific Standard
	    "yst"	=>  -9*3600,	 # Yukon Standard
	    "hst"	=> -10*3600,	 # Hawaii Standard
	    "cat"	=> -10*3600,	 # Central Alaska
	    "ahst"	=> -10*3600,	 # Alaska-Hawaii Standard
	    "taht"	=> -10*3600,	 # Tahiti Time
	    "nt"	=> -11*3600,	 # Nome
	    "idlw"	=> -12*3600,	 # International Date Line West
	    "cet"	=>  +1*3600,	 # Central European
	    "mez"	=>  +1*3600,	 # Central European (German)
	    "met"	=>  +1*3600,	 # Middle European
	    "mewt"	=>  +1*3600,	 # Middle European Winter
	    "swt"	=>  +1*3600,	 # Swedish Winter
	    "set"	=>  +1*3600,	 # Seychelles
	    "fwt"	=>  +1*3600,	 # French Winter
	    "west"	=>  +1*3600,	 # Western Europe Summer Time
	    "eet"	=>  +2*3600,	 # Eastern Europe, USSR Zone 1
	    "ukr"	=>  +2*3600,	 # Ukraine
	    "sast"	=>  +2*3600,	 # South Africa Standard Time
	    "bt"	=>  +3*3600,	 # Baghdad, USSR Zone 2
	    "eat"	=>  +3*3600,	 # East Africa Time
	#   "it"	=>  +3*3600+1800,# Iran
	    "irst"	=>  +3*3600+1800,# Iran Standard Time
	    "zp4"	=>  +4*3600,	 # USSR Zone 3
	    "msd"	=>  +4*3600,	 # Moscow Daylight Time
	    "sct"	=>  +4*3600,	 # Seychelles Time
	    "zp5"	=>  +5*3600,	 # USSR Zone 4
	    "azst"	=>  +5*3600,	 # Azerbaijan Summer Time
	    "mvt"	=>  +5*3600,	 # Maldives Time
	    "uzt"	=>  +5*3600,	 # Uzbekistan Time
	    "ist"	=>  +5*3600+1800,# Indian Standard
	    "zp6"	=>  +6*3600,	 # USSR Zone 5
	    "lkt"	=>  +6*3600,	 # Sri Lanka Time
	    "pkst"	=>  +6*3600,	 # Pakistan Summer Time
	    "yekst"	=>  +6*3600,	 # Yekaterinburg Summer Time
	# For completeness.  NST is also Newfoundland Stanard, and SST is also Swedish Summer.
	#   "nst"	=>  +6*3600+1800,# North Sumatra
	#   "sst"	=>  +7*3600,	 # South Sumatra, USSR Zone 6
	    "wast"	=>  +7*3600,	 # West Australian Standard
	    "ict"	=>  +7*3600,	 # Indochina Time
	    "wit"	=>  +7*3600,	 # Western Indonesia Time
	#   "jt"	=>  +7*3600+1800,# Java (3pm in Cronusland!)
	    "cct"	=>  +8*3600,	 # China Coast, USSR Zone 7
	    "wst"	=>  +8*3600,	 # West Australian Standard
	    "hkt"	=>  +8*3600,	 # Hong Kong
	    "bnt"	=>  +8*3600,	 # Brunei Darussalam Time
	    "cit"	=>  +8*3600,	 # Central Indonesia Time
	    "myt"	=>  +8*3600,	 # Malaysia Time
	    "pht"	=>  +8*3600,	 # Philippines Time
	    "sgt"	=>  +8*3600,	 # Singapore Time
	    "jst"	=>  +9*3600,	 # Japan Standard, USSR Zone 8
	    "kst"	=>  +9*3600,	 # Korean Standard
	#   "cast"	=>  +9*3600+1800,# Central Australian Standard
	    "east"	=> +10*3600,	 # Eastern Australian Standard
	    "gst"	=> +10*3600,	 # Guam Standard, USSR Zone 9
	    "nct"	=> +11*3600,	 # New Caledonia Time
	    "nzt"	=> +12*3600,	 # New Zealand
	    "nzst"	=> +12*3600,	 # New Zealand Standard
	    "fjt"	=> +12*3600,	 # Fiji Time
	    "idle"	=> +12*3600,	 # International Date Line East
	);

	%zoneOff = reverse(%Zone);
	%dstZoneOff = reverse(%dstZone);

	# Preferences

	$zoneOff{0}	  = 'gmt';
	$dstZoneOff{3600} = 'bst';

}

sub tz_offset
{
	my ($zone, $time) = @_;

	return &tz_local_offset() unless($zone);

	$time = time() unless $time;
	my(@l) = localtime($time);
	my $dst = $l[8];

	$zone = lc $zone;

	if ($zone =~ /^([\-\+]\d{3,4})$/) {
		my $sign = $1 < 0 ? -1 : 1 ;
		my $v = abs(0 + $1);
		return $sign * 60 * (int($v / 100) * 60 + ($v % 100));
	} elsif (exists $dstZone{$zone} && ($dst || !exists $Zone{$zone})) {
		return $dstZone{$zone};
	} elsif(exists $Zone{$zone}) {
		return $Zone{$zone};
	}
	undef;
}

sub tz_name
{
	my ($off, $time) = @_;

	$time = time() unless $time;
	my(@l) = localtime($time);
	my $dst = $l[8];

	if (exists $dstZoneOff{$off} && ($dst || !exists $zoneOff{$off})) {
		return $dstZoneOff{$off};
	} elsif (exists $zoneOff{$off}) {
		return $zoneOff{$off};
	}
	sprintf("%+05d", int($off / 60) * 100 + $off % 60);
}
# end Timezone.pm
1;

__END__

=head1 NAME

Blosxom Plug-in: entries_index_tagged

=head1 SYNOPSIS

Purpose: Preserves original creation timestamp on weblog entries, 
allowing for editing of entries without altering the original 
creation time.

Maintains an index ($blosxom::plugin_state_dir/.entries_index.index) of 
filenames and their creation times.  Adds new entries to the index 
the first time Blosxom encounters them (read: is run after their 
creation).

This is a hack based on Rael's original entries_index.  This version
will set the date based on a meta tag, if found.  By default, the tag is
called meta-creation_timestamp.  It is recommended that you also install
the meta plugin so that the tag will not be displayed with the story.

The tag should be set to an mtime-style integer.  For instance, 
meta-creation_timestamp: 105114630 corresponds with April 23, 2003 9:05pm.
You may create this timestamp manually or with the encodeBlogDates perl 
script designed for this purpose (see below).  

This functionality is useful if you upload your stories to your host via a
method that does not preserve the original file's mtime, like FTP.  

Replaces the default $blosxom::entries subroutine, and consequently
Rael's entries_index plugin.  In other words, don't use entries_index if
you intend to use this plugin.

=head1 VERSION

2004-05-18 (v0.2)

=head1 AUTHORS

Eric Sherman (decodeDate) <enkidu@primitiveworker.org>, http://primitiveworker.org
Rael Dornfest (initial entries_index code) <rael@oreilly.com>, http://www.raelity.org/

=head1 SEE ALSO

encodeBlogDates script: http://primitiveworker.org/files/development/blosxom/entries_index_tagged/encodeBlogDates
entries_index plugin: http://www.raelity.org/apps/blosxom/plugins/indexing/entries_index.individual
meta plugin: http://www.raelity.org/apps/blosxom/plugins/meta/meta.individual
this plugin: http://primitiveworker.org/blo.g/development/blosxom/entries_index_tagged/
Blosxom Plugin Docs: http://www.raelity.org/apps/blosxom/plugin.shtml

=head1 BUGS

Address bug reports and comments to the Blosxom mailing list 
[http://www.yahoogroups.com/group/blosxom].

=head1 LICENSE

this Blosxom Plug-in
Copyright 2003, Eric Sherman

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
