#!/usr/bin/perl

#covert *some* common ISO (and ISO-like?) date/time formats to seconds
#since the epoch, preserve any provided decimal portion

$^W=1;
use strict;

use POSIX qw(mktime tzset);

my $usage=<<""
usage: $0 [-0] [--help|-h|-?] [file|timespec] ...
$0 reads timespecs from command line argument(s) and/or files,
or standard input, if no non-option arguments are given,
and outputs seconds since the epoch.
-h|--help overrides all other options/arguments/input and provides usage
-0 null terminated/separated input and output (default is \\n)
timespecs should be of the form:
YYYY-MM-DD(T| )HH:MM:SS(\\.\\d+|[-+]\\d{2}(|:?\\d{2}))?
non-option arguments matching the above are taken as timespecs,
otherwise they are taken as files.

;

use Getopt::Long;
use File::Find;

Getopt::Long::Configure (

	# start with "sane", conservitive POSIX (compatible) defaults
	"posix_default",

	# then tweak as seems fitting
	"bundling"

);

my $null=undef; # -0 option
my $help=undef; # --help|-h|-? option

GetOptions	(
				"0" => \$null,
				"help|h|?" => \$help
			)
	or die	(	"$0: bad option(s), aborting\n${usage}aborting"
);

my $error=0;		# track if we got error(s)

sub printorwarnerror{
	if(!print(@_)){
		warn "$0: print failed";
		$error=1;
		return 0;
	}else{
		return 1;
	};
};

if($help){
	if(!$null && $#ARGV == -1){
		# no other options or arguments
		&printorwarnerror("$usage") or die;
		exit 0;
	}else{
		warn "$0: --help|-h|-? option overrides other options/arguments\n";
		die "$usage";
	};
};

my $TRS;	# Timespec Record Separator
my $TORS;	# Timespec Output Record "Separator"(terminator, actually)
if($null){
	$TORS=$TRS="\0";
}else{
	$TORS=$TRS="\n";
};

my $conversion=0;	# track if we did successful conversion(s)

my $orig_tz;	# our original timezone
# save our original timezone
if(defined($ENV{TZ})){
	$orig_tz=$ENV{TZ};
}else{
	$orig_tz=undef;
};

my $year=$1;	# year
my $mon=$2;		# month
my $mday=$3;	# day of the month
my $hour=$4;	# hours
my $min=$5;		# minutes
my $sec=$6;		# seconds
my $secfrac=$7;	# fractional second
my $tzhour=$8;	# timezone hours
my $tzmin=$9;	# timezone minutes

sub matchtimespec{
	if($#_!=0){
		die "$0: internal error in calling matchtimespec() - not precisely one argument?, aborting";
	};
	$_=$_[0];
	if	(
			/
				^
				(\d{4})-(\d{2})-(\d{2})
				[T\ ]
				(\d{2}):(\d{2}):(\d{2})
				(?:
					(\.\d+)
					|
					([-+]\d{2})
					(?:
						|
						:?(\d{2})
					)
				)?
				$
			/x
		)
	{
		# timespec matched, set our variables
		$year=$1;	# year
		$mon=$2;		# month
		$mday=$3;	# day of the month
		$hour=$4;	# hours
		$min=$5;		# minutes
		$sec=$6;		# seconds
		$secfrac=$7;	# fractional second
		$tzhour=$8;	# timezone hours
		$tzmin=$9;	# timezone minutes
		return(1);
	}else{
		return undef;
	};
};

my $time_t;

sub toepoch{
	# use &matchtimespec successfully (return of true)
	# "just" before calling &toepoch
	# &toepoch returns seconds since the epoch or undef on error

	if(defined($tzhour)){

		my $TZhour=$tzhour;

		#we need the sign flipped for use by $ENV{TZ}
		if($TZhour !~ s/^-/+/){
			$TZhour !~ s/^\+/-/;
		};

		# fudge us up a timezone for mktime:
		if(defined($tzmin)){
			$ENV{TZ}="XXX$TZhour:$tzmin";
		}else{
			$ENV{TZ}="XXX$TZhour";
		};

	};

	if(
		defined(
			$time_t =
			mktime(
					$sec,
					$min,
					$hour,
					$mday,
					$mon -1 ,		#zero based
					$year - 1900,	#perl 1900 year base
					-1,
					-1,
					-1
			)
		)
	){
		if(defined($tzhour)){
			# we mucked with $ENV{TZ} and tzset (via mktime), reset them.
			if(defined($orig_tz)){
				$ENV{TZ}=$orig_tz;
			}else{
				delete($ENV{TZ});
			};
			tzset();
		};
		$time_t += 0;	# squash '0 but true' to 0
		if(defined($secfrac)){
			return $time_t . $secfrac;
		}else{
			return $time_t;
		};
	}else{
		return undef;
	};
	#unreachable:
	die "$0: reached the unreachable, aborting";
};

if($#ARGV == -1){
	# no non-option arguments, process standard input
	local $/=$TRS;
	while(<>){
		chomp;
		if(&matchtimespec($_)){
			if(defined($time_t=&toepoch())){
				&printorwarnerror($time_t,"$TORS");
				$conversion=1;	# track that we did successful conversion
			}else{
				warn "$0: failed to convert timespec on: $_\n";
				$error=1;		# track that we got error(s)
			};
		}else{
			warn "$0: failed to match timespec on: $_\n";
			$error=1;		# track that we got error(s)
		};
	};
	if($conversion&&!$error){
		exit 0;
	}else{
		exit 1;
	};
}else{
	# non-option arguments, process them
	my $fileortimespec;
	while(defined($fileortimespec=shift @ARGV)){
		if(defined(&matchtimespec($fileortimespec))){
			# looks like a timespec
			if(defined($time_t=&toepoch())){
				&printorwarnerror($time_t,"$TORS");
				$conversion=1;	# track that we did successful conversion
			}else{
				warn "$0: failed to convert timespec on: $fileortimespec\n";
				$error=1;		# track that we got error(s)
			};
		}else{
			# doesn't look like a timespec, treat as file
			if(open(FILE,'<',$fileortimespec)){
				local $/=$TRS;
				while(<FILE>){
					chomp;
					if(&matchtimespec($_)){
						if(defined($time_t=&toepoch())){
							&printorwarnerror($time_t,"$TORS");
							$conversion=1;	# track that we did successful conversion
						}else{
							warn "$0: failed to convert timespec on: $_\n";
							$error=1;		# track that we got error(s)
						};
					}else{
						warn "$0: failed to match timespec on: $_\n";
						$error=1;		# track that we got error(s)
					};
				};
				if(!close(FILE)){
					warn "$0: error closing file $fileortimespec\n";
					$error=1;		# track that we got error(s)
				};
			}else{
				warn "$0: failed to open file $fileortimespec\n";
				$error=1;		# track that we got error(s)
			};
		};
	};
	if($conversion&&!$error){
		exit 0;
	}else{
		exit 1;
	};
};
