/*
  date_parse.c: Coded by Tadayoshi Funaba 2011,2012
*/

#include "ruby.h"
#include "ruby/encoding.h"
#include "ruby/re.h"
#include <ctype.h>

#define sizeof_array(o) (sizeof o / sizeof o[0])

#define f_negate(x) rb_funcall(x, rb_intern("-@"), 0)
#define f_add(x,y) rb_funcall(x, '+', 1, y)
#define f_sub(x,y) rb_funcall(x, '-', 1, y)
#define f_mul(x,y) rb_funcall(x, '*', 1, y)
#define f_div(x,y) rb_funcall(x, '/', 1, y)
#define f_idiv(x,y) rb_funcall(x, rb_intern("div"), 1, y)
#define f_mod(x,y) rb_funcall(x, '%', 1, y)
#define f_expt(x,y) rb_funcall(x, rb_intern("**"), 1, y)

#define f_lt_p(x,y) rb_funcall(x, '<', 1, y)
#define f_gt_p(x,y) rb_funcall(x, '>', 1, y)
#define f_le_p(x,y) rb_funcall(x, rb_intern("<="), 1, y)
#define f_ge_p(x,y) rb_funcall(x, rb_intern(">="), 1, y)

#define f_to_s(x) rb_funcall(x, rb_intern("to_s"), 0)

#define f_match(r,s) rb_funcall(r, rb_intern("match"), 1, s)
#define f_aref(o,i) rb_funcall(o, rb_intern("[]"), 1, i)
#define f_aref2(o,i,j) rb_funcall(o, rb_intern("[]"), 2, i, j)
#define f_begin(o,i) rb_funcall(o, rb_intern("begin"), 1, i)
#define f_end(o,i) rb_funcall(o, rb_intern("end"), 1, i)
#define f_aset(o,i,v) rb_funcall(o, rb_intern("[]="), 2, i, v)
#define f_aset2(o,i,j,v) rb_funcall(o, rb_intern("[]="), 3, i, j, v)
#define f_sub_bang(s,r,x) rb_funcall(s, rb_intern("sub!"), 2, r, x)
#define f_gsub_bang(s,r,x) rb_funcall(s, rb_intern("gsub!"), 2, r, x)

#define set_hash(k,v) rb_hash_aset(hash, ID2SYM(rb_intern(k)), v)
#define ref_hash(k) rb_hash_aref(hash, ID2SYM(rb_intern(k)))
#define del_hash(k) rb_hash_delete(hash, ID2SYM(rb_intern(k)))

#define cstr2num(s) rb_cstr_to_inum(s, 10, 0)
#define str2num(s) rb_str_to_inum(s, 10, 0)

static const char *abbr_days[] = {
    "sun", "mon", "tue", "wed",
    "thu", "fri", "sat"
};

static const char *abbr_months[] = {
    "jan", "feb", "mar", "apr", "may", "jun",
    "jul", "aug", "sep", "oct", "nov", "dec"
};

#define issign(c) ((c) == '-' || (c) == '+')
#define asp_string() rb_str_new(" ", 1)

static void
s3e(VALUE hash, VALUE y, VALUE m, VALUE d, int bc)
{
    VALUE c = Qnil;

    if (TYPE(m) != T_STRING)
	m = f_to_s(m);

    if (!NIL_P(y) && !NIL_P(m) && NIL_P(d)) {
	VALUE oy = y;
	VALUE om = m;
	VALUE od = d;

	y = od;
	m = oy;
	d = om;
    }

    if (NIL_P(y)) {
	if (!NIL_P(d) && RSTRING_LEN(d) > 2) {
	    y = d;
	    d = Qnil;
	}
	if (!NIL_P(d) && *RSTRING_PTR(d) == '\'') {
	    y = d;
	    d = Qnil;
	}
    }

    if (!NIL_P(y)) {
	const char *s, *bp, *ep;
	size_t l;

	s = RSTRING_PTR(y);
	while (!issign(*s) && !isdigit(*s))
	    s++;
	bp = s;
	if (issign(*s))
	    s++;
	l = strspn(s, "0123456789");
	ep = s + l;
	if (*ep) {
	    y = d;
	    d = rb_str_new(bp, ep - bp);
	}
    }

    if (!NIL_P(m)) {
	const char *s;

	s = RSTRING_PTR(m);
	if (*s == '\'' || RSTRING_LEN(m) > 2) {
	    /* us -> be */
	    VALUE oy = y;
	    VALUE om = m;
	    VALUE od = d;

	    y = om;
	    m = od;
	    d = oy;
	}
    }

    if (!NIL_P(d)) {
	const char *s;

	s = RSTRING_PTR(d);
	if (*s == '\'' || RSTRING_LEN(d) > 2) {
	    VALUE oy = y;
	    VALUE od = d;

	    y = od;
	    d = oy;
	}
    }

    if (!NIL_P(y)) {
	const char *s, *bp, *ep;
	int sign = 0;
	size_t l;
	VALUE iy;

	s = RSTRING_PTR(y);
	while (!issign(*s) && !isdigit(*s))
	    s++;
	bp = s;
	if (issign(*s)) {
	    s++;
	    sign = 1;
	}
	if (sign)
	    c = Qfalse;
	l = strspn(s, "0123456789");
	ep = s + l;
	if (l > 2)
	    c = Qfalse;
	{
	    char *buf;

	    buf = ALLOCA_N(char, ep - bp + 1);
	    memcpy(buf, bp, ep - bp);
	    buf[ep - bp] = '\0';
	    iy = cstr2num(buf);
	}
	if (bc)
	    iy = f_add(f_negate(iy), INT2FIX(1));
	set_hash("year", iy);
    }

    if (!NIL_P(m)) {
	const char *s, *bp, *ep;
	size_t l;
	VALUE im;

	s = RSTRING_PTR(m);
	while (!isdigit(*s))
	    s++;
	bp = s;
	l = strspn(s, "0123456789");
	ep = s + l;
	{
	    char *buf;

	    buf = ALLOCA_N(char, ep - bp + 1);
	    memcpy(buf, bp, ep - bp);
	    buf[ep - bp] = '\0';
	    im = cstr2num(buf);
	}
	set_hash("mon", im);
    }

    if (!NIL_P(d)) {
	const char *s, *bp, *ep;
	size_t l;
	VALUE id;

	s = RSTRING_PTR(d);
	while (!isdigit(*s))
	    s++;
	bp = s;
	l = strspn(s, "0123456789");
	ep = s + l;
	{
	    char *buf;

	    buf = ALLOCA_N(char, ep - bp + 1);
	    memcpy(buf, bp, ep - bp);
	    buf[ep - bp] = '\0';
	    id = cstr2num(buf);
	}
	set_hash("mday", id);
    }

    if (!NIL_P(c))
	set_hash("_comp", c);
}

#define DAYS "sunday|monday|tuesday|wednesday|thursday|friday|saturday"
#define MONTHS "january|february|march|april|may|june|july|august|september|october|november|december"
#define ABBR_DAYS "sun|mon|tue|wed|thu|fri|sat"
#define ABBR_MONTHS "jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec"

static VALUE
regcomp(const char *source, long len, int opt)
{
    VALUE pat;

    pat = rb_reg_new(source, len, opt);
    rb_gc_register_mark_object(pat);
    return pat;
}

#define REGCOMP(pat,opt) \
{ \
    if (NIL_P(pat)) \
	pat = regcomp(pat##_source, sizeof pat##_source - 1, opt); \
}

#define REGCOMP_0(pat) REGCOMP(pat, 0)
#define REGCOMP_I(pat) REGCOMP(pat, ONIG_OPTION_IGNORECASE)

#define MATCH(s,p,c) \
{ \
    return match(s, p, hash, c); \
}

static int
match(VALUE str, VALUE pat, VALUE hash, int (*cb)(VALUE, VALUE))
{
    VALUE m;

    m = f_match(pat, str);

    if (NIL_P(m))
	return 0;

    (*cb)(m, hash);

    return 1;
}

#define SUBS(s,p,c) \
{ \
    return subs(s, p, hash, c);	\
}

static int
subs(VALUE str, VALUE pat, VALUE hash, int (*cb)(VALUE, VALUE))
{
    VALUE m;

    m = f_match(pat, str);

    if (NIL_P(m))
	return 0;

    {
	VALUE be, en;

	be = f_begin(m, INT2FIX(0));
	en = f_end(m, INT2FIX(0));
	f_aset2(str, be, LONG2NUM(NUM2LONG(en) - NUM2LONG(be)), asp_string());
	(*cb)(m, hash);
    }

    return 1;
}

struct zone {
    const char *name;
    int offset;
};

static struct zone zones_source[] = {
    {"ut",   0*3600}, {"gmt",  0*3600}, {"est", -5*3600}, {"edt", -4*3600},
    {"cst", -6*3600}, {"cdt", -5*3600}, {"mst", -7*3600}, {"mdt", -6*3600},
    {"pst", -8*3600}, {"pdt", -7*3600},
    {"a",    1*3600}, {"b",    2*3600}, {"c",    3*3600}, {"d",    4*3600},
    {"e",    5*3600}, {"f",    6*3600}, {"g",    7*3600}, {"h",    8*3600},
    {"i",    9*3600}, {"k",   10*3600}, {"l",   11*3600}, {"m",   12*3600},
    {"n",   -1*3600}, {"o",   -2*3600}, {"p",   -3*3600}, {"q",   -4*3600},
    {"r",   -5*3600}, {"s",   -6*3600}, {"t",   -7*3600}, {"u",   -8*3600},
    {"v",   -9*3600}, {"w",  -10*3600}, {"x",  -11*3600}, {"y",  -12*3600},
    {"z",    0*3600},

    {"utc",  0*3600}, {"wet",  0*3600},
    {"at",  -2*3600}, {"brst",-2*3600}, {"ndt", -(2*3600+1800)},
    {"art", -3*3600}, {"adt", -3*3600}, {"brt", -3*3600}, {"clst",-3*3600},
    {"nst", -(3*3600+1800)},
    {"ast", -4*3600}, {"clt", -4*3600},
    {"akdt",-8*3600}, {"ydt", -8*3600},
    {"akst",-9*3600}, {"hadt",-9*3600}, {"hdt", -9*3600}, {"yst", -9*3600},
    {"ahst",-10*3600},{"cat",-10*3600}, {"hast",-10*3600},{"hst",-10*3600},
    {"nt",  -11*3600},
    {"idlw",-12*3600},
    {"bst",  1*3600}, {"cet",  1*3600}, {"fwt",  1*3600}, {"met",  1*3600},
    {"mewt", 1*3600}, {"mez",  1*3600}, {"swt",  1*3600}, {"wat",  1*3600},
    {"west", 1*3600},
    {"cest", 2*3600}, {"eet",  2*3600}, {"fst",  2*3600}, {"mest", 2*3600},
    {"mesz", 2*3600}, {"sast", 2*3600}, {"sst",  2*3600},
    {"bt",   3*3600}, {"eat",  3*3600}, {"eest", 3*3600}, {"msk",  3*3600},
    {"msd",  4*3600}, {"zp4",  4*3600},
    {"zp5",  5*3600}, {"ist",  (5*3600+1800)},
    {"zp6",  6*3600},
    {"wast", 7*3600},
    {"cct",  8*3600}, {"sgt",  8*3600}, {"wadt", 8*3600},
    {"jst",  9*3600}, {"kst",  9*3600},
    {"east",10*3600}, {"gst", 10*3600},
    {"eadt",11*3600},
    {"idle",12*3600}, {"nzst",12*3600}, {"nzt", 12*3600},
    {"nzdt",13*3600},

    {"afghanistan",             16200}, {"alaskan",                -32400},
    {"arab",                    10800}, {"arabian",                 14400},
    {"arabic",                  10800}, {"atlantic",               -14400},
    {"aus central",             34200}, {"aus eastern",             36000},
    {"azores",                  -3600}, {"canada central",         -21600},
    {"cape verde",              -3600}, {"caucasus",                14400},
    {"cen. australia",          34200}, {"central america",        -21600},
    {"central asia",            21600}, {"central europe",           3600},
    {"central european",         3600}, {"central pacific",         39600},
    {"central",                -21600}, {"china",                   28800},
    {"dateline",               -43200}, {"e. africa",               10800},
    {"e. australia",            36000}, {"e. europe",                7200},
    {"e. south america",       -10800}, {"eastern",                -18000},
    {"egypt",                    7200}, {"ekaterinburg",            18000},
    {"fiji",                    43200}, {"fle",                      7200},
    {"greenland",              -10800}, {"greenwich",                   0},
    {"gtb",                      7200}, {"hawaiian",               -36000},
    {"india",                   19800}, {"iran",                    12600},
    {"jerusalem",                7200}, {"korea",                   32400},
    {"mexico",                 -21600}, {"mid-atlantic",            -7200},
    {"mountain",               -25200}, {"myanmar",                 23400},
    {"n. central asia",         21600}, {"nepal",                   20700},
    {"new zealand",             43200}, {"newfoundland",           -12600},
    {"north asia east",         28800}, {"north asia",              25200},
    {"pacific sa",             -14400}, {"pacific",                -28800},
    {"romance",                  3600}, {"russian",                 10800},
    {"sa eastern",             -10800}, {"sa pacific",             -18000},
    {"sa western",             -14400}, {"samoa",                  -39600},
    {"se asia",                 25200}, {"malay peninsula",         28800},
    {"south africa",             7200}, {"sri lanka",               21600},
    {"taipei",                  28800}, {"tasmania",                36000},
    {"tokyo",                   32400}, {"tonga",                   46800},
    {"us eastern",             -18000}, {"us mountain",            -25200},
    {"vladivostok",             36000}, {"w. australia",            28800},
    {"w. central africa",        3600}, {"w. europe",                3600},
    {"west asia",               18000}, {"west pacific",            36000},
    {"yakutsk",                 32400}
};

VALUE
date_zone_to_diff(VALUE str)
{
    VALUE offset = Qnil;

    long l, i;
    char *s, *dest, *d;
    int sp = 1;

    l = RSTRING_LEN(str);
    s = RSTRING_PTR(str);

    dest = d = ALLOCA_N(char, l + 1);

    for (i = 0; i < l; i++) {
	if (isspace(s[i]) || s[i] == '\0') {
	    if (!sp)
		*d++ = ' ';
	    sp = 1;
	}
	else {
	    if (isalpha(s[i]))
		*d++ = tolower(s[i]);
	    else
		*d++ = s[i];
	    sp = 0;
	}
    }
    if (d > dest) {
	if (*(d - 1) == ' ')
	    --d;
	*d = '\0';
    }
    str = rb_str_new2(dest);
    {
#define STD " standard time"
#define DST " daylight time"
	char *ss, *ds;
	long sl, dl;
	int dst = 0;

	sl = RSTRING_LEN(str) - (sizeof STD - 1);
	ss = RSTRING_PTR(str) + sl;
	dl = RSTRING_LEN(str) - (sizeof DST - 1);
	ds = RSTRING_PTR(str) + dl;

	if (sl >= 0 && strcmp(ss, STD) == 0) {
	    str = rb_str_new(RSTRING_PTR(str), sl);
	}
	else if (dl >= 0 && strcmp(ds, DST) == 0) {
	    str = rb_str_new(RSTRING_PTR(str), dl);
	    dst = 1;
	}
#undef STD
#undef DST
	else {
#define DST " dst"
	    char *ds;
	    long dl;

	    dl = RSTRING_LEN(str) - (sizeof DST - 1);
	    ds = RSTRING_PTR(str) + dl;

	    if (dl >= 0 && strcmp(ds, DST) == 0) {
		str = rb_str_new(RSTRING_PTR(str), dl);
		dst = 1;
	    }
#undef DST
	}
	{
	    static VALUE zones = Qnil;

	    if (NIL_P(zones)) {
		int i;

		zones = rb_hash_new();
		rb_gc_register_mark_object(zones);
		for (i = 0; i < (int)sizeof_array(zones_source); i++) {
		    VALUE name = rb_str_new2(zones_source[i].name);
		    VALUE offset = INT2FIX(zones_source[i].offset);
		    rb_hash_aset(zones, name, offset);
		}
	    }

	    offset = f_aref(zones, str);
	    if (!NIL_P(offset)) {
		if (dst)
		    offset = f_add(offset, INT2FIX(3600));
		goto ok;
	    }
	}
	{
	    char *s, *p;
	    VALUE sign;
	    VALUE hour = Qnil, min = Qnil, sec = Qnil;
	    VALUE str_orig;

	    s = RSTRING_PTR(str);
	    str_orig = str;

	    if (strncmp(s, "gmt", 3) == 0 ||
		strncmp(s, "utc", 3) == 0)
		s += 3;
	    if (issign(*s)) {
		sign = rb_str_new(s, 1);
		s++;

		str = rb_str_new2(s);

		if (p = strchr(s, ':')) {
		    hour = rb_str_new(s, p - s);
		    s = ++p;
		    if (p = strchr(s, ':')) {
			min = rb_str_new(s, p - s);
			s = ++p;
			if (p = strchr(s, ':')) {
			    sec = rb_str_new(s, p - s);
			}
			else
			    sec = rb_str_new2(s);
		    }
		    else
			min = rb_str_new2(s);
		    RB_GC_GUARD(str_orig);
		    goto num;
		}
		if (strpbrk(RSTRING_PTR(str), ",.")) {
		    char *a, *b;

		    a = ALLOCA_N(char, RSTRING_LEN(str) + 1);
		    strcpy(a, RSTRING_PTR(str));
		    b = strpbrk(a, ",.");
		    *b = '\0';
		    b++;

		    hour = cstr2num(a);
		    min = f_mul(rb_rational_new2
				(cstr2num(b),
				 f_expt(INT2FIX(10),
					LONG2NUM((long)strlen(b)))),
				INT2FIX(60));
		    goto num;
		}
		{
		    const char *cs = RSTRING_PTR(str);
		    long cl = RSTRING_LEN(str);

		    if (cl % 2) {
			if (cl >= 1)
			    hour = rb_str_new(&cs[0], 1);
			if (cl >= 3)
			    min  = rb_str_new(&cs[1], 2);
			if (cl >= 5)
			    min  = rb_str_new(&cs[3], 2);
		    }
		    else {
			if (cl >= 2)
			    hour = rb_str_new(&cs[0], 2);
			if (cl >= 4)
			    min  = rb_str_new(&cs[2], 2);
			if (cl >= 6)
			    sec  = rb_str_new(&cs[4], 2);
		    }
		    goto num;
		}
	      num:
		if (NIL_P(hour))
		    offset = INT2FIX(0);
		else {
		    if (TYPE(hour) == T_STRING)
			hour = str2num(hour);
		    offset = f_mul(hour, INT2FIX(3600));
		}
		if (!NIL_P(min)) {
		    if (TYPE(min) == T_STRING)
			min = str2num(min);
		    offset = f_add(offset, f_mul(min, INT2FIX(60)));
		}
		if (!NIL_P(sec))
		    offset = f_add(offset, str2num(sec));
		if (!NIL_P(sign) &&
		    RSTRING_LEN(sign) == 1 &&
		    *RSTRING_PTR(sign) == '-')
		    offset = f_negate(offset);
	    }
	}
    }
    RB_GC_GUARD(str);
  ok:
    return offset;
}

static int
day_num(VALUE s)
{
    int i;

    for (i = 0; i < (int)sizeof_array(abbr_days); i++)
	if (strncasecmp(abbr_days[i], RSTRING_PTR(s), 3) == 0)
	    break;
    return i;
}

static int
mon_num(VALUE s)
{
    int i;

    for (i = 0; i < (int)sizeof_array(abbr_months); i++)
	if (strncasecmp(abbr_months[i], RSTRING_PTR(s), 3) == 0)
	    break;
    return i + 1;
}

static int
parse_day_cb(VALUE m, VALUE hash)
{
    VALUE s;

    s = rb_reg_nth_match(1, m);
    set_hash("wday", INT2FIX(day_num(s)));
    return 1;
}

static int
parse_day(VALUE str, VALUE hash)
{
    static const char pat_source[] = "\\b(" ABBR_DAYS ")[^-\\d\\s]*";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    SUBS(str, pat, parse_day_cb);
}

static int
parse_time2_cb(VALUE m, VALUE hash)
{
    VALUE h, min, s, f, p;

    h = rb_reg_nth_match(1, m);
    h = str2num(h);

    min = rb_reg_nth_match(2, m);
    if (!NIL_P(min))
	min = str2num(min);

    s = rb_reg_nth_match(3, m);
    if (!NIL_P(s))
	s = str2num(s);

    f = rb_reg_nth_match(4, m);

    if (!NIL_P(f))
	f = rb_rational_new2(str2num(f),
			     f_expt(INT2FIX(10), LONG2NUM(RSTRING_LEN(f))));

    p = rb_reg_nth_match(5, m);

    if (!NIL_P(p)) {
	int ih = NUM2INT(h);
	ih %= 12;
	if (*RSTRING_PTR(p) == 'P' || *RSTRING_PTR(p) == 'p')
	    ih += 12;
	h = INT2FIX(ih);
    }

    set_hash("hour", h);
    if (!NIL_P(min))
	set_hash("min", min);
    if (!NIL_P(s))
	set_hash("sec", s);
    if (!NIL_P(f))
	set_hash("sec_fraction", f);

    return 1;
}

static int
parse_time_cb(VALUE m, VALUE hash)
{
    static const char pat_source[] =
	    "\\A(\\d+)h?"
	      "(?:\\s*:?\\s*(\\d+)m?"
		"(?:"
		  "\\s*:?\\s*(\\d+)(?:[,.](\\d+))?s?"
		")?"
	      ")?"
	    "(?:\\s*([ap])(?:m\\b|\\.m\\.))?";
    static VALUE pat = Qnil;
    VALUE s1, s2;

    s1 = rb_reg_nth_match(1, m);
    s2 = rb_reg_nth_match(2, m);

    if (!NIL_P(s2))
	set_hash("zone", s2);

    REGCOMP_I(pat);

    {
	VALUE m = f_match(pat, s1);

	if (NIL_P(m))
	    return 0;
	parse_time2_cb(m, hash);
    }

    return 1;
}

static int
parse_time(VALUE str, VALUE hash)
{
    static const char pat_source[] =
		"("
		   "(?:"
		     "\\d+\\s*:\\s*\\d+"
		     "(?:"
		       "\\s*:\\s*\\d+(?:[,.]\\d*)?"
		     ")?"
		   "|"
		     "\\d+\\s*h(?:\\s*\\d+m?(?:\\s*\\d+s?)?)?"
		   ")"
		   "(?:"
		     "\\s*"
		     "[ap](?:m\\b|\\.m\\.)"
		   ")?"
		 "|"
		   "\\d+\\s*[ap](?:m\\b|\\.m\\.)"
		 ")"
		 "(?:"
		   "\\s*"
		   "("
		     "(?:gmt|utc?)?[-+]\\d+(?:[,.:]\\d+(?::\\d+)?)?"
		   "|"
		     "(?-i:[[:alpha:].\\s]+)(?:standard|daylight)\\stime\\b"
		   "|"
		     "(?-i:[[:alpha:]]+)(?:\\sdst)?\\b"
		   ")"
		")?";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    SUBS(str, pat, parse_time_cb);
}

static int
parse_eu_cb(VALUE m, VALUE hash)
{
    VALUE y, mon, d, b;

    d = rb_reg_nth_match(1, m);
    mon = rb_reg_nth_match(2, m);
    b = rb_reg_nth_match(3, m);
    y = rb_reg_nth_match(4, m);

    mon = INT2FIX(mon_num(mon));

    s3e(hash, y, mon, d, !NIL_P(b) &&
	(*RSTRING_PTR(b) == 'B' ||
	 *RSTRING_PTR(b) == 'b'));
    return 1;
}

static int
parse_eu(VALUE str, VALUE hash)
{
    static const char pat_source[] =
		"'?(\\d+)[^-\\d\\s]*"
		 "\\s*"
		 "(" ABBR_MONTHS ")[^-\\d\\s']*"
		 "(?:"
		   "\\s*"
		   "(c(?:e|\\.e\\.)|b(?:ce|\\.c\\.e\\.)|a(?:d|\\.d\\.)|b(?:c|\\.c\\.))?"
		   "\\s*"
		   "('?-?\\d+(?:(?:st|nd|rd|th)\\b)?)"
		")?";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    SUBS(str, pat, parse_eu_cb);
}

static int
parse_us_cb(VALUE m, VALUE hash)
{
    VALUE y, mon, d, b;

    mon = rb_reg_nth_match(1, m);
    d = rb_reg_nth_match(2, m);
    b = rb_reg_nth_match(3, m);
    y = rb_reg_nth_match(4, m);

    mon = INT2FIX(mon_num(mon));

    s3e(hash, y, mon, d, !NIL_P(b) &&
	(*RSTRING_PTR(b) == 'B' ||
	 *RSTRING_PTR(b) == 'b'));
    return 1;
}

static int
parse_us(VALUE str, VALUE hash)
{
    static const char pat_source[] =
		"\\b(" ABBR_MONTHS ")[^-\\d\\s']*"
		 "\\s*"
		 "('?\\d+)[^-\\d\\s']*"
		 "(?:"
		   "\\s*"
		   "(c(?:e|\\.e\\.)|b(?:ce|\\.c\\.e\\.)|a(?:d|\\.d\\.)|b(?:c|\\.c\\.))?"
		   "\\s*"
		   "('?-?\\d+)"
		")?";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    SUBS(str, pat, parse_us_cb);
}

static int
parse_iso_cb(VALUE m, VALUE hash)
{
    VALUE y, mon, d;

    y = rb_reg_nth_match(1, m);
    mon = rb_reg_nth_match(2, m);
    d = rb_reg_nth_match(3, m);

    s3e(hash, y, mon, d, 0);
    return 1;
}

static int
parse_iso(VALUE str, VALUE hash)
{
    static const char pat_source[] = "('?[-+]?\\d+)-(\\d+)-('?-?\\d+)";
    static VALUE pat = Qnil;

    REGCOMP_0(pat);
    SUBS(str, pat, parse_iso_cb);
}

static int
parse_iso21_cb(VALUE m, VALUE hash)
{
    VALUE y, w, d;

    y = rb_reg_nth_match(1, m);
    w = rb_reg_nth_match(2, m);
    d = rb_reg_nth_match(3, m);

    if (!NIL_P(y))
	set_hash("cwyear", str2num(y));
    set_hash("cweek", str2num(w));
    if (!NIL_P(d))
	set_hash("cwday", str2num(d));

    return 1;
}

static int
parse_iso21(VALUE str, VALUE hash)
{
    static const char pat_source[] =
	"\\b(\\d{2}|\\d{4})?-?w(\\d{2})(?:-?(\\d))?\\b";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    SUBS(str, pat, parse_iso21_cb);
}

static int
parse_iso22_cb(VALUE m, VALUE hash)
{
    VALUE d;

    d = rb_reg_nth_match(1, m);
    set_hash("cwday", str2num(d));
    return 1;
}

static int
parse_iso22(VALUE str, VALUE hash)
{
    static const char pat_source[] = "-w-(\\d)\\b";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    SUBS(str, pat, parse_iso22_cb);
}

static int
parse_iso23_cb(VALUE m, VALUE hash)
{
    VALUE mon, d;

    mon = rb_reg_nth_match(1, m);
    d = rb_reg_nth_match(2, m);

    if (!NIL_P(mon))
	set_hash("mon", str2num(mon));
    set_hash("mday", str2num(d));

    return 1;
}

static int
parse_iso23(VALUE str, VALUE hash)
{
    static const char pat_source[] = "--(\\d{2})?-(\\d{2})\\b";
    static VALUE pat = Qnil;

    REGCOMP_0(pat);
    SUBS(str, pat, parse_iso23_cb);
}

static int
parse_iso24_cb(VALUE m, VALUE hash)
{
    VALUE mon, d;

    mon = rb_reg_nth_match(1, m);
    d = rb_reg_nth_match(2, m);

    set_hash("mon", str2num(mon));
    if (!NIL_P(d))
	set_hash("mday", str2num(d));

    return 1;
}

static int
parse_iso24(VALUE str, VALUE hash)
{
    static const char pat_source[] = "--(\\d{2})(\\d{2})?\\b";
    static VALUE pat = Qnil;

    REGCOMP_0(pat);
    SUBS(str, pat, parse_iso24_cb);
}

static int
parse_iso25_cb(VALUE m, VALUE hash)
{
    VALUE y, d;

    y = rb_reg_nth_match(1, m);
    d = rb_reg_nth_match(2, m);

    set_hash("year", str2num(y));
    set_hash("yday", str2num(d));

    return 1;
}

static int
parse_iso25(VALUE str, VALUE hash)
{
    static const char pat0_source[] = "[,.](\\d{2}|\\d{4})-\\d{3}\\b";
    static VALUE pat0 = Qnil;
    static const char pat_source[] = "\\b(\\d{2}|\\d{4})-(\\d{3})\\b";
    static VALUE pat = Qnil;

    REGCOMP_0(pat0);
    REGCOMP_0(pat);

    if (!NIL_P(f_match(pat0, str)))
	return 0;
    SUBS(str, pat, parse_iso25_cb);
}

static int
parse_iso26_cb(VALUE m, VALUE hash)
{
    VALUE d;

    d = rb_reg_nth_match(1, m);
    set_hash("yday", str2num(d));

    return 1;
}
static int
parse_iso26(VALUE str, VALUE hash)
{
    static const char pat0_source[] = "\\d-\\d{3}\\b";
    static VALUE pat0 = Qnil;
    static const char pat_source[] = "\\b-(\\d{3})\\b";
    static VALUE pat = Qnil;

    REGCOMP_0(pat0);
    REGCOMP_0(pat);

    if (!NIL_P(f_match(pat0, str)))
	return 0;
    SUBS(str, pat, parse_iso26_cb);
}

static int
parse_iso2(VALUE str, VALUE hash)
{
    if (parse_iso21(str, hash))
	goto ok;
    if (parse_iso22(str, hash))
	goto ok;
    if (parse_iso23(str, hash))
	goto ok;
    if (parse_iso24(str, hash))
	goto ok;
    if (parse_iso25(str, hash))
	goto ok;
    if (parse_iso26(str, hash))
	goto ok;
    return 0;

  ok:
    return 1;
}

static int
gengo(int c)
{
    int e;

    switch (c) {
      case 'M': case 'm': e = 1867; break;
      case 'T': case 't': e = 1911; break;
      case 'S': case 's': e = 1925; break;
      case 'H': case 'h': e = 1988; break;
      default:  e = 0; break;
    }
    return e;
}

static int
parse_jis_cb(VALUE m, VALUE hash)
{
    VALUE e, y, mon, d;
    int ep;

    e = rb_reg_nth_match(1, m);
    y = rb_reg_nth_match(2, m);
    mon = rb_reg_nth_match(3, m);
    d = rb_reg_nth_match(4, m);

    ep = gengo(*RSTRING_PTR(e));

    set_hash("year", f_add(str2num(y), INT2FIX(ep)));
    set_hash("mon", str2num(mon));
    set_hash("mday", str2num(d));

    return 1;
}

static int
parse_jis(VALUE str, VALUE hash)
{
    static const char pat_source[] = "\\b([mtsh])(\\d+)\\.(\\d+)\\.(\\d+)";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    SUBS(str, pat, parse_jis_cb);
}

static int
parse_vms11_cb(VALUE m, VALUE hash)
{
    VALUE y, mon, d;

    d = rb_reg_nth_match(1, m);
    mon = rb_reg_nth_match(2, m);
    y = rb_reg_nth_match(3, m);

    mon = INT2FIX(mon_num(mon));

    s3e(hash, y, mon, d, 0);
    return 1;
}

static int
parse_vms11(VALUE str, VALUE hash)
{
    static const char pat_source[] =
	"('?-?\\d+)-(" ABBR_MONTHS ")[^-]*"
	"-('?-?\\d+)";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    SUBS(str, pat, parse_vms11_cb);
}

static int
parse_vms12_cb(VALUE m, VALUE hash)
{
    VALUE y, mon, d;

    mon = rb_reg_nth_match(1, m);
    d = rb_reg_nth_match(2, m);
    y = rb_reg_nth_match(3, m);

    mon = INT2FIX(mon_num(mon));

    s3e(hash, y, mon, d, 0);
    return 1;
}

static int
parse_vms12(VALUE str, VALUE hash)
{
    static const char pat_source[] =
	"\\b(" ABBR_MONTHS ")[^-]*"
	"-('?-?\\d+)(?:-('?-?\\d+))?";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    SUBS(str, pat, parse_vms12_cb);
}

static int
parse_vms(VALUE str, VALUE hash)
{
    if (parse_vms11(str, hash))
	goto ok;
    if (parse_vms12(str, hash))
	goto ok;
    return 0;

  ok:
    return 1;
}

static int
parse_sla_cb(VALUE m, VALUE hash)
{
    VALUE y, mon, d;

    y = rb_reg_nth_match(1, m);
    mon = rb_reg_nth_match(2, m);
    d = rb_reg_nth_match(3, m);

    s3e(hash, y, mon, d, 0);
    return 1;
}

static int
parse_sla(VALUE str, VALUE hash)
{
    static const char pat_source[] =
	"('?-?\\d+)/\\s*('?\\d+)(?:\\D\\s*('?-?\\d+))?";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    SUBS(str, pat, parse_sla_cb);
}

static int
parse_dot_cb(VALUE m, VALUE hash)
{
    VALUE y, mon, d;

    y = rb_reg_nth_match(1, m);
    mon = rb_reg_nth_match(2, m);
    d = rb_reg_nth_match(3, m);

    s3e(hash, y, mon, d, 0);
    return 1;
}

static int
parse_dot(VALUE str, VALUE hash)
{
    static const char pat_source[] =
	"('?-?\\d+)\\.\\s*('?\\d+)\\.\\s*('?-?\\d+)";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    SUBS(str, pat, parse_dot_cb);
}

static int
parse_year_cb(VALUE m, VALUE hash)
{
    VALUE y;

    y = rb_reg_nth_match(1, m);
    set_hash("year", str2num(y));
    return 1;
}

static int
parse_year(VALUE str, VALUE hash)
{
    static const char pat_source[] = "'(\\d+)\\b";
    static VALUE pat = Qnil;

    REGCOMP_0(pat);
    SUBS(str, pat, parse_year_cb);
}

static int
parse_mon_cb(VALUE m, VALUE hash)
{
    VALUE mon;

    mon = rb_reg_nth_match(1, m);
    set_hash("mon", INT2FIX(mon_num(mon)));
    return 1;
}

static int
parse_mon(VALUE str, VALUE hash)
{
    static const char pat_source[] = "\\b(" ABBR_MONTHS ")\\S*";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    SUBS(str, pat, parse_mon_cb);
}

static int
parse_mday_cb(VALUE m, VALUE hash)
{
    VALUE d;

    d = rb_reg_nth_match(1, m);
    set_hash("mday", str2num(d));
    return 1;
}

static int
parse_mday(VALUE str, VALUE hash)
{
    static const char pat_source[] = "(\\d+)(st|nd|rd|th)\\b";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    SUBS(str, pat, parse_mday_cb);
}

static int
n2i(const char *s, long f, long w)
{
    long e, i;
    int v;

    e = f + w;
    v = 0;
    for (i = f; i < e; i++) {
	v *= 10;
	v += s[i] - '0';
    }
    return v;
}

static int
parse_ddd_cb(VALUE m, VALUE hash)
{
    VALUE s1, s2, s3, s4, s5;
    const char *cs2, *cs3, *cs5;
    long l2, l3, l4, l5;

    s1 = rb_reg_nth_match(1, m);
    s2 = rb_reg_nth_match(2, m);
    s3 = rb_reg_nth_match(3, m);
    s4 = rb_reg_nth_match(4, m);
    s5 = rb_reg_nth_match(5, m);

    cs2 = RSTRING_PTR(s2);
    l2 = RSTRING_LEN(s2);

    switch (l2) {
      case 2:
	if (NIL_P(s3) && !NIL_P(s4))
	    set_hash("sec",  INT2FIX(n2i(cs2, l2-2, 2)));
	else
	    set_hash("mday", INT2FIX(n2i(cs2,    0, 2)));
	break;
      case 4:
	if (NIL_P(s3) && !NIL_P(s4)) {
	    set_hash("sec",  INT2FIX(n2i(cs2, l2-2, 2)));
	    set_hash("min",  INT2FIX(n2i(cs2, l2-4, 2)));
	}
	else {
	    set_hash("mon",  INT2FIX(n2i(cs2,    0, 2)));
	    set_hash("mday", INT2FIX(n2i(cs2,    2, 2)));
	}
	break;
      case 6:
	if (NIL_P(s3) && !NIL_P(s4)) {
	    set_hash("sec",  INT2FIX(n2i(cs2, l2-2, 2)));
	    set_hash("min",  INT2FIX(n2i(cs2, l2-4, 2)));
	    set_hash("hour", INT2FIX(n2i(cs2, l2-6, 2)));
	}
	else {
	    int                  y = n2i(cs2,    0, 2);
	    if (!NIL_P(s1) && *RSTRING_PTR(s1) == '-')
		y = -y;
	    set_hash("year", INT2FIX(y));
	    set_hash("mon",  INT2FIX(n2i(cs2,    2, 2)));
	    set_hash("mday", INT2FIX(n2i(cs2,    4, 2)));
	}
	break;
      case 8:
      case 10:
      case 12:
      case 14:
	if (NIL_P(s3) && !NIL_P(s4)) {
	    set_hash("sec",  INT2FIX(n2i(cs2, l2-2, 2)));
	    set_hash("min",  INT2FIX(n2i(cs2, l2-4, 2)));
	    set_hash("hour", INT2FIX(n2i(cs2, l2-6, 2)));
	    set_hash("mday", INT2FIX(n2i(cs2, l2-8, 2)));
	    if (l2 >= 10)
		set_hash("mon", INT2FIX(n2i(cs2, l2-10, 2)));
	    if (l2 == 12) {
		int y = n2i(cs2, l2-12, 2);
		if (!NIL_P(s1) && *RSTRING_PTR(s1) == '-')
		    y = -y;
		set_hash("year", INT2FIX(y));
	    }
	    if (l2 == 14) {
		int y = n2i(cs2, l2-14, 4);
		if (!NIL_P(s1) && *RSTRING_PTR(s1) == '-')
		    y = -y;
		set_hash("year", INT2FIX(y));
		set_hash("_comp", Qfalse);
	    }
	}
	else {
	    int                  y = n2i(cs2,    0, 4);
	    if (!NIL_P(s1) && *RSTRING_PTR(s1) == '-')
		y = -y;
	    set_hash("year", INT2FIX(y));
	    set_hash("mon",  INT2FIX(n2i(cs2,    4, 2)));
	    set_hash("mday", INT2FIX(n2i(cs2,    6, 2)));
	    if (l2 >= 10)
		set_hash("hour", INT2FIX(n2i(cs2,    8, 2)));
	    if (l2 >= 12)
		set_hash("min",  INT2FIX(n2i(cs2,   10, 2)));
	    if (l2 >= 14)
		set_hash("sec",  INT2FIX(n2i(cs2,   12, 2)));
	    set_hash("_comp", Qfalse);
	}
	break;
      case 3:
	if (NIL_P(s3) && !NIL_P(s4)) {
	    set_hash("sec",  INT2FIX(n2i(cs2, l2-2, 2)));
	    set_hash("min",  INT2FIX(n2i(cs2, l2-3, 1)));
	}
	else
	    set_hash("yday", INT2FIX(n2i(cs2,    0, 3)));
	break;
      case 5:
	if (NIL_P(s3) && !NIL_P(s4)) {
	    set_hash("sec",  INT2FIX(n2i(cs2, l2-2, 2)));
	    set_hash("min",  INT2FIX(n2i(cs2, l2-4, 2)));
	    set_hash("hour", INT2FIX(n2i(cs2, l2-5, 1)));
	}
	else {
	    int                  y = n2i(cs2,    0, 2);
	    if (!NIL_P(s1) && *RSTRING_PTR(s1) == '-')
		y = -y;
	    set_hash("year", INT2FIX(y));
	    set_hash("yday", INT2FIX(n2i(cs2,    2, 3)));
	}
	break;
      case 7:
	if (NIL_P(s3) && !NIL_P(s4)) {
	    set_hash("sec",  INT2FIX(n2i(cs2, l2-2, 2)));
	    set_hash("min",  INT2FIX(n2i(cs2, l2-4, 2)));
	    set_hash("hour", INT2FIX(n2i(cs2, l2-6, 2)));
	    set_hash("mday", INT2FIX(n2i(cs2, l2-7, 1)));
	}
	else {
	    int                  y = n2i(cs2,    0, 4);
	    if (!NIL_P(s1) && *RSTRING_PTR(s1) == '-')
		y = -y;
	    set_hash("year", INT2FIX(y));
	    set_hash("yday", INT2FIX(n2i(cs2,    4, 3)));
	}
	break;
    }
    RB_GC_GUARD(s2);
    if (!NIL_P(s3)) {
	cs3 = RSTRING_PTR(s3);
	l3 = RSTRING_LEN(s3);

	if (!NIL_P(s4)) {
	    switch (l3) {
	      case 2:
	      case 4:
	      case 6:
		set_hash("sec", INT2FIX(n2i(cs3, l3-2, 2)));
		if (l3 >= 4)
		    set_hash("min", INT2FIX(n2i(cs3, l3-4, 2)));
		if (l3 >= 6)
		    set_hash("hour", INT2FIX(n2i(cs3, l3-6, 2)));
		break;
	    }
	}
	else {
	    switch (l3) {
	      case 2:
	      case 4:
	      case 6:
		set_hash("hour", INT2FIX(n2i(cs3, 0, 2)));
		if (l3 >= 4)
		    set_hash("min", INT2FIX(n2i(cs3, 2, 2)));
		if (l3 >= 6)
		    set_hash("sec", INT2FIX(n2i(cs3, 4, 2)));
		break;
	    }
	}
	RB_GC_GUARD(s3);
    }
    if (!NIL_P(s4)) {
	l4 = RSTRING_LEN(s4);

	set_hash("sec_fraction",
		 rb_rational_new2(str2num(s4),
				  f_expt(INT2FIX(10), LONG2NUM(l4))));
    }
    if (!NIL_P(s5)) {
	cs5 = RSTRING_PTR(s5);
	l5 = RSTRING_LEN(s5);

	set_hash("zone", s5);

	if (*cs5 == '[') {
	    char *buf = ALLOCA_N(char, l5 + 1);
	    char *s1, *s2, *s3;
	    VALUE zone;

	    memcpy(buf, cs5, l5);
	    buf[l5 - 1] = '\0';

	    s1 = buf + 1;
	    s2 = strchr(buf, ':');
	    if (s2) {
		*s2 = '\0';
		s2++;
	    }
	    if (s2)
		s3 = s2;
	    else
		s3 = s1;
	    zone = rb_str_new2(s3);
	    set_hash("zone", zone);
	    if (isdigit(*s1))
		*--s1 = '+';
	    set_hash("offset", date_zone_to_diff(rb_str_new2(s1)));
	}
	RB_GC_GUARD(s5);
    }

    return 1;
}

static int
parse_ddd(VALUE str, VALUE hash)
{
    static const char pat_source[] =
		"([-+]?)(\\d{2,14})"
		  "(?:"
		    "\\s*"
		    "t?"
		    "\\s*"
		    "(\\d{2,6})?(?:[,.](\\d*))?"
		  ")?"
		  "(?:"
		    "\\s*"
		    "("
		      "z\\b"
		    "|"
		      "[-+]\\d{1,4}\\b"
		    "|"
		      "\\[[-+]?\\d[^\\]]*\\]"
		    ")"
		")?";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    SUBS(str, pat, parse_ddd_cb);
}

static int
parse_bc_cb(VALUE m, VALUE hash)
{
    VALUE y;

    y = ref_hash("year");
    if (!NIL_P(y))
	set_hash("year", f_add(f_negate(y), INT2FIX(1)));

    return 1;
}

static int
parse_bc(VALUE str, VALUE hash)
{
    static const char pat_source[] =
	"\\b(bc\\b|bce\\b|b\\.c\\.|b\\.c\\.e\\.)";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    SUBS(str, pat, parse_bc_cb);
}

static int
parse_frag_cb(VALUE m, VALUE hash)
{
    VALUE s, n;

    s = rb_reg_nth_match(1, m);

    if (!NIL_P(ref_hash("hour")) && NIL_P(ref_hash("mday"))) {
	n = str2num(s);
	if (f_ge_p(n, INT2FIX(1)) &&
	    f_le_p(n, INT2FIX(31)))
	    set_hash("mday", n);
    }
    if (!NIL_P(ref_hash("mday")) && NIL_P(ref_hash("hour"))) {
	n = str2num(s);
	if (f_ge_p(n, INT2FIX(0)) &&
	    f_le_p(n, INT2FIX(24)))
	    set_hash("hour", n);
    }

    return 1;
}

static int
parse_frag(VALUE str, VALUE hash)
{
    static const char pat_source[] = "\\A\\s*(\\d{1,2})\\s*\\z";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    SUBS(str, pat, parse_frag_cb);
}

#define HAVE_ALPHA (1<<0)
#define HAVE_DIGIT (1<<1)
#define HAVE_DASH (1<<2)
#define HAVE_DOT (1<<3)
#define HAVE_SLASH (1<<4)

static unsigned
check_class(VALUE s)
{
    unsigned flags;
    long i;

    flags = 0;
    for (i = 0; i < RSTRING_LEN(s); i++) {
	if (isalpha(RSTRING_PTR(s)[i]))
	    flags |= HAVE_ALPHA;
	if (isdigit(RSTRING_PTR(s)[i]))
	    flags |= HAVE_DIGIT;
	if (RSTRING_PTR(s)[i] == '-')
	    flags |= HAVE_DASH;
	if (RSTRING_PTR(s)[i] == '.')
	    flags |= HAVE_DOT;
	if (RSTRING_PTR(s)[i] == '/')
	    flags |= HAVE_SLASH;
    }
    return flags;
}

#define HAVE_ELEM_P(x) ((check_class(str) & (x)) == (x))

VALUE
date__parse(VALUE str, VALUE comp)
{
    VALUE backref, hash;

    backref = rb_backref_get();
    rb_match_busy(backref);

    {
	static const char pat_source[] = "[^-+',./:@[:alnum:]\\[\\]]+";
	static VALUE pat = Qnil;

	REGCOMP_0(pat);
	str = rb_str_dup(str);
	f_gsub_bang(str, pat, asp_string());
    }

    hash = rb_hash_new();
    set_hash("_comp", comp);

    if (HAVE_ELEM_P(HAVE_ALPHA))
	parse_day(str, hash);
    if (HAVE_ELEM_P(HAVE_DIGIT))
	parse_time(str, hash);

    if (HAVE_ELEM_P(HAVE_ALPHA|HAVE_DIGIT))
	if (parse_eu(str, hash))
	    goto ok;
    if (HAVE_ELEM_P(HAVE_ALPHA|HAVE_DIGIT))
	if (parse_us(str, hash))
	    goto ok;
    if (HAVE_ELEM_P(HAVE_DIGIT|HAVE_DASH))
	if (parse_iso(str, hash))
	    goto ok;
    if (HAVE_ELEM_P(HAVE_DIGIT|HAVE_DOT))
	if (parse_jis(str, hash))
	    goto ok;
    if (HAVE_ELEM_P(HAVE_ALPHA|HAVE_DIGIT|HAVE_DASH))
	if (parse_vms(str, hash))
	    goto ok;
    if (HAVE_ELEM_P(HAVE_DIGIT|HAVE_SLASH))
	if (parse_sla(str, hash))
	    goto ok;
    if (HAVE_ELEM_P(HAVE_DIGIT|HAVE_DOT))
	if (parse_dot(str, hash))
	    goto ok;
    if (HAVE_ELEM_P(HAVE_DIGIT))
	if (parse_iso2(str, hash))
	    goto ok;
    if (HAVE_ELEM_P(HAVE_DIGIT))
	if (parse_year(str, hash))
	    goto ok;
    if (HAVE_ELEM_P(HAVE_ALPHA))
	if (parse_mon(str, hash))
	    goto ok;
    if (HAVE_ELEM_P(HAVE_DIGIT))
	if (parse_mday(str, hash))
	    goto ok;
    if (HAVE_ELEM_P(HAVE_DIGIT))
	if (parse_ddd(str, hash))
	    goto ok;

  ok:
    if (HAVE_ELEM_P(HAVE_ALPHA))
	parse_bc(str, hash);
    if (HAVE_ELEM_P(HAVE_DIGIT))
	parse_frag(str, hash);

    {
	if (RTEST(ref_hash("_comp"))) {
	    VALUE y;

	    y = ref_hash("cwyear");
	    if (!NIL_P(y))
		if (f_ge_p(y, INT2FIX(0)) && f_le_p(y, INT2FIX(99))) {
		    if (f_ge_p(y, INT2FIX(69)))
			set_hash("cwyear", f_add(y, INT2FIX(1900)));
		    else
			set_hash("cwyear", f_add(y, INT2FIX(2000)));
		}
	    y = ref_hash("year");
	    if (!NIL_P(y))
		if (f_ge_p(y, INT2FIX(0)) && f_le_p(y, INT2FIX(99))) {
		    if (f_ge_p(y, INT2FIX(69)))
			set_hash("year", f_add(y, INT2FIX(1900)));
		    else
			set_hash("year", f_add(y, INT2FIX(2000)));
		}
	}
    }

    del_hash("_comp");

    {
	VALUE zone = ref_hash("zone");
	if (!NIL_P(zone) && NIL_P(ref_hash("offset")))
	    set_hash("offset", date_zone_to_diff(zone));
    }

    rb_backref_set(backref);

    return hash;
}

static VALUE
comp_year69(VALUE y)
{
    if (f_ge_p(y, INT2FIX(69)))
	return f_add(y, INT2FIX(1900));
    return f_add(y, INT2FIX(2000));
}

static VALUE
comp_year50(VALUE y)
{
    if (f_ge_p(y, INT2FIX(50)))
	return f_add(y, INT2FIX(1900));
    return f_add(y, INT2FIX(2000));
}

static VALUE
sec_fraction(VALUE f)
{
    return rb_rational_new2(str2num(f),
			    f_expt(INT2FIX(10),
				   LONG2NUM(RSTRING_LEN(f))));
}

#define SNUM 14

static int
iso8601_ext_datetime_cb(VALUE m, VALUE hash)
{
    VALUE s[SNUM + 1], y;

    {
	int i;
	s[0] = Qnil;
	for (i = 1; i <= SNUM; i++)
	    s[i] = rb_reg_nth_match(i, m);
    }

    if (!NIL_P(s[3])) {
	set_hash("mday", str2num(s[3]));
	if (strcmp(RSTRING_PTR(s[1]), "-") != 0) {
	    y = str2num(s[1]);
	    if (RSTRING_LEN(s[1]) < 4)
		y = comp_year69(y);
	    set_hash("year", y);
	}
	if (NIL_P(s[2])) {
	    if (strcmp(RSTRING_PTR(s[1]), "-") != 0)
		return 0;
	}
	else
	    set_hash("mon", str2num(s[2]));
    }
    else if (!NIL_P(s[5])) {
	set_hash("yday", str2num(s[5]));
	if (!NIL_P(s[4])) {
	    y = str2num(s[4]);
	    if (RSTRING_LEN(s[4]) < 4)
		y = comp_year69(y);
	    set_hash("year", y);
	}
    }
    else if (!NIL_P(s[8])) {
	set_hash("cweek", str2num(s[7]));
	set_hash("cwday", str2num(s[8]));
	if (!NIL_P(s[6])) {
	    y = str2num(s[6]);
	    if (RSTRING_LEN(s[6]) < 4)
		y = comp_year69(y);
	    set_hash("cwyear", y);
	}
    }
    else if (!NIL_P(s[9])) {
	set_hash("cwday", str2num(s[9]));
    }
    if (!NIL_P(s[10])) {
	set_hash("hour", str2num(s[10]));
	set_hash("min", str2num(s[11]));
	if (!NIL_P(s[12]))
	    set_hash("sec", str2num(s[12]));
    }
    if (!NIL_P(s[13])) {
	set_hash("sec_fraction", sec_fraction(s[13]));
    }
    if (!NIL_P(s[14])) {
	set_hash("zone", s[14]);
	set_hash("offset", date_zone_to_diff(s[14]));
    }

    return 1;
}

static int
iso8601_ext_datetime(VALUE str, VALUE hash)
{
    static const char pat_source[] =
	"\\A\\s*(?:([-+]?\\d{2,}|-)-(\\d{2})?-(\\d{2})|"
		"([-+]?\\d{2,})?-(\\d{3})|"
		"(\\d{4}|\\d{2})?-w(\\d{2})-(\\d)|"
		"-w-(\\d))"
	"(?:t"
	"(\\d{2}):(\\d{2})(?::(\\d{2})(?:[,.](\\d+))?)?"
	"(z|[-+]\\d{2}(?::?\\d{2})?)?)?\\s*\\z";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    MATCH(str, pat, iso8601_ext_datetime_cb);
}

#undef SNUM
#define SNUM 17

static int
iso8601_bas_datetime_cb(VALUE m, VALUE hash)
{
    VALUE s[SNUM + 1], y;

    {
	int i;
	s[0] = Qnil;
	for (i = 1; i <= SNUM; i++)
	    s[i] = rb_reg_nth_match(i, m);
    }

    if (!NIL_P(s[3])) {
	set_hash("mday", str2num(s[3]));
	if (strcmp(RSTRING_PTR(s[1]), "--") != 0) {
	    y = str2num(s[1]);
	    if (RSTRING_LEN(s[1]) < 4)
		y = comp_year69(y);
	    set_hash("year", y);
	}
	if (*RSTRING_PTR(s[2]) == '-') {
	    if (strcmp(RSTRING_PTR(s[1]), "--") != 0)
		return 0;
	}
	else
	    set_hash("mon", str2num(s[2]));
    }
    else if (!NIL_P(s[5])) {
	set_hash("yday", str2num(s[5]));
	y = str2num(s[4]);
	if (RSTRING_LEN(s[4]) < 4)
	    y = comp_year69(y);
	set_hash("year", y);
    }
    else if (!NIL_P(s[6])) {
	set_hash("yday", str2num(s[6]));
    }
    else if (!NIL_P(s[9])) {
	set_hash("cweek", str2num(s[8]));
	set_hash("cwday", str2num(s[9]));
	y = str2num(s[7]);
	if (RSTRING_LEN(s[7]) < 4)
	    y = comp_year69(y);
	set_hash("cwyear", y);
    }
    else if (!NIL_P(s[11])) {
	set_hash("cweek", str2num(s[10]));
	set_hash("cwday", str2num(s[11]));
    }
    else if (!NIL_P(s[12])) {
	set_hash("cwday", str2num(s[12]));
    }
    if (!NIL_P(s[13])) {
	set_hash("hour", str2num(s[13]));
	set_hash("min", str2num(s[14]));
	if (!NIL_P(s[15]))
	    set_hash("sec", str2num(s[15]));
    }
    if (!NIL_P(s[16])) {
	set_hash("sec_fraction", sec_fraction(s[16]));
    }
    if (!NIL_P(s[17])) {
	set_hash("zone", s[17]);
	set_hash("offset", date_zone_to_diff(s[17]));
    }

    return 1;
}

static int
iso8601_bas_datetime(VALUE str, VALUE hash)
{
    static const char pat_source[] =
	"\\A\\s*(?:([-+]?(?:\\d{4}|\\d{2})|--)(\\d{2}|-)(\\d{2})|"
		   "([-+]?(?:\\d{4}|\\d{2}))(\\d{3})|"
		   "-(\\d{3})|"
		   "(\\d{4}|\\d{2})w(\\d{2})(\\d)|"
		   "-w(\\d{2})(\\d)|"
		   "-w-(\\d))"
	"(?:t?"
	"(\\d{2})(\\d{2})(?:(\\d{2})(?:[,.](\\d+))?)?"
	"(z|[-+]\\d{2}(?:\\d{2})?)?)?\\s*\\z";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    MATCH(str, pat, iso8601_bas_datetime_cb);
}

#undef SNUM
#define SNUM 5

static int
iso8601_ext_time_cb(VALUE m, VALUE hash)
{
    VALUE s[SNUM + 1];

    {
	int i;
	s[0] = Qnil;
	for (i = 1; i <= SNUM; i++)
	    s[i] = rb_reg_nth_match(i, m);
    }

    set_hash("hour", str2num(s[1]));
    set_hash("min", str2num(s[2]));
    if (!NIL_P(s[3]))
	set_hash("sec", str2num(s[3]));
    if (!NIL_P(s[4]))
	set_hash("sec_fraction", sec_fraction(s[4]));
    if (!NIL_P(s[5])) {
	set_hash("zone", s[5]);
	set_hash("offset", date_zone_to_diff(s[5]));
    }

    return 1;
}

#define iso8601_bas_time_cb iso8601_ext_time_cb

static int
iso8601_ext_time(VALUE str, VALUE hash)
{
    static const char pat_source[] =
	"\\A\\s*(?:(\\d{2}):(\\d{2})(?::(\\d{2})(?:[,.](\\d+))?)?"
	"(z|[-+]\\d{2}(:?\\d{2})?)?)?\\s*\\z";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    MATCH(str, pat, iso8601_ext_time_cb);
}

static int
iso8601_bas_time(VALUE str, VALUE hash)
{
    static const char pat_source[] =
	"\\A\\s*(?:(\\d{2})(\\d{2})(?:(\\d{2})(?:[,.](\\d+))?)?"
	"(z|[-+]\\d{2}(\\d{2})?)?)?\\s*\\z";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    MATCH(str, pat, iso8601_bas_time_cb);
}

VALUE
date__iso8601(VALUE str)
{
    VALUE backref, hash;

    backref = rb_backref_get();
    rb_match_busy(backref);

    hash = rb_hash_new();

    if (iso8601_ext_datetime(str, hash))
	goto ok;
    if (iso8601_bas_datetime(str, hash))
	goto ok;
    if (iso8601_ext_time(str, hash))
	goto ok;
    if (iso8601_bas_time(str, hash))
	goto ok;

  ok:
    rb_backref_set(backref);

    return hash;
}

#undef SNUM
#define SNUM 8

static int
rfc3339_cb(VALUE m, VALUE hash)
{
    VALUE s[SNUM + 1];

    {
	int i;
	s[0] = Qnil;
	for (i = 1; i <= SNUM; i++)
	    s[i] = rb_reg_nth_match(i, m);
    }

    set_hash("year", str2num(s[1]));
    set_hash("mon", str2num(s[2]));
    set_hash("mday", str2num(s[3]));
    set_hash("hour", str2num(s[4]));
    set_hash("min", str2num(s[5]));
    set_hash("sec", str2num(s[6]));
    set_hash("zone", s[8]);
    set_hash("offset", date_zone_to_diff(s[8]));
    if (!NIL_P(s[7]))
	set_hash("sec_fraction", sec_fraction(s[7]));

    return 1;
}

static int
rfc3339(VALUE str, VALUE hash)
{
    static const char pat_source[] =
	"\\A\\s*(-?\\d{4})-(\\d{2})-(\\d{2})"
	"(?:t|\\s)"
	"(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d+))?"
	"(z|[-+]\\d{2}:\\d{2})\\s*\\z";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    MATCH(str, pat, rfc3339_cb);
}

VALUE
date__rfc3339(VALUE str)
{
    VALUE backref, hash;

    backref = rb_backref_get();
    rb_match_busy(backref);

    hash = rb_hash_new();
    rfc3339(str, hash);
    rb_backref_set(backref);
    return hash;
}

#undef SNUM
#define SNUM 8

static int
xmlschema_datetime_cb(VALUE m, VALUE hash)
{
    VALUE s[SNUM + 1];

    {
	int i;
	s[0] = Qnil;
	for (i = 1; i <= SNUM; i++)
	    s[i] = rb_reg_nth_match(i, m);
    }

    set_hash("year", str2num(s[1]));
    if (!NIL_P(s[2]))
	set_hash("mon", str2num(s[2]));
    if (!NIL_P(s[3]))
	set_hash("mday", str2num(s[3]));
    if (!NIL_P(s[4]))
	set_hash("hour", str2num(s[4]));
    if (!NIL_P(s[5]))
	set_hash("min", str2num(s[5]));
    if (!NIL_P(s[6]))
	set_hash("sec", str2num(s[6]));
    if (!NIL_P(s[7]))
	set_hash("sec_fraction", sec_fraction(s[7]));
    if (!NIL_P(s[8])) {
	set_hash("zone", s[8]);
	set_hash("offset", date_zone_to_diff(s[8]));
    }

    return 1;
}

static int
xmlschema_datetime(VALUE str, VALUE hash)
{
    static const char pat_source[] =
	"\\A\\s*(-?\\d{4,})(?:-(\\d{2})(?:-(\\d{2}))?)?"
	"(?:t"
	  "(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d+))?)?"
	"(z|[-+]\\d{2}:\\d{2})?\\s*\\z";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    MATCH(str, pat, xmlschema_datetime_cb);
}

#undef SNUM
#define SNUM 5

static int
xmlschema_time_cb(VALUE m, VALUE hash)
{
    VALUE s[SNUM + 1];

    {
	int i;
	s[0] = Qnil;
	for (i = 1; i <= SNUM; i++)
	    s[i] = rb_reg_nth_match(i, m);
    }

    set_hash("hour", str2num(s[1]));
    set_hash("min", str2num(s[2]));
    if (!NIL_P(s[3]))
	set_hash("sec", str2num(s[3]));
    if (!NIL_P(s[4]))
	set_hash("sec_fraction", sec_fraction(s[4]));
    if (!NIL_P(s[5])) {
	set_hash("zone", s[5]);
	set_hash("offset", date_zone_to_diff(s[5]));
    }

    return 1;
}

static int
xmlschema_time(VALUE str, VALUE hash)
{
    static const char pat_source[] =
	"\\A\\s*(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d+))?"
	"(z|[-+]\\d{2}:\\d{2})?\\s*\\z";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    MATCH(str, pat, xmlschema_time_cb);
}

#undef SNUM
#define SNUM 4

static int
xmlschema_trunc_cb(VALUE m, VALUE hash)
{
    VALUE s[SNUM + 1];

    {
	int i;
	s[0] = Qnil;
	for (i = 1; i <= SNUM; i++)
	    s[i] = rb_reg_nth_match(i, m);
    }

    if (!NIL_P(s[1]))
	set_hash("mon", str2num(s[1]));
    if (!NIL_P(s[2]))
	set_hash("mday", str2num(s[2]));
    if (!NIL_P(s[3]))
	set_hash("mday", str2num(s[3]));
    if (!NIL_P(s[4])) {
	set_hash("zone", s[4]);
	set_hash("offset", date_zone_to_diff(s[4]));
    }

    return 1;
}

static int
xmlschema_trunc(VALUE str, VALUE hash)
{
    static const char pat_source[] =
	"\\A\\s*(?:--(\\d{2})(?:-(\\d{2}))?|---(\\d{2}))"
	"(z|[-+]\\d{2}:\\d{2})?\\s*\\z";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    MATCH(str, pat, xmlschema_trunc_cb);
}

VALUE
date__xmlschema(VALUE str)
{
    VALUE backref, hash;

    backref = rb_backref_get();
    rb_match_busy(backref);

    hash = rb_hash_new();

    if (xmlschema_datetime(str, hash))
	goto ok;
    if (xmlschema_time(str, hash))
	goto ok;
    if (xmlschema_trunc(str, hash))
	goto ok;

  ok:
    rb_backref_set(backref);

    return hash;
}

#undef SNUM
#define SNUM 8

static int
rfc2822_cb(VALUE m, VALUE hash)
{
    VALUE s[SNUM + 1], y;

    {
	int i;
	s[0] = Qnil;
	for (i = 1; i <= SNUM; i++)
	    s[i] = rb_reg_nth_match(i, m);
    }

    if (!NIL_P(s[1])) {
	set_hash("wday", INT2FIX(day_num(s[1])));
    }
    set_hash("mday", str2num(s[2]));
    set_hash("mon", INT2FIX(mon_num(s[3])));
    y = str2num(s[4]);
    if (RSTRING_LEN(s[4]) < 4)
	y = comp_year50(y);
    set_hash("year", y);
    set_hash("hour", str2num(s[5]));
    set_hash("min", str2num(s[6]));
    if (!NIL_P(s[7]))
	set_hash("sec", str2num(s[7]));
    set_hash("zone", s[8]);
    set_hash("offset", date_zone_to_diff(s[8]));

    return 1;
}

static int
rfc2822(VALUE str, VALUE hash)
{
    static const char pat_source[] =
	"\\A\\s*(?:(" ABBR_DAYS ")\\s*,\\s+)?"
	"(\\d{1,2})\\s+"
	"(" ABBR_MONTHS ")\\s+"
	"(-?\\d{2,})\\s+"
	"(\\d{2}):(\\d{2})(?::(\\d{2}))?\\s*"
	"([-+]\\d{4}|ut|gmt|e[sd]t|c[sd]t|m[sd]t|p[sd]t|[a-ik-z])\\s*\\z";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    MATCH(str, pat, rfc2822_cb);
}

VALUE
date__rfc2822(VALUE str)
{
    VALUE backref, hash;

    backref = rb_backref_get();
    rb_match_busy(backref);

    hash = rb_hash_new();
    rfc2822(str, hash);
    rb_backref_set(backref);
    return hash;
}

#undef SNUM
#define SNUM 8

static int
httpdate_type1_cb(VALUE m, VALUE hash)
{
    VALUE s[SNUM + 1];

    {
	int i;
	s[0] = Qnil;
	for (i = 1; i <= SNUM; i++)
	    s[i] = rb_reg_nth_match(i, m);
    }

    set_hash("wday", INT2FIX(day_num(s[1])));
    set_hash("mday", str2num(s[2]));
    set_hash("mon", INT2FIX(mon_num(s[3])));
    set_hash("year", str2num(s[4]));
    set_hash("hour", str2num(s[5]));
    set_hash("min", str2num(s[6]));
    set_hash("sec", str2num(s[7]));
    set_hash("zone", s[8]);
    set_hash("offset", INT2FIX(0));

    return 1;
}

static int
httpdate_type1(VALUE str, VALUE hash)
{
    static const char pat_source[] =
	"\\A\\s*(" ABBR_DAYS ")\\s*,\\s+"
	"(\\d{2})\\s+"
	"(" ABBR_MONTHS ")\\s+"
	"(-?\\d{4})\\s+"
	"(\\d{2}):(\\d{2}):(\\d{2})\\s+"
	"(gmt)\\s*\\z";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    MATCH(str, pat, httpdate_type1_cb);
}

#undef SNUM
#define SNUM 8

static int
httpdate_type2_cb(VALUE m, VALUE hash)
{
    VALUE s[SNUM + 1], y;

    {
	int i;
	s[0] = Qnil;
	for (i = 1; i <= SNUM; i++)
	    s[i] = rb_reg_nth_match(i, m);
    }

    set_hash("wday", INT2FIX(day_num(s[1])));
    set_hash("mday", str2num(s[2]));
    set_hash("mon", INT2FIX(mon_num(s[3])));
    y = str2num(s[4]);
    if (f_ge_p(y, INT2FIX(0)) && f_le_p(y, INT2FIX(99)))
	y = comp_year69(y);
    set_hash("year", y);
    set_hash("hour", str2num(s[5]));
    set_hash("min", str2num(s[6]));
    set_hash("sec", str2num(s[7]));
    set_hash("zone", s[8]);
    set_hash("offset", INT2FIX(0));

    return 1;
}

static int
httpdate_type2(VALUE str, VALUE hash)
{
    static const char pat_source[] =
	"\\A\\s*(" DAYS ")\\s*,\\s+"
	"(\\d{2})\\s*-\\s*"
	"(" ABBR_MONTHS ")\\s*-\\s*"
	"(\\d{2})\\s+"
	"(\\d{2}):(\\d{2}):(\\d{2})\\s+"
	"(gmt)\\s*\\z";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    MATCH(str, pat, httpdate_type2_cb);
}

#undef SNUM
#define SNUM 7

static int
httpdate_type3_cb(VALUE m, VALUE hash)
{
    VALUE s[SNUM + 1];

    {
	int i;
	s[0] = Qnil;
	for (i = 1; i <= SNUM; i++)
	    s[i] = rb_reg_nth_match(i, m);
    }

    set_hash("wday", INT2FIX(day_num(s[1])));
    set_hash("mon", INT2FIX(mon_num(s[2])));
    set_hash("mday", str2num(s[3]));
    set_hash("hour", str2num(s[4]));
    set_hash("min", str2num(s[5]));
    set_hash("sec", str2num(s[6]));
    set_hash("year", str2num(s[7]));

    return 1;
}

static int
httpdate_type3(VALUE str, VALUE hash)
{
    static const char pat_source[] =
	"\\A\\s*(" ABBR_DAYS ")\\s+"
	"(" ABBR_MONTHS ")\\s+"
	"(\\d{1,2})\\s+"
	"(\\d{2}):(\\d{2}):(\\d{2})\\s+"
	"(\\d{4})\\s*\\z";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    MATCH(str, pat, httpdate_type3_cb);
}

VALUE
date__httpdate(VALUE str)
{
    VALUE backref, hash;

    backref = rb_backref_get();
    rb_match_busy(backref);

    hash = rb_hash_new();

    if (httpdate_type1(str, hash))
	goto ok;
    if (httpdate_type2(str, hash))
	goto ok;
    if (httpdate_type3(str, hash))
	goto ok;

  ok:
    rb_backref_set(backref);

    return hash;
}

#undef SNUM
#define SNUM 9

static int
jisx0301_cb(VALUE m, VALUE hash)
{
    VALUE s[SNUM + 1];
    int ep;

    {
	int i;
	s[0] = Qnil;
	for (i = 1; i <= SNUM; i++)
	    s[i] = rb_reg_nth_match(i, m);
    }

    ep = gengo(NIL_P(s[1]) ? 'h' : *RSTRING_PTR(s[1]));
    set_hash("year", f_add(str2num(s[2]), INT2FIX(ep)));
    set_hash("mon", str2num(s[3]));
    set_hash("mday", str2num(s[4]));
    if (!NIL_P(s[5])) {
	set_hash("hour", str2num(s[5]));
	if (!NIL_P(s[6]))
	    set_hash("min", str2num(s[6]));
	if (!NIL_P(s[7]))
	    set_hash("sec", str2num(s[7]));
    }
    if (!NIL_P(s[8]))
	set_hash("sec_fraction", sec_fraction(s[8]));
    if (!NIL_P(s[9])) {
	set_hash("zone", s[9]);
	set_hash("offset", date_zone_to_diff(s[9]));
    }

    return 1;
}

static int
jisx0301(VALUE str, VALUE hash)
{
    static const char pat_source[] =
	"\\A\\s*([mtsh])?(\\d{2})\\.(\\d{2})\\.(\\d{2})"
	"(?:t"
	"(?:(\\d{2}):(\\d{2})(?::(\\d{2})(?:[,.](\\d*))?)?"
	"(z|[-+]\\d{2}(?::?\\d{2})?)?)?)?\\s*\\z";
    static VALUE pat = Qnil;

    REGCOMP_I(pat);
    MATCH(str, pat, jisx0301_cb);
}

VALUE
date__jisx0301(VALUE str)
{
    VALUE backref, hash;

    backref = rb_backref_get();
    rb_match_busy(backref);

    hash = rb_hash_new();
    if (jisx0301(str, hash))
	goto ok;
    hash = date__iso8601(str);

  ok:
    rb_backref_set(backref);
    return hash;
}

/*
Local variables:
c-file-style: "ruby"
End:
*/
