/src/dcmtk/dcmdata/libsrc/dcvrdt.cc
Line | Count | Source |
1 | | /* |
2 | | * |
3 | | * Copyright (C) 1994-2026, OFFIS e.V. |
4 | | * All rights reserved. See COPYRIGHT file for details. |
5 | | * |
6 | | * This software and supporting documentation were developed by |
7 | | * |
8 | | * OFFIS e.V. |
9 | | * R&D Division Health |
10 | | * Escherweg 2 |
11 | | * D-26121 Oldenburg, Germany |
12 | | * |
13 | | * |
14 | | * Module: dcmdata |
15 | | * |
16 | | * Author: Gerd Ehlers, Andreas Barth, Joerg Riesmeier |
17 | | * |
18 | | * Purpose: Implementation of class DcmDateTime |
19 | | * |
20 | | */ |
21 | | |
22 | | #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ |
23 | | |
24 | | #include "dcmtk/dcmdata/dcvrdt.h" |
25 | | #include "dcmtk/dcmdata/dcvrda.h" |
26 | | #include "dcmtk/dcmdata/dcvrtm.h" |
27 | | #include "dcmtk/dcmdata/dcmatch.h" |
28 | | #include "dcmtk/ofstd/ofstring.h" |
29 | | #include "dcmtk/ofstd/ofstd.h" |
30 | | |
31 | 0 | #define MAX_DT_LENGTH 26 |
32 | | |
33 | | |
34 | | // ******************************** |
35 | | |
36 | | |
37 | | DcmDateTime::DcmDateTime(const DcmTag &tag, |
38 | | const Uint32 len) |
39 | 0 | : DcmByteString(tag, len) |
40 | 0 | { |
41 | 0 | setMaxLength(MAX_DT_LENGTH); |
42 | 0 | setNonSignificantChars(" \\"); |
43 | 0 | } |
44 | | |
45 | | DcmDateTime::DcmDateTime(const DcmDateTime &old) |
46 | 0 | : DcmByteString(old) |
47 | 0 | { |
48 | 0 | } |
49 | | |
50 | | |
51 | | DcmDateTime::~DcmDateTime() |
52 | 0 | { |
53 | 0 | } |
54 | | |
55 | | |
56 | | DcmDateTime &DcmDateTime::operator=(const DcmDateTime &obj) |
57 | 0 | { |
58 | 0 | DcmByteString::operator=(obj); |
59 | 0 | return *this; |
60 | 0 | } |
61 | | |
62 | | |
63 | | OFCondition DcmDateTime::copyFrom(const DcmObject &rhs) |
64 | 0 | { |
65 | 0 | if (this != &rhs) |
66 | 0 | { |
67 | 0 | if (rhs.ident() != ident()) return EC_IllegalCall; |
68 | 0 | *this = OFstatic_cast(const DcmDateTime &, rhs); |
69 | 0 | } |
70 | 0 | return EC_Normal; |
71 | 0 | } |
72 | | |
73 | | |
74 | | // ******************************** |
75 | | |
76 | | |
77 | | DcmEVR DcmDateTime::ident() const |
78 | 0 | { |
79 | 0 | return EVR_DT; |
80 | 0 | } |
81 | | |
82 | | |
83 | | OFCondition DcmDateTime::checkValue(const OFString &vm, |
84 | | const OFBool /*oldFormat*/) |
85 | 0 | { |
86 | 0 | OFString strVal; |
87 | | /* get "raw value" without any modifications (if possible) */ |
88 | 0 | OFCondition l_error = getStringValue(strVal); |
89 | 0 | if (l_error.good()) |
90 | 0 | l_error = DcmDateTime::checkStringValue(strVal, vm); |
91 | 0 | return l_error; |
92 | 0 | } |
93 | | |
94 | | |
95 | | // ******************************** |
96 | | |
97 | | |
98 | | OFCondition DcmDateTime::getOFString(OFString &stringVal, |
99 | | const unsigned long pos, |
100 | | OFBool normalize) |
101 | 0 | { |
102 | 0 | OFCondition l_error = DcmByteString::getOFString(stringVal, pos, normalize); |
103 | 0 | if (l_error.good() && normalize) |
104 | 0 | normalizeString(stringVal, !MULTIPART, !DELETE_LEADING, DELETE_TRAILING); |
105 | 0 | return l_error; |
106 | 0 | } |
107 | | |
108 | | |
109 | | // ******************************** |
110 | | |
111 | | |
112 | | OFCondition DcmDateTime::getOFDateTime(OFDateTime &dateTimeValue, |
113 | | const unsigned long pos) |
114 | 0 | { |
115 | 0 | OFString dicomDateTime; |
116 | | /* convert the current element value to OFDateTime format */ |
117 | 0 | OFCondition l_error = getOFString(dicomDateTime, pos); |
118 | 0 | if (l_error.good()) |
119 | 0 | l_error = getOFDateTimeFromString(dicomDateTime, dateTimeValue); |
120 | 0 | else |
121 | 0 | dateTimeValue.clear(); |
122 | 0 | return l_error; |
123 | 0 | } |
124 | | |
125 | | |
126 | | OFCondition DcmDateTime::getISOFormattedDateTime(OFString &formattedDateTime, |
127 | | const unsigned long pos, |
128 | | const OFBool seconds, |
129 | | const OFBool fraction, |
130 | | const OFBool timeZone, |
131 | | const OFBool createMissingPart, |
132 | | const OFString &dateTimeSeparator, |
133 | | const OFString &timeZoneSeparator) |
134 | 0 | { |
135 | 0 | OFString dicomDateTime; |
136 | | /* get current element value and convert to ISO formatted date/time */ |
137 | 0 | OFCondition l_error = getOFString(dicomDateTime, pos); |
138 | 0 | if (l_error.good()) |
139 | 0 | { |
140 | 0 | l_error = getISOFormattedDateTimeFromString(dicomDateTime, formattedDateTime, seconds, fraction, |
141 | 0 | timeZone, createMissingPart, dateTimeSeparator, timeZoneSeparator); |
142 | 0 | } else |
143 | 0 | formattedDateTime.clear(); |
144 | 0 | return l_error; |
145 | 0 | } |
146 | | |
147 | | |
148 | | OFCondition DcmDateTime::setCurrentDateTime(const OFBool seconds, |
149 | | const OFBool fraction, |
150 | | const OFBool timeZone) |
151 | 0 | { |
152 | 0 | OFString dicomDateTime; |
153 | 0 | OFCondition l_error = getCurrentDateTime(dicomDateTime, seconds, fraction, timeZone); |
154 | 0 | if (l_error.good()) |
155 | 0 | l_error = putOFStringArray(dicomDateTime); |
156 | 0 | return l_error; |
157 | 0 | } |
158 | | |
159 | | |
160 | | OFCondition DcmDateTime::setOFDateTime(const OFDateTime &dateTimeValue) |
161 | 0 | { |
162 | 0 | OFString dicomDateTime; |
163 | | /* convert OFDateTime value to DICOM DT format and set the element value */ |
164 | 0 | OFCondition l_error = getDicomDateTimeFromOFDateTime(dateTimeValue, dicomDateTime); |
165 | 0 | if (l_error.good()) |
166 | 0 | l_error = putOFStringArray(dicomDateTime); |
167 | 0 | return l_error; |
168 | 0 | } |
169 | | |
170 | | |
171 | | // ******************************** |
172 | | |
173 | | |
174 | | OFCondition DcmDateTime::getCurrentDateTime(OFString &dicomDateTime, |
175 | | const OFBool seconds, |
176 | | const OFBool fraction, |
177 | | const OFBool timeZone, |
178 | | const OFBool createMissingPart) |
179 | 0 | { |
180 | 0 | OFCondition l_error = EC_IllegalCall; |
181 | 0 | OFDateTime dateTimeValue; |
182 | | /* get the current system time */ |
183 | 0 | if (dateTimeValue.setCurrentDateTime()) |
184 | 0 | { |
185 | | /* format: YYYYMMDDHHMM[SS[.FFFFFF]][&ZZZZ] */ |
186 | 0 | if (dateTimeValue.getISOFormattedDateTime(dicomDateTime, seconds, fraction, timeZone, OFFalse /*showDelimiter*/, createMissingPart)) |
187 | 0 | l_error = EC_Normal; |
188 | 0 | } |
189 | | /* set default date/time if an error occurred */ |
190 | 0 | if (l_error.bad()) |
191 | 0 | { |
192 | | /* format: YYYYMMDDHHMM */ |
193 | 0 | dicomDateTime = "190001010000"; |
194 | 0 | if (seconds && createMissingPart) |
195 | 0 | { |
196 | | /* format: SS */ |
197 | 0 | dicomDateTime += "00"; |
198 | 0 | if (fraction && createMissingPart) |
199 | 0 | { |
200 | | /* format: .FFFFFF */ |
201 | 0 | dicomDateTime += ".000000"; |
202 | 0 | } |
203 | 0 | } |
204 | | /* a missing time zone is never created */ |
205 | 0 | } |
206 | 0 | return l_error; |
207 | 0 | } |
208 | | |
209 | | |
210 | | OFCondition DcmDateTime::getDicomDateTimeFromOFDateTime(const OFDateTime &dateTimeValue, |
211 | | OFString &dicomDateTime, |
212 | | const OFBool seconds, |
213 | | const OFBool fraction, |
214 | | const OFBool timeZone, |
215 | | const OFBool createMissingPart) |
216 | 0 | { |
217 | 0 | OFCondition l_error = EC_IllegalParameter; |
218 | | /* convert OFDateTime value to DICOM DT format */ |
219 | 0 | if (dateTimeValue.getISOFormattedDateTime(dicomDateTime, seconds, fraction, timeZone, OFFalse /*showDelimiter*/, createMissingPart)) |
220 | 0 | l_error = EC_Normal; |
221 | 0 | return l_error; |
222 | 0 | } |
223 | | |
224 | | |
225 | | OFCondition DcmDateTime::getOFDateTimeFromString(const OFString &dicomDateTime, |
226 | | OFDateTime &dateTimeValue) |
227 | 0 | { |
228 | 0 | return getOFDateTimeFromString(dicomDateTime.c_str(), dicomDateTime.size(), dateTimeValue); |
229 | 0 | } |
230 | | |
231 | | |
232 | | OFCondition DcmDateTime::getOFDateTimeFromString(const char *dicomDateTime, |
233 | | size_t dicomDateTimeSize, |
234 | | OFDateTime &dateTimeValue) |
235 | 0 | { |
236 | | // clear result variable |
237 | 0 | dateTimeValue.clear(); |
238 | | /* minimal check for valid format: YYYY */ |
239 | 0 | if ((dicomDateTimeSize < 4) || !OFStandard::checkDigits<4>(dicomDateTime)) |
240 | 0 | return EC_IllegalParameter; |
241 | 0 | unsigned int month = 1; |
242 | 0 | unsigned int day = 1; |
243 | 0 | double timeZone = 0.0; |
244 | | // check for/extract time zone |
245 | 0 | if ((dicomDateTimeSize >= 9) && DcmTime::getTimeZoneFromString(dicomDateTime + dicomDateTimeSize - 5, 5, timeZone).good()) |
246 | 0 | dicomDateTimeSize -= 5; |
247 | 0 | else |
248 | 0 | timeZone = OFTime::unspecifiedTimeZone; |
249 | 0 | switch(dicomDateTimeSize) |
250 | 0 | { |
251 | 0 | default: |
252 | | // check whether a time value is contained or it is simply an error |
253 | 0 | if (dicomDateTimeSize >= 10) |
254 | 0 | { |
255 | 0 | OFCondition status = DcmTime::getOFTimeFromString(dicomDateTime + 8, |
256 | 0 | dicomDateTimeSize - 8, |
257 | 0 | dateTimeValue.Time, |
258 | 0 | OFFalse, // no support for HH:MM:SS in VR=DT |
259 | 0 | timeZone); |
260 | 0 | if (status.bad()) |
261 | 0 | return status; |
262 | 0 | } |
263 | 0 | else break; |
264 | | |
265 | 0 | case 8: |
266 | 0 | if (OFStandard::checkDigits<2>(dicomDateTime + 6)) |
267 | 0 | day = OFStandard::extractDigits<unsigned int, 2>(dicomDateTime + 6); |
268 | 0 | else |
269 | 0 | break; |
270 | 0 | case 6: |
271 | 0 | if (OFStandard::checkDigits<2>(dicomDateTime + 4)) |
272 | 0 | month = OFStandard::extractDigits<unsigned int, 2>(dicomDateTime + 4); |
273 | 0 | else |
274 | 0 | break; |
275 | 0 | case 4: |
276 | 0 | if (dateTimeValue.Date.setDate(OFStandard::extractDigits<unsigned int, 4>(dicomDateTime), month, day)) |
277 | 0 | { |
278 | | // set timezone if it hasn't been set |
279 | 0 | if (dicomDateTimeSize <= 8) |
280 | 0 | dateTimeValue.Time.setTimeZone(timeZone); |
281 | 0 | return EC_Normal; |
282 | 0 | } |
283 | 0 | break; |
284 | 0 | } |
285 | 0 | return EC_IllegalParameter; |
286 | 0 | } |
287 | | |
288 | | |
289 | | OFCondition DcmDateTime::getISOFormattedDateTimeFromString(const OFString &dicomDateTime, |
290 | | OFString &formattedDateTime, |
291 | | const OFBool seconds, |
292 | | const OFBool fraction, |
293 | | const OFBool timeZone, |
294 | | const OFBool createMissingPart, |
295 | | const OFString &dateTimeSeparator, |
296 | | const OFString &timeZoneSeparator) |
297 | 0 | { |
298 | 0 | OFCondition l_error = EC_Normal; |
299 | 0 | const size_t length = dicomDateTime.length(); |
300 | | /* minimum DT format: YYYYMMDD */ |
301 | 0 | if (length >= 8) |
302 | 0 | { |
303 | 0 | OFString timeString; |
304 | 0 | OFDate dateValue; |
305 | | /* get formatted date: YYYY-MM-DD */ |
306 | 0 | l_error = DcmDate::getOFDateFromString(dicomDateTime.substr(0, 8), dateValue, OFFalse /*supportOldFormat*/); |
307 | 0 | if (l_error.good()) |
308 | 0 | { |
309 | 0 | dateValue.getISOFormattedDate(formattedDateTime); |
310 | | /* get formatted time: [HH[:MM[:SS[.FFFFFF]]]] */ |
311 | 0 | const size_t posSign = dicomDateTime.find_first_of("+-", 8); |
312 | 0 | OFString dicomTime = (posSign != OFString_npos) ? dicomDateTime.substr(8, posSign - 8) : dicomDateTime.substr(8); |
313 | 0 | l_error = DcmTime::getISOFormattedTimeFromString(dicomTime, timeString, seconds, fraction, createMissingPart, OFFalse /*supportOldFormat*/); |
314 | 0 | if (l_error.good() && !timeString.empty()) |
315 | 0 | { |
316 | | /* add time string with separator */ |
317 | 0 | formattedDateTime += dateTimeSeparator; |
318 | 0 | formattedDateTime += timeString; |
319 | | /* add optional time zone: [+/-HH:MM] */ |
320 | 0 | if (timeZone) |
321 | 0 | { |
322 | | /* check whether optional time zone is present: &ZZZZ */ |
323 | 0 | if ((posSign != OFString_npos) && (length >= posSign + 5)) |
324 | 0 | { |
325 | 0 | formattedDateTime += timeZoneSeparator; |
326 | 0 | formattedDateTime += dicomDateTime[posSign]; |
327 | 0 | formattedDateTime += dicomDateTime.substr(posSign + 1, 2); |
328 | 0 | formattedDateTime += ":"; |
329 | 0 | formattedDateTime += dicomDateTime.substr(posSign + 3, 2); |
330 | 0 | } |
331 | | /* a missing time zone is never created */ |
332 | 0 | } |
333 | 0 | } |
334 | 0 | } |
335 | 0 | } |
336 | 0 | else if (length == 0) |
337 | 0 | { |
338 | | /* an empty input string is no error ... */ |
339 | 0 | formattedDateTime.clear(); |
340 | 0 | } else { |
341 | | /* ... but all other formats are (if not handled before) */ |
342 | 0 | l_error = EC_IllegalParameter; |
343 | 0 | } |
344 | | /* clear result variable in case of error */ |
345 | 0 | if (l_error.bad()) |
346 | 0 | formattedDateTime.clear(); |
347 | 0 | return l_error; |
348 | 0 | } |
349 | | |
350 | | |
351 | | // ******************************** |
352 | | |
353 | | |
354 | | OFBool DcmDateTime::check(const char *dicomDateTime, |
355 | | const size_t dicomDateTimeSize) |
356 | 0 | { |
357 | 0 | const int vrID = DcmElement::scanValue("dt", dicomDateTime, dicomDateTimeSize); |
358 | 0 | return vrID == 7 /* DT */ || vrID == 18 /* dubious DT (pre 1850 or post 2049) */; |
359 | 0 | } |
360 | | |
361 | | |
362 | | OFCondition DcmDateTime::checkStringValue(const OFString &value, |
363 | | const OFString &vm) |
364 | 0 | { |
365 | 0 | OFCondition result = EC_Normal; |
366 | 0 | const size_t valLen = value.length(); |
367 | 0 | if (valLen > 0) |
368 | 0 | { |
369 | 0 | size_t posStart = 0; |
370 | 0 | unsigned long vmNum = 0; |
371 | | /* iterate over all value components */ |
372 | 0 | while (posStart != OFString_npos) |
373 | 0 | { |
374 | 0 | ++vmNum; |
375 | | /* search for next component separator */ |
376 | 0 | const size_t posEnd = value.find('\\', posStart); |
377 | 0 | const size_t length = (posEnd == OFString_npos) ? valLen - posStart : posEnd - posStart; |
378 | | /* check length of current value component */ |
379 | 0 | if (length > MAX_DT_LENGTH) |
380 | 0 | { |
381 | 0 | result = EC_MaximumLengthViolated; |
382 | 0 | break; |
383 | 0 | } |
384 | 0 | else if (dcmEnableVRCheckerForStringValues.get()) |
385 | 0 | { |
386 | | /* check value representation */ |
387 | 0 | if (!check(value.data() + posStart, length)) |
388 | 0 | { |
389 | 0 | result = EC_ValueRepresentationViolated; |
390 | 0 | break; |
391 | 0 | } |
392 | 0 | } |
393 | 0 | posStart = (posEnd == OFString_npos) ? posEnd : posEnd + 1; |
394 | 0 | } |
395 | 0 | if (result.good() && !vm.empty()) |
396 | 0 | { |
397 | | /* check value multiplicity */ |
398 | 0 | result = DcmElement::checkVM(vmNum, vm); |
399 | 0 | } |
400 | 0 | } |
401 | 0 | return result; |
402 | 0 | } |
403 | | |
404 | | |
405 | | OFBool DcmDateTime::matches(const OFString &key, |
406 | | const OFString &candidate, |
407 | | const OFBool enableWildCardMatching) const |
408 | 0 | { |
409 | 0 | OFstatic_cast(void, enableWildCardMatching); |
410 | 0 | return DcmAttributeMatching::rangeMatchingDateTime(key.c_str(), key.length(), candidate.c_str(), candidate.length()); |
411 | 0 | } |