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

pkiraly / metadata-qa-marc / #1527

22 Aug 2025 02:21PM UTC coverage: 90.345%. Remained the same
#1527

push

pkiraly
Improve timeline handling

5191 of 6416 new or added lines in 219 files covered. (80.91%)

886 existing lines in 78 files now uncovered.

36717 of 40641 relevant lines covered (90.34%)

0.9 hits per line

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

86.11
/src/main/java/de/gwdg/metadataqa/marc/dao/Control005.java
1
package de.gwdg.metadataqa.marc.dao;
2

3
import de.gwdg.metadataqa.marc.Extractable;
4
import de.gwdg.metadataqa.marc.dao.record.BibliographicRecord;
5
import de.gwdg.metadataqa.marc.definition.MarcVersion;
6
import de.gwdg.metadataqa.marc.definition.tags.control.Control005Definition;
7
import de.gwdg.metadataqa.marc.model.validation.ValidationError;
8
import de.gwdg.metadataqa.marc.model.validation.ValidationErrorType;
9
import org.apache.commons.lang3.StringUtils;
10

11
import java.util.ArrayList;
12
import java.util.logging.Level;
13
import java.util.logging.Logger;
14
import java.util.regex.Matcher;
15
import java.util.regex.Pattern;
16

17
/**
18
 *
19
 * @author Péter Király <peter.kiraly at gwdg.de>
20
 */
21
public class Control005  extends SimpleControlField implements Extractable {
22
  private static final Logger logger = Logger.getLogger(Control005.class.getCanonicalName());
1✔
23
  private static final Pattern DATE_TIME = Pattern.compile(
1✔
24
      "^(?<year>\\d{4})(?<month>\\d{2})(?<day>\\d{2})(?<hour>\\d{2})" +
25
      "(?<minute>\\d{2})(?<second>\\d{2})\\.(?<decisecond>\\d)$");
26

27
  // TODO discuss why this contains two spaces at the end. I guess it's because of the remaining tenth of a second
28
  private static final Pattern DATE_ONLY = Pattern.compile("^(?<year>\\d{4})(?<month>\\d{2})(?<day>\\d{2})(?<hour>[\\d ]{2})(?<minute>[\\d ]{2})(?<second>[\\d ]{2}) {2}$");
1✔
29
  private Integer year;
30
  private Integer month;
31
  private Integer day;
32
  private Integer hour;
33
  private Integer min;
34
  private Integer sec;
35
  private Integer ms;
36

37
  public Control005(String content) {
38
    super(Control005Definition.getInstance(), content);
1✔
39
    processContent();
1✔
40
  }
1✔
41

42
  public Control005(String content, BibliographicRecord marcRecord) {
43
    super(Control005Definition.getInstance(), content);
1✔
44
    this.marcRecord = marcRecord;
1✔
45
    processContent();
1✔
46
  }
1✔
47

48
  protected void processContent() {
49
    // do nothing, this string should not be parsed
50
    Matcher matcher = DATE_TIME.matcher(content);
1✔
51
    if (matcher.matches()) {
1✔
52
      parseDateAndTime(matcher);
1✔
53
      return;
1✔
54
    }
55
    initializationErrors.add(createError("The field value does not match the expected pattern"));
1✔
56

57
    // TODO: handle values such as '20131127        '. Not sure if this TODO has been addressed, as it seems that the code below actually handles this case
58
    // I'm also not sure why there's an initialization error before this, and I'm not sure why this hasn't been used
59
    // for the previous case, too
60
    matcher = DATE_ONLY.matcher(content);
1✔
61
    if (matcher.matches()) {
1✔
NEW
62
      parseDateAndOptionalTime(matcher);
×
NEW
63
      return;
×
64
    }
65

66
    // The following removes all (at most 10) dots and spaces from the end of the string
67
    // and also removes all dots from within the string
68
    String cleanContent = content.replaceAll("[. ]{1,10}$", "").replace(".", "");
1✔
69

70
    // This has the potential for the following bug:
71
    // In case the cleanContent is just '20' and all spaces behind, it will try to extract the month 20,
72
    // which doesn't seem like the desired behaviour
73
    if (cleanContent.length() >= 4) {
1✔
74
      year = extractRaw(cleanContent, 4, "year");
1✔
75
      cleanContent = cleanContent.substring(4);
1✔
76
    }
77
    if (cleanContent.length() >= 2) {
1✔
78
      month = extractRaw(cleanContent, 2, "month");
1✔
79
      cleanContent = cleanContent.substring(2);
1✔
80
    }
81
    if (cleanContent.length() >= 2) {
1✔
82
      day = extractRaw(cleanContent, 2, "day");
1✔
83
      cleanContent = cleanContent.substring(2);
1✔
84
    }
85
    if (cleanContent.length() >= 2) {
1✔
86
      hour = extractRaw(cleanContent, 2, "hour");
1✔
87
      cleanContent = cleanContent.substring(2);
1✔
88
    }
89
    if (cleanContent.length() >= 2) {
1✔
90
      min = extractRaw(cleanContent, 2, "min");
1✔
91
      cleanContent = cleanContent.substring(2);
1✔
92
    }
93
    if (cleanContent.length() >= 2) {
1✔
94
      sec = extractRaw(cleanContent, 2, "sec");
1✔
95
    }
96
  }
1✔
97

98
  private void parseDateAndTime(Matcher matcher) {
99
    year = Integer.parseInt(matcher.group("year"));
1✔
100
    month = Integer.parseInt(matcher.group("month"));
1✔
101
    day = Integer.parseInt(matcher.group("day"));
1✔
102
    hour = Integer.parseInt(matcher.group("hour"));
1✔
103
    min = Integer.parseInt(matcher.group("minute"));
1✔
104
    sec = Integer.parseInt(matcher.group("second"));
1✔
105
    ms = Integer.parseInt(matcher.group("decisecond"));
1✔
106
  }
1✔
107

108
  private void parseDateAndOptionalTime(Matcher matcher) {
NEW
109
    year = Integer.parseInt(matcher.group("year"));
×
NEW
110
    month = Integer.parseInt(matcher.group("month"));
×
NEW
111
    day = Integer.parseInt(matcher.group("day"));
×
112

NEW
113
    String stringHour = matcher.group("hour");
×
NEW
114
    if (StringUtils.isNumeric(stringHour)) {
×
NEW
115
      hour = Integer.parseInt(stringHour);
×
116
    }
117

NEW
118
    String stringMinute = matcher.group("minute");
×
NEW
119
    if (StringUtils.isNumeric(stringMinute)) {
×
NEW
120
      min = Integer.parseInt(stringMinute);
×
121
    }
122

NEW
123
    String stringSecond = matcher.group("second");
×
NEW
124
    if (StringUtils.isNumeric(stringSecond)) {
×
NEW
125
      sec = Integer.parseInt(stringSecond);
×
126
    }
UNCOV
127
  }
×
128

129
  /**
130
   * Extracts an integer from the beginning of the string until the given end position.
131
   * @param cleanContent The string to extract the integer from
132
   * @param end The end position of the extracted integer
133
   * @param field The name of the field to be used in the error message
134
   * @return The extracted integer
135
   */
136
  private Integer extractRaw(String cleanContent, int end, String field) {
137
    String text = cleanContent.substring(0, end);
1✔
138
    Integer data = null;
1✔
139
    try {
140
      data = Integer.parseInt(text);
1✔
141
    } catch (NumberFormatException e) {
1✔
142
      String id = marcRecord != null ? String.format("#%s) ", marcRecord.getId()) : "";
1✔
143
      logger.log(Level.SEVERE, "{0}Bad input for {1}: {2}", new Object[]{id, field, text});
1✔
144
      initializationErrors.add(createError(String.format("invalid %s: %s", field, text)));
1✔
145
    }
1✔
146
    return data;
1✔
147
  }
148

149
  @Override
150
  public boolean validate(MarcVersion marcVersion) {
151
    validationErrors = new ArrayList<>();
1✔
152
    if (!initializationErrors.isEmpty())
1✔
153
      validationErrors.addAll(initializationErrors);
1✔
154
    return isValidMonth() && isValidDay()
1✔
155
      && isValidHour() && isValidMin() && isValidSec();
1✔
156
  }
157

158
  private boolean isValidMonth() {
159
    boolean valid = month != null && month >= 1 && month <= 12;
1✔
160
    if (!valid)
1✔
161
      addError(String.format("invalid month: %d", month));
1✔
162

163
    return valid;
1✔
164
  }
165

166
  private boolean isValidDay() {
167
    boolean valid = day != null && day >= 1 && day <= 31;
1✔
168
    if (!valid)
1✔
169
      addError(String.format("invalid day: %d", day));
1✔
170

171
    return valid;
1✔
172
  }
173

174
  private boolean isValidHour() {
175
    boolean valid = hour != null && hour >= 0 && hour <= 23;
1✔
176
    if (!valid)
1✔
177
      addError(String.format("invalid hour: %d", hour));
1✔
178

179
    return valid;
1✔
180
  }
181

182
  private boolean isValidMin() {
183
    boolean valid = min != null && min >= 0 && min <= 59;
1✔
184
    if (!valid)
1✔
185
      addError(String.format("invalid minute: %d", min));
1✔
186

187
    return valid;
1✔
188
  }
189

190
  private boolean isValidSec() {
191
    boolean valid = sec != null && sec >= 0 && sec <= 59;
1✔
192
    if (!valid)
1✔
193
      addError(String.format("invalid second: %d", sec));
1✔
194

195
    return valid;
1✔
196
  }
197

198
  public Integer getYear() {
199
    return year;
1✔
200
  }
201

202
  public Integer getMonth() {
203
    return month;
1✔
204
  }
205

206
  public Integer getDay() {
207
    return day;
1✔
208
  }
209

210
  public Integer getHour() {
211
    return hour;
1✔
212
  }
213

214
  public Integer getMin() {
215
    return min;
1✔
216
  }
217

218
  public Integer getSec() {
219
    return sec;
1✔
220
  }
221

222
  public Integer getMs() {
223
    return ms;
1✔
224
  }
225

226
  private void addError(String msg) {
227
    validationErrors.add(createError(msg));
1✔
228
  }
1✔
229

230
  private ValidationError createError(String msg) {
231
    String id = marcRecord != null ? marcRecord.getId() : null;
1✔
232
    return new ValidationError(
1✔
233
      id,
234
      definition.getTag(),
1✔
235
      ValidationErrorType.CONTROL_POSITION_INVALID_VALUE,
236
      String.format("%s in '%s'", msg, content),
1✔
237
      definition.getDescriptionUrl()
1✔
238
    );
239
  }
240

241
  @Override
242
  public String toString() {
243
    return "Control005{" +
1✔
244
      "content='" + content + '\'' +
245
      '}';
246
  }
247
}
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

© 2026 Coveralls, Inc