#!/usr/bin/perl -w
# 
# $Id: Datetime.pm,v 1.1.1.1 2003/10/28 16:04:37 andy Exp $
# 
# This code is copyright 1999-2000 by Scott Guelich <scott@scripted.com>
# and is distributed according to the same conditions as Perl itself
# Please visit http://www.scripted.com/wddx/ for more information
#

package WDDX::Datetime;

# Auto-inserted by build scripts
$VERSION = "1.01";

use strict;
use Carp;
use Time::Local;

require WDDX;

{ my $i_hate_the_w_flag_sometimes = [
    $WDDX::PACKET_HEADER,
    $WDDX::PACKET_FOOTER,
    $WDDX::Datetime::VERSION
] }

1;


#/-----------------------------------------------------------------------
# Public Methods
# 

sub new {
    my( $class, $value ) = @_;
    
    croak "You must supply the date in (integer) seconds when creating " .
          "Datetime objects\n" if $value =~ /\D/;
    
    my $self = {
        value   => $value,
        tz_info => 1,
    };
    
    bless $self, $class;
    return $self;
}


sub type {
    return "datetime";
}


sub as_packet {
    my( $self ) = @_;
    my $output = $WDDX::PACKET_HEADER .
                 $self->_serialize .
                 $WDDX::PACKET_FOOTER;
}


sub as_scalar {
    my( $self ) = @_;
    return $self->_deserialize;
}


sub as_javascript {
    my( $self, $js_var ) = @_;
    my $time_in_secs = $self->{value};
    
    my( $sec, $min, $hour, $day, $mon, $year ) = localtime( $time_in_secs );
    return "$js_var=new Date($year,$mon,$day,$hour,$min,$sec);";
}


# Timezone info is included in new packets by default
sub use_timezone_info {
    my( $self, $arg ) = @_;
    $self->{tz_info} = ( $arg ? 1 : 0 ) if defined $arg;
    return $self->{tz_info}
}

#/-----------------------------------------------------------------------
# Private Methods
# 

sub is_parser {
    return 0;
}


sub _serialize {
    my( $self ) = @_;
    my $time_in_secs = $self->{value};
    
    my( $sec, $min, $hour, $day, $mon, $year ) = localtime( $time_in_secs );
    my $output = sprintf "<dateTime>%02d-%02d-%02dT%02d:%02d:%02d",
                    $year + 1900, $mon + 1, $day, $hour, $min, $sec;
    $output .= tz_info() if $self->use_timezone_info;
    $output .= "</dateTime>";
    return $output;
}


sub _deserialize {
    my( $self ) = @_;
    return $self->{value};
}


# This generates the timezone info by looking at the difference between
# gmtime and localtime; uses functions from standard Time::Local module
sub tz_info {
    my $local = timelocal( localtime );
    my $gmt   = timegm   ( localtime );
    
    my $diff = abs( $gmt - $local );
    my $hrs  = int( $diff / ( 60 * 60 ) );
    my $mins = int( $diff / 60 ) - $hrs * 60;
    my $dir  = $gmt - $local >= 0 ? '+' : '-';
    
    return sprintf "$dir%0.2d:%0.2d", $hrs, $mins;
}


#/-----------------------------------------------------------------------
# Parsing Code
# 

package WDDX::Datetime::Parser;

use Time::Local;


sub new {
    my $class = shift;
    
    my $self = {
        value   => "",
        tz_info => undef
    };
    return bless $self, $class;
}


sub start_tag {
    my( $self, $element, $attribs ) = @_;
    
    unless ( $element eq "datetime" ) {
        die "<$element> not allowed within <datetime> element\n";
    }
    
    return $self;
}


sub end_tag {
    my( $self, $element ) = @_;
    my $value = $self->{value};
    my $time_in_secs;
    
    unless ( $element eq "datetime" ) {
        die "</$element> not allowed within <datetime> element\n";
    }
    
    my( $yr, $mon, $day, $hr, $min, $sec, $tz_dir, $tz_hr, $tz_min ) = 
     $value =~ /^(\d{4})-(\d+)-(\d+)T(\d+):(\d+):(\d+)(?:([+-])(\d+):(\d+))?$/i
     or die "Invalid dateTime value: '$value'\n";
    
    # Note: this isn't a Y2K bug; years >= 2000 represented w/ 3 digits
    $yr -= 1900;
    die "DateTime values prior to 1900-01-01 are not supported\n" if $yr < 0;
    $mon--;
    
    eval {
        $time_in_secs = timelocal( $sec, $min, $hr, $day, $mon, $yr );
    };
    if ( $@ ) {
        die "Invalid dateTime value. $@\n";
    }
    if ( $time_in_secs < 0 ) {
        die "DateTime value exceeds the integer limit for this machine\n";
    }
    
    if ( $tz_dir ) {
        # Adjust according to timezone info in packet
        if ( $tz_dir eq '+' ) {
            $time_in_secs += $tz_min * 60;
            $time_in_secs += $tz_hr  * 60 * 60;
        }
        else {
            $time_in_secs -= $tz_min * 60;
            $time_in_secs -= $tz_hr  * 60 * 60;
        }
        
        # Readjust to compensate for our own timezone diff relative to UTC/GMT
        my $tz_info = WDDX::Datetime::tz_info();
        my( $loc_dir, $loc_hr, $loc_min ) = $tz_info =~ /([+-])(\d+):(\d+)/;
        
        if ( $loc_dir eq '-' ) {
            $time_in_secs += $loc_min * 60;
            $time_in_secs += $loc_hr  * 60 * 60;
        }
        else {
            $time_in_secs -= $loc_min * 60;
            $time_in_secs -= $loc_hr  * 60 * 60;
        }
    }
    
    $self = new WDDX::Datetime( $time_in_secs );
    $self->use_timezone_info( 0 ) unless $tz_dir;
    
    return $self;
}


sub append_data {
    my( $self, $data ) = @_;
    $self->{value} .= $data;
}


sub is_parser {
    return 1;
}