Coverage Report

Created: 2026-06-05 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}