phobos/std/dateparse.d
jmdavis a6ef12b6c9 Renamed endOfMonthDay to daysInMonth.
endOfMonthDay is too close to endOfMonth, and std.date had daysInMonth
(which amounts to the same thing), so I'm renaming endOfMonthDay to
daysInMonth. So, endOfMonthDay is now scheduled for deprecation. I also
adjusted the various deprecation messages related to std.datetime so
that they included the month when the items in question would be deprecated.
2011-06-25 09:03:48 -07:00

781 lines
24 KiB
D

// Written in the D programming language.
/**
* $(RED Scheduled for deprecation in August 2011. Please use std.datetime instead.)
*
* dateparse module.
*
* Copyright: Copyright Digital Mars 2000 - 2009.
* License: <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
* Authors: $(WEB digitalmars.com, Walter Bright)
* Source: $(PHOBOSSRC std/_dateparse.d)
*/
/*
* Copyright Digital Mars 2000 - 2009.
* Distributed under the Boost Software License, Version 1.0.
* (See accompanying file LICENSE_1_0.txt or copy at
* http://www.boost.org/LICENSE_1_0.txt)
*/
module std.dateparse;
private
{
import std.algorithm, std.string;
import std.c.stdlib;
import std.date;
}
//debug=dateparse;
class DateParseError : Error
{
this(string s)
{
super("Invalid date string: " ~ s);
}
}
struct DateParse
{
void parse(string s, out Date date)
{
this = DateParse.init;
//version (Win32)
buffer = (cast(char *)alloca(s.length))[0 .. s.length];
//else
//buffer = new char[s.length];
debug(dateparse) printf("DateParse.parse('%.*s')\n", s);
if (!parseString(s))
{
goto Lerror;
}
/+
if (year == year.init)
year = 0;
else
+/
debug(dateparse)
printf("year = %d, month = %d, day = %d\n%02d:%02d:%02d.%03d\nweekday = %d, tzcorrection = %d\n",
year, month, day,
hours, minutes, seconds, ms,
weekday, tzcorrection);
if (
year == year.init ||
(month < 1 || month > 12) ||
(day < 1 || day > 31) ||
(hours < 0 || hours > 23) ||
(minutes < 0 || minutes > 59) ||
(seconds < 0 || seconds > 59) ||
(tzcorrection != int.min &&
((tzcorrection < -2300 || tzcorrection > 2300) ||
(tzcorrection % 10)))
)
{
Lerror:
throw new DateParseError(s);
}
if (ampm)
{ if (hours > 12)
goto Lerror;
if (hours < 12)
{
if (ampm == 2) // if P.M.
hours += 12;
}
else if (ampm == 1) // if 12am
{
hours = 0; // which is midnight
}
}
// if (tzcorrection != tzcorrection.init)
// tzcorrection /= 100;
if (year >= 0 && year <= 99)
year += 1900;
date.year = year;
date.month = month;
date.day = day;
date.hour = hours;
date.minute = minutes;
date.second = seconds;
date.ms = ms;
date.weekday = weekday;
date.tzcorrection = tzcorrection;
}
private:
int year = int.min; // our "nan" Date value
int month; // 1..12
int day; // 1..31
int hours; // 0..23
int minutes; // 0..59
int seconds; // 0..59
int ms; // 0..999
int weekday; // 1..7
int ampm; // 0: not specified
// 1: AM
// 2: PM
int tzcorrection = int.min; // -1200..1200 correction in hours
string s;
int si;
int number;
char[] buffer;
enum DP : byte
{
err,
weekday,
month,
number,
end,
colon,
minus,
slash,
ampm,
plus,
tz,
dst,
dsttz,
}
DP nextToken()
{ int nest;
uint c;
int bi;
DP result = DP.err;
//printf("DateParse::nextToken()\n");
for (;;)
{
assert(si <= s.length);
if (si == s.length)
{ result = DP.end;
goto Lret;
}
//printf("\ts[%d] = '%c'\n", si, s[si]);
switch (s[si])
{
case ':': result = DP.colon; goto ret_inc;
case '+': result = DP.plus; goto ret_inc;
case '-': result = DP.minus; goto ret_inc;
case '/': result = DP.slash; goto ret_inc;
case '.':
version(DATE_DOT_DELIM)
{
result = DP.slash;
goto ret_inc;
}
else
{
si++;
break;
}
ret_inc:
si++;
goto Lret;
case ' ':
case '\n':
case '\r':
case '\t':
case ',':
si++;
break;
case '(': // comment
nest = 1;
for (;;)
{
si++;
if (si == s.length)
goto Lret; // error
switch (s[si])
{
case '(':
nest++;
break;
case ')':
if (--nest == 0)
goto Lendofcomment;
break;
default:
break;
}
}
Lendofcomment:
si++;
break;
default:
number = 0;
for (;;)
{
if (si == s.length)
// c cannot be undefined here
break;
c = s[si];
if (!(c >= '0' && c <= '9'))
break;
result = DP.number;
number = number * 10 + (c - '0');
si++;
}
if (result == DP.number)
goto Lret;
bi = 0;
bufloop:
while (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')
{
if (c < 'a') // if upper case
c += cast(uint)'a' - cast(uint)'A'; // to lower case
buffer[bi] = cast(char)c;
bi++;
do
{
si++;
if (si == s.length)
break bufloop;
c = s[si];
} while (c == '.'); // ignore embedded '.'s
}
result = classify(buffer[0 .. bi].idup);
goto Lret;
}
}
Lret:
//printf("-DateParse::nextToken()\n");
return result;
}
DP classify(string buf)
{
struct DateID
{
string name;
DP tok;
short value;
}
static immutable DateID dateidtab[] =
[
{ "january", DP.month, 1},
{ "february", DP.month, 2},
{ "march", DP.month, 3},
{ "april", DP.month, 4},
{ "may", DP.month, 5},
{ "june", DP.month, 6},
{ "july", DP.month, 7},
{ "august", DP.month, 8},
{ "september", DP.month, 9},
{ "october", DP.month, 10},
{ "november", DP.month, 11},
{ "december", DP.month, 12},
{ "jan", DP.month, 1},
{ "feb", DP.month, 2},
{ "mar", DP.month, 3},
{ "apr", DP.month, 4},
{ "jun", DP.month, 6},
{ "jul", DP.month, 7},
{ "aug", DP.month, 8},
{ "sep", DP.month, 9},
{ "sept", DP.month, 9},
{ "oct", DP.month, 10},
{ "nov", DP.month, 11},
{ "dec", DP.month, 12},
{ "sunday", DP.weekday, 1},
{ "monday", DP.weekday, 2},
{ "tuesday", DP.weekday, 3},
{ "tues", DP.weekday, 3},
{ "wednesday", DP.weekday, 4},
{ "wednes", DP.weekday, 4},
{ "thursday", DP.weekday, 5},
{ "thur", DP.weekday, 5},
{ "thurs", DP.weekday, 5},
{ "friday", DP.weekday, 6},
{ "saturday", DP.weekday, 7},
{ "sun", DP.weekday, 1},
{ "mon", DP.weekday, 2},
{ "tue", DP.weekday, 3},
{ "wed", DP.weekday, 4},
{ "thu", DP.weekday, 5},
{ "fri", DP.weekday, 6},
{ "sat", DP.weekday, 7},
{ "am", DP.ampm, 1},
{ "pm", DP.ampm, 2},
{ "gmt", DP.tz, +000},
{ "ut", DP.tz, +000},
{ "utc", DP.tz, +000},
{ "wet", DP.tz, +000},
{ "z", DP.tz, +000},
{ "wat", DP.tz, +100},
{ "a", DP.tz, +100},
{ "at", DP.tz, +200},
{ "b", DP.tz, +200},
{ "c", DP.tz, +300},
{ "ast", DP.tz, +400},
{ "d", DP.tz, +400},
{ "est", DP.tz, +500},
{ "e", DP.tz, +500},
{ "cst", DP.tz, +600},
{ "f", DP.tz, +600},
{ "mst", DP.tz, +700},
{ "g", DP.tz, +700},
{ "pst", DP.tz, +800},
{ "h", DP.tz, +800},
{ "yst", DP.tz, +900},
{ "i", DP.tz, +900},
{ "ahst", DP.tz, +1000},
{ "cat", DP.tz, +1000},
{ "hst", DP.tz, +1000},
{ "k", DP.tz, +1000},
{ "nt", DP.tz, +1100},
{ "l", DP.tz, +1100},
{ "idlw", DP.tz, +1200},
{ "m", DP.tz, +1200},
{ "cet", DP.tz, -100},
{ "fwt", DP.tz, -100},
{ "met", DP.tz, -100},
{ "mewt", DP.tz, -100},
{ "swt", DP.tz, -100},
{ "n", DP.tz, -100},
{ "eet", DP.tz, -200},
{ "o", DP.tz, -200},
{ "bt", DP.tz, -300},
{ "p", DP.tz, -300},
{ "zp4", DP.tz, -400},
{ "q", DP.tz, -400},
{ "zp5", DP.tz, -500},
{ "r", DP.tz, -500},
{ "zp6", DP.tz, -600},
{ "s", DP.tz, -600},
{ "wast", DP.tz, -700},
{ "t", DP.tz, -700},
{ "cct", DP.tz, -800},
{ "u", DP.tz, -800},
{ "jst", DP.tz, -900},
{ "v", DP.tz, -900},
{ "east", DP.tz, -1000},
{ "gst", DP.tz, -1000},
{ "w", DP.tz, -1000},
{ "x", DP.tz, -1100},
{ "idle", DP.tz, -1200},
{ "nzst", DP.tz, -1200},
{ "nzt", DP.tz, -1200},
{ "y", DP.tz, -1200},
{ "bst", DP.dsttz, 000},
{ "adt", DP.dsttz, +400},
{ "edt", DP.dsttz, +500},
{ "cdt", DP.dsttz, +600},
{ "mdt", DP.dsttz, +700},
{ "pdt", DP.dsttz, +800},
{ "ydt", DP.dsttz, +900},
{ "hdt", DP.dsttz, +1000},
{ "mest", DP.dsttz, -100},
{ "mesz", DP.dsttz, -100},
{ "sst", DP.dsttz, -100},
{ "fst", DP.dsttz, -100},
{ "wadt", DP.dsttz, -700},
{ "eadt", DP.dsttz, -1000},
{ "nzdt", DP.dsttz, -1200},
{ "dst", DP.dst, 0},
];
//message(DTEXT("DateParse::classify('%s')\n"), buf);
// Do a linear search. Yes, it would be faster with a binary
// one.
for (uint i = 0; i < dateidtab.length; i++)
{
if (cmp(dateidtab[i].name, buf) == 0)
{
number = dateidtab[i].value;
return dateidtab[i].tok;
}
}
return DP.err;
}
int parseString(string s)
{
int n1;
int dp;
int sisave;
int result;
//message(DTEXT("DateParse::parseString('%ls')\n"), s);
this.s = s;
si = 0;
dp = nextToken();
for (;;)
{
//message(DTEXT("\tdp = %d\n"), dp);
switch (dp)
{
case DP.end:
result = 1;
Lret:
return result;
case DP.err:
case_error:
//message(DTEXT("\terror\n"));
default:
result = 0;
goto Lret;
case DP.minus:
break; // ignore spurious '-'
case DP.weekday:
weekday = number;
break;
case DP.month: // month day, [year]
month = number;
dp = nextToken();
if (dp == DP.number)
{
day = number;
sisave = si;
dp = nextToken();
if (dp == DP.number)
{
n1 = number;
dp = nextToken();
if (dp == DP.colon)
{ // back up, not a year
si = sisave;
}
else
{ year = n1;
continue;
}
break;
}
}
continue;
case DP.number:
n1 = number;
dp = nextToken();
switch (dp)
{
case DP.end:
year = n1;
break;
case DP.minus:
case DP.slash: // n1/ ? ? ?
dp = parseCalendarDate(n1);
if (dp == DP.err)
goto case_error;
break;
case DP.colon: // hh:mm [:ss] [am | pm]
dp = parseTimeOfDay(n1);
if (dp == DP.err)
goto case_error;
break;
case DP.ampm:
hours = n1;
minutes = 0;
seconds = 0;
ampm = number;
break;
case DP.month:
day = n1;
month = number;
dp = nextToken();
if (dp == DP.number)
{ // day month year
year = number;
dp = nextToken();
}
break;
default:
year = n1;
break;
}
continue;
}
dp = nextToken();
}
// @@@ bug in the compiler: this is never reachable
assert(0);
}
int parseCalendarDate(int n1)
{
int n2;
int n3;
int dp;
debug(dateparse) printf("DateParse.parseCalendarDate(%d)\n", n1);
dp = nextToken();
if (dp == DP.month) // day/month
{
day = n1;
month = number;
dp = nextToken();
if (dp == DP.number)
{ // day/month year
year = number;
dp = nextToken();
}
else if (dp == DP.minus || dp == DP.slash)
{ // day/month/year
dp = nextToken();
if (dp != DP.number)
goto case_error;
year = number;
dp = nextToken();
}
return dp;
}
if (dp != DP.number)
goto case_error;
n2 = number;
//message(DTEXT("\tn2 = %d\n"), n2);
dp = nextToken();
if (dp == DP.minus || dp == DP.slash)
{
dp = nextToken();
if (dp != DP.number)
goto case_error;
n3 = number;
//message(DTEXT("\tn3 = %d\n"), n3);
dp = nextToken();
// case1: year/month/day
// case2: month/day/year
int case1, case2;
case1 = (n1 > 12 ||
(n2 >= 1 && n2 <= 12) &&
(n3 >= 1 && n3 <= 31));
case2 = ((n1 >= 1 && n1 <= 12) &&
(n2 >= 1 && n2 <= 31) ||
n3 > 31);
if (case1 == case2)
goto case_error;
if (case1)
{
year = n1;
month = n2;
day = n3;
}
else
{
month = n1;
day = n2;
year = n3;
}
}
else
{ // must be month/day
month = n1;
day = n2;
}
return dp;
case_error:
return DP.err;
}
int parseTimeOfDay(int n1)
{
int dp;
int sign;
// 12am is midnight
// 12pm is noon
//message(DTEXT("DateParse::parseTimeOfDay(%d)\n"), n1);
hours = n1;
dp = nextToken();
if (dp != DP.number)
goto case_error;
minutes = number;
dp = nextToken();
if (dp == DP.colon)
{
dp = nextToken();
if (dp != DP.number)
goto case_error;
seconds = number;
dp = nextToken();
}
else
seconds = 0;
if (dp == DP.ampm)
{
ampm = number;
dp = nextToken();
}
else if (dp == DP.plus || dp == DP.minus)
{
Loffset:
sign = (dp == DP.minus) ? -1 : 1;
dp = nextToken();
if (dp != DP.number)
goto case_error;
tzcorrection = -sign * number;
dp = nextToken();
}
else if (dp == DP.tz)
{
tzcorrection = number;
dp = nextToken();
if (number == 0 && (dp == DP.plus || dp == DP.minus))
goto Loffset;
if (dp == DP.dst)
{ tzcorrection += 100;
dp = nextToken();
}
}
else if (dp == DP.dsttz)
{
tzcorrection = number;
dp = nextToken();
}
return dp;
case_error:
return DP.err;
}
}
unittest
{
DateParse dp;
Date d;
dp.parse("March 10, 1959 12:00 -800", d);
assert(d.year == 1959);
assert(d.month == 3);
assert(d.day == 10);
assert(d.hour == 12);
assert(d.minute == 0);
assert(d.second == 0);
assert(d.ms == 0);
assert(d.weekday == 0);
assert(d.tzcorrection == 800);
dp.parse("Tue Apr 02 02:04:57 GMT-0800 1996", d);
assert(d.year == 1996);
assert(d.month == 4);
assert(d.day == 2);
assert(d.hour == 2);
assert(d.minute == 4);
assert(d.second == 57);
assert(d.ms == 0);
assert(d.weekday == 3);
assert(d.tzcorrection == 800);
dp.parse("March 14, -1980 21:14:50", d);
assert(d.year == 1980);
assert(d.month == 3);
assert(d.day == 14);
assert(d.hour == 21);
assert(d.minute == 14);
assert(d.second == 50);
assert(d.ms == 0);
assert(d.weekday == 0);
assert(d.tzcorrection == int.min);
dp.parse("Tue Apr 02 02:04:57 1996", d);
assert(d.year == 1996);
assert(d.month == 4);
assert(d.day == 2);
assert(d.hour == 2);
assert(d.minute == 4);
assert(d.second == 57);
assert(d.ms == 0);
assert(d.weekday == 3);
assert(d.tzcorrection == int.min);
dp.parse("Tue, 02 Apr 1996 02:04:57 G.M.T.", d);
assert(d.year == 1996);
assert(d.month == 4);
assert(d.day == 2);
assert(d.hour == 2);
assert(d.minute == 4);
assert(d.second == 57);
assert(d.ms == 0);
assert(d.weekday == 3);
assert(d.tzcorrection == 0);
dp.parse("December 31, 3000", d);
assert(d.year == 3000);
assert(d.month == 12);
assert(d.day == 31);
assert(d.hour == 0);
assert(d.minute == 0);
assert(d.second == 0);
assert(d.ms == 0);
assert(d.weekday == 0);
assert(d.tzcorrection == int.min);
dp.parse("Wed, 31 Dec 1969 16:00:00 GMT", d);
assert(d.year == 1969);
assert(d.month == 12);
assert(d.day == 31);
assert(d.hour == 16);
assert(d.minute == 0);
assert(d.second == 0);
assert(d.ms == 0);
assert(d.weekday == 4);
assert(d.tzcorrection == 0);
dp.parse("1/1/1999 12:30 AM", d);
assert(d.year == 1999);
assert(d.month == 1);
assert(d.day == 1);
assert(d.hour == 0);
assert(d.minute == 30);
assert(d.second == 0);
assert(d.ms == 0);
assert(d.weekday == 0);
assert(d.tzcorrection == int.min);
dp.parse("Tue, 20 May 2003 15:38:58 +0530", d);
assert(d.year == 2003);
assert(d.month == 5);
assert(d.day == 20);
assert(d.hour == 15);
assert(d.minute == 38);
assert(d.second == 58);
assert(d.ms == 0);
assert(d.weekday == 3);
assert(d.tzcorrection == -530);
debug(dateparse) printf("year = %d, month = %d, day = %d\n%02d:%02d:%02d.%03d\nweekday = %d, tzcorrection = %d\n",
d.year, d.month, d.day,
d.hour, d.minute, d.second, d.ms,
d.weekday, d.tzcorrection);
}