#!/usr/bin/perl # Copyright (C) 2006 Eric L. Wilhelm use warnings; use strict; =head1 NAME day - print a formatted date =head1 Synopsis Some of these may work. day today day tomorrow day yesterday day next th day next thursday day last wednesday day last week day this week day last month day last mo-fr day next first tu day next last th day 2 weeks away day 2 weeks from tomorrow =cut package bin::day; use warnings; use strict; use Carp; use Date::Piece qw(today date days weeks months); use File::Basename (); use constant {I => __PACKAGE__}; my $now; my %links = ( map({$_ => $_} qw(today yesterday tomorrow)), ); sub main { my (@args) = @_; # XXX do some option parsing for format and stuff my $name = File::Basename::basename($0); if(my $link = $links{$name}) { unshift(@args, $link); } print_date(@args); } ######################################################################## ######################################################################## =head2 print_date print_date(@stuff); =cut sub print_date { print join("\n", string_dates(@_)), "\n"; } # end subroutine print_date definition ######################################################################## =head2 string_dates string_dates(@stuff); =cut sub string_dates { my (@args) = @_; my $spec; if($args[0] eq '-s') { $spec = shift(@args); } $now = today; my @dates = make_date($now, @args); if($spec and $#dates) { @dates = ($dates[0], 'thru', $dates[-1]); } return(@dates); } # end subroutine string_dates definition ######################################################################## =head2 make_date $date = make_date(@stuff); =cut sub make_date { my ($date, @args) = @_; @args or return($date); # today or we're done my %dispatch = ( 'today' => sub {$date}, 'tomorrow' => sub {$date + 1*days}, 'yesterday' => sub {$date - 1*days}, 'last' => sub {next_last('last', @_)}, 'this' => sub {next_last('this', @_)}, 'next' => sub {next_last('next', @_)}, 'from' => sub {shift(@_); from($date, @_)}, ); my $cmd = shift(@args); if($cmd =~ m/^\d+$/) { my $lookup = join("_", @args); if(my $sub = I->can($lookup)) { return($sub->($cmd)); } else { die "don't know what to do with '$cmd @args'"; } } if($dispatch{$cmd}) { return($dispatch{$cmd}->($date, @args)); } die "the rest is unfinished" } # end subroutine make_date definition ######################################################################## =head2 days_ago my $date = days_ago($number); =cut sub days_ago { my ($number) = @_; return($now - $number*days); } # end subroutine days_ago definition ######################################################################## =head2 next_last Will eventually do more. my @list = next_last('next|last', $date_obj, 'week'); =cut sub next_last { my ($dir, $date, @args) = @_; my $cmd = shift(@args); my %dirmap = ('this' => 0, 'next' => 1, 'last' => -1); my %dispatch = ( iso_week => sub { my ($date, $count) = @_; $count ||= 1; $date += $dirmap{$dir}*$count*weeks; my $diff = 1 - $date->iso_dow; $date += $diff*days; my @days = map({$date+$_*days} 0..6); @days = grep({$_ <= $now} @days) if ($dir eq 'this'); return(@days); }, 'week' => sub { my ($date, $count) = @_; $count ||= 1; $date += $dirmap{$dir}*$count*weeks; # XXX hack: rewind to monday my $diff = 1 - $date->day_of_week; $date += $diff*days; my @days = map({$date+$_*days} 0..6); @days = grep({$_ <= $now} @days) if ($dir eq 'this'); return(@days); }, 'work' => sub { my ($date, $count) = @_; $count ||= 1; $date += $dirmap{$dir}*$count*weeks; # rewind to base my $base = -2; my $dow = $date->day_of_week; my $diff = 1 + $base - $dow; #warn "diff: $diff"; $diff += 7 unless($diff > -7); $date+= $diff*days; my @days = map({$date+$_*days} 0..6); @days = grep({$_ <= $now} @days) if ($dir eq 'this'); return(@days); }, month => sub { my ($date, $count) = @_; $count ||= 1; my $start = $date + $dirmap{$dir}*$count*months; $start = $start->start_of_month; my $end = $start->end_of_month; return($start->thru($end)); }, ); my @dow = qw(mo tu we th fr sa su); if($dispatch{$cmd}) { return($dispatch{$cmd}->($date, @args)); } elsif(my ($dow) = grep({$cmd =~ m/^$dow[$_-1]/i} 1..@dow)) { # last tuesday my $ndow = $now->day_of_week; # NOTE next is broken and count doesn't work here if($dir eq 'this') { # XXX how do we define 'this monday'? It depends on the boundary. # ISO or not? } else { #$date += 1*weeks if($dir eq 'next'); $dow += 7 * $dirmap{$dir} if($ndow <= $dow); return($date - ($ndow - $dow)*days); } } die "the rest is unfinished" } # end subroutine next_last definition ######################################################################## =head2 from Returns all of the days (inclusive) between $day1 and $day2 (or $now) if $day2 is omitted. from($now, $day1, to => $day2); =cut sub from { my ($now, $from, @opt) = @_; my %args; if(@opt) { unless(@opt % 2) { %args = @opt; } else { (@opt == 1) or croak("odd number of arguments"); $args{to} = shift(@opt); } } else { $args{to} = $now; } return sort(date($from)->thru($args{to})); } # end subroutine from definition ######################################################################## package main; if($0 eq __FILE__) { bin::day::main(@ARGV); } # vi:ts=2:sw=2:et:sta my $package = 'bin::day';