Move 'next' calcuations into MFHD::Caption, since they depend primarily on the captio...
authordjfiander <djfiander@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Sun, 5 Apr 2009 00:57:26 +0000 (00:57 +0000)
committerdjfiander <djfiander@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Sun, 5 Apr 2009 00:57:26 +0000 (00:57 +0000)
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

Open-ILS/src/perlmods/OpenILS/Utils/MFHD/Caption.pm
Open-ILS/src/perlmods/OpenILS/Utils/MFHD/Holding.pm

index cf70493..a486044 100755 (executable)
@@ -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;
index d88151b..5fb1bb0 100755 (executable)
@@ -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);
 }