package Date;

use strict; use warnings;

use Time::localtime;
use Time::Local;
use Data::Dumper;
use Time::JulianDay;

our @month = (undef, qw(January February March April May June July August September October November December));
our @weekday = (undef, qw(Sunday Monday Tuesday Wednesday Thursday Friday Saturday));

my $saturday_wday = 7;
my $sunday_wday   = 1;

sub new {
	my ($pkg, $time, $gmt) = @_;
	my $self = bless {
		time => $time,
		gmt => $gmt||0,
	}, $pkg;
	$self->calc;
	return $self;
}

sub dup {
	my ($ref) = @_;
	my $pkg = ref $ref;
	return $pkg->new($ref->{time}, $ref->{gmt});
}

sub today {
	my ($pkg, $gmt) = @_;
	return $pkg->new(time(), $gmt);
}

sub new_ymd {
	my ($pkg, $y, $m, $d, $gmt) = @_;
	return $pkg->new(day_start($y, $m, $d, $gmt), $gmt);
}

my $secs_in_day = 3600*24;

sub calc {
	my ($self) = @_;
	my $tm;
	my $time = $self->{time};
	if ($self->{gmt}) {
		$tm = gmtime($time);
	} else {
		$tm = localtime($time);
	}
	my $y = $tm->year + 1900;
	my $m = $tm->mon + 1;
	my $d = $tm->mday;
	my $wday = $tm->wday + 1;
	my $day_start = day_start($y, $m, $d, $self->{gmt});
	$time = $day_start + $secs_in_day / 2;
	$self->{time} = $time;
	$self->{y} = $y;
	$self->{m} = $m;
	$self->{d} = $d;
	$self->{wday} = $wday;
	$self->{weekday} = $weekday[$wday];
	$self->{wkd} = substr $weekday[$wday], 0, 3;
	$self->{month} = $month[$m];
	$self->{jday} = julian_day($y, $m, $d)
}

sub day_start {
	my ($y, $m, $d, $gmt) = @_;
	my $day_start;
	if ($gmt) {
		$day_start = timegm(0, 0, 0, $d, $m-1, $y-1900);
	} else {
		$day_start = timelocal(0, 0, 0, $d, $m-1, $y-1900);
	}
	return $day_start;
}

sub ymd {
	my ($self) = @_;
	return "$self->{y}.$self->{m}.$self->{d}";
}

sub yyyymmdd {
	my ($self) = @_;
	return sprintf("%04d%02d%02d", $self->{y}, $self->{m}, $self->{d});
}

sub ymd2 {
	my ($self) = @_;
	return sprintf("%04d.%02d.%02d", $self->{y}, $self->{m}, $self->{d});
}

sub ymdw {
	my ($self) = @_;
	return $self->ymd." ".$self->{wkd};
}

sub back_days {
	my ($self, $count) = @_;
	if ($count < 0) {
		return $self->forward_days(-$count);
	}
	while ($count > 0) {
		--$count;
		$self->{time} -= $secs_in_day;
		$self->calc;
	}
}

sub yesterday {
	my ($self, $count) = @_;
	$self->back_days(1);
}

sub forward_days {
	my ($self, $count) = @_;
	if ($count < 0) {
		return $self->back_days(-$count);
	}
	while ($count > 0) {
		--$count;
		$self->{time} += $secs_in_day;
		$self->calc;
	}
}

sub tomorrow {
	my ($self, $count) = @_;
	$self->forward_days(1);
}

sub month_day {
	my ($self) = @_;
	my $m = $self->{m};
	my $d = $self->{d};
	my $month = $month[$m];
	return $month." ".Th($d);
}

sub mon_day {
	my ($self) = @_;
	my $mon = $self->mon;
	my $d = $self->{d};
	return "$mon $d";
}

sub mon {
	my ($self) = @_;
	my $m = $self->{m};
	my $month = $month[$m];
	return substr($month, 0, 3);
}

sub wkd_month_day {
	my ($self) = @_;
	return $self->{wkd}." ".$self->month_day;
}

sub weekday_month_day {
	my ($self) = @_;
	return $self->{weekday}." ".$self->month_day;
}

sub jday {
	my ($self) = @_;
	return $self->{jday};
}

sub wmd {
	my ($self) = @_;
	return $self->weekday_month_day();
}

sub wmd_short {
	my ($self) = @_;
	return ($self->{wkd})." ".($self->{d})."/".($self->{m});
}

sub Th {
	my ($n) = @_;
	return $n._th($n);
}

sub _th {
	my ($n) = @_;
	if ($n =~ /1[123]$/) { return "th"; }
	if ($n =~ /1$/) { return "st"; }
	if ($n =~ /2$/) { return "nd"; }
	if ($n =~ /3$/) { return "rd"; }
	return "th";
}

sub is_weekend {
	my ($date) = @_;
	return $date->{wday} == $saturday_wday || $date->{wday} == $sunday_wday;
}

1
