• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pulibrary / bibdata / 2d1e9eb3-fa5a-4980-87e2-ef3c95fd7c17

21 Aug 2025 01:07AM UTC coverage: 91.51% (-0.2%) from 91.671%
2d1e9eb3-fa5a-4980-87e2-ef3c95fd7c17

Pull #2880

circleci

sandbergja
Re-implement pub_created_display in rust
Pull Request #2880: Re-implement pub_created_display in rust

131 of 157 new or added lines in 5 files covered. (83.44%)

7351 of 8033 relevant lines covered (91.51%)

367.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.22
/lib/bibdata_rs/src/marc/fixed_field/dates.rs
1
// This module describes the dates from the 008
2

3
use std::{fmt::Display, str::FromStr, sync::LazyLock};
4

5
use marctk::Record;
6
use regex::Regex;
7

8
#[derive(Debug, PartialEq)]
9
pub enum DateType {
10
    NoDatesGivenBCDateInvolved,
11
    ContinuousResourceCurrentlyPublished,
12
    ContinuousResourceCeasedPublication,
13
    DetailedDate,
14
    InclusiveDatesOfCollection,
15
    RangeOfYearsOfBulkOfCollection,
16
    MultipleDates,
17
    DatesUnknown,
18
    DateOfDistributionReleaseIssueAndProductionRecordingSessionWhenDifferent,
19
    QuestionableDate,
20
    ReprintReissueDateAndOriginalDate,
21
    SingleKnownDateProbableDate,
22
    PublicationDateAndCopyrightDate,
23
    ContinuingResourceStatusUnknown,
24
    None,
25
}
26

27
impl From<char> for DateType {
28
    fn from(value: char) -> Self {
1✔
29
        match value {
1✔
NEW
30
            'b' => Self::NoDatesGivenBCDateInvolved,
×
NEW
31
            'c' => Self::ContinuousResourceCurrentlyPublished,
×
32
            'd' => Self::ContinuousResourceCeasedPublication,
1✔
NEW
33
            'e' => Self::DetailedDate,
×
NEW
34
            'i' => Self::InclusiveDatesOfCollection,
×
NEW
35
            'k' => Self::RangeOfYearsOfBulkOfCollection,
×
NEW
36
            'm' => Self::MultipleDates,
×
NEW
37
            'n' => Self::DatesUnknown,
×
NEW
38
            'p' => Self::DateOfDistributionReleaseIssueAndProductionRecordingSessionWhenDifferent,
×
NEW
39
            'r' => Self::ReprintReissueDateAndOriginalDate,
×
NEW
40
            's' => Self::SingleKnownDateProbableDate,
×
NEW
41
            't' => Self::PublicationDateAndCopyrightDate,
×
NEW
42
            'u' => Self::ContinuingResourceStatusUnknown,
×
NEW
43
            _ => Self::None,
×
44
        }
45
    }
1✔
46
}
47

48
impl From<&Record> for DateType {
49
    fn from(record: &Record) -> Self {
2✔
50
        match record
2✔
51
            .get_control_fields("008")
2✔
52
            .into_iter()
2✔
53
            .next()
2✔
54
            .and_then(|field| field.content().chars().nth(6))
2✔
55
        {
56
            Some(code) => DateType::from(code),
1✔
57
            None => DateType::None,
1✔
58
        }
59
    }
2✔
60
}
61

62
#[derive(Debug, PartialEq)]
63
pub enum Date {
64
    KnownYear(String),          // e.g. 1995
65
    PartiallyKnownYear(String), // e.g. 198u
66
    UnknownYear,                // e.g. uuuu
67
    YearNotAvailable,           // e.g. 9999
68
}
69

70
#[derive(Debug)]
71
pub struct InvalidDateString;
72

73
impl FromStr for Date {
74
    type Err = InvalidDateString;
75

76
    fn from_str(s: &str) -> Result<Self, Self::Err> {
5✔
77
        match s {
1✔
78
            "9999" => Ok(Self::YearNotAvailable),
5✔
79
            "uuuu" => Ok(Self::UnknownYear),
4✔
80
            s if is_known_year(s) => Ok(Self::KnownYear(s.to_string())),
3✔
81
            s if is_partially_known_year(s) => Ok(Self::PartiallyKnownYear(s.to_string())),
1✔
NEW
82
            _ => Err(InvalidDateString),
×
83
        }
84
    }
5✔
85
}
86

87
fn is_known_year(s: &str) -> bool {
3✔
88
    s.len() == 4 && s.chars().all(|char| char.is_numeric())
12✔
89
}
3✔
90

91
fn is_partially_known_year(s: &str) -> bool {
1✔
92
    static PARTIALLY_KNOWN_YEAR: LazyLock<Regex> =
93
        LazyLock::new(|| Regex::new(r"^[u\d]{4}$").unwrap());
1✔
94
    PARTIALLY_KNOWN_YEAR.is_match(s)
1✔
95
}
1✔
96

97
#[derive(Debug, PartialEq)]
98
pub struct EndDate(Date);
99

100
#[derive(Debug)]
101
pub struct NoEndDate;
102

103
impl TryFrom<&Record> for EndDate {
104
    type Error = NoEndDate;
105

106
    fn try_from(record: &Record) -> Result<Self, Self::Error> {
8✔
107
        let year = record
8✔
108
            .get_control_fields("008")
8✔
109
            .into_iter()
8✔
110
            .next()
8✔
111
            .ok_or(NoEndDate)?
8✔
112
            .content()
6✔
113
            .get(11..15)
6✔
114
            .ok_or(NoEndDate)?;
6✔
115

116
        match Date::from_str(year) {
5✔
117
            Ok(date) => Ok(Self(date)),
5✔
NEW
118
            Err(_) => Err(NoEndDate),
×
119
        }
120
    }
8✔
121
}
122

123
impl Display for EndDate {
124
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1✔
125
        match &self.0 {
1✔
126
            Date::KnownYear(year) => {
1✔
127
                write!(f, "{year}")
1✔
128
            }
NEW
129
            Date::PartiallyKnownYear(year) => {
×
130
                // assume 9, since we are in EndDate and it is the latest possible
NEW
131
                write!(f, "{}", year.replace("u", "9"))
×
132
            }
NEW
133
            _ => Ok(()),
×
134
        }
135
    }
1✔
136
}
137

138
#[cfg(test)]
139
mod tests {
140
    use super::*;
141

142
    #[test]
143
    fn it_gets_the_end_date_from_a_record() {
1✔
144
        let record = Record::from_breaker("=008 940720d19761979it mr 0 a0ita d").unwrap();
1✔
145
        let end_date = EndDate::try_from(&record).unwrap();
1✔
146
        assert_eq!(end_date, EndDate(Date::KnownYear("1979".to_owned())));
1✔
147
    }
1✔
148

149
    #[test]
150
    fn it_gets_partially_unknown_end_date_from_a_record() {
1✔
151
        let record = Record::from_breaker("=008 940720d1976197uit mr 0 a0ita d").unwrap();
1✔
152
        let end_date = EndDate::try_from(&record).unwrap();
1✔
153
        assert_eq!(
1✔
154
            end_date,
155
            EndDate(Date::PartiallyKnownYear("197u".to_owned()))
1✔
156
        );
157
    }
1✔
158

159
    #[test]
160
    fn it_gets_unknown_end_date_from_a_record() {
1✔
161
        let record = Record::from_breaker("=008 940720d1976uuuuit mr 0 a0ita d").unwrap();
1✔
162
        let end_date = EndDate::try_from(&record).unwrap();
1✔
163
        assert_eq!(end_date, EndDate(Date::UnknownYear));
1✔
164
    }
1✔
165

166
    #[test]
167
    fn it_gets_end_date_year_not_available_from_a_record() {
1✔
168
        let record = Record::from_breaker("=008 940720d19769999it mr 0 a0ita d").unwrap();
1✔
169
        let end_date = EndDate::try_from(&record).unwrap();
1✔
170
        assert_eq!(end_date, EndDate(Date::YearNotAvailable));
1✔
171
    }
1✔
172

173
    #[test]
174
    fn it_does_not_get_the_end_date_from_a_record_with_no_008() {
1✔
175
        let record = Record::new();
1✔
176
        assert!(EndDate::try_from(&record).is_err());
1✔
177
    }
1✔
178

179
    #[test]
180
    fn it_does_not_get_the_end_date_from_an_incomplete_008() {
1✔
181
        let record = Record::from_breaker("=008 940720d1976197").unwrap();
1✔
182
        assert!(EndDate::try_from(&record).is_err());
1✔
183
    }
1✔
184
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc