BDE 4.14.0 Production release
Loading...
Searching...
No Matches
bdlt_iso8601util

Macros

#define BDLT_ISO8601UTIL_DEPRECATE_GENERATE
 
#define BDLT_ISO8601UTIL_DEPRECATE_GENERATE_ORDER
 
#define BDLT_ISO8601UTIL_DEPRECATE_GENERATERAW
 
#define BDLT_ISO8601UTIL_DEPRECATE_PARSERELAXED
 

Detailed Description

Outline

Purpose

Provide conversions between date/time objects and ISO 8601 strings.

Classes

See also
bdlt_iso8601utilconfiguration

Description

This component provides a namespace, bdlt::Iso8601Util, containing functions that convert bsls timeinterval and bdlt date, time, and datetime objects to and from ("generate" and "parse", respectively) corresponding string representations that are compliant with the ISO 8601 standard. The version of the ISO 8601 standard that is the basis for this component can be found at:

http://dotat.at/tmp/ISO_8601-2004_E.pdf

In general terms, Iso8601Util functions support what ISO 8601 refers to as complete representations in extended format. We first present a brief overview before delving into the details of the ISO 8601 representations that are supported for each of the relevant vocabulary types.

Each function that generates ISO 8601 strings (named generate and generateRaw) takes an object and a char * buffer, bsl::string, or bsl::ostream, and writes an ISO 8601 representation of the object to the buffer, string, or stream. The "raw" functions are distinguished from their non-"raw" counterparts in three respects:

Since the generate functions always succeed, no status value is returned. Instead, either the number of characters output to the char * buffer or string, or a reference to the stream, is returned. (Note that the generating functions also take an optional bdlt::Iso8601UtilConfiguration object, which is discussed shortly.)

Each function that parses ISO 8601 strings (named parse) take the address of a target object and a const char * (paired with a length argument) or bsl::string_view, and loads the object with the result of parsing the character string. Since parsing can fail, the parse functions return an int status value (0 for success and a non-zero value for failure). Note that, besides elementary syntactical considerations, the validity of parsed strings are subject to the semantic constraints imposed by the various isValid* class methods, (i.e., Date::isValidYearMonthDay, Time::isValid, etc.).

Terminology

As this component concerns ISO 8601, some terms from that specification are used liberally in what follows. Two ISO 8601 terms of particular note are zone designator and fractional second.

An ISO 8601 zone designator corresponds to what other bdlt components commonly refer to as a timezone offset (or simply as an offset; e.g., see bdlt_datetimetz ). For example, the ISO 8601 string 2002-03-17T15:46:00+04:00 has a zone designator of +04:00, indicating a timezone 4 hours ahead of UTC.

An ISO 8601 fractional second corresponds to, for example, the combined millisecond and microsecond attributes of a bdlt::Time object, or the combined millisecond and microsecond attributes of a bdlt::Datetime object. For example, the Time value (and ISO 8601 string) 15:46:09.330000 has a millisecond attribute value of 330 and a microsecond attribute of 0 (i.e., a fractional second of .33).

ISO 8601 String Generation

Strings produced by the generate and generateRaw functions are a straightforward transposition of the attributes of the source value into an appropriate ISO 8601 format, and are best illustrated by a few examples. Note that for Datetime and DatetimeTz, the fractional second is generated with the precision specified in the configuration. Also note that for Time and TimeTz, the fractional second is generated with the precision specified in the configuration up to a maximum precision of 6.

+--------------------------------------+---------------------------------+
| Object Value | Generated ISO 8601 String |
| | (using default configuration) |
+======================================+=================================+
| bsls::TimeInterval(1000, 3000000) | PT16M40.003S |
+--------------------------------------+---------------------------------+
| Date(2002, 03, 17) | 2002-03-17 |
+--------------------------------------+---------------------------------+
| Time(15, 46, 09, 330) | 15:46:09.330 |
+--------------------------------------+---------------------------------+
| Datetime(Date(2002, 03, 17) | |
| Time(15, 46, 09, 330)) | 2002-03-17T15:46:09.330 |
+--------------------------------------+---------------------------------+
| DateTz(Date(2002, 03, 17), -120) | 2002-03-17-02:00 [*] |
+--------------------------------------+---------------------------------+
| TimeTz(Time(15, 46, 09, 330), 270) | 15:46:09.330+04:30 |
+--------------------------------------+---------------------------------+
| DatetimeTz(Datetime( | |
| Date(2002, 03, 17), | |
| Time(15, 46, 09, 330)), | |
| 0) | 2002-03-17T15:46:09.330+00:00 |
+--------------------------------------+---------------------------------+
Definition bsls_timeinterval.h:301

[*] Note that the ISO 8601 specification does not have an equivalent to bdlt::DateTz.

Configuration

The generate and generateRaw functions provide an optional configuration parameter. This optional parameter, of type Iso8601UtilConfiguration, enables configuration of four aspects of ISO 8601 string generation:

Iso8601UtilConfiguration has four attributes that directly correspond to these aspects. In addition, for generate methods that are not supplied with a configuration argument, a process-wide configuration takes effect. See bdlt_iso8601utilconfiguration for details.

ISO 8601 String Parsing

The parse functions accept all strings that are produced by the generate functions. In addition, the parse functions accept some variation in the generated strings, the details of which are discussed next. Note that the parse methods are not configurable like the generate methods (i.e., via an optional Iso8601UtilConfiguration argument). Moreover, the process-wide configuration has no effect on parsing either. Instead, the parse methods automatically accept . or , as the decimal sign in fractional seconds, and treat +00:00, +0000, Z, and z as equivalent zone designators (all denoting UTC).

Parsing in Relaxed mode accepts "relaxed" ISO 8601 format that is a superset of the strict ISO 8601 format, meaning this function will parse ISO 8601 values as well as supporting some common variations. Currently this allows a SPACE character to be used as an alternative separator between date and time elements (where strict ISO 8601 requires a T), but the set of extensions may grow in the future. Relaxed parsing is done by specifying the relaxed option using the Iso8601UtilParseConfiguration type and passing it to the parse functions. There are also parseRelaxed functions which are now deprecated.

Zone Designators

The zone designator is optional, and can be present when parsing for any type other than bsls::TimeInterval, i.e., even for Date, Time, and Datetime. If a zone designator is parsed for a Date, it must be valid, so it can affect the status value that is returned in that case, but it is otherwise ignored. For Time and Datetime, any zone designator present in the parsed string will affect the resulting object value (unless the zone designator denotes UTC) because the result is converted to UTC. If the zone designator is absent, it is treated as if +00:00 were specified:

+------------------------------------+-----------------------------------+
| Parsed ISO 8601 String | Result Object Value |
+====================================+===================================+
| 2002-03-17-02:00 | Date(2002, 03, 17) |
| | # zone designator ignored |
+------------------------------------+-----------------------------------+
| 2002-03-17-02:65 | Date: parsing fails |
| | # invalid zone designator |
+------------------------------------+-----------------------------------+
| 15:46:09.330+04:30 | Time(11, 16, 09, 330) |
| | # converted to UTC |
+------------------------------------+-----------------------------------+
| 15:46:09.330+04:30 | TimeTz(Time(15, 46, 09, 330), |
| | 270) |
+------------------------------------+-----------------------------------+
| 15:46:09.330 | TimeTz(Time(15, 46, 09, 330), |
| | 0) |
| | # implied '+00:00' |
+------------------------------------+-----------------------------------+
| 2002-03-17T23:46:09.222-05:00 | Datetime(Date(2002, 03, 18), |
| | Time(04, 46, 09, 222)) |
| | # carry into 'day' attribute |
| | # when converted to UTC |
+------------------------------------+-----------------------------------+

There is also the basic format, which may be specified when parsing by using the bdlt::Iso8601UtilParseConfiguration type, in which case there are to be no -s in the Date and no :s in the Time. The : in the time zone is always optional, whether in basic or default format.

+------------------------------------+-----------------------------------+
| Parsed Basic ISO 8601 String | Result Object Value |
+====================================+===================================+
| 20020317-0200 | Date(2002, 03, 17) |
| | # zone designator ignored |
+------------------------------------+-----------------------------------+
| 20020317-0265 | Date: parsing fails |
| | # invalid zone designator |
+------------------------------------+-----------------------------------+
| 154609.330+0430 | Time(11, 16, 09, 330) |
| | # converted to UTC |
+------------------------------------+-----------------------------------+
| 154609.330+0430 | TimeTz(Time(15, 46, 09, 330), |
| | 270) |
+------------------------------------+-----------------------------------+
| 154609.330 | TimeTz(Time(15, 46, 09, 330), |
| | 0) |
| | # implied '+00:00' |
+------------------------------------+-----------------------------------+
| 20020317T234609.222-0500 | Datetime(Date(2002, 03, 18), |
| | Time(04, 46, 09, 222)) |
| | # carry into 'day' attribute |
| | # when converted to UTC |
+------------------------------------+-----------------------------------+

In the last example above, the conversion to UTC incurs a carry into the day attribute of the Date component of the resulting Datetime value. Note that if such a carry causes an underflow or overflow at the extreme ends of the valid range of dates (0001/01/01 and 9999/12/31), then parsing for Datetime fails.

Fractional Seconds

The fractional second is optional. When the fractional second is absent, it is treated as if .0 were specified. When the fractional second is present, it can have one or more digits (i.e., it can contain more than six). For Datetime, DatetimeTz, Time, and TimeTz, if more than six digits are included in the fractional second, values are rounded to a full microsecond; i.e., values greater than or equal to .5 microseconds are rounded up. For bsls::TimeInterval, if more than nine digits are included in the fractional second, values are rounded to a full nanosecond; i.e., values greater than or equal to .5 microseconds are rounded up. These roundings may incur a carry of one second into the second attribute:

+--------------------------------------+---------------------------------+
| Parsed ISO 8601 String | Result Object Value |
+======================================+=================================+
| 15:46:09.1 | Time(15, 46, 09, 100) |
+--------------------------------------+---------------------------------+
| 15:46:09-05:00 | TimeTz(Time(15, 46, 09, 000), |
| | -300) |
| | # implied '.0' |
+--------------------------------------+---------------------------------+
| 15:46:09.99999949 | Time(15, 46, 09, 999, 999) |
| | # truncate last two digits |
+--------------------------------------+---------------------------------+
| 15:46:09.9999995 | Time(15, 46, 10, 000, 000) |
| | # round up and carry |
+--------------------------------------+---------------------------------+

Note that, for Datetime and DatetimeTz, if a carry due to rounding of the fractional second would cause an overflow at the extreme upper end of the valid range of dates (i.e., 9999/12/31), then parsing would fail.

Leap Seconds

Leap seconds are not representable by bdlt::Time or bdlt::Datetime. Hence, they are not produced by any of the Iso8601Util generate functions. However, positive leap seconds are supported by the parse functions. A leap second is recognized when the value parsed for the second attribute of a Time is 60 – regardless of the values parsed for the hour, minute, millisecond, and microsecond attributes. Note that this behavior is more generous than that afforded by the ISO 8601 specification (which indicates that a positive leap second can only be represented as "23:59:60Z").

When a leap second is detected during parsing of an ISO 8601 string, the second attribute is taken to be 59, so that the value of the Time object can be validly set; then an additional second is added to the object. Note that the possible carry incurred by a leap second (i.e., when loading the result of parsing into a Datetime or DatetimeTz object) has the same potential for overflow as may occur with fractional seconds that are rounded up (although in admittedly pathological cases).

The Time 24:00

According to the ISO 8601 specification, the time 24:00 is interpreted as midnight, i.e., the last instant of a day. However, this concept is not supported by bdlt. Although 24:00 is representable by bdlt, i.e., as the default value for bdlt::Time, Time(24, 0) does not represent midnight when it is the value for the "time" attribute of a Datetime (or DatetimeTz) object. For example:

bdlt::Datetime notMidnight =
bdlt::Datetime(bdlt::Date(2002, 03, 17), bdlt::Time(24, 0, 0));
notMidnight.addSeconds(1);
assert(notMidnight ==
bdlt::Datetime(bdlt::Date(2002, 03, 17), bdlt::Time( 0, 0, 1));
Definition bdlt_date.h:294
Definition bdlt_datetime.h:331
Datetime & addSeconds(bsls::Types::Int64 seconds)
Definition bdlt_datetime.h:2024
Definition bdlt_time.h:196

It is important to be aware of this peculiarity of Datetime (and DatetimeTz) as it relates to ISO 8601.

The following table shows some examples of parsing an ISO 8601 string containing "24:00". Note that parsing fails if the zone designator is not equivalent to "+00:00" when the time 24:00 is encountered:

+------------------------------------+-----------------------------------+
| Parsed ISO 8601 String | Result Object Value |
+====================================+===================================+
| 24:00:00.000000 | Time(24, 0, 0, 0) |
| | # preserve default 'Time' value |
+------------------------------------+-----------------------------------+
| 24:00:00.000000-04:00 | TimeTz: parsing fails |
| | # zone designator not UTC |
+------------------------------------+-----------------------------------+
| 0001-01-01T24:00:00.000000 | Datetime(Date(0001, 01, 01), |
| | Time(24, 0, 0, 0)) |
| | # preserve 'Datetime' default |
| | # value |
+------------------------------------+-----------------------------------+
| 2002-03-17T24:00:00.000000 | Datetime(Date(2002, 03, 17), |
| | Time(24, 0, 0, 0)) |
| | # preserve default 'Time' value |
+------------------------------------+-----------------------------------+

An hour attribute value of 24 is also "preserved" by the generate functions provided by this component:

+------------------------------------+-----------------------------------+
| Source Object Value | Generated ISO 8601 String |
+====================================+===================================+
| Time(24, 0, 0, 0) | 24:00:00.000 |
+------------------------------------+-----------------------------------+
| Datetime(Date(2002, 03, 17), | 2002-03-17T24:00:00.000 |
| Time(24, 0, 0, 0)) | |
+------------------------------------+-----------------------------------+

Summary of Supported ISO 8601 Representations

The syntax description below summarizes the ISO 8601 string representations supported by this component. Although not quoted (for readability), [+-:.,TtZz] are literal characters that can occur in ISO 8601 strings. Furthermore, for clarity, the (rarely used) lowercase t and z characters are omitted from the specifications below, as well as from the function-level documentation. The characters [YMDhms] each denote a decimal digit, {} brackets optional elements, () is used for grouping, and | separates alternatives:

<Generated Date> ::= <DATE>
<Parsed Date> ::= <Parsed DateTz>
<Generated DateTz> ::= <DATE><ZONE>
<Parsed DateTz> ::= <DATE>{<ZONE>}
<Generated Time> ::= <TIME FLEXIBLE>
<Parsed Time> ::= <Parsed TimeTz>
<Generated TimeTz> ::= <TIME FLEXIBLE><ZONE>
<Parsed TimeTz> ::= <TIME FLEXIBLE>{<ZONE>}
<Generated Datetime> ::= <DATE>T<TIME FLEXIBLE>
<Parsed Datetime> ::= <Parsed DatetimeTz>
<Generated DatetimeTz> ::= <DATE>T<TIME FLEXIBLE><ZONE>
<Parsed DatetimeTz> ::= <DATE>(T|t)<TIME FLEXIBLE>{<ZONE>}
(Default)
<Parsed DatetimeTz> ::= <DATE>(T|t| )<TIME FLEXIBLE>{<ZONE>}
(Relaxed)
<DATE> (Default) ::= YYYY-MM-DD
<DATE> (Basic) ::= YYYYMMDD
<TIME FLEXIBLE> (Default) ::= hh:mm:ss{(.|,)s+} # one or more digits in
# the fractional second
<TIME FLEXIBLE> (Basic) ::= hhmmss{(.|,)s+} # one or more digits in the
# fractional second
<ZONE> ::= (+|-)hh{:}mm|Z # zone designator (':' is
# optional whether default
# or basic)

Summary of Supported ISO 8601 Duration Representations

The syntax description below summarizes the ISO 8601 string representations for durations supported by this component. Although not quoted (for readability), [.,PWDTHMS] are literal characters that can occur in ISO 8601 strings. The characters [wdhms] each denote a decimal digit, {} brackets optional elements, () is used for grouping, and | separates alternatives:

<Date Duration> ::= {w+W}{d+D}
<Time Duration> ::= T{h+H}{m+M}{s+{(.|,)s+}S} # must contain
# at least one
# optional field
# (i.e. "T" is
# not valid)
<Generated Duration> ::= P<Date Duration><Time Duration> # all values
# guaranteed to
# be less than
# their modulus
# (weeks can be
# be up to 14
# digits), and
# it must
# contain the
# seconds
# portion
<Parsed Duration> ::= P<Date Duration>{<Time Duration>} # must contain
# at least one
# optional field
# in <Date
# Duration> or
# must contain
# a <Time
# Duration>
# (i.e. "P" is
# not valid)

Usage

This section illustrates intended use of this component.

Example 1: Basic bdlt::Iso8601Util Usage

This example demonstrates basic use of one generate function and two parse functions.

First, we construct a few objects that are prerequisites for this and the following example:

const bdlt::Date date(2005, 1, 31); // 2005/01/31
const bdlt::Time time(8, 59, 59, 123); // 08:59:59.123
const int tzOffset = 240; // +04:00 (four hours west of UTC)

Then, we construct a bdlt::DatetimeTz object for which a corresponding ISO 8601-compliant string will be generated shortly:

const bdlt::DatetimeTz sourceDatetimeTz(bdlt::Datetime(date, time),
tzOffset);
Definition bdlt_datetimetz.h:308

For comparison with the ISO 8601 string generated below, note that streaming the value of sourceDatetimeTz to stdout:

bsl::cout << sourceDatetimeTz << bsl::endl;

produces:

31JAN2005_08:59:59.123000+0400

Next, we use a generate function to produce an ISO 8601-compliant string for sourceDatetimeTz, writing the output to a bsl::ostringstream, and assert that both the return value and the string that is produced are as expected:

const bsl::ostream& ret =
bdlt::Iso8601Util::generate(oss, sourceDatetimeTz);
assert(&oss == &ret);
const bsl::string iso8601 = oss.str();
assert(iso8601 == "2005-01-31T08:59:59.123+04:00");
Definition bslstl_ostringstream.h:175
void str(const StringType &value)
Definition bslstl_ostringstream.h:581
Definition bslstl_string.h:1281
static int generate(char *buffer, ssize_t bufferLength, const bsls::TimeInterval &object)
Definition bdlt_iso8601util.h:1967

For comparison, see the output that was produced by the streaming operator above.

Now, we parse the string that was just produced, loading the result of the parse into a second bdlt::DatetimeTz object, and assert that the parse was successful and that the target object has the same value as that of the original (i.e., sourceDatetimeTz):

bdlt::DatetimeTz targetDatetimeTz;
int rc = bdlt::Iso8601Util::parse(&targetDatetimeTz,
iso8601.c_str(),
static_cast<int>(iso8601.length()));
assert( 0 == rc);
assert(sourceDatetimeTz == targetDatetimeTz);
size_type length() const BSLS_KEYWORD_NOEXCEPT
Definition bslstl_string.h:6601
const CHAR_TYPE * c_str() const BSLS_KEYWORD_NOEXCEPT
Definition bslstl_string.h:6705
static int parse(bsls::TimeInterval *result, const char *string, ssize_t length)

Finally, we parse the iso8601 string a second time, this time loading the result into a bdlt::Datetime object (instead of a bdlt::DatetimeTz):

bdlt::Datetime targetDatetime;
rc = bdlt::Iso8601Util::parse(&targetDatetime,
iso8601.c_str(),
static_cast<int>(iso8601.length()));
assert( 0 == rc);
assert(sourceDatetimeTz.utcDatetime() == targetDatetime);

Note that this time the value of the target object has been converted to UTC.

Example 2: Configuring ISO 8601 String Generation

This example demonstrates use of a bdlt::Iso8601UtilConfiguration object to influence the format of the ISO 8601 strings that are generated by this component by passing that configuration object to generate. We also take this opportunity to illustrate the flavor of the generate functions that outputs to a char * buffer of a specified length.

First, we construct a bdlt::TimeTz object for which a corresponding ISO 8601-compliant string will be generated shortly:

const bdlt::TimeTz sourceTimeTz(time, tzOffset);
Definition bdlt_timetz.h:190

For comparison with the ISO 8601 string generated below, note that streaming the value of sourceTimeTz to stdout:

bsl::cout << sourceTimeTz << bsl::endl;

produces:

08:59:59.123+0400

Then, we construct the bdlt::Iso8601UtilConfiguration object that indicates how we would like to affect the generated output ISO 8601 string. In this case, we want to use , as the decimal sign (in fractional seconds) and omit the : in zone designators:

configuration.setOmitColonInZoneDesignator(true);
configuration.setUseCommaForDecimalSign(true);
Definition bdlt_iso8601utilconfiguration.h:225
void setOmitColonInZoneDesignator(bool value)
void setUseCommaForDecimalSign(bool value)

Next, we define the char * buffer that will be used to stored the generated string. A buffer of size bdlt::Iso8601Util::k_TIMETZ_STRLEN + 1 is large enough to hold any string generated by this component for a bdlt::TimeTz object, including a null terminator:

const int BUFLEN = bdlt::Iso8601Util::k_TIMETZ_STRLEN + 1;
char buffer[BUFLEN];
@ k_TIMETZ_STRLEN
Definition bdlt_iso8601util.h:754

Then, we use a generate function that accepts our configuration to produce an ISO 8601-compliant string for sourceTimeTz, this time writing the output to a char * buffer, and assert that both the return value and the string that is produced are as expected. Note that in comparing the return value against BUFLEN - 5 we account for the omission of the : from the zone designator, and also for the fact that, although a null terminator was generated, it is not included in the character count returned by generate. Also note that we use bsl::strcmp to compare the resulting string knowing that we supplied a buffer having sufficient capacity to accommodate a null terminator:

BUFLEN,
sourceTimeTz,
configuration);
assert(BUFLEN - 5 == rc);
assert( 0 == bsl::strcmp(buffer, "08:59:59,123+0400"));

For comparison, see the output that was produced by the streaming operator above.

Next, we parse the string that was just produced, loading the result of the parse into a second bdlt::TimeTz object, and assert that the parse was successful and that the target object has the same value as that of the original (i.e., sourceTimeTz). Note that BUFLEN - 5 is passed and not BUFLEN because the former indicates the correct number of characters in buffer that we wish to parse:

bdlt::TimeTz targetTimeTz;
rc = bdlt::Iso8601Util::parse(&targetTimeTz, buffer, BUFLEN - 5);
assert( 0 == rc);
assert(sourceTimeTz == targetTimeTz);

Then, we parse the string in buffer a second time, this time loading the result into a bdlt::Time object (instead of a bdlt::TimeTz):

bdlt::Time targetTime;
rc = bdlt::Iso8601Util::parse(&targetTime, buffer, BUFLEN - 5);
assert( 0 == rc);
assert(sourceTimeTz.utcTime() == targetTime);

Note that this time the value of the target object has been converted to UTC.

Finally, we modify the configuration to display the bdlt::TimeTz without fractional seconds:

BUFLEN,
sourceTimeTz,
configuration);
assert(BUFLEN - 9 == rc);
assert( 0 == bsl::strcmp(buffer, "08:59:59+0400"));
void setFractionalSecondPrecision(int value)

Macro Definition Documentation

◆ BDLT_ISO8601UTIL_DEPRECATE_GENERATE

#define BDLT_ISO8601UTIL_DEPRECATE_GENERATE
Value:
"generate", \
"use overload with GenerateConfiguration")
#define BSLS_DEPRECATE_FEATURE(UOR, FEATURE, MESSAGE)
Definition bsls_deprecatefeature.h:319

◆ BDLT_ISO8601UTIL_DEPRECATE_GENERATE_ORDER

#define BDLT_ISO8601UTIL_DEPRECATE_GENERATE_ORDER
Value:
"generate", \
"use overload with length before object")

◆ BDLT_ISO8601UTIL_DEPRECATE_GENERATERAW

#define BDLT_ISO8601UTIL_DEPRECATE_GENERATERAW
Value:
"generateRaw", \
"use overload with GenerateConfiguration")

◆ BDLT_ISO8601UTIL_DEPRECATE_PARSERELAXED

#define BDLT_ISO8601UTIL_DEPRECATE_PARSERELAXED
Value:
"parseRelaxed", \
"use parse with ParseConfiguration")