Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for optional fractional part of a second #17

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions ofstd/include/dcmtk/ofstd/ofdatime.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
8 changes: 4 additions & 4 deletions ofstd/include/dcmtk/ofstd/oftime.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
60 changes: 38 additions & 22 deletions ofstd/libsrc/ofdatime.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}


Expand Down
163 changes: 100 additions & 63 deletions ofstd/libsrc/oftime.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
*
*/


#include "dcmtk/config/osconfig.h"

#define INCLUDE_CSTDIO
Expand Down Expand Up @@ -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);
}


Expand Down
25 changes: 25 additions & 0 deletions ofstd/tests/tofdatim.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}


Expand Down Expand Up @@ -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");
}


Expand Down