From c8cb6af82120bb18ec36ef1c7eaf2e1258cf07ba Mon Sep 17 00:00:00 2001 From: djfiander Date: Sun, 5 Apr 2009 00:57:26 +0000 Subject: [PATCH] Move 'next' calcuations into MFHD::Caption, since they depend primarily on the caption and pattern fields. Leave Holding::next() where it is, so a holding statement can report on the 'next' date. git-svn-id: svn://svn.open-ils.org/ILS/trunk@12793 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- .../src/perlmods/OpenILS/Utils/MFHD/Caption.pm | 290 ++++++++++++++++++++ .../src/perlmods/OpenILS/Utils/MFHD/Holding.pm | 297 +-------------------- 2 files changed, 293 insertions(+), 294 deletions(-) diff --git a/Open-ILS/src/perlmods/OpenILS/Utils/MFHD/Caption.pm b/Open-ILS/src/perlmods/OpenILS/Utils/MFHD/Caption.pm index cf70493865..a4860444fc 100755 --- a/Open-ILS/src/perlmods/OpenILS/Utils/MFHD/Caption.pm +++ b/Open-ILS/src/perlmods/OpenILS/Utils/MFHD/Caption.pm @@ -434,4 +434,294 @@ sub enum_is_combined { return 0; } + + +my %increments = ( + a => {years => 1}, # annual + b => {months => 2}, # bimonthly + c => {days => 3}, # semiweekly + d => {days => 1}, # daily + e => {weeks => 2}, # biweekly + f => {months => 6}, # semiannual + g => {years => 2}, # biennial + h => {years => 3}, # triennial + i => {days => 2}, # three times / week + j => {days => 10}, # three times /month + # k => continuous + m => {months => 1}, # monthly + q => {months => 3}, # quarterly + s => {days => 15}, # semimonthly + t => {months => 4}, # three times / year + w => {weeks => 1}, # weekly + # x => completely irregular +); + +sub incr_date { + my $incr = shift; + my @new = @_; + + if (scalar(@new) == 1) { + # only a year is specified. Next date is easy + $new[0] += $incr->{years} || 1; + } elsif (scalar(@new) == 2) { + # Year and month or season + if ($new[1] > 20) { + # season + $new[1] += ($incr->{months}/3) || 1; + if ($new[1] > 24) { + # carry + $new[0] += 1; + $new[1] -= 4; # 25 - 4 == 21 == Spring after Winter + } + } else { + # month + $new[1] += $incr->{months} || 1; + if ($new[1] > 12) { + # carry + $new[0] += 1; + $new[1] -= 12; + } + $new[1] = '0' . $new[1] if ($new[1] < 10); + } + } elsif (scalar(@new) == 3) { + # Year, Month, Day: now it gets complicated. + + if ($new[2] =~ /^[0-9]+$/) { + # A single number for the day of month, relatively simple + my $dt = DateTime->new(year => $new[0], + month=> $new[1], + day => $new[2]); + $dt->add(%{$incr}); + $new[0] = $dt->year; + $new[1] = $dt->month; + $new[2] = $dt->day; + } + $new[1] = '0' . $new[1] if ($new[1] < 10); + $new[2] = '0' . $new[2] if ($new[2] < 10); + } else { + warn("Don't know how to cope with @new"); + } + + return @new; +} + +# Test to see if $m1/$d1 is on or after $m2/$d2 +# if $d2 is undefined, test is based on just months +sub on_or_after { + my ($m1, $d1, $m2, $d2) = @_; + + return (($m1 > $m2) + || ($m1 == $m2 && ((!defined $d2) || ($d1 >= $d2)))); +} + +sub calendar_increment { + my $self = shift; + my $cur = shift; + my @new = @_; + my $cal_change = $self->calendar_change; + my $month; + my $day; + my $cur_before; + my $new_on_or_after; + + # A calendar change is defined, need to check if it applies + if ((scalar(@new) == 2 && $new[1] > 20) || (scalar(@new) == 1)) { + carp "Can't calculate date change for ", $self->as_string; + return; + } + + foreach my $change (@{$cal_change}) { + my $incr; + + if (length($change) == 2) { + $month = $change; + } elsif (length($change) == 4) { + ($month, $day) = unpack("a2a2", $change); + } + + if ($cur->[0] == $new[0]) { + # Same year, so a 'simple' month/day comparison will be fine + $incr = (!on_or_after($cur->[1], $cur->[2], $month, $day) + && on_or_after($new[1], $new[2], $month, $day)); + } else { + # @cur is in the year before @new. There are + # two possible cases for the calendar change date that + # indicate that it's time to change the volume: + # (1) the change date is AFTER @cur in the year, or + # (2) the change date is BEFORE @new in the year. + # + # -------|------|------X------|------| + # @cur (1) Jan 1 (2) @new + + $incr = (on_or_after($new[1], $new[2], $month, $day) + || !on_or_after($cur->[1], $cur->[2], $month, $day)); + } + return $incr if $incr; + } +} + +sub next_date { + my $self = shift; + my $next = shift; + my $carry = shift; + my @keys = @_; + my @cur; + my @new; + my $incr; + + my $reg = $self->{_mfhdc_REGULARITY}; + my $pattern = $self->{_mfhdc_PATTERN}; + my $freq = $pattern->{w}; + + foreach my $i (0..$#keys) { + $new[$i] = $cur[$i] = $next->{$keys[$i]} if exists $next->{$keys[$i]}; + } + + # If the current issue has a combined date (eg, May/June) + # get rid of the first date and base the calculation + # on the final date in the combined issue. + $new[-1] =~ s|^[^/]+/||; + + # If $frequency is not one of the standard codes defined in %increments + # then there has to be a $yp publication regularity pattern that + # lists the dates of publication. Use that that list to find the next + # date following the current one. + # XXX: the code doesn't handle this case yet. + if (!defined($freq)) { + carp "Undefined frequency in next_date!"; + } elsif (!exists $increments{$freq}) { + carp "Don't know how to deal with frequency '$freq'!"; + } else { + # + # One of the standard defined issue frequencies + # + @new = incr_date($increments{$freq}, @new); + + while ($self->is_omitted(@new)) { + @new = incr_date($increments{$freq}, @new); + } + + if ($self->is_combined(@new)) { + my @second_date = incr_date($increments{$freq}, @new); + + # I am cheating: This code assumes that only the smallest + # time increment is combined. So, no "Apr 15/May 1" allowed. + $new[-1] = $new[-1] . '/' . $second_date[-1]; + } + } + + for my $i (0..$#new) { + $next->{$keys[$i]} = $new[$i]; + } + + # Figure out if we need to adust volume number + # right now just use the $carry that was passed in. + # in long run, need to base this on ($carry or date_change) + if ($carry) { + # if $carry is set, the date doesn't matter: we're not + # going to increment the v. number twice at year-change. + $next->{a} += $carry; + } elsif (defined $self->{_mfhdc_PATTERN}->{x}) { + $next->{a} += $self->calendar_increment(\@cur, @new); + } +} + +sub next_alt_enum { + my $self = shift; + my $next = shift; + + # First handle any "alternative enumeration", since they're + # a lot simpler, and don't depend on the the calendar + foreach my $key ('h', 'g') { + next if !exists $next->{$key}; + if (!$self->capstr($key)) { + warn "Holding data exists for $key, but no caption specified"; + $next->{$key} += 1; + last; + } + + my $cap = $self->capfield($key); + if ($cap->{RESTART} && $cap->{COUNT} + && ($next->{$key} == $cap->{COUNT})) { + $next->{$key} = 1; + } else { + $next->{$key} += 1; + last; + } + } +} + +sub next_enum { + my $self = shift; + my $next = shift; + my $carry; + + # $carry keeps track of whether we need to carry into the next + # higher level of enumeration. It's not actually necessary except + # for when the loop ends: if we need to carry from $b into $a + # then $carry will be set when the loop ends. + # + # We need to keep track of this because there are two different + # reasons why we might increment the highest level of enumeration ($a) + # 1) we hit the correct number of items in $b (ie, 5th iss of quarterly) + # 2) it's the right time of the year. + # + $carry = 0; + foreach my $key (reverse('b'..'f')) { + next if !exists $next->{$key}; + + if (!$self->capstr($key)) { + # Just assume that it increments continuously and give up + warn "Holding data exists for $key, but no caption specified"; + $next->{$key} += 1; + $carry = 0; + last; + } + + # If the current issue has a combined issue number (eg, 2/3) + # get rid of the first issue number and base the calculation + # on the final issue number in the combined issue. + if ($next->{$key} =~ m|/|) { + $next->{$key} =~ s|^[^/]+/||; + } + + my $cap = $self->capfield($key); + if ($cap->{RESTART} && $cap->{COUNT} + && ($next->{$key} eq $cap->{COUNT})) { + $next->{$key} = 1; + $carry = 1; + } else { + # If I don't need to "carry" beyond here, then I just increment + # this level of the enumeration and stop looping, since the + # "next" hash has been initialized with the current values + + $next->{$key} += 1; + $carry = 0; + } + + # You can't have a combined issue that spans two volumes: no.12/1 + # is forbidden + if ($self->enum_is_combined($key, $next->{$key})) { + $next->{$key} .= '/' . ($next->{$key} + 1); + } + + last if !$carry; + } + + # The easy part is done. There are two things left to do: + # 1) Calculate the date of the next issue, if necessary + # 2) Increment the highest level of enumeration (either by date + # or because $carry is set because of the above loop + + if (!$self->subfield('i')) { + # The simple case: if there is no chronology specified + # then just check $carry and return + $next->{'a'} += $carry; + } else { + # Figure out date of next issue, then decide if we need + # to adjust top level enumeration based on that + $self->next_date($next, $carry, ('i'..'m')); + } +} + 1; diff --git a/Open-ILS/src/perlmods/OpenILS/Utils/MFHD/Holding.pm b/Open-ILS/src/perlmods/OpenILS/Utils/MFHD/Holding.pm index d88151b96b..5fb1bb009e 100755 --- a/Open-ILS/src/perlmods/OpenILS/Utils/MFHD/Holding.pm +++ b/Open-ILS/src/perlmods/OpenILS/Utils/MFHD/Holding.pm @@ -184,297 +184,6 @@ sub format { return $str; } -my %increments = ( - a => {years => 1}, # annual - b => {months => 2}, # bimonthly - c => {days => 3}, # semiweekly - d => {days => 1}, # daily - e => {weeks => 2}, # biweekly - f => {months => 6}, # semiannual - g => {years => 2}, # biennial - h => {years => 3}, # triennial - i => {days => 2}, # three times / week - j => {days => 10}, # three times /month - # k => continuous - m => {months => 1}, # monthly - q => {months => 3}, # quarterly - s => {days => 15}, # semimonthly - t => {months => 4}, # three times / year - w => {weeks => 1}, # weekly - # x => completely irregular -); - -sub incr_date { - my $incr = shift; - my @new = @_; - - if (scalar(@new) == 1) { - # only a year is specified. Next date is easy - $new[0] += $incr->{years} || 1; - } elsif (scalar(@new) == 2) { - # Year and month or season - if ($new[1] > 20) { - # season - $new[1] += ($incr->{months}/3) || 1; - if ($new[1] > 24) { - # carry - $new[0] += 1; - $new[1] -= 4; # 25 - 4 == 21 == Spring after Winter - } - } else { - # month - $new[1] += $incr->{months} || 1; - if ($new[1] > 12) { - # carry - $new[0] += 1; - $new[1] -= 12; - } - $new[1] = '0' . $new[1] if ($new[1] < 10); - } - } elsif (scalar(@new) == 3) { - # Year, Month, Day: now it gets complicated. - - if ($new[2] =~ /^[0-9]+$/) { - # A single number for the day of month, relatively simple - my $dt = DateTime->new(year => $new[0], - month=> $new[1], - day => $new[2]); - $dt->add(%{$incr}); - $new[0] = $dt->year; - $new[1] = $dt->month; - $new[2] = $dt->day; - } - $new[1] = '0' . $new[1] if ($new[1] < 10); - $new[2] = '0' . $new[2] if ($new[2] < 10); - } else { - warn("Don't know how to cope with @new"); - } - - return @new; -} - -# Test to see if $m1/$d1 is on or after $m2/$d2 -# if $d2 is undefined, test is based on just months -sub on_or_after { - my ($m1, $d1, $m2, $d2) = @_; - - return (($m1 > $m2) - || ($m1 == $m2 && ((!defined $d2) || ($d1 >= $d2)))); -} - -sub calendar_increment { - my $caption = shift; - my $cur = shift; - my @new = @_; - my $cal_change = $caption->calendar_change; - my $month; - my $day; - my $cur_before; - my $new_on_or_after; - - # A calendar change is defined, need to check if it applies - if ((scalar(@new) == 2 && $new[1] > 20) || (scalar(@new) == 1)) { - carp "Can't calculate date change for ", $caption->as_string; - return; - } - - foreach my $change (@{$cal_change}) { - my $incr; - - if (length($change) == 2) { - $month = $change; - } elsif (length($change) == 4) { - ($month, $day) = unpack("a2a2", $change); - } - - if ($cur->[0] == $new[0]) { - # Same year, so a 'simple' month/day comparison will be fine - $incr = (!on_or_after($cur->[1], $cur->[2], $month, $day) - && on_or_after($new[1], $new[2], $month, $day)); - } else { - # @cur is in the year before @new. There are - # two possible cases for the calendar change date that - # indicate that it's time to change the volume: - # (1) the change date is AFTER @cur in the year, or - # (2) the change date is BEFORE @new in the year. - # - # -------|------|------X------|------| - # @cur (1) Jan 1 (2) @new - - $incr = (on_or_after($new[1], $new[2], $month, $day) - || !on_or_after($cur->[1], $cur->[2], $month, $day)); - } - return $incr if $incr; - } -} - -sub next_date { - my $self = shift; - my $next = shift; - my $carry = shift; - my @keys = @_; - my @cur; - my @new; - my $incr; - - my $caption = $self->{_mfhdh_CAPTION}; - my $reg = $caption->{_mfhdc_REGULARITY}; - my $pattern = $caption->{_mfhdc_PATTERN}; - my $freq = $pattern->{w}; - - foreach my $i (0..$#keys) { - $new[$i] = $cur[$i] = $next->{$keys[$i]} if exists $next->{$keys[$i]}; - } - - # If the current issue has a combined date (eg, May/June) - # get rid of the first date and base the calculation - # on the final date in the combined issue. - $new[-1] =~ s|^[^/]+/||; - - # If $frequency is not one of the standard codes defined in %increments - # then there has to be a $yp publication regularity pattern that - # lists the dates of publication. Use that that list to find the next - # date following the current one. - # XXX: the code doesn't handle this case yet. - if (!defined($freq)) { - carp "Undefined frequency in next_date!"; - } elsif (!exists $increments{$freq}) { - carp "Don't know how to deal with frequency '$freq'!"; - } else { - # - # One of the standard defined issue frequencies - # - @new = incr_date($increments{$freq}, @new); - - while ($caption->is_omitted(@new)) { - @new = incr_date($increments{$freq}, @new); - } - - if ($caption->is_combined(@new)) { - my @second_date = incr_date($increments{$freq}, @new); - - # I am cheating: This code assumes that only the smallest - # time increment is combined. So, no "Apr 15/May 1" allowed. - $new[-1] = $new[-1] . '/' . $second_date[-1]; - } - } - - for my $i (0..$#new) { - $next->{$keys[$i]} = $new[$i]; - } - - # Figure out if we need to adust volume number - # right now just use the $carry that was passed in. - # in long run, need to base this on ($carry or date_change) - if ($carry) { - # if $carry is set, the date doesn't matter: we're not - # going to increment the v. number twice at year-change. - $next->{a} += $carry; - } elsif (defined $caption->calendar_change) { - $next->{a} += calendar_increment($caption, \@cur, @new); - } -} - -sub next_alt_enum { - my $self = shift; - my $next = shift; - my $caption = $self->{_mfhdh_CAPTION}; - - # First handle any "alternative enumeration", since they're - # a lot simpler, and don't depend on the the calendar - foreach my $key ('h', 'g') { - next if !exists $next->{$key}; - if (!$caption->capstr($key)) { - warn "Holding data exists for $key, but no caption specified"; - $next->{$key} += 1; - last; - } - - my $cap = $caption->capfield($key); - if ($cap->{RESTART} && $cap->{COUNT} - && ($next->{$key} == $cap->{COUNT})) { - $next->{$key} = 1; - } else { - $next->{$key} += 1; - last; - } - } -} - -sub next_enum { - my $self = shift; - my $next = shift; - my $caption = $self->{_mfhdh_CAPTION}; - my $carry; - - # $carry keeps track of whether we need to carry into the next - # higher level of enumeration. It's not actually necessary except - # for when the loop ends: if we need to carry from $b into $a - # then $carry will be set when the loop ends. - # - # We need to keep track of this because there are two different - # reasons why we might increment the highest level of enumeration ($a) - # 1) we hit the correct number of items in $b (ie, 5th iss of quarterly) - # 2) it's the right time of the year. - # - $carry = 0; - foreach my $key (reverse('b'..'f')) { - next if !exists $next->{$key}; - - if (!$caption->capstr($key)) { - # Just assume that it increments continuously and give up - warn "Holding data exists for $key, but no caption specified"; - $next->{$key} += 1; - $carry = 0; - last; - } - - # If the current issue has a combined issue number (eg, 2/3) - # get rid of the first issue number and base the calculation - # on the final issue number in the combined issue. - if ($next->{$key} =~ m|/|) { - $next->{$key} =~ s|^[^/]+/||; - } - - my $cap = $caption->capfield($key); - if ($cap->{RESTART} && $cap->{COUNT} - && ($next->{$key} eq $cap->{COUNT})) { - $next->{$key} = 1; - $carry = 1; - } else { - # If I don't need to "carry" beyond here, then I just increment - # this level of the enumeration and stop looping, since the - # "next" hash has been initialized with the current values - - $next->{$key} += 1; - $carry = 0; - } - - # You can't have a combined issue that spans two volumes: no.12/1 - # is forbidden - if ($caption->enum_is_combined($key, $next->{$key})) { - $next->{$key} .= '/' . ($next->{$key} + 1); - } - - last if !$carry; - } - - # The easy part is done. There are two things left to do: - # 1) Calculate the date of the next issue, if necessary - # 2) Increment the highest level of enumeration (either by date - # or because $carry is set because of the above loop - - if (!$caption->subfield('i')) { - # The simple case: if there is no chronology specified - # then just check $carry and return - $next->{'a'} += $carry; - } else { - # Figure out date of next issue, then decide if we need - # to adjust top level enumeration based on that - $self->next_date($next, $carry, ('i'..'m')); - } -} - # next: Given a holding statement, return a hash containing the # enumeration values for the next issues, whether we hold it or not @@ -493,7 +202,7 @@ sub next { $next->{$key} = $self->{_mfhdh_SUBFIELDS}->{$key} if exists $self->{_mfhdh_SUBFIELDS}->{$key}; } - $self->next_date($next, $carry, ('a' .. 'h')); + $caption->next_date($next, $carry, ('a' .. 'h')); return $next; } @@ -509,10 +218,10 @@ sub next { } if (exists $next->{'h'}) { - $self->next_alt_enum($next); + $caption->next_alt_enum($next); } - $self->next_enum($next); + $caption->next_enum($next); return($next); } -- 2.11.0