9 our @ISA = qw(MARC::Field);
14 my $class = ref($proto) || $proto;
16 my $last_enum = undef;
18 $self->{_mfhdc_ENUMS} = {};
19 $self->{_mfhdc_CHRONS} = {};
20 $self->{_mfhdc_PATTERN} = {};
21 $self->{_mfhdc_COPY} = undef;
22 $self->{_mfhdc_UNIT} = undef;
23 $self->{_mfhdc_COMPRESSIBLE} = 1; # until proven otherwise
25 foreach my $subfield ($self->subfields) {
26 my ($key, $val) = @$subfield;
29 } elsif ($key =~ /[a-h]/) {
30 # Enumeration Captions
31 $self->{_mfhdc_ENUMS}->{$key} = {CAPTION => $val,
39 } elsif ($key =~ /[i-m]/) {
41 $self->{_mfhdc_CHRONS}->{$key} = $val;
42 } elsif ($key eq 'u') {
43 # Bib units per next higher enumeration level
44 carp('$u specified for top-level enumeration')
45 unless defined($last_enum);
46 $self->{_mfhdc_ENUMS}->{$last_enum}->{COUNT} = $val;
47 } elsif ($key eq 'v') {
48 carp '$v specified for top-level enumeration'
49 unless defined($last_enum);
50 $self->{_mfhdc_ENUMS}->{$last_enum}->{RESTART} = ($val eq 'r');
51 } elsif ($key =~ /[npwxz]/) {
52 # Publication Pattern ('o' == type of unit, 'q'..'t' undefined)
53 $self->{_mfhdc_PATTERN}->{$key} = $val;
54 } elsif ($key eq 'y') {
55 # Publication pattern: 'y' is repeatable
56 $self->{_mfhdc_PATTERN}->{y} = [] if (!defined $self->{_mfhdc_PATTERN}->{y});
57 push @{$self->{_mfhdc_PATTERN}->{y}}, $val;
58 } elsif ($key eq 'o') {
60 $self->{_mfhdc_UNIT} = $val;
61 } elsif ($key eq 't') {
62 $self->{_mfhdc_COPY} = $val;
64 carp "Unknown caption subfield '$key'";
68 # subsequent levels of enumeration (primary and alternate)
69 # If an enumeration level doesn't document the number
70 # of "issues" per "volume", or whether numbering of issues
71 # restarts, then we can't compress.
72 foreach my $key ('b', 'c', 'd', 'e', 'f', 'h') {
73 if (exists $self->{_mfhdc_ENUMS}->{$key}) {
74 my $pattern = $self->{_mfhdc_ENUMS}->{$key};
75 if (!$pattern->{RESTART} || !$pattern->{COUNT}
76 || ($pattern->{COUNT} eq 'var')
77 || ($pattern->{COUNT} eq 'und')) {
78 $self->{_mfhdc_COMPRESSIBLE} = 0;
84 # If there's a $x subfield and a $j, then it's compressible
85 if (exists $self->{_mfhdc_PATTERN}->{x} && exists $self->{_mfhdc_CHRONS}->{'j'}) {
86 $self->{_mfhdc_COMPRESSIBLE} = 1;
89 bless ($self, $class);
91 if (exists $self->{_mfhdc_PATTERN}->{y}) {
92 $self->decode_pattern;
100 my $pattern = $self->{_mfhdc_PATTERN}->{y};
108 return $self->{_mfhdc_COMPRESSIBLE};
115 if (exists $self->{_mfhdc_CHRONS}->{$key}) {
116 return $self->{_mfhdc_CHRONS}->{$key};
126 if (exists $self->{_mfhdc_ENUMS}->{$key}) {
127 return $self->{_mfhdc_ENUMS}->{$key};
128 } elsif (exists $self->{_mfhdc_CHRONS}->{$key}) {
129 return $self->{_mfhdc_CHRONS}->{$key};
138 my $val = $self->capfield($key);
141 return $val->{CAPTION};
147 # If items are identified by chronology only, with no separate
148 # enumeration (eg, a newspaper issue), then the chronology is
149 # recorded in the enumeration subfields $a - $f. We can tell
150 # that this is the case if there are $a - $f subfields and no
151 # chronology subfields ($i-$k), and none of the $a-$f subfields
152 # have associated $u or $v subfields, but there are $w and $y
155 sub enumeration_is_chronology {
158 # There is always a '$a' subfield in well-formed fields.
159 return 0 if exists $self->{_mfhdc_CHRONS}->{i};
161 foreach my $key ('a' .. 'f') {
164 last if !exists $self->{_mfhdc_ENUMS}->{$key};
166 $enum = $self->{_mfhdc_ENUMS}->{$key};
167 return 0 if defined $enum->{COUNT} || defined $enum->{RESTART};
170 return (exists $self->{_mfhdc_PATTERN}->{w} && exists $self->{_mfhdc_PATTERN}->{y});
183 my $daypat = '(mo|tu|we|th|fr|sa|su)';
184 my $weekpat = '(99|98|97|00|01|02|03|04|05)';
186 my $monthpat = '(01|02|03|04|05|06|07|08|09|10|11|12)';
187 my $seasonpat = '(21|22|23|24)';
189 # Initialize $weeknopat to be '(01|02|03|...|51|52|53)'
191 foreach my $weekno (1..52) {
192 $weeknopat .= sprintf("%02d|", $weekno);
199 # Translate daynames into day of week for DateTime
200 # also used to check if dayname is valid.
202 if (exists $daynames{$pat}) {
204 # figure out day of week for date and compare
205 my $dt = DateTime->new(year => $date[0],
208 return ($dt->day_of_week == $daynames{$pat});
209 } elsif (length($pat) == 2) {
211 return $pat == $date[3];
212 } elsif (length($pat) == 4) {
215 $mon = substr($pat, 0, 2);
216 $day = substr($pat, 2, 2);
218 return (($mon == $date[1]) && ($day == $date[2]));
220 carp "Invalid day pattern '$pat'";
225 # Calcuate date of "n"th last "dayname" of month: second last Tuesday
226 sub last_week_of_month {
230 my $end_dt = DateTime->last_day_of_month(year => $dt->year,
231 month => $dt->month);
233 $day = $daynames{$day};
234 while ($end_dt->day_of_week != $day) {
235 $end_dt->subtract(days => 1);
238 # 99: last week of month, 98: second last, etc.
239 for (my $i = 99 - $week; $i > 0; $i--) {
240 $end_dt->subtract(weeks => 1);
254 return (($dt->month == $month)
255 && (($dt->week_of_month == $weekno)
256 || ($dt->week_of_month == last_day_of_month($dt, $weekno, 'th')->week_of_month)));
260 if ($daynames{$day} != $dt->day_of_week) {
261 # if it's the wrong day of the week, rest doesn't matter
265 if (!defined $month) {
267 return (($dt->weekday_of_month == $weekno)
268 || ($dt->weekday_of_month == last_day_of_month($dt, $weekno, $day)->weekday_of_month));
272 if ($month != $dt->month) {
273 # If it's the wrong month, then we're done
277 # It's the right day of the week
278 # It's the right month
280 if ($weekno == $dt->weekday_of_month) {
281 # If this matches, then we're counting from the beginning
282 # of the month and it matches and we're done.
286 # only case left is that the week number is counting from
287 # the end of the month: eg, second last wednesday
288 return (last_week_of_month($weekno, $day)->weekday_of_month == $dt->weekday_of_month);
294 my $dt = DateTime->new(year => $date[0],
298 if ($pat =~ m/^$weekpat$daypat$/) {
299 # WWdd: 03we = Third Wednesday
300 return check_date($dt, undef, $1, $2);
301 } elsif ($pat =~ m/^$monthpat$weekpat$daypat$/) {
302 # MMWWdd: 0599tu Last Tuesday in May XXX WRITE ME
303 return check_date($dt, $1, $2, $3);
304 } elsif ($pat =~ m/^$monthpat$weekpat$/) {
305 # MMWW: 1204: Fourth week in December XXX WRITE ME
306 return check_date($dt, $1, $2, undef);
308 carp "invalid week pattern '$pat'";
317 return ($pat eq $date[1]);
324 return ($pat eq $date[1]);
337 'm' => \&match_month,
338 's' => \&match_season,
341 sub regularity_match {
346 foreach my $regularity ($self->{_mfhdc_PATTERN}->{y}) {
347 next unless $regularity =~ m/^$pubcode/;
349 my $chroncode= substr($regularity, 1, 1);
350 my @pats = split(/,/, substr($regularity, 2));
353 foreach my $pat (@pats) {
354 if ($dispatch{$chroncode}->($pat, @date)) {
367 return $self->regularity_match('o', @date);
374 return $self->regularity_match('p', @date);
381 return $self->regularity_match('c', @date);