tango.time.ISO8601

License:

BSD style: see license.txt

Version:

Aug 2007: Initial release Feb 2008: Retooled

Author:

Matti Niemenmaa

This module is based on the ISO 8601:2004 standard, and has functions for parsing (almost) every date/time format specified therein. (The ones not supported are intervals, durations, and recurring intervals.)

Refer to the standard for a full description of the formats supported.

The functions (parseTime, parseDate, and parseDateAndTime) are overloaded into two different versions of each: one updates a given Time, and the other updates a given ExtendedDate struct. The purpose of this struct is to support more detailed information which the Time data type does not (and, given its simple integer nature, cannot) support.

Times with specified time zones are simply converted into UTC: this may lead to the date changing when only a time was parsed: e.g. "01:00+03" is the same as "22:00", except that when the former is parsed, one is subtracted from the day.

struct ExtendedDate [public] #
An extended date type, wrapping a Time together with some additional information.
DT val #
The Time value, containing the information it can.
int year() #
Returns the year part of the date: a value in the range [-1_000_000_000,-1] ∪ [1,999_999_999], where -1 is the year 1 BCE.
Do not use val.year directly unless you are absolutely sure that it is in the range a Time can hold (-10000 to 9999).
uint seconds() #
Returns the seconds part of the date: may be 60 if a leap second occurred. In such a case, val's seconds part is 59.
bool endOfDay() #
Whether the ISO 8601 representation of this hour is 24 or 00: whether this instant of midnight is to be considered the end of the previous day or the start of the next.
If the time of val is not exactly 00:00:00.000, this value is undefined.
size_t parseDate(T)(T[] src, ref DT dt) [public] #
size_t parseDate(T)(T[] src, ref FullDate fd, ubyte expanded = 0) [public] #
Parses a date in a format specified in ISO 8601:2004.
Returns the number of characters used to compose a valid date: 0 if no date can be composed.

Fields in dt will either be correct (e.g. months will be >= 1 and <= 12) or the default, which is 1 for year, month, and day, and 0 for all other fields. Unless one is absolutely sure that 0001-01-01 can never be encountered, one should check the return value to be sure that the parsing succeeded as expected.

A third parameter is available for the ExtendedDate version: this allows for parsing expanded year representations. The parameter is the number of extra year digits beyond four, and defaults to zero. It must be within the range [0,5]: this allows for a maximum year of 999 999 999, which should be enough for now.

When using expanded year representations, be careful to use ExtendedDate.year instead of the Time's year value.

Examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Time t;
ExtendedDate ed;

parseDate("19",             t);    // January 1st, 1900
parseDate("1970",           t);    // January 1st, 1970
parseDate("1970-02",        t);    // February 1st, 1970
parseDate("19700203",       t);    // February 3rd, 1970
parseDate("+19700203",     ed, 2); // March 1st, 197002
parseDate("-197002-04-01", ed, 2); // April 1st, -197003 (197003 BCE)
parseDate("00000101",       t);    // January 1st, -1 (1 BCE)
parseDate("1700-W14-2",     t);    // April 6th, 1700
parseDate("2008W01",        t);    // December 31st, 2007
parseDate("1987-221",       t);    // August 9th, 1987
parseDate("1234abcd",       t);    // January 1st, 1234; return value is 4
parseDate("12abcdef",       t);    // January 1st, 1200; return value is 2
parseDate("abcdefgh",       t);    // January 1st, 0001; return value is 0
size_t parseTime(T)(T[] src, ref DT dt) [public] #
size_t parseTime(T)(T[] src, ref FullDate fd) [public] #
Parses a time of day in a format specified in ISO 8601:2004.
Returns the number of characters used to compose a valid time: 0 if no time can be composed.

Fields in dt will either be correct or the default, which is 0 for all time-related fields. fields. Unless one is absolutely sure that midnight can never be encountered, one should check the return value to be sure that the parsing succeeded as expected.

Extra fields in ExtendedDate:

Seconds may be 60 if the hours and minutes are 23 and 59, as leap seconds are occasionally added to UTC time. A Time's seconds will be 59 in this case.

Hours may be 0 or 24: the latter marks the end of a day and the former the beginning, although they both refer to the same instant in time. A Time will be precisely 00:00 in either case.

Examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Time t;
ExtendedDate ed;

// ",000" omitted for clarity
parseTime("20",             t); // 20:00:00
parseTime("2004",           t); // 20:04:00
parseTime("20:04:06",       t); // 20:04:06
parseTime("16:49:30,001",   t); // 16:49:30,001
parseTime("16:49:30,1",     t); // 16:49:30,100
parseTime("16:49,4",        t); // 16:49:24
parseTime("23:59:60",      ed); // 23:59:60
parseTime("24:00:01",       t); // 00:00:00; return value is 5
parseTime("24:00:01",      ed); // 00:00:00; return value is 5; endOfDay
parseTime("30",             t); // 00:00:00; return value is 0
parseTime("21:32:43-12:34", t); // 10:06:43; day increased by one
size_t parseDateAndTime(T)(T[] src, ref DT dt) [public] #
size_t parseDateAndTime(T)(T[] src, ref FullDate fd) [public] #
Parses a combined date and time in a format specified in ISO 8601:2004.
Returns the number of characters used to compose a valid date and time. Zero is returned if a complete date and time cannot be extracted. In that case, the value of the resulting Time or ExtendedDate is undefined.

This function is stricter than just calling parseDate followed by

parseTime:

there are no allowances for expanded years or reduced dates (two-digit years), and separator usage must be consistent.

Although the standard allows for omitting the T between the date and the time, this function requires it.

Examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Time t;

// January 1st, 2008 00:01:00
parseDateAndTime("2007-12-31T23:01-01", t);

// April 12th, 1985 23:50:30,042
parseDateAndTime("1985W155T235030,042", t);

// Invalid time: returns zero
parseDateAndTime("1902-03-04T10:1a", t);

// Separating T omitted: returns zero
parseDateAndTime("1985-04-1210:15:30+04:00", t);

// Inconsistent separators: all return zero
parseDateAndTime("200512-01T10:02",          t);
parseDateAndTime("1985-04-12T10:15:30+0400", t);
parseDateAndTime("1902-03-04T050607",        t);