#!/usr/bin/perl
#
# zzzdatediff -- calculate difference between a (e.g. birth) date and now
# Copyright (C) 2004 Fabian "zzznowman" Pietsch
#
# 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 2 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.
#
# Version 0.1 (2004-04-24)
#

use strict;
use integer;
use Time::Local;
use vars qw($SEC_DAYS $SEC_HOURS $SEC_MINUTES);

# some time duration constants
*SEC_DAYS    = \( 24 * 60 * 60 );
*SEC_HOURS   = \(      60 * 60 );
*SEC_MINUTES = \(           60 );


# splitsecs() -- convert seconds to more human-readable time units
sub splitsecs($) {
	my ($secs, $ret, $rest, $tmp) = (shift(), '');
	$rest = $secs;

	# negative amount of seconds?
	if ($rest < 0) {
		# output sign only once, not each and for every subunit
		$ret .= '-';
		$rest *= -1;
	}

	# split into human-readable time units which are as big as possible
	if (($rest != abs($secs)) || ($rest / $SEC_DAYS)) {
		$ret .= sprintf(" %dd", $rest / $SEC_DAYS);
		$rest %= $SEC_DAYS;
	}
	if (($rest != abs($secs)) || ($rest / $SEC_HOURS)) {
		$ret .= sprintf(" %dh", $rest / $SEC_HOURS);
		$rest %= $SEC_HOURS;
	}
	if (($rest != abs($secs)) || ($rest / $SEC_MINUTES)) {
		$ret .= sprintf(" %dm", $rest / $SEC_MINUTES);
		$rest %= $SEC_MINUTES;
	}

	# return string composed so far plus the remaining seconds
	return $ret . sprintf(" %ds", $rest);
}

# conv_datehash_ctime() -- convert a hash containing a date to UNIX ctime
sub conv_datehash_ctime(%) {
	my %dh = %{shift()};

	return timelocal($dh{second}, $dh{minute}, $dh{hour},
                         $dh{day}, $dh{month} - 1,
                         $dh{year} > 200 ? $dh{year} - 1900 :
                           ($dh{year} >= 70 ? $dh{year} : $dh{year} + 100));
}


# assert the script is called with correct command line arguments
die("Usage: $0 DESTDATE\n",
    "DESTDATE should be given as [YY]YY-MM-DD[ HH:MM[:SS]]\n")
  if (scalar(@ARGV) != 1);

# declare variables; first command line argument goes into $dest_str
my ($now_ctime, $dest_str) = (time(), shift());
my ($dest_ctime, %dest, $next_ctime, %next);

# try to interpret user-supplied date string (should be [YY]YY-MM-DD[ HH:MM[:SS]])
die("Can't interpret date, died") unless ($dest_str =~
  /^([0-9]{2,4})-([0-9]{1,2})-([0-9]{1,2})(?: ([0-9]{1,2}):([0-9]{1,2})(?::([0-9]{1,2}))?)?$/);

($dest{year}, $dest{month}, $dest{day}, $dest{hour}, $dest{minute}, $dest{second})
  = ($1, $2, $3, $4, $5, $6);

# convert date hash to UNIX ctime
$dest_ctime = conv_datehash_ctime(\%dest);

# output destination, now & difference
printf("Destination:  %s\n", scalar(localtime($dest_ctime)));
printf("Now:          %s\n", scalar(localtime($now_ctime)));
printf("Now to dest.: %s\n", splitsecs($dest_ctime - $now_ctime));

# exit if destination is still in the future
exit unless ($dest_ctime - $now_ctime <= 0);

# compute next anniversary; start from original destination
%next = %dest;

# try the current year
$next{year} = 1900 + (localtime($now_ctime))[5];
$next_ctime = conv_datehash_ctime(\%next);

# is current year's anniversary already in the past?
if ($next_ctime - $now_ctime <= 0) {
	# try the next year
	$next{year}++;
	$next_ctime = conv_datehash_ctime(\%next);
}

# output difference between now & next anniversary
printf("Next anniversary in: %s\n", splitsecs($next_ctime - $now_ctime));

