package HNS::PIM::Schedule;
# $Id: Schedule.pm,v 1.21.2.1 2001/09/27 03:18:57 kenji Exp $
################################################################
=head1 NAME
HNS::PIM::Schedule - スケジュールクラス
=head1 SYNOPSIS
my $sch = new HNS::PIM::Schedule(dir=>'diary');
$sch->Read;
print $sch->AsHTML;
=cut
################################################################
use strict;
#require 'jcode.pl';
use ObjectTemplate;
use CodeConv;
@HNS::PIM::Schedule::ISA = qw(ObjectTemplate);
use HNS;
use HNS::System;
use HNS::Template;
attributes qw(contents error);
################################################################
use vars qw($MaxNum $ContentTemplate $PastContentTemplate $TodayContentTemplate $BeginTemplate $EndTemplate @WeekString @ABCString $Unfixed $Range $RRange $GraceDays);
$MaxNum = 8; # max number of showing
@WeekString = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat');
@ABCString = ('Early', 'Middle', 'Later');
$Unfixed = "??";
$Range = 1;
$RRange = $Range;
$GraceDays = 0;
# template
$ContentTemplate = qq(
%month/%day%week %content\n);
$TodayContentTemplate = '';
$PastContentTemplate = qq(%month/%day%week %content\n);
$BeginTemplate = "";
sub initialize($)
{
my $self = shift;
$self->contents([]);
}
################################################################
=head2 $t->Read;
データファイルを読み込む
=cut
sub Read ($)
{
my $self = shift;
use DateTime::Date;
my $date = new DateTime::Date;
$date->year($HNS::Status->start_time->year);
$date->month($HNS::Status->start_time->month);
$date->day($HNS::Status->start_time->day);
my %abc = (A => 10.5, B => 20.5, C => 31.5,
a => 10.5, b => 20.5, c => 31.5);
my %dowNum = ('sun', 0, 'mon', 1, 'tue', 2, 'wed', 3,
'thu', 4, 'fri', 5, 'sat', 6,
'su', 0, 'mo', 1, 'tu', 2, 'we', 3,
'th', 4, 'fr', 5, 'sa', 6,
'日', 0, '月', 1, '火', 2, '水', 3,
'木', 4, '金', 5, '土', 6);
# collect reading file
my @files;
my $readMonths = $Range;
if ($GraceDays > 0) {
$date-='1M';
$readMonths++;
}
for (0 .. $readMonths) {
push(@files, $self->get_files($date));
$date+='1M';
}
# print "content-type: text/html\n\n";
# read yotei files
for my $file (@files){
# read file
open(F, $file) || next;
# print "$file, ";
# set default year, month
my ($y, $m) = $file =~ /y(\d{4})(\d{2})$/;
# read content
while (){
next if /^$/;
CodeConv::toeuc(*_);
my ($num, $content) = /^(\S+)\s+(.*)$/;
my $date = new DateTime::Date(year=>$y, month=>$m);
if ($num =~ m!^(\d+)/(\d+)$!){ # 03/31 content..
$date->month($1), $date->day($2);
} elsif ($num =~ m!^(\d+)$!){ # 31 content..
$date->day($num);
} elsif ($num =~ m!^(\d+)/([ABCabc])$!) { # 03/A content ...
$date->month($1);
$date->day($abc{$2});
} elsif ($num =~ m!^[ABCabc]$!) { # A content ...
$date->day($abc{$num});
} else {
$date->day(32);
}
push(@{$self->contents}, {date=>$date, content=>$content});
}
close F;
}
closedir(DIR);
# add repeat plan
my $filename = "$HNS::System::DiaryDir/repeat";
my @repeats;
open(F, $filename);
while(){
next if /^\s*#/;
next if /^\s*$/;
if (/^([^\[\s]+)(?:\[([^\]]+)\])?\s+(.*)$/) {
my @range = parse_range($2);
push(@repeats, [$1, $3, @range]); # [num content begin end]
} elsif (/^\[([^\]]+)\]\s+(.*)$/) {
my @range = parse_range($1);
push(@repeats, ['', $2, @range]); # [num content begin end]
}
}
close F;
$date->year($HNS::Status->start_time->year);
$date->month($HNS::Status->start_time->month);
$date->day($HNS::Status->start_time->day);
$readMonths = $RRange;
if ($GraceDays > 0) {
$date-='1M';
$readMonths++;
}
for (0 .. $readMonths) {
my ($y, $m) = ($date->year, $date->month);
$date+='1M';
foreach my $rplan (@repeats) {
my ($num, $content, $brng, $erng) = @$rplan;
my $tmpd = new DateTime::Date(year=>$y, month=>$m, day=>1);
my $dmonth = $tmpd->DaysMonth();
if (@$rplan == 4 && $num ne ''
&& ((sprintf("%04d%02d%02d", $y, $m, $dmonth) < $brng)
|| (sprintf("%04d%02d%02d", $y, $m, 1) > $erng))) {
next;
}
my $d;
my $m2 = $m;
if ($num =~ m!^(\d+)/(\d+)$!){ # yearly 1/31 content..
$m2 = $1;
$d = $2;
} elsif ($num =~ m!^(\d+)/([ABCabc])$!) { # yearly 1/A content ...
$m2 = $1;
$d = $abc{$2};
} elsif ($num =~ m!^(\d+)/(-?\d+)([^\d].*)$!) { # yearly 1/nth week
my $dow = $3;
$dow =~ tr/A-Z/a-z/;
if ($dowNum{$dow} eq '') {
$d = 32;
$content = "$num? $content";
} else {
$m2 = $1;
$d = get_nth_dow($y, $m2, $2, $dowNum{$dow});
}
} elsif ($num =~ m!^(\d+)$!){ # monthly 31 content..
$d = $num;
} elsif ($num =~ m!^[ABCabc]$!) { # monthly A content ...
$d = $abc{$num};
} elsif ($num =~ m!^[Ee]?(-\d+)?$! && $num ne '') { # monthly End content ...
$d = $dmonth + $1;
} elsif ($num =~ /^(-?\d+)([^\d].*)$/) { # monthly nth week
my $dow = $2;
$dow =~ tr/A-Z/a-z/;
if ($dowNum{$dow} eq '') {
$d = 32;
$content = "$num? $content";
} else {
$d = get_nth_dow($y, $m, $1, $dowNum{$dow});
}
} else {
my $dow = $num;
$dow =~ tr/A-Z/a-z/;
if ($dowNum{$dow} ne '') { # weekly
for (my $i = 1; $i <= 5; $i++) {
$d = get_nth_dow($y, $m, $i, $dowNum{$dow});
my $s = sprintf("%04d%02d%02d", $y, $m, $d);
if ($d >= 1 && $d <= $dmonth
&& (@$rplan == 2
|| ($s >= $brng && $s <= $erng))) {
my $tmpd2 = new DateTime::Date(year=>$y, month=>$m, day=>$d);
push(@{$self->contents}, {date=>$tmpd2, content=>$content});
}
}
$d = 0;
$m2 = 0;
} elsif (@$rplan == 4 && $num eq '') { # every day with range
for (my $i = 1; $i <= $dmonth; $i++) {
my $s = sprintf("%04d%02d%02d", $y, $m, $i);
if ($s >= $brng && $s <= $erng) {
my $tmpd2 = new DateTime::Date(year=>$y, month=>$m, day=>$i);
push(@{$self->contents}, {date=>$tmpd2, content=>$content});
}
}
$d = 0;
$m2 = 0;
} else {
$content = "$num? $content";
$d = 32;
}
}
if ($m == $m2 && 1 <= $d && $d <= $dmonth) {
my $s = sprintf("%04d%02d%02d", $y, $m, $d);
if ((@$rplan == 2) || ($s >= $brng) && ($s <= $erng)) {
$tmpd->day($d);
push(@{$self->contents}, {date=>$tmpd, content=>$content});
}
}
}
}
}
=head2 $t->AsHTML;
HTML変換して返す
=cut
sub AsHTML($)
{
my $self = shift;
my $templ = new HNS::Template;
my $cnt;
my $html;
my $day;
my $week;
my $now_date = $HNS::Status->start_time;
my $limit_date = new DateTime::Date(year=>$now_date->year,
month=>$now_date->month,
day=>$now_date->day);
$limit_date -= "${GraceDays}D";
$html .= $BeginTemplate;
my $until_date = new DateTime::Date(year=>$now_date->year,
month=>$now_date->month,
day=>$now_date->day);
$until_date += $Range . 'M';
# error check
if ($self->error){
$html .= $self->error;
}
for (sort { $a->{date} <=> $b->{date} ||
$a->{content} cmp $b->{content} } @{$self->contents}){
next unless $limit_date <= $_->{date} && # now and future
$until_date > $_->{date}; # until next month
last if ++$cnt > $MaxNum; # less than equal $MaxNum
$week = "";
if ($_->{date}->day == 32) { # Unfixed
$day = $Unfixed;
} elsif ($_->{date}->day == 10.5 ) { # early
$day = $ABCString[0];
} elsif ($_->{date}->day == 20.5 ) { # middle
$day = $ABCString[1];
} elsif ($_->{date}->day == 31.5 ) { # late
$day = $ABCString[2];
} else {
$day = sprintf("%02d", $_->{date}->day);
$week = '(' . $WeekString[$_->{date}->week] . ')';
}
my $template;
if ($now_date > $_->{date}) {
$template = $PastContentTemplate
} elsif ($now_date == $_->{date}) {
$template = $TodayContentTemplate || $ContentTemplate;
} else {
$template = $ContentTemplate
}
$html .= $templ->Expand($template,
{year=>sprintf("%04d", $_->{date}->year),
month=>sprintf("%02d", $_->{date}->month),
day=>$day,
week=>$week,
content=>$_->{content}});
}
$html .= $EndTemplate;
}
################################################################
# private:
sub get_files($$)
{
my ($self, $date) = @_;
my @files;
my $file_flat = sprintf("%s/y%04d%02d",
$HNS::System::DiaryDir,
$date->year, $date->month);
my $file_year = sprintf("%s/%04d/y%04d%02d",
$HNS::System::DiaryDir,
$date->year, $date->year, $date->month);
if (-f $file_flat){
if (-f $file_year){
$self->set_error("Duplicate: $file_flat, $file_year");
# die "Dupricate: $file_flat, $file_year";
}
push(@files, $file_flat);
} elsif (-f $file_year){
push(@files, $file_year);
}
return @files;
}
sub set_error($$){
my ($self, $msg) = @_;
$self->error($msg);
}
#指定年月の第$n$dow曜日($n<0なら月末から数える, $n=-1なら最終)
sub get_nth_dow($$$$) {
my ($y, $m, $n, $dow) = @_;
my $dtmp = new DateTime::Date(year=>$y, month=>$m, day=>1);
my $days = $dtmp->DaysMonth();
my $d;
if ($n > 0) {
# $dtmp->day(1);
my $first = $dtmp->week();
if ($dow >= $first) {
$d = ($n-1)*7 + $dow - $first + 1;
} else {
$d = $n*7 + $dow - $first + 1;
}
} elsif($n < 0) {
$dtmp->day($days);
my $last = $dtmp->week();
if ($dow <= $last) {
$d = $days - ($last - $dow) + ($n+1)*7;
} else {
$d = $days - ($last - $dow) + $n*7;
}
}
if ($d < 1 || $d > $days) {
return 0;
}
return $d;
}
# repeatの範囲指定をparseする
# 戻り値: () : 範囲指定なし
# ('yyyymmdd', 'yyyymmdd') : 範囲の最初と最後の日
sub parse_range($) {
my ($spec) = @_;
my @ba = (0, 1, 1);
my @ea = (9999, 12, 31);
return () if ($spec eq '');
if ($spec =~ /^([^-]*)-(.*)$/) {
my ($b, $e) = ($1, $2);
if ($b eq '') {
# @ba = (0, 1, 1);
} elsif ($b =~ /^\d{4}$/) {
@ba = ($b, 1, 1);
} elsif ($b =~ m!^(\d{4})/(\d+)$!) {
@ba = ($1, $2, 1);
} elsif ($b =~ m!^(\d{4})/(\d+)/(\d+)$!) {
@ba = ($1, $2, $3);
}
if ($e eq '') {
# @ea = (9999, 12, 31);
} elsif ($e =~ /^\d{4}$/) {
@ea = ($e, 12, 31);
} elsif ($e =~ m!^(\d{4})/(\d+)$!) {
# my $tmpd = new DateTime::Date(year=>$y, month=>$m, day=>1);
# my $dmonth = $tmpd->DaysMonth();
# @ea = ($1, $2, $dmonth);
@ea = ($1, $2, 31);
} elsif ($e =~ m!^(\d{4})/(\d+)/(\d+)$!) {
@ea = ($1, $2, $3);
}
} else {
# illegal format
}
return (sprintf("%04d%02d%02d", @ba), sprintf("%04d%02d%02d", @ea));
}
1;