123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587 |
|
/*******************************************************************************
Copyright: Copyright (c) 2007-2008 Matti Niemenmaa.
All rights reserved
License: BSD style: $(LICENSE)
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.
*******************************************************************************/
module tango.time.ISO8601;
public import tango.time.Time;
public import tango.time.chrono.Gregorian;
import tango.core.Exception : IllegalArgumentException;
import tango.math.Math : min;
private alias Time DT;
private alias ExtendedDate FullDate;
/** An extended date type, wrapping a Time together with some additional
* information. */
public struct ExtendedDate {
/** The Time value, containing the information it can. */
DT val;
private 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).
*/
@property const int year()
out(val) {
assert ( (val >= -1_000_000_000 && val <= -1)
|| (val >= 1 && val <= 999_999_999));
} body {
if (year_)
return year_;
auto era = Gregorian.generic.getEra(val);
if (era == Gregorian.AD_ERA)
return Gregorian.generic.getYear(val);
else
return -Gregorian.generic.getYear(val);
}
// y may be zero: if so, it refers to the year 1 BCE
@property private void year(int y) {
if (DTyear(y)) {
year_ = 0;
// getYear returns uint: be careful with promotion to unsigned
int toAdd = y - Gregorian.generic.getYear(val);
val = Gregorian.generic.addYears(val, toAdd);
} else
year_ = y < 0 ? y-1 : y;
}
private byte mask; // leap second and endofday
/** 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.
*/
@property const uint seconds() { return val.time.seconds + ((mask >>> 0) & 1); }
alias seconds secs, second, sec;
/** 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.
*/
@property const bool endOfDay() { return 1 == ((mask >>> 1) & 1); }
private void setLeap () { mask |= 1 << 0; }
private void setEndOfDay() { mask |= 1 << 1; }
debug (Tango_ISO8601) private char[] toStr() {
return Stdout.layout.convert(
"{:d} and {:d}-{:d2}-{:d2} :: {:d2}:{:d2}:{:d2}.{:d3} and {:d2}, {}",
year_, years(*this), months(*this), days(*this),
hours(*this), mins(*this), .secs(*this), ms(*this),
this.seconds, this.endOfDay);
}
}
/** 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:
* ---
* 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
* ---
*/
public size_t parseDate(T)(T[] src, ref DT dt) {
auto fd = FullDate(dt);
auto ret = parseDate(src, fd);
dt = fd.val;
return ret;
}
/** ditto */
public size_t parseDate(T)(T[] src, ref FullDate fd, ubyte expanded = 0) {
ubyte dummy = void;
T* p = src.ptr;
return doIso8601Date(p, src, fd, expanded, dummy);
}
private size_t doIso8601Date(T)(
ref T* p, T[] src,
ref FullDate fd,
ubyte expanded,
out ubyte separators
) {
if (expanded > 5)
throw new IllegalArgumentException(
"ISO8601 :: year expanded by more than 5 digits does not fit in int");
size_t eaten() { return p - src.ptr; }
size_t remaining() { return src.length - eaten(); }
bool done(const(T[]) s) { return .done(eaten(), src.length, p, s); }
if (!parseYear(p, src.length, expanded, fd))
return 0;
auto onlyYear = eaten();
// /([+-]Y{expanded})?(YYYY|YY)/
if (done("-0123W"))
return onlyYear;
if (accept(p, '-')) {
separators = YES;
if (remaining() == 0)
return eaten() - 1;
}
if (accept(p, 'W')) {
T* p2 = p;
int i = parseInt(p, min(cast(size_t)3, remaining()));
if (i)
{
if (p - p2 == 2) {
// (year)-Www
if (done("-")) {
if (getMonthAndDayFromWeek(fd, i))
return eaten();
// (year)-Www-D
} else if (demand(p, '-')) {
if (remaining() == 0)
return eaten() - 1;
if (separators == NO) {
// (year)Www after all
if (getMonthAndDayFromWeek(fd, i))
return eaten() - 1;
} else if (getMonthAndDayFromWeek(fd, i, *p++ - '0'))
return eaten();
}
} else if (p - p2 == 3) {
// (year)WwwD, i == wwD
if (separators == YES) {
// (year)-Www after all
if (getMonthAndDayFromWeek(fd, i / 10))
return eaten() - 1;
} else if (getMonthAndDayFromWeek(fd, i / 10, i % 10))
return eaten();
}
}
return onlyYear;
}
// next up, MM or MM[-]DD or DDD
T* p2 = p;
int i = parseInt(p, remaining());
if (!i)
return onlyYear;
switch (p - p2) {
case 2:
// MM or MM-DD
if (i >= 1 && i <= 12)
addMonths(fd, i);
else
return onlyYear;
auto onlyMonth = eaten();
// (year)-MM
if (done("-") || !demand(p, '-') || separators == NO)
return onlyMonth;
int day = parseInt(p, min(cast(size_t)2, remaining()));
// (year)-MM-DD
if (day && day <= daysPerMonth(months(fd), fd.year))
addDays(fd, day);
else
return onlyMonth;
break;
case 4:
// e.g. 20010203, i = 203 now
int month = i / 100;
int day = i % 100;
if (separators == YES) {
// Don't accept the day: behave as though we only got the month
p -= 2;
i = month;
goto case 2;
}
// (year)MMDD
if (
month >= 1 && month <= 12 &&
day >= 1 && day <= daysPerMonth(month, fd.year)
) {
addMonths(fd, month);
addDays (fd, day);
} else
return onlyYear;
break;
case 3:
// (year)-DDD
// i is the ordinal of the day within the year
if (i > 365 + isLeapYear(fd.year))
return onlyYear;
addDays(fd, i);
break;
default: break;
}
return eaten();
}
/** 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:
* ---
* 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
* ---
*/
public size_t parseTime(T)(T[] src, ref DT dt) {
auto fd = FullDate(dt);
auto ret = parseTime(src, fd);
dt = fd.val;
return ret;
}
/** ditto */
public size_t parseTime(T)(T[] src, ref FullDate fd) {
bool dummy = void;
T* p = src.ptr;
return doIso8601Time(p, src, fd, WHATEVER, dummy);
}
// separators
private enum : ubyte { NO = 0, YES = 1, WHATEVER }
// bothValid is used only to get parseDateAndTime() to catch errors correctly
private size_t doIso8601Time(T)(
ref T* p, T[] src,
ref FullDate fd,
ubyte separators,
out bool bothValid
) {
size_t eaten() { return p - src.ptr; }
size_t remaining() { return src.length - eaten(); }
bool done(const(T[]) s) { return .done(eaten(), src.length, p, s); }
bool checkColon() { return .checkColon(p, separators); }
byte getTimeZone() { return .getTimeZone(p, remaining(), fd, separators, &done); }
if (separators == WHATEVER)
accept(p, 'T');
int hour = void;
if (parseInt(p, min(cast(size_t)2, remaining()), hour) != 2 || hour > 24)
return 0;
if (hour == 24)
fd.setEndOfDay();
// Add the hours even if endOfDay: the day should be the next day, not the
// previous
addHours(fd, hour);
auto onlyHour = eaten();
// hh
if (done("+,-.012345:"))
return onlyHour;
switch (getDecimal(p, remaining(), fd, HOUR)) {
case NOTFOUND: break;
case FOUND:
auto onlyDecimal = eaten();
if (getTimeZone() == BAD)
return onlyDecimal;
// /hh,h+/
return eaten();
case BAD: return onlyHour;
default: assert (false);
}
switch (getTimeZone()) {
case NOTFOUND: break;
case FOUND: return eaten();
case BAD: return onlyHour;
default: assert (false);
}
if (!checkColon())
return onlyHour;
int mins = void;
if (
parseInt(p, min(cast(size_t)2, remaining()), mins) != 2 ||
mins > 59 ||
// end of day is only for 24:00:00
(fd.endOfDay && mins != 0)
)
return onlyHour;
addMins(fd, mins);
auto onlyMinute = eaten();
// hh:mm
if (done("+,-.0123456:")) {
bothValid = true;
return onlyMinute;
}
switch (getDecimal(p, remaining(), fd, MINUTE)) {
case NOTFOUND: break;
case FOUND:
auto onlyDecimal = eaten();
if (getTimeZone() == BAD)
return onlyDecimal;
// /hh:mm,m+/
bothValid = true;
return eaten();
case BAD: return onlyMinute;
default: assert (false);
}
switch (getTimeZone()) {
case NOTFOUND: break;
case FOUND: bothValid = true; return eaten();
case BAD: return onlyMinute;
default: assert (false);
}
if (!checkColon())
return onlyMinute;
int sec = void;
if (
parseInt(p, min(cast(size_t)2, remaining()), sec) != 2 ||
sec > 60 ||
(fd.endOfDay && sec != 0)
)
return onlyMinute;
if (sec == 60) {
if (hours(fd) != 23 && .mins(fd) != 59)
return onlyMinute;
fd.setLeap();
--sec;
}
addSecs(fd, sec);
auto onlySecond = eaten();
// hh:mm:ss
if (done("+,-.Z")) {
bothValid = true;
return onlySecond;
}
switch (getDecimal(p, remaining(), fd, SECOND)) {
case NOTFOUND: break;
case FOUND:
auto onlyDecimal = eaten();
if (getTimeZone() == BAD)
return onlyDecimal;
// /hh:mm:ss,s+/
bothValid = true;
return eaten();
case BAD: return onlySecond;
default: assert (false);
}
if (getTimeZone() == BAD)
return onlySecond;
else {
bothValid = true;
return eaten(); // hh:mm:ss with timezone
}
}
/** 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:
* ---
* 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);
* ---
*/
public size_t parseDateAndTime(T)(T[] src, ref DT dt) {
FullDate fd;
auto ret = parseDateAndTime(src, fd);
dt = fd.val;
return ret;
}
/** ditto */
public size_t parseDateAndTime(T)(T[] src, ref FullDate fd) {
T* p = src.ptr;
ubyte sep;
bool bothValid = false;
if (
doIso8601Date(p, src, fd, cast(ubyte)0, sep) &&
// by mutual agreement this T may be omitted
// but this is just a convenience method for date+time anyway
src.length - (p - src.ptr) >= 1 &&
demand(p, 'T') &&
doIso8601Time(p, src, fd, sep, bothValid) &&
bothValid
)
return p - src.ptr;
else
return 0;
}
/+ +++++++++++++++++++++++++++++++++++++++ +\
Privates used by date
\+ +++++++++++++++++++++++++++++++++++++++ +/
private:
// /([+-]Y{expanded})?(YYYY|YY)/
bool parseYear(T)(ref T* p, size_t len, ubyte expanded, ref FullDate fd) {
int year = void;
bool doParse() {
T* p2 = p;
if (!parseInt(p, min(cast(size_t)(expanded + 4), len), year))
return false;
// it's Y{expanded}YY, Y{expanded}YYYY, or unacceptable
if (p - p2 - expanded == 2)
year *= 100;
else if (p - p2 - expanded != 4)
return false;
return true;
}
if (accept(p, '-')) {
if (!doParse() || year < 0)
return false;
year = -year;
} else {
accept(p, '+');
if (!doParse() || year < 0)
return false;
}
fd.year = year;
return true;
}
// find the month and day given a calendar week and the day of the week
// uses fd.year for leap year calculations
// returns false if week and fd.year are incompatible
bool getMonthAndDayFromWeek(ref FullDate fd, int week, int day = 1) {
if (week < 1 || week > 53 || day < 1 || day > 7)
return false;
int year = fd.year;
// only years starting with Thursday and leap years starting with Wednesday
// have 53 weeks
if (week == 53) {
int startingDay = dayOfWeek(year, 1, 1);
if (!(startingDay == 4 || (isLeapYear(year) && startingDay == 3)))
return false;
}
// XXX
// days since year-01-04, plus 4 (?)...
/* This is a bit scary, actually: I have ***no idea why this works***. I
* came up with this completely by accident. It seems to work though -
* unless it fails in some (very) obscure case which isn't represented in
* the unit tests.
*/
addDays(fd, 7*(week - 1) + day - dayOfWeek(year, 1, 4) + 4);
return true;
}
bool isLeapYear(int year) {
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
}
int dayOfWeek(int year, int month, int day)
in {
assert (month >= 1 && month <= 12);
assert (day >= 1 && day <= 31);
} out(result) {
assert (result >= 1 && result <= 7);
} body {
uint era = erafy(year);
int result =
Gregorian.generic.getDayOfWeek(
Gregorian.generic.toTime(year, month, day, 0, 0, 0, 0, era));
if (result == Gregorian.DayOfWeek.Sunday)
return 7;
else
return result;
}
/+ +++++++++++++++++++++++++++++++++++++++ +\
Privates used by time
\+ +++++++++++++++++++++++++++++++++++++++ +/
enum : ubyte { HOUR, MINUTE, SECOND }
enum : byte { BAD, FOUND, NOTFOUND }
bool checkColon(T)(ref T* p, ref ubyte separators) {
ubyte foundSep = accept(p, ':') ? YES : NO;
if (foundSep != separators) {
if (separators == WHATEVER)
separators = foundSep;
else
return false;
}
return true;
}
byte getDecimal(T)(ref T* p, size_t len, ref FullDate fd, ubyte which) {
if (!(accept(p, ',') || accept(p, '.')))
return NOTFOUND;
T* p2 = p;
int i = void;
auto iLen = parseInt(p, len-1, i);
if (
iLen == 0 ||
// if i is 0, must have at least 3 digits
// ... or at least that's what I think the standard means
// when it says "[i]f the magnitude of the number is less
// than unity, the decimal sign shall be preceded by two
// zeros"...
// surely that should read "followed" and not "preceded"
(i == 0 && iLen < 3)
)
return BAD;
// 10 to the power of (iLen - 1)
int pow = 1;
while (--iLen)
pow *= 10;
switch (which) {
case HOUR:
addMins(fd, 6 * i / pow);
addSecs(fd, 6 * i % pow);
break;
case MINUTE:
addSecs(fd, 6 * i / pow);
addMs (fd, 6000 * i / pow % 1000);
break;
case SECOND:
addMs(fd, 100 * i / pow);
break;
default: assert (false);
}
return FOUND;
}
// the DT is always UTC, so this just adds the offset to the date fields
// another option would be to add time zone fields to DT and have this fill them
byte getTimeZone(T)(ref const(T)* p, size_t len, ref FullDate fd, ubyte separators, bool delegate(const(T[])) done) {
bool checkColon() { return .checkColon(p, separators); }
if (len == 0)
return NOTFOUND;
auto p0 = p;
if (accept(p, 'Z'))
return FOUND;
int factor = -1;
if (accept(p, '-'))
factor = 1;
else if (!accept(p, '+'))
return NOTFOUND;
int hour = void;
if (parseInt(p, min(cast(size_t)2, len-1), hour) != 2 || hour > 12 || (hour == 0 && factor == 1))
return BAD;
addHours(fd, factor * hour);
// if we go forward in time to midnight, it's 24:00
if (
factor > 0 &&
hours(fd) == 0 && mins(fd) == 0 && secs(fd) == 0 && ms(fd) == 0
)
fd.setEndOfDay();
if (done("012345:"))
return FOUND;
auto afterHours = p;
if (!checkColon())
return BAD;
int minute = void;
if (parseInt(p, min(cast(size_t)2, len - (p-p0)), minute) != 2) {
// The hours were valid even if the minutes weren't
p = afterHours;
return FOUND;
}
addMins(fd, factor * minute);
// as above
if (
factor > 0 &&
hours(fd) == 0 && mins(fd) == 0 && secs(fd) == 0 && ms(fd) == 0
)
fd.setEndOfDay();
return FOUND;
}
/+ +++++++++++++++++++++++++++++++++++++++ +\
Privates used by both date and time
\+ +++++++++++++++++++++++++++++++++++++++ +/
bool accept(T)(ref T* p, char c) {
if (*p == c) {
++p;
return true;
}
return false;
}
bool demand(T)(ref T* p, char c) {
return (*p++ == c);
}
bool done(T)(size_t eaten, size_t srcLen, const(T*) p, const(T[]) s) {
if (eaten == srcLen)
return true;
// s is the array of characters which may come next
// (i.e. which *p may be)
// sorted in ascending order
T t = *p;
foreach (c; s) {
if (t < c)
return true;
else if (t == c)
break;
}
return false;
}
int daysPerMonth(int month, int year) {
uint era = erafy(year);
return Gregorian.generic.getDaysInMonth(year, month, era);
}
uint erafy(ref int year) {
if (year < 0) {
year *= -1;
return Gregorian.BC_ERA;
} else
return Gregorian.AD_ERA;
}
/+ +++++++++++++++++++++++++++++++++++++++ +\
Extract an integer from the input, accept no more than max digits
\+ +++++++++++++++++++++++++++++++++++++++ +/
// note: code relies on these always being positive, failing if *p == '-'
int parseInt(T)(ref T* p, size_t max) {
size_t i = 0;
int value = 0;
while (i < max && p[i] >= '0' && p[i] <= '9')
value = value * 10 + p[i++] - '0';
p += i;
return value;
}
// ... and return the amount of digits processed
size_t parseInt(T)(ref T* p, size_t max, out int i) {
T* p2 = p;
i = parseInt(p, max);
return p - p2;
}
/+ +++++++++++++++++++++++++++++++++++++++ +\
Helpers for DT/FullDate manipulation
\+ +++++++++++++++++++++++++++++++++++++++ +/
// as documented in tango.time.Time
bool DTyear(int year) { return year >= -10000 && year <= 9999; }
void addMonths(ref FullDate d, int n) { d.val = Gregorian.generic.addMonths(d.val, n-1); } // -1 due to initial being 1
void addDays (ref FullDate d, int n) { d.val += TimeSpan.fromDays (n-1); } // ditto
void addHours (ref FullDate d, int n) { d.val += TimeSpan.fromHours (n); }
void addMins (ref FullDate d, int n) { d.val += TimeSpan.fromMinutes(n); }
void addSecs (ref FullDate d, int n) { d.val += TimeSpan.fromSeconds(n); }
void addMs (ref FullDate d, int n) { d.val += TimeSpan.fromMillis (n); }
// years and secs always just get the DT value
int years (const(FullDate) d) { return Gregorian.generic.getYear (d.val); }
int months(const(FullDate) d) { return Gregorian.generic.getMonth (d.val); }
int days (const(FullDate) d) { return Gregorian.generic.getDayOfMonth(d.val); }
int hours (const(FullDate) d) { return d.val.time.hours; }
int mins (const(FullDate) d) { return d.val.time.minutes; }
int secs (const(FullDate) d) { return d.val.time.seconds; }
int ms (const(FullDate) d) { return d.val.time.millis; }
////////////////////
// Unit tests
debug (UnitTest) {
// void main() {}
debug (Tango_ISO8601_Valgrind) import tango.stdc.stdlib : malloc, free;
unittest {
FullDate fd;
// date
size_t d(const(char[]) s, ubyte e = 0) {
fd = fd.init;
return parseDate(s, fd, e);
}
auto
INIT_YEAR = years (FullDate.init),
INIT_MONTH = months(FullDate.init),
INIT_DAY = days (FullDate.init);
assert (d("20abc") == 2);
assert (years(fd) == 2000);
assert (d("2004") == 4);
assert (years(fd) == 2004);
assert (d("+0019", 2) == 5);
assert (years(fd) == 1900);
assert (d("+111985", 2) == 7);
assert (years(fd) == INIT_YEAR);
assert (fd.year == 111985);
assert (d("+111985", 1) == 6);
assert (years(fd) == INIT_YEAR);
assert (fd.year == 11198);
assert (d("+111985", 3) == 0);
assert (years(fd) == INIT_YEAR);
assert (fd.year == INIT_YEAR);
assert (d("+111985", 4) == 7);
assert (years(fd) == INIT_YEAR);
assert (fd.year == 11198500);
assert (d("-111985", 5) == 0);
assert (years(fd) == INIT_YEAR);
assert (fd.year == INIT_YEAR);
assert (d("+999999999", 5) == 10);
assert (years(fd) == INIT_YEAR);
assert (fd.year == 999_999_999);
try {
d("+10000000000", 6);
assert (false);
} catch (IllegalArgumentException) {
assert (years(fd) == INIT_YEAR);
assert (fd.year == INIT_YEAR);
}
assert (d("-999999999", 5) == 10);
assert (years(fd) == INIT_YEAR);
assert (fd.year == -1_000_000_000);
assert (d("0001") == 4);
assert (years(fd) == 1);
assert (fd.year == 1);
assert (d("0000") == 4);
assert (fd.year == -1);
assert (d("-0001") == 5);
assert (fd.year == -2);
assert (d("abc") == 0);
assert (years(fd) == INIT_YEAR);
assert (fd.year == INIT_YEAR);
assert (d("abc123") == 0);
assert (years(fd) == INIT_YEAR);
assert (fd.year == INIT_YEAR);
assert (d("2007-08") == 7);
assert (years(fd) == 2007);
assert (months(fd) == 8);
assert (d("+001985-04", 2) == 10);
assert (years(fd) == 1985);
assert (months(fd) == 4);
assert (d("2007-08-07") == 10);
assert (years(fd) == 2007);
assert (months(fd) == 8);
assert (days(fd) == 7);
assert (d("2008-20-30") == 4);
assert (years(fd) == 2008);
assert (months(fd) == INIT_MONTH);
assert (d("2007-02-30") == 7);
assert (years(fd) == 2007);
assert (months(fd) == 2);
assert (d("20060708") == 8);
assert (years(fd) == 2006);
assert (months(fd) == 7);
assert (days(fd) == 8);
assert (d("19953080") == 4);
assert (years(fd) == 1995);
assert (months(fd) == INIT_MONTH);
assert (d("2007-0201") == 7);
assert (years(fd) == 2007);
assert (months(fd) == 2);
assert (d("200702-01") == 6);
assert (years(fd) == 2007);
assert (months(fd) == 2);
assert (d("+001985-04-12", 2) == 13);
assert (years(fd) == 1985);
assert (fd.year == 1985);
assert (months(fd) == 4);
assert (days(fd) == 12);
assert (d("-0123450607", 2) == 11);
assert (years(fd) == INIT_YEAR);
assert (fd.year == -12346);
assert (months(fd) == 6);
assert (days(fd) == 7);
assert (d("1985W15") == 7);
assert (years(fd) == 1985);
assert (months(fd) == 4);
assert (days(fd) == 8);
assert (d("2008-W01") == 8);
assert (years(fd) == 2007);
assert (months(fd) == 12);
assert (days(fd) == 31);
assert (d("2008-W012") == 8);
assert (years(fd) == 2007);
assert (months(fd) == 12);
assert (days(fd) == 31);
assert (d("2008W01-2") == 7);
assert (years(fd) == 2007);
assert (months(fd) == 12);
assert (days(fd) == 31);
assert (d("2008-W01-2") == 10);
assert (years(fd) == 2008);
assert (months(fd) == 1);
assert (days(fd) == 1);
assert (d("2009-W53-4") == 10);
assert (years(fd) == 2009);
assert (months(fd) == 12);
assert (days(fd) == 31);
assert (d("2009-W01-1") == 10);
assert (years(fd) == 2008);
assert (months(fd) == 12);
assert (days(fd) == 29);
assert (d("2009W537") == 8);
assert (years(fd) == 2010);
assert (months(fd) == 1);
assert (days(fd) == 3);
assert (d("2010W537") == 4);
assert (years(fd) == 2010);
assert (months(fd) == INIT_MONTH);
assert (d("2009-W01-3") == 10);
assert (years(fd) == 2008);
assert (months(fd) == 12);
assert (days(fd) == 31);
assert (d("2009-W01-4") == 10);
assert (years(fd) == 2009);
assert (months(fd) == 1);
assert (days(fd) == 1);
assert (d("2004-W53-6") == 10);
assert (years(fd) == 2005);
assert (months(fd) == 1);
assert (days(fd) == 1);
assert (d("2004-W53-7") == 10);
assert (years(fd) == 2005);
assert (months(fd) == 1);
assert (days(fd) == 2);
assert (d("2005-W52-6") == 10);
assert (years(fd) == 2005);
assert (months(fd) == 12);
assert (days(fd) == 31);
assert (d("2007-W01-1") == 10);
assert (years(fd) == 2007);
assert (months(fd) == 1);
assert (days(fd) == 1);
assert (d("1000-W07-7") == 10);
assert (years(fd) == 1000);
assert (months(fd) == 2);
assert (days(fd) == 16);
assert (d("1500-W11-1") == 10);
assert (years(fd) == 1500);
assert (months(fd) == 3);
assert (days(fd) == 12);
assert (d("1700-W14-2") == 10);
assert (years(fd) == 1700);
assert (months(fd) == 4);
assert (days(fd) == 6);
assert (d("1800-W19-3") == 10);
assert (years(fd) == 1800);
assert (months(fd) == 5);
assert (days(fd) == 7);
assert (d("1900-W25-4") == 10);
assert (years(fd) == 1900);
assert (months(fd) == 6);
assert (days(fd) == 21);
assert (d("0900-W27-5") == 10);
assert (years(fd) == 900);
assert (months(fd) == 7);
assert (days(fd) == 9);
assert (d("0800-W33-6") == 10);
assert (years(fd) == 800);
assert (months(fd) == 8);
assert (days(fd) == 19);
assert (d("0700-W37-7") == 10);
assert (years(fd) == 700);
assert (months(fd) == 9);
assert (days(fd) == 16);
assert (d("0600-W41-4") == 10);
assert (years(fd) == 600);
assert (months(fd) == 10);
assert (days(fd) == 9);
assert (d("0500-W45-7") == 10);
assert (years(fd) == 500);
assert (months(fd) == 11);
assert (days(fd) == 14);
assert (d("2000-W55") == 4);
assert (years(fd) == 2000);
assert (d("1980-002") == 8);
assert (years(fd) == 1980);
assert (months(fd) == 1);
assert (days(fd) == 2);
assert (d("1981-034") == 8);
assert (years(fd) == 1981);
assert (months(fd) == 2);
assert (days(fd) == 3);
assert (d("1982-063") == 8);
assert (years(fd) == 1982);
assert (months(fd) == 3);
assert (days(fd) == 4);
assert (d("1983-095") == 8);
assert (years(fd) == 1983);
assert (months(fd) == 4);
assert (days(fd) == 5);
assert (d("1984-127") == 8);
assert (years(fd) == 1984);
assert (months(fd) == 5);
assert (days(fd) == 6);
assert (d("1985-158") == 8);
assert (years(fd) == 1985);
assert (months(fd) == 6);
assert (days(fd) == 7);
assert (d("1986-189") == 8);
assert (years(fd) == 1986);
assert (months(fd) == 7);
assert (days(fd) == 8);
assert (d("1987-221") == 8);
assert (years(fd) == 1987);
assert (months(fd) == 8);
assert (days(fd) == 9);
assert (d("1988-254") == 8);
assert (years(fd) == 1988);
assert (months(fd) == 9);
assert (days(fd) == 10);
assert (d("1989-284") == 8);
assert (years(fd) == 1989);
assert (months(fd) == 10);
assert (days(fd) == 11);
assert (d("1990316") == 7);
assert (years(fd) == 1990);
assert (months(fd) == 11);
assert (days(fd) == 12);
assert (d("1991-347") == 8);
assert (years(fd) == 1991);
assert (months(fd) == 12);
assert (days(fd) == 13);
assert (d("1992-000") == 4);
assert (years(fd) == 1992);
assert (d("1993-370") == 4);
assert (years(fd) == 1993);
// time
size_t t(const(char[]) s) {
fd = fd.init;
return parseTime(s, fd);
}
assert (t("20") == 2);
assert (hours(fd) == 20);
assert (mins(fd) == 0);
assert (secs(fd) == 0);
assert (t("30") == 0);
assert (t("T15") == 3);
assert (hours(fd) == 15);
assert (mins(fd) == 0);
assert (secs(fd) == 0);
assert (t("T1") == 0);
assert (t("T") == 0);
assert (t("2004") == 4);
assert (hours(fd) == 20);
assert (mins(fd) == 4);
assert (secs(fd) == 0);
assert (t("200406") == 6);
assert (hours(fd) == 20);
assert (mins(fd) == 4);
assert (secs(fd) == 6);
assert (t("24:00") == 5);
assert (fd.endOfDay);
assert (days(fd) == INIT_DAY + 1);
assert (hours(fd) == 0);
assert (mins(fd) == 0);
assert (secs(fd) == 0);
assert (t("00:00") == 5);
assert (hours(fd) == 0);
assert (mins(fd) == 0);
assert (secs(fd) == 0);
assert (t("23:59:60") == 8);
assert (hours(fd) == 23);
assert (mins(fd) == 59);
assert (secs(fd) == 59);
assert (fd.seconds == 60);
assert (t("12:3456") == 5);
assert (hours(fd) == 12);
assert (mins(fd) == 34);
assert (t("1234:56") == 4);
assert (hours(fd) == 12);
assert (mins(fd) == 34);
assert (t("16:49:30,001") == 12);
assert (hours(fd) == 16);
assert (mins(fd) == 49);
assert (secs(fd) == 30);
assert (ms(fd) == 1);
assert (t("15:48:29,1") == 10);
assert (hours(fd) == 15);
assert (mins(fd) == 48);
assert (secs(fd) == 29);
assert (ms(fd) == 100);
assert (t("02:10:34,a") == 8);
assert (hours(fd) == 2);
assert (mins(fd) == 10);
assert (secs(fd) == 34);
assert (t("14:50,5") == 7);
assert (hours(fd) == 14);
assert (mins(fd) == 50);
assert (secs(fd) == 30);
assert (t("1540,4") == 6);
assert (hours(fd) == 15);
assert (mins(fd) == 40);
assert (secs(fd) == 24);
assert (t("1250,") == 4);
assert (hours(fd) == 12);
assert (mins(fd) == 50);
assert (t("14,5") == 4);
assert (hours(fd) == 14);
assert (mins(fd) == 30);
assert (t("12,") == 2);
assert (hours(fd) == 12);
assert (mins(fd) == 0);
assert (t("24:00:01") == 5);
assert (fd.endOfDay);
assert (hours(fd) == 0);
assert (mins(fd) == 0);
assert (secs(fd) == 0);
assert (t("12:34+:56") == 5);
assert (hours(fd) == 12);
assert (mins(fd) == 34);
assert (secs(fd) == 0);
// time zones
assert (t("14:45:15Z") == 9);
assert (hours(fd) == 14);
assert (mins(fd) == 45);
assert (secs(fd) == 15);
assert (t("23Z") == 3);
assert (hours(fd) == 23);
assert (mins(fd) == 0);
assert (secs(fd) == 0);
assert (t("21:32:43-12:34") == 14);
assert (days(fd) == INIT_DAY + 1);
assert (hours(fd) == 10);
assert (mins(fd) == 6);
assert (secs(fd) == 43);
assert (t("12:34,5+00:00") == 13);
assert (hours(fd) == 12);
assert (mins(fd) == 34);
assert (secs(fd) == 30);
assert (t("03:04+07") == 8);
assert (hours(fd) == 20);
assert (mins(fd) == 4);
assert (secs(fd) == 0);
assert (t("11,5+") == 4);
assert (hours(fd) == 11);
assert (mins(fd) == 30);
assert (t("07-") == 2);
assert (hours(fd) == 7);
assert (t("06:12,7-") == 7);
assert (hours(fd) == 6);
assert (mins(fd) == 12);
assert (secs(fd) == 42);
assert (t("050403,2+") == 8);
assert (hours(fd) == 5);
assert (mins(fd) == 4);
assert (secs(fd) == 3);
assert (ms(fd) == 200);
assert (t("061656-") == 6);
assert (hours(fd) == 6);
assert (mins(fd) == 16);
assert (secs(fd) == 56);
// date and time together
size_t b(const(char[]) s) {
fd = fd.init;
return parseDateAndTime(s, fd);
}
assert (b("2007-08-09T12:34:56") == 19);
assert (years(fd) == 2007);
assert (months(fd) == 8);
assert (days(fd) == 9);
assert (hours(fd) == 12);
assert (mins(fd) == 34);
assert (secs(fd) == 56);
assert (b("1985W155T235030,768") == 19);
assert (years(fd) == 1985);
assert (months(fd) == 4);
assert (days(fd) == 12);
assert (hours(fd) == 23);
assert (mins(fd) == 50);
assert (secs(fd) == 30);
assert (ms(fd) == 768);
// time zones
assert (b("2009-08-07T01:02:03Z") == 20);
assert (years(fd) == 2009);
assert (months(fd) == 8);
assert (days(fd) == 7);
assert (hours(fd) == 1);
assert (mins(fd) == 2);
assert (secs(fd) == 3);
assert (b("2007-08-09T03:02,5+04:56") == 24);
assert (years(fd) == 2007);
assert (months(fd) == 8);
assert (days(fd) == 8);
assert (hours(fd) == 22);
assert (mins(fd) == 6);
assert (secs(fd) == 30);
assert (b("20000228T2330-01") == 16);
assert (years(fd) == 2000);
assert (months(fd) == 2);
assert (days(fd) == 29);
assert (hours(fd) == 0);
assert (mins(fd) == 30);
assert (secs(fd) == 0);
assert (b("2007-01-01T00:00+01") == 19);
assert (years(fd) == 2006);
assert (months(fd) == 12);
assert (days(fd) == 31);
assert (hours(fd) == 23);
assert (mins(fd) == 0);
assert (secs(fd) == 0);
assert (b("2007-12-31T23:00-01") == 19);
assert (fd.endOfDay);
assert (years(fd) == 2008);
assert (months(fd) == 1);
assert (days(fd) == 1);
assert (hours(fd) == 0);
assert (mins(fd) == 0);
assert (secs(fd) == 0);
assert (b("2007-12-31T23:01-01") == 19);
assert (!fd.endOfDay);
assert (years(fd) == 2008);
assert (months(fd) == 1);
assert (days(fd) == 1);
assert (hours(fd) == 0);
assert (mins(fd) == 1);
assert (secs(fd) == 0);
assert (b("1902-03-04T1a") == 0);
assert (b("1902-03-04T10:aa") == 0);
assert (b("1902-03-04T10:1aa") == 0);
assert (b("200512-01T10:02") == 0);
assert (b("1985-04-1210:15:30+04:00") == 0);
assert (b("1985-04-12T10:15:30+0400") == 0);
assert (b("19020304T05:06:07") == 0);
assert (b("1902-03-04T050607") == 0);
assert (b("19020304T05:06:07abcd") == 0);
assert (b("1902-03-04T050607abcd") == 0);
assert (b("1985-04-12T10:15:30-05:4") == 22);
assert (years(fd) == 1985);
assert (months(fd) == 4);
assert (days(fd) == 12);
assert (hours(fd) == 15);
assert (mins(fd) == 15);
assert (secs(fd) == 30);
assert (b("2009-04-13T23:00-01") == 19);
assert (fd.endOfDay);
assert (years(fd) == 2009);
assert (months(fd) == 4);
assert (days(fd) == 14);
assert (hours(fd) == 0);
assert (mins(fd) == 0);
assert (secs(fd) == 0);
assert (b("2009-04-13T24:00Z") == 17);
assert (fd.endOfDay);
assert (years(fd) == 2009);
assert (months(fd) == 4);
assert (days(fd) == 14);
assert (hours(fd) == 0);
assert (mins(fd) == 0);
assert (secs(fd) == 0);
// unimplemented: intervals, durations, recurring intervals
debug (Tango_ISO8601_Valgrind) {
size_t valgrind(size_t delegate(const(char[])) f, const(char[]) s) {
auto p = cast(char*)malloc(s.length);
auto ps = p[0..s.length];
ps[] = s[];
auto result = f(ps);
free(p);
return result;
}
size_t vd(const(char[]) s) {
size_t date(const(char[]) ss) { return d(ss); }
return valgrind(&date, s);
}
size_t vt(const(char[]) s) { return valgrind(&t, s); }
size_t vb(const(char[]) s) { return valgrind(&b, s); }
assert (vd("1") == 0);
assert (vd("19") == 2);
assert (vd("199") == 0);
assert (vd("1999") == 4);
assert (vd("1999-") == 4);
assert (vd("1999-W") == 4);
assert (vd("1999-W0") == 4);
assert (vd("1999-W01") == 8);
assert (vd("1999-W01-") == 8);
assert (vd("1999-W01-3") == 10);
assert (vd("1999W") == 4);
assert (vd("1999W0") == 4);
assert (vd("1999W01") == 7);
assert (vd("1999W01-") == 7);
assert (vd("1999W01-3") == 7);
assert (vd("1999W013") == 8);
assert (vd("1999-0") == 4);
assert (vd("1999-01") == 7);
assert (vd("1999-01-") == 7);
assert (vd("1999-01-0") == 7);
assert (vd("1999-01-01") == 10);
assert (vd("1999-0101") == 7);
assert (vd("1999-365") == 8);
assert (vd("1999365") == 7);
assert (vt("1") == 0);
assert (vt("15") == 2);
assert (vt("15:") == 2);
assert (vt("15:3") == 2);
assert (vt("15:30") == 5);
assert (vt("153") == 2);
assert (vt("1530") == 4);
assert (vt("1530:") == 4);
assert (vt("15304") == 4);
assert (vt("153045") == 6);
assert (vt("15:30:") == 5);
assert (vt("15:30:4") == 5);
assert (vt("15:30:45") == 8);
assert (vt("T15") == 3);
assert (vt("T1") == 0);
assert (vt("T") == 0);
assert (vt("15,") == 2);
assert (vt("15,2") == 4);
assert (vt("1530,") == 4);
assert (vt("1530,2") == 6);
assert (vt("15:30:45,") == 8);
assert (vt("15:30:45,2") == 10);
assert (vt("153045,") == 6);
assert (vt("153045,2") == 8);
assert (vt("153045,22") == 9);
assert (vt("153045,222") == 10);
assert (vt("15Z") == 3);
assert (vt("15+") == 2);
assert (vt("15-") == 2);
assert (vt("15+0") == 2);
assert (vt("15+00") == 5);
assert (vt("15+00:") == 5);
assert (vt("15+00:0") == 5);
assert (vt("15+00:00") == 8);
assert (vb("1999-01-01") == 0);
assert (vb("1999-01-01T") == 0);
assert (vb("1999-01-01T15:30:45") == 19);
}
}
}
|