root/ext/date/date_core.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. f_cmp
  2. f_lt_p
  3. f_gt_p
  4. f_le_p
  5. f_ge_p
  6. f_eqeq_p
  7. f_zero_p
  8. f_negative_p
  9. canon
  10. c_find_fdoy
  11. c_find_ldoy
  12. c_find_fdom
  13. c_find_ldom
  14. c_civil_to_jd
  15. c_jd_to_civil
  16. c_ordinal_to_jd
  17. c_jd_to_ordinal
  18. c_commercial_to_jd
  19. c_jd_to_commercial
  20. c_weeknum_to_jd
  21. c_jd_to_weeknum
  22. c_nth_kday_to_jd
  23. c_jd_to_wday
  24. c_jd_to_nth_kday
  25. c_valid_ordinal_p
  26. c_julian_leap_p
  27. c_gregorian_leap_p
  28. c_julian_last_day_of_month
  29. c_gregorian_last_day_of_month
  30. c_valid_julian_p
  31. c_valid_gregorian_p
  32. c_valid_civil_p
  33. c_valid_commercial_p
  34. c_valid_weeknum_p
  35. c_valid_nth_kday_p
  36. c_valid_time_p
  37. c_valid_start_p
  38. df_local_to_utc
  39. df_utc_to_local
  40. jd_local_to_utc
  41. jd_utc_to_local
  42. time_to_df
  43. df_to_time
  44. sec_to_day
  45. isec_to_day
  46. ns_to_day
  47. ms_to_sec
  48. ns_to_sec
  49. ins_to_day
  50. safe_mul_p
  51. day_to_sec
  52. day_to_ns
  53. sec_to_ms
  54. sec_to_ns
  55. isec_to_ns
  56. div_day
  57. div_df
  58. div_sf
  59. decode_day
  60. s_virtual_sg
  61. c_virtual_sg
  62. m_virtual_sg
  63. canonicalize_s_jd
  64. get_s_jd
  65. get_s_civil
  66. get_c_df
  67. get_c_time
  68. canonicalize_c_jd
  69. get_c_jd
  70. get_c_civil
  71. local_jd
  72. local_df
  73. decode_year
  74. encode_year
  75. decode_jd
  76. encode_jd
  77. guess_style
  78. m_canonicalize_jd
  79. m_nth
  80. m_jd
  81. m_real_jd
  82. m_local_jd
  83. m_real_local_jd
  84. m_df
  85. m_df_in_day
  86. m_local_df
  87. m_local_df_in_day
  88. m_sf
  89. m_sf_in_day
  90. m_sf_in_sec
  91. m_fr
  92. m_ajd
  93. m_amjd
  94. m_of
  95. m_of_in_day
  96. m_sg
  97. m_julian_p
  98. m_gregorian_p
  99. m_proleptic_julian_p
  100. m_proleptic_gregorian_p
  101. m_year
  102. m_real_year
  103. m_mon
  104. m_mday
  105. c_julian_to_yday
  106. c_gregorian_to_yday
  107. m_yday
  108. m_wday
  109. m_cwyear
  110. m_real_cwyear
  111. m_cweek
  112. m_cwday
  113. m_wnumx
  114. m_wnum0
  115. m_wnum1
  116. m_hour
  117. m_min
  118. m_sec
  119. of2str
  120. m_zone
  121. f_kind_of_p
  122. k_date_p
  123. k_numeric_p
  124. k_rational_p
  125. expect_numeric
  126. civil_to_jd
  127. jd_to_civil
  128. ordinal_to_jd
  129. jd_to_ordinal
  130. commercial_to_jd
  131. jd_to_commercial
  132. weeknum_to_jd
  133. jd_to_weeknum
  134. nth_kday_to_jd
  135. jd_to_nth_kday
  136. valid_ordinal_p
  137. valid_gregorian_p
  138. valid_civil_p
  139. valid_commercial_p
  140. valid_weeknum_p
  141. valid_nth_kday_p
  142. offset_to_sec
  143. valid_jd_sub
  144. date_s__valid_jd_p
  145. date_s_valid_jd_p
  146. valid_civil_sub
  147. date_s__valid_civil_p
  148. date_s_valid_civil_p
  149. valid_ordinal_sub
  150. date_s__valid_ordinal_p
  151. date_s_valid_ordinal_p
  152. valid_commercial_sub
  153. date_s__valid_commercial_p
  154. date_s_valid_commercial_p
  155. valid_weeknum_sub
  156. date_s__valid_weeknum_p
  157. date_s_valid_weeknum_p
  158. valid_nth_kday_sub
  159. date_s__valid_nth_kday_p
  160. date_s_valid_nth_kday_p
  161. date_s_zone_to_diff
  162. date_s_julian_leap_p
  163. date_s_gregorian_leap_p
  164. d_lite_gc_mark
  165. d_lite_memsize
  166. d_simple_new_internal
  167. d_complex_new_internal
  168. d_lite_s_alloc_simple
  169. d_lite_s_alloc_complex
  170. d_lite_s_alloc
  171. old_to_new
  172. date_s_new_bang
  173. wholenum_p
  174. to_integer
  175. d_trunc
  176. h_trunc
  177. min_trunc
  178. s_trunc
  179. date_s_jd
  180. date_s_ordinal
  181. date_s_civil
  182. date_s_commercial
  183. date_s_weeknum
  184. date_s_nth_kday
  185. gmtime_r
  186. localtime_r
  187. date_s_today
  188. rt_rewrite_frags
  189. rt_complete_frags
  190. rt__valid_jd_p
  191. rt__valid_ordinal_p
  192. rt__valid_civil_p
  193. rt__valid_commercial_p
  194. rt__valid_weeknum_p
  195. rt__valid_date_frags_p
  196. d_new_by_frags
  197. date_s__strptime_internal
  198. date_s__strptime
  199. date_s_strptime
  200. date_s__parse_internal
  201. date_s__parse
  202. date_s_parse
  203. date_s__iso8601
  204. date_s_iso8601
  205. date_s__rfc3339
  206. date_s_rfc3339
  207. date_s__xmlschema
  208. date_s_xmlschema
  209. date_s__rfc2822
  210. date_s_rfc2822
  211. date_s__httpdate
  212. date_s_httpdate
  213. date_s__jisx0301
  214. date_s_jisx0301
  215. dup_obj
  216. dup_obj_as_complex
  217. d_lite_initialize
  218. d_lite_initialize_copy
  219. d_lite_fill
  220. d_lite_ajd
  221. d_lite_amjd
  222. d_lite_jd
  223. d_lite_mjd
  224. d_lite_ld
  225. d_lite_year
  226. d_lite_yday
  227. d_lite_mon
  228. d_lite_mday
  229. d_lite_day_fraction
  230. d_lite_cwyear
  231. d_lite_cweek
  232. d_lite_cwday
  233. d_lite_wnum0
  234. d_lite_wnum1
  235. d_lite_wday
  236. d_lite_sunday_p
  237. d_lite_monday_p
  238. d_lite_tuesday_p
  239. d_lite_wednesday_p
  240. d_lite_thursday_p
  241. d_lite_friday_p
  242. d_lite_saturday_p
  243. d_lite_nth_kday_p
  244. d_lite_hour
  245. d_lite_min
  246. d_lite_sec
  247. d_lite_sec_fraction
  248. d_lite_offset
  249. d_lite_zone
  250. d_lite_julian_p
  251. d_lite_gregorian_p
  252. d_lite_leap_p
  253. d_lite_start
  254. clear_civil
  255. set_sg
  256. dup_obj_with_new_start
  257. d_lite_new_start
  258. d_lite_italy
  259. d_lite_england
  260. d_lite_julian
  261. d_lite_gregorian
  262. set_of
  263. dup_obj_with_new_offset
  264. d_lite_new_offset
  265. d_lite_plus
  266. minus_dd
  267. d_lite_minus
  268. d_lite_next_day
  269. d_lite_prev_day
  270. d_lite_next
  271. d_lite_rshift
  272. d_lite_lshift
  273. d_lite_next_month
  274. d_lite_prev_month
  275. d_lite_next_year
  276. d_lite_prev_year
  277. d_lite_step
  278. d_lite_upto
  279. d_lite_downto
  280. cmp_gen
  281. cmp_dd
  282. d_lite_cmp
  283. equal_gen
  284. d_lite_equal
  285. d_lite_eql_p
  286. d_lite_hash
  287. d_lite_to_s
  288. mk_inspect_raw
  289. d_lite_inspect_raw
  290. mk_inspect
  291. d_lite_inspect
  292. date_strftime_alloc
  293. tmx_m_secs
  294. tmx_m_msecs
  295. tmx_m_of
  296. tmx_m_zone
  297. set_tmx
  298. date_strftime_internal
  299. d_lite_strftime
  300. strftimev
  301. d_lite_asctime
  302. d_lite_iso8601
  303. d_lite_rfc3339
  304. d_lite_rfc2822
  305. d_lite_httpdate
  306. DECIMAL_SIZE_OF_BITS
  307. jisx0301_date_format
  308. d_lite_jisx0301
  309. d_lite_marshal_dump_old
  310. d_lite_marshal_dump
  311. d_lite_marshal_load
  312. date_s__load
  313. datetime_s_jd
  314. datetime_s_ordinal
  315. datetime_s_civil
  316. datetime_s_commercial
  317. datetime_s_weeknum
  318. datetime_s_nth_kday
  319. datetime_s_now
  320. dt_new_by_frags
  321. datetime_s__strptime
  322. datetime_s_strptime
  323. datetime_s_parse
  324. datetime_s_iso8601
  325. datetime_s_rfc3339
  326. datetime_s_xmlschema
  327. datetime_s_rfc2822
  328. datetime_s_httpdate
  329. datetime_s_jisx0301
  330. dt_lite_to_s
  331. dt_lite_strftime
  332. iso8601_timediv
  333. dt_lite_iso8601
  334. dt_lite_rfc3339
  335. dt_lite_jisx0301
  336. time_to_time
  337. time_to_date
  338. time_to_datetime
  339. date_to_time
  340. date_to_date
  341. date_to_datetime
  342. datetime_to_time
  343. datetime_to_date
  344. datetime_to_datetime
  345. test_civil
  346. date_s_test_civil
  347. test_ordinal
  348. date_s_test_ordinal
  349. test_commercial
  350. date_s_test_commercial
  351. test_weeknum
  352. date_s_test_weeknum
  353. test_nth_kday
  354. date_s_test_nth_kday
  355. test_unit_v2v
  356. test_unit_v2v_iter2
  357. test_unit_v2v_iter
  358. date_s_test_unit_conv
  359. date_s_test_all
  360. mk_ary_of_str
  361. Init_date_core

/*
  date_core.c: Coded by Tadayoshi Funaba 2010-2014
*/

#include "ruby.h"
#include "ruby/encoding.h"
#include "ruby/util.h"
#include <math.h>
#include <time.h>
#if defined(HAVE_SYS_TIME_H)
#include <sys/time.h>
#endif

#define NDEBUG
#include <assert.h>

#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 <float.h>
#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)
{
    *nth = f_idiv(jd, INT2FIX(CM_PERIOD));
    if (f_zero_p(*nth)) {
        *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);
}

static inline void
expect_numeric(VALUE x)
{
    if (!k_numeric_p(x))
        rb_raise(rb_eTypeError, "expected numeric");
}

#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:
        expect_numeric(vof);
        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 (RB_INTEGER_TYPE_P(x))
        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: 2001-02-03 ...>
 *    Date.jd(2451945)          #=> #<Date: 2001-02-04 ...>
 *    Date.jd(0)                #=> #<Date: -4712-01-01 ...>
 *
 * 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: 2001-01-01 ...>
 *    Date.ordinal(2001,34)     #=> #<Date: 2001-02-03 ...>
 *    Date.ordinal(2001,-1)     #=> #<Date: 2001-12-31 ...>
 *
 * 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: 2001-01-01 ...>
 *    Date.new(2001,2,3)        #=> #<Date: 2001-02-03 ...>
 *    Date.new(2001,2,-1)       #=> #<Date: 2001-02-28 ...>
 *
 * 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: 2001-01-01 ...>
 *    Date.commercial(2002)     #=> #<Date: 2001-12-31 ...>
 *    Date.commercial(2001,5,6) #=> #<Date: 2001-02-03 ...>
 *
 * 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                #=> #<Date: 2011-06-11 ..>
 *
 * 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;
    long 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);
    }

    {
        long i, eno = 0, idx = 0;

        for (i = 0; i < RARRAY_LEN(tab); i++) {
            VALUE x, a;

            x = RARRAY_AREF(tab, i);
            a = RARRAY_AREF(x, 1);

            {
                long j, n = 0;

                for (j = 0; j < RARRAY_LEN(a); j++)
                    if (!NIL_P(ref_hash0(RARRAY_AREF(a, j))))
                        n++;
                if (n > eno) {
                    eno = n;
                    idx = i;
                }
            }
        }
        if (eno == 0)
            g = 0;
        else {
            g = 1;
            k = RARRAY_AREF(RARRAY_AREF(tab, idx), 0);
            a = RARRAY_AREF(RARRAY_AREF(tab, idx), 1);
            e = eno;
        }
    }

    d = Qnil;

    if (g && !NIL_P(k) && (RARRAY_LEN(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")) {
            long i;

            for (i = 0; i < RARRAY_LEN(a); i++) {
                VALUE e = RARRAY_AREF(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")) {
            long i;

            for (i = 0; i < RARRAY_LEN(a); i++) {
                VALUE e = RARRAY_AREF(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")) {
            long i;

            for (i = 0; i < RARRAY_LEN(a); i++) {
                VALUE e = RARRAY_AREF(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")) {
            long i;

            for (i = 0; i < RARRAY_LEN(a); i++) {
                VALUE e = RARRAY_AREF(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: 2001-02-03 ...>
 *    Date.strptime('03-02-2001', '%d-%m-%Y')   #=> #<Date: 2001-02-03 ...>
 *    Date.strptime('2001-034', '%Y-%j')        #=> #<Date: 2001-02-03 ...>
 *    Date.strptime('2001-W05-6', '%G-W%V-%u')  #=> #<Date: 2001-02-03 ...>
 *    Date.strptime('2001 04 6', '%Y %U %w')    #=> #<Date: 2001-02-03 ...>
 *    Date.strptime('2001 05 6', '%Y %W %u')    #=> #<Date: 2001-02-03 ...>
 *    Date.strptime('sat3feb01', '%a%d%b%y')    #=> #<Date: 2001-02-03 ...>
 *
 * 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: 2001-02-03 ...>
 *    Date.parse('20010203')            #=> #<Date: 2001-02-03 ...>
 *    Date.parse('3rd Feb 2001')        #=> #<Date: 2001-02-03 ...>
 */
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: 2001-02-03 ...>
 *    Date.iso8601('20010203')          #=> #<Date: 2001-02-03 ...>
 *    Date.iso8601('2001-W05-6')        #=> #<Date: 2001-02-03 ...>
 */
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') #=> #<Date: 2001-02-03 ...>
 */
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')      #=> #<Date: 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')
 *                                              #=> #<Date: 2001-02-03 ...>
 */
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')
 *                                              #=> #<Date: 2001-02-03 ...>
 */
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')                #=> #<Date: 2001-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(self, &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
 *
 * Returns 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
 *
 * Returns 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)         #=> #<Date: 1582-10-05 ...>
 */
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')
 *                              #=> #<DateTime: 2001-02-03T04:05:06-02:00 ...>
 *    d.new_offset('+09:00')    #=> #<DateTime: 2001-02-03T15:05:06+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    #=> #<Date: 2001-02-04 ...>
 *    DateTime.new(2001,2,3) + Rational(1,2)
 *                              #=> #<DateTime: 2001-02-03T12:00:00+00:00 ...>
 *    DateTime.new(2001,2,3) + Rational(-1,2)
 *                              #=> #<DateTime: 2001-02-02T12:00:00+00:00 ...>
 *    DateTime.jd(0,12) + DateTime.new(2001,2,3).ajd
 *                              #=> #<DateTime: 2001-02-03T00:00:00+00:00 ...>
 */
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:
        expect_numeric(other);
        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   #=> #<Date: 2001-02-02 ...>
 *     DateTime.new(2001,2,3) - Rational(1,2)
 *                              #=> #<DateTime: 2001-02-02T12:00:00+00:00 ...>
 *     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:
        expect_numeric(other);
        /* 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: 2001-03-03 ...>
 *    Date.new(2001,1,31) >> 1  #=> #<Date: 2001-02-28 ...>
 *    Date.new(2001,2,3) >> -2  #=> #<Date: 2000-12-03 ...>
 */
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: 2001-01-03 ...>
 *    Date.new(2001,1,31) << 11 #=> #<Date: 2000-02-29 ...>
 *    Date.new(2001,2,3) << -1  #=> #<Date: 2001-03-03 ...>
 */
static VALUE
d_lite_lshift(VALUE self, VALUE other)
{
    expect_numeric(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_raw(union DateData *x, VALUE klass)
{
    char flags[5];

    flags[0] = (x->flags & COMPLEX_DAT) ? 'C' : 'S';
    flags[1] = (x->flags & HAVE_JD)     ? 'j' : '-';
    flags[2] = (x->flags & HAVE_DF)     ? 'd' : '-';
    flags[3] = (x->flags & HAVE_CIVIL)  ? 'c' : '-';
    flags[4] = (x->flags & HAVE_TIME)   ? 't' : '-';
    flags[5] = '\0';

    if (simple_dat_p(x)) {
        return rb_enc_sprintf(rb_usascii_encoding(),
                              "#<%"PRIsVALUE": "
                              "(%+"PRIsVALUE"th,%dj),+0s,%.0fj; "
                              "%dy%dm%dd; %s>",
                              klass,
                              x->s.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
                              flags);
    }
    else {
        return rb_enc_sprintf(rb_usascii_encoding(),
                              "#<%"PRIsVALUE": "
                              "(%+"PRIsVALUE"th,%dj,%ds,%+"PRIsVALUE"n),"
                              "%+ds,%.0fj; "
                              "%dy%dm%dd %dh%dm%ds; %s>",
                              klass,
                              x->c.nth, x->c.jd, x->c.df, x->c.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
                              flags);
    }
}

static VALUE
d_lite_inspect_raw(VALUE self)
{
    get_d1(self);
    return mk_inspect_raw(dat, rb_obj_class(self));
}
#endif

static VALUE
mk_inspect(union DateData *x, VALUE klass, VALUE to_s)
{
    return rb_enc_sprintf(rb_usascii_encoding(),
                          "#<%"PRIsVALUE": %"PRIsVALUE" "
                          "((%+"PRIsVALUE"j,%ds,%+"PRIsVALUE"n),%+ds,%.0fj)>",
                          klass, to_s,
                          m_real_jd(x), m_df(x), m_sf(x),
                          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
 *              #=> "#<Date: 2001-02-03 ((2451944j,0s,0n),+0s,2299161j)>"
 *    DateTime.new(2001,2,3,4,5,6,'-7').inspect
 *              #=> "#<DateTime: 2001-02-03T04:05:06-07:00 ((2451944j,39906s,0n),-25200s,2299161j)>"
 */
static VALUE
d_lite_inspect(VALUE self)
{
    get_d1(self);
    return mk_inspect(dat, rb_obj_class(self), self);
}

#include <errno.h>
#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 const 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><width><modifier><conversion>
 *
 * 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")
 *                              #=> #<DateTime: 2007-11-19T08:37:48-0600 ...>
 *    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);
    RB_GC_GUARD(self);
    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);
}

enum {
    DECIMAL_SIZE_OF_LONG = DECIMAL_SIZE_OF_BITS(CHAR_BIT*sizeof(long)),
    JISX0301_DATE_SIZE = DECIMAL_SIZE_OF_LONG+8
};

static const char *
jisx0301_date_format(char *fmt, size_t size, VALUE jd, VALUE y)
{
    if (FIXNUM_P(jd)) {
        long d = FIX2INT(jd);
        long s;
        char c;
        if (d < 2405160)
            return "%Y-%m-%d";
        if (d < 2419614) {
            c = 'M';
            s = 1867;
        }
        else if (d < 2424875) {
            c = 'T';
            s = 1911;
        }
        else if (d < 2447535) {
            c = 'S';
            s = 1925;
        }
        else {
            c = 'H';
            s = 1988;
        }
        snprintf(fmt, size, "%c%02ld" ".%%m.%%d", c, FIX2INT(y) - s);
        return fmt;
    }
    return "%Y-%m-%d";
}

/*
 * 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)
{
    char fmtbuf[JISX0301_DATE_SIZE];
    const char *fmt;

    get_d1(self);
    fmt = jisx0301_date_format(fmtbuf, sizeof(fmtbuf),
                               m_real_local_jd(dat),
                               m_real_year(dat));
    return strftimev(fmt, 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_AREF(a, 0), half_days_in_day);
                of = INT2FIX(0);
                sg = RARRAY_AREF(a, 1);
                if (!k_numeric_p(sg))
                    sg = DBL2NUM(RTEST(sg) ? GREGORIAN : JULIAN);
            }
            else {
                ajd = RARRAY_AREF(a, 0);
                of = RARRAY_AREF(a, 1);
                sg = RARRAY_AREF(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_AREF(a, 0);
            jd = NUM2INT(RARRAY_AREF(a, 1));
            df = NUM2INT(RARRAY_AREF(a, 2));
            sf = RARRAY_AREF(a, 3);
            of = NUM2INT(RARRAY_AREF(a, 4));
            sg = NUM2DBL(RARRAY_AREF(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: 2001-02-03T00:00:00+00:00 ...>
 *    DateTime.jd(2451945)      #=> #<DateTime: 2001-02-04T00:00:00+00:00 ...>
 *    DateTime.jd(Rational('0.5'))
 *                              #=> #<DateTime: -4712-01-01T12:00:00+00:00 ...>
 */
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: 2001-02-03T00:00:00+00:00 ...>
 *    DateTime.ordinal(2001,34,4,5,6,'+7')
 *                              #=> #<DateTime: 2001-02-03T04:05:06+07:00 ...>
 *    DateTime.ordinal(2001,-332,-20,-55,-54,'+7')
 *                              #=> #<DateTime: 2001-02-03T04:05:06+07:00 ...>
 */
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: 2001-02-03T00:00:00+00:00 ...>
 *    DateTime.new(2001,2,3,4,5,6,'+7')
 *                              #=> #<DateTime: 2001-02-03T04:05:06+07:00 ...>
 *    DateTime.new(2001,-11,-26,-20,-55,-54,'+7')
 *                              #=> #<DateTime: 2001-02-03T04:05:06+07:00 ...>
 */
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: 2001-01-01T00:00:00+00:00 ...>
 *    DateTime.commercial(2002) #=> #<DateTime: 2001-12-31T00:00:00+00:00 ...>
 *    DateTime.commercial(2001,5,6,4,5,6,'+7')
 *                              #=> #<DateTime: 2001-02-03T04:05:06+07:00 ...>
 */
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              #=> #<DateTime: 2011-06-11T21:20:44+09:00 ...>
 */
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: 2001-02-03T04:05:06+07:00 ...>
 *    DateTime.strptime('03-02-2001 04:05:06 PM', '%d-%m-%Y %I:%M:%S %p')
 *                              #=> #<DateTime: 2001-02-03T16:05:06+00:00 ...>
 *    DateTime.strptime('2001-W05-6T04:05:06+07:00', '%G-W%V-%uT%H:%M:%S%z')
 *                              #=> #<DateTime: 2001-02-03T04:05:06+07:00 ...>
 *    DateTime.strptime('2001 04 6 04 05 06 +7', '%Y %U %w %H %M %S %z')
 *                              #=> #<DateTime: 2001-02-03T04:05:06+07:00 ...>
 *    DateTime.strptime('2001 05 6 04 05 06 +7', '%Y %W %u %H %M %S %z')
 *                              #=> #<DateTime: 2001-02-03T04:05:06+07:00 ...>
 *    DateTime.strptime('-1', '%s')
 *                              #=> #<DateTime: 1969-12-31T23:59:59+00:00 ...>
 *    DateTime.strptime('-1000', '%Q')
 *                              #=> #<DateTime: 1969-12-31T23:59:59+00:00 ...>
 *    DateTime.strptime('sat3feb014pm+7', '%a%d%b%y%H%p%z')
 *                              #=> #<DateTime: 2001-02-03T16:00:00+07:00 ...>
 *
 * 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: 2001-02-03T04:05:06+07:00 ...>
 *    DateTime.parse('20010203T040506+0700')
 *                              #=> #<DateTime: 2001-02-03T04:05:06+07:00 ...>
 *    DateTime.parse('3rd Feb 2001 04:05:06 PM')
 *                              #=> #<DateTime: 2001-02-03T16:05:06+00:00 ...>
 */
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: 2001-02-03T04:05:06+07:00 ...>
 *    DateTime.iso8601('20010203T040506+0700')
 *                              #=> #<DateTime: 2001-02-03T04:05:06+07:00 ...>
 *    DateTime.iso8601('2001-W05-6T04:05:06+07:00')
 *                              #=> #<DateTime: 2001-02-03T04: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')
 *                              #=> #<DateTime: 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')
 *                              #=> #<DateTime: 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')
 *                              #=> #<DateTime: 2001-02-03T04:05:06+07:00 ...>
 */
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')
 *                              #=> #<DateTime: 2001-02-03T04:05:06+00:00 ...>
 */
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')
 *                              #=> #<DateTime: 2001-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><width><modifier><conversion>
 *
 * 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")
 *                              #=> #<DateTime: 2007-11-19T08:37:48-0600 ...>
 *    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, long n)
{
    static const char timefmt[] = "T%H:%M:%S";
    static const char zone[] = "%:z";
    char fmt[sizeof(timefmt) + sizeof(zone) + rb_strlen_lit(".%N") +
             DECIMAL_SIZE_OF_LONG];
    char *p = fmt;

    memcpy(p, timefmt, sizeof(timefmt)-1);
    p += sizeof(timefmt)-1;
    if (n > 0) p += snprintf(p, fmt+sizeof(fmt)-p, ".%%%ldN", n);
    memcpy(p, zone, sizeof(zone));
    return strftimev(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)
{
    long n = 0;

    rb_check_arity(argc, 0, 1);
    if (argc >= 1)
        n = NUM2LONG(argv[0]);

    return rb_str_append(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)
{
    long n = 0;

    rb_check_arity(argc, 0, 1);
    if (argc >= 1)
        n = NUM2LONG(argv[0]);

    return rb_str_append(d_lite_jisx0301(self),
                         iso8601_timediv(self, n));
}

/* conversions */

#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)

/*
 * call-seq:
 *    t.to_time  ->  time
 *
 * Returns self.
 */
static VALUE
time_to_time(VALUE self)
{
    return 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(self);
    {
        VALUE t;

        get_d1(dup);

        t = rb_funcall(rb_cTime,
                   rb_intern("new"),
                   7,
                   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)),
                   INT2FIX(m_of(dat)));
        return 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: 2001-02-03 ...>
     *     Date.jd(2451944)
     *      #=> #<Date: 2001-02-03 ...>
     *     Date.ordinal(2001,34)
     *      #=> #<Date: 2001-02-03 ...>
     *     Date.commercial(2001,5,6)
     *      #=> #<Date: 2001-02-03 ...>
     *     Date.parse('2001-02-03')
     *      #=> #<Date: 2001-02-03 ...>
     *     Date.strptime('03-02-2001', '%d-%m-%Y')
     *      #=> #<Date: 2001-02-03 ...>
     *     Time.new(2001,2,3).to_date
     *      #=> #<Date: 2001-02-03 ...>
     *
     * 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')
     *                                  #=> #<Date: 2001-02-03 ...>
     *     d.year                       #=> 2001
     *     d.mon                        #=> 2
     *     d.mday                       #=> 3
     *     d.wday                       #=> 6
     *     d += 1                       #=> #<Date: 2001-02-04 ...>
     *     d.strftime('%a %d %b %Y')    #=> "Sun 04 Feb 2001"
     *
     */
    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
     *
     * 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)
     *                         #=> #<DateTime: 2001-02-03T04:05:06+00:00 ...>
     *
     * 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)
     *                         #=> #<DateTime: 2001-02-03T12:00:00+00:00 ...>
     *
     * An optional argument the offset indicates the difference
     * between the local time and UTC. For example, <tt>Rational(3,24)</tt>
     * represents ahead of 3 hours of UTC, <tt>Rational(-5,24)</tt> 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))
     *                         #=> #<DateTime: 2001-02-03T04:05:06+03:00 ...>
     *
     * also accepts string form.
     *
     *     DateTime.new(2001,2,3,4,5,6,'+03:00')
     *                         #=> #<DateTime: 2001-02-03T04:05:06+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')
     *                         #=> #<DateTime: 2001-02-03T04: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')
     *                         #=> #<DateTime: 2001-02-04%16:05:06+03:30 ...>
     *     d = d.new_offset('+09:00')
     *                         #=> #<DateTime: 2001-02-04%21:35:06+09:00 ...>
     *     d.strftime('%I:%M:%S %p')
     *                         #=> "09:35:06 PM"
     *     d > DateTime.new(1999)
     *                         #=> true
     *
     * === When should you use DateTime and when should you use Time?
     *
     * It's a common misconception that
     * {William Shakespeare}[http://en.wikipedia.org/wiki/William_Shakespeare]
     * and
     * {Miguel de Cervantes}[http://en.wikipedia.org/wiki/Miguel_de_Cervantes]
     * died on the same day in history -
     * so much so that UNESCO named April 23 as
     * {World Book Day because of this fact}[http://en.wikipedia.org/wiki/World_Book_Day].
     * However because England hadn't yet adopted
     * {Gregorian Calendar Reform}[http://en.wikipedia.org/wiki/Gregorian_calendar#Gregorian_reform]
     * (and wouldn't until {1752}[http://en.wikipedia.org/wiki/Calendar_(New_Style)_Act_1750])
     * their deaths are actually 10 days apart.
     * Since Ruby's Time class implements a
     * {proleptic Gregorian calendar}[http://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar]
     * and has no concept of calendar reform then there's no way
     * to express this. This is where DateTime steps in:
     *
     *     shakespeare = DateTime.iso8601('1616-04-23', Date::ENGLAND)
     *      #=> Tue, 23 Apr 1616 00:00:00 +0000
     *     cervantes = DateTime.iso8601('1616-04-23', Date::ITALY)
     *      #=> Sat, 23 Apr 1616 00:00:00 +0000
     *
     * Already you can see something's weird - the days of the week
     * are different, taking this further:
     *
     *     cervantes == shakespeare
     *      #=> false
     *     (shakespeare - cervantes).to_i
     *      #=> 10
     *
     * This shows that in fact they died 10 days apart (in reality
     * 11 days since Cervantes died a day earlier but was buried on
     * the 23rd). We can see the actual date of Shakespeare's death by
     * using the #gregorian method to convert it:
     *
     *     shakespeare.gregorian
     *      #=> Tue, 03 May 1616 00:00:00 +0000
     *
     * So there's an argument that all the celebrations that take
     * place on the 23rd April in Stratford-upon-Avon are actually
     * the wrong date since England is now using the Gregorian calendar.
     * You can see why when we transition across the reform
     * date boundary:
     *
     *     # start off with the anniversary of Shakespeare's birth in 1751
     *     shakespeare = DateTime.iso8601('1751-04-23', Date::ENGLAND)
     *      #=> Tue, 23 Apr 1751 00:00:00 +0000
     *
     *     # add 366 days since 1752 is a leap year and April 23 is after February 29
     *     shakespeare + 366
     *      #=> Thu, 23 Apr 1752 00:00:00 +0000
     *
     *     # add another 365 days to take us to the anniversary in 1753
     *     shakespeare + 366 + 365
     *      #=> Fri, 04 May 1753 00:00:00 +0000
     *
     * As you can see, if we're accurately tracking the number of
     * {solar years}[http://en.wikipedia.org/wiki/Tropical_year]
     * since Shakespeare's birthday then the correct anniversary date
     * would be the 4th May and not the 23rd April.
     *
     * So when should you use DateTime in Ruby and when should
     * you use Time? Almost certainly you'll want to use Time
     * since your app is probably dealing with current dates and
     * times. However, if you need to deal with dates and times in a
     * historical context you'll want to use DateTime to avoid
     * making the same mistakes as UNESCO. If you also have to deal
     * with timezones then best of luck - just bear in mind that
     * you'll probably be dealing with
     * {local solar times}[http://en.wikipedia.org/wiki/Solar_time],
     * since it wasn't until the 19th century that the introduction
     * of the railways necessitated the need for
     * {Standard Time}[http://en.wikipedia.org/wiki/Standard_time#Great_Britain]
     * and eventually timezones.
     */

    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:
*/

/* [previous][next][first][last][top][bottom][index][help] */