diff --git a/ofstd/include/dcmtk/ofstd/ofdatime.h b/ofstd/include/dcmtk/ofstd/ofdatime.h index 4a974b4a95..371990e3f2 100644 --- a/ofstd/include/dcmtk/ofstd/ofdatime.h +++ b/ofstd/include/dcmtk/ofstd/ofdatime.h @@ -205,10 +205,10 @@ class DCMTK_OFSTD_EXPORT OFDateTime /** set the date/time value to the given ISO formatted date/time string. * The two ISO date/time formats supported by this function are - * - "YYYY-MM-DD HH:MM[:SS [&ZZ:ZZ]]" (with arbitrary delimiters) and - * - "YYYYMMDDHHMM[SS[&ZZZZ]]" (without delimiters, useful for DICOM datetime type) - * where the brackets enclose optional parts. Please note that the optional fractional - * part of a second ".FFFFFF" (see getISOFormattedDateTime()) is not yet supported. + * - "YYYY-MM-DD HH:MM[:SS[.FFFFFF] [&ZZ:ZZ]]" (with arbitrary delimiters) and + * - "YYYYMMDDHHMM[SS[.FFFFFF][&ZZZZ]]" (without delimiters, useful for DICOM datetime type) + * where the brackets enclose optional parts. Optional fractional + * part of a second ".FFFFFF" (see getISOFormattedDateTime()) is supported. * @param formattedDateTime ISO formatted date/time value to be set * @return OFTrue if input is valid and result variable has been set, OFFalse otherwise */ diff --git a/ofstd/include/dcmtk/ofstd/oftime.h b/ofstd/include/dcmtk/ofstd/oftime.h index cfeb3d4c7c..114aa59a68 100644 --- a/ofstd/include/dcmtk/ofstd/oftime.h +++ b/ofstd/include/dcmtk/ofstd/oftime.h @@ -230,10 +230,10 @@ class DCMTK_OFSTD_EXPORT OFTime /** set the time value to the given ISO formatted time string. * The two ISO time formats supported by this function are - * - "HH:MM[:SS [&ZZ:ZZ]]" (with arbitrary delimiters) and - * - "HHMM[SS[&ZZZZ]]" (without delimiters) - * where the brackets enclose optional parts. Please note that the optional fractional - * part of a second ".FFFFFF" (see getISOFormattedTime()) is not yet supported. + * - "HH:MM[:SS[.FFFFFF] [&ZZ:ZZ]]" (with arbitrary delimiters) and + * - "HHMM[SS[.FFFFFF][&ZZZZ]]" (without delimiters) + * where the brackets enclose optional parts. Optional fractional + * part of a second ".FFFFFF" (see getISOFormattedTime()) is supported. * @param formattedTime ISO formatted time value to be set * @return OFTrue if input is valid and result variable has been set, OFFalse otherwise */ diff --git a/ofstd/libsrc/ofdatime.cc b/ofstd/libsrc/ofdatime.cc index acda440bfb..56384c63bf 100644 --- a/ofstd/libsrc/ofdatime.cc +++ b/ofstd/libsrc/ofdatime.cc @@ -203,31 +203,47 @@ OFBool OFDateTime::setCurrentDateTime() OFBool OFDateTime::setISOFormattedDateTime(const OFString &formattedDateTime) { - OFBool result = OFFalse; const size_t length = formattedDateTime.length(); - const size_t firstSep = formattedDateTime.find_first_not_of("0123456789"); - const OFBool separators = (firstSep != OFString_npos); - /* check for supported formats: "YYYYMMDDHHMM[SS]" or "YYYYMMDDHHMMSS&ZZZZ" */ - if (((((length == 12) || (length == 14)) && !separators) || - ((length == 19) && (firstSep == 14) && ((formattedDateTime[14] == '+') || (formattedDateTime[14] == '-'))))) - { - if (Date.setISOFormattedDate(formattedDateTime.substr(0, 8)) && Time.setISOFormattedTime(formattedDateTime.substr(8))) - result = OFTrue; + + size_t tm_pos = 0; + size_t date_digits = 0; + size_t date_size = 0; + + /* find end of YYYYMMDD or YYYY-MM-DD */ + while (tm_pos < length && date_digits < 8) { + if (isdigit(formattedDateTime.at(tm_pos))) + date_digits++; + tm_pos++; } - /* "YYYY-MM-DD HH:MM[:SS]" or "YYYY-MM-DD HH:MM:SS &ZZ:ZZ" */ - else if ((length >= 16) && separators) - { - if (Date.setISOFormattedDate(formattedDateTime.substr(0, 10))) - { - size_t pos = 10; - /* search for first digit of the time value (skip arbitrary separators) */ - while ((pos < length) && !isdigit(OFstatic_cast(unsigned char, formattedDateTime.at(pos)))) - ++pos; - if ((pos < length) && Time.setISOFormattedTime(formattedDateTime.substr(pos))) - result = OFTrue; - } + + /* check if we found enough number of digits for date and symbols for time to continue parsing */ + if (date_digits < 8 || tm_pos == length) + return OFFalse; + + date_size = tm_pos; + + if (formattedDateTime.at(tm_pos) == 'T') { + /* dateTtime, skip T */ + tm_pos++; + } else { + /* skip heading spaces from time */ + while (tm_pos < length && formattedDateTime.at(tm_pos) == ' ') + tm_pos++; } - return result; + + /* check if we have enough symbols for time to continue parsing */ + if (tm_pos == length) + return OFFalse; + + /* parse ISO formatted date */ + if (!Date.setISOFormattedDate(formattedDateTime.substr(0, date_size))) + return OFFalse; + + /* parse ISO formatted time */ + if (!Time.setISOFormattedTime(formattedDateTime.substr(tm_pos))) + return OFFalse; + + return OFTrue; } diff --git a/ofstd/libsrc/oftime.cc b/ofstd/libsrc/oftime.cc index 75099d2b9e..57ac65c683 100644 --- a/ofstd/libsrc/oftime.cc +++ b/ofstd/libsrc/oftime.cc @@ -19,7 +19,6 @@ * */ - #include "dcmtk/config/osconfig.h" #define INCLUDE_CSTDIO @@ -389,77 +388,115 @@ OFBool OFTime::setCurrentTime(const time_t &tt) OFBool OFTime::setISOFormattedTime(const OFString &formattedTime) { - OFBool status = OFFalse; + unsigned int hours, minutes; + double seconds, timezone; + + size_t pos = 0; const size_t length = formattedTime.length(); - const size_t firstSep = formattedTime.find_first_not_of("0123456789"); - const OFBool separators = (firstSep != OFString_npos); - unsigned int hours, minutes, seconds; - /* check for supported formats: HHMM */ - if ((length == 4) && !separators) - { - /* extract "HH" and "MM" components from time string */ - if (sscanf(formattedTime.c_str(), "%02u%02u", &hours, &minutes) == 2) - status = setTime(hours, minutes, 0 /*seconds*/); + OFBool delim_must_present = OFFalse; + char delimiter = ':'; + + #define FTOA_TODIGIT(c) ((c) - '0') + + if ((length - pos) < 2 || !isdigit(formattedTime[pos]) || !isdigit(formattedTime[pos + 1])) { + /* no digits available to get hours */ + return OFFalse; } - /* HH:MM */ - else if ((length == 5) && separators) - { - /* extract "HH" and "MM" components from time string */ - if (sscanf(formattedTime.c_str(), "%02u%*c%02u", &hours, &minutes) == 2) - status = setTime(hours, minutes, 0 /*seconds*/); + hours = FTOA_TODIGIT(formattedTime[pos]) * 10 + FTOA_TODIGIT(formattedTime[pos + 1]); + + /* step to minutes or delimiter */ + pos += 2; + if ((length - pos) < 1) { + /* length is not enough for ':' (possible) delimiter */ + return OFFalse; } - /* HHMMSS */ - else if ((length == 6) && !separators) - { - /* extract "HH", "MM" and "SS" components from time string */ - if (sscanf(formattedTime.c_str(), "%02u%02u%02u", &hours, &minutes, &seconds) == 3) - status = setTime(hours, minutes, seconds); + /* skip delimiter ':' if it found here */ + if (formattedTime[pos] == ':' || formattedTime[pos] == '-') { + /* after first ':' between HH and MM we require it everywhere */ + delim_must_present = OFTrue; + delimiter = formattedTime[pos]; + pos++; } - /* HH:MM:SS */ - else if ((length == 8) && separators) - { - /* extract "HH", "MM" and "SS" components from time string */ - if (sscanf(formattedTime.c_str(), "%02u%*c%02u%*c%02u", &hours, &minutes, &seconds) == 3) - status = setTime(hours, minutes, seconds); + if ((length - pos) < 2 || !isdigit(formattedTime[pos]) || !isdigit(formattedTime[pos + 1])) { + /* no digits available to get minutes */ + return OFFalse; } - /* HHMMSS&ZZZZ */ - else if ((length == 11) && (firstSep == 6) && ((formattedTime[6] == '+') || (formattedTime[6] == '-'))) - { - int tzHours; - unsigned int tzMinutes; - /* extract "HH", "MM", "SS" and "&ZZZZ" components from time string */ - if (sscanf(formattedTime.c_str(), "%02u%02u%02u%03d%02u", &hours, &minutes, &seconds, &tzHours, &tzMinutes) == 5) - { - const double timeZone = (tzHours < 0) ? tzHours - OFstatic_cast(double, tzMinutes) / 60 - : tzHours + OFstatic_cast(double, tzMinutes) / 60; - status = setTime(hours, minutes, seconds, timeZone); + minutes = FTOA_TODIGIT(formattedTime[pos]) * 10 + FTOA_TODIGIT(formattedTime[pos + 1]); + + /* step to seconds or delimiter */ + pos += 2; + if ((length - pos) < 1) { + /* HHMM or HH:MM is okay */ + return setTime(hours, minutes, seconds); + } + /* skip one allowed ':' if it found here */ + if (formattedTime[pos] == delimiter) { + pos++; + } else if (delim_must_present) { + return OFFalse; + } + + if ((length - pos) < 2 || !isdigit(formattedTime[pos]) || !isdigit(formattedTime[pos + 1])) { + /* no digits available to get seconds */ + return OFFalse; + } + seconds = FTOA_TODIGIT(formattedTime[pos]) * 10.0 + FTOA_TODIGIT(formattedTime[pos + 1]); + + /* step to fractional part of seconds or time zone */ + pos += 2; + + if ((length - pos) >= 1 && formattedTime[pos] == '.') { + pos++; + + size_t fp_start = pos, fp_size = 0; + + /* we are on beginning of the fractional part of seconds */ + while ( pos < length && isdigit(formattedTime[pos]) ) { + fp_size++; + pos++; + } + /* Part 05, sect 6.2. The FFFFFF component, if present, shall contain 1 to 6 digits. */ + if (fp_size == 0 || fp_size > 6) { + return OFFalse; + } + double fp_divider = 0.1; + for (size_t fpi = fp_start; fpi < pos; fpi++, fp_divider /= 10.0) { + seconds += fp_divider * FTOA_TODIGIT(formattedTime[fpi]); } } - /* HH:MM:SS &ZZ:ZZ */ - else if ((length >= 14) && separators) - { - /* first, extract "HH", "MM" and "SS" components from time string */ - if (sscanf(formattedTime.c_str(), "%02u%*c%02u%*c%02u", &hours, &minutes, &seconds) == 3) - { - size_t pos = 8; - /* then search for the first digit of the time zone value (skip arbitrary separators) */ - while ((pos < length) && !isdigit(OFstatic_cast(unsigned char, formattedTime.at(pos)))) - ++pos; - if (pos < length) - { - /* and finally, extract the time zone component from the time string */ - int tzHours; - unsigned int tzMinutes; - if (sscanf(formattedTime.c_str() + pos - 1, "%03d%*c%02u", &tzHours, &tzMinutes) == 2) - { - const double timeZone = (tzHours < 0) ? tzHours - OFstatic_cast(double, tzMinutes) / 60 - : tzHours + OFstatic_cast(double, tzMinutes) / 60; - status = setTime(hours, minutes, seconds, timeZone); - } - } + + if ((length - pos) >= 1) { + /* extract "&ZZZZ" or "&ZZ:ZZ" components from time string */ + + if (delim_must_present) { + while (pos < length && formattedTime[pos] == ' ') + pos++; } + + if ((length - pos) < 1) { + /* length is not enough for '&' delimiter */ + return OFFalse; + } + + if (((formattedTime[pos] != '+') && (formattedTime[pos] != '-'))) + return OFFalse; + + int tzHours; + unsigned int tzMinutes; + + if (delim_must_present) { + if (sscanf(formattedTime.c_str() + pos, "%03d%*c%02u", &tzHours, &tzMinutes) != 2) + return OFFalse; + } else { + if (sscanf(formattedTime.c_str() + pos, "%03d%02u", &tzHours, &tzMinutes) != 2) + return OFFalse; + } + const double timeZone = (tzHours < 0) ? tzHours - OFstatic_cast(double, tzMinutes) / 60 + : tzHours + OFstatic_cast(double, tzMinutes) / 60; + return setTime(hours, minutes, seconds, timeZone); } - return status; + + return setTime(hours, minutes, seconds); } diff --git a/ofstd/tests/tofdatim.cc b/ofstd/tests/tofdatim.cc index f18250cc18..174f804d9a 100644 --- a/ofstd/tests/tofdatim.cc +++ b/ofstd/tests/tofdatim.cc @@ -105,6 +105,17 @@ OFTEST(ofstd_OFTime) OFCHECK_EQUAL(time1.getTimeInSeconds(OFTrue /*useTimeZone*/, OFFalse /*normalize*/), 13599); OFCHECK(time1.setTimeInHours(99, 0, OFTrue /*normalized*/)); OFCHECK_EQUAL(time1.getTimeInHours(OFTrue /*useTimeZone*/, OFFalse /*normalize*/), 3); + /* the "seconds" part is mandatory if fractional part is present */ + OFCHECK(!time1.setISOFormattedTime("10:15.9")); + OFCHECK(time1.setISOFormattedTime("10:15:30.9")); + OFCHECK_EQUAL(time1.getSecond(), 30.9); + OFCHECK(time1.setISOFormattedTime("101530.987654")); + OFCHECK_EQUAL(time1.getHour(), 10); + OFCHECK_EQUAL(time1.getMinute(), 15); + OFCHECK_EQUAL(time1.getSecond(), 30.987654); + OFCHECK(time1.setISOFormattedTime("10:15:30.102030 +01:30")); + OFCHECK_EQUAL(time1.getTimeZone(), +1.50); + OFCHECK_EQUAL(time1.getSecond(), 30.10203); } @@ -152,6 +163,20 @@ OFTEST(ofstd_OFDateTime) /* the "seconds" part is mandatory if time zone is present */ OFCHECK(!dateTime2.setISOFormattedDateTime("2000-12-31 10:15 -02:30")); OFCHECK(!dateTime2.setISOFormattedDateTime("200012311015+0100")); + /* the "seconds" part is mandatory if fractional part is present */ + OFCHECK(!dateTime1.setISOFormattedDateTime("200012311015.9")); + /* fractional part must contain digits only */ + OFCHECK(!dateTime1.setISOFormattedDateTime("20001231101530.9B2")); + OFCHECK(dateTime1.setISOFormattedDateTime("20001231101530.9")); + OFCHECK(dateTime1.getISOFormattedDateTime(tmpString, OFTrue /*showSeconds*/, OFTrue /*showFraction*/, + OFTrue /*showTimeZone*/, OFFalse /*showDelimiter*/, "" /*dateTimeSeparator*/, "" /*timeZoneSeparator*/)); + OFCHECK_EQUAL(tmpString, "20001231101530.900000+0000"); + /* maximal length is 6 digits */ + OFCHECK(!dateTime1.setISOFormattedDateTime("20001231101530.0000001")); + OFCHECK(dateTime1.setISOFormattedDateTime("20001231101530.123456-0130")); + OFCHECK(dateTime1.getISOFormattedDateTime(tmpString, OFTrue /*showSeconds*/, OFTrue /*showFraction*/, + OFTrue /*showTimeZone*/, OFTrue /*showDelimiter*/, " " /*dateTimeSeparator*/, " " /*timeZoneSeparator*/)); + OFCHECK_EQUAL(tmpString, "2000-12-31 10:15:30.123456 -01:30"); }