From fcbf63e62c627deae76c1b8cb8c0876c536ed811 Mon Sep 17 00:00:00 2001 From: Jari Vetoniemi Date: Mon, 16 Mar 2020 18:49:26 +0900 Subject: Fresh start --- jni/ruby/ext/date/date_core.c | 9573 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 9573 insertions(+) create mode 100644 jni/ruby/ext/date/date_core.c (limited to 'jni/ruby/ext/date/date_core.c') diff --git a/jni/ruby/ext/date/date_core.c b/jni/ruby/ext/date/date_core.c new file mode 100644 index 0000000..07b65a7 --- /dev/null +++ b/jni/ruby/ext/date/date_core.c @@ -0,0 +1,9573 @@ +/* + date_core.c: Coded by Tadayoshi Funaba 2010-2014 +*/ + +#include "ruby.h" +#include "ruby/encoding.h" +#include +#include +#if defined(HAVE_SYS_TIME_H) +#include +#endif + +#define NDEBUG +#include + +#ifdef RUBY_EXTCONF_H +#include RUBY_EXTCONF_H +#endif + +#define USE_PACK + +static ID id_cmp, id_le_p, id_ge_p, id_eqeq_p; +static VALUE cDate, cDateTime; +static VALUE half_days_in_day, day_in_nanoseconds; +static double positive_inf, negative_inf; + +#define f_boolcast(x) ((x) ? Qtrue : Qfalse) + +#define f_abs(x) rb_funcall(x, rb_intern("abs"), 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_quo(x,y) rb_funcall(x, rb_intern("quo"), 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_remainder(x,y) rb_funcall(x, rb_intern("remainder"), 1, y) +#define f_expt(x,y) rb_funcall(x, rb_intern("**"), 1, y) +#define f_floor(x) rb_funcall(x, rb_intern("floor"), 0) +#define f_ceil(x) rb_funcall(x, rb_intern("ceil"), 0) +#define f_truncate(x) rb_funcall(x, rb_intern("truncate"), 0) +#define f_round(x) rb_funcall(x, rb_intern("round"), 0) + +#define f_to_i(x) rb_funcall(x, rb_intern("to_i"), 0) +#define f_to_r(x) rb_funcall(x, rb_intern("to_r"), 0) +#define f_to_s(x) rb_funcall(x, rb_intern("to_s"), 0) +#define f_inspect(x) rb_funcall(x, rb_intern("inspect"), 0) + +#define f_add3(x,y,z) f_add(f_add(x, y), z) +#define f_sub3(x,y,z) f_sub(f_sub(x, y), z) + +inline static VALUE +f_cmp(VALUE x, VALUE y) +{ + if (FIXNUM_P(x) && FIXNUM_P(y)) { + long c = FIX2LONG(x) - FIX2LONG(y); + if (c > 0) + c = 1; + else if (c < 0) + c = -1; + return INT2FIX(c); + } + return rb_funcall(x, id_cmp, 1, y); +} + +inline static VALUE +f_lt_p(VALUE x, VALUE y) +{ + if (FIXNUM_P(x) && FIXNUM_P(y)) + return f_boolcast(FIX2LONG(x) < FIX2LONG(y)); + return rb_funcall(x, '<', 1, y); +} + +inline static VALUE +f_gt_p(VALUE x, VALUE y) +{ + if (FIXNUM_P(x) && FIXNUM_P(y)) + return f_boolcast(FIX2LONG(x) > FIX2LONG(y)); + return rb_funcall(x, '>', 1, y); +} + +inline static VALUE +f_le_p(VALUE x, VALUE y) +{ + if (FIXNUM_P(x) && FIXNUM_P(y)) + return f_boolcast(FIX2LONG(x) <= FIX2LONG(y)); + return rb_funcall(x, id_le_p, 1, y); +} + +inline static VALUE +f_ge_p(VALUE x, VALUE y) +{ + if (FIXNUM_P(x) && FIXNUM_P(y)) + return f_boolcast(FIX2LONG(x) >= FIX2LONG(y)); + return rb_funcall(x, rb_intern(">="), 1, y); +} + +inline static VALUE +f_eqeq_p(VALUE x, VALUE y) +{ + if (FIXNUM_P(x) && FIXNUM_P(y)) + return f_boolcast(FIX2LONG(x) == FIX2LONG(y)); + return rb_funcall(x, rb_intern("=="), 1, y); +} + +inline static VALUE +f_zero_p(VALUE x) +{ + switch (TYPE(x)) { + case T_FIXNUM: + return f_boolcast(FIX2LONG(x) == 0); + case T_BIGNUM: + return Qfalse; + case T_RATIONAL: + { + VALUE num = rb_rational_num(x); + return f_boolcast(FIXNUM_P(num) && FIX2LONG(num) == 0); + } + } + return rb_funcall(x, id_eqeq_p, 1, INT2FIX(0)); +} + +#define f_nonzero_p(x) (!f_zero_p(x)) + +inline static VALUE +f_negative_p(VALUE x) +{ + if (FIXNUM_P(x)) + return f_boolcast(FIX2LONG(x) < 0); + return rb_funcall(x, '<', 1, INT2FIX(0)); +} + +#define f_positive_p(x) (!f_negative_p(x)) + +#define f_ajd(x) rb_funcall(x, rb_intern("ajd"), 0) +#define f_jd(x) rb_funcall(x, rb_intern("jd"), 0) +#define f_year(x) rb_funcall(x, rb_intern("year"), 0) +#define f_mon(x) rb_funcall(x, rb_intern("mon"), 0) +#define f_mday(x) rb_funcall(x, rb_intern("mday"), 0) +#define f_wday(x) rb_funcall(x, rb_intern("wday"), 0) +#define f_hour(x) rb_funcall(x, rb_intern("hour"), 0) +#define f_min(x) rb_funcall(x, rb_intern("min"), 0) +#define f_sec(x) rb_funcall(x, rb_intern("sec"), 0) + +/* copied from time.c */ +#define NDIV(x,y) (-(-((x)+1)/(y))-1) +#define NMOD(x,y) ((y)-(-((x)+1)%(y))-1) +#define DIV(n,d) ((n)<0 ? NDIV((n),(d)) : (n)/(d)) +#define MOD(n,d) ((n)<0 ? NMOD((n),(d)) : (n)%(d)) + +#define HAVE_JD (1 << 0) +#define HAVE_DF (1 << 1) +#define HAVE_CIVIL (1 << 2) +#define HAVE_TIME (1 << 3) +#define COMPLEX_DAT (1 << 7) + +#define have_jd_p(x) ((x)->flags & HAVE_JD) +#define have_df_p(x) ((x)->flags & HAVE_DF) +#define have_civil_p(x) ((x)->flags & HAVE_CIVIL) +#define have_time_p(x) ((x)->flags & HAVE_TIME) +#define complex_dat_p(x) ((x)->flags & COMPLEX_DAT) +#define simple_dat_p(x) (!complex_dat_p(x)) + +#define ITALY 2299161 /* 1582-10-15 */ +#define ENGLAND 2361222 /* 1752-09-14 */ +#define JULIAN positive_inf +#define GREGORIAN negative_inf +#define DEFAULT_SG ITALY + +#define UNIX_EPOCH_IN_CJD INT2FIX(2440588) /* 1970-01-01 */ + +#define MINUTE_IN_SECONDS 60 +#define HOUR_IN_SECONDS 3600 +#define DAY_IN_SECONDS 86400 +#define SECOND_IN_MILLISECONDS 1000 +#define SECOND_IN_NANOSECONDS 1000000000 + +#define JC_PERIOD0 1461 /* 365.25 * 4 */ +#define GC_PERIOD0 146097 /* 365.2425 * 400 */ +#define CM_PERIOD0 71149239 /* (lcm 7 1461 146097) */ +#define CM_PERIOD (0xfffffff / CM_PERIOD0 * CM_PERIOD0) +#define CM_PERIOD_JCY (CM_PERIOD / JC_PERIOD0 * 4) +#define CM_PERIOD_GCY (CM_PERIOD / GC_PERIOD0 * 400) + +#define REFORM_BEGIN_YEAR 1582 +#define REFORM_END_YEAR 1930 +#define REFORM_BEGIN_JD 2298874 /* ns 1582-01-01 */ +#define REFORM_END_JD 2426355 /* os 1930-12-31 */ + +#ifdef USE_PACK +#define SEC_WIDTH 6 +#define MIN_WIDTH 6 +#define HOUR_WIDTH 5 +#define MDAY_WIDTH 5 +#define MON_WIDTH 4 + +#define SEC_SHIFT 0 +#define MIN_SHIFT SEC_WIDTH +#define HOUR_SHIFT (MIN_WIDTH + SEC_WIDTH) +#define MDAY_SHIFT (HOUR_WIDTH + MIN_WIDTH + SEC_WIDTH) +#define MON_SHIFT (MDAY_WIDTH + HOUR_WIDTH + MIN_WIDTH + SEC_WIDTH) + +#define PK_MASK(x) ((1 << (x)) - 1) + +#define EX_SEC(x) (((x) >> SEC_SHIFT) & PK_MASK(SEC_WIDTH)) +#define EX_MIN(x) (((x) >> MIN_SHIFT) & PK_MASK(MIN_WIDTH)) +#define EX_HOUR(x) (((x) >> HOUR_SHIFT) & PK_MASK(HOUR_WIDTH)) +#define EX_MDAY(x) (((x) >> MDAY_SHIFT) & PK_MASK(MDAY_WIDTH)) +#define EX_MON(x) (((x) >> MON_SHIFT) & PK_MASK(MON_WIDTH)) + +#define PACK5(m,d,h,min,s) \ + (((m) << MON_SHIFT) | ((d) << MDAY_SHIFT) |\ + ((h) << HOUR_SHIFT) | ((min) << MIN_SHIFT) | ((s) << SEC_SHIFT)) + +#define PACK2(m,d) \ + (((m) << MON_SHIFT) | ((d) << MDAY_SHIFT)) +#endif + +#ifdef HAVE_FLOAT_H +#include +#endif + +#if defined(FLT_RADIX) && defined(FLT_MANT_DIG) && FLT_RADIX == 2 && FLT_MANT_DIG > 22 +#define date_sg_t float +#else +#define date_sg_t double +#endif + +/* A set of nth, jd, df and sf denote ajd + 1/2. Each ajd begin at + * noon of GMT (assume equal to UTC). However, this begins at + * midnight. + */ + +struct SimpleDateData +{ + unsigned flags; + VALUE nth; /* not always canonicalized */ + int jd; /* as utc */ + /* df is zero */ + /* sf is zero */ + /* of is zero */ + date_sg_t sg; /* 2298874..2426355 or -/+oo -- at most 22 bits */ + /* decoded as utc=local */ + int year; /* truncated */ +#ifndef USE_PACK + int mon; + int mday; + /* hour is zero */ + /* min is zero */ + /* sec is zero */ +#else + /* packed civil */ + unsigned pc; +#endif +}; + +struct ComplexDateData +{ + unsigned flags; + VALUE nth; /* not always canonicalized */ + int jd; /* as utc */ + int df; /* as utc, in secs */ + VALUE sf; /* in nano secs */ + int of; /* in secs */ + date_sg_t sg; /* 2298874..2426355 or -/+oo -- at most 22 bits */ + /* decoded as local */ + int year; /* truncated */ +#ifndef USE_PACK + int mon; + int mday; + int hour; + int min; + int sec; +#else + /* packed civil */ + unsigned pc; +#endif +}; + +union DateData { + unsigned flags; + struct SimpleDateData s; + struct ComplexDateData c; +}; + +#define get_d1(x)\ + union DateData *dat;\ + TypedData_Get_Struct(x, union DateData, &d_lite_type, dat); + +#define get_d1a(x)\ + union DateData *adat;\ + TypedData_Get_Struct(x, union DateData, &d_lite_type, adat); + +#define get_d1b(x)\ + union DateData *bdat;\ + TypedData_Get_Struct(x, union DateData, &d_lite_type, bdat); + +#define get_d2(x,y)\ + union DateData *adat, *bdat;\ + TypedData_Get_Struct(x, union DateData, &d_lite_type, adat);\ + TypedData_Get_Struct(y, union DateData, &d_lite_type, bdat); + +inline static VALUE +canon(VALUE x) +{ + if (RB_TYPE_P(x, T_RATIONAL)) { + VALUE den = rb_rational_den(x); + if (FIXNUM_P(den) && FIX2LONG(den) == 1) + return rb_rational_num(x); + } + return x; +} + +#ifndef USE_PACK +#define set_to_simple(obj, x, _nth, _jd ,_sg, _year, _mon, _mday, _flags) \ +{\ + RB_OBJ_WRITE((obj), &(x)->nth, canon(_nth)); \ + (x)->jd = _jd;\ + (x)->sg = (date_sg_t)(_sg);\ + (x)->year = _year;\ + (x)->mon = _mon;\ + (x)->mday = _mday;\ + (x)->flags = _flags;\ +} +#else +#define set_to_simple(obj, x, _nth, _jd ,_sg, _year, _mon, _mday, _flags) \ +{\ + RB_OBJ_WRITE((obj), &(x)->nth, canon(_nth)); \ + (x)->jd = _jd;\ + (x)->sg = (date_sg_t)(_sg);\ + (x)->year = _year;\ + (x)->pc = PACK2(_mon, _mday);\ + (x)->flags = _flags;\ +} +#endif + +#ifndef USE_PACK +#define set_to_complex(obj, x, _nth, _jd ,_df, _sf, _of, _sg,\ +_year, _mon, _mday, _hour, _min, _sec, _flags) \ +{\ + RB_OBJ_WRITE((obj), &(x)->nth, canon(_nth));\ + (x)->jd = _jd;\ + (x)->df = _df;\ + RB_OBJ_WRITE((obj), &(x)->sf, canon(_sf));\ + (x)->of = _of;\ + (x)->sg = (date_sg_t)(_sg);\ + (x)->year = _year;\ + (x)->mon = _mon;\ + (x)->mday = _mday;\ + (x)->hour = _hour;\ + (x)->min = _min;\ + (x)->sec = _sec;\ + (x)->flags = _flags;\ +} +#else +#define set_to_complex(obj, x, _nth, _jd ,_df, _sf, _of, _sg,\ +_year, _mon, _mday, _hour, _min, _sec, _flags) \ +{\ + RB_OBJ_WRITE((obj), &(x)->nth, canon(_nth));\ + (x)->jd = _jd;\ + (x)->df = _df;\ + RB_OBJ_WRITE((obj), &(x)->sf, canon(_sf));\ + (x)->of = _of;\ + (x)->sg = (date_sg_t)(_sg);\ + (x)->year = _year;\ + (x)->pc = PACK5(_mon, _mday, _hour, _min, _sec);\ + (x)->flags = _flags;\ +} +#endif + +#ifndef USE_PACK +#define copy_simple_to_complex(obj, x, y) \ +{\ + RB_OBJ_WRITE((obj), &(x)->nth, (y)->nth);\ + (x)->jd = (y)->jd;\ + (x)->df = 0;\ + (x)->sf = INT2FIX(0);\ + (x)->of = 0;\ + (x)->sg = (date_sg_t)((y)->sg);\ + (x)->year = (y)->year;\ + (x)->mon = (y)->mon;\ + (x)->mday = (y)->mday;\ + (x)->hour = 0;\ + (x)->min = 0;\ + (x)->sec = 0;\ + (x)->flags = (y)->flags;\ +} +#else +#define copy_simple_to_complex(obj, x, y) \ +{\ + RB_OBJ_WRITE((obj), &(x)->nth, (y)->nth);\ + (x)->jd = (y)->jd;\ + (x)->df = 0;\ + RB_OBJ_WRITE((obj), &(x)->sf, INT2FIX(0));\ + (x)->of = 0;\ + (x)->sg = (date_sg_t)((y)->sg);\ + (x)->year = (y)->year;\ + (x)->pc = PACK5(EX_MON((y)->pc), EX_MDAY((y)->pc), 0, 0, 0);\ + (x)->flags = (y)->flags;\ +} +#endif + +#ifndef USE_PACK +#define copy_complex_to_simple(obj, x, y) \ +{\ + RB_OBJ_WRITE((obj), &(x)->nth, (y)->nth);\ + (x)->jd = (y)->jd;\ + (x)->sg = (date_sg_t)((y)->sg);\ + (x)->year = (y)->year;\ + (x)->mon = (y)->mon;\ + (x)->mday = (y)->mday;\ + (x)->flags = (y)->flags;\ +} +#else +#define copy_complex_to_simple(obj, x, y) \ +{\ + RB_OBJ_WRITE((obj), &(x)->nth, (y)->nth);\ + (x)->jd = (y)->jd;\ + (x)->sg = (date_sg_t)((y)->sg);\ + (x)->year = (y)->year;\ + (x)->pc = PACK2(EX_MON((y)->pc), EX_MDAY((y)->pc));\ + (x)->flags = (y)->flags;\ +} +#endif + +/* base */ + +static int c_valid_civil_p(int, int, int, double, + int *, int *, int *, int *); + +static int +c_find_fdoy(int y, double sg, int *rjd, int *ns) +{ + int d, rm, rd; + + for (d = 1; d < 31; d++) + if (c_valid_civil_p(y, 1, d, sg, &rm, &rd, rjd, ns)) + return 1; + return 0; +} + +static int +c_find_ldoy(int y, double sg, int *rjd, int *ns) +{ + int i, rm, rd; + + for (i = 0; i < 30; i++) + if (c_valid_civil_p(y, 12, 31 - i, sg, &rm, &rd, rjd, ns)) + return 1; + return 0; +} + +#ifndef NDEBUG +static int +c_find_fdom(int y, int m, double sg, int *rjd, int *ns) +{ + int d, rm, rd; + + for (d = 1; d < 31; d++) + if (c_valid_civil_p(y, m, d, sg, &rm, &rd, rjd, ns)) + return 1; + return 0; +} +#endif + +static int +c_find_ldom(int y, int m, double sg, int *rjd, int *ns) +{ + int i, rm, rd; + + for (i = 0; i < 30; i++) + if (c_valid_civil_p(y, m, 31 - i, sg, &rm, &rd, rjd, ns)) + return 1; + return 0; +} + +static void +c_civil_to_jd(int y, int m, int d, double sg, int *rjd, int *ns) +{ + double a, b, jd; + + if (m <= 2) { + y -= 1; + m += 12; + } + a = floor(y / 100.0); + b = 2 - a + floor(a / 4.0); + jd = floor(365.25 * (y + 4716)) + + floor(30.6001 * (m + 1)) + + d + b - 1524; + if (jd < sg) { + jd -= b; + *ns = 0; + } + else + *ns = 1; + + *rjd = (int)jd; +} + +static void +c_jd_to_civil(int jd, double sg, int *ry, int *rm, int *rdom) +{ + double x, a, b, c, d, e, y, m, dom; + + if (jd < sg) + a = jd; + else { + x = floor((jd - 1867216.25) / 36524.25); + a = jd + 1 + x - floor(x / 4.0); + } + b = a + 1524; + c = floor((b - 122.1) / 365.25); + d = floor(365.25 * c); + e = floor((b - d) / 30.6001); + dom = b - d - floor(30.6001 * e); + if (e <= 13) { + m = e - 1; + y = c - 4716; + } + else { + m = e - 13; + y = c - 4715; + } + + *ry = (int)y; + *rm = (int)m; + *rdom = (int)dom; +} + +static void +c_ordinal_to_jd(int y, int d, double sg, int *rjd, int *ns) +{ + int ns2; + + c_find_fdoy(y, sg, rjd, &ns2); + *rjd += d - 1; + *ns = (*rjd < sg) ? 0 : 1; +} + +static void +c_jd_to_ordinal(int jd, double sg, int *ry, int *rd) +{ + int rm2, rd2, rjd, ns; + + c_jd_to_civil(jd, sg, ry, &rm2, &rd2); + c_find_fdoy(*ry, sg, &rjd, &ns); + *rd = (jd - rjd) + 1; +} + +static void +c_commercial_to_jd(int y, int w, int d, double sg, int *rjd, int *ns) +{ + int rjd2, ns2; + + c_find_fdoy(y, sg, &rjd2, &ns2); + rjd2 += 3; + *rjd = + (rjd2 - MOD((rjd2 - 1) + 1, 7)) + + 7 * (w - 1) + + (d - 1); + *ns = (*rjd < sg) ? 0 : 1; +} + +static void +c_jd_to_commercial(int jd, double sg, int *ry, int *rw, int *rd) +{ + int ry2, rm2, rd2, a, rjd2, ns2; + + c_jd_to_civil(jd - 3, sg, &ry2, &rm2, &rd2); + a = ry2; + c_commercial_to_jd(a + 1, 1, 1, sg, &rjd2, &ns2); + if (jd >= rjd2) + *ry = a + 1; + else { + c_commercial_to_jd(a, 1, 1, sg, &rjd2, &ns2); + *ry = a; + } + *rw = 1 + DIV(jd - rjd2, 7); + *rd = MOD(jd + 1, 7); + if (*rd == 0) + *rd = 7; +} + +static void +c_weeknum_to_jd(int y, int w, int d, int f, double sg, int *rjd, int *ns) +{ + int rjd2, ns2; + + c_find_fdoy(y, sg, &rjd2, &ns2); + rjd2 += 6; + *rjd = (rjd2 - MOD(((rjd2 - f) + 1), 7) - 7) + 7 * w + d; + *ns = (*rjd < sg) ? 0 : 1; +} + +static void +c_jd_to_weeknum(int jd, int f, double sg, int *ry, int *rw, int *rd) +{ + int rm, rd2, rjd, ns, j; + + c_jd_to_civil(jd, sg, ry, &rm, &rd2); + c_find_fdoy(*ry, sg, &rjd, &ns); + rjd += 6; + j = jd - (rjd - MOD((rjd - f) + 1, 7)) + 7; + *rw = (int)DIV(j, 7); + *rd = (int)MOD(j, 7); +} + +#ifndef NDEBUG +static void +c_nth_kday_to_jd(int y, int m, int n, int k, double sg, int *rjd, int *ns) +{ + int rjd2, ns2; + + if (n > 0) { + c_find_fdom(y, m, sg, &rjd2, &ns2); + rjd2 -= 1; + } + else { + c_find_ldom(y, m, sg, &rjd2, &ns2); + rjd2 += 7; + } + *rjd = (rjd2 - MOD((rjd2 - k) + 1, 7)) + 7 * n; + *ns = (*rjd < sg) ? 0 : 1; +} +#endif + +inline static int +c_jd_to_wday(int jd) +{ + return MOD(jd + 1, 7); +} + +#ifndef NDEBUG +static void +c_jd_to_nth_kday(int jd, double sg, int *ry, int *rm, int *rn, int *rk) +{ + int rd, rjd, ns2; + + c_jd_to_civil(jd, sg, ry, rm, &rd); + c_find_fdom(*ry, *rm, sg, &rjd, &ns2); + *rn = DIV(jd - rjd, 7) + 1; + *rk = c_jd_to_wday(jd); +} +#endif + +static int +c_valid_ordinal_p(int y, int d, double sg, + int *rd, int *rjd, int *ns) +{ + int ry2, rd2; + + if (d < 0) { + int rjd2, ns2; + + if (!c_find_ldoy(y, sg, &rjd2, &ns2)) + return 0; + c_jd_to_ordinal(rjd2 + d + 1, sg, &ry2, &rd2); + if (ry2 != y) + return 0; + d = rd2; + } + c_ordinal_to_jd(y, d, sg, rjd, ns); + c_jd_to_ordinal(*rjd, sg, &ry2, &rd2); + if (ry2 != y || rd2 != d) + return 0; + return 1; +} + +static const int monthtab[2][13] = { + { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } +}; + +inline static int +c_julian_leap_p(int y) +{ + return MOD(y, 4) == 0; +} + +inline static int +c_gregorian_leap_p(int y) +{ + return (MOD(y, 4) == 0 && y % 100 != 0) || MOD(y, 400) == 0; +} + +static int +c_julian_last_day_of_month(int y, int m) +{ + assert(m >= 1 && m <= 12); + return monthtab[c_julian_leap_p(y) ? 1 : 0][m]; +} + +static int +c_gregorian_last_day_of_month(int y, int m) +{ + assert(m >= 1 && m <= 12); + return monthtab[c_gregorian_leap_p(y) ? 1 : 0][m]; +} + +static int +c_valid_julian_p(int y, int m, int d, int *rm, int *rd) +{ + int last; + + if (m < 0) + m += 13; + if (m < 1 || m > 12) + return 0; + last = c_julian_last_day_of_month(y, m); + if (d < 0) + d = last + d + 1; + if (d < 1 || d > last) + return 0; + *rm = m; + *rd = d; + return 1; +} + +static int +c_valid_gregorian_p(int y, int m, int d, int *rm, int *rd) +{ + int last; + + if (m < 0) + m += 13; + if (m < 1 || m > 12) + return 0; + last = c_gregorian_last_day_of_month(y, m); + if (d < 0) + d = last + d + 1; + if (d < 1 || d > last) + return 0; + *rm = m; + *rd = d; + return 1; +} + +static int +c_valid_civil_p(int y, int m, int d, double sg, + int *rm, int *rd, int *rjd, int *ns) +{ + int ry; + + if (m < 0) + m += 13; + if (d < 0) { + if (!c_find_ldom(y, m, sg, rjd, ns)) + return 0; + c_jd_to_civil(*rjd + d + 1, sg, &ry, rm, rd); + if (ry != y || *rm != m) + return 0; + d = *rd; + } + c_civil_to_jd(y, m, d, sg, rjd, ns); + c_jd_to_civil(*rjd, sg, &ry, rm, rd); + if (ry != y || *rm != m || *rd != d) + return 0; + return 1; +} + +static int +c_valid_commercial_p(int y, int w, int d, double sg, + int *rw, int *rd, int *rjd, int *ns) +{ + int ns2, ry2, rw2, rd2; + + if (d < 0) + d += 8; + if (w < 0) { + int rjd2; + + c_commercial_to_jd(y + 1, 1, 1, sg, &rjd2, &ns2); + c_jd_to_commercial(rjd2 + w * 7, sg, &ry2, &rw2, &rd2); + if (ry2 != y) + return 0; + w = rw2; + } + c_commercial_to_jd(y, w, d, sg, rjd, ns); + c_jd_to_commercial(*rjd, sg, &ry2, rw, rd); + if (y != ry2 || w != *rw || d != *rd) + return 0; + return 1; +} + +static int +c_valid_weeknum_p(int y, int w, int d, int f, double sg, + int *rw, int *rd, int *rjd, int *ns) +{ + int ns2, ry2, rw2, rd2; + + if (d < 0) + d += 7; + if (w < 0) { + int rjd2; + + c_weeknum_to_jd(y + 1, 1, f, f, sg, &rjd2, &ns2); + c_jd_to_weeknum(rjd2 + w * 7, f, sg, &ry2, &rw2, &rd2); + if (ry2 != y) + return 0; + w = rw2; + } + c_weeknum_to_jd(y, w, d, f, sg, rjd, ns); + c_jd_to_weeknum(*rjd, f, sg, &ry2, rw, rd); + if (y != ry2 || w != *rw || d != *rd) + return 0; + return 1; +} + +#ifndef NDEBUG +static int +c_valid_nth_kday_p(int y, int m, int n, int k, double sg, + int *rm, int *rn, int *rk, int *rjd, int *ns) +{ + int ns2, ry2, rm2, rn2, rk2; + + if (k < 0) + k += 7; + if (n < 0) { + int t, ny, nm, rjd2; + + t = y * 12 + m; + ny = DIV(t, 12); + nm = MOD(t, 12) + 1; + + c_nth_kday_to_jd(ny, nm, 1, k, sg, &rjd2, &ns2); + c_jd_to_nth_kday(rjd2 + n * 7, sg, &ry2, &rm2, &rn2, &rk2); + if (ry2 != y || rm2 != m) + return 0; + n = rn2; + } + c_nth_kday_to_jd(y, m, n, k, sg, rjd, ns); + c_jd_to_nth_kday(*rjd, sg, &ry2, rm, rn, rk); + if (y != ry2 || m != *rm || n != *rn || k != *rk) + return 0; + return 1; +} +#endif + +static int +c_valid_time_p(int h, int min, int s, int *rh, int *rmin, int *rs) +{ + if (h < 0) + h += 24; + if (min < 0) + min += 60; + if (s < 0) + s += 60; + *rh = h; + *rmin = min; + *rs = s; + return !(h < 0 || h > 24 || + min < 0 || min > 59 || + s < 0 || s > 59 || + (h == 24 && (min > 0 || s > 0))); +} + +inline static int +c_valid_start_p(double sg) +{ + if (isnan(sg)) + return 0; + if (isinf(sg)) + return 1; + if (sg < REFORM_BEGIN_JD || sg > REFORM_END_JD) + return 0; + return 1; +} + +inline static int +df_local_to_utc(int df, int of) +{ + df -= of; + if (df < 0) + df += DAY_IN_SECONDS; + else if (df >= DAY_IN_SECONDS) + df -= DAY_IN_SECONDS; + return df; +} + +inline static int +df_utc_to_local(int df, int of) +{ + df += of; + if (df < 0) + df += DAY_IN_SECONDS; + else if (df >= DAY_IN_SECONDS) + df -= DAY_IN_SECONDS; + return df; +} + +inline static int +jd_local_to_utc(int jd, int df, int of) +{ + df -= of; + if (df < 0) + jd -= 1; + else if (df >= DAY_IN_SECONDS) + jd += 1; + return jd; +} + +inline static int +jd_utc_to_local(int jd, int df, int of) +{ + df += of; + if (df < 0) + jd -= 1; + else if (df >= DAY_IN_SECONDS) + jd += 1; + return jd; +} + +inline static int +time_to_df(int h, int min, int s) +{ + return h * HOUR_IN_SECONDS + min * MINUTE_IN_SECONDS + s; +} + +inline static void +df_to_time(int df, int *h, int *min, int *s) +{ + *h = df / HOUR_IN_SECONDS; + df %= HOUR_IN_SECONDS; + *min = df / MINUTE_IN_SECONDS; + *s = df % MINUTE_IN_SECONDS; +} + +static VALUE +sec_to_day(VALUE s) +{ + if (FIXNUM_P(s)) + return rb_rational_new2(s, INT2FIX(DAY_IN_SECONDS)); + return f_quo(s, INT2FIX(DAY_IN_SECONDS)); +} + +inline static VALUE +isec_to_day(int s) +{ + return sec_to_day(INT2FIX(s)); +} + +static VALUE +ns_to_day(VALUE n) +{ + if (FIXNUM_P(n)) + return rb_rational_new2(n, day_in_nanoseconds); + return f_quo(n, day_in_nanoseconds); +} + +#ifndef NDEBUG +static VALUE +ms_to_sec(VALUE m) +{ + if (FIXNUM_P(m)) + return rb_rational_new2(m, INT2FIX(SECOND_IN_MILLISECONDS)); + return f_quo(m, INT2FIX(SECOND_IN_MILLISECONDS)); +} +#endif + +static VALUE +ns_to_sec(VALUE n) +{ + if (FIXNUM_P(n)) + return rb_rational_new2(n, INT2FIX(SECOND_IN_NANOSECONDS)); + return f_quo(n, INT2FIX(SECOND_IN_NANOSECONDS)); +} + +#ifndef NDEBUG +inline static VALUE +ins_to_day(int n) +{ + return ns_to_day(INT2FIX(n)); +} +#endif + +static int +safe_mul_p(VALUE x, long m) +{ + long ix; + + if (!FIXNUM_P(x)) + return 0; + ix = FIX2LONG(x); + if (ix < 0) { + if (ix <= (FIXNUM_MIN / m)) + return 0; + } + else { + if (ix >= (FIXNUM_MAX / m)) + return 0; + } + return 1; +} + +static VALUE +day_to_sec(VALUE d) +{ + if (safe_mul_p(d, DAY_IN_SECONDS)) + return LONG2FIX(FIX2LONG(d) * DAY_IN_SECONDS); + return f_mul(d, INT2FIX(DAY_IN_SECONDS)); +} + +#ifndef NDEBUG +static VALUE +day_to_ns(VALUE d) +{ + return f_mul(d, day_in_nanoseconds); +} +#endif + +static VALUE +sec_to_ms(VALUE s) +{ + if (safe_mul_p(s, SECOND_IN_MILLISECONDS)) + return LONG2FIX(FIX2LONG(s) * SECOND_IN_MILLISECONDS); + return f_mul(s, INT2FIX(SECOND_IN_MILLISECONDS)); +} + +static VALUE +sec_to_ns(VALUE s) +{ + if (safe_mul_p(s, SECOND_IN_NANOSECONDS)) + return LONG2FIX(FIX2LONG(s) * SECOND_IN_NANOSECONDS); + return f_mul(s, INT2FIX(SECOND_IN_NANOSECONDS)); +} + +#ifndef NDEBUG +static VALUE +isec_to_ns(int s) +{ + return sec_to_ns(INT2FIX(s)); +} +#endif + +static VALUE +div_day(VALUE d, VALUE *f) +{ + if (f) + *f = f_mod(d, INT2FIX(1)); + return f_floor(d); +} + +static VALUE +div_df(VALUE d, VALUE *f) +{ + VALUE s = day_to_sec(d); + + if (f) + *f = f_mod(s, INT2FIX(1)); + return f_floor(s); +} + +#ifndef NDEBUG +static VALUE +div_sf(VALUE s, VALUE *f) +{ + VALUE n = sec_to_ns(s); + + if (f) + *f = f_mod(n, INT2FIX(1)); + return f_floor(n); +} +#endif + +static void +decode_day(VALUE d, VALUE *jd, VALUE *df, VALUE *sf) +{ + VALUE f; + + *jd = div_day(d, &f); + *df = div_df(f, &f); + *sf = sec_to_ns(f); +} + +inline static double +s_virtual_sg(union DateData *x) +{ + if (isinf(x->s.sg)) + return x->s.sg; + if (f_zero_p(x->s.nth)) + return x->s.sg; + else if (f_negative_p(x->s.nth)) + return positive_inf; + return negative_inf; +} + +inline static double +c_virtual_sg(union DateData *x) +{ + if (isinf(x->c.sg)) + return x->c.sg; + if (f_zero_p(x->c.nth)) + return x->c.sg; + else if (f_negative_p(x->c.nth)) + return positive_inf; + return negative_inf; +} + +inline static double +m_virtual_sg(union DateData *x) +{ + if (simple_dat_p(x)) + return s_virtual_sg(x); + else + return c_virtual_sg(x); +} + +#define canonicalize_jd(_nth, _jd) \ +{\ + if (_jd < 0) {\ + _nth = f_sub(_nth, INT2FIX(1));\ + _jd += CM_PERIOD;\ + }\ + if (_jd >= CM_PERIOD) {\ + _nth = f_add(_nth, INT2FIX(1));\ + _jd -= CM_PERIOD;\ + }\ +} + +inline static void +canonicalize_s_jd(VALUE obj, union DateData *x) +{ + int j = x->s.jd; + VALUE nth = x->s.nth; + assert(have_jd_p(x)); + canonicalize_jd(nth, x->s.jd); + RB_OBJ_WRITE(obj, &x->s.nth, nth); + if (x->s.jd != j) + x->flags &= ~HAVE_CIVIL; +} + +inline static void +get_s_jd(union DateData *x) +{ + assert(simple_dat_p(x)); + if (!have_jd_p(x)) { + int jd, ns; + + assert(have_civil_p(x)); +#ifndef USE_PACK + c_civil_to_jd(x->s.year, x->s.mon, x->s.mday, + s_virtual_sg(x), &jd, &ns); +#else + c_civil_to_jd(x->s.year, EX_MON(x->s.pc), EX_MDAY(x->s.pc), + s_virtual_sg(x), &jd, &ns); +#endif + x->s.jd = jd; + x->s.flags |= HAVE_JD; + } +} + +inline static void +get_s_civil(union DateData *x) +{ + assert(simple_dat_p(x)); + if (!have_civil_p(x)) { + int y, m, d; + + assert(have_jd_p(x)); + c_jd_to_civil(x->s.jd, s_virtual_sg(x), &y, &m, &d); + x->s.year = y; +#ifndef USE_PACK + x->s.mon = m; + x->s.mday = d; +#else + x->s.pc = PACK2(m, d); +#endif + x->s.flags |= HAVE_CIVIL; + } +} + +inline static void +get_c_df(union DateData *x) +{ + assert(complex_dat_p(x)); + if (!have_df_p(x)) { + assert(have_time_p(x)); +#ifndef USE_PACK + x->c.df = df_local_to_utc(time_to_df(x->c.hour, x->c.min, x->c.sec), + x->c.of); +#else + x->c.df = df_local_to_utc(time_to_df(EX_HOUR(x->c.pc), + EX_MIN(x->c.pc), + EX_SEC(x->c.pc)), + x->c.of); +#endif + x->c.flags |= HAVE_DF; + } +} + +inline static void +get_c_time(union DateData *x) +{ + assert(complex_dat_p(x)); + if (!have_time_p(x)) { +#ifndef USE_PACK + int r; + assert(have_df_p(x)); + r = df_utc_to_local(x->c.df, x->c.of); + df_to_time(r, &x->c.hour, &x->c.min, &x->c.sec); + x->c.flags |= HAVE_TIME; +#else + int r, m, d, h, min, s; + + assert(have_df_p(x)); + m = EX_MON(x->c.pc); + d = EX_MDAY(x->c.pc); + r = df_utc_to_local(x->c.df, x->c.of); + df_to_time(r, &h, &min, &s); + x->c.pc = PACK5(m, d, h, min, s); + x->c.flags |= HAVE_TIME; +#endif + } +} + +inline static void +canonicalize_c_jd(VALUE obj, union DateData *x) +{ + int j = x->c.jd; + VALUE nth = x->c.nth; + assert(have_jd_p(x)); + canonicalize_jd(nth, x->c.jd); + RB_OBJ_WRITE(obj, &x->c.nth, nth); + if (x->c.jd != j) + x->flags &= ~HAVE_CIVIL; +} + +inline static void +get_c_jd(union DateData *x) +{ + assert(complex_dat_p(x)); + if (!have_jd_p(x)) { + int jd, ns; + + assert(have_civil_p(x)); +#ifndef USE_PACK + c_civil_to_jd(x->c.year, x->c.mon, x->c.mday, + c_virtual_sg(x), &jd, &ns); +#else + c_civil_to_jd(x->c.year, EX_MON(x->c.pc), EX_MDAY(x->c.pc), + c_virtual_sg(x), &jd, &ns); +#endif + + get_c_time(x); +#ifndef USE_PACK + x->c.jd = jd_local_to_utc(jd, + time_to_df(x->c.hour, x->c.min, x->c.sec), + x->c.of); +#else + x->c.jd = jd_local_to_utc(jd, + time_to_df(EX_HOUR(x->c.pc), + EX_MIN(x->c.pc), + EX_SEC(x->c.pc)), + x->c.of); +#endif + x->c.flags |= HAVE_JD; + } +} + +inline static void +get_c_civil(union DateData *x) +{ + assert(complex_dat_p(x)); + if (!have_civil_p(x)) { +#ifndef USE_PACK + int jd, y, m, d; +#else + int jd, y, m, d, h, min, s; +#endif + + assert(have_jd_p(x)); + get_c_df(x); + jd = jd_utc_to_local(x->c.jd, x->c.df, x->c.of); + c_jd_to_civil(jd, c_virtual_sg(x), &y, &m, &d); + x->c.year = y; +#ifndef USE_PACK + x->c.mon = m; + x->c.mday = d; +#else + h = EX_HOUR(x->c.pc); + min = EX_MIN(x->c.pc); + s = EX_SEC(x->c.pc); + x->c.pc = PACK5(m, d, h, min, s); +#endif + x->c.flags |= HAVE_CIVIL; + } +} + +inline static int +local_jd(union DateData *x) +{ + assert(complex_dat_p(x)); + assert(have_jd_p(x)); + assert(have_df_p(x)); + return jd_utc_to_local(x->c.jd, x->c.df, x->c.of); +} + +inline static int +local_df(union DateData *x) +{ + assert(complex_dat_p(x)); + assert(have_df_p(x)); + return df_utc_to_local(x->c.df, x->c.of); +} + +static void +decode_year(VALUE y, double style, + VALUE *nth, int *ry) +{ + int period; + VALUE t; + + period = (style < 0) ? + CM_PERIOD_GCY : + CM_PERIOD_JCY; + if (FIXNUM_P(y)) { + long iy, it, inth; + + iy = FIX2LONG(y); + if (iy >= (FIXNUM_MAX - 4712)) + goto big; + it = iy + 4712; /* shift */ + inth = DIV(it, ((long)period)); + *nth = LONG2FIX(inth); + if (inth) + it = MOD(it, ((long)period)); + *ry = (int)it - 4712; /* unshift */ + return; + } + big: + t = f_add(y, INT2FIX(4712)); /* shift */ + *nth = f_idiv(t, INT2FIX(period)); + if (f_nonzero_p(*nth)) + t = f_mod(t, INT2FIX(period)); + *ry = FIX2INT(t) - 4712; /* unshift */ +} + +static void +encode_year(VALUE nth, int y, double style, + VALUE *ry) +{ + int period; + VALUE t; + + period = (style < 0) ? + CM_PERIOD_GCY : + CM_PERIOD_JCY; + if (f_zero_p(nth)) + *ry = INT2FIX(y); + else { + t = f_mul(INT2FIX(period), nth); + t = f_add(t, INT2FIX(y)); + *ry = t; + } +} + +static void +decode_jd(VALUE jd, VALUE *nth, int *rjd) +{ + assert(FIXNUM_P(jd) || RB_TYPE_P(jd, T_BIGNUM)); + *nth = f_idiv(jd, INT2FIX(CM_PERIOD)); + if (f_zero_p(*nth)) { + assert(FIXNUM_P(jd)); + *rjd = FIX2INT(jd); + return; + } + *rjd = FIX2INT(f_mod(jd, INT2FIX(CM_PERIOD))); +} + +static void +encode_jd(VALUE nth, int jd, VALUE *rjd) +{ + if (f_zero_p(nth)) { + *rjd = INT2FIX(jd); + return; + } + *rjd = f_add(f_mul(INT2FIX(CM_PERIOD), nth), INT2FIX(jd)); +} + +inline static double +guess_style(VALUE y, double sg) /* -/+oo or zero */ +{ + double style = 0; + + if (isinf(sg)) + style = sg; + else if (!FIXNUM_P(y)) + style = f_positive_p(y) ? negative_inf : positive_inf; + else { + long iy = FIX2LONG(y); + + assert(FIXNUM_P(y)); + if (iy < REFORM_BEGIN_YEAR) + style = positive_inf; + else if (iy > REFORM_END_YEAR) + style = negative_inf; + } + return style; +} + +inline static void +m_canonicalize_jd(VALUE obj, union DateData *x) +{ + if (simple_dat_p(x)) { + get_s_jd(x); + canonicalize_s_jd(obj, x); + } + else { + get_c_jd(x); + canonicalize_c_jd(obj, x); + } +} + +inline static VALUE +m_nth(union DateData *x) +{ + if (simple_dat_p(x)) + return x->s.nth; + else { + get_c_civil(x); + return x->c.nth; + } +} + +inline static int +m_jd(union DateData *x) +{ + if (simple_dat_p(x)) { + get_s_jd(x); + return x->s.jd; + } + else { + get_c_jd(x); + return x->c.jd; + } +} + +static VALUE +m_real_jd(union DateData *x) +{ + VALUE nth, rjd; + int jd; + + nth = m_nth(x); + jd = m_jd(x); + + encode_jd(nth, jd, &rjd); + return rjd; +} + +static int +m_local_jd(union DateData *x) +{ + if (simple_dat_p(x)) { + get_s_jd(x); + return x->s.jd; + } + else { + get_c_jd(x); + get_c_df(x); + return local_jd(x); + } +} + +static VALUE +m_real_local_jd(union DateData *x) +{ + VALUE nth, rjd; + int jd; + + nth = m_nth(x); + jd = m_local_jd(x); + + encode_jd(nth, jd, &rjd); + return rjd; +} + +inline static int +m_df(union DateData *x) +{ + if (simple_dat_p(x)) + return 0; + else { + get_c_df(x); + return x->c.df; + } +} + +#ifndef NDEBUG +static VALUE +m_df_in_day(union DateData *x) +{ + return isec_to_day(m_df(x)); +} +#endif + +static int +m_local_df(union DateData *x) +{ + if (simple_dat_p(x)) + return 0; + else { + get_c_df(x); + return local_df(x); + } +} + +#ifndef NDEBUG +static VALUE +m_local_df_in_day(union DateData *x) +{ + return isec_to_day(m_local_df(x)); +} +#endif + +inline static VALUE +m_sf(union DateData *x) +{ + if (simple_dat_p(x)) + return INT2FIX(0); + else + return x->c.sf; +} + +#ifndef NDEBUG +static VALUE +m_sf_in_day(union DateData *x) +{ + return ns_to_day(m_sf(x)); +} +#endif + +static VALUE +m_sf_in_sec(union DateData *x) +{ + return ns_to_sec(m_sf(x)); +} + +static VALUE +m_fr(union DateData *x) +{ + if (simple_dat_p(x)) + return INT2FIX(0); + else { + int df; + VALUE sf, fr; + + df = m_local_df(x); + sf = m_sf(x); + fr = isec_to_day(df); + if (f_nonzero_p(sf)) + fr = f_add(fr, ns_to_day(sf)); + return fr; + } +} + +#define HALF_DAYS_IN_SECONDS (DAY_IN_SECONDS / 2) + +static VALUE +m_ajd(union DateData *x) +{ + VALUE r, sf; + int df; + + if (simple_dat_p(x)) { + r = m_real_jd(x); + if (FIXNUM_P(r) && FIX2LONG(r) <= (FIXNUM_MAX / 2)) { + long ir = FIX2LONG(r); + ir = ir * 2 - 1; + return rb_rational_new2(LONG2FIX(ir), INT2FIX(2)); + } + else + return rb_rational_new2(f_sub(f_mul(r, + INT2FIX(2)), + INT2FIX(1)), + INT2FIX(2)); + } + + r = m_real_jd(x); + df = m_df(x); + df -= HALF_DAYS_IN_SECONDS; + if (df) + r = f_add(r, isec_to_day(df)); + sf = m_sf(x); + if (f_nonzero_p(sf)) + r = f_add(r, ns_to_day(sf)); + + return r; +} + +static VALUE +m_amjd(union DateData *x) +{ + VALUE r, sf; + int df; + + r = m_real_jd(x); + if (FIXNUM_P(r) && FIX2LONG(r) >= (FIXNUM_MIN + 2400001)) { + long ir = FIX2LONG(r); + ir -= 2400001; + r = rb_rational_new1(LONG2FIX(ir)); + } + else + r = rb_rational_new1(f_sub(m_real_jd(x), + INT2FIX(2400001))); + + if (simple_dat_p(x)) + return r; + + df = m_df(x); + if (df) + r = f_add(r, isec_to_day(df)); + sf = m_sf(x); + if (f_nonzero_p(sf)) + r = f_add(r, ns_to_day(sf)); + + return r; +} + +inline static int +m_of(union DateData *x) +{ + if (simple_dat_p(x)) + return 0; + else { + get_c_jd(x); + return x->c.of; + } +} + +static VALUE +m_of_in_day(union DateData *x) +{ + return isec_to_day(m_of(x)); +} + +inline static double +m_sg(union DateData *x) +{ + if (simple_dat_p(x)) + return x->s.sg; + else { + get_c_jd(x); + return x->c.sg; + } +} + +static int +m_julian_p(union DateData *x) +{ + int jd; + double sg; + + if (simple_dat_p(x)) { + get_s_jd(x); + jd = x->s.jd; + sg = s_virtual_sg(x); + } + else { + get_c_jd(x); + jd = x->c.jd; + sg = c_virtual_sg(x); + } + if (isinf(sg)) + return sg == positive_inf; + return jd < sg; +} + +inline static int +m_gregorian_p(union DateData *x) +{ + return !m_julian_p(x); +} + +inline static int +m_proleptic_julian_p(union DateData *x) +{ + double sg; + + sg = m_sg(x); + if (isinf(sg) && sg > 0) + return 1; + return 0; +} + +inline static int +m_proleptic_gregorian_p(union DateData *x) +{ + double sg; + + sg = m_sg(x); + if (isinf(sg) && sg < 0) + return 1; + return 0; +} + +inline static int +m_year(union DateData *x) +{ + if (simple_dat_p(x)) { + get_s_civil(x); + return x->s.year; + } + else { + get_c_civil(x); + return x->c.year; + } +} + +static VALUE +m_real_year(union DateData *x) +{ + VALUE nth, ry; + int year; + + nth = m_nth(x); + year = m_year(x); + + if (f_zero_p(nth)) + return INT2FIX(year); + + encode_year(nth, year, + m_gregorian_p(x) ? -1 : +1, + &ry); + return ry; +} + +inline static int +m_mon(union DateData *x) +{ + if (simple_dat_p(x)) { + get_s_civil(x); +#ifndef USE_PACK + return x->s.mon; +#else + return EX_MON(x->s.pc); +#endif + } + else { + get_c_civil(x); +#ifndef USE_PACK + return x->c.mon; +#else + return EX_MON(x->c.pc); +#endif + } +} + +inline static int +m_mday(union DateData *x) +{ + if (simple_dat_p(x)) { + get_s_civil(x); +#ifndef USE_PACK + return x->s.mday; +#else + return EX_MDAY(x->s.pc); +#endif + } + else { + get_c_civil(x); +#ifndef USE_PACK + return x->c.mday; +#else + return EX_MDAY(x->c.pc); +#endif + } +} + +static const int yeartab[2][13] = { + { 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }, + { 0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 } +}; + +static int +c_julian_to_yday(int y, int m, int d) +{ + assert(m >= 1 && m <= 12); + return yeartab[c_julian_leap_p(y) ? 1 : 0][m] + d; +} + +static int +c_gregorian_to_yday(int y, int m, int d) +{ + assert(m >= 1 && m <= 12); + return yeartab[c_gregorian_leap_p(y) ? 1 : 0][m] + d; +} + +static int +m_yday(union DateData *x) +{ + int jd, ry, rd; + double sg; + + jd = m_local_jd(x); + sg = m_virtual_sg(x); /* !=m_sg() */ + + if (m_proleptic_gregorian_p(x) || + (jd - sg) > 366) + return c_gregorian_to_yday(m_year(x), m_mon(x), m_mday(x)); + if (m_proleptic_julian_p(x)) + return c_julian_to_yday(m_year(x), m_mon(x), m_mday(x)); + c_jd_to_ordinal(jd, sg, &ry, &rd); + return rd; +} + +static int +m_wday(union DateData *x) +{ + return c_jd_to_wday(m_local_jd(x)); +} + +static int +m_cwyear(union DateData *x) +{ + int ry, rw, rd; + + c_jd_to_commercial(m_local_jd(x), m_virtual_sg(x), /* !=m_sg() */ + &ry, &rw, &rd); + return ry; +} + +static VALUE +m_real_cwyear(union DateData *x) +{ + VALUE nth, ry; + int year; + + nth = m_nth(x); + year = m_cwyear(x); + + if (f_zero_p(nth)) + return INT2FIX(year); + + encode_year(nth, year, + m_gregorian_p(x) ? -1 : +1, + &ry); + return ry; +} + +static int +m_cweek(union DateData *x) +{ + int ry, rw, rd; + + c_jd_to_commercial(m_local_jd(x), m_virtual_sg(x), /* !=m_sg() */ + &ry, &rw, &rd); + return rw; +} + +static int +m_cwday(union DateData *x) +{ + int w; + + w = m_wday(x); + if (w == 0) + w = 7; + return w; +} + +static int +m_wnumx(union DateData *x, int f) +{ + int ry, rw, rd; + + c_jd_to_weeknum(m_local_jd(x), f, m_virtual_sg(x), /* !=m_sg() */ + &ry, &rw, &rd); + return rw; +} + +static int +m_wnum0(union DateData *x) +{ + return m_wnumx(x, 0); +} + +static int +m_wnum1(union DateData *x) +{ + return m_wnumx(x, 1); +} + +inline static int +m_hour(union DateData *x) +{ + if (simple_dat_p(x)) + return 0; + else { + get_c_time(x); +#ifndef USE_PACK + return x->c.hour; +#else + return EX_HOUR(x->c.pc); +#endif + } +} + +inline static int +m_min(union DateData *x) +{ + if (simple_dat_p(x)) + return 0; + else { + get_c_time(x); +#ifndef USE_PACK + return x->c.min; +#else + return EX_MIN(x->c.pc); +#endif + } +} + +inline static int +m_sec(union DateData *x) +{ + if (simple_dat_p(x)) + return 0; + else { + get_c_time(x); +#ifndef USE_PACK + return x->c.sec; +#else + return EX_SEC(x->c.pc); +#endif + } +} + +#define decode_offset(of,s,h,m)\ +{\ + int a;\ + s = (of < 0) ? '-' : '+';\ + a = (of < 0) ? -of : of;\ + h = a / HOUR_IN_SECONDS;\ + m = a % HOUR_IN_SECONDS / MINUTE_IN_SECONDS;\ +} + +static VALUE +of2str(int of) +{ + int s, h, m; + + decode_offset(of, s, h, m); + return rb_enc_sprintf(rb_usascii_encoding(), "%c%02d:%02d", s, h, m); +} + +static VALUE +m_zone(union DateData *x) +{ + if (simple_dat_p(x)) + return rb_usascii_str_new2("+00:00"); + return of2str(m_of(x)); +} + +inline static VALUE +f_kind_of_p(VALUE x, VALUE c) +{ + return rb_obj_is_kind_of(x, c); +} + +inline static VALUE +k_date_p(VALUE x) +{ + return f_kind_of_p(x, cDate); +} + +inline static VALUE +k_numeric_p(VALUE x) +{ + return f_kind_of_p(x, rb_cNumeric); +} + +inline static VALUE +k_rational_p(VALUE x) +{ + return f_kind_of_p(x, rb_cRational); +} + +#ifndef NDEBUG +static void +civil_to_jd(VALUE y, int m, int d, double sg, + VALUE *nth, int *ry, + int *rjd, + int *ns) +{ + double style = guess_style(y, sg); + + if (style == 0) { + int jd; + + c_civil_to_jd(FIX2INT(y), m, d, sg, &jd, ns); + decode_jd(INT2FIX(jd), nth, rjd); + if (f_zero_p(*nth)) + *ry = FIX2INT(y); + else { + VALUE nth2; + decode_year(y, *ns ? -1 : +1, &nth2, ry); + } + } + else { + decode_year(y, style, nth, ry); + c_civil_to_jd(*ry, m, d, style, rjd, ns); + } +} + +static void +jd_to_civil(VALUE jd, double sg, + VALUE *nth, int *rjd, + int *ry, int *rm, int *rd) +{ + decode_jd(jd, nth, rjd); + c_jd_to_civil(*rjd, sg, ry, rm, rd); +} + +static void +ordinal_to_jd(VALUE y, int d, double sg, + VALUE *nth, int *ry, + int *rjd, + int *ns) +{ + double style = guess_style(y, sg); + + if (style == 0) { + int jd; + + c_ordinal_to_jd(FIX2INT(y), d, sg, &jd, ns); + decode_jd(INT2FIX(jd), nth, rjd); + if (f_zero_p(*nth)) + *ry = FIX2INT(y); + else { + VALUE nth2; + decode_year(y, *ns ? -1 : +1, &nth2, ry); + } + } + else { + decode_year(y, style, nth, ry); + c_ordinal_to_jd(*ry, d, style, rjd, ns); + } +} + +static void +jd_to_ordinal(VALUE jd, double sg, + VALUE *nth, int *rjd, + int *ry, int *rd) +{ + decode_jd(jd, nth, rjd); + c_jd_to_ordinal(*rjd, sg, ry, rd); +} + +static void +commercial_to_jd(VALUE y, int w, int d, double sg, + VALUE *nth, int *ry, + int *rjd, + int *ns) +{ + double style = guess_style(y, sg); + + if (style == 0) { + int jd; + + c_commercial_to_jd(FIX2INT(y), w, d, sg, &jd, ns); + decode_jd(INT2FIX(jd), nth, rjd); + if (f_zero_p(*nth)) + *ry = FIX2INT(y); + else { + VALUE nth2; + decode_year(y, *ns ? -1 : +1, &nth2, ry); + } + } + else { + decode_year(y, style, nth, ry); + c_commercial_to_jd(*ry, w, d, style, rjd, ns); + } +} + +static void +jd_to_commercial(VALUE jd, double sg, + VALUE *nth, int *rjd, + int *ry, int *rw, int *rd) +{ + decode_jd(jd, nth, rjd); + c_jd_to_commercial(*rjd, sg, ry, rw, rd); +} + +static void +weeknum_to_jd(VALUE y, int w, int d, int f, double sg, + VALUE *nth, int *ry, + int *rjd, + int *ns) +{ + double style = guess_style(y, sg); + + if (style == 0) { + int jd; + + c_weeknum_to_jd(FIX2INT(y), w, d, f, sg, &jd, ns); + decode_jd(INT2FIX(jd), nth, rjd); + if (f_zero_p(*nth)) + *ry = FIX2INT(y); + else { + VALUE nth2; + decode_year(y, *ns ? -1 : +1, &nth2, ry); + } + } + else { + decode_year(y, style, nth, ry); + c_weeknum_to_jd(*ry, w, d, f, style, rjd, ns); + } +} + +static void +jd_to_weeknum(VALUE jd, int f, double sg, + VALUE *nth, int *rjd, + int *ry, int *rw, int *rd) +{ + decode_jd(jd, nth, rjd); + c_jd_to_weeknum(*rjd, f, sg, ry, rw, rd); +} + +static void +nth_kday_to_jd(VALUE y, int m, int n, int k, double sg, + VALUE *nth, int *ry, + int *rjd, + int *ns) +{ + double style = guess_style(y, sg); + + if (style == 0) { + int jd; + + c_nth_kday_to_jd(FIX2INT(y), m, n, k, sg, &jd, ns); + decode_jd(INT2FIX(jd), nth, rjd); + if (f_zero_p(*nth)) + *ry = FIX2INT(y); + else { + VALUE nth2; + decode_year(y, *ns ? -1 : +1, &nth2, ry); + } + } + else { + decode_year(y, style, nth, ry); + c_nth_kday_to_jd(*ry, m, n, k, style, rjd, ns); + } +} + +static void +jd_to_nth_kday(VALUE jd, double sg, + VALUE *nth, int *rjd, + int *ry, int *rm, int *rn, int *rk) +{ + decode_jd(jd, nth, rjd); + c_jd_to_nth_kday(*rjd, sg, ry, rm, rn, rk); +} +#endif + +static int +valid_ordinal_p(VALUE y, int d, double sg, + VALUE *nth, int *ry, + int *rd, int *rjd, + int *ns) +{ + double style = guess_style(y, sg); + int r; + + if (style == 0) { + int jd; + + r = c_valid_ordinal_p(FIX2INT(y), d, sg, rd, &jd, ns); + if (!r) + return 0; + decode_jd(INT2FIX(jd), nth, rjd); + if (f_zero_p(*nth)) + *ry = FIX2INT(y); + else { + VALUE nth2; + decode_year(y, *ns ? -1 : +1, &nth2, ry); + } + } + else { + decode_year(y, style, nth, ry); + r = c_valid_ordinal_p(*ry, d, style, rd, rjd, ns); + } + return r; +} + +static int +valid_gregorian_p(VALUE y, int m, int d, + VALUE *nth, int *ry, + int *rm, int *rd) +{ + decode_year(y, -1, nth, ry); + return c_valid_gregorian_p(*ry, m, d, rm, rd); +} + +static int +valid_civil_p(VALUE y, int m, int d, double sg, + VALUE *nth, int *ry, + int *rm, int *rd, int *rjd, + int *ns) +{ + double style = guess_style(y, sg); + int r; + + if (style == 0) { + int jd; + + r = c_valid_civil_p(FIX2INT(y), m, d, sg, rm, rd, &jd, ns); + if (!r) + return 0; + decode_jd(INT2FIX(jd), nth, rjd); + if (f_zero_p(*nth)) + *ry = FIX2INT(y); + else { + VALUE nth2; + decode_year(y, *ns ? -1 : +1, &nth2, ry); + } + } + else { + decode_year(y, style, nth, ry); + if (style < 0) + r = c_valid_gregorian_p(*ry, m, d, rm, rd); + else + r = c_valid_julian_p(*ry, m, d, rm, rd); + if (!r) + return 0; + c_civil_to_jd(*ry, *rm, *rd, style, rjd, ns); + } + return r; +} + +static int +valid_commercial_p(VALUE y, int w, int d, double sg, + VALUE *nth, int *ry, + int *rw, int *rd, int *rjd, + int *ns) +{ + double style = guess_style(y, sg); + int r; + + if (style == 0) { + int jd; + + r = c_valid_commercial_p(FIX2INT(y), w, d, sg, rw, rd, &jd, ns); + if (!r) + return 0; + decode_jd(INT2FIX(jd), nth, rjd); + if (f_zero_p(*nth)) + *ry = FIX2INT(y); + else { + VALUE nth2; + decode_year(y, *ns ? -1 : +1, &nth2, ry); + } + } + else { + decode_year(y, style, nth, ry); + r = c_valid_commercial_p(*ry, w, d, style, rw, rd, rjd, ns); + } + return r; +} + +static int +valid_weeknum_p(VALUE y, int w, int d, int f, double sg, + VALUE *nth, int *ry, + int *rw, int *rd, int *rjd, + int *ns) +{ + double style = guess_style(y, sg); + int r; + + if (style == 0) { + int jd; + + r = c_valid_weeknum_p(FIX2INT(y), w, d, f, sg, rw, rd, &jd, ns); + if (!r) + return 0; + decode_jd(INT2FIX(jd), nth, rjd); + if (f_zero_p(*nth)) + *ry = FIX2INT(y); + else { + VALUE nth2; + decode_year(y, *ns ? -1 : +1, &nth2, ry); + } + } + else { + decode_year(y, style, nth, ry); + r = c_valid_weeknum_p(*ry, w, d, f, style, rw, rd, rjd, ns); + } + return r; +} + +#ifndef NDEBUG +static int +valid_nth_kday_p(VALUE y, int m, int n, int k, double sg, + VALUE *nth, int *ry, + int *rm, int *rn, int *rk, int *rjd, + int *ns) +{ + double style = guess_style(y, sg); + int r; + + if (style == 0) { + int jd; + + r = c_valid_nth_kday_p(FIX2INT(y), m, n, k, sg, rm, rn, rk, &jd, ns); + if (!r) + return 0; + decode_jd(INT2FIX(jd), nth, rjd); + if (f_zero_p(*nth)) + *ry = FIX2INT(y); + else { + VALUE nth2; + decode_year(y, *ns ? -1 : +1, &nth2, ry); + } + } + else { + decode_year(y, style, nth, ry); + r = c_valid_nth_kday_p(*ry, m, n, k, style, rm, rn, rk, rjd, ns); + } + return r; +} +#endif + +VALUE date_zone_to_diff(VALUE); + +static int +offset_to_sec(VALUE vof, int *rof) +{ + switch (TYPE(vof)) { + case T_FIXNUM: + { + long n; + + n = FIX2LONG(vof); + if (n != -1 && n != 0 && n != 1) + return 0; + *rof = (int)n * DAY_IN_SECONDS; + return 1; + } + case T_FLOAT: + { + double n; + + n = RFLOAT_VALUE(vof) * DAY_IN_SECONDS; + if (n < -DAY_IN_SECONDS || n > DAY_IN_SECONDS) + return 0; + *rof = (int)round(n); + if (*rof != n) + rb_warning("fraction of offset is ignored"); + return 1; + } + default: + if (!k_numeric_p(vof)) + rb_raise(rb_eTypeError, "expected numeric"); + vof = f_to_r(vof); +#ifdef CANONICALIZATION_FOR_MATHN + if (!k_rational_p(vof)) + return offset_to_sec(vof, rof); +#endif + /* fall through */ + case T_RATIONAL: + { + VALUE vs, vn, vd; + long n; + + vs = day_to_sec(vof); + +#ifdef CANONICALIZATION_FOR_MATHN + if (!k_rational_p(vs)) { + if (!FIXNUM_P(vs)) + return 0; + n = FIX2LONG(vs); + if (n < -DAY_IN_SECONDS || n > DAY_IN_SECONDS) + return 0; + *rof = (int)n; + return 1; + } +#endif + vn = rb_rational_num(vs); + vd = rb_rational_den(vs); + + if (FIXNUM_P(vn) && FIXNUM_P(vd) && (FIX2LONG(vd) == 1)) + n = FIX2LONG(vn); + else { + vn = f_round(vs); + if (!f_eqeq_p(vn, vs)) + rb_warning("fraction of offset is ignored"); + if (!FIXNUM_P(vn)) + return 0; + n = FIX2LONG(vn); + if (n < -DAY_IN_SECONDS || n > DAY_IN_SECONDS) + return 0; + } + *rof = (int)n; + return 1; + } + case T_STRING: + { + VALUE vs = date_zone_to_diff(vof); + long n; + + if (!FIXNUM_P(vs)) + return 0; + n = FIX2LONG(vs); + if (n < -DAY_IN_SECONDS || n > DAY_IN_SECONDS) + return 0; + *rof = (int)n; + return 1; + } + } + return 0; +} + +/* date */ + +#define valid_sg(sg) \ +{\ + if (!c_valid_start_p(sg)) {\ + sg = 0;\ + rb_warning("invalid start is ignored");\ + }\ +} + +static VALUE +valid_jd_sub(int argc, VALUE *argv, VALUE klass, int need_jd) +{ + double sg = NUM2DBL(argv[1]); + valid_sg(sg); + return argv[0]; +} + +#ifndef NDEBUG +static VALUE +date_s__valid_jd_p(int argc, VALUE *argv, VALUE klass) +{ + VALUE vjd, vsg; + VALUE argv2[2]; + + rb_scan_args(argc, argv, "11", &vjd, &vsg); + + argv2[0] = vjd; + if (argc < 2) + argv2[1] = DBL2NUM(GREGORIAN); + else + argv2[1] = vsg; + + return valid_jd_sub(2, argv2, klass, 1); +} +#endif + +/* + * call-seq: + * Date.valid_jd?(jd[, start=Date::ITALY]) -> bool + * + * Just returns true. It's nonsense, but is for symmetry. + * + * Date.valid_jd?(2451944) #=> true + * + * See also jd. + */ +static VALUE +date_s_valid_jd_p(int argc, VALUE *argv, VALUE klass) +{ + VALUE vjd, vsg; + VALUE argv2[2]; + + rb_scan_args(argc, argv, "11", &vjd, &vsg); + + argv2[0] = vjd; + if (argc < 2) + argv2[1] = INT2FIX(DEFAULT_SG); + else + argv2[1] = vsg; + + if (NIL_P(valid_jd_sub(2, argv2, klass, 0))) + return Qfalse; + return Qtrue; +} + +static VALUE +valid_civil_sub(int argc, VALUE *argv, VALUE klass, int need_jd) +{ + VALUE nth, y; + int m, d, ry, rm, rd; + double sg; + + y = argv[0]; + m = NUM2INT(argv[1]); + d = NUM2INT(argv[2]); + sg = NUM2DBL(argv[3]); + + valid_sg(sg); + + if (!need_jd && (guess_style(y, sg) < 0)) { + if (!valid_gregorian_p(y, m, d, + &nth, &ry, + &rm, &rd)) + return Qnil; + return INT2FIX(0); /* dummy */ + } + else { + int rjd, ns; + VALUE rjd2; + + if (!valid_civil_p(y, m, d, sg, + &nth, &ry, + &rm, &rd, &rjd, + &ns)) + return Qnil; + if (!need_jd) + return INT2FIX(0); /* dummy */ + encode_jd(nth, rjd, &rjd2); + return rjd2; + } +} + +#ifndef NDEBUG +static VALUE +date_s__valid_civil_p(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vm, vd, vsg; + VALUE argv2[4]; + + rb_scan_args(argc, argv, "31", &vy, &vm, &vd, &vsg); + + argv2[0] = vy; + argv2[1] = vm; + argv2[2] = vd; + if (argc < 4) + argv2[3] = DBL2NUM(GREGORIAN); + else + argv2[3] = vsg; + + return valid_civil_sub(4, argv2, klass, 1); +} +#endif + +/* + * call-seq: + * Date.valid_civil?(year, month, mday[, start=Date::ITALY]) -> bool + * Date.valid_date?(year, month, mday[, start=Date::ITALY]) -> bool + * + * Returns true if the given calendar date is valid, and false if not. + * + * Date.valid_date?(2001,2,3) #=> true + * Date.valid_date?(2001,2,29) #=> false + * + * See also jd and civil. + */ +static VALUE +date_s_valid_civil_p(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vm, vd, vsg; + VALUE argv2[4]; + + rb_scan_args(argc, argv, "31", &vy, &vm, &vd, &vsg); + + argv2[0] = vy; + argv2[1] = vm; + argv2[2] = vd; + if (argc < 4) + argv2[3] = INT2FIX(DEFAULT_SG); + else + argv2[3] = vsg; + + if (NIL_P(valid_civil_sub(4, argv2, klass, 0))) + return Qfalse; + return Qtrue; +} + +static VALUE +valid_ordinal_sub(int argc, VALUE *argv, VALUE klass, int need_jd) +{ + VALUE nth, y; + int d, ry, rd; + double sg; + + y = argv[0]; + d = NUM2INT(argv[1]); + sg = NUM2DBL(argv[2]); + + valid_sg(sg); + + { + int rjd, ns; + VALUE rjd2; + + if (!valid_ordinal_p(y, d, sg, + &nth, &ry, + &rd, &rjd, + &ns)) + return Qnil; + if (!need_jd) + return INT2FIX(0); /* dummy */ + encode_jd(nth, rjd, &rjd2); + return rjd2; + } +} + +#ifndef NDEBUG +static VALUE +date_s__valid_ordinal_p(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vd, vsg; + VALUE argv2[3]; + + rb_scan_args(argc, argv, "21", &vy, &vd, &vsg); + + argv2[0] = vy; + argv2[1] = vd; + if (argc < 3) + argv2[2] = DBL2NUM(GREGORIAN); + else + argv2[2] = vsg; + + return valid_ordinal_sub(3, argv2, klass, 1); +} +#endif + +/* + * call-seq: + * Date.valid_ordinal?(year, yday[, start=Date::ITALY]) -> bool + * + * Returns true if the given ordinal date is valid, and false if not. + * + * Date.valid_ordinal?(2001,34) #=> true + * Date.valid_ordinal?(2001,366) #=> false + * + * See also jd and ordinal. + */ +static VALUE +date_s_valid_ordinal_p(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vd, vsg; + VALUE argv2[3]; + + rb_scan_args(argc, argv, "21", &vy, &vd, &vsg); + + argv2[0] = vy; + argv2[1] = vd; + if (argc < 3) + argv2[2] = INT2FIX(DEFAULT_SG); + else + argv2[2] = vsg; + + if (NIL_P(valid_ordinal_sub(3, argv2, klass, 0))) + return Qfalse; + return Qtrue; +} + +static VALUE +valid_commercial_sub(int argc, VALUE *argv, VALUE klass, int need_jd) +{ + VALUE nth, y; + int w, d, ry, rw, rd; + double sg; + + y = argv[0]; + w = NUM2INT(argv[1]); + d = NUM2INT(argv[2]); + sg = NUM2DBL(argv[3]); + + valid_sg(sg); + + { + int rjd, ns; + VALUE rjd2; + + if (!valid_commercial_p(y, w, d, sg, + &nth, &ry, + &rw, &rd, &rjd, + &ns)) + return Qnil; + if (!need_jd) + return INT2FIX(0); /* dummy */ + encode_jd(nth, rjd, &rjd2); + return rjd2; + } +} + +#ifndef NDEBUG +static VALUE +date_s__valid_commercial_p(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vw, vd, vsg; + VALUE argv2[4]; + + rb_scan_args(argc, argv, "31", &vy, &vw, &vd, &vsg); + + argv2[0] = vy; + argv2[1] = vw; + argv2[2] = vd; + if (argc < 4) + argv2[3] = DBL2NUM(GREGORIAN); + else + argv2[3] = vsg; + + return valid_commercial_sub(4, argv2, klass, 1); +} +#endif + +/* + * call-seq: + * Date.valid_commercial?(cwyear, cweek, cwday[, start=Date::ITALY]) -> bool + * + * Returns true if the given week date is valid, and false if not. + * + * Date.valid_commercial?(2001,5,6) #=> true + * Date.valid_commercial?(2001,5,8) #=> false + * + * See also jd and commercial. + */ +static VALUE +date_s_valid_commercial_p(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vw, vd, vsg; + VALUE argv2[4]; + + rb_scan_args(argc, argv, "31", &vy, &vw, &vd, &vsg); + + argv2[0] = vy; + argv2[1] = vw; + argv2[2] = vd; + if (argc < 4) + argv2[3] = INT2FIX(DEFAULT_SG); + else + argv2[3] = vsg; + + if (NIL_P(valid_commercial_sub(4, argv2, klass, 0))) + return Qfalse; + return Qtrue; +} + +#ifndef NDEBUG +static VALUE +valid_weeknum_sub(int argc, VALUE *argv, VALUE klass, int need_jd) +{ + VALUE nth, y; + int w, d, f, ry, rw, rd; + double sg; + + y = argv[0]; + w = NUM2INT(argv[1]); + d = NUM2INT(argv[2]); + f = NUM2INT(argv[3]); + sg = NUM2DBL(argv[4]); + + valid_sg(sg); + + { + int rjd, ns; + VALUE rjd2; + + if (!valid_weeknum_p(y, w, d, f, sg, + &nth, &ry, + &rw, &rd, &rjd, + &ns)) + return Qnil; + if (!need_jd) + return INT2FIX(0); /* dummy */ + encode_jd(nth, rjd, &rjd2); + return rjd2; + } +} + +static VALUE +date_s__valid_weeknum_p(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vw, vd, vf, vsg; + VALUE argv2[5]; + + rb_scan_args(argc, argv, "41", &vy, &vw, &vd, &vf, &vsg); + + argv2[0] = vy; + argv2[1] = vw; + argv2[2] = vd; + argv2[3] = vf; + if (argc < 5) + argv2[4] = DBL2NUM(GREGORIAN); + else + argv2[4] = vsg; + + return valid_weeknum_sub(5, argv2, klass, 1); +} + +static VALUE +date_s_valid_weeknum_p(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vw, vd, vf, vsg; + VALUE argv2[5]; + + rb_scan_args(argc, argv, "41", &vy, &vw, &vd, &vf, &vsg); + + argv2[0] = vy; + argv2[1] = vw; + argv2[2] = vd; + argv2[3] = vf; + if (argc < 5) + argv2[4] = INT2FIX(DEFAULT_SG); + else + argv2[4] = vsg; + + if (NIL_P(valid_weeknum_sub(5, argv2, klass, 0))) + return Qfalse; + return Qtrue; +} + +static VALUE +valid_nth_kday_sub(int argc, VALUE *argv, VALUE klass, int need_jd) +{ + VALUE nth, y; + int m, n, k, ry, rm, rn, rk; + double sg; + + y = argv[0]; + m = NUM2INT(argv[1]); + n = NUM2INT(argv[2]); + k = NUM2INT(argv[3]); + sg = NUM2DBL(argv[4]); + + { + int rjd, ns; + VALUE rjd2; + + if (!valid_nth_kday_p(y, m, n, k, sg, + &nth, &ry, + &rm, &rn, &rk, &rjd, + &ns)) + return Qnil; + if (!need_jd) + return INT2FIX(0); /* dummy */ + encode_jd(nth, rjd, &rjd2); + return rjd2; + } +} + +static VALUE +date_s__valid_nth_kday_p(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vm, vn, vk, vsg; + VALUE argv2[5]; + + rb_scan_args(argc, argv, "41", &vy, &vm, &vn, &vk, &vsg); + + argv2[0] = vy; + argv2[1] = vm; + argv2[2] = vn; + argv2[3] = vk; + if (argc < 5) + argv2[4] = DBL2NUM(GREGORIAN); + else + argv2[4] = vsg; + + return valid_nth_kday_sub(5, argv2, klass, 1); +} + +static VALUE +date_s_valid_nth_kday_p(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vm, vn, vk, vsg; + VALUE argv2[5]; + + rb_scan_args(argc, argv, "41", &vy, &vm, &vn, &vk, &vsg); + + argv2[0] = vy; + argv2[1] = vm; + argv2[2] = vn; + argv2[3] = vk; + if (argc < 5) + argv2[4] = INT2FIX(DEFAULT_SG); + else + argv2[4] = vsg; + + if (NIL_P(valid_nth_kday_sub(5, argv2, klass, 0))) + return Qfalse; + return Qtrue; +} + +static VALUE +date_s_zone_to_diff(VALUE klass, VALUE str) +{ + return date_zone_to_diff(str); +} +#endif + +/* + * call-seq: + * Date.julian_leap?(year) -> bool + * + * Returns true if the given year is a leap year of the proleptic + * Julian calendar. + * + * Date.julian_leap?(1900) #=> true + * Date.julian_leap?(1901) #=> false + */ +static VALUE +date_s_julian_leap_p(VALUE klass, VALUE y) +{ + VALUE nth; + int ry; + + decode_year(y, +1, &nth, &ry); + return f_boolcast(c_julian_leap_p(ry)); +} + +/* + * call-seq: + * Date.gregorian_leap?(year) -> bool + * Date.leap?(year) -> bool + * + * Returns true if the given year is a leap year of the proleptic + * Gregorian calendar. + * + * Date.gregorian_leap?(1900) #=> false + * Date.gregorian_leap?(2000) #=> true + */ +static VALUE +date_s_gregorian_leap_p(VALUE klass, VALUE y) +{ + VALUE nth; + int ry; + + decode_year(y, -1, &nth, &ry); + return f_boolcast(c_gregorian_leap_p(ry)); +} + +static void +d_lite_gc_mark(void *ptr) +{ + union DateData *dat = ptr; + if (simple_dat_p(dat)) + rb_gc_mark(dat->s.nth); + else { + rb_gc_mark(dat->c.nth); + rb_gc_mark(dat->c.sf); + } +} + +static size_t +d_lite_memsize(const void *ptr) +{ + const union DateData *dat = ptr; + return complex_dat_p(dat) ? sizeof(struct ComplexDateData) : sizeof(struct SimpleDateData); +} + +static const rb_data_type_t d_lite_type = { + "Date", + {d_lite_gc_mark, RUBY_TYPED_DEFAULT_FREE, d_lite_memsize,}, + 0, 0, + RUBY_TYPED_FREE_IMMEDIATELY|RUBY_TYPED_WB_PROTECTED, +}; + +inline static VALUE +d_simple_new_internal(VALUE klass, + VALUE nth, int jd, + double sg, + int y, int m, int d, + unsigned flags) +{ + struct SimpleDateData *dat; + VALUE obj; + + obj = TypedData_Make_Struct(klass, struct SimpleDateData, + &d_lite_type, dat); + set_to_simple(obj, dat, nth, jd, sg, y, m, d, flags & ~COMPLEX_DAT); + + assert(have_jd_p(dat) || have_civil_p(dat)); + + return obj; +} + +inline static VALUE +d_complex_new_internal(VALUE klass, + VALUE nth, int jd, + int df, VALUE sf, + int of, double sg, + int y, int m, int d, + int h, int min, int s, + unsigned flags) +{ + struct ComplexDateData *dat; + VALUE obj; + + obj = TypedData_Make_Struct(klass, struct ComplexDateData, + &d_lite_type, dat); + set_to_complex(obj, dat, nth, jd, df, sf, of, sg, + y, m, d, h, min, s, flags | COMPLEX_DAT); + + assert(have_jd_p(dat) || have_civil_p(dat)); + assert(have_df_p(dat) || have_time_p(dat)); + + return obj; +} + +static VALUE +d_lite_s_alloc_simple(VALUE klass) +{ + return d_simple_new_internal(klass, + INT2FIX(0), 0, + DEFAULT_SG, + 0, 0, 0, + HAVE_JD); +} + +static VALUE +d_lite_s_alloc_complex(VALUE klass) +{ + return d_complex_new_internal(klass, + INT2FIX(0), 0, + 0, INT2FIX(0), + 0, DEFAULT_SG, + 0, 0, 0, + 0, 0, 0, + HAVE_JD | HAVE_DF); +} + +static VALUE +d_lite_s_alloc(VALUE klass) +{ + return d_lite_s_alloc_complex(klass); +} + +static void +old_to_new(VALUE ajd, VALUE of, VALUE sg, + VALUE *rnth, int *rjd, int *rdf, VALUE *rsf, + int *rof, double *rsg) +{ + VALUE jd, df, sf, of2, t; + + decode_day(f_add(ajd, half_days_in_day), + &jd, &df, &sf); + t = day_to_sec(of); + of2 = f_round(t); + + if (!f_eqeq_p(of2, t)) + rb_warning("fraction of offset is ignored"); + + decode_jd(jd, rnth, rjd); + + *rdf = NUM2INT(df); + *rsf = sf; + *rof = NUM2INT(of2); + *rsg = NUM2DBL(sg); + + if (*rdf < 0 || *rdf >= DAY_IN_SECONDS) + rb_raise(rb_eArgError, "invalid day fraction"); + + if (f_lt_p(*rsf, INT2FIX(0)) || + f_ge_p(*rsf, INT2FIX(SECOND_IN_NANOSECONDS))) + + if (*rof < -DAY_IN_SECONDS || *rof > DAY_IN_SECONDS) { + *rof = 0; + rb_warning("invalid offset is ignored"); + } + + if (!c_valid_start_p(*rsg)) { + *rsg = DEFAULT_SG; + rb_warning("invalid start is ignored"); + } +} + +#ifndef NDEBUG +static VALUE +date_s_new_bang(int argc, VALUE *argv, VALUE klass) +{ + VALUE ajd, of, sg, nth, sf; + int jd, df, rof; + double rsg; + + rb_scan_args(argc, argv, "03", &ajd, &of, &sg); + + switch (argc) { + case 0: + ajd = INT2FIX(0); + case 1: + of = INT2FIX(0); + case 2: + sg = INT2FIX(DEFAULT_SG); + } + + old_to_new(ajd, of, sg, + &nth, &jd, &df, &sf, &rof, &rsg); + + if (!df && f_zero_p(sf) && !rof) + return d_simple_new_internal(klass, + nth, jd, + rsg, + 0, 0, 0, + HAVE_JD); + else + return d_complex_new_internal(klass, + nth, jd, + df, sf, + rof, rsg, + 0, 0, 0, + 0, 0, 0, + HAVE_JD | HAVE_DF); +} +#endif + +inline static int +wholenum_p(VALUE x) +{ + if (FIXNUM_P(x)) + return 1; + switch (TYPE(x)) { + case T_BIGNUM: + return 1; + case T_FLOAT: + { + double d = RFLOAT_VALUE(x); + return round(d) == d; + } + break; + case T_RATIONAL: + { + VALUE den = rb_rational_den(x); + return FIXNUM_P(den) && FIX2LONG(den) == 1; + } + break; + } + return 0; +} + +inline static VALUE +to_integer(VALUE x) +{ + if (FIXNUM_P(x) || RB_TYPE_P(x, T_BIGNUM)) + return x; + return f_to_i(x); +} + +inline static VALUE +d_trunc(VALUE d, VALUE *fr) +{ + VALUE rd; + + if (wholenum_p(d)) { + rd = to_integer(d); + *fr = INT2FIX(0); + } + else { + rd = f_idiv(d, INT2FIX(1)); + *fr = f_mod(d, INT2FIX(1)); + } + return rd; +} + +#define jd_trunc d_trunc +#define k_trunc d_trunc + +inline static VALUE +h_trunc(VALUE h, VALUE *fr) +{ + VALUE rh; + + if (wholenum_p(h)) { + rh = to_integer(h); + *fr = INT2FIX(0); + } + else { + rh = f_idiv(h, INT2FIX(1)); + *fr = f_mod(h, INT2FIX(1)); + *fr = f_quo(*fr, INT2FIX(24)); + } + return rh; +} + +inline static VALUE +min_trunc(VALUE min, VALUE *fr) +{ + VALUE rmin; + + if (wholenum_p(min)) { + rmin = to_integer(min); + *fr = INT2FIX(0); + } + else { + rmin = f_idiv(min, INT2FIX(1)); + *fr = f_mod(min, INT2FIX(1)); + *fr = f_quo(*fr, INT2FIX(1440)); + } + return rmin; +} + +inline static VALUE +s_trunc(VALUE s, VALUE *fr) +{ + VALUE rs; + + if (wholenum_p(s)) { + rs = to_integer(s); + *fr = INT2FIX(0); + } + else { + rs = f_idiv(s, INT2FIX(1)); + *fr = f_mod(s, INT2FIX(1)); + *fr = f_quo(*fr, INT2FIX(86400)); + } + return rs; +} + +#define num2num_with_frac(s,n) \ +{\ + s = s##_trunc(v##s, &fr);\ + if (f_nonzero_p(fr)) {\ + if (argc > n)\ + rb_raise(rb_eArgError, "invalid fraction");\ + fr2 = fr;\ + }\ +} + +#define num2int_with_frac(s,n) \ +{\ + s = NUM2INT(s##_trunc(v##s, &fr));\ + if (f_nonzero_p(fr)) {\ + if (argc > n)\ + rb_raise(rb_eArgError, "invalid fraction");\ + fr2 = fr;\ + }\ +} + +#define canon24oc() \ +{\ + if (rh == 24) {\ + rh = 0;\ + fr2 = f_add(fr2, INT2FIX(1));\ + }\ +} + +#define add_frac() \ +{\ + if (f_nonzero_p(fr2))\ + ret = d_lite_plus(ret, fr2);\ +} + +#define val2sg(vsg,dsg) \ +{\ + dsg = NUM2DBL(vsg);\ + if (!c_valid_start_p(dsg)) {\ + dsg = DEFAULT_SG;\ + rb_warning("invalid start is ignored");\ + }\ +} + +static VALUE d_lite_plus(VALUE, VALUE); + +/* + * call-seq: + * Date.jd([jd=0[, start=Date::ITALY]]) -> date + * + * Creates a date object denoting the given chronological Julian day + * number. + * + * Date.jd(2451944) #=> # + * Date.jd(2451945) #=> # + * Date.jd(0) #=> # + * + * See also new. + */ +static VALUE +date_s_jd(int argc, VALUE *argv, VALUE klass) +{ + VALUE vjd, vsg, jd, fr, fr2, ret; + double sg; + + rb_scan_args(argc, argv, "02", &vjd, &vsg); + + jd = INT2FIX(0); + fr2 = INT2FIX(0); + sg = DEFAULT_SG; + + switch (argc) { + case 2: + val2sg(vsg, sg); + case 1: + num2num_with_frac(jd, positive_inf); + } + + { + VALUE nth; + int rjd; + + decode_jd(jd, &nth, &rjd); + ret = d_simple_new_internal(klass, + nth, rjd, + sg, + 0, 0, 0, + HAVE_JD); + } + add_frac(); + return ret; +} + +/* + * call-seq: + * Date.ordinal([year=-4712[, yday=1[, start=Date::ITALY]]]) -> date + * + * Creates a date object denoting the given ordinal date. + * + * The day of year should be a negative or a positive number (as a + * relative day from the end of year when negative). It should not be + * zero. + * + * Date.ordinal(2001) #=> # + * Date.ordinal(2001,34) #=> # + * Date.ordinal(2001,-1) #=> # + * + * See also jd and new. + */ +static VALUE +date_s_ordinal(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vd, vsg, y, fr, fr2, ret; + int d; + double sg; + + rb_scan_args(argc, argv, "03", &vy, &vd, &vsg); + + y = INT2FIX(-4712); + d = 1; + fr2 = INT2FIX(0); + sg = DEFAULT_SG; + + switch (argc) { + case 3: + val2sg(vsg, sg); + case 2: + num2int_with_frac(d, positive_inf); + case 1: + y = vy; + } + + { + VALUE nth; + int ry, rd, rjd, ns; + + if (!valid_ordinal_p(y, d, sg, + &nth, &ry, + &rd, &rjd, + &ns)) + rb_raise(rb_eArgError, "invalid date"); + + ret = d_simple_new_internal(klass, + nth, rjd, + sg, + 0, 0, 0, + HAVE_JD); + } + add_frac(); + return ret; +} + +/* + * call-seq: + * Date.civil([year=-4712[, month=1[, mday=1[, start=Date::ITALY]]]]) -> date + * Date.new([year=-4712[, month=1[, mday=1[, start=Date::ITALY]]]]) -> date + * + * Creates a date object denoting the given calendar date. + * + * In this class, BCE years are counted astronomically. Thus, the + * year before the year 1 is the year zero, and the year preceding the + * year zero is the year -1. The month and the day of month should be + * a negative or a positive number (as a relative month/day from the + * end of year/month when negative). They should not be zero. + * + * The last argument should be a Julian day number which denotes the + * day of calendar reform. Date::ITALY (2299161=1582-10-15), + * Date::ENGLAND (2361222=1752-09-14), Date::GREGORIAN (the proleptic + * Gregorian calendar) and Date::JULIAN (the proleptic Julian + * calendar) can be specified as a day of calendar reform. + * + * Date.new(2001) #=> # + * Date.new(2001,2,3) #=> # + * Date.new(2001,2,-1) #=> # + * + * See also jd. + */ +static VALUE +date_s_civil(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vm, vd, vsg, y, fr, fr2, ret; + int m, d; + double sg; + + rb_scan_args(argc, argv, "04", &vy, &vm, &vd, &vsg); + + y = INT2FIX(-4712); + m = 1; + d = 1; + fr2 = INT2FIX(0); + sg = DEFAULT_SG; + + switch (argc) { + case 4: + val2sg(vsg, sg); + case 3: + num2int_with_frac(d, positive_inf); + case 2: + m = NUM2INT(vm); + case 1: + y = vy; + } + + if (guess_style(y, sg) < 0) { + VALUE nth; + int ry, rm, rd; + + if (!valid_gregorian_p(y, m, d, + &nth, &ry, + &rm, &rd)) + rb_raise(rb_eArgError, "invalid date"); + + ret = d_simple_new_internal(klass, + nth, 0, + sg, + ry, rm, rd, + HAVE_CIVIL); + } + else { + VALUE nth; + int ry, rm, rd, rjd, ns; + + if (!valid_civil_p(y, m, d, sg, + &nth, &ry, + &rm, &rd, &rjd, + &ns)) + rb_raise(rb_eArgError, "invalid date"); + + ret = d_simple_new_internal(klass, + nth, rjd, + sg, + ry, rm, rd, + HAVE_JD | HAVE_CIVIL); + } + add_frac(); + return ret; +} + +/* + * call-seq: + * Date.commercial([cwyear=-4712[, cweek=1[, cwday=1[, start=Date::ITALY]]]]) -> date + * + * Creates a date object denoting the given week date. + * + * The week and the day of week should be a negative or a positive + * number (as a relative week/day from the end of year/week when + * negative). They should not be zero. + * + * Date.commercial(2001) #=> # + * Date.commercial(2002) #=> # + * Date.commercial(2001,5,6) #=> # + * + * See also jd and new. + */ +static VALUE +date_s_commercial(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vw, vd, vsg, y, fr, fr2, ret; + int w, d; + double sg; + + rb_scan_args(argc, argv, "04", &vy, &vw, &vd, &vsg); + + y = INT2FIX(-4712); + w = 1; + d = 1; + fr2 = INT2FIX(0); + sg = DEFAULT_SG; + + switch (argc) { + case 4: + val2sg(vsg, sg); + case 3: + num2int_with_frac(d, positive_inf); + case 2: + w = NUM2INT(vw); + case 1: + y = vy; + } + + { + VALUE nth; + int ry, rw, rd, rjd, ns; + + if (!valid_commercial_p(y, w, d, sg, + &nth, &ry, + &rw, &rd, &rjd, + &ns)) + rb_raise(rb_eArgError, "invalid date"); + + ret = d_simple_new_internal(klass, + nth, rjd, + sg, + 0, 0, 0, + HAVE_JD); + } + add_frac(); + return ret; +} + +#ifndef NDEBUG +static VALUE +date_s_weeknum(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vw, vd, vf, vsg, y, fr, fr2, ret; + int w, d, f; + double sg; + + rb_scan_args(argc, argv, "05", &vy, &vw, &vd, &vf, &vsg); + + y = INT2FIX(-4712); + w = 0; + d = 1; + f = 0; + fr2 = INT2FIX(0); + sg = DEFAULT_SG; + + switch (argc) { + case 5: + val2sg(vsg, sg); + case 4: + f = NUM2INT(vf); + case 3: + num2int_with_frac(d, positive_inf); + case 2: + w = NUM2INT(vw); + case 1: + y = vy; + } + + { + VALUE nth; + int ry, rw, rd, rjd, ns; + + if (!valid_weeknum_p(y, w, d, f, sg, + &nth, &ry, + &rw, &rd, &rjd, + &ns)) + rb_raise(rb_eArgError, "invalid date"); + + ret = d_simple_new_internal(klass, + nth, rjd, + sg, + 0, 0, 0, + HAVE_JD); + } + add_frac(); + return ret; +} + +static VALUE +date_s_nth_kday(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vm, vn, vk, vsg, y, fr, fr2, ret; + int m, n, k; + double sg; + + rb_scan_args(argc, argv, "05", &vy, &vm, &vn, &vk, &vsg); + + y = INT2FIX(-4712); + m = 1; + n = 1; + k = 1; + fr2 = INT2FIX(0); + sg = DEFAULT_SG; + + switch (argc) { + case 5: + val2sg(vsg, sg); + case 4: + num2int_with_frac(k, positive_inf); + case 3: + n = NUM2INT(vn); + case 2: + m = NUM2INT(vm); + case 1: + y = vy; + } + + { + VALUE nth; + int ry, rm, rn, rk, rjd, ns; + + if (!valid_nth_kday_p(y, m, n, k, sg, + &nth, &ry, + &rm, &rn, &rk, &rjd, + &ns)) + rb_raise(rb_eArgError, "invalid date"); + + ret = d_simple_new_internal(klass, + nth, rjd, + sg, + 0, 0, 0, + HAVE_JD); + } + add_frac(); + return ret; +} +#endif + +#if !defined(HAVE_GMTIME_R) +static struct tm* +gmtime_r(const time_t *t, struct tm *tm) +{ + auto struct tm *tmp = gmtime(t); + if (tmp) + *tm = *tmp; + return tmp; +} + +static struct tm* +localtime_r(const time_t *t, struct tm *tm) +{ + auto struct tm *tmp = localtime(t); + if (tmp) + *tm = *tmp; + return tmp; +} +#endif + +static void set_sg(union DateData *, double); + +/* + * call-seq: + * Date.today([start=Date::ITALY]) -> date + * + * Date.today #=> # + * + * Creates a date object denoting the present day. + */ +static VALUE +date_s_today(int argc, VALUE *argv, VALUE klass) +{ + VALUE vsg, nth, ret; + double sg; + time_t t; + struct tm tm; + int y, ry, m, d; + + rb_scan_args(argc, argv, "01", &vsg); + + if (argc < 1) + sg = DEFAULT_SG; + else + val2sg(vsg, sg); + + if (time(&t) == -1) + rb_sys_fail("time"); + tzset(); + if (!localtime_r(&t, &tm)) + rb_sys_fail("localtime"); + + y = tm.tm_year + 1900; + m = tm.tm_mon + 1; + d = tm.tm_mday; + + decode_year(INT2FIX(y), -1, &nth, &ry); + + ret = d_simple_new_internal(klass, + nth, 0, + GREGORIAN, + ry, m, d, + HAVE_CIVIL); + { + get_d1(ret); + set_sg(dat, sg); + } + return ret; +} + +#define set_hash0(k,v) rb_hash_aset(hash, k, v) +#define ref_hash0(k) rb_hash_aref(hash, k) +#define del_hash0(k) rb_hash_delete(hash, k) + +#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))) + +static VALUE +rt_rewrite_frags(VALUE hash) +{ + VALUE seconds; + + seconds = ref_hash("seconds"); + if (!NIL_P(seconds)) { + VALUE offset, d, h, min, s, fr; + + offset = ref_hash("offset"); + if (!NIL_P(offset)) + seconds = f_add(seconds, offset); + + d = f_idiv(seconds, INT2FIX(DAY_IN_SECONDS)); + fr = f_mod(seconds, INT2FIX(DAY_IN_SECONDS)); + + h = f_idiv(fr, INT2FIX(HOUR_IN_SECONDS)); + fr = f_mod(fr, INT2FIX(HOUR_IN_SECONDS)); + + min = f_idiv(fr, INT2FIX(MINUTE_IN_SECONDS)); + fr = f_mod(fr, INT2FIX(MINUTE_IN_SECONDS)); + + s = f_idiv(fr, INT2FIX(1)); + fr = f_mod(fr, INT2FIX(1)); + + set_hash("jd", f_add(UNIX_EPOCH_IN_CJD, d)); + set_hash("hour", h); + set_hash("min", min); + set_hash("sec", s); + set_hash("sec_fraction", fr); + del_hash("seconds"); + } + return hash; +} + +#define sym(x) ID2SYM(rb_intern(x)) + +static VALUE d_lite_year(VALUE); +static VALUE d_lite_wday(VALUE); +static VALUE d_lite_jd(VALUE); + +static VALUE +rt_complete_frags(VALUE klass, VALUE hash) +{ + static VALUE tab = Qnil; + int g, e; + VALUE k, a, d; + + if (NIL_P(tab)) { + tab = rb_ary_new3(11, + rb_ary_new3(2, + sym("time"), + rb_ary_new3(3, + sym("hour"), + sym("min"), + sym("sec"))), + rb_ary_new3(2, + Qnil, + rb_ary_new3(1, + sym("jd"))), + rb_ary_new3(2, + sym("ordinal"), + rb_ary_new3(5, + sym("year"), + sym("yday"), + sym("hour"), + sym("min"), + sym("sec"))), + rb_ary_new3(2, + sym("civil"), + rb_ary_new3(6, + sym("year"), + sym("mon"), + sym("mday"), + sym("hour"), + sym("min"), + sym("sec"))), + rb_ary_new3(2, + sym("commercial"), + rb_ary_new3(6, + sym("cwyear"), + sym("cweek"), + sym("cwday"), + sym("hour"), + sym("min"), + sym("sec"))), + rb_ary_new3(2, + sym("wday"), + rb_ary_new3(4, + sym("wday"), + sym("hour"), + sym("min"), + sym("sec"))), + rb_ary_new3(2, + sym("wnum0"), + rb_ary_new3(6, + sym("year"), + sym("wnum0"), + sym("wday"), + sym("hour"), + sym("min"), + sym("sec"))), + rb_ary_new3(2, + sym("wnum1"), + rb_ary_new3(6, + sym("year"), + sym("wnum1"), + sym("wday"), + sym("hour"), + sym("min"), + sym("sec"))), + rb_ary_new3(2, + Qnil, + rb_ary_new3(6, + sym("cwyear"), + sym("cweek"), + sym("wday"), + sym("hour"), + sym("min"), + sym("sec"))), + rb_ary_new3(2, + Qnil, + rb_ary_new3(6, + sym("year"), + sym("wnum0"), + sym("cwday"), + sym("hour"), + sym("min"), + sym("sec"))), + rb_ary_new3(2, + Qnil, + rb_ary_new3(6, + sym("year"), + sym("wnum1"), + sym("cwday"), + sym("hour"), + sym("min"), + sym("sec")))); + rb_gc_register_mark_object(tab); + } + + { + int i, eno = 0, idx = 0; + + for (i = 0; i < RARRAY_LENINT(tab); i++) { + VALUE x, a; + + x = RARRAY_PTR(tab)[i]; + a = RARRAY_PTR(x)[1]; + + { + int j, n = 0; + + for (j = 0; j < RARRAY_LENINT(a); j++) + if (!NIL_P(ref_hash0(RARRAY_PTR(a)[j]))) + n++; + if (n > eno) { + eno = n; + idx = i; + } + } + } + if (eno == 0) + g = 0; + else { + g = 1; + k = RARRAY_PTR(RARRAY_PTR(tab)[idx])[0]; + a = RARRAY_PTR(RARRAY_PTR(tab)[idx])[1]; + e = eno; + } + } + + d = Qnil; + + if (g && !NIL_P(k) && (RARRAY_LENINT(a) - e)) { + if (k == sym("ordinal")) { + if (NIL_P(ref_hash("year"))) { + if (NIL_P(d)) + d = date_s_today(0, (VALUE *)0, cDate); + set_hash("year", d_lite_year(d)); + } + if (NIL_P(ref_hash("yday"))) + set_hash("yday", INT2FIX(1)); + } + else if (k == sym("civil")) { + int i; + + for (i = 0; i < RARRAY_LENINT(a); i++) { + VALUE e = RARRAY_PTR(a)[i]; + + if (!NIL_P(ref_hash0(e))) + break; + if (NIL_P(d)) + d = date_s_today(0, (VALUE *)0, cDate); + set_hash0(e, rb_funcall(d, SYM2ID(e), 0)); + } + if (NIL_P(ref_hash("mon"))) + set_hash("mon", INT2FIX(1)); + if (NIL_P(ref_hash("mday"))) + set_hash("mday", INT2FIX(1)); + } + else if (k == sym("commercial")) { + int i; + + for (i = 0; i < RARRAY_LENINT(a); i++) { + VALUE e = RARRAY_PTR(a)[i]; + + if (!NIL_P(ref_hash0(e))) + break; + if (NIL_P(d)) + d = date_s_today(0, (VALUE *)0, cDate); + set_hash0(e, rb_funcall(d, SYM2ID(e), 0)); + } + if (NIL_P(ref_hash("cweek"))) + set_hash("cweek", INT2FIX(1)); + if (NIL_P(ref_hash("cwday"))) + set_hash("cwday", INT2FIX(1)); + } + else if (k == sym("wday")) { + if (NIL_P(d)) + d = date_s_today(0, (VALUE *)0, cDate); + set_hash("jd", d_lite_jd(f_add(f_sub(d, + d_lite_wday(d)), + ref_hash("wday")))); + } + else if (k == sym("wnum0")) { + int i; + + for (i = 0; i < RARRAY_LENINT(a); i++) { + VALUE e = RARRAY_PTR(a)[i]; + + if (!NIL_P(ref_hash0(e))) + break; + if (NIL_P(d)) + d = date_s_today(0, (VALUE *)0, cDate); + set_hash0(e, rb_funcall(d, SYM2ID(e), 0)); + } + if (NIL_P(ref_hash("wnum0"))) + set_hash("wnum0", INT2FIX(0)); + if (NIL_P(ref_hash("wday"))) + set_hash("wday", INT2FIX(0)); + } + else if (k == sym("wnum1")) { + int i; + + for (i = 0; i < RARRAY_LENINT(a); i++) { + VALUE e = RARRAY_PTR(a)[i]; + + if (!NIL_P(ref_hash0(e))) + break; + if (NIL_P(d)) + d = date_s_today(0, (VALUE *)0, cDate); + set_hash0(e, rb_funcall(d, SYM2ID(e), 0)); + } + if (NIL_P(ref_hash("wnum1"))) + set_hash("wnum1", INT2FIX(0)); + if (NIL_P(ref_hash("wday"))) + set_hash("wday", INT2FIX(1)); + } + } + + if (g && k == sym("time")) { + if (f_le_p(klass, cDateTime)) { + if (NIL_P(d)) + d = date_s_today(0, (VALUE *)0, cDate); + if (NIL_P(ref_hash("jd"))) + set_hash("jd", d_lite_jd(d)); + } + } + + if (NIL_P(ref_hash("hour"))) + set_hash("hour", INT2FIX(0)); + if (NIL_P(ref_hash("min"))) + set_hash("min", INT2FIX(0)); + if (NIL_P(ref_hash("sec"))) + set_hash("sec", INT2FIX(0)); + else if (f_gt_p(ref_hash("sec"), INT2FIX(59))) + set_hash("sec", INT2FIX(59)); + + return hash; +} + +static VALUE +rt__valid_jd_p(VALUE jd, VALUE sg) +{ + return jd; +} + +static VALUE +rt__valid_ordinal_p(VALUE y, VALUE d, VALUE sg) +{ + VALUE nth, rjd2; + int ry, rd, rjd, ns; + + if (!valid_ordinal_p(y, NUM2INT(d), NUM2DBL(sg), + &nth, &ry, + &rd, &rjd, + &ns)) + return Qnil; + encode_jd(nth, rjd, &rjd2); + return rjd2; +} + +static VALUE +rt__valid_civil_p(VALUE y, VALUE m, VALUE d, VALUE sg) +{ + VALUE nth, rjd2; + int ry, rm, rd, rjd, ns; + + if (!valid_civil_p(y, NUM2INT(m), NUM2INT(d), NUM2DBL(sg), + &nth, &ry, + &rm, &rd, &rjd, + &ns)) + return Qnil; + encode_jd(nth, rjd, &rjd2); + return rjd2; +} + +static VALUE +rt__valid_commercial_p(VALUE y, VALUE w, VALUE d, VALUE sg) +{ + VALUE nth, rjd2; + int ry, rw, rd, rjd, ns; + + if (!valid_commercial_p(y, NUM2INT(w), NUM2INT(d), NUM2DBL(sg), + &nth, &ry, + &rw, &rd, &rjd, + &ns)) + return Qnil; + encode_jd(nth, rjd, &rjd2); + return rjd2; +} + +static VALUE +rt__valid_weeknum_p(VALUE y, VALUE w, VALUE d, VALUE f, VALUE sg) +{ + VALUE nth, rjd2; + int ry, rw, rd, rjd, ns; + + if (!valid_weeknum_p(y, NUM2INT(w), NUM2INT(d), NUM2INT(f), NUM2DBL(sg), + &nth, &ry, + &rw, &rd, &rjd, + &ns)) + return Qnil; + encode_jd(nth, rjd, &rjd2); + return rjd2; +} + +static VALUE +rt__valid_date_frags_p(VALUE hash, VALUE sg) +{ + { + VALUE vjd; + + if (!NIL_P(vjd = ref_hash("jd"))) { + VALUE jd = rt__valid_jd_p(vjd, sg); + if (!NIL_P(jd)) + return jd; + } + } + + { + VALUE year, yday; + + if (!NIL_P(yday = ref_hash("yday")) && + !NIL_P(year = ref_hash("year"))) { + VALUE jd = rt__valid_ordinal_p(year, yday, sg); + if (!NIL_P(jd)) + return jd; + } + } + + { + VALUE year, mon, mday; + + if (!NIL_P(mday = ref_hash("mday")) && + !NIL_P(mon = ref_hash("mon")) && + !NIL_P(year = ref_hash("year"))) { + VALUE jd = rt__valid_civil_p(year, mon, mday, sg); + if (!NIL_P(jd)) + return jd; + } + } + + { + VALUE year, week, wday; + + wday = ref_hash("cwday"); + if (NIL_P(wday)) { + wday = ref_hash("wday"); + if (!NIL_P(wday)) + if (f_zero_p(wday)) + wday = INT2FIX(7); + } + + if (!NIL_P(wday) && + !NIL_P(week = ref_hash("cweek")) && + !NIL_P(year = ref_hash("cwyear"))) { + VALUE jd = rt__valid_commercial_p(year, week, wday, sg); + if (!NIL_P(jd)) + return jd; + } + } + + { + VALUE year, week, wday; + + wday = ref_hash("wday"); + if (NIL_P(wday)) { + wday = ref_hash("cwday"); + if (!NIL_P(wday)) + if (f_eqeq_p(wday, INT2FIX(7))) + wday = INT2FIX(0); + } + + if (!NIL_P(wday) && + !NIL_P(week = ref_hash("wnum0")) && + !NIL_P(year = ref_hash("year"))) { + VALUE jd = rt__valid_weeknum_p(year, week, wday, INT2FIX(0), sg); + if (!NIL_P(jd)) + return jd; + } + } + + { + VALUE year, week, wday; + + wday = ref_hash("wday"); + if (NIL_P(wday)) + wday = ref_hash("cwday"); + if (!NIL_P(wday)) + wday = f_mod(f_sub(wday, INT2FIX(1)), + INT2FIX(7)); + + if (!NIL_P(wday) && + !NIL_P(week = ref_hash("wnum1")) && + !NIL_P(year = ref_hash("year"))) { + VALUE jd = rt__valid_weeknum_p(year, week, wday, INT2FIX(1), sg); + if (!NIL_P(jd)) + return jd; + } + } + return Qnil; +} + +static VALUE +d_new_by_frags(VALUE klass, VALUE hash, VALUE sg) +{ + VALUE jd; + + if (!c_valid_start_p(NUM2DBL(sg))) { + sg = INT2FIX(DEFAULT_SG); + rb_warning("invalid start is ignored"); + } + + if (NIL_P(hash)) + rb_raise(rb_eArgError, "invalid date"); + + if (NIL_P(ref_hash("jd")) && + NIL_P(ref_hash("yday")) && + !NIL_P(ref_hash("year")) && + !NIL_P(ref_hash("mon")) && + !NIL_P(ref_hash("mday"))) + jd = rt__valid_civil_p(ref_hash("year"), + ref_hash("mon"), + ref_hash("mday"), sg); + else { + hash = rt_rewrite_frags(hash); + hash = rt_complete_frags(klass, hash); + jd = rt__valid_date_frags_p(hash, sg); + } + + if (NIL_P(jd)) + rb_raise(rb_eArgError, "invalid date"); + { + VALUE nth; + int rjd; + + decode_jd(jd, &nth, &rjd); + return d_simple_new_internal(klass, + nth, rjd, + NUM2DBL(sg), + 0, 0, 0, + HAVE_JD); + } +} + +VALUE date__strptime(const char *str, size_t slen, + const char *fmt, size_t flen, VALUE hash); + +static VALUE +date_s__strptime_internal(int argc, VALUE *argv, VALUE klass, + const char *default_fmt) +{ + VALUE vstr, vfmt, hash; + const char *str, *fmt; + size_t slen, flen; + + rb_scan_args(argc, argv, "11", &vstr, &vfmt); + + StringValue(vstr); + if (!rb_enc_str_asciicompat_p(vstr)) + rb_raise(rb_eArgError, + "string should have ASCII compatible encoding"); + str = RSTRING_PTR(vstr); + slen = RSTRING_LEN(vstr); + if (argc < 2) { + fmt = default_fmt; + flen = strlen(default_fmt); + } + else { + StringValue(vfmt); + if (!rb_enc_str_asciicompat_p(vfmt)) + rb_raise(rb_eArgError, + "format should have ASCII compatible encoding"); + fmt = RSTRING_PTR(vfmt); + flen = RSTRING_LEN(vfmt); + } + hash = rb_hash_new(); + if (NIL_P(date__strptime(str, slen, fmt, flen, hash))) + return Qnil; + + { + VALUE zone = ref_hash("zone"); + VALUE left = ref_hash("leftover"); + + if (!NIL_P(zone)) { + rb_enc_copy(zone, vstr); + OBJ_INFECT(zone, vstr); + set_hash("zone", zone); + } + if (!NIL_P(left)) { + rb_enc_copy(left, vstr); + OBJ_INFECT(left, vstr); + set_hash("leftover", left); + } + } + + return hash; +} + +/* + * call-seq: + * Date._strptime(string[, format='%F']) -> hash + * + * Parses the given representation of date and time with the given + * template, and returns a hash of parsed elements. _strptime does + * not support specification of flags and width unlike strftime. + * + * Date._strptime('2001-02-03', '%Y-%m-%d') + * #=> {:year=>2001, :mon=>2, :mday=>3} + * + * See also strptime(3) and strftime. + */ +static VALUE +date_s__strptime(int argc, VALUE *argv, VALUE klass) +{ + return date_s__strptime_internal(argc, argv, klass, "%F"); +} + +/* + * call-seq: + * Date.strptime([string='-4712-01-01'[, format='%F'[, start=ITALY]]]) -> date + * + * Parses the given representation of date and time with the given + * template, and creates a date object. strptime does not support + * specification of flags and width unlike strftime. + * + * Date.strptime('2001-02-03', '%Y-%m-%d') #=> # + * Date.strptime('03-02-2001', '%d-%m-%Y') #=> # + * Date.strptime('2001-034', '%Y-%j') #=> # + * Date.strptime('2001-W05-6', '%G-W%V-%u') #=> # + * Date.strptime('2001 04 6', '%Y %U %w') #=> # + * Date.strptime('2001 05 6', '%Y %W %u') #=> # + * Date.strptime('sat3feb01', '%a%d%b%y') #=> # + * + * See also strptime(3) and strftime. + */ +static VALUE +date_s_strptime(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, fmt, sg; + + rb_scan_args(argc, argv, "03", &str, &fmt, &sg); + + switch (argc) { + case 0: + str = rb_str_new2("-4712-01-01"); + case 1: + fmt = rb_str_new2("%F"); + case 2: + sg = INT2FIX(DEFAULT_SG); + } + + { + VALUE argv2[2], hash; + + argv2[0] = str; + argv2[1] = fmt; + hash = date_s__strptime(2, argv2, klass); + return d_new_by_frags(klass, hash, sg); + } +} + +VALUE date__parse(VALUE str, VALUE comp); + +static VALUE +date_s__parse_internal(int argc, VALUE *argv, VALUE klass) +{ + VALUE vstr, vcomp, hash; + + rb_scan_args(argc, argv, "11", &vstr, &vcomp); + StringValue(vstr); + if (!rb_enc_str_asciicompat_p(vstr)) + rb_raise(rb_eArgError, + "string should have ASCII compatible encoding"); + if (argc < 2) + vcomp = Qtrue; + + hash = date__parse(vstr, vcomp); + + { + VALUE zone = ref_hash("zone"); + + if (!NIL_P(zone)) { + rb_enc_copy(zone, vstr); + OBJ_INFECT(zone, vstr); + set_hash("zone", zone); + } + } + + return hash; +} + +/* + * call-seq: + * Date._parse(string[, comp=true]) -> hash + * + * Parses the given representation of date and time, and returns a + * hash of parsed elements. This method does not function as a + * validator. + * + * If the optional second argument is true and the detected year is in + * the range "00" to "99", considers the year a 2-digit form and makes + * it full. + * + * Date._parse('2001-02-03') #=> {:year=>2001, :mon=>2, :mday=>3} + */ +static VALUE +date_s__parse(int argc, VALUE *argv, VALUE klass) +{ + return date_s__parse_internal(argc, argv, klass); +} + +/* + * call-seq: + * Date.parse(string='-4712-01-01'[, comp=true[, start=ITALY]]) -> date + * + * Parses the given representation of date and time, and creates a + * date object. This method does not function as a validator. + * + * If the optional second argument is true and the detected year is in + * the range "00" to "99", considers the year a 2-digit form and makes + * it full. + * + * Date.parse('2001-02-03') #=> # + * Date.parse('20010203') #=> # + * Date.parse('3rd Feb 2001') #=> # + */ +static VALUE +date_s_parse(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, comp, sg; + + rb_scan_args(argc, argv, "03", &str, &comp, &sg); + + switch (argc) { + case 0: + str = rb_str_new2("-4712-01-01"); + case 1: + comp = Qtrue; + case 2: + sg = INT2FIX(DEFAULT_SG); + } + + { + VALUE argv2[2], hash; + + argv2[0] = str; + argv2[1] = comp; + hash = date_s__parse(2, argv2, klass); + return d_new_by_frags(klass, hash, sg); + } +} + +VALUE date__iso8601(VALUE); +VALUE date__rfc3339(VALUE); +VALUE date__xmlschema(VALUE); +VALUE date__rfc2822(VALUE); +VALUE date__httpdate(VALUE); +VALUE date__jisx0301(VALUE); + +/* + * call-seq: + * Date._iso8601(string) -> hash + * + * Returns a hash of parsed elements. + */ +static VALUE +date_s__iso8601(VALUE klass, VALUE str) +{ + return date__iso8601(str); +} + +/* + * call-seq: + * Date.iso8601(string='-4712-01-01'[, start=ITALY]) -> date + * + * Creates a new Date object by parsing from a string according to + * some typical ISO 8601 formats. + * + * Date.iso8601('2001-02-03') #=> # + * Date.iso8601('20010203') #=> # + * Date.iso8601('2001-W05-6') #=> # + */ +static VALUE +date_s_iso8601(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, sg; + + rb_scan_args(argc, argv, "02", &str, &sg); + + switch (argc) { + case 0: + str = rb_str_new2("-4712-01-01"); + case 1: + sg = INT2FIX(DEFAULT_SG); + } + + { + VALUE hash = date_s__iso8601(klass, str); + return d_new_by_frags(klass, hash, sg); + } +} + +/* + * call-seq: + * Date._rfc3339(string) -> hash + * + * Returns a hash of parsed elements. + */ +static VALUE +date_s__rfc3339(VALUE klass, VALUE str) +{ + return date__rfc3339(str); +} + +/* + * call-seq: + * Date.rfc3339(string='-4712-01-01T00:00:00+00:00'[, start=ITALY]) -> date + * + * Creates a new Date object by parsing from a string according to + * some typical RFC 3339 formats. + * + * Date.rfc3339('2001-02-03T04:05:06+07:00') #=> # + */ +static VALUE +date_s_rfc3339(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, sg; + + rb_scan_args(argc, argv, "02", &str, &sg); + + switch (argc) { + case 0: + str = rb_str_new2("-4712-01-01T00:00:00+00:00"); + case 1: + sg = INT2FIX(DEFAULT_SG); + } + + { + VALUE hash = date_s__rfc3339(klass, str); + return d_new_by_frags(klass, hash, sg); + } +} + +/* + * call-seq: + * Date._xmlschema(string) -> hash + * + * Returns a hash of parsed elements. + */ +static VALUE +date_s__xmlschema(VALUE klass, VALUE str) +{ + return date__xmlschema(str); +} + +/* + * call-seq: + * Date.xmlschema(string='-4712-01-01'[, start=ITALY]) -> date + * + * Creates a new Date object by parsing from a string according to + * some typical XML Schema formats. + * + * Date.xmlschema('2001-02-03') #=> # + */ +static VALUE +date_s_xmlschema(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, sg; + + rb_scan_args(argc, argv, "02", &str, &sg); + + switch (argc) { + case 0: + str = rb_str_new2("-4712-01-01"); + case 1: + sg = INT2FIX(DEFAULT_SG); + } + + { + VALUE hash = date_s__xmlschema(klass, str); + return d_new_by_frags(klass, hash, sg); + } +} + +/* + * call-seq: + * Date._rfc2822(string) -> hash + * Date._rfc822(string) -> hash + * + * Returns a hash of parsed elements. + */ +static VALUE +date_s__rfc2822(VALUE klass, VALUE str) +{ + return date__rfc2822(str); +} + +/* + * call-seq: + * Date.rfc2822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=ITALY]) -> date + * Date.rfc822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=ITALY]) -> date + * + * Creates a new Date object by parsing from a string according to + * some typical RFC 2822 formats. + * + * Date.rfc2822('Sat, 3 Feb 2001 00:00:00 +0000') + * #=> # + */ +static VALUE +date_s_rfc2822(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, sg; + + rb_scan_args(argc, argv, "02", &str, &sg); + + switch (argc) { + case 0: + str = rb_str_new2("Mon, 1 Jan -4712 00:00:00 +0000"); + case 1: + sg = INT2FIX(DEFAULT_SG); + } + + { + VALUE hash = date_s__rfc2822(klass, str); + return d_new_by_frags(klass, hash, sg); + } +} + +/* + * call-seq: + * Date._httpdate(string) -> hash + * + * Returns a hash of parsed elements. + */ +static VALUE +date_s__httpdate(VALUE klass, VALUE str) +{ + return date__httpdate(str); +} + +/* + * call-seq: + * Date.httpdate(string='Mon, 01 Jan -4712 00:00:00 GMT'[, start=ITALY]) -> date + * + * Creates a new Date object by parsing from a string according to + * some RFC 2616 format. + * + * Date.httpdate('Sat, 03 Feb 2001 00:00:00 GMT') + * #=> # + */ +static VALUE +date_s_httpdate(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, sg; + + rb_scan_args(argc, argv, "02", &str, &sg); + + switch (argc) { + case 0: + str = rb_str_new2("Mon, 01 Jan -4712 00:00:00 GMT"); + case 1: + sg = INT2FIX(DEFAULT_SG); + } + + { + VALUE hash = date_s__httpdate(klass, str); + return d_new_by_frags(klass, hash, sg); + } +} + +/* + * call-seq: + * Date._jisx0301(string) -> hash + * + * Returns a hash of parsed elements. + */ +static VALUE +date_s__jisx0301(VALUE klass, VALUE str) +{ + return date__jisx0301(str); +} + +/* + * call-seq: + * Date.jisx0301(string='-4712-01-01'[, start=ITALY]) -> date + * + * Creates a new Date object by parsing from a string according to + * some typical JIS X 0301 formats. + * + * Date.jisx0301('H13.02.03') #=> # + */ +static VALUE +date_s_jisx0301(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, sg; + + rb_scan_args(argc, argv, "02", &str, &sg); + + switch (argc) { + case 0: + str = rb_str_new2("-4712-01-01"); + case 1: + sg = INT2FIX(DEFAULT_SG); + } + + { + VALUE hash = date_s__jisx0301(klass, str); + return d_new_by_frags(klass, hash, sg); + } +} + +static VALUE +dup_obj(VALUE self) +{ + get_d1a(self); + + if (simple_dat_p(adat)) { + VALUE new = d_lite_s_alloc_simple(rb_obj_class(self)); + { + get_d1b(new); + bdat->s = adat->s; + RB_OBJ_WRITTEN(new, Qundef, bdat->s.nth); + return new; + } + } + else { + VALUE new = d_lite_s_alloc_complex(rb_obj_class(self)); + { + get_d1b(new); + bdat->c = adat->c; + RB_OBJ_WRITTEN(new, Qundef, bdat->c.nth); + RB_OBJ_WRITTEN(new, Qundef, bdat->c.sf); + return new; + } + } +} + +static VALUE +dup_obj_as_complex(VALUE self) +{ + get_d1a(self); + + if (simple_dat_p(adat)) { + VALUE new = d_lite_s_alloc_complex(rb_obj_class(self)); + { + get_d1b(new); + copy_simple_to_complex(new, &bdat->c, &adat->s); + bdat->c.flags |= HAVE_DF | COMPLEX_DAT; + return new; + } + } + else { + VALUE new = d_lite_s_alloc_complex(rb_obj_class(self)); + { + get_d1b(new); + bdat->c = adat->c; + RB_OBJ_WRITTEN(new, Qundef, bdat->c.nth); + RB_OBJ_WRITTEN(new, Qundef, bdat->c.sf); + return new; + } + } +} + +#define val2off(vof,iof) \ +{\ + if (!offset_to_sec(vof, &iof)) {\ + iof = 0;\ + rb_warning("invalid offset is ignored");\ + }\ +} + +#ifndef NDEBUG +static VALUE +d_lite_initialize(int argc, VALUE *argv, VALUE self) +{ + VALUE jd, vjd, vdf, sf, vsf, vof, vsg; + int df, of; + double sg; + + rb_check_frozen(self); + rb_check_trusted(self); + + rb_scan_args(argc, argv, "05", &vjd, &vdf, &vsf, &vof, &vsg); + + jd = INT2FIX(0); + df = 0; + sf = INT2FIX(0); + of = 0; + sg = DEFAULT_SG; + + switch (argc) { + case 5: + val2sg(vsg, sg); + case 4: + val2off(vof, of); + case 3: + sf = vsf; + if (f_lt_p(sf, INT2FIX(0)) || + f_ge_p(sf, INT2FIX(SECOND_IN_NANOSECONDS))) + rb_raise(rb_eArgError, "invalid second fraction"); + case 2: + df = NUM2INT(vdf); + if (df < 0 || df >= DAY_IN_SECONDS) + rb_raise(rb_eArgError, "invalid day fraction"); + case 1: + jd = vjd; + } + + { + VALUE nth; + int rjd; + + get_d1(self); + + decode_jd(jd, &nth, &rjd); + if (!df && f_zero_p(sf) && !of) { + set_to_simple(self, &dat->s, nth, rjd, sg, 0, 0, 0, HAVE_JD); + } + else { + if (!complex_dat_p(dat)) + rb_raise(rb_eArgError, + "cannot load complex into simple"); + + set_to_complex(&dat->c, nth, rjd, df, sf, of, sg, + 0, 0, 0, 0, 0, 0, HAVE_JD | HAVE_DF | COMPLEX_DAT); + } + } + return self; +} +#endif + +/* :nodoc: */ +static VALUE +d_lite_initialize_copy(VALUE copy, VALUE date) +{ + rb_check_frozen(copy); + rb_check_trusted(copy); + + if (copy == date) + return copy; + { + get_d2(copy, date); + if (simple_dat_p(bdat)) { + adat->s = bdat->s; + adat->s.flags &= ~COMPLEX_DAT; + } + else { + if (!complex_dat_p(adat)) + rb_raise(rb_eArgError, + "cannot load complex into simple"); + + adat->c = bdat->c; + adat->c.flags |= COMPLEX_DAT; + } + } + return copy; +} + +#ifndef NDEBUG +static VALUE +d_lite_fill(VALUE self) +{ + get_d1(self); + + if (simple_dat_p(dat)) { + get_s_jd(dat); + get_s_civil(dat); + } + else { + get_c_jd(dat); + get_c_civil(dat); + get_c_df(dat); + get_c_time(dat); + } + return self; +} +#endif + +/* + * call-seq: + * d.ajd -> rational + * + * Returns the astronomical Julian day number. This is a fractional + * number, which is not adjusted by the offset. + * + * DateTime.new(2001,2,3,4,5,6,'+7').ajd #=> (11769328217/4800) + * DateTime.new(2001,2,2,14,5,6,'-7').ajd #=> (11769328217/4800) + */ +static VALUE +d_lite_ajd(VALUE self) +{ + get_d1(self); + return m_ajd(dat); +} + +/* + * call-seq: + * d.amjd -> rational + * + * Returns the astronomical modified Julian day number. This is + * a fractional number, which is not adjusted by the offset. + * + * DateTime.new(2001,2,3,4,5,6,'+7').amjd #=> (249325817/4800) + * DateTime.new(2001,2,2,14,5,6,'-7').amjd #=> (249325817/4800) + */ +static VALUE +d_lite_amjd(VALUE self) +{ + get_d1(self); + return m_amjd(dat); +} + +/* + * call-seq: + * d.jd -> integer + * + * Returns the Julian day number. This is a whole number, which is + * adjusted by the offset as the local time. + * + * DateTime.new(2001,2,3,4,5,6,'+7').jd #=> 2451944 + * DateTime.new(2001,2,3,4,5,6,'-7').jd #=> 2451944 + */ +static VALUE +d_lite_jd(VALUE self) +{ + get_d1(self); + return m_real_local_jd(dat); +} + +/* + * call-seq: + * d.mjd -> integer + * + * Returns the modified Julian day number. This is a whole number, + * which is adjusted by the offset as the local time. + * + * DateTime.new(2001,2,3,4,5,6,'+7').mjd #=> 51943 + * DateTime.new(2001,2,3,4,5,6,'-7').mjd #=> 51943 + */ +static VALUE +d_lite_mjd(VALUE self) +{ + get_d1(self); + return f_sub(m_real_local_jd(dat), INT2FIX(2400001)); +} + +/* + * call-seq: + * d.ld -> integer + * + * Returns the Lilian day number. This is a whole number, which is + * adjusted by the offset as the local time. + * + * Date.new(2001,2,3).ld #=> 152784 + */ +static VALUE +d_lite_ld(VALUE self) +{ + get_d1(self); + return f_sub(m_real_local_jd(dat), INT2FIX(2299160)); +} + +/* + * call-seq: + * d.year -> integer + * + * Returns the year. + * + * Date.new(2001,2,3).year #=> 2001 + * (Date.new(1,1,1) - 1).year #=> 0 + */ +static VALUE +d_lite_year(VALUE self) +{ + get_d1(self); + return m_real_year(dat); +} + +/* + * call-seq: + * d.yday -> fixnum + * + * Returns the day of the year (1-366). + * + * Date.new(2001,2,3).yday #=> 34 + */ +static VALUE +d_lite_yday(VALUE self) +{ + get_d1(self); + return INT2FIX(m_yday(dat)); +} + +/* + * call-seq: + * d.mon -> fixnum + * d.month -> fixnum + * + * Returns the month (1-12). + * + * Date.new(2001,2,3).mon #=> 2 + */ +static VALUE +d_lite_mon(VALUE self) +{ + get_d1(self); + return INT2FIX(m_mon(dat)); +} + +/* + * call-seq: + * d.mday -> fixnum + * d.day -> fixnum + * + * Returns the day of the month (1-31). + * + * Date.new(2001,2,3).mday #=> 3 + */ +static VALUE +d_lite_mday(VALUE self) +{ + get_d1(self); + return INT2FIX(m_mday(dat)); +} + +/* + * call-seq: + * d.day_fraction -> rational + * + * Returns the fractional part of the day. + * + * DateTime.new(2001,2,3,12).day_fraction #=> (1/2) + */ +static VALUE +d_lite_day_fraction(VALUE self) +{ + get_d1(self); + if (simple_dat_p(dat)) + return INT2FIX(0); + return m_fr(dat); +} + +/* + * call-seq: + * d.cwyear -> integer + * + * Returns the calendar week based year. + * + * Date.new(2001,2,3).cwyear #=> 2001 + * Date.new(2000,1,1).cwyear #=> 1999 + */ +static VALUE +d_lite_cwyear(VALUE self) +{ + get_d1(self); + return m_real_cwyear(dat); +} + +/* + * call-seq: + * d.cweek -> fixnum + * + * Returns the calendar week number (1-53). + * + * Date.new(2001,2,3).cweek #=> 5 + */ +static VALUE +d_lite_cweek(VALUE self) +{ + get_d1(self); + return INT2FIX(m_cweek(dat)); +} + +/* + * call-seq: + * d.cwday -> fixnum + * + * Returns the day of calendar week (1-7, Monday is 1). + * + * Date.new(2001,2,3).cwday #=> 6 + */ +static VALUE +d_lite_cwday(VALUE self) +{ + get_d1(self); + return INT2FIX(m_cwday(dat)); +} + +#ifndef NDEBUG +static VALUE +d_lite_wnum0(VALUE self) +{ + get_d1(self); + return INT2FIX(m_wnum0(dat)); +} + +static VALUE +d_lite_wnum1(VALUE self) +{ + get_d1(self); + return INT2FIX(m_wnum1(dat)); +} +#endif + +/* + * call-seq: + * d.wday -> fixnum + * + * Returns the day of week (0-6, Sunday is zero). + * + * Date.new(2001,2,3).wday #=> 6 + */ +static VALUE +d_lite_wday(VALUE self) +{ + get_d1(self); + return INT2FIX(m_wday(dat)); +} + +/* + * call-seq: + * d.sunday? -> bool + * + * Returns true if the date is Sunday. + */ +static VALUE +d_lite_sunday_p(VALUE self) +{ + get_d1(self); + return f_boolcast(m_wday(dat) == 0); +} + +/* + * call-seq: + * d.monday? -> bool + * + * Returns true if the date is Monday. + */ +static VALUE +d_lite_monday_p(VALUE self) +{ + get_d1(self); + return f_boolcast(m_wday(dat) == 1); +} + +/* + * call-seq: + * d.tuesday? -> bool + * + * Returns true if the date is Tuesday. + */ +static VALUE +d_lite_tuesday_p(VALUE self) +{ + get_d1(self); + return f_boolcast(m_wday(dat) == 2); +} + +/* + * call-seq: + * d.wednesday? -> bool + * + * Returns true if the date is Wednesday. + */ +static VALUE +d_lite_wednesday_p(VALUE self) +{ + get_d1(self); + return f_boolcast(m_wday(dat) == 3); +} + +/* + * call-seq: + * d.thursday? -> bool + * + * Returns true if the date is Thursday. + */ +static VALUE +d_lite_thursday_p(VALUE self) +{ + get_d1(self); + return f_boolcast(m_wday(dat) == 4); +} + +/* + * call-seq: + * d.friday? -> bool + * + * Returns true if the date is Friday. + */ +static VALUE +d_lite_friday_p(VALUE self) +{ + get_d1(self); + return f_boolcast(m_wday(dat) == 5); +} + +/* + * call-seq: + * d.saturday? -> bool + * + * Returns true if the date is Saturday. + */ +static VALUE +d_lite_saturday_p(VALUE self) +{ + get_d1(self); + return f_boolcast(m_wday(dat) == 6); +} + +#ifndef NDEBUG +static VALUE +d_lite_nth_kday_p(VALUE self, VALUE n, VALUE k) +{ + int rjd, ns; + + get_d1(self); + + if (NUM2INT(k) != m_wday(dat)) + return Qfalse; + + c_nth_kday_to_jd(m_year(dat), m_mon(dat), + NUM2INT(n), NUM2INT(k), m_virtual_sg(dat), /* !=m_sg() */ + &rjd, &ns); + if (m_local_jd(dat) != rjd) + return Qfalse; + return Qtrue; +} +#endif + +/* + * call-seq: + * d.hour -> fixnum + * + * Returns the hour (0-23). + * + * DateTime.new(2001,2,3,4,5,6).hour #=> 4 + */ +static VALUE +d_lite_hour(VALUE self) +{ + get_d1(self); + return INT2FIX(m_hour(dat)); +} + +/* + * call-seq: + * d.min -> fixnum + * d.minute -> fixnum + * + * Returns the minute (0-59). + * + * DateTime.new(2001,2,3,4,5,6).min #=> 5 + */ +static VALUE +d_lite_min(VALUE self) +{ + get_d1(self); + return INT2FIX(m_min(dat)); +} + +/* + * call-seq: + * d.sec -> fixnum + * d.second -> fixnum + * + * Returns the second (0-59). + * + * DateTime.new(2001,2,3,4,5,6).sec #=> 6 + */ +static VALUE +d_lite_sec(VALUE self) +{ + get_d1(self); + return INT2FIX(m_sec(dat)); +} + +/* + * call-seq: + * d.sec_fraction -> rational + * d.second_fraction -> rational + * + * Returns the fractional part of the second. + * + * DateTime.new(2001,2,3,4,5,6.5).sec_fraction #=> (1/2) + */ +static VALUE +d_lite_sec_fraction(VALUE self) +{ + get_d1(self); + return m_sf_in_sec(dat); +} + +/* + * call-seq: + * d.offset -> rational + * + * Returns the offset. + * + * DateTime.parse('04pm+0730').offset #=> (5/16) + */ +static VALUE +d_lite_offset(VALUE self) +{ + get_d1(self); + return m_of_in_day(dat); +} + +/* + * call-seq: + * d.zone -> string + * + * Returns the timezone. + * + * DateTime.parse('04pm+0730').zone #=> "+07:30" + */ +static VALUE +d_lite_zone(VALUE self) +{ + get_d1(self); + return m_zone(dat); +} + +/* + * call-seq: + * d.julian? -> bool + * + * Retruns true if the date is before the day of calendar reform. + * + * Date.new(1582,10,15).julian? #=> false + * (Date.new(1582,10,15) - 1).julian? #=> true + */ +static VALUE +d_lite_julian_p(VALUE self) +{ + get_d1(self); + return f_boolcast(m_julian_p(dat)); +} + +/* + * call-seq: + * d.gregorian? -> bool + * + * Retunrs true if the date is on or after the day of calendar reform. + * + * Date.new(1582,10,15).gregorian? #=> true + * (Date.new(1582,10,15) - 1).gregorian? #=> false + */ +static VALUE +d_lite_gregorian_p(VALUE self) +{ + get_d1(self); + return f_boolcast(m_gregorian_p(dat)); +} + +/* + * call-seq: + * d.leap? -> bool + * + * Returns true if the year is a leap year. + * + * Date.new(2000).leap? #=> true + * Date.new(2001).leap? #=> false + */ +static VALUE +d_lite_leap_p(VALUE self) +{ + int rjd, ns, ry, rm, rd; + + get_d1(self); + if (m_gregorian_p(dat)) + return f_boolcast(c_gregorian_leap_p(m_year(dat))); + + c_civil_to_jd(m_year(dat), 3, 1, m_virtual_sg(dat), + &rjd, &ns); + c_jd_to_civil(rjd - 1, m_virtual_sg(dat), &ry, &rm, &rd); + return f_boolcast(rd == 29); +} + +/* + * call-seq: + * d.start -> float + * + * Returns the Julian day number denoting the day of calendar reform. + * + * Date.new(2001,2,3).start #=> 2299161.0 + * Date.new(2001,2,3,Date::GREGORIAN).start #=> -Infinity + */ +static VALUE +d_lite_start(VALUE self) +{ + get_d1(self); + return DBL2NUM(m_sg(dat)); +} + +static void +clear_civil(union DateData *x) +{ + if (simple_dat_p(x)) { + x->s.year = 0; +#ifndef USE_PACK + x->s.mon = 0; + x->s.mday = 0; +#else + x->s.pc = 0; +#endif + x->s.flags &= ~HAVE_CIVIL; + } + else { + x->c.year = 0; +#ifndef USE_PACK + x->c.mon = 0; + x->c.mday = 0; + x->c.hour = 0; + x->c.min = 0; + x->c.sec = 0; +#else + x->c.pc = 0; +#endif + x->c.flags &= ~(HAVE_CIVIL | HAVE_TIME); + } +} + +static void +set_sg(union DateData *x, double sg) +{ + if (simple_dat_p(x)) { + get_s_jd(x); + clear_civil(x); + x->s.sg = (date_sg_t)sg; + } else { + get_c_jd(x); + get_c_df(x); + clear_civil(x); + x->c.sg = (date_sg_t)sg; + } +} + +static VALUE +dup_obj_with_new_start(VALUE obj, double sg) +{ + volatile VALUE dup = dup_obj(obj); + { + get_d1(dup); + set_sg(dat, sg); + } + return dup; +} + +/* + * call-seq: + * d.new_start([start=Date::ITALY]) -> date + * + * Duplicates self and resets its the day of calendar reform. + * + * d = Date.new(1582,10,15) + * d.new_start(Date::JULIAN) #=> # + */ +static VALUE +d_lite_new_start(int argc, VALUE *argv, VALUE self) +{ + VALUE vsg; + double sg; + + rb_scan_args(argc, argv, "01", &vsg); + + sg = DEFAULT_SG; + if (argc >= 1) + val2sg(vsg, sg); + + return dup_obj_with_new_start(self, sg); +} + +/* + * call-seq: + * d.italy -> date + * + * This method is equivalent to new_start(Date::ITALY). + */ +static VALUE +d_lite_italy(VALUE self) +{ + return dup_obj_with_new_start(self, ITALY); +} + +/* + * call-seq: + * d.england -> date + * + * This method is equivalent to new_start(Date::ENGLAND). + */ +static VALUE +d_lite_england(VALUE self) +{ + return dup_obj_with_new_start(self, ENGLAND); +} + +/* + * call-seq: + * d.julian -> date + * + * This method is equivalent to new_start(Date::JULIAN). + */ +static VALUE +d_lite_julian(VALUE self) +{ + return dup_obj_with_new_start(self, JULIAN); +} + +/* + * call-seq: + * d.gregorian -> date + * + * This method is equivalent to new_start(Date::GREGORIAN). + */ +static VALUE +d_lite_gregorian(VALUE self) +{ + return dup_obj_with_new_start(self, GREGORIAN); +} + +static void +set_of(union DateData *x, int of) +{ + assert(complex_dat_p(x)); + get_c_jd(x); + get_c_df(x); + clear_civil(x); + x->c.of = of; +} + +static VALUE +dup_obj_with_new_offset(VALUE obj, int of) +{ + volatile VALUE dup = dup_obj_as_complex(obj); + { + get_d1(dup); + set_of(dat, of); + } + return dup; +} + +/* + * call-seq: + * d.new_offset([offset=0]) -> date + * + * Duplicates self and resets its offset. + * + * d = DateTime.new(2001,2,3,4,5,6,'-02:00') + * #=> # + * d.new_offset('+09:00') #=> # + */ +static VALUE +d_lite_new_offset(int argc, VALUE *argv, VALUE self) +{ + VALUE vof; + int rof; + + rb_scan_args(argc, argv, "01", &vof); + + rof = 0; + if (argc >= 1) + val2off(vof, rof); + + return dup_obj_with_new_offset(self, rof); +} + +/* + * call-seq: + * d + other -> date + * + * Returns a date object pointing other days after self. The other + * should be a numeric value. If the other is flonum, assumes its + * precision is at most nanosecond. + * + * Date.new(2001,2,3) + 1 #=> # + * DateTime.new(2001,2,3) + Rational(1,2) + * #=> # + * DateTime.new(2001,2,3) + Rational(-1,2) + * #=> # + * DateTime.jd(0,12) + DateTime.new(2001,2,3).ajd + * #=> # + */ +static VALUE +d_lite_plus(VALUE self, VALUE other) +{ + get_d1(self); + + switch (TYPE(other)) { + case T_FIXNUM: + { + VALUE nth; + long t; + int jd; + + nth = m_nth(dat); + t = FIX2LONG(other); + if (DIV(t, CM_PERIOD)) { + nth = f_add(nth, INT2FIX(DIV(t, CM_PERIOD))); + t = MOD(t, CM_PERIOD); + } + + if (!t) + jd = m_jd(dat); + else { + jd = m_jd(dat) + (int)t; + canonicalize_jd(nth, jd); + } + + if (simple_dat_p(dat)) + return d_simple_new_internal(rb_obj_class(self), + nth, jd, + dat->s.sg, + 0, 0, 0, + (dat->s.flags | HAVE_JD) & + ~HAVE_CIVIL); + else + return d_complex_new_internal(rb_obj_class(self), + nth, jd, + dat->c.df, dat->c.sf, + dat->c.of, dat->c.sg, + 0, 0, 0, +#ifndef USE_PACK + dat->c.hour, + dat->c.min, + dat->c.sec, +#else + EX_HOUR(dat->c.pc), + EX_MIN(dat->c.pc), + EX_SEC(dat->c.pc), +#endif + (dat->c.flags | HAVE_JD) & + ~HAVE_CIVIL); + } + break; + case T_BIGNUM: + { + VALUE nth; + int jd, s; + + if (f_positive_p(other)) + s = +1; + else { + s = -1; + other = f_negate(other); + } + + nth = f_idiv(other, INT2FIX(CM_PERIOD)); + jd = FIX2INT(f_mod(other, INT2FIX(CM_PERIOD))); + + if (s < 0) { + nth = f_negate(nth); + jd = -jd; + } + + if (!jd) + jd = m_jd(dat); + else { + jd = m_jd(dat) + jd; + canonicalize_jd(nth, jd); + } + + if (f_zero_p(nth)) + nth = m_nth(dat); + else + nth = f_add(m_nth(dat), nth); + + if (simple_dat_p(dat)) + return d_simple_new_internal(rb_obj_class(self), + nth, jd, + dat->s.sg, + 0, 0, 0, + (dat->s.flags | HAVE_JD) & + ~HAVE_CIVIL); + else + return d_complex_new_internal(rb_obj_class(self), + nth, jd, + dat->c.df, dat->c.sf, + dat->c.of, dat->c.sg, + 0, 0, 0, +#ifndef USE_PACK + dat->c.hour, + dat->c.min, + dat->c.sec, +#else + EX_HOUR(dat->c.pc), + EX_MIN(dat->c.pc), + EX_SEC(dat->c.pc), +#endif + (dat->c.flags | HAVE_JD) & + ~HAVE_CIVIL); + } + break; + case T_FLOAT: + { + double jd, o, tmp; + int s, df; + VALUE nth, sf; + + o = RFLOAT_VALUE(other); + + if (o > 0) + s = +1; + else { + s = -1; + o = -o; + } + + o = modf(o, &tmp); + + if (!floor(tmp / CM_PERIOD)) { + nth = INT2FIX(0); + jd = (int)tmp; + } + else { + double i, f; + + f = modf(tmp / CM_PERIOD, &i); + nth = f_floor(DBL2NUM(i)); + jd = (int)(f * CM_PERIOD); + } + + o *= DAY_IN_SECONDS; + o = modf(o, &tmp); + df = (int)tmp; + o *= SECOND_IN_NANOSECONDS; + sf = INT2FIX((int)round(o)); + + if (s < 0) { + jd = -jd; + df = -df; + sf = f_negate(sf); + } + + if (f_zero_p(sf)) + sf = m_sf(dat); + else { + sf = f_add(m_sf(dat), sf); + if (f_lt_p(sf, INT2FIX(0))) { + df -= 1; + sf = f_add(sf, INT2FIX(SECOND_IN_NANOSECONDS)); + } + else if (f_ge_p(sf, INT2FIX(SECOND_IN_NANOSECONDS))) { + df += 1; + sf = f_sub(sf, INT2FIX(SECOND_IN_NANOSECONDS)); + } + } + + if (!df) + df = m_df(dat); + else { + df = m_df(dat) + df; + if (df < 0) { + jd -= 1; + df += DAY_IN_SECONDS; + } + else if (df >= DAY_IN_SECONDS) { + jd += 1; + df -= DAY_IN_SECONDS; + } + } + + if (!jd) + jd = m_jd(dat); + else { + jd = m_jd(dat) + jd; + canonicalize_jd(nth, jd); + } + + if (f_zero_p(nth)) + nth = m_nth(dat); + else + nth = f_add(m_nth(dat), nth); + + if (!df && f_zero_p(sf) && !m_of(dat)) + return d_simple_new_internal(rb_obj_class(self), + nth, (int)jd, + m_sg(dat), + 0, 0, 0, + (dat->s.flags | HAVE_JD) & + ~(HAVE_CIVIL | HAVE_TIME | + COMPLEX_DAT)); + else + return d_complex_new_internal(rb_obj_class(self), + nth, (int)jd, + df, sf, + m_of(dat), m_sg(dat), + 0, 0, 0, + 0, 0, 0, + (dat->c.flags | + HAVE_JD | HAVE_DF) & + ~(HAVE_CIVIL | HAVE_TIME)); + } + break; + default: + if (!k_numeric_p(other)) + rb_raise(rb_eTypeError, "expected numeric"); + other = f_to_r(other); +#ifdef CANONICALIZATION_FOR_MATHN + if (!k_rational_p(other)) + return d_lite_plus(self, other); +#endif + /* fall through */ + case T_RATIONAL: + { + VALUE nth, sf, t; + int jd, df, s; + + if (wholenum_p(other)) + return d_lite_plus(self, rb_rational_num(other)); + + if (f_positive_p(other)) + s = +1; + else { + s = -1; + other = f_negate(other); + } + + nth = f_idiv(other, INT2FIX(CM_PERIOD)); + t = f_mod(other, INT2FIX(CM_PERIOD)); + + jd = FIX2INT(f_idiv(t, INT2FIX(1))); + t = f_mod(t, INT2FIX(1)); + + t = f_mul(t, INT2FIX(DAY_IN_SECONDS)); + df = FIX2INT(f_idiv(t, INT2FIX(1))); + t = f_mod(t, INT2FIX(1)); + + sf = f_mul(t, INT2FIX(SECOND_IN_NANOSECONDS)); + + if (s < 0) { + nth = f_negate(nth); + jd = -jd; + df = -df; + sf = f_negate(sf); + } + + if (f_zero_p(sf)) + sf = m_sf(dat); + else { + sf = f_add(m_sf(dat), sf); + if (f_lt_p(sf, INT2FIX(0))) { + df -= 1; + sf = f_add(sf, INT2FIX(SECOND_IN_NANOSECONDS)); + } + else if (f_ge_p(sf, INT2FIX(SECOND_IN_NANOSECONDS))) { + df += 1; + sf = f_sub(sf, INT2FIX(SECOND_IN_NANOSECONDS)); + } + } + + if (!df) + df = m_df(dat); + else { + df = m_df(dat) + df; + if (df < 0) { + jd -= 1; + df += DAY_IN_SECONDS; + } + else if (df >= DAY_IN_SECONDS) { + jd += 1; + df -= DAY_IN_SECONDS; + } + } + + if (!jd) + jd = m_jd(dat); + else { + jd = m_jd(dat) + jd; + canonicalize_jd(nth, jd); + } + + if (f_zero_p(nth)) + nth = m_nth(dat); + else + nth = f_add(m_nth(dat), nth); + + if (!df && f_zero_p(sf) && !m_of(dat)) + return d_simple_new_internal(rb_obj_class(self), + nth, jd, + m_sg(dat), + 0, 0, 0, + (dat->s.flags | HAVE_JD) & + ~(HAVE_CIVIL | HAVE_TIME | + COMPLEX_DAT)); + else + return d_complex_new_internal(rb_obj_class(self), + nth, jd, + df, sf, + m_of(dat), m_sg(dat), + 0, 0, 0, + 0, 0, 0, + (dat->c.flags | + HAVE_JD | HAVE_DF) & + ~(HAVE_CIVIL | HAVE_TIME)); + } + break; + } +} + +static VALUE +minus_dd(VALUE self, VALUE other) +{ + get_d2(self, other); + + { + int d, df; + VALUE n, sf, r; + + n = f_sub(m_nth(adat), m_nth(bdat)); + d = m_jd(adat) - m_jd(bdat); + df = m_df(adat) - m_df(bdat); + sf = f_sub(m_sf(adat), m_sf(bdat)); + canonicalize_jd(n, d); + + if (df < 0) { + d -= 1; + df += DAY_IN_SECONDS; + } + else if (df >= DAY_IN_SECONDS) { + d += 1; + df -= DAY_IN_SECONDS; + } + + if (f_lt_p(sf, INT2FIX(0))) { + df -= 1; + sf = f_add(sf, INT2FIX(SECOND_IN_NANOSECONDS)); + } + else if (f_ge_p(sf, INT2FIX(SECOND_IN_NANOSECONDS))) { + df += 1; + sf = f_sub(sf, INT2FIX(SECOND_IN_NANOSECONDS)); + } + + if (f_zero_p(n)) + r = INT2FIX(0); + else + r = f_mul(n, INT2FIX(CM_PERIOD)); + + if (d) + r = f_add(r, rb_rational_new1(INT2FIX(d))); + if (df) + r = f_add(r, isec_to_day(df)); + if (f_nonzero_p(sf)) + r = f_add(r, ns_to_day(sf)); + + if (RB_TYPE_P(r, T_RATIONAL)) + return r; + return rb_rational_new1(r); + } +} + +/* + * call-seq: + * d - other -> date or rational + * + * Returns the difference between the two dates if the other is a date + * object. If the other is a numeric value, returns a date object + * pointing other days before self. If the other is flonum, assumes + * its precision is at most nanosecond. + * + * Date.new(2001,2,3) - 1 #=> # + * DateTime.new(2001,2,3) - Rational(1,2) + * #=> # + * Date.new(2001,2,3) - Date.new(2001) + * #=> (33/1) + * DateTime.new(2001,2,3) - DateTime.new(2001,2,2,12) + * #=> (1/2) + */ +static VALUE +d_lite_minus(VALUE self, VALUE other) +{ + if (k_date_p(other)) + return minus_dd(self, other); + + switch (TYPE(other)) { + case T_FIXNUM: + return d_lite_plus(self, LONG2NUM(-FIX2LONG(other))); + case T_FLOAT: + return d_lite_plus(self, DBL2NUM(-RFLOAT_VALUE(other))); + default: + if (!k_numeric_p(other)) + rb_raise(rb_eTypeError, "expected numeric"); + /* fall through */ + case T_BIGNUM: + case T_RATIONAL: + return d_lite_plus(self, f_negate(other)); + } +} + +/* + * call-seq: + * d.next_day([n=1]) -> date + * + * This method is equivalent to d + n. + */ +static VALUE +d_lite_next_day(int argc, VALUE *argv, VALUE self) +{ + VALUE n; + + rb_scan_args(argc, argv, "01", &n); + if (argc < 1) + n = INT2FIX(1); + return d_lite_plus(self, n); +} + +/* + * call-seq: + * d.prev_day([n=1]) -> date + * + * This method is equivalent to d - n. + */ +static VALUE +d_lite_prev_day(int argc, VALUE *argv, VALUE self) +{ + VALUE n; + + rb_scan_args(argc, argv, "01", &n); + if (argc < 1) + n = INT2FIX(1); + return d_lite_minus(self, n); +} + +/* + * call-seq: + * d.succ -> date + * d.next -> date + * + * Returns a date object denoting the following day. + */ +static VALUE +d_lite_next(VALUE self) +{ + return d_lite_next_day(0, (VALUE *)NULL, self); +} + +/* + * call-seq: + * d >> n -> date + * + * Returns a date object pointing n months after self. The n should + * be a numeric value. + * + * Date.new(2001,2,3) >> 1 #=> # + * Date.new(2001,1,31) >> 1 #=> # + * Date.new(2001,2,3) >> -2 #=> # + */ +static VALUE +d_lite_rshift(VALUE self, VALUE other) +{ + VALUE t, y, nth, rjd2; + int m, d, rjd; + double sg; + + get_d1(self); + t = f_add3(f_mul(m_real_year(dat), INT2FIX(12)), + INT2FIX(m_mon(dat) - 1), + other); + if (FIXNUM_P(t)) { + long it = FIX2LONG(t); + y = LONG2NUM(DIV(it, 12)); + it = MOD(it, 12); + m = (int)it + 1; + } + else { + y = f_idiv(t, INT2FIX(12)); + t = f_mod(t, INT2FIX(12)); + m = FIX2INT(t) + 1; + } + d = m_mday(dat); + sg = m_sg(dat); + + while (1) { + int ry, rm, rd, ns; + + if (valid_civil_p(y, m, d, sg, + &nth, &ry, + &rm, &rd, &rjd, &ns)) + break; + if (--d < 1) + rb_raise(rb_eArgError, "invalid date"); + } + encode_jd(nth, rjd, &rjd2); + return d_lite_plus(self, f_sub(rjd2, m_real_local_jd(dat))); +} + +/* + * call-seq: + * d << n -> date + * + * Returns a date object pointing n months before self. The n should + * be a numeric value. + * + * Date.new(2001,2,3) << 1 #=> # + * Date.new(2001,1,31) << 11 #=> # + * Date.new(2001,2,3) << -1 #=> # + */ +static VALUE +d_lite_lshift(VALUE self, VALUE other) +{ + return d_lite_rshift(self, f_negate(other)); +} + +/* + * call-seq: + * d.next_month([n=1]) -> date + * + * This method is equivalent to d >> n + */ +static VALUE +d_lite_next_month(int argc, VALUE *argv, VALUE self) +{ + VALUE n; + + rb_scan_args(argc, argv, "01", &n); + if (argc < 1) + n = INT2FIX(1); + return d_lite_rshift(self, n); +} + +/* + * call-seq: + * d.prev_month([n=1]) -> date + * + * This method is equivalent to d << n + */ +static VALUE +d_lite_prev_month(int argc, VALUE *argv, VALUE self) +{ + VALUE n; + + rb_scan_args(argc, argv, "01", &n); + if (argc < 1) + n = INT2FIX(1); + return d_lite_lshift(self, n); +} + +/* + * call-seq: + * d.next_year([n=1]) -> date + * + * This method is equivalent to d >> (n * 12) + */ +static VALUE +d_lite_next_year(int argc, VALUE *argv, VALUE self) +{ + VALUE n; + + rb_scan_args(argc, argv, "01", &n); + if (argc < 1) + n = INT2FIX(1); + return d_lite_rshift(self, f_mul(n, INT2FIX(12))); +} + +/* + * call-seq: + * d.prev_year([n=1]) -> date + * + * This method is equivalent to d << (n * 12) + */ +static VALUE +d_lite_prev_year(int argc, VALUE *argv, VALUE self) +{ + VALUE n; + + rb_scan_args(argc, argv, "01", &n); + if (argc < 1) + n = INT2FIX(1); + return d_lite_lshift(self, f_mul(n, INT2FIX(12))); +} + +static VALUE d_lite_cmp(VALUE, VALUE); + +/* + * call-seq: + * d.step(limit[, step=1]) -> enumerator + * d.step(limit[, step=1]){|date| ...} -> self + * + * Iterates evaluation of the given block, which takes a date object. + * The limit should be a date object. + * + * Date.new(2001).step(Date.new(2001,-1,-1)).select{|d| d.sunday?}.size + * #=> 52 + */ +static VALUE +d_lite_step(int argc, VALUE *argv, VALUE self) +{ + VALUE limit, step, date; + + rb_scan_args(argc, argv, "11", &limit, &step); + + if (argc < 2) + step = INT2FIX(1); + +#if 0 + if (f_zero_p(step)) + rb_raise(rb_eArgError, "step can't be 0"); +#endif + + RETURN_ENUMERATOR(self, argc, argv); + + date = self; + switch (FIX2INT(f_cmp(step, INT2FIX(0)))) { + case -1: + while (FIX2INT(d_lite_cmp(date, limit)) >= 0) { + rb_yield(date); + date = d_lite_plus(date, step); + } + break; + case 0: + while (1) + rb_yield(date); + break; + case 1: + while (FIX2INT(d_lite_cmp(date, limit)) <= 0) { + rb_yield(date); + date = d_lite_plus(date, step); + } + break; + default: + abort(); + } + return self; +} + +/* + * call-seq: + * d.upto(max) -> enumerator + * d.upto(max){|date| ...} -> self + * + * This method is equivalent to step(max, 1){|date| ...}. + */ +static VALUE +d_lite_upto(VALUE self, VALUE max) +{ + VALUE date; + + RETURN_ENUMERATOR(self, 1, &max); + + date = self; + while (FIX2INT(d_lite_cmp(date, max)) <= 0) { + rb_yield(date); + date = d_lite_plus(date, INT2FIX(1)); + } + return self; +} + +/* + * call-seq: + * d.downto(min) -> enumerator + * d.downto(min){|date| ...} -> self + * + * This method is equivalent to step(min, -1){|date| ...}. + */ +static VALUE +d_lite_downto(VALUE self, VALUE min) +{ + VALUE date; + + RETURN_ENUMERATOR(self, 1, &min); + + date = self; + while (FIX2INT(d_lite_cmp(date, min)) >= 0) { + rb_yield(date); + date = d_lite_plus(date, INT2FIX(-1)); + } + return self; +} + +static VALUE +cmp_gen(VALUE self, VALUE other) +{ + get_d1(self); + + if (k_numeric_p(other)) + return f_cmp(m_ajd(dat), other); + else if (k_date_p(other)) + return f_cmp(m_ajd(dat), f_ajd(other)); + return rb_num_coerce_cmp(self, other, rb_intern("<=>")); +} + +static VALUE +cmp_dd(VALUE self, VALUE other) +{ + get_d2(self, other); + + { + VALUE a_nth, b_nth, + a_sf, b_sf; + int a_jd, b_jd, + a_df, b_df; + + m_canonicalize_jd(self, adat); + m_canonicalize_jd(other, bdat); + a_nth = m_nth(adat); + b_nth = m_nth(bdat); + if (f_eqeq_p(a_nth, b_nth)) { + a_jd = m_jd(adat); + b_jd = m_jd(bdat); + if (a_jd == b_jd) { + a_df = m_df(adat); + b_df = m_df(bdat); + if (a_df == b_df) { + a_sf = m_sf(adat); + b_sf = m_sf(bdat); + if (f_eqeq_p(a_sf, b_sf)) { + return INT2FIX(0); + } + else if (f_lt_p(a_sf, b_sf)) { + return INT2FIX(-1); + } + else { + return INT2FIX(1); + } + } + else if (a_df < b_df) { + return INT2FIX(-1); + } + else { + return INT2FIX(1); + } + } + else if (a_jd < b_jd) { + return INT2FIX(-1); + } + else { + return INT2FIX(1); + } + } + else if (f_lt_p(a_nth, b_nth)) { + return INT2FIX(-1); + } + else { + return INT2FIX(1); + } + } +} + +/* + * call-seq: + * d <=> other -> -1, 0, +1 or nil + * + * Compares the two dates and returns -1, zero, 1 or nil. The other + * should be a date object or a numeric value as an astronomical + * Julian day number. + * + * Date.new(2001,2,3) <=> Date.new(2001,2,4) #=> -1 + * Date.new(2001,2,3) <=> Date.new(2001,2,3) #=> 0 + * Date.new(2001,2,3) <=> Date.new(2001,2,2) #=> 1 + * Date.new(2001,2,3) <=> Object.new #=> nil + * Date.new(2001,2,3) <=> Rational(4903887,2)#=> 0 + * + * See also Comparable. + */ +static VALUE +d_lite_cmp(VALUE self, VALUE other) +{ + if (!k_date_p(other)) + return cmp_gen(self, other); + + { + get_d2(self, other); + + if (!(simple_dat_p(adat) && simple_dat_p(bdat) && + m_gregorian_p(adat) == m_gregorian_p(bdat))) + return cmp_dd(self, other); + + { + VALUE a_nth, b_nth; + int a_jd, b_jd; + + m_canonicalize_jd(self, adat); + m_canonicalize_jd(other, bdat); + a_nth = m_nth(adat); + b_nth = m_nth(bdat); + if (f_eqeq_p(a_nth, b_nth)) { + a_jd = m_jd(adat); + b_jd = m_jd(bdat); + if (a_jd == b_jd) { + return INT2FIX(0); + } + else if (a_jd < b_jd) { + return INT2FIX(-1); + } + else { + return INT2FIX(1); + } + } + else if (f_lt_p(a_nth, b_nth)) { + return INT2FIX(-1); + } + else { + return INT2FIX(1); + } + } + } +} + +static VALUE +equal_gen(VALUE self, VALUE other) +{ + get_d1(self); + + if (k_numeric_p(other)) + return f_eqeq_p(m_real_local_jd(dat), other); + else if (k_date_p(other)) + return f_eqeq_p(m_real_local_jd(dat), f_jd(other)); + return rb_num_coerce_cmp(self, other, rb_intern("==")); +} + +/* + * call-seq: + * d === other -> bool + * + * Returns true if they are the same day. + * + * Date.new(2001,2,3) === Date.new(2001,2,3) + * #=> true + * Date.new(2001,2,3) === Date.new(2001,2,4) + * #=> false + * DateTime.new(2001,2,3) === DateTime.new(2001,2,3,12) + * #=> true + * DateTime.new(2001,2,3) === DateTime.new(2001,2,3,0,0,0,'+24:00') + * #=> true + * DateTime.new(2001,2,3) === DateTime.new(2001,2,4,0,0,0,'+24:00') + * #=> false + */ +static VALUE +d_lite_equal(VALUE self, VALUE other) +{ + if (!k_date_p(other)) + return equal_gen(self, other); + + { + get_d2(self, other); + + if (!(m_gregorian_p(adat) == m_gregorian_p(bdat))) + return equal_gen(self, other); + + { + VALUE a_nth, b_nth; + int a_jd, b_jd; + + m_canonicalize_jd(self, adat); + m_canonicalize_jd(other, bdat); + a_nth = m_nth(adat); + b_nth = m_nth(bdat); + a_jd = m_local_jd(adat); + b_jd = m_local_jd(bdat); + if (f_eqeq_p(a_nth, b_nth) && + a_jd == b_jd) + return Qtrue; + return Qfalse; + } + } +} + +/* :nodoc: */ +static VALUE +d_lite_eql_p(VALUE self, VALUE other) +{ + if (!k_date_p(other)) + return Qfalse; + return f_zero_p(d_lite_cmp(self, other)); +} + +/* :nodoc: */ +static VALUE +d_lite_hash(VALUE self) +{ + st_index_t v, h[4]; + + get_d1(self); + h[0] = m_nth(dat); + h[1] = m_jd(dat); + h[2] = m_df(dat); + h[3] = m_sf(dat); + v = rb_memhash(h, sizeof(h)); + return LONG2FIX(v); +} + +#include "date_tmx.h" +static void set_tmx(VALUE, struct tmx *); +static VALUE strftimev(const char *, VALUE, + void (*)(VALUE, struct tmx *)); + +/* + * call-seq: + * d.to_s -> string + * + * Returns a string in an ISO 8601 format (This method doesn't use the + * expanded representations). + * + * Date.new(2001,2,3).to_s #=> "2001-02-03" + */ +static VALUE +d_lite_to_s(VALUE self) +{ + return strftimev("%Y-%m-%d", self, set_tmx); +} + +#ifndef NDEBUG +static VALUE +mk_inspect_flags(union DateData *x) +{ + return rb_enc_sprintf(rb_usascii_encoding(), + "%c%c%c%c%c", + (x->flags & COMPLEX_DAT) ? 'C' : 'S', + (x->flags & HAVE_JD) ? 'j' : '-', + (x->flags & HAVE_DF) ? 'd' : '-', + (x->flags & HAVE_CIVIL) ? 'c' : '-', + (x->flags & HAVE_TIME) ? 't' : '-'); +} + +static VALUE +mk_inspect_raw(union DateData *x, const char *klass) +{ + if (simple_dat_p(x)) { + VALUE nth, flags; + + RB_GC_GUARD(nth) = f_inspect(x->s.nth); + RB_GC_GUARD(flags) = mk_inspect_flags(x); + + return rb_enc_sprintf(rb_usascii_encoding(), + "#<%s: " + "(%sth,%dj),+0s,%.0fj; " + "%dy%dm%dd; %s>", + klass ? klass : "?", + RSTRING_PTR(nth), x->s.jd, x->s.sg, +#ifndef USE_PACK + x->s.year, x->s.mon, x->s.mday, +#else + x->s.year, + EX_MON(x->s.pc), EX_MDAY(x->s.pc), +#endif + RSTRING_PTR(flags)); + } + else { + VALUE nth, sf, flags; + + RB_GC_GUARD(nth) = f_inspect(x->c.nth); + RB_GC_GUARD(sf) = f_inspect(x->c.sf); + RB_GC_GUARD(flags) = mk_inspect_flags(x); + + return rb_enc_sprintf(rb_usascii_encoding(), + "#<%s: " + "(%sth,%dj,%ds,%sn),%+ds,%.0fj; " + "%dy%dm%dd %dh%dm%ds; %s>", + klass ? klass : "?", + RSTRING_PTR(nth), x->c.jd, x->c.df, + RSTRING_PTR(sf), + x->c.of, x->c.sg, +#ifndef USE_PACK + x->c.year, x->c.mon, x->c.mday, + x->c.hour, x->c.min, x->c.sec, +#else + x->c.year, + EX_MON(x->c.pc), EX_MDAY(x->c.pc), + EX_HOUR(x->c.pc), EX_MIN(x->c.pc), + EX_SEC(x->c.pc), +#endif + RSTRING_PTR(flags)); + } +} + +static VALUE +d_lite_inspect_raw(VALUE self) +{ + get_d1(self); + return mk_inspect_raw(dat, rb_obj_classname(self)); +} +#endif + +static VALUE +mk_inspect(union DateData *x, const char *klass, const char *to_s) +{ + VALUE jd, sf; + + RB_GC_GUARD(jd) = f_inspect(m_real_jd(x)); + RB_GC_GUARD(sf) = f_inspect(m_sf(x)); + + return rb_enc_sprintf(rb_usascii_encoding(), + "#<%s: %s ((%sj,%ds,%sn),%+ds,%.0fj)>", + klass ? klass : "?", + to_s ? to_s : "?", + RSTRING_PTR(jd), m_df(x), RSTRING_PTR(sf), + m_of(x), m_sg(x)); +} + +/* + * call-seq: + * d.inspect -> string + * + * Returns the value as a string for inspection. + * + * Date.new(2001,2,3).inspect + * #=> "#" + * DateTime.new(2001,2,3,4,5,6,'-7').inspect + * #=> "#" + */ +static VALUE +d_lite_inspect(VALUE self) +{ + get_d1(self); + { + VALUE to_s; + + RB_GC_GUARD(to_s) = f_to_s(self); + return mk_inspect(dat, rb_obj_classname(self), RSTRING_PTR(to_s)); + } +} + +#include +#include "date_tmx.h" + +size_t date_strftime(char *s, size_t maxsize, const char *format, + const struct tmx *tmx); + +#define SMALLBUF 100 +static size_t +date_strftime_alloc(char **buf, const char *format, + struct tmx *tmx) +{ + size_t size, len, flen; + + (*buf)[0] = '\0'; + flen = strlen(format); + if (flen == 0) { + return 0; + } + errno = 0; + len = date_strftime(*buf, SMALLBUF, format, tmx); + if (len != 0 || (**buf == '\0' && errno != ERANGE)) return len; + for (size=1024; ; size*=2) { + *buf = xmalloc(size); + (*buf)[0] = '\0'; + len = date_strftime(*buf, size, format, tmx); + /* + * buflen can be zero EITHER because there's not enough + * room in the string, or because the control command + * goes to the empty string. Make a reasonable guess that + * if the buffer is 1024 times bigger than the length of the + * format string, it's not failing for lack of room. + */ + if (len > 0) break; + xfree(*buf); + if (size >= 1024 * flen) { + rb_sys_fail(format); + break; + } + } + return len; +} + +static VALUE +tmx_m_secs(union DateData *x) +{ + VALUE s; + int df; + + s = day_to_sec(f_sub(m_real_jd(x), + UNIX_EPOCH_IN_CJD)); + if (simple_dat_p(x)) + return s; + df = m_df(x); + if (df) + s = f_add(s, INT2FIX(df)); + return s; +} + +#define MILLISECOND_IN_NANOSECONDS 1000000 + +static VALUE +tmx_m_msecs(union DateData *x) +{ + VALUE s, sf; + + s = sec_to_ms(tmx_m_secs(x)); + if (simple_dat_p(x)) + return s; + sf = m_sf(x); + if (f_nonzero_p(sf)) + s = f_add(s, f_div(sf, INT2FIX(MILLISECOND_IN_NANOSECONDS))); + return s; +} + +static int +tmx_m_of(union DateData *x) +{ + return m_of(x); +} + +static char * +tmx_m_zone(union DateData *x) +{ + return RSTRING_PTR(m_zone(x)); +} + +static struct tmx_funcs tmx_funcs = { + (VALUE (*)(void *))m_real_year, + (int (*)(void *))m_yday, + (int (*)(void *))m_mon, + (int (*)(void *))m_mday, + (VALUE (*)(void *))m_real_cwyear, + (int (*)(void *))m_cweek, + (int (*)(void *))m_cwday, + (int (*)(void *))m_wnum0, + (int (*)(void *))m_wnum1, + (int (*)(void *))m_wday, + (int (*)(void *))m_hour, + (int (*)(void *))m_min, + (int (*)(void *))m_sec, + (VALUE (*)(void *))m_sf_in_sec, + (VALUE (*)(void *))tmx_m_secs, + (VALUE (*)(void *))tmx_m_msecs, + (int (*)(void *))tmx_m_of, + (char *(*)(void *))tmx_m_zone +}; + +static void +set_tmx(VALUE self, struct tmx *tmx) +{ + get_d1(self); + tmx->dat = (void *)dat; + tmx->funcs = &tmx_funcs; +} + +static VALUE +date_strftime_internal(int argc, VALUE *argv, VALUE self, + const char *default_fmt, + void (*func)(VALUE, struct tmx *)) +{ + VALUE vfmt; + const char *fmt; + long len; + char buffer[SMALLBUF], *buf = buffer; + struct tmx tmx; + VALUE str; + + rb_scan_args(argc, argv, "01", &vfmt); + + if (argc < 1) + vfmt = rb_usascii_str_new2(default_fmt); + else { + StringValue(vfmt); + if (!rb_enc_str_asciicompat_p(vfmt)) { + rb_raise(rb_eArgError, + "format should have ASCII compatible encoding"); + } + } + fmt = RSTRING_PTR(vfmt); + len = RSTRING_LEN(vfmt); + (*func)(self, &tmx); + if (memchr(fmt, '\0', len)) { + /* Ruby string may contain \0's. */ + const char *p = fmt, *pe = fmt + len; + + str = rb_str_new(0, 0); + while (p < pe) { + len = date_strftime_alloc(&buf, p, &tmx); + rb_str_cat(str, buf, len); + p += strlen(p); + if (buf != buffer) { + xfree(buf); + buf = buffer; + } + for (fmt = p; p < pe && !*p; ++p); + if (p > fmt) rb_str_cat(str, fmt, p - fmt); + } + rb_enc_copy(str, vfmt); + OBJ_INFECT(str, vfmt); + return str; + } + else + len = date_strftime_alloc(&buf, fmt, &tmx); + + str = rb_str_new(buf, len); + if (buf != buffer) xfree(buf); + rb_enc_copy(str, vfmt); + OBJ_INFECT(str, vfmt); + return str; +} + +/* + * call-seq: + * d.strftime([format='%F']) -> string + * + * Formats date according to the directives in the given format + * string. + * The directives begins with a percent (%) character. + * Any text not listed as a directive will be passed through to the + * output string. + * + * The directive consists of a percent (%) character, + * zero or more flags, optional minimum field width, + * optional modifier and a conversion specifier + * as follows. + * + * % + * + * Flags: + * - don't pad a numerical output. + * _ use spaces for padding. + * 0 use zeros for padding. + * ^ upcase the result string. + * # change case. + * + * The minimum field width specifies the minimum width. + * + * The modifiers are "E", "O", ":", "::" and ":::". + * "E" and "O" are ignored. No effect to result currently. + * + * Format directives: + * + * Date (Year, Month, Day): + * %Y - Year with century (can be negative, 4 digits at least) + * -0001, 0000, 1995, 2009, 14292, etc. + * %C - year / 100 (round down. 20 in 2009) + * %y - year % 100 (00..99) + * + * %m - Month of the year, zero-padded (01..12) + * %_m blank-padded ( 1..12) + * %-m no-padded (1..12) + * %B - The full month name (``January'') + * %^B uppercased (``JANUARY'') + * %b - The abbreviated month name (``Jan'') + * %^b uppercased (``JAN'') + * %h - Equivalent to %b + * + * %d - Day of the month, zero-padded (01..31) + * %-d no-padded (1..31) + * %e - Day of the month, blank-padded ( 1..31) + * + * %j - Day of the year (001..366) + * + * Time (Hour, Minute, Second, Subsecond): + * %H - Hour of the day, 24-hour clock, zero-padded (00..23) + * %k - Hour of the day, 24-hour clock, blank-padded ( 0..23) + * %I - Hour of the day, 12-hour clock, zero-padded (01..12) + * %l - Hour of the day, 12-hour clock, blank-padded ( 1..12) + * %P - Meridian indicator, lowercase (``am'' or ``pm'') + * %p - Meridian indicator, uppercase (``AM'' or ``PM'') + * + * %M - Minute of the hour (00..59) + * + * %S - Second of the minute (00..59) + * + * %L - Millisecond of the second (000..999) + * %N - Fractional seconds digits, default is 9 digits (nanosecond) + * %3N millisecond (3 digits) %15N femtosecond (15 digits) + * %6N microsecond (6 digits) %18N attosecond (18 digits) + * %9N nanosecond (9 digits) %21N zeptosecond (21 digits) + * %12N picosecond (12 digits) %24N yoctosecond (24 digits) + * + * Time zone: + * %z - Time zone as hour and minute offset from UTC (e.g. +0900) + * %:z - hour and minute offset from UTC with a colon (e.g. +09:00) + * %::z - hour, minute and second offset from UTC (e.g. +09:00:00) + * %:::z - hour, minute and second offset from UTC + * (e.g. +09, +09:30, +09:30:30) + * %Z - Time zone abbreviation name or something similar information. + * + * Weekday: + * %A - The full weekday name (``Sunday'') + * %^A uppercased (``SUNDAY'') + * %a - The abbreviated name (``Sun'') + * %^a uppercased (``SUN'') + * %u - Day of the week (Monday is 1, 1..7) + * %w - Day of the week (Sunday is 0, 0..6) + * + * ISO 8601 week-based year and week number: + * The week 1 of YYYY starts with a Monday and includes YYYY-01-04. + * The days in the year before the first week are in the last week of + * the previous year. + * %G - The week-based year + * %g - The last 2 digits of the week-based year (00..99) + * %V - Week number of the week-based year (01..53) + * + * Week number: + * The week 1 of YYYY starts with a Sunday or Monday (according to %U + * or %W). The days in the year before the first week are in week 0. + * %U - Week number of the year. The week starts with Sunday. (00..53) + * %W - Week number of the year. The week starts with Monday. (00..53) + * + * Seconds since the Unix Epoch: + * %s - Number of seconds since 1970-01-01 00:00:00 UTC. + * %Q - Number of milliseconds since 1970-01-01 00:00:00 UTC. + * + * Literal string: + * %n - Newline character (\n) + * %t - Tab character (\t) + * %% - Literal ``%'' character + * + * Combination: + * %c - date and time (%a %b %e %T %Y) + * %D - Date (%m/%d/%y) + * %F - The ISO 8601 date format (%Y-%m-%d) + * %v - VMS date (%e-%b-%Y) + * %x - Same as %D + * %X - Same as %T + * %r - 12-hour time (%I:%M:%S %p) + * %R - 24-hour time (%H:%M) + * %T - 24-hour time (%H:%M:%S) + * %+ - date(1) (%a %b %e %H:%M:%S %Z %Y) + * + * This method is similar to strftime() function defined in ISO C and POSIX. + * Several directives (%a, %A, %b, %B, %c, %p, %r, %x, %X, %E*, %O* and %Z) + * are locale dependent in the function. + * However this method is locale independent. + * So, the result may differ even if a same format string is used in other + * systems such as C. + * It is good practice to avoid %x and %X because there are corresponding + * locale independent representations, %D and %T. + * + * Examples: + * + * d = DateTime.new(2007,11,19,8,37,48,"-06:00") + * #=> # + * d.strftime("Printed on %m/%d/%Y") #=> "Printed on 11/19/2007" + * d.strftime("at %I:%M%p") #=> "at 08:37AM" + * + * Various ISO 8601 formats: + * %Y%m%d => 20071119 Calendar date (basic) + * %F => 2007-11-19 Calendar date (extended) + * %Y-%m => 2007-11 Calendar date, reduced accuracy, specific month + * %Y => 2007 Calendar date, reduced accuracy, specific year + * %C => 20 Calendar date, reduced accuracy, specific century + * %Y%j => 2007323 Ordinal date (basic) + * %Y-%j => 2007-323 Ordinal date (extended) + * %GW%V%u => 2007W471 Week date (basic) + * %G-W%V-%u => 2007-W47-1 Week date (extended) + * %GW%V => 2007W47 Week date, reduced accuracy, specific week (basic) + * %G-W%V => 2007-W47 Week date, reduced accuracy, specific week (extended) + * %H%M%S => 083748 Local time (basic) + * %T => 08:37:48 Local time (extended) + * %H%M => 0837 Local time, reduced accuracy, specific minute (basic) + * %H:%M => 08:37 Local time, reduced accuracy, specific minute (extended) + * %H => 08 Local time, reduced accuracy, specific hour + * %H%M%S,%L => 083748,000 Local time with decimal fraction, comma as decimal sign (basic) + * %T,%L => 08:37:48,000 Local time with decimal fraction, comma as decimal sign (extended) + * %H%M%S.%L => 083748.000 Local time with decimal fraction, full stop as decimal sign (basic) + * %T.%L => 08:37:48.000 Local time with decimal fraction, full stop as decimal sign (extended) + * %H%M%S%z => 083748-0600 Local time and the difference from UTC (basic) + * %T%:z => 08:37:48-06:00 Local time and the difference from UTC (extended) + * %Y%m%dT%H%M%S%z => 20071119T083748-0600 Date and time of day for calendar date (basic) + * %FT%T%:z => 2007-11-19T08:37:48-06:00 Date and time of day for calendar date (extended) + * %Y%jT%H%M%S%z => 2007323T083748-0600 Date and time of day for ordinal date (basic) + * %Y-%jT%T%:z => 2007-323T08:37:48-06:00 Date and time of day for ordinal date (extended) + * %GW%V%uT%H%M%S%z => 2007W471T083748-0600 Date and time of day for week date (basic) + * %G-W%V-%uT%T%:z => 2007-W47-1T08:37:48-06:00 Date and time of day for week date (extended) + * %Y%m%dT%H%M => 20071119T0837 Calendar date and local time (basic) + * %FT%R => 2007-11-19T08:37 Calendar date and local time (extended) + * %Y%jT%H%MZ => 2007323T0837Z Ordinal date and UTC of day (basic) + * %Y-%jT%RZ => 2007-323T08:37Z Ordinal date and UTC of day (extended) + * %GW%V%uT%H%M%z => 2007W471T0837-0600 Week date and local time and difference from UTC (basic) + * %G-W%V-%uT%R%:z => 2007-W47-1T08:37-06:00 Week date and local time and difference from UTC (extended) + * + * See also strftime(3) and strptime. + */ +static VALUE +d_lite_strftime(int argc, VALUE *argv, VALUE self) +{ + return date_strftime_internal(argc, argv, self, + "%Y-%m-%d", set_tmx); +} + +static VALUE +strftimev(const char *fmt, VALUE self, + void (*func)(VALUE, struct tmx *)) +{ + char buffer[SMALLBUF], *buf = buffer; + struct tmx tmx; + long len; + VALUE str; + + (*func)(self, &tmx); + len = date_strftime_alloc(&buf, fmt, &tmx); + str = rb_usascii_str_new(buf, len); + if (buf != buffer) xfree(buf); + return str; +} + +/* + * call-seq: + * d.asctime -> string + * d.ctime -> string + * + * Returns a string in asctime(3) format (but without "\n\0" at the + * end). This method is equivalent to strftime('%c'). + * + * See also asctime(3) or ctime(3). + */ +static VALUE +d_lite_asctime(VALUE self) +{ + return strftimev("%a %b %e %H:%M:%S %Y", self, set_tmx); +} + +/* + * call-seq: + * d.iso8601 -> string + * d.xmlschema -> string + * + * This method is equivalent to strftime('%F'). + */ +static VALUE +d_lite_iso8601(VALUE self) +{ + return strftimev("%Y-%m-%d", self, set_tmx); +} + +/* + * call-seq: + * d.rfc3339 -> string + * + * This method is equivalent to strftime('%FT%T%:z'). + */ +static VALUE +d_lite_rfc3339(VALUE self) +{ + return strftimev("%Y-%m-%dT%H:%M:%S%:z", self, set_tmx); +} + +/* + * call-seq: + * d.rfc2822 -> string + * d.rfc822 -> string + * + * This method is equivalent to strftime('%a, %-d %b %Y %T %z'). + */ +static VALUE +d_lite_rfc2822(VALUE self) +{ + return strftimev("%a, %-d %b %Y %T %z", self, set_tmx); +} + +/* + * call-seq: + * d.httpdate -> string + * + * This method is equivalent to strftime('%a, %d %b %Y %T GMT'). + * See also RFC 2616. + */ +static VALUE +d_lite_httpdate(VALUE self) +{ + volatile VALUE dup = dup_obj_with_new_offset(self, 0); + return strftimev("%a, %d %b %Y %T GMT", dup, set_tmx); +} + +static VALUE +jisx0301_date(VALUE jd, VALUE y) +{ + VALUE a[2]; + + if (f_lt_p(jd, INT2FIX(2405160))) + return rb_usascii_str_new2("%Y-%m-%d"); + if (f_lt_p(jd, INT2FIX(2419614))) { + a[0] = rb_usascii_str_new2("M%02d" ".%%m.%%d"); + a[1] = f_sub(y, INT2FIX(1867)); + } + else if (f_lt_p(jd, INT2FIX(2424875))) { + a[0] = rb_usascii_str_new2("T%02d" ".%%m.%%d"); + a[1] = f_sub(y, INT2FIX(1911)); + } + else if (f_lt_p(jd, INT2FIX(2447535))) { + a[0] = rb_usascii_str_new2("S%02d" ".%%m.%%d"); + a[1] = f_sub(y, INT2FIX(1925)); + } + else { + a[0] = rb_usascii_str_new2("H%02d" ".%%m.%%d"); + a[1] = f_sub(y, INT2FIX(1988)); + } + return rb_f_sprintf(2, a); +} + +/* + * call-seq: + * d.jisx0301 -> string + * + * Returns a string in a JIS X 0301 format. + * + * Date.new(2001,2,3).jisx0301 #=> "H13.02.03" + */ +static VALUE +d_lite_jisx0301(VALUE self) +{ + VALUE s; + + get_d1(self); + s = jisx0301_date(m_real_local_jd(dat), + m_real_year(dat)); + return strftimev(RSTRING_PTR(s), self, set_tmx); +} + +#ifndef NDEBUG +static VALUE +d_lite_marshal_dump_old(VALUE self) +{ + VALUE a; + + get_d1(self); + + a = rb_ary_new3(3, + m_ajd(dat), + m_of_in_day(dat), + DBL2NUM(m_sg(dat))); + + if (FL_TEST(self, FL_EXIVAR)) { + rb_copy_generic_ivar(a, self); + FL_SET(a, FL_EXIVAR); + } + + return a; +} +#endif + +/* :nodoc: */ +static VALUE +d_lite_marshal_dump(VALUE self) +{ + VALUE a; + + get_d1(self); + + a = rb_ary_new3(6, + m_nth(dat), + INT2FIX(m_jd(dat)), + INT2FIX(m_df(dat)), + m_sf(dat), + INT2FIX(m_of(dat)), + DBL2NUM(m_sg(dat))); + + if (FL_TEST(self, FL_EXIVAR)) { + rb_copy_generic_ivar(a, self); + FL_SET(a, FL_EXIVAR); + } + + return a; +} + +/* :nodoc: */ +static VALUE +d_lite_marshal_load(VALUE self, VALUE a) +{ + get_d1(self); + + rb_check_frozen(self); + rb_check_trusted(self); + + if (!RB_TYPE_P(a, T_ARRAY)) + rb_raise(rb_eTypeError, "expected an array"); + + switch (RARRAY_LEN(a)) { + case 2: /* 1.6.x */ + case 3: /* 1.8.x, 1.9.2 */ + { + VALUE ajd, of, sg, nth, sf; + int jd, df, rof; + double rsg; + + + if (RARRAY_LEN(a) == 2) { + ajd = f_sub(RARRAY_PTR(a)[0], half_days_in_day); + of = INT2FIX(0); + sg = RARRAY_PTR(a)[1]; + if (!k_numeric_p(sg)) + sg = DBL2NUM(RTEST(sg) ? GREGORIAN : JULIAN); + } + else { + ajd = RARRAY_PTR(a)[0]; + of = RARRAY_PTR(a)[1]; + sg = RARRAY_PTR(a)[2]; + } + + old_to_new(ajd, of, sg, + &nth, &jd, &df, &sf, &rof, &rsg); + + if (!df && f_zero_p(sf) && !rof) { + set_to_simple(self, &dat->s, nth, jd, rsg, 0, 0, 0, HAVE_JD); + } else { + if (!complex_dat_p(dat)) + rb_raise(rb_eArgError, + "cannot load complex into simple"); + + set_to_complex(self, &dat->c, nth, jd, df, sf, rof, rsg, + 0, 0, 0, 0, 0, 0, + HAVE_JD | HAVE_DF | COMPLEX_DAT); + } + } + break; + case 6: + { + VALUE nth, sf; + int jd, df, of; + double sg; + + nth = RARRAY_PTR(a)[0]; + jd = NUM2INT(RARRAY_PTR(a)[1]); + df = NUM2INT(RARRAY_PTR(a)[2]); + sf = RARRAY_PTR(a)[3]; + of = NUM2INT(RARRAY_PTR(a)[4]); + sg = NUM2DBL(RARRAY_PTR(a)[5]); + if (!df && f_zero_p(sf) && !of) { + set_to_simple(self, &dat->s, nth, jd, sg, 0, 0, 0, HAVE_JD); + } else { + if (!complex_dat_p(dat)) + rb_raise(rb_eArgError, + "cannot load complex into simple"); + + set_to_complex(self, &dat->c, nth, jd, df, sf, of, sg, + 0, 0, 0, 0, 0, 0, + HAVE_JD | HAVE_DF | COMPLEX_DAT); + } + } + break; + default: + rb_raise(rb_eTypeError, "invalid size"); + break; + } + + if (FL_TEST(a, FL_EXIVAR)) { + rb_copy_generic_ivar(self, a); + FL_SET(self, FL_EXIVAR); + } + + return self; +} + +/* :nodoc: */ +static VALUE +date_s__load(VALUE klass, VALUE s) +{ + VALUE a, obj; + + a = rb_marshal_load(s); + obj = d_lite_s_alloc(klass); + return d_lite_marshal_load(obj, a); +} + +/* datetime */ + +/* + * call-seq: + * DateTime.jd([jd=0[, hour=0[, minute=0[, second=0[, offset=0[, start=Date::ITALY]]]]]]) -> datetime + * + * Creates a datetime object denoting the given chronological Julian + * day number. + * + * DateTime.jd(2451944) #=> # + * DateTime.jd(2451945) #=> # + * DateTime.jd(Rational('0.5')) + * #=> # + */ +static VALUE +datetime_s_jd(int argc, VALUE *argv, VALUE klass) +{ + VALUE vjd, vh, vmin, vs, vof, vsg, jd, fr, fr2, ret; + int h, min, s, rof; + double sg; + + rb_scan_args(argc, argv, "06", &vjd, &vh, &vmin, &vs, &vof, &vsg); + + jd = INT2FIX(0); + + h = min = s = 0; + fr2 = INT2FIX(0); + rof = 0; + sg = DEFAULT_SG; + + switch (argc) { + case 6: + val2sg(vsg, sg); + case 5: + val2off(vof, rof); + case 4: + num2int_with_frac(s, positive_inf); + case 3: + num2int_with_frac(min, 3); + case 2: + num2int_with_frac(h, 2); + case 1: + num2num_with_frac(jd, 1); + } + + { + VALUE nth; + int rh, rmin, rs, rjd, rjd2; + + if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) + rb_raise(rb_eArgError, "invalid date"); + canon24oc(); + + decode_jd(jd, &nth, &rjd); + rjd2 = jd_local_to_utc(rjd, + time_to_df(rh, rmin, rs), + rof); + + ret = d_complex_new_internal(klass, + nth, rjd2, + 0, INT2FIX(0), + rof, sg, + 0, 0, 0, + rh, rmin, rs, + HAVE_JD | HAVE_TIME); + } + add_frac(); + return ret; +} + +/* + * call-seq: + * DateTime.ordinal([year=-4712[, yday=1[, hour=0[, minute=0[, second=0[, offset=0[, start=Date::ITALY]]]]]]]) -> datetime + * + * Creates a date-time object denoting the given ordinal date. + * + * DateTime.ordinal(2001,34) #=> # + * DateTime.ordinal(2001,34,4,5,6,'+7') + * #=> # + * DateTime.ordinal(2001,-332,-20,-55,-54,'+7') + * #=> # + */ +static VALUE +datetime_s_ordinal(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vd, vh, vmin, vs, vof, vsg, y, fr, fr2, ret; + int d, h, min, s, rof; + double sg; + + rb_scan_args(argc, argv, "07", &vy, &vd, &vh, &vmin, &vs, &vof, &vsg); + + y = INT2FIX(-4712); + d = 1; + + h = min = s = 0; + fr2 = INT2FIX(0); + rof = 0; + sg = DEFAULT_SG; + + switch (argc) { + case 7: + val2sg(vsg, sg); + case 6: + val2off(vof, rof); + case 5: + num2int_with_frac(s, positive_inf); + case 4: + num2int_with_frac(min, 4); + case 3: + num2int_with_frac(h, 3); + case 2: + num2int_with_frac(d, 2); + case 1: + y = vy; + } + + { + VALUE nth; + int ry, rd, rh, rmin, rs, rjd, rjd2, ns; + + if (!valid_ordinal_p(y, d, sg, + &nth, &ry, + &rd, &rjd, + &ns)) + rb_raise(rb_eArgError, "invalid date"); + if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) + rb_raise(rb_eArgError, "invalid date"); + canon24oc(); + + rjd2 = jd_local_to_utc(rjd, + time_to_df(rh, rmin, rs), + rof); + + ret = d_complex_new_internal(klass, + nth, rjd2, + 0, INT2FIX(0), + rof, sg, + 0, 0, 0, + rh, rmin, rs, + HAVE_JD | HAVE_TIME); + } + add_frac(); + return ret; +} + +/* + * call-seq: + * DateTime.civil([year=-4712[, month=1[, mday=1[, hour=0[, minute=0[, second=0[, offset=0[, start=Date::ITALY]]]]]]]]) -> datetime + * DateTime.new([year=-4712[, month=1[, mday=1[, hour=0[, minute=0[, second=0[, offset=0[, start=Date::ITALY]]]]]]]]) -> datetime + * + * Creates a date-time object denoting the given calendar date. + * + * DateTime.new(2001,2,3) #=> # + * DateTime.new(2001,2,3,4,5,6,'+7') + * #=> # + * DateTime.new(2001,-11,-26,-20,-55,-54,'+7') + * #=> # + */ +static VALUE +datetime_s_civil(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vm, vd, vh, vmin, vs, vof, vsg, y, fr, fr2, ret; + int m, d, h, min, s, rof; + double sg; + + rb_scan_args(argc, argv, "08", &vy, &vm, &vd, &vh, &vmin, &vs, &vof, &vsg); + + y = INT2FIX(-4712); + m = 1; + d = 1; + + h = min = s = 0; + fr2 = INT2FIX(0); + rof = 0; + sg = DEFAULT_SG; + + switch (argc) { + case 8: + val2sg(vsg, sg); + case 7: + val2off(vof, rof); + case 6: + num2int_with_frac(s, positive_inf); + case 5: + num2int_with_frac(min, 5); + case 4: + num2int_with_frac(h, 4); + case 3: + num2int_with_frac(d, 3); + case 2: + m = NUM2INT(vm); + case 1: + y = vy; + } + + if (guess_style(y, sg) < 0) { + VALUE nth; + int ry, rm, rd, rh, rmin, rs; + + if (!valid_gregorian_p(y, m, d, + &nth, &ry, + &rm, &rd)) + rb_raise(rb_eArgError, "invalid date"); + if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) + rb_raise(rb_eArgError, "invalid date"); + canon24oc(); + + ret = d_complex_new_internal(klass, + nth, 0, + 0, INT2FIX(0), + rof, sg, + ry, rm, rd, + rh, rmin, rs, + HAVE_CIVIL | HAVE_TIME); + } + else { + VALUE nth; + int ry, rm, rd, rh, rmin, rs, rjd, rjd2, ns; + + if (!valid_civil_p(y, m, d, sg, + &nth, &ry, + &rm, &rd, &rjd, + &ns)) + rb_raise(rb_eArgError, "invalid date"); + if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) + rb_raise(rb_eArgError, "invalid date"); + canon24oc(); + + rjd2 = jd_local_to_utc(rjd, + time_to_df(rh, rmin, rs), + rof); + + ret = d_complex_new_internal(klass, + nth, rjd2, + 0, INT2FIX(0), + rof, sg, + ry, rm, rd, + rh, rmin, rs, + HAVE_JD | HAVE_CIVIL | HAVE_TIME); + } + add_frac(); + return ret; +} + +/* + * call-seq: + * DateTime.commercial([cwyear=-4712[, cweek=1[, cwday=1[, hour=0[, minute=0[, second=0[, offset=0[, start=Date::ITALY]]]]]]]]) -> datetime + * + * Creates a date-time object denoting the given week date. + * + * DateTime.commercial(2001) #=> # + * DateTime.commercial(2002) #=> # + * DateTime.commercial(2001,5,6,4,5,6,'+7') + * #=> # + */ +static VALUE +datetime_s_commercial(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vw, vd, vh, vmin, vs, vof, vsg, y, fr, fr2, ret; + int w, d, h, min, s, rof; + double sg; + + rb_scan_args(argc, argv, "08", &vy, &vw, &vd, &vh, &vmin, &vs, &vof, &vsg); + + y = INT2FIX(-4712); + w = 1; + d = 1; + + h = min = s = 0; + fr2 = INT2FIX(0); + rof = 0; + sg = DEFAULT_SG; + + switch (argc) { + case 8: + val2sg(vsg, sg); + case 7: + val2off(vof, rof); + case 6: + num2int_with_frac(s, positive_inf); + case 5: + num2int_with_frac(min, 5); + case 4: + num2int_with_frac(h, 4); + case 3: + num2int_with_frac(d, 3); + case 2: + w = NUM2INT(vw); + case 1: + y = vy; + } + + { + VALUE nth; + int ry, rw, rd, rh, rmin, rs, rjd, rjd2, ns; + + if (!valid_commercial_p(y, w, d, sg, + &nth, &ry, + &rw, &rd, &rjd, + &ns)) + rb_raise(rb_eArgError, "invalid date"); + if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) + rb_raise(rb_eArgError, "invalid date"); + canon24oc(); + + rjd2 = jd_local_to_utc(rjd, + time_to_df(rh, rmin, rs), + rof); + + ret = d_complex_new_internal(klass, + nth, rjd2, + 0, INT2FIX(0), + rof, sg, + 0, 0, 0, + rh, rmin, rs, + HAVE_JD | HAVE_TIME); + } + add_frac(); + return ret; +} + +#ifndef NDEBUG +static VALUE +datetime_s_weeknum(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vw, vd, vf, vh, vmin, vs, vof, vsg, y, fr, fr2, ret; + int w, d, f, h, min, s, rof; + double sg; + + rb_scan_args(argc, argv, "09", &vy, &vw, &vd, &vf, + &vh, &vmin, &vs, &vof, &vsg); + + y = INT2FIX(-4712); + w = 0; + d = 1; + f = 0; + + h = min = s = 0; + fr2 = INT2FIX(0); + rof = 0; + sg = DEFAULT_SG; + + switch (argc) { + case 9: + val2sg(vsg, sg); + case 8: + val2off(vof, rof); + case 7: + num2int_with_frac(s, positive_inf); + case 6: + num2int_with_frac(min, 6); + case 5: + num2int_with_frac(h, 5); + case 4: + f = NUM2INT(vf); + case 3: + num2int_with_frac(d, 4); + case 2: + w = NUM2INT(vw); + case 1: + y = vy; + } + + { + VALUE nth; + int ry, rw, rd, rh, rmin, rs, rjd, rjd2, ns; + + if (!valid_weeknum_p(y, w, d, f, sg, + &nth, &ry, + &rw, &rd, &rjd, + &ns)) + rb_raise(rb_eArgError, "invalid date"); + if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) + rb_raise(rb_eArgError, "invalid date"); + canon24oc(); + + rjd2 = jd_local_to_utc(rjd, + time_to_df(rh, rmin, rs), + rof); + ret = d_complex_new_internal(klass, + nth, rjd2, + 0, INT2FIX(0), + rof, sg, + 0, 0, 0, + rh, rmin, rs, + HAVE_JD | HAVE_TIME); + } + add_frac(); + return ret; +} + +static VALUE +datetime_s_nth_kday(int argc, VALUE *argv, VALUE klass) +{ + VALUE vy, vm, vn, vk, vh, vmin, vs, vof, vsg, y, fr, fr2, ret; + int m, n, k, h, min, s, rof; + double sg; + + rb_scan_args(argc, argv, "09", &vy, &vm, &vn, &vk, + &vh, &vmin, &vs, &vof, &vsg); + + y = INT2FIX(-4712); + m = 1; + n = 1; + k = 1; + + h = min = s = 0; + fr2 = INT2FIX(0); + rof = 0; + sg = DEFAULT_SG; + + switch (argc) { + case 9: + val2sg(vsg, sg); + case 8: + val2off(vof, rof); + case 7: + num2int_with_frac(s, positive_inf); + case 6: + num2int_with_frac(min, 6); + case 5: + num2int_with_frac(h, 5); + case 4: + num2int_with_frac(k, 4); + case 3: + n = NUM2INT(vn); + case 2: + m = NUM2INT(vm); + case 1: + y = vy; + } + + { + VALUE nth; + int ry, rm, rn, rk, rh, rmin, rs, rjd, rjd2, ns; + + if (!valid_nth_kday_p(y, m, n, k, sg, + &nth, &ry, + &rm, &rn, &rk, &rjd, + &ns)) + rb_raise(rb_eArgError, "invalid date"); + if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) + rb_raise(rb_eArgError, "invalid date"); + canon24oc(); + + rjd2 = jd_local_to_utc(rjd, + time_to_df(rh, rmin, rs), + rof); + ret = d_complex_new_internal(klass, + nth, rjd2, + 0, INT2FIX(0), + rof, sg, + 0, 0, 0, + rh, rmin, rs, + HAVE_JD | HAVE_TIME); + } + add_frac(); + return ret; +} +#endif + +/* + * call-seq: + * DateTime.now([start=Date::ITALY]) -> datetime + * + * Creates a date-time object denoting the present time. + * + * DateTime.now #=> # + */ +static VALUE +datetime_s_now(int argc, VALUE *argv, VALUE klass) +{ + VALUE vsg, nth, ret; + double sg; +#ifdef HAVE_CLOCK_GETTIME + struct timespec ts; +#else + struct timeval tv; +#endif + time_t sec; + struct tm tm; + long sf, of; + int y, ry, m, d, h, min, s; + + rb_scan_args(argc, argv, "01", &vsg); + + if (argc < 1) + sg = DEFAULT_SG; + else + sg = NUM2DBL(vsg); + +#ifdef HAVE_CLOCK_GETTIME + if (clock_gettime(CLOCK_REALTIME, &ts) == -1) + rb_sys_fail("clock_gettime"); + sec = ts.tv_sec; +#else + if (gettimeofday(&tv, NULL) == -1) + rb_sys_fail("gettimeofday"); + sec = tv.tv_sec; +#endif + tzset(); + if (!localtime_r(&sec, &tm)) + rb_sys_fail("localtime"); + + y = tm.tm_year + 1900; + m = tm.tm_mon + 1; + d = tm.tm_mday; + h = tm.tm_hour; + min = tm.tm_min; + s = tm.tm_sec; + if (s == 60) + s = 59; +#ifdef HAVE_STRUCT_TM_TM_GMTOFF + of = tm.tm_gmtoff; +#elif defined(HAVE_VAR_TIMEZONE) +#ifdef HAVE_VAR_ALTZONE + of = (long)-((tm.tm_isdst > 0) ? altzone : timezone); +#else + of = (long)-timezone; + if (tm.tm_isdst) { + time_t sec2; + + tm.tm_isdst = 0; + sec2 = mktime(&tm); + of += (long)difftime(sec2, sec); + } +#endif +#elif defined(HAVE_TIMEGM) + { + time_t sec2; + + sec2 = timegm(&tm); + of = (long)difftime(sec2, sec); + } +#else + { + struct tm tm2; + time_t sec2; + + if (!gmtime_r(&sec, &tm2)) + rb_sys_fail("gmtime"); + tm2.tm_isdst = tm.tm_isdst; + sec2 = mktime(&tm2); + of = (long)difftime(sec, sec2); + } +#endif +#ifdef HAVE_CLOCK_GETTIME + sf = ts.tv_nsec; +#else + sf = tv.tv_usec * 1000; +#endif + + if (of < -DAY_IN_SECONDS || of > DAY_IN_SECONDS) { + of = 0; + rb_warning("invalid offset is ignored"); + } + + decode_year(INT2FIX(y), -1, &nth, &ry); + + ret = d_complex_new_internal(klass, + nth, 0, + 0, LONG2NUM(sf), + (int)of, GREGORIAN, + ry, m, d, + h, min, s, + HAVE_CIVIL | HAVE_TIME); + { + get_d1(ret); + set_sg(dat, sg); + } + return ret; +} + +static VALUE +dt_new_by_frags(VALUE klass, VALUE hash, VALUE sg) +{ + VALUE jd, sf, t; + int df, of; + + if (!c_valid_start_p(NUM2DBL(sg))) { + sg = INT2FIX(DEFAULT_SG); + rb_warning("invalid start is ignored"); + } + + if (NIL_P(hash)) + rb_raise(rb_eArgError, "invalid date"); + + if (NIL_P(ref_hash("jd")) && + NIL_P(ref_hash("yday")) && + !NIL_P(ref_hash("year")) && + !NIL_P(ref_hash("mon")) && + !NIL_P(ref_hash("mday"))) { + jd = rt__valid_civil_p(ref_hash("year"), + ref_hash("mon"), + ref_hash("mday"), sg); + + if (NIL_P(ref_hash("hour"))) + set_hash("hour", INT2FIX(0)); + if (NIL_P(ref_hash("min"))) + set_hash("min", INT2FIX(0)); + if (NIL_P(ref_hash("sec"))) + set_hash("sec", INT2FIX(0)); + else if (f_eqeq_p(ref_hash("sec"), INT2FIX(60))) + set_hash("sec", INT2FIX(59)); + } + else { + hash = rt_rewrite_frags(hash); + hash = rt_complete_frags(klass, hash); + jd = rt__valid_date_frags_p(hash, sg); + } + + if (NIL_P(jd)) + rb_raise(rb_eArgError, "invalid date"); + + { + int rh, rmin, rs; + + if (!c_valid_time_p(NUM2INT(ref_hash("hour")), + NUM2INT(ref_hash("min")), + NUM2INT(ref_hash("sec")), + &rh, &rmin, &rs)) + rb_raise(rb_eArgError, "invalid date"); + + df = time_to_df(rh, rmin, rs); + } + + t = ref_hash("sec_fraction"); + if (NIL_P(t)) + sf = INT2FIX(0); + else + sf = sec_to_ns(t); + + t = ref_hash("offset"); + if (NIL_P(t)) + of = 0; + else { + of = NUM2INT(t); + if (of < -DAY_IN_SECONDS || of > DAY_IN_SECONDS) { + of = 0; + rb_warning("invalid offset is ignored"); + } + } + { + VALUE nth; + int rjd, rjd2; + + decode_jd(jd, &nth, &rjd); + rjd2 = jd_local_to_utc(rjd, df, of); + df = df_local_to_utc(df, of); + + return d_complex_new_internal(klass, + nth, rjd2, + df, sf, + of, NUM2DBL(sg), + 0, 0, 0, + 0, 0, 0, + HAVE_JD | HAVE_DF); + } +} + +/* + * call-seq: + * DateTime._strptime(string[, format='%FT%T%z']) -> hash + * + * Parses the given representation of date and time with the given + * template, and returns a hash of parsed elements. _strptime does + * not support specification of flags and width unlike strftime. + * + * See also strptime(3) and strftime. + */ +static VALUE +datetime_s__strptime(int argc, VALUE *argv, VALUE klass) +{ + return date_s__strptime_internal(argc, argv, klass, "%FT%T%z"); +} + +/* + * call-seq: + * DateTime.strptime([string='-4712-01-01T00:00:00+00:00'[, format='%FT%T%z'[ ,start=ITALY]]]) -> datetime + * + * Parses the given representation of date and time with the given + * template, and creates a date object. strptime does not support + * specification of flags and width unlike strftime. + * + * DateTime.strptime('2001-02-03T04:05:06+07:00', '%Y-%m-%dT%H:%M:%S%z') + * #=> # + * DateTime.strptime('03-02-2001 04:05:06 PM', '%d-%m-%Y %I:%M:%S %p') + * #=> # + * DateTime.strptime('2001-W05-6T04:05:06+07:00', '%G-W%V-%uT%H:%M:%S%z') + * #=> # + * DateTime.strptime('2001 04 6 04 05 06 +7', '%Y %U %w %H %M %S %z') + * #=> # + * DateTime.strptime('2001 05 6 04 05 06 +7', '%Y %W %u %H %M %S %z') + * #=> # + * DateTime.strptime('-1', '%s') + * #=> # + * DateTime.strptime('-1000', '%Q') + * #=> # + * DateTime.strptime('sat3feb014pm+7', '%a%d%b%y%H%p%z') + * #=> # + * + * See also strptime(3) and strftime. + */ +static VALUE +datetime_s_strptime(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, fmt, sg; + + rb_scan_args(argc, argv, "03", &str, &fmt, &sg); + + switch (argc) { + case 0: + str = rb_str_new2("-4712-01-01T00:00:00+00:00"); + case 1: + fmt = rb_str_new2("%FT%T%z"); + case 2: + sg = INT2FIX(DEFAULT_SG); + } + + { + VALUE argv2[2], hash; + + argv2[0] = str; + argv2[1] = fmt; + hash = date_s__strptime(2, argv2, klass); + return dt_new_by_frags(klass, hash, sg); + } +} + +/* + * call-seq: + * DateTime.parse(string='-4712-01-01T00:00:00+00:00'[, comp=true[, start=ITALY]]) -> datetime + * + * Parses the given representation of date and time, and creates a + * date object. This method does not function as a validator. + * + * If the optional second argument is true and the detected year is in + * the range "00" to "99", makes it full. + * + * DateTime.parse('2001-02-03T04:05:06+07:00') + * #=> # + * DateTime.parse('20010203T040506+0700') + * #=> # + * DateTime.parse('3rd Feb 2001 04:05:06 PM') + * #=> # + */ +static VALUE +datetime_s_parse(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, comp, sg; + + rb_scan_args(argc, argv, "03", &str, &comp, &sg); + + switch (argc) { + case 0: + str = rb_str_new2("-4712-01-01T00:00:00+00:00"); + case 1: + comp = Qtrue; + case 2: + sg = INT2FIX(DEFAULT_SG); + } + + { + VALUE argv2[2], hash; + + argv2[0] = str; + argv2[1] = comp; + hash = date_s__parse(2, argv2, klass); + return dt_new_by_frags(klass, hash, sg); + } +} + +/* + * call-seq: + * DateTime.iso8601(string='-4712-01-01T00:00:00+00:00'[, start=ITALY]) -> datetime + * + * Creates a new Date object by parsing from a string according to + * some typical ISO 8601 formats. + * + * DateTime.iso8601('2001-02-03T04:05:06+07:00') + * #=> # + * DateTime.iso8601('20010203T040506+0700') + * #=> # + * DateTime.iso8601('2001-W05-6T04:05:06+07:00') + * #=> # + */ +static VALUE +datetime_s_iso8601(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, sg; + + rb_scan_args(argc, argv, "02", &str, &sg); + + switch (argc) { + case 0: + str = rb_str_new2("-4712-01-01T00:00:00+00:00"); + case 1: + sg = INT2FIX(DEFAULT_SG); + } + + { + VALUE hash = date_s__iso8601(klass, str); + return dt_new_by_frags(klass, hash, sg); + } +} + +/* + * call-seq: + * DateTime.rfc3339(string='-4712-01-01T00:00:00+00:00'[, start=ITALY]) -> datetime + * + * Creates a new Date object by parsing from a string according to + * some typical RFC 3339 formats. + * + * DateTime.rfc3339('2001-02-03T04:05:06+07:00') + * #=> # + */ +static VALUE +datetime_s_rfc3339(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, sg; + + rb_scan_args(argc, argv, "02", &str, &sg); + + switch (argc) { + case 0: + str = rb_str_new2("-4712-01-01T00:00:00+00:00"); + case 1: + sg = INT2FIX(DEFAULT_SG); + } + + { + VALUE hash = date_s__rfc3339(klass, str); + return dt_new_by_frags(klass, hash, sg); + } +} + +/* + * call-seq: + * DateTime.xmlschema(string='-4712-01-01T00:00:00+00:00'[, start=ITALY]) -> datetime + * + * Creates a new Date object by parsing from a string according to + * some typical XML Schema formats. + * + * DateTime.xmlschema('2001-02-03T04:05:06+07:00') + * #=> # + */ +static VALUE +datetime_s_xmlschema(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, sg; + + rb_scan_args(argc, argv, "02", &str, &sg); + + switch (argc) { + case 0: + str = rb_str_new2("-4712-01-01T00:00:00+00:00"); + case 1: + sg = INT2FIX(DEFAULT_SG); + } + + { + VALUE hash = date_s__xmlschema(klass, str); + return dt_new_by_frags(klass, hash, sg); + } +} + +/* + * call-seq: + * DateTime.rfc2822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=ITALY]) -> datetime + * DateTime.rfc822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=ITALY]) -> datetime + * + * Creates a new Date object by parsing from a string according to + * some typical RFC 2822 formats. + * + * DateTime.rfc2822('Sat, 3 Feb 2001 04:05:06 +0700') + * #=> # + */ +static VALUE +datetime_s_rfc2822(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, sg; + + rb_scan_args(argc, argv, "02", &str, &sg); + + switch (argc) { + case 0: + str = rb_str_new2("Mon, 1 Jan -4712 00:00:00 +0000"); + case 1: + sg = INT2FIX(DEFAULT_SG); + } + + { + VALUE hash = date_s__rfc2822(klass, str); + return dt_new_by_frags(klass, hash, sg); + } +} + +/* + * call-seq: + * DateTime.httpdate(string='Mon, 01 Jan -4712 00:00:00 GMT'[, start=ITALY]) -> datetime + * + * Creates a new Date object by parsing from a string according to + * some RFC 2616 format. + * + * DateTime.httpdate('Sat, 03 Feb 2001 04:05:06 GMT') + * #=> # + */ +static VALUE +datetime_s_httpdate(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, sg; + + rb_scan_args(argc, argv, "02", &str, &sg); + + switch (argc) { + case 0: + str = rb_str_new2("Mon, 01 Jan -4712 00:00:00 GMT"); + case 1: + sg = INT2FIX(DEFAULT_SG); + } + + { + VALUE hash = date_s__httpdate(klass, str); + return dt_new_by_frags(klass, hash, sg); + } +} + +/* + * call-seq: + * DateTime.jisx0301(string='-4712-01-01T00:00:00+00:00'[, start=ITALY]) -> datetime + * + * Creates a new Date object by parsing from a string according to + * some typical JIS X 0301 formats. + * + * DateTime.jisx0301('H13.02.03T04:05:06+07:00') + * #=> # + */ +static VALUE +datetime_s_jisx0301(int argc, VALUE *argv, VALUE klass) +{ + VALUE str, sg; + + rb_scan_args(argc, argv, "02", &str, &sg); + + switch (argc) { + case 0: + str = rb_str_new2("-4712-01-01T00:00:00+00:00"); + case 1: + sg = INT2FIX(DEFAULT_SG); + } + + { + VALUE hash = date_s__jisx0301(klass, str); + return dt_new_by_frags(klass, hash, sg); + } +} + +/* + * call-seq: + * dt.to_s -> string + * + * Returns a string in an ISO 8601 format (This method doesn't use the + * expanded representations). + * + * DateTime.new(2001,2,3,4,5,6,'-7').to_s + * #=> "2001-02-03T04:05:06-07:00" + */ +static VALUE +dt_lite_to_s(VALUE self) +{ + return strftimev("%Y-%m-%dT%H:%M:%S%:z", self, set_tmx); +} + +/* + * call-seq: + * dt.strftime([format='%FT%T%:z']) -> string + * + * Formats date according to the directives in the given format + * string. + * The directives begins with a percent (%) character. + * Any text not listed as a directive will be passed through to the + * output string. + * + * The directive consists of a percent (%) character, + * zero or more flags, optional minimum field width, + * optional modifier and a conversion specifier + * as follows. + * + * % + * + * Flags: + * - don't pad a numerical output. + * _ use spaces for padding. + * 0 use zeros for padding. + * ^ upcase the result string. + * # change case. + * : use colons for %z. + * + * The minimum field width specifies the minimum width. + * + * The modifier is "E" and "O". + * They are ignored. + * + * Format directives: + * + * Date (Year, Month, Day): + * %Y - Year with century (can be negative, 4 digits at least) + * -0001, 0000, 1995, 2009, 14292, etc. + * %C - year / 100 (round down. 20 in 2009) + * %y - year % 100 (00..99) + * + * %m - Month of the year, zero-padded (01..12) + * %_m blank-padded ( 1..12) + * %-m no-padded (1..12) + * %B - The full month name (``January'') + * %^B uppercased (``JANUARY'') + * %b - The abbreviated month name (``Jan'') + * %^b uppercased (``JAN'') + * %h - Equivalent to %b + * + * %d - Day of the month, zero-padded (01..31) + * %-d no-padded (1..31) + * %e - Day of the month, blank-padded ( 1..31) + * + * %j - Day of the year (001..366) + * + * Time (Hour, Minute, Second, Subsecond): + * %H - Hour of the day, 24-hour clock, zero-padded (00..23) + * %k - Hour of the day, 24-hour clock, blank-padded ( 0..23) + * %I - Hour of the day, 12-hour clock, zero-padded (01..12) + * %l - Hour of the day, 12-hour clock, blank-padded ( 1..12) + * %P - Meridian indicator, lowercase (``am'' or ``pm'') + * %p - Meridian indicator, uppercase (``AM'' or ``PM'') + * + * %M - Minute of the hour (00..59) + * + * %S - Second of the minute (00..59) + * + * %L - Millisecond of the second (000..999) + * %N - Fractional seconds digits, default is 9 digits (nanosecond) + * %3N millisecond (3 digits) %15N femtosecond (15 digits) + * %6N microsecond (6 digits) %18N attosecond (18 digits) + * %9N nanosecond (9 digits) %21N zeptosecond (21 digits) + * %12N picosecond (12 digits) %24N yoctosecond (24 digits) + * + * Time zone: + * %z - Time zone as hour and minute offset from UTC (e.g. +0900) + * %:z - hour and minute offset from UTC with a colon (e.g. +09:00) + * %::z - hour, minute and second offset from UTC (e.g. +09:00:00) + * %:::z - hour, minute and second offset from UTC + * (e.g. +09, +09:30, +09:30:30) + * %Z - Time zone abbreviation name or something similar information. + * + * Weekday: + * %A - The full weekday name (``Sunday'') + * %^A uppercased (``SUNDAY'') + * %a - The abbreviated name (``Sun'') + * %^a uppercased (``SUN'') + * %u - Day of the week (Monday is 1, 1..7) + * %w - Day of the week (Sunday is 0, 0..6) + * + * ISO 8601 week-based year and week number: + * The week 1 of YYYY starts with a Monday and includes YYYY-01-04. + * The days in the year before the first week are in the last week of + * the previous year. + * %G - The week-based year + * %g - The last 2 digits of the week-based year (00..99) + * %V - Week number of the week-based year (01..53) + * + * Week number: + * The week 1 of YYYY starts with a Sunday or Monday (according to %U + * or %W). The days in the year before the first week are in week 0. + * %U - Week number of the year. The week starts with Sunday. (00..53) + * %W - Week number of the year. The week starts with Monday. (00..53) + * + * Seconds since the Unix Epoch: + * %s - Number of seconds since 1970-01-01 00:00:00 UTC. + * %Q - Number of milliseconds since 1970-01-01 00:00:00 UTC. + * + * Literal string: + * %n - Newline character (\n) + * %t - Tab character (\t) + * %% - Literal ``%'' character + * + * Combination: + * %c - date and time (%a %b %e %T %Y) + * %D - Date (%m/%d/%y) + * %F - The ISO 8601 date format (%Y-%m-%d) + * %v - VMS date (%e-%b-%Y) + * %x - Same as %D + * %X - Same as %T + * %r - 12-hour time (%I:%M:%S %p) + * %R - 24-hour time (%H:%M) + * %T - 24-hour time (%H:%M:%S) + * %+ - date(1) (%a %b %e %H:%M:%S %Z %Y) + * + * This method is similar to strftime() function defined in ISO C and POSIX. + * Several directives (%a, %A, %b, %B, %c, %p, %r, %x, %X, %E*, %O* and %Z) + * are locale dependent in the function. + * However this method is locale independent. + * So, the result may differ even if a same format string is used in other + * systems such as C. + * It is good practice to avoid %x and %X because there are corresponding + * locale independent representations, %D and %T. + * + * Examples: + * + * d = DateTime.new(2007,11,19,8,37,48,"-06:00") + * #=> # + * d.strftime("Printed on %m/%d/%Y") #=> "Printed on 11/19/2007" + * d.strftime("at %I:%M%p") #=> "at 08:37AM" + * + * Various ISO 8601 formats: + * %Y%m%d => 20071119 Calendar date (basic) + * %F => 2007-11-19 Calendar date (extended) + * %Y-%m => 2007-11 Calendar date, reduced accuracy, specific month + * %Y => 2007 Calendar date, reduced accuracy, specific year + * %C => 20 Calendar date, reduced accuracy, specific century + * %Y%j => 2007323 Ordinal date (basic) + * %Y-%j => 2007-323 Ordinal date (extended) + * %GW%V%u => 2007W471 Week date (basic) + * %G-W%V-%u => 2007-W47-1 Week date (extended) + * %GW%V => 2007W47 Week date, reduced accuracy, specific week (basic) + * %G-W%V => 2007-W47 Week date, reduced accuracy, specific week (extended) + * %H%M%S => 083748 Local time (basic) + * %T => 08:37:48 Local time (extended) + * %H%M => 0837 Local time, reduced accuracy, specific minute (basic) + * %H:%M => 08:37 Local time, reduced accuracy, specific minute (extended) + * %H => 08 Local time, reduced accuracy, specific hour + * %H%M%S,%L => 083748,000 Local time with decimal fraction, comma as decimal sign (basic) + * %T,%L => 08:37:48,000 Local time with decimal fraction, comma as decimal sign (extended) + * %H%M%S.%L => 083748.000 Local time with decimal fraction, full stop as decimal sign (basic) + * %T.%L => 08:37:48.000 Local time with decimal fraction, full stop as decimal sign (extended) + * %H%M%S%z => 083748-0600 Local time and the difference from UTC (basic) + * %T%:z => 08:37:48-06:00 Local time and the difference from UTC (extended) + * %Y%m%dT%H%M%S%z => 20071119T083748-0600 Date and time of day for calendar date (basic) + * %FT%T%:z => 2007-11-19T08:37:48-06:00 Date and time of day for calendar date (extended) + * %Y%jT%H%M%S%z => 2007323T083748-0600 Date and time of day for ordinal date (basic) + * %Y-%jT%T%:z => 2007-323T08:37:48-06:00 Date and time of day for ordinal date (extended) + * %GW%V%uT%H%M%S%z => 2007W471T083748-0600 Date and time of day for week date (basic) + * %G-W%V-%uT%T%:z => 2007-W47-1T08:37:48-06:00 Date and time of day for week date (extended) + * %Y%m%dT%H%M => 20071119T0837 Calendar date and local time (basic) + * %FT%R => 2007-11-19T08:37 Calendar date and local time (extended) + * %Y%jT%H%MZ => 2007323T0837Z Ordinal date and UTC of day (basic) + * %Y-%jT%RZ => 2007-323T08:37Z Ordinal date and UTC of day (extended) + * %GW%V%uT%H%M%z => 2007W471T0837-0600 Week date and local time and difference from UTC (basic) + * %G-W%V-%uT%R%:z => 2007-W47-1T08:37-06:00 Week date and local time and difference from UTC (extended) + * + * See also strftime(3) and strptime. + */ +static VALUE +dt_lite_strftime(int argc, VALUE *argv, VALUE self) +{ + return date_strftime_internal(argc, argv, self, + "%Y-%m-%dT%H:%M:%S%:z", set_tmx); +} + +static VALUE +iso8601_timediv(VALUE self, VALUE n) +{ + VALUE fmt; + + n = to_integer(n); + fmt = rb_usascii_str_new2("T%H:%M:%S"); + if (f_gt_p(n, INT2FIX(0))) { + VALUE argv[3]; + + get_d1(self); + + argv[0] = rb_usascii_str_new2(".%0*d"); + argv[1] = n; + argv[2] = f_round(f_quo(m_sf_in_sec(dat), + f_quo(INT2FIX(1), + f_expt(INT2FIX(10), n)))); + rb_str_append(fmt, rb_f_sprintf(3, argv)); + } + rb_str_append(fmt, rb_usascii_str_new2("%:z")); + return strftimev(RSTRING_PTR(fmt), self, set_tmx); +} + +/* + * call-seq: + * dt.iso8601([n=0]) -> string + * dt.xmlschema([n=0]) -> string + * + * This method is equivalent to strftime('%FT%T'). The optional + * argument n is length of fractional seconds. + * + * DateTime.parse('2001-02-03T04:05:06.123456789+07:00').iso8601(9) + * #=> "2001-02-03T04:05:06.123456789+07:00" + */ +static VALUE +dt_lite_iso8601(int argc, VALUE *argv, VALUE self) +{ + VALUE n; + + rb_scan_args(argc, argv, "01", &n); + + if (argc < 1) + n = INT2FIX(0); + + return f_add(strftimev("%Y-%m-%d", self, set_tmx), + iso8601_timediv(self, n)); +} + +/* + * call-seq: + * dt.rfc3339([n=0]) -> string + * + * This method is equivalent to strftime('%FT%T'). The optional + * argument n is length of fractional seconds. + * + * DateTime.parse('2001-02-03T04:05:06.123456789+07:00').rfc3339(9) + * #=> "2001-02-03T04:05:06.123456789+07:00" + */ +static VALUE +dt_lite_rfc3339(int argc, VALUE *argv, VALUE self) +{ + return dt_lite_iso8601(argc, argv, self); +} + +/* + * call-seq: + * dt.jisx0301([n=0]) -> string + * + * Returns a string in a JIS X 0301 format. The optional argument n + * is length of fractional seconds. + * + * DateTime.parse('2001-02-03T04:05:06.123456789+07:00').jisx0301(9) + * #=> "H13.02.03T04:05:06.123456789+07:00" + */ +static VALUE +dt_lite_jisx0301(int argc, VALUE *argv, VALUE self) +{ + VALUE n, s; + + rb_scan_args(argc, argv, "01", &n); + + if (argc < 1) + n = INT2FIX(0); + + { + get_d1(self); + s = jisx0301_date(m_real_local_jd(dat), + m_real_year(dat)); + return rb_str_append(strftimev(RSTRING_PTR(s), self, set_tmx), + iso8601_timediv(self, n)); + } +} + +/* conversions */ + +#define f_getlocal(x) rb_funcall(x, rb_intern("getlocal"), 0) +#define f_subsec(x) rb_funcall(x, rb_intern("subsec"), 0) +#define f_utc_offset(x) rb_funcall(x, rb_intern("utc_offset"), 0) +#define f_local3(x,y,m,d) rb_funcall(x, rb_intern("local"), 3, y, m, d) +#define f_utc6(x,y,m,d,h,min,s) rb_funcall(x, rb_intern("utc"), 6,\ + y, m, d, h, min, s) + +/* + * call-seq: + * t.to_time -> time + * + * Returns a copy of self as local mode. + */ +static VALUE +time_to_time(VALUE self) +{ + return f_getlocal(self); +} + +/* + * call-seq: + * t.to_date -> date + * + * Returns a Date object which denotes self. + */ +static VALUE +time_to_date(VALUE self) +{ + VALUE y, nth, ret; + int ry, m, d; + + y = f_year(self); + m = FIX2INT(f_mon(self)); + d = FIX2INT(f_mday(self)); + + decode_year(y, -1, &nth, &ry); + + ret = d_simple_new_internal(cDate, + nth, 0, + GREGORIAN, + ry, m, d, + HAVE_CIVIL); + { + get_d1(ret); + set_sg(dat, DEFAULT_SG); + } + return ret; +} + +/* + * call-seq: + * t.to_datetime -> datetime + * + * Returns a DateTime object which denotes self. + */ +static VALUE +time_to_datetime(VALUE self) +{ + VALUE y, sf, nth, ret; + int ry, m, d, h, min, s, of; + + y = f_year(self); + m = FIX2INT(f_mon(self)); + d = FIX2INT(f_mday(self)); + + h = FIX2INT(f_hour(self)); + min = FIX2INT(f_min(self)); + s = FIX2INT(f_sec(self)); + if (s == 60) + s = 59; + + sf = sec_to_ns(f_subsec(self)); + of = FIX2INT(f_utc_offset(self)); + + decode_year(y, -1, &nth, &ry); + + ret = d_complex_new_internal(cDateTime, + nth, 0, + 0, sf, + of, DEFAULT_SG, + ry, m, d, + h, min, s, + HAVE_CIVIL | HAVE_TIME); + { + get_d1(ret); + set_sg(dat, DEFAULT_SG); + } + return ret; +} + +/* + * call-seq: + * d.to_time -> time + * + * Returns a Time object which denotes self. + */ +static VALUE +date_to_time(VALUE self) +{ + get_d1(self); + + return f_local3(rb_cTime, + m_real_year(dat), + INT2FIX(m_mon(dat)), + INT2FIX(m_mday(dat))); +} + +/* + * call-seq: + * d.to_date -> self + * + * Returns self; + */ +static VALUE +date_to_date(VALUE self) +{ + return self; +} + +/* + * call-seq: + * d.to_datetime -> datetime + * + * Returns a DateTime object which denotes self. + */ +static VALUE +date_to_datetime(VALUE self) +{ + get_d1a(self); + + if (simple_dat_p(adat)) { + VALUE new = d_lite_s_alloc_simple(cDateTime); + { + get_d1b(new); + bdat->s = adat->s; + return new; + } + } + else { + VALUE new = d_lite_s_alloc_complex(cDateTime); + { + get_d1b(new); + bdat->c = adat->c; + bdat->c.df = 0; + RB_OBJ_WRITE(new, &bdat->c.sf, INT2FIX(0)); +#ifndef USE_PACK + bdat->c.hour = 0; + bdat->c.min = 0; + bdat->c.sec = 0; +#else + bdat->c.pc = PACK5(EX_MON(adat->c.pc), EX_MDAY(adat->c.pc), + 0, 0, 0); + bdat->c.flags |= HAVE_DF | HAVE_TIME; +#endif + return new; + } + } +} + +/* + * call-seq: + * dt.to_time -> time + * + * Returns a Time object which denotes self. + */ +static VALUE +datetime_to_time(VALUE self) +{ + volatile VALUE dup = dup_obj_with_new_offset(self, 0); + { + VALUE t; + + get_d1(dup); + + t = f_utc6(rb_cTime, + m_real_year(dat), + INT2FIX(m_mon(dat)), + INT2FIX(m_mday(dat)), + INT2FIX(m_hour(dat)), + INT2FIX(m_min(dat)), + f_add(INT2FIX(m_sec(dat)), + m_sf_in_sec(dat))); + return f_getlocal(t); + } +} + +/* + * call-seq: + * dt.to_date -> date + * + * Returns a Date object which denotes self. + */ +static VALUE +datetime_to_date(VALUE self) +{ + get_d1a(self); + + if (simple_dat_p(adat)) { + VALUE new = d_lite_s_alloc_simple(cDate); + { + get_d1b(new); + bdat->s = adat->s; + bdat->s.jd = m_local_jd(adat); + return new; + } + } + else { + VALUE new = d_lite_s_alloc_simple(cDate); + { + get_d1b(new); + copy_complex_to_simple(new, &bdat->s, &adat->c) + bdat->s.jd = m_local_jd(adat); + bdat->s.flags &= ~(HAVE_DF | HAVE_TIME | COMPLEX_DAT); + return new; + } + } +} + +/* + * call-seq: + * dt.to_datetime -> self + * + * Returns self. + */ +static VALUE +datetime_to_datetime(VALUE self) +{ + return self; +} + +#ifndef NDEBUG +/* tests */ + +#define MIN_YEAR -4713 +#define MAX_YEAR 1000000 +#define MIN_JD -327 +#define MAX_JD 366963925 + +static int +test_civil(int from, int to, double sg) +{ + int j; + + fprintf(stderr, "test_civil: %d...%d (%d) - %.0f\n", + from, to, to - from, sg); + for (j = from; j <= to; j++) { + int y, m, d, rj, ns; + + c_jd_to_civil(j, sg, &y, &m, &d); + c_civil_to_jd(y, m, d, sg, &rj, &ns); + if (j != rj) { + fprintf(stderr, "%d != %d\n", j, rj); + return 0; + } + } + return 1; +} + +static VALUE +date_s_test_civil(VALUE klass) +{ + if (!test_civil(MIN_JD, MIN_JD + 366, GREGORIAN)) + return Qfalse; + if (!test_civil(2305814, 2598007, GREGORIAN)) + return Qfalse; + if (!test_civil(MAX_JD - 366, MAX_JD, GREGORIAN)) + return Qfalse; + + if (!test_civil(MIN_JD, MIN_JD + 366, ITALY)) + return Qfalse; + if (!test_civil(2305814, 2598007, ITALY)) + return Qfalse; + if (!test_civil(MAX_JD - 366, MAX_JD, ITALY)) + return Qfalse; + + return Qtrue; +} + +static int +test_ordinal(int from, int to, double sg) +{ + int j; + + fprintf(stderr, "test_ordinal: %d...%d (%d) - %.0f\n", + from, to, to - from, sg); + for (j = from; j <= to; j++) { + int y, d, rj, ns; + + c_jd_to_ordinal(j, sg, &y, &d); + c_ordinal_to_jd(y, d, sg, &rj, &ns); + if (j != rj) { + fprintf(stderr, "%d != %d\n", j, rj); + return 0; + } + } + return 1; +} + +static VALUE +date_s_test_ordinal(VALUE klass) +{ + if (!test_ordinal(MIN_JD, MIN_JD + 366, GREGORIAN)) + return Qfalse; + if (!test_ordinal(2305814, 2598007, GREGORIAN)) + return Qfalse; + if (!test_ordinal(MAX_JD - 366, MAX_JD, GREGORIAN)) + return Qfalse; + + if (!test_ordinal(MIN_JD, MIN_JD + 366, ITALY)) + return Qfalse; + if (!test_ordinal(2305814, 2598007, ITALY)) + return Qfalse; + if (!test_ordinal(MAX_JD - 366, MAX_JD, ITALY)) + return Qfalse; + + return Qtrue; +} + +static int +test_commercial(int from, int to, double sg) +{ + int j; + + fprintf(stderr, "test_commercial: %d...%d (%d) - %.0f\n", + from, to, to - from, sg); + for (j = from; j <= to; j++) { + int y, w, d, rj, ns; + + c_jd_to_commercial(j, sg, &y, &w, &d); + c_commercial_to_jd(y, w, d, sg, &rj, &ns); + if (j != rj) { + fprintf(stderr, "%d != %d\n", j, rj); + return 0; + } + } + return 1; +} + +static VALUE +date_s_test_commercial(VALUE klass) +{ + if (!test_commercial(MIN_JD, MIN_JD + 366, GREGORIAN)) + return Qfalse; + if (!test_commercial(2305814, 2598007, GREGORIAN)) + return Qfalse; + if (!test_commercial(MAX_JD - 366, MAX_JD, GREGORIAN)) + return Qfalse; + + if (!test_commercial(MIN_JD, MIN_JD + 366, ITALY)) + return Qfalse; + if (!test_commercial(2305814, 2598007, ITALY)) + return Qfalse; + if (!test_commercial(MAX_JD - 366, MAX_JD, ITALY)) + return Qfalse; + + return Qtrue; +} + +static int +test_weeknum(int from, int to, int f, double sg) +{ + int j; + + fprintf(stderr, "test_weeknum: %d...%d (%d) - %.0f\n", + from, to, to - from, sg); + for (j = from; j <= to; j++) { + int y, w, d, rj, ns; + + c_jd_to_weeknum(j, f, sg, &y, &w, &d); + c_weeknum_to_jd(y, w, d, f, sg, &rj, &ns); + if (j != rj) { + fprintf(stderr, "%d != %d\n", j, rj); + return 0; + } + } + return 1; +} + +static VALUE +date_s_test_weeknum(VALUE klass) +{ + int f; + + for (f = 0; f <= 1; f++) { + if (!test_weeknum(MIN_JD, MIN_JD + 366, f, GREGORIAN)) + return Qfalse; + if (!test_weeknum(2305814, 2598007, f, GREGORIAN)) + return Qfalse; + if (!test_weeknum(MAX_JD - 366, MAX_JD, f, GREGORIAN)) + return Qfalse; + + if (!test_weeknum(MIN_JD, MIN_JD + 366, f, ITALY)) + return Qfalse; + if (!test_weeknum(2305814, 2598007, f, ITALY)) + return Qfalse; + if (!test_weeknum(MAX_JD - 366, MAX_JD, f, ITALY)) + return Qfalse; + } + + return Qtrue; +} + +static int +test_nth_kday(int from, int to, double sg) +{ + int j; + + fprintf(stderr, "test_nth_kday: %d...%d (%d) - %.0f\n", + from, to, to - from, sg); + for (j = from; j <= to; j++) { + int y, m, n, k, rj, ns; + + c_jd_to_nth_kday(j, sg, &y, &m, &n, &k); + c_nth_kday_to_jd(y, m, n, k, sg, &rj, &ns); + if (j != rj) { + fprintf(stderr, "%d != %d\n", j, rj); + return 0; + } + } + return 1; +} + +static VALUE +date_s_test_nth_kday(VALUE klass) +{ + if (!test_nth_kday(MIN_JD, MIN_JD + 366, GREGORIAN)) + return Qfalse; + if (!test_nth_kday(2305814, 2598007, GREGORIAN)) + return Qfalse; + if (!test_nth_kday(MAX_JD - 366, MAX_JD, GREGORIAN)) + return Qfalse; + + if (!test_nth_kday(MIN_JD, MIN_JD + 366, ITALY)) + return Qfalse; + if (!test_nth_kday(2305814, 2598007, ITALY)) + return Qfalse; + if (!test_nth_kday(MAX_JD - 366, MAX_JD, ITALY)) + return Qfalse; + + return Qtrue; +} + +static int +test_unit_v2v(VALUE i, + VALUE (* conv1)(VALUE), + VALUE (* conv2)(VALUE)) +{ + VALUE c, o; + c = (*conv1)(i); + o = (*conv2)(c); + return f_eqeq_p(o, i); +} + +static int +test_unit_v2v_iter2(VALUE (* conv1)(VALUE), + VALUE (* conv2)(VALUE)) +{ + if (!test_unit_v2v(INT2FIX(0), conv1, conv2)) + return 0; + if (!test_unit_v2v(INT2FIX(1), conv1, conv2)) + return 0; + if (!test_unit_v2v(INT2FIX(2), conv1, conv2)) + return 0; + if (!test_unit_v2v(INT2FIX(3), conv1, conv2)) + return 0; + if (!test_unit_v2v(INT2FIX(11), conv1, conv2)) + return 0; + if (!test_unit_v2v(INT2FIX(65535), conv1, conv2)) + return 0; + if (!test_unit_v2v(INT2FIX(1073741823), conv1, conv2)) + return 0; + if (!test_unit_v2v(INT2NUM(1073741824), conv1, conv2)) + return 0; + if (!test_unit_v2v(rb_rational_new2(INT2FIX(0), INT2FIX(1)), conv1, conv2)) + return 0; + if (!test_unit_v2v(rb_rational_new2(INT2FIX(1), INT2FIX(1)), conv1, conv2)) + return 0; + if (!test_unit_v2v(rb_rational_new2(INT2FIX(1), INT2FIX(2)), conv1, conv2)) + return 0; + if (!test_unit_v2v(rb_rational_new2(INT2FIX(2), INT2FIX(3)), conv1, conv2)) + return 0; + return 1; +} + +static int +test_unit_v2v_iter(VALUE (* conv1)(VALUE), + VALUE (* conv2)(VALUE)) +{ + if (!test_unit_v2v_iter2(conv1, conv2)) + return 0; + if (!test_unit_v2v_iter2(conv2, conv1)) + return 0; + return 1; +} + +static VALUE +date_s_test_unit_conv(VALUE klass) +{ + if (!test_unit_v2v_iter(sec_to_day, day_to_sec)) + return Qfalse; + if (!test_unit_v2v_iter(ms_to_sec, sec_to_ms)) + return Qfalse; + if (!test_unit_v2v_iter(ns_to_day, day_to_ns)) + return Qfalse; + if (!test_unit_v2v_iter(ns_to_sec, sec_to_ns)) + return Qfalse; + return Qtrue; +} + +static VALUE +date_s_test_all(VALUE klass) +{ + if (date_s_test_civil(klass) == Qfalse) + return Qfalse; + if (date_s_test_ordinal(klass) == Qfalse) + return Qfalse; + if (date_s_test_commercial(klass) == Qfalse) + return Qfalse; + if (date_s_test_weeknum(klass) == Qfalse) + return Qfalse; + if (date_s_test_nth_kday(klass) == Qfalse) + return Qfalse; + if (date_s_test_unit_conv(klass) == Qfalse) + return Qfalse; + return Qtrue; +} +#endif + +static const char *monthnames[] = { + NULL, + "January", "February", "March", + "April", "May", "June", + "July", "August", "September", + "October", "November", "December" +}; + +static const char *abbr_monthnames[] = { + NULL, + "Jan", "Feb", "Mar", "Apr", + "May", "Jun", "Jul", "Aug", + "Sep", "Oct", "Nov", "Dec" +}; + +static const char *daynames[] = { + "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday" +}; + +static const char *abbr_daynames[] = { + "Sun", "Mon", "Tue", "Wed", + "Thu", "Fri", "Sat" +}; + +static VALUE +mk_ary_of_str(long len, const char *a[]) +{ + VALUE o; + long i; + + o = rb_ary_new2(len); + for (i = 0; i < len; i++) { + VALUE e; + + if (!a[i]) + e = Qnil; + else { + e = rb_usascii_str_new2(a[i]); + rb_obj_freeze(e); + } + rb_ary_push(o, e); + } + rb_obj_freeze(o); + return o; +} + +void +Init_date_core(void) +{ +#undef rb_intern +#define rb_intern(str) rb_intern_const(str) + + assert(fprintf(stderr, "assert() is now active\n")); + + id_cmp = rb_intern("<=>"); + id_le_p = rb_intern("<="); + id_ge_p = rb_intern(">="); + id_eqeq_p = rb_intern("=="); + + half_days_in_day = rb_rational_new2(INT2FIX(1), INT2FIX(2)); + +#if (LONG_MAX / DAY_IN_SECONDS) > SECOND_IN_NANOSECONDS + day_in_nanoseconds = LONG2NUM((long)DAY_IN_SECONDS * + SECOND_IN_NANOSECONDS); +#elif defined HAVE_LONG_LONG + day_in_nanoseconds = LL2NUM((LONG_LONG)DAY_IN_SECONDS * + SECOND_IN_NANOSECONDS); +#else + day_in_nanoseconds = f_mul(INT2FIX(DAY_IN_SECONDS), + INT2FIX(SECOND_IN_NANOSECONDS)); +#endif + + rb_gc_register_mark_object(half_days_in_day); + rb_gc_register_mark_object(day_in_nanoseconds); + + positive_inf = +INFINITY; + negative_inf = -INFINITY; + + /* + * date and datetime class - Tadayoshi Funaba 1998-2011 + * + * 'date' provides two classes Date and DateTime. + * + * == Terms and definitions + * + * Some terms and definitions are based on ISO 8601 and JIS X 0301. + * + * === calendar date + * + * The calendar date is a particular day of a calendar year, + * identified by its ordinal number within a calendar month within + * that year. + * + * In those classes, this is so-called "civil". + * + * === ordinal date + * + * The ordinal date is a particular day of a calendar year identified + * by its ordinal number within the year. + * + * In those classes, this is so-called "ordinal". + * + * === week date + * + * The week date is a date identified by calendar week and day numbers. + * + * The calendar week is a seven day period within a calendar year, + * starting on a Monday and identified by its ordinal number within + * the year; the first calendar week of the year is the one that + * includes the first Thursday of that year. In the Gregorian + * calendar, this is equivalent to the week which includes January 4. + * + * In those classes, this so-called "commercial". + * + * === julian day number + * + * The Julian day number is in elapsed days since noon (Greenwich mean + * time) on January 1, 4713 BCE (in the Julian calendar). + * + * In this document, the astronomical Julian day number is same as the + * original Julian day number. And the chronological Julian day + * number is a variation of the Julian day number. Its days begin at + * midnight on local time. + * + * In this document, when the term "Julian day number" simply appears, + * it just refers to "chronological Julian day number", not the + * original. + * + * In those classes, those are so-called "ajd" and "jd". + * + * === modified julian day number + * + * The modified Julian day number is in elapsed days since midnight + * (Coordinated universal time) on November 17, 1858 CE (in the + * Gregorian calendar). + * + * In this document, the astronomical modified Julian day number is + * same as the original modified Julian day number. And the + * chronological modified Julian day number is a variation of the + * modified Julian day number. Its days begin at midnight on local + * time. + * + * In this document, when the term "modified Julian day number" simply + * appears, it just refers to "chronological modified Julian day + * number", not the original. + * + * In those classes, this is so-called "mjd". + * + * == Date + * + * A subclass of Object that includes Comparable module and easily handles + * date. + * + * Date object is created with Date::new, Date::jd, Date::ordinal, + * Date::commercial, Date::parse, Date::strptime, Date::today, + * Time#to_date or etc. + * + * require 'date' + * + * Date.new(2001,2,3) + * #=> # + * Date.jd(2451944) + * #=> # + * Date.ordinal(2001,34) + * #=> # + * Date.commercial(2001,5,6) + * #=> # + * Date.parse('2001-02-03') + * #=> # + * Date.strptime('03-02-2001', '%d-%m-%Y') + * #=> # + * Time.new(2001,2,3).to_date + * #=> # + * + * All date objects are immutable; hence cannot modify themselves. + * + * The concept of this date object can be represented as a tuple + * of the day count, the offset and the day of calendar reform. + * + * The day count denotes the absolute position of a temporal + * dimension. The offset is relative adjustment, which determines + * decoded local time with the day count. The day of calendar + * reform denotes the start day of the new style. The old style + * of the West is the Julian calendar which was adopted by + * Caesar. The new style is the Gregorian calendar, which is the + * current civil calendar of many countries. + * + * The day count is virtually the astronomical Julian day number. + * The offset in this class is usually zero, and cannot be + * specified directly. + * + * An optional argument the day of calendar reform (start) as a + * Julian day number, which should be 2298874 to 2426355 or -/+oo. + * The default value is Date::ITALY (2299161=1582-10-15). See + * also sample/cal.rb. + * + * $ ruby sample/cal.rb -c it 10 1582 + * October 1582 + * S M Tu W Th F S + * 1 2 3 4 15 16 + * 17 18 19 20 21 22 23 + * 24 25 26 27 28 29 30 + * 31 + * + * $ ruby sample/cal.rb -c gb 9 1752 + * September 1752 + * S M Tu W Th F S + * 1 2 14 15 16 + * 17 18 19 20 21 22 23 + * 24 25 26 27 28 29 30 + * + * Date object has various methods. See each reference. + * + * d = Date.parse('3rd Feb 2001') + * #=> # + * d.year #=> 2001 + * d.mon #=> 2 + * d.mday #=> 3 + * d.wday #=> 6 + * d += 1 #=> # + * d.strftime('%a %d %b %Y') #=> "Sun 04 Feb 2001" + * + * + * == DateTime + * + * A subclass of Date that easily handles date, hour, minute, second and + * offset. + * + * DateTime does not consider any leap seconds, does not track + * any summer time rules. + * + * DateTime object is created with DateTime::new, DateTime::jd, + * DateTime::ordinal, DateTime::commercial, DateTime::parse, + * DateTime::strptime, DateTime::now, Time#to_datetime or etc. + * + * require 'date' + * + * DateTime.new(2001,2,3,4,5,6) + * #=> # + * + * The last element of day, hour, minute or second can be + * fractional number. The fractional number's precision is assumed + * at most nanosecond. + * + * DateTime.new(2001,2,3.5) + * #=> # + * + * An optional argument the offset indicates the difference + * between the local time and UTC. For example, Rational(3,24) + * represents ahead of 3 hours of UTC, Rational(-5,24) represents + * behind of 5 hours of UTC. The offset should be -1 to +1, and + * its precision is assumed at most second. The default value is + * zero(equals to UTC). + * + * DateTime.new(2001,2,3,4,5,6,Rational(3,24)) + * #=> # + * + * also accepts string form. + * + * DateTime.new(2001,2,3,4,5,6,'+03:00') + * #=> # + * + * An optional argument the day of calendar reform (start) denotes + * a Julian day number, which should be 2298874 to 2426355 or + * -/+oo. The default value is Date::ITALY (2299161=1582-10-15). + * + * DateTime object has various methods. See each reference. + * + * d = DateTime.parse('3rd Feb 2001 04:05:06+03:30') + * #=> # + * d.hour #=> 4 + * d.min #=> 5 + * d.sec #=> 6 + * d.offset #=> (7/48) + * d.zone #=> "+03:30" + * d += Rational('1.5') + * #=> # + * d = d.new_offset('+09:00') + * #=> # + * d.strftime('%I:%M:%S %p') + * #=> "09:35:06 PM" + * d > DateTime.new(1999) + * #=> true + */ + cDate = rb_define_class("Date", rb_cObject); + + rb_include_module(cDate, rb_mComparable); + + /* An array of strings of full month names in English. The first + * element is nil. + */ + rb_define_const(cDate, "MONTHNAMES", mk_ary_of_str(13, monthnames)); + + /* An array of strings of abbreviated month names in English. The + * first element is nil. + */ + rb_define_const(cDate, "ABBR_MONTHNAMES", + mk_ary_of_str(13, abbr_monthnames)); + + /* An array of strings of the full names of days of the week in English. + * The first is "Sunday". + */ + rb_define_const(cDate, "DAYNAMES", mk_ary_of_str(7, daynames)); + + /* An array of strings of abbreviated day names in English. The + * first is "Sun". + */ + rb_define_const(cDate, "ABBR_DAYNAMES", mk_ary_of_str(7, abbr_daynames)); + + /* The Julian day number of the day of calendar reform for Italy + * and some catholic countries. + */ + rb_define_const(cDate, "ITALY", INT2FIX(ITALY)); + + /* The Julian day number of the day of calendar reform for England + * and her colonies. + */ + rb_define_const(cDate, "ENGLAND", INT2FIX(ENGLAND)); + + /* The Julian day number of the day of calendar reform for the + * proleptic Julian calendar + */ + rb_define_const(cDate, "JULIAN", DBL2NUM(JULIAN)); + + /* The Julian day number of the day of calendar reform for the + * proleptic Gregorian calendar + */ + rb_define_const(cDate, "GREGORIAN", DBL2NUM(GREGORIAN)); + + rb_define_alloc_func(cDate, d_lite_s_alloc); + +#ifndef NDEBUG +#define de_define_private_method rb_define_private_method + de_define_private_method(CLASS_OF(cDate), "_valid_jd?", + date_s__valid_jd_p, -1); + de_define_private_method(CLASS_OF(cDate), "_valid_ordinal?", + date_s__valid_ordinal_p, -1); + de_define_private_method(CLASS_OF(cDate), "_valid_civil?", + date_s__valid_civil_p, -1); + de_define_private_method(CLASS_OF(cDate), "_valid_date?", + date_s__valid_civil_p, -1); + de_define_private_method(CLASS_OF(cDate), "_valid_commercial?", + date_s__valid_commercial_p, -1); + de_define_private_method(CLASS_OF(cDate), "_valid_weeknum?", + date_s__valid_weeknum_p, -1); + de_define_private_method(CLASS_OF(cDate), "_valid_nth_kday?", + date_s__valid_nth_kday_p, -1); +#endif + + rb_define_singleton_method(cDate, "valid_jd?", date_s_valid_jd_p, -1); + rb_define_singleton_method(cDate, "valid_ordinal?", + date_s_valid_ordinal_p, -1); + rb_define_singleton_method(cDate, "valid_civil?", date_s_valid_civil_p, -1); + rb_define_singleton_method(cDate, "valid_date?", date_s_valid_civil_p, -1); + rb_define_singleton_method(cDate, "valid_commercial?", + date_s_valid_commercial_p, -1); + +#ifndef NDEBUG + de_define_private_method(CLASS_OF(cDate), "valid_weeknum?", + date_s_valid_weeknum_p, -1); + de_define_private_method(CLASS_OF(cDate), "valid_nth_kday?", + date_s_valid_nth_kday_p, -1); + de_define_private_method(CLASS_OF(cDate), "zone_to_diff", + date_s_zone_to_diff, 1); +#endif + + rb_define_singleton_method(cDate, "julian_leap?", date_s_julian_leap_p, 1); + rb_define_singleton_method(cDate, "gregorian_leap?", + date_s_gregorian_leap_p, 1); + rb_define_singleton_method(cDate, "leap?", + date_s_gregorian_leap_p, 1); + +#ifndef NDEBUG +#define de_define_singleton_method rb_define_singleton_method +#define de_define_alias rb_define_alias + de_define_singleton_method(cDate, "new!", date_s_new_bang, -1); + de_define_alias(rb_singleton_class(cDate), "new_l!", "new"); +#endif + + rb_define_singleton_method(cDate, "jd", date_s_jd, -1); + rb_define_singleton_method(cDate, "ordinal", date_s_ordinal, -1); + rb_define_singleton_method(cDate, "civil", date_s_civil, -1); + rb_define_singleton_method(cDate, "new", date_s_civil, -1); + rb_define_singleton_method(cDate, "commercial", date_s_commercial, -1); + +#ifndef NDEBUG + de_define_singleton_method(cDate, "weeknum", date_s_weeknum, -1); + de_define_singleton_method(cDate, "nth_kday", date_s_nth_kday, -1); +#endif + + rb_define_singleton_method(cDate, "today", date_s_today, -1); + rb_define_singleton_method(cDate, "_strptime", date_s__strptime, -1); + rb_define_singleton_method(cDate, "strptime", date_s_strptime, -1); + rb_define_singleton_method(cDate, "_parse", date_s__parse, -1); + rb_define_singleton_method(cDate, "parse", date_s_parse, -1); + rb_define_singleton_method(cDate, "_iso8601", date_s__iso8601, 1); + rb_define_singleton_method(cDate, "iso8601", date_s_iso8601, -1); + rb_define_singleton_method(cDate, "_rfc3339", date_s__rfc3339, 1); + rb_define_singleton_method(cDate, "rfc3339", date_s_rfc3339, -1); + rb_define_singleton_method(cDate, "_xmlschema", date_s__xmlschema, 1); + rb_define_singleton_method(cDate, "xmlschema", date_s_xmlschema, -1); + rb_define_singleton_method(cDate, "_rfc2822", date_s__rfc2822, 1); + rb_define_singleton_method(cDate, "_rfc822", date_s__rfc2822, 1); + rb_define_singleton_method(cDate, "rfc2822", date_s_rfc2822, -1); + rb_define_singleton_method(cDate, "rfc822", date_s_rfc2822, -1); + rb_define_singleton_method(cDate, "_httpdate", date_s__httpdate, 1); + rb_define_singleton_method(cDate, "httpdate", date_s_httpdate, -1); + rb_define_singleton_method(cDate, "_jisx0301", date_s__jisx0301, 1); + rb_define_singleton_method(cDate, "jisx0301", date_s_jisx0301, -1); + +#ifndef NDEBUG +#define de_define_method rb_define_method + de_define_method(cDate, "initialize", d_lite_initialize, -1); +#endif + rb_define_method(cDate, "initialize_copy", d_lite_initialize_copy, 1); + +#ifndef NDEBUG + de_define_method(cDate, "fill", d_lite_fill, 0); +#endif + + rb_define_method(cDate, "ajd", d_lite_ajd, 0); + rb_define_method(cDate, "amjd", d_lite_amjd, 0); + rb_define_method(cDate, "jd", d_lite_jd, 0); + rb_define_method(cDate, "mjd", d_lite_mjd, 0); + rb_define_method(cDate, "ld", d_lite_ld, 0); + + rb_define_method(cDate, "year", d_lite_year, 0); + rb_define_method(cDate, "yday", d_lite_yday, 0); + rb_define_method(cDate, "mon", d_lite_mon, 0); + rb_define_method(cDate, "month", d_lite_mon, 0); + rb_define_method(cDate, "mday", d_lite_mday, 0); + rb_define_method(cDate, "day", d_lite_mday, 0); + rb_define_method(cDate, "day_fraction", d_lite_day_fraction, 0); + + rb_define_method(cDate, "cwyear", d_lite_cwyear, 0); + rb_define_method(cDate, "cweek", d_lite_cweek, 0); + rb_define_method(cDate, "cwday", d_lite_cwday, 0); + +#ifndef NDEBUG + de_define_private_method(cDate, "wnum0", d_lite_wnum0, 0); + de_define_private_method(cDate, "wnum1", d_lite_wnum1, 0); +#endif + + rb_define_method(cDate, "wday", d_lite_wday, 0); + + rb_define_method(cDate, "sunday?", d_lite_sunday_p, 0); + rb_define_method(cDate, "monday?", d_lite_monday_p, 0); + rb_define_method(cDate, "tuesday?", d_lite_tuesday_p, 0); + rb_define_method(cDate, "wednesday?", d_lite_wednesday_p, 0); + rb_define_method(cDate, "thursday?", d_lite_thursday_p, 0); + rb_define_method(cDate, "friday?", d_lite_friday_p, 0); + rb_define_method(cDate, "saturday?", d_lite_saturday_p, 0); + +#ifndef NDEBUG + de_define_method(cDate, "nth_kday?", d_lite_nth_kday_p, 2); +#endif + + rb_define_private_method(cDate, "hour", d_lite_hour, 0); + rb_define_private_method(cDate, "min", d_lite_min, 0); + rb_define_private_method(cDate, "minute", d_lite_min, 0); + rb_define_private_method(cDate, "sec", d_lite_sec, 0); + rb_define_private_method(cDate, "second", d_lite_sec, 0); + rb_define_private_method(cDate, "sec_fraction", d_lite_sec_fraction, 0); + rb_define_private_method(cDate, "second_fraction", d_lite_sec_fraction, 0); + rb_define_private_method(cDate, "offset", d_lite_offset, 0); + rb_define_private_method(cDate, "zone", d_lite_zone, 0); + + rb_define_method(cDate, "julian?", d_lite_julian_p, 0); + rb_define_method(cDate, "gregorian?", d_lite_gregorian_p, 0); + rb_define_method(cDate, "leap?", d_lite_leap_p, 0); + + rb_define_method(cDate, "start", d_lite_start, 0); + rb_define_method(cDate, "new_start", d_lite_new_start, -1); + rb_define_method(cDate, "italy", d_lite_italy, 0); + rb_define_method(cDate, "england", d_lite_england, 0); + rb_define_method(cDate, "julian", d_lite_julian, 0); + rb_define_method(cDate, "gregorian", d_lite_gregorian, 0); + + rb_define_private_method(cDate, "new_offset", d_lite_new_offset, -1); + + rb_define_method(cDate, "+", d_lite_plus, 1); + rb_define_method(cDate, "-", d_lite_minus, 1); + + rb_define_method(cDate, "next_day", d_lite_next_day, -1); + rb_define_method(cDate, "prev_day", d_lite_prev_day, -1); + rb_define_method(cDate, "next", d_lite_next, 0); + rb_define_method(cDate, "succ", d_lite_next, 0); + + rb_define_method(cDate, ">>", d_lite_rshift, 1); + rb_define_method(cDate, "<<", d_lite_lshift, 1); + + rb_define_method(cDate, "next_month", d_lite_next_month, -1); + rb_define_method(cDate, "prev_month", d_lite_prev_month, -1); + rb_define_method(cDate, "next_year", d_lite_next_year, -1); + rb_define_method(cDate, "prev_year", d_lite_prev_year, -1); + + rb_define_method(cDate, "step", d_lite_step, -1); + rb_define_method(cDate, "upto", d_lite_upto, 1); + rb_define_method(cDate, "downto", d_lite_downto, 1); + + rb_define_method(cDate, "<=>", d_lite_cmp, 1); + rb_define_method(cDate, "===", d_lite_equal, 1); + rb_define_method(cDate, "eql?", d_lite_eql_p, 1); + rb_define_method(cDate, "hash", d_lite_hash, 0); + + rb_define_method(cDate, "to_s", d_lite_to_s, 0); +#ifndef NDEBUG + de_define_method(cDate, "inspect_raw", d_lite_inspect_raw, 0); +#endif + rb_define_method(cDate, "inspect", d_lite_inspect, 0); + + rb_define_method(cDate, "strftime", d_lite_strftime, -1); + + rb_define_method(cDate, "asctime", d_lite_asctime, 0); + rb_define_method(cDate, "ctime", d_lite_asctime, 0); + rb_define_method(cDate, "iso8601", d_lite_iso8601, 0); + rb_define_method(cDate, "xmlschema", d_lite_iso8601, 0); + rb_define_method(cDate, "rfc3339", d_lite_rfc3339, 0); + rb_define_method(cDate, "rfc2822", d_lite_rfc2822, 0); + rb_define_method(cDate, "rfc822", d_lite_rfc2822, 0); + rb_define_method(cDate, "httpdate", d_lite_httpdate, 0); + rb_define_method(cDate, "jisx0301", d_lite_jisx0301, 0); + +#ifndef NDEBUG + de_define_method(cDate, "marshal_dump_old", d_lite_marshal_dump_old, 0); +#endif + rb_define_method(cDate, "marshal_dump", d_lite_marshal_dump, 0); + rb_define_method(cDate, "marshal_load", d_lite_marshal_load, 1); + rb_define_singleton_method(cDate, "_load", date_s__load, 1); + + /* datetime */ + + cDateTime = rb_define_class("DateTime", cDate); + + rb_define_singleton_method(cDateTime, "jd", datetime_s_jd, -1); + rb_define_singleton_method(cDateTime, "ordinal", datetime_s_ordinal, -1); + rb_define_singleton_method(cDateTime, "civil", datetime_s_civil, -1); + rb_define_singleton_method(cDateTime, "new", datetime_s_civil, -1); + rb_define_singleton_method(cDateTime, "commercial", + datetime_s_commercial, -1); + +#ifndef NDEBUG + de_define_singleton_method(cDateTime, "weeknum", + datetime_s_weeknum, -1); + de_define_singleton_method(cDateTime, "nth_kday", + datetime_s_nth_kday, -1); +#endif + + rb_undef_method(CLASS_OF(cDateTime), "today"); + + rb_define_singleton_method(cDateTime, "now", datetime_s_now, -1); + rb_define_singleton_method(cDateTime, "_strptime", + datetime_s__strptime, -1); + rb_define_singleton_method(cDateTime, "strptime", + datetime_s_strptime, -1); + rb_define_singleton_method(cDateTime, "parse", + datetime_s_parse, -1); + rb_define_singleton_method(cDateTime, "iso8601", + datetime_s_iso8601, -1); + rb_define_singleton_method(cDateTime, "rfc3339", + datetime_s_rfc3339, -1); + rb_define_singleton_method(cDateTime, "xmlschema", + datetime_s_xmlschema, -1); + rb_define_singleton_method(cDateTime, "rfc2822", + datetime_s_rfc2822, -1); + rb_define_singleton_method(cDateTime, "rfc822", + datetime_s_rfc2822, -1); + rb_define_singleton_method(cDateTime, "httpdate", + datetime_s_httpdate, -1); + rb_define_singleton_method(cDateTime, "jisx0301", + datetime_s_jisx0301, -1); + +#define f_public(m,s) rb_funcall(m, rb_intern("public"), 1,\ + ID2SYM(rb_intern(s))) + + f_public(cDateTime, "hour"); + f_public(cDateTime, "min"); + f_public(cDateTime, "minute"); + f_public(cDateTime, "sec"); + f_public(cDateTime, "second"); + f_public(cDateTime, "sec_fraction"); + f_public(cDateTime, "second_fraction"); + f_public(cDateTime, "offset"); + f_public(cDateTime, "zone"); + f_public(cDateTime, "new_offset"); + + rb_define_method(cDateTime, "to_s", dt_lite_to_s, 0); + + rb_define_method(cDateTime, "strftime", dt_lite_strftime, -1); + + rb_define_method(cDateTime, "iso8601", dt_lite_iso8601, -1); + rb_define_method(cDateTime, "xmlschema", dt_lite_iso8601, -1); + rb_define_method(cDateTime, "rfc3339", dt_lite_rfc3339, -1); + rb_define_method(cDateTime, "jisx0301", dt_lite_jisx0301, -1); + + /* conversions */ + + rb_define_method(rb_cTime, "to_time", time_to_time, 0); + rb_define_method(rb_cTime, "to_date", time_to_date, 0); + rb_define_method(rb_cTime, "to_datetime", time_to_datetime, 0); + + rb_define_method(cDate, "to_time", date_to_time, 0); + rb_define_method(cDate, "to_date", date_to_date, 0); + rb_define_method(cDate, "to_datetime", date_to_datetime, 0); + + rb_define_method(cDateTime, "to_time", datetime_to_time, 0); + rb_define_method(cDateTime, "to_date", datetime_to_date, 0); + rb_define_method(cDateTime, "to_datetime", datetime_to_datetime, 0); + +#ifndef NDEBUG + /* tests */ + + de_define_singleton_method(cDate, "test_civil", date_s_test_civil, 0); + de_define_singleton_method(cDate, "test_ordinal", date_s_test_ordinal, 0); + de_define_singleton_method(cDate, "test_commercial", + date_s_test_commercial, 0); + de_define_singleton_method(cDate, "test_weeknum", date_s_test_weeknum, 0); + de_define_singleton_method(cDate, "test_nth_kday", date_s_test_nth_kday, 0); + de_define_singleton_method(cDate, "test_unit_conv", + date_s_test_unit_conv, 0); + de_define_singleton_method(cDate, "test_all", date_s_test_all, 0); +#endif +} + +/* +Local variables: +c-file-style: "ruby" +End: +*/ -- cgit v1.2.3