diff options
Diffstat (limited to 'ext/date/date_core.c')
| -rw-r--r-- | ext/date/date_core.c | 3267 |
1 files changed, 1926 insertions, 1341 deletions
diff --git a/ext/date/date_core.c b/ext/date/date_core.c index 3b18bbd5b1..72d697c8ea 100644 --- a/ext/date/date_core.c +++ b/ext/date/date_core.c @@ -11,6 +11,7 @@ #include <sys/time.h> #endif +#undef NDEBUG #define NDEBUG #include <assert.h> @@ -22,9 +23,14 @@ static ID id_cmp, id_le_p, id_ge_p, id_eqeq_p; static VALUE cDate, cDateTime; +static VALUE eDateError; static VALUE half_days_in_day, day_in_nanoseconds; static double positive_inf, negative_inf; +/* used by deconstruct_keys */ +static VALUE sym_year, sym_month, sym_day, sym_yday, sym_wday; +static VALUE sym_hour, sym_min, sym_sec, sym_sec_fraction, sym_zone; + #define f_boolcast(x) ((x) ? Qtrue : Qfalse) #define f_abs(x) rb_funcall(x, rb_intern("abs"), 0) @@ -51,18 +57,32 @@ static double positive_inf, negative_inf; #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 +#define f_frozen_ary(...) rb_ary_freeze(rb_ary_new3(__VA_ARGS__)) + +static VALUE date_initialize(int argc, VALUE *argv, VALUE self); +static VALUE datetime_initialize(int argc, VALUE *argv, VALUE self); + +#define RETURN_FALSE_UNLESS_NUMERIC(obj) if(!RTEST(rb_obj_is_kind_of((obj), rb_cNumeric))) return Qfalse +inline static void +check_numeric(VALUE obj, const char* field) +{ + if(!RTEST(rb_obj_is_kind_of(obj, rb_cNumeric))) { + rb_raise(rb_eTypeError, "invalid %s (not numeric)", field); + } +} + +inline static int f_cmp(VALUE x, VALUE y) { if (FIXNUM_P(x) && FIXNUM_P(y)) { long c = FIX2LONG(x) - FIX2LONG(y); if (c > 0) - c = 1; + return 1; else if (c < 0) - c = -1; - return INT2FIX(c); + return -1; + return 0; } - return rb_funcall(x, id_cmp, 1, y); + return rb_cmpint(rb_funcallv(x, id_cmp, 1, &y), x, y); } inline static VALUE @@ -94,7 +114,7 @@ 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); + return rb_funcall(x, id_ge_p, 1, y); } inline static VALUE @@ -102,7 +122,7 @@ 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); + return rb_funcall(x, id_eqeq_p, 1, y); } inline static VALUE @@ -228,6 +248,11 @@ f_negative_p(VALUE x) #define date_sg_t double #endif +#define JULIAN_EPOCH_DATE "-4712-01-01" +#define JULIAN_EPOCH_DATETIME JULIAN_EPOCH_DATE "T00:00:00+00:00" +#define JULIAN_EPOCH_DATETIME_RFC3339 "Mon, 1 Jan -4712 00:00:00 +0000" +#define JULIAN_EPOCH_DATETIME_HTTPDATE "Mon, 01 Jan -4712 00:00:00 GMT" + /* 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. @@ -236,11 +261,8 @@ f_negative_p(VALUE x) struct SimpleDateData { unsigned flags; - VALUE nth; /* not always canonicalized */ int jd; /* as utc */ - /* df is zero */ - /* sf is zero */ - /* of is zero */ + VALUE nth; /* not always canonicalized */ date_sg_t sg; /* 2298874..2426355 or -/+oo -- at most 22 bits */ /* decoded as utc=local */ int year; /* truncated */ @@ -259,11 +281,8 @@ struct SimpleDateData 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 */ + VALUE nth; /* not always canonicalized */ date_sg_t sg; /* 2298874..2426355 or -/+oo -- at most 22 bits */ /* decoded as local */ int year; /* truncated */ @@ -277,6 +296,9 @@ struct ComplexDateData /* packed civil */ unsigned pc; #endif + int df; /* as utc, in secs */ + int of; /* in secs */ + VALUE sf; /* in nano secs */ }; union DateData { @@ -315,31 +337,31 @@ canon(VALUE x) #ifndef USE_PACK #define set_to_simple(obj, x, _nth, _jd ,_sg, _year, _mon, _mday, _flags) \ -{\ +do {\ 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;\ -} + (x)->flags = (_flags) & ~COMPLEX_DAT;\ +} while (0) #else #define set_to_simple(obj, x, _nth, _jd ,_sg, _year, _mon, _mday, _flags) \ -{\ +do {\ 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;\ -} + (x)->flags = (_flags) & ~COMPLEX_DAT;\ +} while (0) #endif #ifndef USE_PACK #define set_to_complex(obj, x, _nth, _jd ,_df, _sf, _of, _sg,\ _year, _mon, _mday, _hour, _min, _sec, _flags) \ -{\ +do {\ RB_OBJ_WRITE((obj), &(x)->nth, canon(_nth));\ (x)->jd = _jd;\ (x)->df = _df;\ @@ -352,12 +374,12 @@ _year, _mon, _mday, _hour, _min, _sec, _flags) \ (x)->hour = _hour;\ (x)->min = _min;\ (x)->sec = _sec;\ - (x)->flags = _flags;\ -} + (x)->flags = (_flags) | COMPLEX_DAT;\ +} while (0) #else #define set_to_complex(obj, x, _nth, _jd ,_df, _sf, _of, _sg,\ _year, _mon, _mday, _hour, _min, _sec, _flags) \ -{\ +do {\ RB_OBJ_WRITE((obj), &(x)->nth, canon(_nth));\ (x)->jd = _jd;\ (x)->df = _df;\ @@ -366,13 +388,13 @@ _year, _mon, _mday, _hour, _min, _sec, _flags) \ (x)->sg = (date_sg_t)(_sg);\ (x)->year = _year;\ (x)->pc = PACK5(_mon, _mday, _hour, _min, _sec);\ - (x)->flags = _flags;\ -} + (x)->flags = (_flags) | COMPLEX_DAT;\ +} while (0) #endif #ifndef USE_PACK #define copy_simple_to_complex(obj, x, y) \ -{\ +do {\ RB_OBJ_WRITE((obj), &(x)->nth, (y)->nth);\ (x)->jd = (y)->jd;\ (x)->df = 0;\ @@ -386,10 +408,10 @@ _year, _mon, _mday, _hour, _min, _sec, _flags) \ (x)->min = 0;\ (x)->sec = 0;\ (x)->flags = (y)->flags;\ -} +} while (0) #else #define copy_simple_to_complex(obj, x, y) \ -{\ +do {\ RB_OBJ_WRITE((obj), &(x)->nth, (y)->nth);\ (x)->jd = (y)->jd;\ (x)->df = 0;\ @@ -399,12 +421,12 @@ _year, _mon, _mday, _hour, _min, _sec, _flags) \ (x)->year = (y)->year;\ (x)->pc = PACK5(EX_MON((y)->pc), EX_MDAY((y)->pc), 0, 0, 0);\ (x)->flags = (y)->flags;\ -} +} while (0) #endif #ifndef USE_PACK #define copy_complex_to_simple(obj, x, y) \ -{\ +do {\ RB_OBJ_WRITE((obj), &(x)->nth, (y)->nth);\ (x)->jd = (y)->jd;\ (x)->sg = (date_sg_t)((y)->sg);\ @@ -412,17 +434,17 @@ _year, _mon, _mday, _hour, _min, _sec, _flags) \ (x)->mon = (y)->mon;\ (x)->mday = (y)->mday;\ (x)->flags = (y)->flags;\ -} +} while (0) #else #define copy_complex_to_simple(obj, x, y) \ -{\ +do {\ 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;\ -} +} while (0) #endif /* base */ @@ -430,11 +452,43 @@ _year, _mon, _mday, _hour, _min, _sec, _flags) \ static int c_valid_civil_p(int, int, int, double, int *, int *, int *, int *); +/* Check if using pure Gregorian calendar (sg == -Infinity) */ +#define c_gregorian_only_p(sg) (isinf(sg) && (sg) < 0) + +/* + * Fast path macros for pure Gregorian calendar. + * Sets *rjd to the JD value, *ns to 1 (new style), and returns. + */ +#define GREGORIAN_JD_FAST_PATH_RET(sg, jd_expr, rjd, ns) \ + if (c_gregorian_only_p(sg)) { \ + *(rjd) = (jd_expr); \ + *(ns) = 1; \ + return 1; \ + } + +#define GREGORIAN_JD_FAST_PATH(sg, jd_expr, rjd, ns) \ + if (c_gregorian_only_p(sg)) { \ + *(rjd) = (jd_expr); \ + *(ns) = 1; \ + return; \ + } + +/* Forward declarations for Neri-Schneider optimized functions */ +static int c_gregorian_civil_to_jd(int y, int m, int d); +static void c_gregorian_jd_to_civil(int jd, int *ry, int *rm, int *rd); +static int c_gregorian_fdoy(int y); +static int c_gregorian_ldoy(int y); +static int c_gregorian_ldom_jd(int y, int m); +static int ns_jd_in_range(int jd); + static int c_find_fdoy(int y, double sg, int *rjd, int *ns) { int d, rm, rd; + GREGORIAN_JD_FAST_PATH_RET(sg, c_gregorian_fdoy(y), rjd, ns); + + /* Keep existing loop for Julian/reform period */ for (d = 1; d < 31; d++) if (c_valid_civil_p(y, 1, d, sg, &rm, &rd, rjd, ns)) return 1; @@ -446,6 +500,9 @@ c_find_ldoy(int y, double sg, int *rjd, int *ns) { int i, rm, rd; + GREGORIAN_JD_FAST_PATH_RET(sg, c_gregorian_ldoy(y), rjd, ns); + + /* Keep existing loop for Julian/reform period */ for (i = 0; i < 30; i++) if (c_valid_civil_p(y, 12, 31 - i, sg, &rm, &rd, rjd, ns)) return 1; @@ -453,6 +510,7 @@ c_find_ldoy(int y, double sg, int *rjd, int *ns) } #ifndef NDEBUG +/* :nodoc: */ static int c_find_fdom(int y, int m, double sg, int *rjd, int *ns) { @@ -470,6 +528,9 @@ c_find_ldom(int y, int m, double sg, int *rjd, int *ns) { int i, rm, rd; + GREGORIAN_JD_FAST_PATH_RET(sg, c_gregorian_ldom_jd(y, m), rjd, ns); + + /* Keep existing loop for Julian/reform period */ for (i = 0; i < 30; i++) if (c_valid_civil_p(y, m, 31 - i, sg, &rm, &rd, rjd, ns)) return 1; @@ -479,55 +540,69 @@ c_find_ldom(int y, int m, double sg, int *rjd, int *ns) static void c_civil_to_jd(int y, int m, int d, double sg, int *rjd, int *ns) { - double a, b, jd; + int jd; + + GREGORIAN_JD_FAST_PATH(sg, c_gregorian_civil_to_jd(y, m, d), rjd, ns); + + /* Calculate Gregorian JD using optimized algorithm */ + jd = c_gregorian_civil_to_jd(y, m, d); - 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; + /* Before Gregorian switchover - use Julian calendar */ + int y2 = y, m2 = m; + if (m2 <= 2) { + y2 -= 1; + m2 += 12; + } + jd = (int)(floor(365.25 * (y2 + 4716)) + + floor(30.6001 * (m2 + 1)) + + d - 1524); *ns = 0; } - else + else { *ns = 1; + } - *rjd = (int)jd; + *rjd = 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; + /* Fast path: pure Gregorian or date after switchover, within safe range */ + if ((c_gregorian_only_p(sg) || jd >= sg) && ns_jd_in_range(jd)) { + c_gregorian_jd_to_civil(jd, ry, rm, rdom); + return; } - *ry = (int)y; - *rm = (int)m; - *rdom = (int)dom; + /* Original algorithm for Julian calendar or extreme dates */ + { + 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 @@ -609,6 +684,7 @@ c_jd_to_weeknum(int jd, int f, double sg, int *ry, int *rw, int *rd) } #ifndef NDEBUG +/* :nodoc: */ static void c_nth_kday_to_jd(int y, int m, int n, int k, double sg, int *rjd, int *ns) { @@ -634,6 +710,7 @@ c_jd_to_wday(int jd) } #ifndef NDEBUG +/* :nodoc: */ static void c_jd_to_nth_kday(int jd, double sg, int *ry, int *rm, int *rn, int *rk) { @@ -700,6 +777,147 @@ c_gregorian_last_day_of_month(int y, int m) return monthtab[c_gregorian_leap_p(y) ? 1 : 0][m]; } +/* + * Neri-Schneider algorithm for optimized Gregorian date conversion. + * Reference: Neri & Schneider, "Euclidean Affine Functions and Applications + * to Calendar Algorithms", Software: Practice and Experience, 2023. + * https://arxiv.org/abs/2102.06959 + * + * This algorithm provides ~2-3x speedup over traditional floating-point + * implementations by using pure integer arithmetic with multiplication + * and bit-shifts instead of expensive division operations. + */ + +/* JDN of March 1, Year 0 in proleptic Gregorian calendar */ +#define NS_EPOCH 1721120 + +/* Days in a 4-year cycle (3 normal years + 1 leap year) */ +#define NS_DAYS_IN_4_YEARS 1461 + +/* Days in a 400-year Gregorian cycle (97 leap years in 400 years) */ +#define NS_DAYS_IN_400_YEARS 146097 + +/* Years per century */ +#define NS_YEARS_PER_CENTURY 100 + +/* + * Multiplier for extracting year within century using fixed-point arithmetic. + * This is ceil(2^32 / NS_DAYS_IN_4_YEARS) for the Euclidean affine function. + */ +#define NS_YEAR_MULTIPLIER 2939745 + +/* + * Coefficients for month calculation from day-of-year. + * Maps day-of-year to month using: month = (NS_MONTH_COEFF * doy + NS_MONTH_OFFSET) >> 16 + */ +#define NS_MONTH_COEFF 2141 +#define NS_MONTH_OFFSET 197913 + +/* + * Coefficients for civil date to JDN month contribution. + * Maps month to accumulated days: days = (NS_CIVIL_MONTH_COEFF * m - NS_CIVIL_MONTH_OFFSET) / 32 + */ +#define NS_CIVIL_MONTH_COEFF 979 +#define NS_CIVIL_MONTH_OFFSET 2919 +#define NS_CIVIL_MONTH_DIVISOR 32 + +/* Days from March 1 to December 31 (for Jan/Feb year adjustment) */ +#define NS_DAYS_BEFORE_NEW_YEAR 306 + +/* + * Safe bounds for Neri-Schneider algorithm to avoid integer overflow. + * These correspond to approximately years -1,000,000 to +1,000,000. + */ +#define NS_JD_MIN -364000000 +#define NS_JD_MAX 538000000 + +inline static int +ns_jd_in_range(int jd) +{ + return jd >= NS_JD_MIN && jd <= NS_JD_MAX; +} + +/* Optimized: Gregorian date -> Julian Day Number */ +static int +c_gregorian_civil_to_jd(int y, int m, int d) +{ + /* Shift epoch to March 1 of year 0 (Jan/Feb belong to previous year) */ + int j = (m < 3) ? 1 : 0; + int y0 = y - j; + int m0 = j ? m + 12 : m; + int d0 = d - 1; + + /* Calculate year contribution with leap year correction */ + int q1 = DIV(y0, NS_YEARS_PER_CENTURY); + int yc = DIV(NS_DAYS_IN_4_YEARS * y0, 4) - q1 + DIV(q1, 4); + + /* Calculate month contribution using integer arithmetic */ + int mc = (NS_CIVIL_MONTH_COEFF * m0 - NS_CIVIL_MONTH_OFFSET) / NS_CIVIL_MONTH_DIVISOR; + + /* Combine and add epoch offset to get JDN */ + return yc + mc + d0 + NS_EPOCH; +} + +/* Optimized: Julian Day Number -> Gregorian date */ +static void +c_gregorian_jd_to_civil(int jd, int *ry, int *rm, int *rd) +{ + int r0, n1, q1, r1, n2, q2, r2, n3, q3, r3, y0, j; + uint64_t u2; + + /* Convert JDN to rata die (March 1, Year 0 epoch) */ + r0 = jd - NS_EPOCH; + + /* Extract century and day within 400-year cycle */ + /* Use Euclidean (floor) division for negative values */ + n1 = 4 * r0 + 3; + q1 = DIV(n1, NS_DAYS_IN_400_YEARS); + r1 = MOD(n1, NS_DAYS_IN_400_YEARS) / 4; + + /* Calculate year within century and day of year */ + n2 = 4 * r1 + 3; + /* Use 64-bit arithmetic to avoid overflow */ + u2 = (uint64_t)NS_YEAR_MULTIPLIER * (uint64_t)n2; + q2 = (int)(u2 >> 32); + r2 = (int)((uint32_t)u2 / NS_YEAR_MULTIPLIER / 4); + + /* Calculate month and day using integer arithmetic */ + n3 = NS_MONTH_COEFF * r2 + NS_MONTH_OFFSET; + q3 = n3 >> 16; + r3 = (n3 & 0xFFFF) / NS_MONTH_COEFF; + + /* Combine century and year */ + y0 = NS_YEARS_PER_CENTURY * q1 + q2; + + /* Adjust for January/February (shift from fiscal year) */ + j = (r2 >= NS_DAYS_BEFORE_NEW_YEAR) ? 1 : 0; + + *ry = y0 + j; + *rm = j ? q3 - 12 : q3; + *rd = r3 + 1; +} + +/* O(1) first day of year for Gregorian calendar */ +inline static int +c_gregorian_fdoy(int y) +{ + return c_gregorian_civil_to_jd(y, 1, 1); +} + +/* O(1) last day of year for Gregorian calendar */ +inline static int +c_gregorian_ldoy(int y) +{ + return c_gregorian_civil_to_jd(y, 12, 31); +} + +/* O(1) last day of month (JDN) for Gregorian calendar */ +inline static int +c_gregorian_ldom_jd(int y, int m) +{ + return c_gregorian_civil_to_jd(y, m, c_gregorian_last_day_of_month(y, m)); +} + static int c_valid_julian_p(int y, int m, int d, int *rm, int *rd) { @@ -746,6 +964,8 @@ c_valid_civil_p(int y, int m, int d, double sg, if (m < 0) m += 13; + if (m < 1 || m > 12) + return 0; if (d < 0) { if (!c_find_ldom(y, m, sg, rjd, ns)) return 0; @@ -810,6 +1030,7 @@ c_valid_weeknum_p(int y, int w, int d, int f, double sg, } #ifndef NDEBUG +/* :nodoc: */ 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) @@ -951,6 +1172,7 @@ ns_to_day(VALUE n) } #ifndef NDEBUG +/* :nodoc: */ static VALUE ms_to_sec(VALUE m) { @@ -969,6 +1191,7 @@ ns_to_sec(VALUE n) } #ifndef NDEBUG +/* :nodoc: */ inline static VALUE ins_to_day(int n) { @@ -1004,6 +1227,7 @@ day_to_sec(VALUE d) } #ifndef NDEBUG +/* :nodoc: */ static VALUE day_to_ns(VALUE d) { @@ -1028,6 +1252,7 @@ sec_to_ns(VALUE s) } #ifndef NDEBUG +/* :nodoc: */ static VALUE isec_to_ns(int s) { @@ -1054,6 +1279,7 @@ div_df(VALUE d, VALUE *f) } #ifndef NDEBUG +/* :nodoc: */ static VALUE div_sf(VALUE s, VALUE *f) { @@ -1109,7 +1335,7 @@ m_virtual_sg(union DateData *x) } #define canonicalize_jd(_nth, _jd) \ -{\ +do {\ if (_jd < 0) {\ _nth = f_sub(_nth, INT2FIX(1));\ _jd += CM_PERIOD;\ @@ -1118,7 +1344,7 @@ m_virtual_sg(union DateData *x) _nth = f_add(_nth, INT2FIX(1));\ _jd -= CM_PERIOD;\ }\ -} +} while (0) inline static void canonicalize_s_jd(VALUE obj, union DateData *x) @@ -1488,6 +1714,7 @@ m_df(union DateData *x) } #ifndef NDEBUG +/* :nodoc: */ static VALUE m_df_in_day(union DateData *x) { @@ -1565,7 +1792,7 @@ m_ajd(union DateData *x) if (simple_dat_p(x)) { r = m_real_jd(x); - if (FIXNUM_P(r) && FIX2LONG(r) <= (FIXNUM_MAX / 2)) { + if (FIXNUM_P(r) && FIX2LONG(r) <= (FIXNUM_MAX / 2) && FIX2LONG(r) >= (FIXNUM_MIN + 1) / 2) { long ir = FIX2LONG(r); ir = ir * 2 - 1; return rb_rational_new2(LONG2FIX(ir), INT2FIX(2)); @@ -1928,13 +2155,13 @@ m_sec(union DateData *x) } #define decode_offset(of,s,h,m)\ -{\ +do {\ int a;\ s = (of < 0) ? '-' : '+';\ a = (of < 0) ? -of : of;\ h = a / HOUR_IN_SECONDS;\ m = a % HOUR_IN_SECONDS / MINUTE_IN_SECONDS;\ -} +} while (0) static VALUE of2str(int of) @@ -1985,6 +2212,7 @@ expect_numeric(VALUE x) } #ifndef NDEBUG +/* :nodoc: */ static void civil_to_jd(VALUE y, int m, int d, double sg, VALUE *nth, int *ry, @@ -2297,6 +2525,7 @@ valid_weeknum_p(VALUE y, int w, int d, int f, double sg, } #ifndef NDEBUG +/* :nodoc: */ static int valid_nth_kday_p(VALUE y, int m, int n, int k, double sg, VALUE *nth, int *ry, @@ -2333,6 +2562,9 @@ VALUE date_zone_to_diff(VALUE); static int offset_to_sec(VALUE vof, int *rof) { + int try_rational = 1; + + again: switch (TYPE(vof)) { case T_FIXNUM: { @@ -2359,10 +2591,11 @@ offset_to_sec(VALUE vof, int *rof) 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 + if (!k_rational_p(vof)) { + if (!try_rational) Check_Type(vof, T_RATIONAL); + try_rational = 0; + goto again; + } /* fall through */ case T_RATIONAL: { @@ -2371,17 +2604,10 @@ offset_to_sec(VALUE vof, int *rof) 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; + vn = vs; + goto rounded; } -#endif vn = rb_rational_num(vs); vd = rb_rational_den(vs); @@ -2391,6 +2617,7 @@ offset_to_sec(VALUE vof, int *rof) vn = f_round(vs); if (!f_eqeq_p(vn, vs)) rb_warning("fraction of offset is ignored"); + rounded: if (!FIXNUM_P(vn)) return 0; n = FIX2LONG(vn); @@ -2420,12 +2647,12 @@ offset_to_sec(VALUE vof, int *rof) /* date */ #define valid_sg(sg) \ -{\ +do {\ if (!c_valid_start_p(sg)) {\ sg = 0;\ rb_warning("invalid start is ignored");\ }\ -} +} while (0) static VALUE valid_jd_sub(int argc, VALUE *argv, VALUE klass, int need_jd) @@ -2436,6 +2663,7 @@ valid_jd_sub(int argc, VALUE *argv, VALUE klass, int need_jd) } #ifndef NDEBUG +/* :nodoc: */ static VALUE date_s__valid_jd_p(int argc, VALUE *argv, VALUE klass) { @@ -2456,13 +2684,16 @@ date_s__valid_jd_p(int argc, VALUE *argv, VALUE klass) /* * call-seq: - * Date.valid_jd?(jd[, start=Date::ITALY]) -> bool + * Date.valid_jd?(jd, start = Date::ITALY) -> true * - * Just returns true. It's nonsense, but is for symmetry. + * Implemented for compatibility; + * returns +true+ unless +jd+ is invalid (i.e., not a Numeric). * - * Date.valid_jd?(2451944) #=> true + * Date.valid_jd?(2451944) # => true * - * See also ::jd. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + * + * Related: Date.jd. */ static VALUE date_s_valid_jd_p(int argc, VALUE *argv, VALUE klass) @@ -2472,6 +2703,7 @@ date_s_valid_jd_p(int argc, VALUE *argv, VALUE klass) rb_scan_args(argc, argv, "11", &vjd, &vsg); + RETURN_FALSE_UNLESS_NUMERIC(vjd); argv2[0] = vjd; if (argc < 2) argv2[1] = INT2FIX(DEFAULT_SG); @@ -2521,6 +2753,7 @@ valid_civil_sub(int argc, VALUE *argv, VALUE klass, int need_jd) } #ifndef NDEBUG +/* :nodoc: */ static VALUE date_s__valid_civil_p(int argc, VALUE *argv, VALUE klass) { @@ -2543,15 +2776,18 @@ date_s__valid_civil_p(int argc, VALUE *argv, VALUE klass) /* * call-seq: - * Date.valid_civil?(year, month, mday[, start=Date::ITALY]) -> bool - * Date.valid_date?(year, month, mday[, start=Date::ITALY]) -> bool + * Date.valid_civil?(year, month, mday, start = Date::ITALY) -> true or false + * + * Returns +true+ if the arguments define a valid ordinal date, + * +false+ otherwise: * - * 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 + * Date.valid_date?(2001, 2, -1) # => true * - * Date.valid_date?(2001,2,3) #=> true - * Date.valid_date?(2001,2,29) #=> false + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * - * See also ::jd and ::civil. + * Related: Date.jd, Date.new. */ static VALUE date_s_valid_civil_p(int argc, VALUE *argv, VALUE klass) @@ -2561,6 +2797,9 @@ date_s_valid_civil_p(int argc, VALUE *argv, VALUE klass) rb_scan_args(argc, argv, "31", &vy, &vm, &vd, &vsg); + RETURN_FALSE_UNLESS_NUMERIC(vy); + RETURN_FALSE_UNLESS_NUMERIC(vm); + RETURN_FALSE_UNLESS_NUMERIC(vd); argv2[0] = vy; argv2[1] = vm; argv2[2] = vd; @@ -2604,6 +2843,7 @@ valid_ordinal_sub(int argc, VALUE *argv, VALUE klass, int need_jd) } #ifndef NDEBUG +/* :nodoc: */ static VALUE date_s__valid_ordinal_p(int argc, VALUE *argv, VALUE klass) { @@ -2625,14 +2865,17 @@ date_s__valid_ordinal_p(int argc, VALUE *argv, VALUE klass) /* * call-seq: - * Date.valid_ordinal?(year, yday[, start=Date::ITALY]) -> bool + * Date.valid_ordinal?(year, yday, start = Date::ITALY) -> true or false + * + * Returns +true+ if the arguments define a valid ordinal date, + * +false+ otherwise: * - * 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 * - * Date.valid_ordinal?(2001,34) #=> true - * Date.valid_ordinal?(2001,366) #=> false + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * - * See also ::jd and ::ordinal. + * Related: Date.jd, Date.ordinal. */ static VALUE date_s_valid_ordinal_p(int argc, VALUE *argv, VALUE klass) @@ -2642,6 +2885,8 @@ date_s_valid_ordinal_p(int argc, VALUE *argv, VALUE klass) rb_scan_args(argc, argv, "21", &vy, &vd, &vsg); + RETURN_FALSE_UNLESS_NUMERIC(vy); + RETURN_FALSE_UNLESS_NUMERIC(vd); argv2[0] = vy; argv2[1] = vd; if (argc < 3) @@ -2685,6 +2930,7 @@ valid_commercial_sub(int argc, VALUE *argv, VALUE klass, int need_jd) } #ifndef NDEBUG +/* :nodoc: */ static VALUE date_s__valid_commercial_p(int argc, VALUE *argv, VALUE klass) { @@ -2707,14 +2953,19 @@ date_s__valid_commercial_p(int argc, VALUE *argv, VALUE klass) /* * call-seq: - * Date.valid_commercial?(cwyear, cweek, cwday[, start=Date::ITALY]) -> bool + * Date.valid_commercial?(cwyear, cweek, cwday, start = Date::ITALY) -> true or false + * + * Returns +true+ if the arguments define a valid commercial date, + * +false+ otherwise: * - * 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 * - * Date.valid_commercial?(2001,5,6) #=> true - * Date.valid_commercial?(2001,5,8) #=> false + * See Date.commercial. * - * See also ::jd and ::commercial. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + * + * Related: Date.jd, Date.commercial. */ static VALUE date_s_valid_commercial_p(int argc, VALUE *argv, VALUE klass) @@ -2724,6 +2975,9 @@ date_s_valid_commercial_p(int argc, VALUE *argv, VALUE klass) rb_scan_args(argc, argv, "31", &vy, &vw, &vd, &vsg); + RETURN_FALSE_UNLESS_NUMERIC(vy); + RETURN_FALSE_UNLESS_NUMERIC(vw); + RETURN_FALSE_UNLESS_NUMERIC(vd); argv2[0] = vy; argv2[1] = vw; argv2[2] = vd; @@ -2738,6 +2992,7 @@ date_s_valid_commercial_p(int argc, VALUE *argv, VALUE klass) } #ifndef NDEBUG +/* :nodoc: */ static VALUE valid_weeknum_sub(int argc, VALUE *argv, VALUE klass, int need_jd) { @@ -2769,6 +3024,7 @@ valid_weeknum_sub(int argc, VALUE *argv, VALUE klass, int need_jd) } } +/* :nodoc: */ static VALUE date_s__valid_weeknum_p(int argc, VALUE *argv, VALUE klass) { @@ -2789,6 +3045,7 @@ date_s__valid_weeknum_p(int argc, VALUE *argv, VALUE klass) return valid_weeknum_sub(5, argv2, klass, 1); } +/* :nodoc: */ static VALUE date_s_valid_weeknum_p(int argc, VALUE *argv, VALUE klass) { @@ -2840,6 +3097,7 @@ valid_nth_kday_sub(int argc, VALUE *argv, VALUE klass, int need_jd) } } +/* :nodoc: */ static VALUE date_s__valid_nth_kday_p(int argc, VALUE *argv, VALUE klass) { @@ -2860,6 +3118,7 @@ date_s__valid_nth_kday_p(int argc, VALUE *argv, VALUE klass) return valid_nth_kday_sub(5, argv2, klass, 1); } +/* :nodoc: */ static VALUE date_s_valid_nth_kday_p(int argc, VALUE *argv, VALUE klass) { @@ -2882,6 +3141,7 @@ date_s_valid_nth_kday_p(int argc, VALUE *argv, VALUE klass) return Qtrue; } +/* :nodoc: */ static VALUE date_s_zone_to_diff(VALUE klass, VALUE str) { @@ -2891,13 +3151,15 @@ date_s_zone_to_diff(VALUE klass, VALUE str) /* * call-seq: - * Date.julian_leap?(year) -> bool + * Date.julian_leap?(year) -> true or false * - * Returns true if the given year is a leap year of the proleptic - * Julian calendar. + * Returns +true+ if the given year is a leap year + * in the {proleptic Julian calendar}[https://en.wikipedia.org/wiki/Proleptic_Julian_calendar], +false+ otherwise: * - * Date.julian_leap?(1900) #=> true - * Date.julian_leap?(1901) #=> false + * Date.julian_leap?(1900) # => true + * Date.julian_leap?(1901) # => false + * + * Related: Date.gregorian_leap?. */ static VALUE date_s_julian_leap_p(VALUE klass, VALUE y) @@ -2905,20 +3167,22 @@ date_s_julian_leap_p(VALUE klass, VALUE y) VALUE nth; int ry; + check_numeric(y, "year"); 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 + * Date.gregorian_leap?(year) -> true or false + * + * Returns +true+ if the given year is a leap year + * in the {proleptic Gregorian calendar}[https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar], +false+ otherwise: * - * Returns true if the given year is a leap year of the proleptic - * Gregorian calendar. + * Date.gregorian_leap?(2000) # => true + * Date.gregorian_leap?(2001) # => false * - * Date.gregorian_leap?(1900) #=> false - * Date.gregorian_leap?(2000) #=> true + * Related: Date.julian_leap?. */ static VALUE date_s_gregorian_leap_p(VALUE klass, VALUE y) @@ -2926,6 +3190,7 @@ date_s_gregorian_leap_p(VALUE klass, VALUE y) VALUE nth; int ry; + check_numeric(y, "year"); decode_year(y, -1, &nth, &ry); return f_boolcast(c_gregorian_leap_p(ry)); } @@ -2949,11 +3214,15 @@ d_lite_memsize(const void *ptr) return complex_dat_p(dat) ? sizeof(struct ComplexDateData) : sizeof(struct SimpleDateData); } +#ifndef HAVE_RB_EXT_RACTOR_SAFE +# define RUBY_TYPED_FROZEN_SHAREABLE 0 +#endif + 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, + RUBY_TYPED_FREE_IMMEDIATELY|RUBY_TYPED_WB_PROTECTED|RUBY_TYPED_FROZEN_SHAREABLE, }; inline static VALUE @@ -2968,7 +3237,7 @@ d_simple_new_internal(VALUE klass, 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); + set_to_simple(obj, dat, nth, jd, sg, y, m, d, flags); assert(have_jd_p(dat) || have_civil_p(dat)); @@ -2990,7 +3259,7 @@ d_complex_new_internal(VALUE klass, 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); + y, m, d, h, min, s, flags); assert(have_jd_p(dat) || have_civil_p(dat)); assert(have_df_p(dat) || have_time_p(dat)); @@ -3049,7 +3318,7 @@ old_to_new(VALUE ajd, VALUE of, VALUE sg, *rsg = NUM2DBL(sg); if (*rdf < 0 || *rdf >= DAY_IN_SECONDS) - rb_raise(rb_eArgError, "invalid day fraction"); + rb_raise(eDateError, "invalid day fraction"); if (f_lt_p(*rsf, INT2FIX(0)) || f_ge_p(*rsf, INT2FIX(SECOND_IN_NANOSECONDS))) @@ -3066,6 +3335,7 @@ old_to_new(VALUE ajd, VALUE of, VALUE sg, } #ifndef NDEBUG +/* :nodoc: */ static VALUE date_s_new_bang(int argc, VALUE *argv, VALUE klass) { @@ -3207,62 +3477,75 @@ s_trunc(VALUE s, VALUE *fr) } #define num2num_with_frac(s,n) \ -{\ +do {\ s = s##_trunc(v##s, &fr);\ if (f_nonzero_p(fr)) {\ if (argc > n)\ - rb_raise(rb_eArgError, "invalid fraction");\ + rb_raise(eDateError, "invalid fraction");\ fr2 = fr;\ }\ -} +} while (0) #define num2int_with_frac(s,n) \ -{\ +do {\ s = NUM2INT(s##_trunc(v##s, &fr));\ if (f_nonzero_p(fr)) {\ if (argc > n)\ - rb_raise(rb_eArgError, "invalid fraction");\ + rb_raise(eDateError, "invalid fraction");\ fr2 = fr;\ }\ -} +} while (0) #define canon24oc() \ -{\ +do {\ if (rh == 24) {\ rh = 0;\ fr2 = f_add(fr2, INT2FIX(1));\ }\ -} +} while (0) #define add_frac() \ -{\ +do {\ if (f_nonzero_p(fr2))\ ret = d_lite_plus(ret, fr2);\ -} +} while (0) #define val2sg(vsg,dsg) \ -{\ +do {\ dsg = NUM2DBL(vsg);\ if (!c_valid_start_p(dsg)) {\ dsg = DEFAULT_SG;\ rb_warning("invalid start is ignored");\ }\ -} +} while (0) static VALUE d_lite_plus(VALUE, VALUE); /* * call-seq: - * Date.jd([jd=0[, start=Date::ITALY]]) -> date + * Date.jd(jd = 0, start = Date::ITALY) -> date + * + * Returns a new \Date object formed from the arguments: + * + * Date.jd(2451944).to_s # => "2001-02-03" + * Date.jd(2451945).to_s # => "2001-02-04" + * Date.jd(0).to_s # => "-4712-01-01" + * + * The returned date is: + * + * - Gregorian, if the argument is greater than or equal to +start+: * - * Creates a date object denoting the given chronological Julian day - * number. + * Date::ITALY # => 2299161 + * Date.jd(Date::ITALY).gregorian? # => true + * Date.jd(Date::ITALY + 1).gregorian? # => true * - * Date.jd(2451944) #=> #<Date: 2001-02-03 ...> - * Date.jd(2451945) #=> #<Date: 2001-02-04 ...> - * Date.jd(0) #=> #<Date: -4712-01-01 ...> + * - Julian, otherwise * - * See also ::new. + * Date.jd(Date::ITALY - 1).julian? # => true + * + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + * + * Related: Date.new. */ static VALUE date_s_jd(int argc, VALUE *argv, VALUE klass) @@ -3280,6 +3563,7 @@ date_s_jd(int argc, VALUE *argv, VALUE klass) case 2: val2sg(vsg, sg); case 1: + check_numeric(vjd, "jd"); num2num_with_frac(jd, positive_inf); } @@ -3300,19 +3584,33 @@ date_s_jd(int argc, VALUE *argv, VALUE klass) /* * call-seq: - * Date.ordinal([year=-4712[, yday=1[, start=Date::ITALY]]]) -> date + * Date.ordinal(year = -4712, yday = 1, start = Date::ITALY) -> date + * + * Returns a new \Date object formed fom the arguments. + * + * With no arguments, returns the date for January 1, -4712: + * + * Date.ordinal.to_s # => "-4712-01-01" + * + * With argument +year+, returns the date for January 1 of that year: + * + * Date.ordinal(2001).to_s # => "2001-01-01" + * Date.ordinal(-2001).to_s # => "-2001-01-01" + * + * With positive argument +yday+ == +n+, + * returns the date for the +nth+ day of the given year: + * + * Date.ordinal(2001, 14).to_s # => "2001-01-14" + * + * With negative argument +yday+, counts backward from the end of the year: * - * Creates a date object denoting the given ordinal date. + * Date.ordinal(2001, -14).to_s # => "2001-12-18" * - * 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. + * Raises an exception if +yday+ is zero or out of range. * - * Date.ordinal(2001) #=> #<Date: 2001-01-01 ...> - * Date.ordinal(2001,34) #=> #<Date: 2001-02-03 ...> - * Date.ordinal(2001,-1) #=> #<Date: 2001-12-31 ...> + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * - * See also ::jd and ::new. + * Related: Date.jd, Date.new. */ static VALUE date_s_ordinal(int argc, VALUE *argv, VALUE klass) @@ -3332,8 +3630,10 @@ date_s_ordinal(int argc, VALUE *argv, VALUE klass) case 3: val2sg(vsg, sg); case 2: + check_numeric(vd, "yday"); num2int_with_frac(d, positive_inf); case 1: + check_numeric(vy, "year"); y = vy; } @@ -3345,7 +3645,7 @@ date_s_ordinal(int argc, VALUE *argv, VALUE klass) &nth, &ry, &rd, &rjd, &ns)) - rb_raise(rb_eArgError, "invalid date"); + rb_raise(eDateError, "invalid date"); ret = d_simple_new_internal(klass, nth, rjd, @@ -3358,36 +3658,48 @@ date_s_ordinal(int argc, VALUE *argv, VALUE klass) } /* + * Same as Date.new. + */ +static VALUE +date_s_civil(int argc, VALUE *argv, VALUE klass) +{ + return date_initialize(argc, argv, d_lite_s_alloc_simple(klass)); +} + +/* * 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 + * Date.new(year = -4712, month = 1, mday = 1, start = Date::ITALY) -> date + * + * Returns a new \Date object constructed from the given arguments: + * + * Date.new(2022).to_s # => "2022-01-01" + * Date.new(2022, 2).to_s # => "2022-02-01" + * Date.new(2022, 2, 4).to_s # => "2022-02-04" * - * Creates a date object denoting the given calendar date. + * Argument +month+ should be in range (1..12) or range (-12..-1); + * when the argument is negative, counts backward from the end of the year: * - * 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. + * Date.new(2022, -11, 4).to_s # => "2022-02-04" * - * 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. + * Argument +mday+ should be in range (1..n) or range (-n..-1) + * where +n+ is the number of days in the month; + * when the argument is negative, counts backward from the end of the month. * - * 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 argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * - * See also ::jd. + * Related: Date.jd. */ static VALUE -date_s_civil(int argc, VALUE *argv, VALUE klass) +date_initialize(int argc, VALUE *argv, VALUE self) { VALUE vy, vm, vd, vsg, y, fr, fr2, ret; int m, d; double sg; + struct SimpleDateData *dat = rb_check_typeddata(self, &d_lite_type); + + if (!simple_dat_p(dat)) { + rb_raise(rb_eTypeError, "Date expected"); + } rb_scan_args(argc, argv, "04", &vy, &vm, &vd, &vsg); @@ -3401,10 +3713,13 @@ date_s_civil(int argc, VALUE *argv, VALUE klass) case 4: val2sg(vsg, sg); case 3: + check_numeric(vd, "day"); num2int_with_frac(d, positive_inf); case 2: + check_numeric(vm, "month"); m = NUM2INT(vm); case 1: + check_numeric(vy, "year"); y = vy; } @@ -3415,13 +3730,9 @@ date_s_civil(int argc, VALUE *argv, VALUE klass) if (!valid_gregorian_p(y, m, d, &nth, &ry, &rm, &rd)) - rb_raise(rb_eArgError, "invalid date"); + rb_raise(eDateError, "invalid date"); - ret = d_simple_new_internal(klass, - nth, 0, - sg, - ry, rm, rd, - HAVE_CIVIL); + set_to_simple(self, dat, nth, 0, sg, ry, rm, rd, HAVE_CIVIL); } else { VALUE nth; @@ -3431,33 +3742,58 @@ date_s_civil(int argc, VALUE *argv, VALUE klass) &nth, &ry, &rm, &rd, &rjd, &ns)) - rb_raise(rb_eArgError, "invalid date"); + rb_raise(eDateError, "invalid date"); - ret = d_simple_new_internal(klass, - nth, rjd, - sg, - ry, rm, rd, - HAVE_JD | HAVE_CIVIL); + set_to_simple(self, dat, nth, rjd, sg, ry, rm, rd, HAVE_JD | HAVE_CIVIL); } + ret = self; add_frac(); return ret; } /* * call-seq: - * Date.commercial([cwyear=-4712[, cweek=1[, cwday=1[, start=Date::ITALY]]]]) -> date + * Date.commercial(cwyear = -4712, cweek = 1, cwday = 1, start = Date::ITALY) -> date + * + * Returns a new \Date object constructed from the arguments. + * + * Argument +cwyear+ gives the year, and should be an integer. * - * Creates a date object denoting the given week date. + * Argument +cweek+ gives the index of the week within the year, + * and should be in range (1..53) or (-53..-1); + * in some years, 53 or -53 will be out-of-range; + * if negative, counts backward from the end of the year: * - * 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(2022, 1, 1).to_s # => "2022-01-03" + * Date.commercial(2022, 52, 1).to_s # => "2022-12-26" * - * Date.commercial(2001) #=> #<Date: 2001-01-01 ...> - * Date.commercial(2002) #=> #<Date: 2001-12-31 ...> - * Date.commercial(2001,5,6) #=> #<Date: 2001-02-03 ...> + * Argument +cwday+ gives the indes of the weekday within the week, + * and should be in range (1..7) or (-7..-1); + * 1 or -7 is Monday; + * if negative, counts backward from the end of the week: * - * See also ::jd and ::new. + * Date.commercial(2022, 1, 1).to_s # => "2022-01-03" + * Date.commercial(2022, 1, -7).to_s # => "2022-01-03" + * + * When +cweek+ is 1: + * + * - If January 1 is a Friday, Saturday, or Sunday, + * the first week begins in the week after: + * + * Date::ABBR_DAYNAMES[Date.new(2023, 1, 1).wday] # => "Sun" + * Date.commercial(2023, 1, 1).to_s # => "2023-01-02" + Date.commercial(2023, 1, 7).to_s # => "2023-01-08" + * + * - Otherwise, the first week is the week of January 1, + * which may mean some of the days fall on the year before: + * + * Date::ABBR_DAYNAMES[Date.new(2020, 1, 1).wday] # => "Wed" + * Date.commercial(2020, 1, 1).to_s # => "2019-12-30" + Date.commercial(2020, 1, 7).to_s # => "2020-01-05" + * + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + * + * Related: Date.jd, Date.new, Date.ordinal. */ static VALUE date_s_commercial(int argc, VALUE *argv, VALUE klass) @@ -3478,10 +3814,13 @@ date_s_commercial(int argc, VALUE *argv, VALUE klass) case 4: val2sg(vsg, sg); case 3: + check_numeric(vd, "cwday"); num2int_with_frac(d, positive_inf); case 2: + check_numeric(vw, "cweek"); w = NUM2INT(vw); case 1: + check_numeric(vy, "year"); y = vy; } @@ -3493,7 +3832,7 @@ date_s_commercial(int argc, VALUE *argv, VALUE klass) &nth, &ry, &rw, &rd, &rjd, &ns)) - rb_raise(rb_eArgError, "invalid date"); + rb_raise(eDateError, "invalid date"); ret = d_simple_new_internal(klass, nth, rjd, @@ -3506,6 +3845,7 @@ date_s_commercial(int argc, VALUE *argv, VALUE klass) } #ifndef NDEBUG +/* :nodoc: */ static VALUE date_s_weeknum(int argc, VALUE *argv, VALUE klass) { @@ -3543,7 +3883,7 @@ date_s_weeknum(int argc, VALUE *argv, VALUE klass) &nth, &ry, &rw, &rd, &rjd, &ns)) - rb_raise(rb_eArgError, "invalid date"); + rb_raise(eDateError, "invalid date"); ret = d_simple_new_internal(klass, nth, rjd, @@ -3555,6 +3895,7 @@ date_s_weeknum(int argc, VALUE *argv, VALUE klass) return ret; } +/* :nodoc: */ static VALUE date_s_nth_kday(int argc, VALUE *argv, VALUE klass) { @@ -3592,7 +3933,7 @@ date_s_nth_kday(int argc, VALUE *argv, VALUE klass) &nth, &ry, &rm, &rn, &rk, &rjd, &ns)) - rb_raise(rb_eArgError, "invalid date"); + rb_raise(eDateError, "invalid date"); ret = d_simple_new_internal(klass, nth, rjd, @@ -3629,11 +3970,14 @@ static void set_sg(union DateData *, double); /* * call-seq: - * Date.today([start=Date::ITALY]) -> date + * Date.today(start = Date::ITALY) -> date * - * Creates a date object denoting the present day. + * Returns a new \Date object constructed from the present date: + * + * Date.today.to_s # => "2022-07-06" + * + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * - * Date.today #=> #<Date: 2011-06-11 ...> */ static VALUE date_s_today(int argc, VALUE *argv, VALUE klass) @@ -3679,16 +4023,18 @@ date_s_today(int argc, VALUE *argv, VALUE klass) #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))) +#define sym(x) ID2SYM(rb_intern(x"")) + +#define set_hash(k,v) set_hash0(sym(k), v) +#define ref_hash(k) ref_hash0(sym(k)) +#define del_hash(k) del_hash0(sym(k)) static VALUE rt_rewrite_frags(VALUE hash) { VALUE seconds; - seconds = ref_hash("seconds"); + seconds = del_hash("seconds"); if (!NIL_P(seconds)) { VALUE offset, d, h, min, s, fr; @@ -3713,13 +4059,10 @@ rt_rewrite_frags(VALUE hash) 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); @@ -3728,94 +4071,93 @@ 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, + tab = f_frozen_ary(11, + f_frozen_ary(2, sym("time"), - rb_ary_new3(3, + f_frozen_ary(3, sym("hour"), sym("min"), sym("sec"))), - rb_ary_new3(2, + f_frozen_ary(2, Qnil, - rb_ary_new3(1, + f_frozen_ary(1, sym("jd"))), - rb_ary_new3(2, + f_frozen_ary(2, sym("ordinal"), - rb_ary_new3(5, + f_frozen_ary(5, sym("year"), sym("yday"), sym("hour"), sym("min"), sym("sec"))), - rb_ary_new3(2, + f_frozen_ary(2, sym("civil"), - rb_ary_new3(6, + f_frozen_ary(6, sym("year"), sym("mon"), sym("mday"), sym("hour"), sym("min"), sym("sec"))), - rb_ary_new3(2, + f_frozen_ary(2, sym("commercial"), - rb_ary_new3(6, + f_frozen_ary(6, sym("cwyear"), sym("cweek"), sym("cwday"), sym("hour"), sym("min"), sym("sec"))), - rb_ary_new3(2, + f_frozen_ary(2, sym("wday"), - rb_ary_new3(4, + f_frozen_ary(4, sym("wday"), sym("hour"), sym("min"), sym("sec"))), - rb_ary_new3(2, + f_frozen_ary(2, sym("wnum0"), - rb_ary_new3(6, + f_frozen_ary(6, sym("year"), sym("wnum0"), sym("wday"), sym("hour"), sym("min"), sym("sec"))), - rb_ary_new3(2, + f_frozen_ary(2, sym("wnum1"), - rb_ary_new3(6, + f_frozen_ary(6, sym("year"), sym("wnum1"), sym("wday"), sym("hour"), sym("min"), sym("sec"))), - rb_ary_new3(2, + f_frozen_ary(2, Qnil, - rb_ary_new3(6, + f_frozen_ary(6, sym("cwyear"), sym("cweek"), sym("wday"), sym("hour"), sym("min"), sym("sec"))), - rb_ary_new3(2, + f_frozen_ary(2, Qnil, - rb_ary_new3(6, + f_frozen_ary(6, sym("year"), sym("wnum0"), sym("cwday"), sym("hour"), sym("min"), sym("sec"))), - rb_ary_new3(2, + f_frozen_ary(2, Qnil, - rb_ary_new3(6, + f_frozen_ary(6, sym("year"), sym("wnum1"), sym("cwday"), @@ -3825,9 +4167,13 @@ rt_complete_frags(VALUE klass, VALUE hash) rb_gc_register_mark_object(tab); } + k = a = Qnil; + { - long i, eno = 0, idx = 0; + long i, eno = 0; + VALUE t = Qnil; + e = 0; for (i = 0; i < RARRAY_LEN(tab); i++) { VALUE x, a; @@ -3842,23 +4188,20 @@ rt_complete_frags(VALUE klass, VALUE hash) n++; if (n > eno) { eno = n; - idx = i; + t = x; } } } - 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; + if (eno > 0) { + k = RARRAY_AREF(t, 0); + a = RARRAY_AREF(t, 1); } + e = eno; } d = Qnil; - if (g && !NIL_P(k) && (RARRAY_LEN(a) - e)) { + if (!NIL_P(k) && (RARRAY_LEN(a) > e)) { if (k == sym("ordinal")) { if (NIL_P(ref_hash("year"))) { if (NIL_P(d)) @@ -3945,7 +4288,7 @@ rt_complete_frags(VALUE klass, VALUE hash) } } - if (g && k == sym("time")) { + if (k == sym("time")) { if (f_le_p(klass, cDateTime)) { if (NIL_P(d)) d = date_s_today(0, (VALUE *)0, cDate); @@ -4140,7 +4483,7 @@ d_new_by_frags(VALUE klass, VALUE hash, VALUE sg) } if (NIL_P(hash)) - rb_raise(rb_eArgError, "invalid date"); + rb_raise(eDateError, "invalid date"); if (NIL_P(ref_hash("jd")) && NIL_P(ref_hash("yday")) && @@ -4157,7 +4500,7 @@ d_new_by_frags(VALUE klass, VALUE hash, VALUE sg) } if (NIL_P(jd)) - rb_raise(rb_eArgError, "invalid date"); + rb_raise(eDateError, "invalid date"); { VALUE nth; int rjd; @@ -4185,6 +4528,7 @@ date_s__strptime_internal(int argc, VALUE *argv, VALUE klass, rb_scan_args(argc, argv, "11", &vstr, &vfmt); StringValue(vstr); + if (argc > 1) StringValue(vfmt); if (!rb_enc_str_asciicompat_p(vstr)) rb_raise(rb_eArgError, "string should have ASCII compatible encoding"); @@ -4195,7 +4539,6 @@ date_s__strptime_internal(int argc, VALUE *argv, VALUE klass, flen = strlen(default_fmt); } else { - StringValue(vfmt); if (!rb_enc_str_asciicompat_p(vfmt)) rb_raise(rb_eArgError, "format should have ASCII compatible encoding"); @@ -4212,12 +4555,10 @@ date_s__strptime_internal(int argc, VALUE *argv, VALUE klass, 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); } } @@ -4227,16 +4568,20 @@ date_s__strptime_internal(int argc, VALUE *argv, VALUE klass, /* * call-seq: - * Date._strptime(string[, format='%F']) -> hash + * 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. + * Returns a hash of values parsed from +string+ + * according to the given +format+: * - * Date._strptime('2001-02-03', '%Y-%m-%d') - * #=> {:year=>2001, :mon=>2, :mday=>3} + * Date._strptime('2001-02-03', '%Y-%m-%d') # => {:year=>2001, :mon=>2, :mday=>3} * - * See also strptime(3) and #strftime. + * For other formats, see + * {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]. + * (Unlike Date.strftime, does not support flags and width.) + * + * See also {strptime(3)}[https://man7.org/linux/man-pages/man3/strptime.3.html]. + * + * Related: Date.strptime (returns a \Date object). */ static VALUE date_s__strptime(int argc, VALUE *argv, VALUE klass) @@ -4246,21 +4591,28 @@ date_s__strptime(int argc, VALUE *argv, VALUE klass) /* * call-seq: - * Date.strptime([string='-4712-01-01'[, format='%F'[, start=Date::ITALY]]]) -> date + * Date.strptime(string = '-4712-01-01', format = '%F', start = Date::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. + * Returns a new \Date object with values parsed from +string+, + * according to the given +format+: * - * 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 ...> + * 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. + * For other formats, see + * {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]. + * (Unlike Date.strftime, does not support flags and width.) + * + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + * + * See also {strptime(3)}[https://man7.org/linux/man-pages/man3/strptime.3.html]. + * + * Related: Date._strptime (returns a hash). */ static VALUE date_s_strptime(int argc, VALUE *argv, VALUE klass) @@ -4271,7 +4623,7 @@ date_s_strptime(int argc, VALUE *argv, VALUE klass) switch (argc) { case 0: - str = rb_str_new2("-4712-01-01"); + str = rb_str_new2(JULIAN_EPOCH_DATE); case 1: fmt = rb_str_new2("%F"); case 2: @@ -4290,13 +4642,42 @@ date_s_strptime(int argc, VALUE *argv, VALUE klass) VALUE date__parse(VALUE str, VALUE comp); +static size_t +get_limit(VALUE opt) +{ + if (!NIL_P(opt)) { + VALUE limit = rb_hash_aref(opt, ID2SYM(rb_intern("limit"))); + if (NIL_P(limit)) return SIZE_MAX; + return NUM2SIZET(limit); + } + return 128; +} + +#ifndef HAVE_RB_CATEGORY_WARN +#define rb_category_warn(category, fmt) rb_warn(fmt) +#endif + +static VALUE +check_limit(VALUE str, VALUE opt) +{ + size_t slen, limit; + StringValue(str); + slen = RSTRING_LEN(str); + limit = get_limit(opt); + if (slen > limit) { + rb_raise(rb_eArgError, + "string length (%"PRI_SIZE_PREFIX"u) exceeds the limit %"PRI_SIZE_PREFIX"u", slen, limit); + } + return str; +} + static VALUE date_s__parse_internal(int argc, VALUE *argv, VALUE klass) { - VALUE vstr, vcomp, hash; + VALUE vstr, vcomp, hash, opt; - rb_scan_args(argc, argv, "11", &vstr, &vcomp); - StringValue(vstr); + argc = rb_scan_args(argc, argv, "11:", &vstr, &vcomp, &opt); + vstr = check_limit(vstr, opt); if (!rb_enc_str_asciicompat_p(vstr)) rb_raise(rb_eArgError, "string should have ASCII compatible encoding"); @@ -4305,32 +4686,37 @@ date_s__parse_internal(int argc, VALUE *argv, VALUE klass) 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 + * Date._parse(string, comp = true, limit: 128) -> hash * - * Parses the given representation of date and time, and returns a - * hash of parsed elements. This method does not function as a - * validator. + * <b>Note</b>: + * This method recognizes many forms in +string+, + * but it is not a validator. + * For formats, see + * {"Specialized Format Strings" in Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc@Specialized+Format+Strings] * - * 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. + * If +string+ does not specify a valid date, + * the result is unpredictable; + * consider using Date._strptime instead. + * + * Returns a hash of values parsed from +string+: + * + * Date._parse('2001-02-03') # => {:year=>2001, :mon=>2, :mday=>3} + * + * If +comp+ is +true+ and the given year is in the range <tt>(0..99)</tt>, + * the current century is supplied; + * otherwise, the year is taken as given: + * + * Date._parse('01-02-03', true) # => {:year=>2001, :mon=>2, :mday=>3} + * Date._parse('01-02-03', false) # => {:year=>1, :mon=>2, :mday=>3} * - * Date._parse('2001-02-03') #=> {:year=>2001, :mon=>2, :mday=>3} + * See argument {limit}[rdoc-ref:Date@Argument+limit]. + * + * Related: Date.parse(returns a \Date object). */ static VALUE date_s__parse(int argc, VALUE *argv, VALUE klass) @@ -4340,29 +4726,47 @@ date_s__parse(int argc, VALUE *argv, VALUE klass) /* * call-seq: - * Date.parse(string='-4712-01-01'[, comp=true[, start=Date::ITALY]]) -> date + * Date.parse(string = '-4712-01-01', comp = true, start = Date::ITALY, limit: 128) -> date * - * Parses the given representation of date and time, and creates a - * date object. This method does not function as a validator. + * <b>Note</b>: + * This method recognizes many forms in +string+, + * but it is not a validator. + * For formats, see + * {"Specialized Format Strings" in Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc@Specialized+Format+Strings] + * If +string+ does not specify a valid date, + * the result is unpredictable; + * consider using Date._strptime instead. * - * 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. + * Returns a new \Date object with values parsed from +string+: + * + * 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> + * + * If +comp+ is +true+ and the given year is in the range <tt>(0..99)</tt>, + * the current century is supplied; + * otherwise, the year is taken as given: * - * 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 ...> + * Date.parse('01-02-03', true) # => #<Date: 2001-02-03> + * Date.parse('01-02-03', false) # => #<Date: 0001-02-03> + * + * See: + * + * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + * - Argument {limit}[rdoc-ref:Date@Argument+limit]. + * + * Related: Date._parse (returns a hash). */ static VALUE date_s_parse(int argc, VALUE *argv, VALUE klass) { - VALUE str, comp, sg; + VALUE str, comp, sg, opt; - rb_scan_args(argc, argv, "03", &str, &comp, &sg); + argc = rb_scan_args(argc, argv, "03:", &str, &comp, &sg, &opt); switch (argc) { case 0: - str = rb_str_new2("-4712-01-01"); + str = rb_str_new2(JULIAN_EPOCH_DATE); case 1: comp = Qtrue; case 2: @@ -4370,11 +4774,12 @@ date_s_parse(int argc, VALUE *argv, VALUE klass) } { - VALUE argv2[2], hash; - - argv2[0] = str; - argv2[1] = comp; - hash = date_s__parse(2, argv2, klass); + int argc2 = 2; + VALUE argv2[3], hash; + argv2[0] = str; + argv2[1] = comp; + if (!NIL_P(opt)) argv2[argc2++] = opt; + hash = date_s__parse(argc2, argv2, klass); return d_new_by_frags(klass, hash, sg); } } @@ -4388,252 +4793,417 @@ VALUE date__jisx0301(VALUE); /* * call-seq: - * Date._iso8601(string) -> hash + * Date._iso8601(string, limit: 128) -> hash + * + * Returns a hash of values parsed from +string+, which should contain + * an {ISO 8601 formatted date}[rdoc-ref:language/strftime_formatting.rdoc@ISO+8601+Format+Specifications]: + * + * d = Date.new(2001, 2, 3) + * s = d.iso8601 # => "2001-02-03" + * Date._iso8601(s) # => {:mday=>3, :year=>2001, :mon=>2} + * + * See argument {limit}[rdoc-ref:Date@Argument+limit]. * - * Returns a hash of parsed elements. + * Related: Date.iso8601 (returns a \Date object). */ static VALUE -date_s__iso8601(VALUE klass, VALUE str) +date_s__iso8601(int argc, VALUE *argv, VALUE klass) { + VALUE str, opt; + + rb_scan_args(argc, argv, "1:", &str, &opt); + if (!NIL_P(str)) str = check_limit(str, opt); + return date__iso8601(str); } /* * call-seq: - * Date.iso8601(string='-4712-01-01'[, start=Date::ITALY]) -> date + * Date.iso8601(string = '-4712-01-01', start = Date::ITALY, limit: 128) -> date * - * Creates a new Date object by parsing from a string according to - * some typical ISO 8601 formats. + * Returns a new \Date object with values parsed from +string+, + * which should contain + * an {ISO 8601 formatted date}[rdoc-ref:language/strftime_formatting.rdoc@ISO+8601+Format+Specifications]: + * + * d = Date.new(2001, 2, 3) + * s = d.iso8601 # => "2001-02-03" + * Date.iso8601(s) # => #<Date: 2001-02-03> + * + * See: * - * 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 ...> + * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + * - Argument {limit}[rdoc-ref:Date@Argument+limit]. + * + * Related: Date._iso8601 (returns a hash). */ static VALUE date_s_iso8601(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); switch (argc) { case 0: - str = rb_str_new2("-4712-01-01"); + str = rb_str_new2(JULIAN_EPOCH_DATE); case 1: sg = INT2FIX(DEFAULT_SG); } { - VALUE hash = date_s__iso8601(klass, str); + int argc2 = 1; + VALUE argv2[2], hash; + argv2[0] = str; + if (!NIL_P(opt)) argv2[argc2++] = opt; + hash = date_s__iso8601(argc2, argv2, klass); return d_new_by_frags(klass, hash, sg); } } /* * call-seq: - * Date._rfc3339(string) -> hash + * Date._rfc3339(string, limit: 128) -> hash + * + * Returns a hash of values parsed from +string+, which should be a valid + * {RFC 3339 format}[rdoc-ref:language/strftime_formatting.rdoc@RFC+3339+Format]: + * + * d = Date.new(2001, 2, 3) + * s = d.rfc3339 # => "2001-02-03T00:00:00+00:00" + * Date._rfc3339(s) + * # => {:year=>2001, :mon=>2, :mday=>3, :hour=>0, :min=>0, :sec=>0, :zone=>"+00:00", :offset=>0} * - * Returns a hash of parsed elements. + * See argument {limit}[rdoc-ref:Date@Argument+limit]. + * + * Related: Date.rfc3339 (returns a \Date object). */ static VALUE -date_s__rfc3339(VALUE klass, VALUE str) +date_s__rfc3339(int argc, VALUE *argv, VALUE klass) { + VALUE str, opt; + + rb_scan_args(argc, argv, "1:", &str, &opt); + if (!NIL_P(str)) str = check_limit(str, opt); + return date__rfc3339(str); } /* * call-seq: - * Date.rfc3339(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY]) -> date + * Date.rfc3339(string = '-4712-01-01T00:00:00+00:00', start = Date::ITALY, limit: 128) -> date * - * Creates a new Date object by parsing from a string according to - * some typical RFC 3339 formats. + * Returns a new \Date object with values parsed from +string+, + * which should be a valid + * {RFC 3339 format}[rdoc-ref:language/strftime_formatting.rdoc@RFC+3339+Format]: + * + * d = Date.new(2001, 2, 3) + * s = d.rfc3339 # => "2001-02-03T00:00:00+00:00" + * Date.rfc3339(s) # => #<Date: 2001-02-03> + * + * See: * - * Date.rfc3339('2001-02-03T04:05:06+07:00') #=> #<Date: 2001-02-03 ...> + * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + * - Argument {limit}[rdoc-ref:Date@Argument+limit]. + * + * Related: Date._rfc3339 (returns a hash). */ static VALUE date_s_rfc3339(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); switch (argc) { case 0: - str = rb_str_new2("-4712-01-01T00:00:00+00:00"); + str = rb_str_new2(JULIAN_EPOCH_DATETIME); case 1: sg = INT2FIX(DEFAULT_SG); } { - VALUE hash = date_s__rfc3339(klass, str); + int argc2 = 1; + VALUE argv2[2], hash; + argv2[0] = str; + if (!NIL_P(opt)) argv2[argc2++] = opt; + hash = date_s__rfc3339(argc2, argv2, klass); return d_new_by_frags(klass, hash, sg); } } /* * call-seq: - * Date._xmlschema(string) -> hash + * Date._xmlschema(string, limit: 128) -> hash + * + * Returns a hash of values parsed from +string+, which should be a valid + * XML date format: + * + * d = Date.new(2001, 2, 3) + * s = d.xmlschema # => "2001-02-03" + * Date._xmlschema(s) # => {:year=>2001, :mon=>2, :mday=>3} * - * Returns a hash of parsed elements. + * See argument {limit}[rdoc-ref:Date@Argument+limit]. + * + * Related: Date.xmlschema (returns a \Date object). */ static VALUE -date_s__xmlschema(VALUE klass, VALUE str) +date_s__xmlschema(int argc, VALUE *argv, VALUE klass) { + VALUE str, opt; + + rb_scan_args(argc, argv, "1:", &str, &opt); + if (!NIL_P(str)) str = check_limit(str, opt); + return date__xmlschema(str); } /* * call-seq: - * Date.xmlschema(string='-4712-01-01'[, start=Date::ITALY]) -> date + * Date.xmlschema(string = '-4712-01-01', start = Date::ITALY, limit: 128) -> date * - * Creates a new Date object by parsing from a string according to - * some typical XML Schema formats. + * Returns a new \Date object with values parsed from +string+, + * which should be a valid XML date format: + * + * d = Date.new(2001, 2, 3) + * s = d.xmlschema # => "2001-02-03" + * Date.xmlschema(s) # => #<Date: 2001-02-03> + * + * See: * - * Date.xmlschema('2001-02-03') #=> #<Date: 2001-02-03 ...> + * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + * - Argument {limit}[rdoc-ref:Date@Argument+limit]. + * + * Related: Date._xmlschema (returns a hash). */ static VALUE date_s_xmlschema(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); switch (argc) { case 0: - str = rb_str_new2("-4712-01-01"); + str = rb_str_new2(JULIAN_EPOCH_DATE); case 1: sg = INT2FIX(DEFAULT_SG); } { - VALUE hash = date_s__xmlschema(klass, str); + int argc2 = 1; + VALUE argv2[2], hash; + argv2[0] = str; + if (!NIL_P(opt)) argv2[argc2++] = opt; + hash = date_s__xmlschema(argc2, argv2, klass); return d_new_by_frags(klass, hash, sg); } } /* * call-seq: - * Date._rfc2822(string) -> hash - * Date._rfc822(string) -> hash + * Date._rfc2822(string, limit: 128) -> hash + * + * Returns a hash of values parsed from +string+, which should be a valid + * {RFC 2822 date format}[rdoc-ref:language/strftime_formatting.rdoc@RFC+2822+Format]: + * + * d = Date.new(2001, 2, 3) + * s = d.rfc2822 # => "Sat, 3 Feb 2001 00:00:00 +0000" + * Date._rfc2822(s) + * # => {:wday=>6, :mday=>3, :mon=>2, :year=>2001, :hour=>0, :min=>0, :sec=>0, :zone=>"+0000", :offset=>0} * - * Returns a hash of parsed elements. + * See argument {limit}[rdoc-ref:Date@Argument+limit]. + * + * Related: Date.rfc2822 (returns a \Date object). */ static VALUE -date_s__rfc2822(VALUE klass, VALUE str) +date_s__rfc2822(int argc, VALUE *argv, VALUE klass) { + VALUE str, opt; + + rb_scan_args(argc, argv, "1:", &str, &opt); + if (!NIL_P(str)) str = check_limit(str, opt); + return date__rfc2822(str); } /* * call-seq: - * Date.rfc2822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY]) -> date - * Date.rfc822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY]) -> date + * Date.rfc2822(string = 'Mon, 1 Jan -4712 00:00:00 +0000', start = Date::ITALY, limit: 128) -> date * - * Creates a new Date object by parsing from a string according to - * some typical RFC 2822 formats. + * Returns a new \Date object with values parsed from +string+, + * which should be a valid + * {RFC 2822 date format}[rdoc-ref:language/strftime_formatting.rdoc@RFC+2822+Format]: + * + * d = Date.new(2001, 2, 3) + * s = d.rfc2822 # => "Sat, 3 Feb 2001 00:00:00 +0000" + * Date.rfc2822(s) # => #<Date: 2001-02-03> + * + * See: * - * Date.rfc2822('Sat, 3 Feb 2001 00:00:00 +0000') - * #=> #<Date: 2001-02-03 ...> + * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + * - Argument {limit}[rdoc-ref:Date@Argument+limit]. + * + * Related: Date._rfc2822 (returns a hash). */ static VALUE date_s_rfc2822(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); switch (argc) { case 0: - str = rb_str_new2("Mon, 1 Jan -4712 00:00:00 +0000"); + str = rb_str_new2(JULIAN_EPOCH_DATETIME_RFC3339); case 1: sg = INT2FIX(DEFAULT_SG); } { - VALUE hash = date_s__rfc2822(klass, str); + int argc2 = 1; + VALUE argv2[2], hash; + argv2[0] = str; + if (!NIL_P(opt)) argv2[argc2++] = opt; + hash = date_s__rfc2822(argc2, argv2, klass); return d_new_by_frags(klass, hash, sg); } } /* * call-seq: - * Date._httpdate(string) -> hash + * Date._httpdate(string, limit: 128) -> hash + * + * Returns a hash of values parsed from +string+, which should be a valid + * {HTTP date format}[rdoc-ref:language/strftime_formatting.rdoc@HTTP+Format]: + * + * d = Date.new(2001, 2, 3) + * s = d.httpdate # => "Sat, 03 Feb 2001 00:00:00 GMT" + * Date._httpdate(s) + * # => {:wday=>6, :mday=>3, :mon=>2, :year=>2001, :hour=>0, :min=>0, :sec=>0, :zone=>"GMT", :offset=>0} * - * Returns a hash of parsed elements. + * Related: Date.httpdate (returns a \Date object). */ static VALUE -date_s__httpdate(VALUE klass, VALUE str) +date_s__httpdate(int argc, VALUE *argv, VALUE klass) { + VALUE str, opt; + + rb_scan_args(argc, argv, "1:", &str, &opt); + if (!NIL_P(str)) str = check_limit(str, opt); + return date__httpdate(str); } /* * call-seq: - * Date.httpdate(string='Mon, 01 Jan -4712 00:00:00 GMT'[, start=Date::ITALY]) -> date + * Date.httpdate(string = 'Mon, 01 Jan -4712 00:00:00 GMT', start = Date::ITALY, limit: 128) -> date * - * Creates a new Date object by parsing from a string according to - * some RFC 2616 format. + * Returns a new \Date object with values parsed from +string+, + * which should be a valid + * {HTTP date format}[rdoc-ref:language/strftime_formatting.rdoc@HTTP+Format]: + * + * d = Date.new(2001, 2, 3) + s = d.httpdate # => "Sat, 03 Feb 2001 00:00:00 GMT" + Date.httpdate(s) # => #<Date: 2001-02-03> + * + * See: * - * Date.httpdate('Sat, 03 Feb 2001 00:00:00 GMT') - * #=> #<Date: 2001-02-03 ...> + * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + * - Argument {limit}[rdoc-ref:Date@Argument+limit]. + * + * Related: Date._httpdate (returns a hash). */ static VALUE date_s_httpdate(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); switch (argc) { case 0: - str = rb_str_new2("Mon, 01 Jan -4712 00:00:00 GMT"); + str = rb_str_new2(JULIAN_EPOCH_DATETIME_HTTPDATE); case 1: sg = INT2FIX(DEFAULT_SG); } { - VALUE hash = date_s__httpdate(klass, str); + int argc2 = 1; + VALUE argv2[2], hash; + argv2[0] = str; + if (!NIL_P(opt)) argv2[argc2++] = opt; + hash = date_s__httpdate(argc2, argv2, klass); return d_new_by_frags(klass, hash, sg); } } /* * call-seq: - * Date._jisx0301(string) -> hash + * Date._jisx0301(string, limit: 128) -> hash + * + * Returns a hash of values parsed from +string+, which should be a valid + * {JIS X 0301 date format}[rdoc-ref:language/strftime_formatting.rdoc@JIS+X+0301+Format]: * - * Returns a hash of parsed elements. + * d = Date.new(2001, 2, 3) + * s = d.jisx0301 # => "H13.02.03" + * Date._jisx0301(s) # => {:year=>2001, :mon=>2, :mday=>3} + * + * See argument {limit}[rdoc-ref:Date@Argument+limit]. + * + * Related: Date.jisx0301 (returns a \Date object). */ static VALUE -date_s__jisx0301(VALUE klass, VALUE str) +date_s__jisx0301(int argc, VALUE *argv, VALUE klass) { + VALUE str, opt; + + rb_scan_args(argc, argv, "1:", &str, &opt); + if (!NIL_P(str)) str = check_limit(str, opt); + return date__jisx0301(str); } /* * call-seq: - * Date.jisx0301(string='-4712-01-01'[, start=Date::ITALY]) -> date + * Date.jisx0301(string = '-4712-01-01', start = Date::ITALY, limit: 128) -> date * - * Creates a new Date object by parsing from a string according to - * some typical JIS X 0301 formats. + * Returns a new \Date object with values parsed from +string+, + * which should be a valid {JIS X 0301 format}[rdoc-ref:language/strftime_formatting.rdoc@JIS+X+0301+Format]: + * + * d = Date.new(2001, 2, 3) + * s = d.jisx0301 # => "H13.02.03" + * Date.jisx0301(s) # => #<Date: 2001-02-03> + * + * For no-era year, legacy format, Heisei is assumed. + * + * Date.jisx0301('13.02.03') # => #<Date: 2001-02-03> + * + * See: * - * Date.jisx0301('H13.02.03') #=> #<Date: 2001-02-03 ...> + * - Argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. + * - Argument {limit}[rdoc-ref:Date@Argument+limit]. + * + * Related: Date._jisx0301 (returns a hash). */ static VALUE date_s_jisx0301(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); switch (argc) { case 0: - str = rb_str_new2("-4712-01-01"); + str = rb_str_new2(JULIAN_EPOCH_DATE); case 1: sg = INT2FIX(DEFAULT_SG); } { - VALUE hash = date_s__jisx0301(klass, str); + int argc2 = 1; + VALUE argv2[2], hash; + argv2[0] = str; + if (!NIL_P(opt)) argv2[argc2++] = opt; + hash = date_s__jisx0301(argc2, argv2, klass); return d_new_by_frags(klass, hash, sg); } } @@ -4691,14 +5261,14 @@ dup_obj_as_complex(VALUE self) } #define val2off(vof,iof) \ -{\ +do {\ if (!offset_to_sec(vof, &iof)) {\ iof = 0;\ rb_warning("invalid offset is ignored");\ }\ -} +} while (0) -#ifndef NDEBUG +#if 0 static VALUE d_lite_initialize(int argc, VALUE *argv, VALUE self) { @@ -4707,7 +5277,6 @@ d_lite_initialize(int argc, VALUE *argv, VALUE self) double sg; rb_check_frozen(self); - rb_check_trusted(self); rb_scan_args(argc, argv, "05", &vjd, &vdf, &vsf, &vof, &vsg); @@ -4726,11 +5295,11 @@ d_lite_initialize(int argc, VALUE *argv, VALUE self) sf = vsf; if (f_lt_p(sf, INT2FIX(0)) || f_ge_p(sf, INT2FIX(SECOND_IN_NANOSECONDS))) - rb_raise(rb_eArgError, "invalid second fraction"); + rb_raise(eDateError, "invalid second fraction"); case 2: df = NUM2INT(vdf); if (df < 0 || df >= DAY_IN_SECONDS) - rb_raise(rb_eArgError, "invalid day fraction"); + rb_raise(eDateError, "invalid day fraction"); case 1: jd = vjd; } @@ -4751,7 +5320,7 @@ d_lite_initialize(int argc, VALUE *argv, VALUE self) "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); + 0, 0, 0, 0, 0, 0, HAVE_JD | HAVE_DF); } } return self; @@ -4763,15 +5332,34 @@ 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; + if (simple_dat_p(adat)) { + adat->s = bdat->s; + } + else { + adat->c.flags = bdat->s.flags | COMPLEX_DAT; + adat->c.nth = bdat->s.nth; + adat->c.jd = bdat->s.jd; + adat->c.df = 0; + adat->c.sf = INT2FIX(0); + adat->c.of = 0; + adat->c.sg = bdat->s.sg; + adat->c.year = bdat->s.year; +#ifndef USE_PACK + adat->c.mon = bdat->s.mon; + adat->c.mday = bdat->s.mday; + adat->c.hour = bdat->s.hour; + adat->c.min = bdat->s.min; + adat->c.sec = bdat->s.sec; +#else + adat->c.pc = bdat->s.pc; +#endif + } } else { if (!complex_dat_p(adat)) @@ -4779,13 +5367,13 @@ d_lite_initialize_copy(VALUE copy, VALUE date) "cannot load complex into simple"); adat->c = bdat->c; - adat->c.flags |= COMPLEX_DAT; } } return copy; } #ifndef NDEBUG +/* :nodoc: */ static VALUE d_lite_fill(VALUE self) { @@ -4875,12 +5463,15 @@ d_lite_mjd(VALUE self) /* * call-seq: - * d.ld -> integer + * ld -> integer * - * Returns the Lilian day number. This is a whole number, which is - * adjusted by the offset as the local time. + * Returns the + * {Lilian day number}[https://en.wikipedia.org/wiki/Lilian_date], + * which is the number of days since the beginning of the Gregorian + * calendar, October 15, 1582. + * + * Date.new(2001, 2, 3).ld # => 152784 * - * Date.new(2001,2,3).ld #=> 152784 */ static VALUE d_lite_ld(VALUE self) @@ -4891,12 +5482,13 @@ d_lite_ld(VALUE self) /* * call-seq: - * d.year -> integer + * year -> integer * - * Returns the year. + * Returns the year: + * + * Date.new(2001, 2, 3).year # => 2001 + * (Date.new(1, 1, 1) - 1).year # => 0 * - * Date.new(2001,2,3).year #=> 2001 - * (Date.new(1,1,1) - 1).year #=> 0 */ static VALUE d_lite_year(VALUE self) @@ -4907,11 +5499,12 @@ d_lite_year(VALUE self) /* * call-seq: - * d.yday -> fixnum + * yday -> integer + * + * Returns the day of the year, in range (1..366): * - * Returns the day of the year (1-366). + * Date.new(2001, 2, 3).yday # => 34 * - * Date.new(2001,2,3).yday #=> 34 */ static VALUE d_lite_yday(VALUE self) @@ -4922,12 +5515,12 @@ d_lite_yday(VALUE self) /* * call-seq: - * d.mon -> fixnum - * d.month -> fixnum + * mon -> integer * - * Returns the month (1-12). + * Returns the month in range (1..12): + * + * Date.new(2001, 2, 3).mon # => 2 * - * Date.new(2001,2,3).mon #=> 2 */ static VALUE d_lite_mon(VALUE self) @@ -4938,12 +5531,12 @@ d_lite_mon(VALUE self) /* * call-seq: - * d.mday -> fixnum - * d.day -> fixnum + * mday -> integer + * + * Returns the day of the month in range (1..31): * - * Returns the day of the month (1-31). + * Date.new(2001, 2, 3).mday # => 3 * - * Date.new(2001,2,3).mday #=> 3 */ static VALUE d_lite_mday(VALUE self) @@ -4954,11 +5547,12 @@ d_lite_mday(VALUE self) /* * call-seq: - * d.day_fraction -> rational + * day_fraction -> rational * - * Returns the fractional part of the day. + * Returns the fractional part of the day in range (Rational(0, 1)...Rational(1, 1)): + * + * DateTime.new(2001,2,3,12).day_fraction # => (1/2) * - * DateTime.new(2001,2,3,12).day_fraction #=> (1/2) */ static VALUE d_lite_day_fraction(VALUE self) @@ -4971,12 +5565,14 @@ d_lite_day_fraction(VALUE self) /* * call-seq: - * d.cwyear -> integer + * cwyear -> integer + * + * Returns commercial-date year for +self+ + * (see Date.commercial): * - * Returns the calendar week based year. + * Date.new(2001, 2, 3).cwyear # => 2001 + * Date.new(2000, 1, 1).cwyear # => 1999 * - * Date.new(2001,2,3).cwyear #=> 2001 - * Date.new(2000,1,1).cwyear #=> 1999 */ static VALUE d_lite_cwyear(VALUE self) @@ -4987,11 +5583,13 @@ d_lite_cwyear(VALUE self) /* * call-seq: - * d.cweek -> fixnum + * cweek -> integer * - * Returns the calendar week number (1-53). + * Returns commercial-date week index for +self+ + * (see Date.commercial): + * + * Date.new(2001, 2, 3).cweek # => 5 * - * Date.new(2001,2,3).cweek #=> 5 */ static VALUE d_lite_cweek(VALUE self) @@ -5002,11 +5600,14 @@ d_lite_cweek(VALUE self) /* * call-seq: - * d.cwday -> fixnum + * cwday -> integer + * + * Returns the commercial-date weekday index for +self+ + * (see Date.commercial); + * 1 is Monday: * - * Returns the day of calendar week (1-7, Monday is 1). + * Date.new(2001, 2, 3).cwday # => 6 * - * Date.new(2001,2,3).cwday #=> 6 */ static VALUE d_lite_cwday(VALUE self) @@ -5016,6 +5617,7 @@ d_lite_cwday(VALUE self) } #ifndef NDEBUG +/* :nodoc: */ static VALUE d_lite_wnum0(VALUE self) { @@ -5023,6 +5625,7 @@ d_lite_wnum0(VALUE self) return INT2FIX(m_wnum0(dat)); } +/* :nodoc: */ static VALUE d_lite_wnum1(VALUE self) { @@ -5033,11 +5636,12 @@ d_lite_wnum1(VALUE self) /* * call-seq: - * d.wday -> fixnum + * wday -> integer + * + * Returns the day of week in range (0..6); Sunday is 0: * - * Returns the day of week (0-6, Sunday is zero). + * Date.new(2001, 2, 3).wday # => 6 * - * Date.new(2001,2,3).wday #=> 6 */ static VALUE d_lite_wday(VALUE self) @@ -5048,9 +5652,9 @@ d_lite_wday(VALUE self) /* * call-seq: - * d.sunday? -> bool + * sunday? -> true or false * - * Returns true if the date is Sunday. + * Returns +true+ if +self+ is a Sunday, +false+ otherwise. */ static VALUE d_lite_sunday_p(VALUE self) @@ -5061,9 +5665,9 @@ d_lite_sunday_p(VALUE self) /* * call-seq: - * d.monday? -> bool + * monday? -> true or false * - * Returns true if the date is Monday. + * Returns +true+ if +self+ is a Monday, +false+ otherwise. */ static VALUE d_lite_monday_p(VALUE self) @@ -5074,9 +5678,9 @@ d_lite_monday_p(VALUE self) /* * call-seq: - * d.tuesday? -> bool + * tuesday? -> true or false * - * Returns true if the date is Tuesday. + * Returns +true+ if +self+ is a Tuesday, +false+ otherwise. */ static VALUE d_lite_tuesday_p(VALUE self) @@ -5087,9 +5691,9 @@ d_lite_tuesday_p(VALUE self) /* * call-seq: - * d.wednesday? -> bool + * wednesday? -> true or false * - * Returns true if the date is Wednesday. + * Returns +true+ if +self+ is a Wednesday, +false+ otherwise. */ static VALUE d_lite_wednesday_p(VALUE self) @@ -5100,9 +5704,9 @@ d_lite_wednesday_p(VALUE self) /* * call-seq: - * d.thursday? -> bool + * thursday? -> true or false * - * Returns true if the date is Thursday. + * Returns +true+ if +self+ is a Thursday, +false+ otherwise. */ static VALUE d_lite_thursday_p(VALUE self) @@ -5113,9 +5717,9 @@ d_lite_thursday_p(VALUE self) /* * call-seq: - * d.friday? -> bool + * friday? -> true or false * - * Returns true if the date is Friday. + * Returns +true+ if +self+ is a Friday, +false+ otherwise. */ static VALUE d_lite_friday_p(VALUE self) @@ -5126,9 +5730,9 @@ d_lite_friday_p(VALUE self) /* * call-seq: - * d.saturday? -> bool + * saturday? -> true or false * - * Returns true if the date is Saturday. + * Returns +true+ if +self+ is a Saturday, +false+ otherwise. */ static VALUE d_lite_saturday_p(VALUE self) @@ -5138,6 +5742,7 @@ d_lite_saturday_p(VALUE self) } #ifndef NDEBUG +/* :nodoc: */ static VALUE d_lite_nth_kday_p(VALUE self, VALUE n, VALUE k) { @@ -5159,11 +5764,12 @@ d_lite_nth_kday_p(VALUE self, VALUE n, VALUE k) /* * call-seq: - * d.hour -> fixnum + * hour -> integer + * + * Returns the hour in range (0..23): * - * Returns the hour (0-23). + * DateTime.new(2001, 2, 3, 4, 5, 6).hour # => 4 * - * DateTime.new(2001,2,3,4,5,6).hour #=> 4 */ static VALUE d_lite_hour(VALUE self) @@ -5174,12 +5780,12 @@ d_lite_hour(VALUE self) /* * call-seq: - * d.min -> fixnum - * d.minute -> fixnum + * min -> integer + * + * Returns the minute in range (0..59): * - * Returns the minute (0-59). + * DateTime.new(2001, 2, 3, 4, 5, 6).min # => 5 * - * DateTime.new(2001,2,3,4,5,6).min #=> 5 */ static VALUE d_lite_min(VALUE self) @@ -5190,12 +5796,12 @@ d_lite_min(VALUE self) /* * call-seq: - * d.sec -> fixnum - * d.second -> fixnum + * sec -> integer * - * Returns the second (0-59). + * Returns the second in range (0..59): + * + * DateTime.new(2001, 2, 3, 4, 5, 6).sec # => 6 * - * DateTime.new(2001,2,3,4,5,6).sec #=> 6 */ static VALUE d_lite_sec(VALUE self) @@ -5206,12 +5812,13 @@ d_lite_sec(VALUE self) /* * call-seq: - * d.sec_fraction -> rational - * d.second_fraction -> rational + * sec_fraction -> rational + * + * Returns the fractional part of the second in range + * (Rational(0, 1)...Rational(1, 1)): * - * Returns the fractional part of the second. + * DateTime.new(2001, 2, 3, 4, 5, 6.5).sec_fraction # => (1/2) * - * DateTime.new(2001,2,3,4,5,6.5).sec_fraction #=> (1/2) */ static VALUE d_lite_sec_fraction(VALUE self) @@ -5252,12 +5859,14 @@ d_lite_zone(VALUE self) /* * call-seq: - * d.julian? -> bool + * d.julian? -> true or false * - * Returns true if the date is before the day of calendar reform. + * Returns +true+ if the date is before the date of calendar reform, + * +false+ otherwise: + * + * (Date.new(1582, 10, 15) - 1).julian? # => true + * Date.new(1582, 10, 15).julian? # => false * - * Date.new(1582,10,15).julian? #=> false - * (Date.new(1582,10,15) - 1).julian? #=> true */ static VALUE d_lite_julian_p(VALUE self) @@ -5268,12 +5877,14 @@ d_lite_julian_p(VALUE self) /* * call-seq: - * d.gregorian? -> bool + * gregorian? -> true or false + * + * Returns +true+ if the date is on or after + * the date of calendar reform, +false+ otherwise: * - * 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 * - * Date.new(1582,10,15).gregorian? #=> true - * (Date.new(1582,10,15) - 1).gregorian? #=> false */ static VALUE d_lite_gregorian_p(VALUE self) @@ -5284,12 +5895,13 @@ d_lite_gregorian_p(VALUE self) /* * call-seq: - * d.leap? -> bool + * leap? -> true or false + * + * Returns +true+ if the year is a leap year, +false+ otherwise: * - * Returns true if the year is a leap year. + * Date.new(2000).leap? # => true + * Date.new(2001).leap? # => false * - * Date.new(2000).leap? #=> true - * Date.new(2001).leap? #=> false */ static VALUE d_lite_leap_p(VALUE self) @@ -5308,12 +5920,25 @@ d_lite_leap_p(VALUE self) /* * call-seq: - * d.start -> float + * start -> float + * + * Returns the Julian start date for calendar reform; + * if not an infinity, the returned value is suitable + * for passing to Date#jd: + * + * d = Date.new(2001, 2, 3, Date::ITALY) + * s = d.start # => 2299161.0 + * Date.jd(s).to_s # => "1582-10-15" * - * Returns the Julian day number denoting the day of calendar reform. + * d = Date.new(2001, 2, 3, Date::ENGLAND) + * s = d.start # => 2361222.0 + * Date.jd(s).to_s # => "1752-09-14" + * + * Date.new(2001, 2, 3, Date::GREGORIAN).start # => -Infinity + * Date.new(2001, 2, 3, Date::JULIAN).start # => Infinity + * + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * - * Date.new(2001,2,3).start #=> 2299161.0 - * Date.new(2001,2,3,Date::GREGORIAN).start #=> -Infinity */ static VALUE d_lite_start(VALUE self) @@ -5378,12 +6003,17 @@ dup_obj_with_new_start(VALUE obj, double sg) /* * call-seq: - * d.new_start([start=Date::ITALY]) -> date + * new_start(start = Date::ITALY) -> new_date + * + * Returns a copy of +self+ with the given +start+ value: + * + * d0 = Date.new(2000, 2, 3) + * d0.julian? # => false + * d1 = d0.new_start(Date::JULIAN) + * d1.julian? # => true * - * Duplicates self and resets its day of calendar reform. + * See argument {start}[rdoc-ref:language/calendars.rdoc@Argument+start]. * - * 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) @@ -5402,9 +6032,10 @@ d_lite_new_start(int argc, VALUE *argv, VALUE self) /* * call-seq: - * d.italy -> date + * italy -> new_date + * + * Equivalent to Date#new_start with argument Date::ITALY. * - * This method is equivalent to new_start(Date::ITALY). */ static VALUE d_lite_italy(VALUE self) @@ -5414,9 +6045,9 @@ d_lite_italy(VALUE self) /* * call-seq: - * d.england -> date + * england -> new_date * - * This method is equivalent to new_start(Date::ENGLAND). + * Equivalent to Date#new_start with argument Date::ENGLAND. */ static VALUE d_lite_england(VALUE self) @@ -5426,9 +6057,9 @@ d_lite_england(VALUE self) /* * call-seq: - * d.julian -> date + * julian -> new_date * - * This method is equivalent to new_start(Date::JULIAN). + * Equivalent to Date#new_start with argument Date::JULIAN. */ static VALUE d_lite_julian(VALUE self) @@ -5438,9 +6069,9 @@ d_lite_julian(VALUE self) /* * call-seq: - * d.gregorian -> date + * gregorian -> new_date * - * This method is equivalent to new_start(Date::GREGORIAN). + * Equivalent to Date#new_start with argument Date::GREGORIAN. */ static VALUE d_lite_gregorian(VALUE self) @@ -5513,8 +6144,10 @@ d_lite_new_offset(int argc, VALUE *argv, VALUE self) static VALUE d_lite_plus(VALUE self, VALUE other) { + int try_rational = 1; get_d1(self); + again: switch (TYPE(other)) { case T_FIXNUM: { @@ -5724,18 +6357,21 @@ d_lite_plus(VALUE self, VALUE other) 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 + if (!k_rational_p(other)) { + if (!try_rational) Check_Type(other, T_RATIONAL); + try_rational = 0; + goto again; + } /* 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 (wholenum_p(other)) { + other = rb_rational_num(other); + goto again; + } if (f_positive_p(other)) s = +1; @@ -5881,9 +6517,11 @@ minus_dd(VALUE self, VALUE other) * 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 a fractional number, + * If the other is a date object, returns a Rational + * whose value is the difference between the two dates in days. + * If the other is a numeric value, returns a date object + * pointing +other+ days before self. + * If the other is a fractional number, * assumes its precision is at most nanosecond. * * Date.new(2001,2,3) - 1 #=> #<Date: 2001-02-02 ...> @@ -5916,9 +6554,9 @@ d_lite_minus(VALUE self, VALUE other) /* * call-seq: - * d.next_day([n=1]) -> date + * next_day(n = 1) -> new_date * - * This method is equivalent to d + n. + * Equivalent to Date#+ with argument +n+. */ static VALUE d_lite_next_day(int argc, VALUE *argv, VALUE self) @@ -5933,9 +6571,9 @@ d_lite_next_day(int argc, VALUE *argv, VALUE self) /* * call-seq: - * d.prev_day([n=1]) -> date + * prev_day(n = 1) -> new_date * - * This method is equivalent to d - n. + * Equivalent to Date#- with argument +n+. */ static VALUE d_lite_prev_day(int argc, VALUE *argv, VALUE self) @@ -5950,10 +6588,14 @@ d_lite_prev_day(int argc, VALUE *argv, VALUE self) /* * call-seq: - * d.succ -> date - * d.next -> date + * d.next -> new_date + * + * Returns a new \Date object representing the following day: + * + * d = Date.new(2001, 2, 3) + * d.to_s # => "2001-02-03" + * d.next.to_s # => "2001-02-04" * - * Returns a date object denoting the following day. */ static VALUE d_lite_next(VALUE self) @@ -5963,26 +6605,30 @@ d_lite_next(VALUE self) /* * call-seq: - * d >> n -> date + * d >> n -> new_date + * + * Returns a new \Date object representing the date + * +n+ months later; +n+ should be a numeric: * - * Returns a date object pointing +n+ months after self. - * The argument +n+ should be a numeric value. + * (Date.new(2001, 2, 3) >> 1).to_s # => "2001-03-03" + * (Date.new(2001, 2, 3) >> -2).to_s # => "2000-12-03" * - * Date.new(2001,2,3) >> 1 #=> #<Date: 2001-03-03 ...> - * Date.new(2001,2,3) >> -2 #=> #<Date: 2000-12-03 ...> + * When the same day does not exist for the new month, + * the last day of that month is used instead: * - * When the same day does not exist for the corresponding month, - * the last day of the month is used instead: + * (Date.new(2001, 1, 31) >> 1).to_s # => "2001-02-28" + * (Date.new(2001, 1, 31) >> -4).to_s # => "2000-09-30" * - * Date.new(2001,1,28) >> 1 #=> #<Date: 2001-02-28 ...> - * Date.new(2001,1,31) >> 1 #=> #<Date: 2001-02-28 ...> + * This results in the following, possibly unexpected, behaviors: * - * This also results in the following, possibly unexpected, behavior: + * d0 = Date.new(2001, 1, 31) + * d1 = d0 >> 1 # => #<Date: 2001-02-28> + * d2 = d1 >> 1 # => #<Date: 2001-03-28> * - * Date.new(2001,1,31) >> 2 #=> #<Date: 2001-03-31 ...> - * Date.new(2001,1,31) >> 1 >> 1 #=> #<Date: 2001-03-28 ...> + * d0 = Date.new(2001, 1, 31) + * d1 = d0 >> 1 # => #<Date: 2001-02-28> + * d2 = d1 >> -1 # => #<Date: 2001-01-28> * - * Date.new(2001,1,31) >> 1 >> -1 #=> #<Date: 2001-01-28 ...> */ static VALUE d_lite_rshift(VALUE self, VALUE other) @@ -6017,7 +6663,7 @@ d_lite_rshift(VALUE self, VALUE other) &rm, &rd, &rjd, &ns)) break; if (--d < 1) - rb_raise(rb_eArgError, "invalid date"); + rb_raise(eDateError, "invalid date"); } encode_jd(nth, rjd, &rjd2); return d_lite_plus(self, f_sub(rjd2, m_real_local_jd(dat))); @@ -6027,24 +6673,28 @@ d_lite_rshift(VALUE self, VALUE other) * call-seq: * d << n -> date * - * Returns a date object pointing +n+ months before self. - * The argument +n+ should be a numeric value. + * Returns a new \Date object representing the date + * +n+ months earlier; +n+ should be a numeric: + * + * (Date.new(2001, 2, 3) << 1).to_s # => "2001-01-03" + * (Date.new(2001, 2, 3) << -2).to_s # => "2001-04-03" * - * Date.new(2001,2,3) << 1 #=> #<Date: 2001-01-03 ...> - * Date.new(2001,2,3) << -2 #=> #<Date: 2001-04-03 ...> + * When the same day does not exist for the new month, + * the last day of that month is used instead: * - * When the same day does not exist for the corresponding month, - * the last day of the month is used instead: + * (Date.new(2001, 3, 31) << 1).to_s # => "2001-02-28" + * (Date.new(2001, 3, 31) << -6).to_s # => "2001-09-30" * - * Date.new(2001,3,28) << 1 #=> #<Date: 2001-02-28 ...> - * Date.new(2001,3,31) << 1 #=> #<Date: 2001-02-28 ...> + * This results in the following, possibly unexpected, behaviors: * - * This also results in the following, possibly unexpected, behavior: + * d0 = Date.new(2001, 3, 31) + * d0 << 2 # => #<Date: 2001-01-31> + * d0 << 1 << 1 # => #<Date: 2001-01-28> * - * Date.new(2001,3,31) << 2 #=> #<Date: 2001-01-31 ...> - * Date.new(2001,3,31) << 1 << 1 #=> #<Date: 2001-01-28 ...> + * d0 = Date.new(2001, 3, 31) + * d1 = d0 << 1 # => #<Date: 2001-02-28> + * d2 = d1 << -1 # => #<Date: 2001-03-28> * - * Date.new(2001,3,31) << 1 << -1 #=> #<Date: 2001-03-28 ...> */ static VALUE d_lite_lshift(VALUE self, VALUE other) @@ -6055,11 +6705,9 @@ d_lite_lshift(VALUE self, VALUE other) /* * call-seq: - * d.next_month([n=1]) -> date + * next_month(n = 1) -> new_date * - * This method is equivalent to d >> n. - * - * See Date#>> for examples. + * Equivalent to #>> with argument +n+. */ static VALUE d_lite_next_month(int argc, VALUE *argv, VALUE self) @@ -6074,11 +6722,9 @@ d_lite_next_month(int argc, VALUE *argv, VALUE self) /* * call-seq: - * d.prev_month([n=1]) -> date - * - * This method is equivalent to d << n. + * prev_month(n = 1) -> new_date * - * See Date#<< for examples. + * Equivalent to #<< with argument +n+. */ static VALUE d_lite_prev_month(int argc, VALUE *argv, VALUE self) @@ -6093,15 +6739,9 @@ d_lite_prev_month(int argc, VALUE *argv, VALUE self) /* * call-seq: - * d.next_year([n=1]) -> date + * next_year(n = 1) -> new_date * - * This method is equivalent to d >> (n * 12). - * - * Date.new(2001,2,3).next_year #=> #<Date: 2002-02-03 ...> - * Date.new(2008,2,29).next_year #=> #<Date: 2009-02-28 ...> - * Date.new(2008,2,29).next_year(4) #=> #<Date: 2012-02-29 ...> - * - * See also Date#>>. + * Equivalent to #>> with argument <tt>n * 12</tt>. */ static VALUE d_lite_next_year(int argc, VALUE *argv, VALUE self) @@ -6116,15 +6756,9 @@ d_lite_next_year(int argc, VALUE *argv, VALUE self) /* * call-seq: - * d.prev_year([n=1]) -> date - * - * This method is equivalent to d << (n * 12). + * prev_year(n = 1) -> new_date * - * Date.new(2001,2,3).prev_year #=> #<Date: 2000-02-03 ...> - * Date.new(2008,2,29).prev_year #=> #<Date: 2007-02-28 ...> - * Date.new(2008,2,29).prev_year(4) #=> #<Date: 2004-02-29 ...> - * - * See also Date#<<. + * Equivalent to #<< with argument <tt>n * 12</tt>. */ static VALUE d_lite_prev_year(int argc, VALUE *argv, VALUE self) @@ -6141,19 +6775,39 @@ static VALUE d_lite_cmp(VALUE, VALUE); /* * call-seq: - * d.step(limit[, step=1]) -> enumerator - * d.step(limit[, step=1]){|date| ...} -> self + * step(limit, step = 1){|date| ... } -> self + * + * Calls the block with specified dates; + * returns +self+. + * + * - The first +date+ is +self+. + * - Each successive +date+ is <tt>date + step</tt>, + * where +step+ is the numeric step size in days. + * - The last date is the last one that is before or equal to +limit+, + * which should be a \Date object. + * + * Example: + * + * limit = Date.new(2001, 12, 31) + * Date.new(2001).step(limit){|date| p date.to_s if date.mday == 31 } + * + * Output: * - * Iterates evaluation of the given block, which takes a date object. - * The limit should be a date object. + * "2001-01-31" + * "2001-03-31" + * "2001-05-31" + * "2001-07-31" + * "2001-08-31" + * "2001-10-31" + * "2001-12-31" * - * Date.new(2001).step(Date.new(2001,-1,-1)).select{|d| d.sunday?}.size - * #=> 52 + * Returns an Enumerator if no block is given. */ static VALUE d_lite_step(int argc, VALUE *argv, VALUE self) { VALUE limit, step, date; + int c; rb_scan_args(argc, argv, "11", &limit, &step); @@ -6168,35 +6822,31 @@ d_lite_step(int argc, VALUE *argv, VALUE self) RETURN_ENUMERATOR(self, argc, argv); date = self; - switch (FIX2INT(f_cmp(step, INT2FIX(0)))) { - case -1: + c = f_cmp(step, INT2FIX(0)); + if (c < 0) { while (FIX2INT(d_lite_cmp(date, limit)) >= 0) { rb_yield(date); date = d_lite_plus(date, step); } - break; - case 0: + } + else if (c == 0) { while (1) rb_yield(date); - break; - case 1: + } + else /* if (c > 0) */ { 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 + * upto(max){|date| ... } -> self * - * This method is equivalent to step(max, 1){|date| ...}. + * Equivalent to #step with arguments +max+ and +1+. */ static VALUE d_lite_upto(VALUE self, VALUE max) @@ -6215,10 +6865,9 @@ d_lite_upto(VALUE self, VALUE max) /* * call-seq: - * d.downto(min) -> enumerator - * d.downto(min){|date| ...} -> self + * downto(min){|date| ... } -> self * - * This method is equivalent to step(min, -1){|date| ...}. + * Equivalent to #step with arguments +min+ and <tt>-1</tt>. */ static VALUE d_lite_downto(VALUE self, VALUE min) @@ -6241,10 +6890,10 @@ cmp_gen(VALUE self, VALUE other) get_d1(self); if (k_numeric_p(other)) - return f_cmp(m_ajd(dat), other); + return INT2FIX(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("<=>")); + return INT2FIX(f_cmp(m_ajd(dat), f_ajd(other))); + return rb_num_coerce_cmp(self, other, id_cmp); } static VALUE @@ -6306,19 +6955,43 @@ cmp_dd(VALUE self, VALUE other) /* * call-seq: - * d <=> other -> -1, 0, +1 or nil + * self <=> other -> -1, 0, 1 or nil + * + * Compares +self+ and +other+, returning: + * + * - <tt>-1</tt> if +other+ is larger. + * - <tt>0</tt> if the two are equal. + * - <tt>1</tt> if +other+ is smaller. + * - +nil+ if the two are incomparable. * - * 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. + * Argument +other+ may be: * - * 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 + * - Another \Date object: + * + * d = Date.new(2022, 7, 27) # => #<Date: 2022-07-27 ((2459788j,0s,0n),+0s,2299161j)> + * prev_date = d.prev_day # => #<Date: 2022-07-26 ((2459787j,0s,0n),+0s,2299161j)> + * next_date = d.next_day # => #<Date: 2022-07-28 ((2459789j,0s,0n),+0s,2299161j)> + * d <=> next_date # => -1 + * d <=> d # => 0 + * d <=> prev_date # => 1 + * + * - A DateTime object: + * + * d <=> DateTime.new(2022, 7, 26) # => 1 + * d <=> DateTime.new(2022, 7, 27) # => 0 + * d <=> DateTime.new(2022, 7, 28) # => -1 + * + * - A numeric (compares <tt>self.ajd</tt> to +other+): + * + * d <=> 2459788 # => -1 + * d <=> 2459787 # => 1 + * d <=> 2459786 # => 1 + * d <=> d.ajd # => 0 + * + * - Any other object: + * + * d <=> Object.new # => nil * - * See also Comparable. */ static VALUE d_lite_cmp(VALUE self, VALUE other) @@ -6373,25 +7046,44 @@ equal_gen(VALUE self, VALUE 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("==")); + return rb_num_coerce_cmp(self, other, id_eqeq_p); } /* * 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 + * self === other -> true, false, or nil. + * + * Returns +true+ if +self+ and +other+ represent the same date, + * +false+ if not, +nil+ if the two are not comparable. + * + * Argument +other+ may be: + * + * - Another \Date object: + * + * d = Date.new(2022, 7, 27) # => #<Date: 2022-07-27 ((2459788j,0s,0n),+0s,2299161j)> + * prev_date = d.prev_day # => #<Date: 2022-07-26 ((2459787j,0s,0n),+0s,2299161j)> + * next_date = d.next_day # => #<Date: 2022-07-28 ((2459789j,0s,0n),+0s,2299161j)> + * d === prev_date # => false + * d === d # => true + * d === next_date # => false + * + * - A DateTime object: + * + * d === DateTime.new(2022, 7, 26) # => false + * d === DateTime.new(2022, 7, 27) # => true + * d === DateTime.new(2022, 7, 28) # => false + * + * - A numeric (compares <tt>self.jd</tt> to +other+): + * + * d === 2459788 # => true + * d === 2459787 # => false + * d === 2459786 # => false + * d === d.jd # => true + * + * - An object not comparable: + * + * d === Object.new # => nil + * */ static VALUE d_lite_equal(VALUE self, VALUE other) @@ -6436,13 +7128,24 @@ d_lite_eql_p(VALUE self, VALUE other) static VALUE d_lite_hash(VALUE self) { - st_index_t v, h[4]; + st_index_t v, h[5]; + VALUE nth; get_d1(self); - h[0] = m_nth(dat); - h[1] = m_jd(dat); - h[2] = m_df(dat); - h[3] = m_sf(dat); + nth = m_nth(dat); + + if (FIXNUM_P(nth)) { + h[0] = 0; + h[1] = (st_index_t)nth; + } else { + h[0] = 1; + h[1] = (st_index_t)FIX2LONG(rb_hash(nth)); + } + + h[2] = m_jd(dat); + h[3] = m_df(dat); + h[4] = m_sf(dat); + v = rb_memhash(h, sizeof(h)); return ST2FIX(v); } @@ -6454,12 +7157,14 @@ static VALUE strftimev(const char *, VALUE, /* * call-seq: - * d.to_s -> string + * to_s -> string * - * Returns a string in an ISO 8601 format. (This method doesn't use the - * expanded representations.) + * Returns a string representation of the date in +self+ + * in {ISO 8601 extended date format}[rdoc-ref:language/strftime_formatting.rdoc@ISO+8601+Format+Specifications] + * (<tt>'%Y-%m-%d'</tt>): + * + * Date.new(2001, 2, 3).to_s # => "2001-02-03" * - * Date.new(2001,2,3).to_s #=> "2001-02-03" */ static VALUE d_lite_to_s(VALUE self) @@ -6468,10 +7173,11 @@ d_lite_to_s(VALUE self) } #ifndef NDEBUG +/* :nodoc: */ static VALUE mk_inspect_raw(union DateData *x, VALUE klass) { - char flags[5]; + char flags[6]; flags[0] = (x->flags & COMPLEX_DAT) ? 'C' : 'S'; flags[1] = (x->flags & HAVE_JD) ? 'j' : '-'; @@ -6517,6 +7223,7 @@ mk_inspect_raw(union DateData *x, VALUE klass) } } +/* :nodoc: */ static VALUE d_lite_inspect_raw(VALUE self) { @@ -6538,14 +7245,13 @@ mk_inspect(union DateData *x, VALUE klass, VALUE to_s) /* * call-seq: - * d.inspect -> string + * inspect -> string + * + * Returns a string representation of +self+: * - * Returns the value as a string for inspection. + * Date.new(2001, 2, 3).inspect + * # => "#<Date: 2001-02-03 ((2451944j,0s,0n),+0s,2299161j)>" * - * 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) @@ -6637,7 +7343,9 @@ tmx_m_of(union DateData *x) static char * tmx_m_zone(union DateData *x) { - return RSTRING_PTR(m_zone(x)); + VALUE zone = m_zone(x); + /* TODO: fix potential dangling pointer */ + return RSTRING_PTR(zone); } static const struct tmx_funcs tmx_funcs = { @@ -6712,7 +7420,6 @@ date_strftime_internal(int argc, VALUE *argv, VALUE self, if (p > fmt) rb_str_cat(str, fmt, p - fmt); } rb_enc_copy(str, vfmt); - OBJ_INFECT(str, vfmt); return str; } else @@ -6721,186 +7428,21 @@ date_strftime_internal(int argc, VALUE *argv, VALUE self, 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 begin with a percent (%) character. - * Any text not listed as a directive will be passed through to the - * output string. - * - * A directive consists of a percent (%) character, - * zero or more flags, an optional minimum field width, - * an 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 - Equivalent to %:z (e.g. +09:00) - * - * 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 the 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 the 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. + * strftime(format = '%F') -> string + * + * Returns a string representation of the date in +self+, + * formatted according the given +format+: + * + * Date.new(2001, 2, 3).strftime # => "2001-02-03" + * + * For other formats, see + * {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]. + * */ static VALUE d_lite_strftime(int argc, VALUE *argv, VALUE self) @@ -6928,13 +7470,16 @@ strftimev(const char *fmt, VALUE self, /* * call-seq: - * d.asctime -> string - * d.ctime -> string + * asctime -> string + * + * Equivalent to #strftime with argument <tt>'%a %b %e %T %Y'</tt> + * (or its {shorthand form}[rdoc-ref:language/strftime_formatting.rdoc@Shorthand+Conversion+Specifiers] + * <tt>'%c'</tt>): + * + * Date.new(2001, 2, 3).asctime # => "Sat Feb 3 00:00:00 2001" * - * Returns a string in asctime(3) format (but without "\n\0" at the - * end). This method is equivalent to strftime('%c'). + * See {asctime}[https://man7.org/linux/man-pages/man3/asctime.3p.html]. * - * See also asctime(3) or ctime(3). */ static VALUE d_lite_asctime(VALUE self) @@ -6944,10 +7489,14 @@ d_lite_asctime(VALUE self) /* * call-seq: - * d.iso8601 -> string - * d.xmlschema -> string + * iso8601 -> string + * + * Equivalent to #strftime with argument <tt>'%Y-%m-%d'</tt> + * (or its {shorthand form}[rdoc-ref:language/strftime_formatting.rdoc@Shorthand+Conversion+Specifiers] + * <tt>'%F'</tt>); + * + * Date.new(2001, 2, 3).iso8601 # => "2001-02-03" * - * This method is equivalent to strftime('%F'). */ static VALUE d_lite_iso8601(VALUE self) @@ -6957,9 +7506,13 @@ d_lite_iso8601(VALUE self) /* * call-seq: - * d.rfc3339 -> string + * rfc3339 -> string + * + * Equivalent to #strftime with argument <tt>'%FT%T%:z'</tt>; + * see {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]: + * + * Date.new(2001, 2, 3).rfc3339 # => "2001-02-03T00:00:00+00:00" * - * This method is equivalent to strftime('%FT%T%:z'). */ static VALUE d_lite_rfc3339(VALUE self) @@ -6969,10 +7522,13 @@ d_lite_rfc3339(VALUE self) /* * call-seq: - * d.rfc2822 -> string - * d.rfc822 -> string + * rfc2822 -> string + * + * Equivalent to #strftime with argument <tt>'%a, %-d %b %Y %T %z'</tt>; + * see {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]: + * + * Date.new(2001, 2, 3).rfc2822 # => "Sat, 3 Feb 2001 00:00:00 +0000" * - * This method is equivalent to strftime('%a, %-d %b %Y %T %z'). */ static VALUE d_lite_rfc2822(VALUE self) @@ -6982,10 +7538,13 @@ d_lite_rfc2822(VALUE self) /* * call-seq: - * d.httpdate -> string + * httpdate -> string + * + * Equivalent to #strftime with argument <tt>'%a, %d %b %Y %T GMT'</tt>; + * see {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]: + * + * Date.new(2001, 2, 3).httpdate # => "Sat, 03 Feb 2001 00:00:00 GMT" * - * This method is equivalent to strftime('%a, %d %b %Y %T GMT'). - * See also RFC 2616. */ static VALUE d_lite_httpdate(VALUE self) @@ -7020,10 +7579,14 @@ jisx0301_date_format(char *fmt, size_t size, VALUE jd, VALUE y) c = 'S'; s = 1925; } - else { + else if (d < 2458605) { c = 'H'; s = 1988; } + else { + c = 'R'; + s = 2018; + } snprintf(fmt, size, "%c%02ld" ".%%m.%%d", c, FIX2INT(y) - s); return fmt; } @@ -7032,11 +7595,13 @@ jisx0301_date_format(char *fmt, size_t size, VALUE jd, VALUE y) /* * call-seq: - * d.jisx0301 -> string + * jisx0301 -> string * - * Returns a string in a JIS X 0301 format. + * Returns a string representation of the date in +self+ + * in JIS X 0301 format. + * + * Date.new(2001, 2, 3).jisx0301 # => "H13.02.03" * - * Date.new(2001,2,3).jisx0301 #=> "H13.02.03" */ static VALUE d_lite_jisx0301(VALUE self) @@ -7051,7 +7616,98 @@ d_lite_jisx0301(VALUE self) return strftimev(fmt, self, set_tmx); } +static VALUE +deconstruct_keys(VALUE self, VALUE keys, int is_datetime) +{ + VALUE h = rb_hash_new(); + long i; + + get_d1(self); + + if (NIL_P(keys)) { + rb_hash_aset(h, sym_year, m_real_year(dat)); + rb_hash_aset(h, sym_month, INT2FIX(m_mon(dat))); + rb_hash_aset(h, sym_day, INT2FIX(m_mday(dat))); + rb_hash_aset(h, sym_yday, INT2FIX(m_yday(dat))); + rb_hash_aset(h, sym_wday, INT2FIX(m_wday(dat))); + if (is_datetime) { + rb_hash_aset(h, sym_hour, INT2FIX(m_hour(dat))); + rb_hash_aset(h, sym_min, INT2FIX(m_min(dat))); + rb_hash_aset(h, sym_sec, INT2FIX(m_sec(dat))); + rb_hash_aset(h, sym_sec_fraction, m_sf_in_sec(dat)); + rb_hash_aset(h, sym_zone, m_zone(dat)); + } + + return h; + } + if (!RB_TYPE_P(keys, T_ARRAY)) { + rb_raise(rb_eTypeError, + "wrong argument type %"PRIsVALUE" (expected Array or nil)", + rb_obj_class(keys)); + + } + + for (i=0; i<RARRAY_LEN(keys); i++) { + VALUE key = RARRAY_AREF(keys, i); + + if (sym_year == key) rb_hash_aset(h, key, m_real_year(dat)); + if (sym_month == key) rb_hash_aset(h, key, INT2FIX(m_mon(dat))); + if (sym_day == key) rb_hash_aset(h, key, INT2FIX(m_mday(dat))); + if (sym_yday == key) rb_hash_aset(h, key, INT2FIX(m_yday(dat))); + if (sym_wday == key) rb_hash_aset(h, key, INT2FIX(m_wday(dat))); + if (is_datetime) { + if (sym_hour == key) rb_hash_aset(h, key, INT2FIX(m_hour(dat))); + if (sym_min == key) rb_hash_aset(h, key, INT2FIX(m_min(dat))); + if (sym_sec == key) rb_hash_aset(h, key, INT2FIX(m_sec(dat))); + if (sym_sec_fraction == key) rb_hash_aset(h, key, m_sf_in_sec(dat)); + if (sym_zone == key) rb_hash_aset(h, key, m_zone(dat)); + } + } + return h; +} + +/* + * call-seq: + * deconstruct_keys(array_of_names_or_nil) -> hash + * + * Returns a hash of the name/value pairs, to use in pattern matching. + * Possible keys are: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, + * <tt>:wday</tt>, <tt>:yday</tt>. + * + * Possible usages: + * + * d = Date.new(2022, 10, 5) + * + * if d in wday: 3, day: ..7 # uses deconstruct_keys underneath + * puts "first Wednesday of the month" + * end + * #=> prints "first Wednesday of the month" + * + * case d + * in year: ...2022 + * puts "too old" + * in month: ..9 + * puts "quarter 1-3" + * in wday: 1..5, month: + * puts "working day in month #{month}" + * end + * #=> prints "working day in month 10" + * + * Note that deconstruction by pattern can also be combined with class check: + * + * if d in Date(wday: 3, day: ..7) + * puts "first Wednesday of the month" + * end + * + */ +static VALUE +d_lite_deconstruct_keys(VALUE self, VALUE keys) +{ + return deconstruct_keys(self, keys, /* is_datetime=false */ 0); +} + #ifndef NDEBUG +/* :nodoc: */ static VALUE d_lite_marshal_dump_old(VALUE self) { @@ -7064,10 +7720,7 @@ d_lite_marshal_dump_old(VALUE self) 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); - } + rb_copy_generic_ivar(a, self); return a; } @@ -7089,10 +7742,8 @@ d_lite_marshal_dump(VALUE self) 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); - } + + rb_copy_generic_ivar(a, self); return a; } @@ -7101,10 +7752,13 @@ d_lite_marshal_dump(VALUE self) static VALUE d_lite_marshal_load(VALUE self, VALUE a) { + VALUE nth, sf; + int jd, df, of; + double sg; + 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"); @@ -7113,63 +7767,33 @@ d_lite_marshal_load(VALUE self, VALUE 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; - + VALUE ajd, vof, vsg; 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); + vof = INT2FIX(0); + vsg = RARRAY_AREF(a, 1); + if (!k_numeric_p(vsg)) + vsg = DBL2NUM(RTEST(vsg) ? GREGORIAN : JULIAN); } else { ajd = RARRAY_AREF(a, 0); - of = RARRAY_AREF(a, 1); - sg = RARRAY_AREF(a, 2); + vof = RARRAY_AREF(a, 1); + vsg = 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); - } + old_to_new(ajd, vof, vsg, + &nth, &jd, &df, &sf, &of, &sg); } 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: @@ -7177,11 +7801,23 @@ d_lite_marshal_load(VALUE self, VALUE a) break; } - if (FL_TEST(a, FL_EXIVAR)) { - rb_copy_generic_ivar(self, a); - FL_SET(self, FL_EXIVAR); + if (simple_dat_p(dat)) { + if (df || !f_zero_p(sf) || of) { + /* loading a fractional date; promote to complex */ + dat = ruby_xrealloc(dat, sizeof(struct ComplexDateData)); + RTYPEDDATA(self)->data = dat; + goto complex_data; + } + set_to_simple(self, &dat->s, nth, jd, sg, 0, 0, 0, HAVE_JD); + } else { + complex_data: + set_to_complex(self, &dat->c, nth, jd, df, sf, of, sg, + 0, 0, 0, 0, 0, 0, + HAVE_JD | HAVE_DF); } + rb_copy_generic_ivar(self, a); + return self; } @@ -7232,12 +7868,16 @@ datetime_s_jd(int argc, VALUE *argv, VALUE klass) case 5: val2off(vof, rof); case 4: + check_numeric(vs, "second"); num2int_with_frac(s, positive_inf); case 3: + check_numeric(vmin, "minute"); num2int_with_frac(min, 3); case 2: + check_numeric(vh, "hour"); num2int_with_frac(h, 2); case 1: + check_numeric(vjd, "jd"); num2num_with_frac(jd, 1); } @@ -7246,7 +7886,7 @@ datetime_s_jd(int argc, VALUE *argv, VALUE klass) int rh, rmin, rs, rjd, rjd2; if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) - rb_raise(rb_eArgError, "invalid date"); + rb_raise(eDateError, "invalid date"); canon24oc(); decode_jd(jd, &nth, &rjd); @@ -7301,14 +7941,19 @@ datetime_s_ordinal(int argc, VALUE *argv, VALUE klass) case 6: val2off(vof, rof); case 5: + check_numeric(vs, "second"); num2int_with_frac(s, positive_inf); case 4: + check_numeric(vmin, "minute"); num2int_with_frac(min, 4); case 3: + check_numeric(vh, "hour"); num2int_with_frac(h, 3); case 2: + check_numeric(vd, "yday"); num2int_with_frac(d, 2); case 1: + check_numeric(vy, "year"); y = vy; } @@ -7320,9 +7965,9 @@ datetime_s_ordinal(int argc, VALUE *argv, VALUE klass) &nth, &ry, &rd, &rjd, &ns)) - rb_raise(rb_eArgError, "invalid date"); + rb_raise(eDateError, "invalid date"); if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) - rb_raise(rb_eArgError, "invalid date"); + rb_raise(eDateError, "invalid date"); canon24oc(); rjd2 = jd_local_to_utc(rjd, @@ -7342,24 +7987,25 @@ datetime_s_ordinal(int argc, VALUE *argv, VALUE klass) } /* - * 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 DateTime 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 ...> + * Same as DateTime.new. */ static VALUE datetime_s_civil(int argc, VALUE *argv, VALUE klass) { + return datetime_initialize(argc, argv, d_lite_s_alloc_complex(klass)); +} + +static VALUE +datetime_initialize(int argc, VALUE *argv, VALUE self) +{ VALUE vy, vm, vd, vh, vmin, vs, vof, vsg, y, fr, fr2, ret; int m, d, h, min, s, rof; double sg; + struct ComplexDateData *dat = rb_check_typeddata(self, &d_lite_type); + + if (!complex_dat_p(dat)) { + rb_raise(rb_eTypeError, "DateTime expected"); + } rb_scan_args(argc, argv, "08", &vy, &vm, &vd, &vh, &vmin, &vs, &vof, &vsg); @@ -7378,16 +8024,22 @@ datetime_s_civil(int argc, VALUE *argv, VALUE klass) case 7: val2off(vof, rof); case 6: + check_numeric(vs, "second"); num2int_with_frac(s, positive_inf); case 5: + check_numeric(vmin, "minute"); num2int_with_frac(min, 5); case 4: + check_numeric(vh, "hour"); num2int_with_frac(h, 4); case 3: + check_numeric(vd, "day"); num2int_with_frac(d, 3); case 2: + check_numeric(vm, "month"); m = NUM2INT(vm); case 1: + check_numeric(vy, "year"); y = vy; } @@ -7398,18 +8050,18 @@ datetime_s_civil(int argc, VALUE *argv, VALUE klass) if (!valid_gregorian_p(y, m, d, &nth, &ry, &rm, &rd)) - rb_raise(rb_eArgError, "invalid date"); + rb_raise(eDateError, "invalid date"); if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) - rb_raise(rb_eArgError, "invalid date"); + rb_raise(eDateError, "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); + set_to_complex(self, dat, + nth, 0, + 0, INT2FIX(0), + rof, sg, + ry, rm, rd, + rh, rmin, rs, + HAVE_CIVIL | HAVE_TIME); } else { VALUE nth; @@ -7419,23 +8071,24 @@ datetime_s_civil(int argc, VALUE *argv, VALUE klass) &nth, &ry, &rm, &rd, &rjd, &ns)) - rb_raise(rb_eArgError, "invalid date"); + rb_raise(eDateError, "invalid date"); if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) - rb_raise(rb_eArgError, "invalid date"); + rb_raise(eDateError, "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); + set_to_complex(self, dat, + nth, rjd2, + 0, INT2FIX(0), + rof, sg, + ry, rm, rd, + rh, rmin, rs, + HAVE_JD | HAVE_CIVIL | HAVE_TIME); } + ret = self; add_frac(); return ret; } @@ -7475,16 +8128,22 @@ datetime_s_commercial(int argc, VALUE *argv, VALUE klass) case 7: val2off(vof, rof); case 6: + check_numeric(vs, "second"); num2int_with_frac(s, positive_inf); case 5: + check_numeric(vmin, "minute"); num2int_with_frac(min, 5); case 4: + check_numeric(vh, "hour"); num2int_with_frac(h, 4); case 3: + check_numeric(vd, "cwday"); num2int_with_frac(d, 3); case 2: + check_numeric(vw, "cweek"); w = NUM2INT(vw); case 1: + check_numeric(vy, "year"); y = vy; } @@ -7496,9 +8155,9 @@ datetime_s_commercial(int argc, VALUE *argv, VALUE klass) &nth, &ry, &rw, &rd, &rjd, &ns)) - rb_raise(rb_eArgError, "invalid date"); + rb_raise(eDateError, "invalid date"); if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) - rb_raise(rb_eArgError, "invalid date"); + rb_raise(eDateError, "invalid date"); canon24oc(); rjd2 = jd_local_to_utc(rjd, @@ -7518,6 +8177,7 @@ datetime_s_commercial(int argc, VALUE *argv, VALUE klass) } #ifndef NDEBUG +/* :nodoc: */ static VALUE datetime_s_weeknum(int argc, VALUE *argv, VALUE klass) { @@ -7567,9 +8227,9 @@ datetime_s_weeknum(int argc, VALUE *argv, VALUE klass) &nth, &ry, &rw, &rd, &rjd, &ns)) - rb_raise(rb_eArgError, "invalid date"); + rb_raise(eDateError, "invalid date"); if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) - rb_raise(rb_eArgError, "invalid date"); + rb_raise(eDateError, "invalid date"); canon24oc(); rjd2 = jd_local_to_utc(rjd, @@ -7587,6 +8247,7 @@ datetime_s_weeknum(int argc, VALUE *argv, VALUE klass) return ret; } +/* :nodoc: */ static VALUE datetime_s_nth_kday(int argc, VALUE *argv, VALUE klass) { @@ -7636,9 +8297,9 @@ datetime_s_nth_kday(int argc, VALUE *argv, VALUE klass) &nth, &ry, &rm, &rn, &rk, &rjd, &ns)) - rb_raise(rb_eArgError, "invalid date"); + rb_raise(eDateError, "invalid date"); if (!c_valid_time_p(h, min, s, &rh, &rmin, &rs)) - rb_raise(rb_eArgError, "invalid date"); + rb_raise(eDateError, "invalid date"); canon24oc(); rjd2 = jd_local_to_utc(rjd, @@ -7711,7 +8372,7 @@ datetime_s_now(int argc, VALUE *argv, VALUE klass) #ifdef HAVE_STRUCT_TM_TM_GMTOFF of = tm.tm_gmtoff; #elif defined(HAVE_TIMEZONE) -#ifdef HAVE_ALTZONE +#if defined(HAVE_ALTZONE) && !defined(_AIX) of = (long)-((tm.tm_isdst > 0) ? altzone : timezone); #else of = (long)-timezone; @@ -7781,7 +8442,7 @@ dt_new_by_frags(VALUE klass, VALUE hash, VALUE sg) } if (NIL_P(hash)) - rb_raise(rb_eArgError, "invalid date"); + rb_raise(eDateError, "invalid date"); if (NIL_P(ref_hash("jd")) && NIL_P(ref_hash("yday")) && @@ -7808,7 +8469,7 @@ dt_new_by_frags(VALUE klass, VALUE hash, VALUE sg) } if (NIL_P(jd)) - rb_raise(rb_eArgError, "invalid date"); + rb_raise(eDateError, "invalid date"); { int rh, rmin, rs; @@ -7817,7 +8478,7 @@ dt_new_by_frags(VALUE klass, VALUE hash, VALUE sg) NUM2INT(ref_hash("min")), NUM2INT(ref_hash("sec")), &rh, &rmin, &rs)) - rb_raise(rb_eArgError, "invalid date"); + rb_raise(eDateError, "invalid date"); df = time_to_df(rh, rmin, rs); } @@ -7908,7 +8569,7 @@ datetime_s_strptime(int argc, VALUE *argv, VALUE klass) switch (argc) { case 0: - str = rb_str_new2("-4712-01-01T00:00:00+00:00"); + str = rb_str_new2(JULIAN_EPOCH_DATETIME); case 1: fmt = rb_str_new2("%FT%T%z"); case 2: @@ -7927,10 +8588,15 @@ datetime_s_strptime(int argc, VALUE *argv, VALUE klass) /* * call-seq: - * DateTime.parse(string='-4712-01-01T00:00:00+00:00'[, comp=true[, start=Date::ITALY]]) -> datetime + * DateTime.parse(string='-4712-01-01T00:00:00+00:00'[, comp=true[, start=Date::ITALY]], limit: 128) -> datetime * * Parses the given representation of date and time, and creates a - * DateTime object. This method does not function as a validator. + * DateTime object. + * + * This method *does* *not* function as a validator. If the input + * string does not match valid formats strictly, you may get a cryptic + * result. Should consider to use DateTime.strptime instead of this + * method as possible. * * If the optional second argument is true and the detected year is in * the range "00" to "99", makes it full. @@ -7941,17 +8607,21 @@ datetime_s_strptime(int argc, VALUE *argv, VALUE klass) * #=> #<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 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing <code>limit: nil</code>, but note + * that it may take a long time to parse. */ static VALUE datetime_s_parse(int argc, VALUE *argv, VALUE klass) { - VALUE str, comp, sg; + VALUE str, comp, sg, opt; - rb_scan_args(argc, argv, "03", &str, &comp, &sg); + argc = rb_scan_args(argc, argv, "03:", &str, &comp, &sg, &opt); switch (argc) { case 0: - str = rb_str_new2("-4712-01-01T00:00:00+00:00"); + str = rb_str_new2(JULIAN_EPOCH_DATETIME); case 1: comp = Qtrue; case 2: @@ -7959,18 +8629,20 @@ datetime_s_parse(int argc, VALUE *argv, VALUE klass) } { - VALUE argv2[2], hash; - - argv2[0] = str; - argv2[1] = comp; - hash = date_s__parse(2, argv2, klass); + int argc2 = 2; + VALUE argv2[3], hash; + argv2[0] = str; + argv2[1] = comp; + argv2[2] = opt; + if (!NIL_P(opt)) argc2++; + hash = date_s__parse(argc2, argv2, klass); return dt_new_by_frags(klass, hash, sg); } } /* * call-seq: - * DateTime.iso8601(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY]) -> datetime + * DateTime.iso8601(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY], limit: 128) -> datetime * * Creates a new DateTime object by parsing from a string according to * some typical ISO 8601 formats. @@ -7981,114 +8653,150 @@ datetime_s_parse(int argc, VALUE *argv, VALUE klass) * #=> #<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 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing <code>limit: nil</code>, but note + * that it may take a long time to parse. */ static VALUE datetime_s_iso8601(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); switch (argc) { case 0: - str = rb_str_new2("-4712-01-01T00:00:00+00:00"); + str = rb_str_new2(JULIAN_EPOCH_DATETIME); case 1: sg = INT2FIX(DEFAULT_SG); } { - VALUE hash = date_s__iso8601(klass, str); + int argc2 = 1; + VALUE argv2[2], hash; + argv2[0] = str; + argv2[1] = opt; + if (!NIL_P(opt)) argc2++; + hash = date_s__iso8601(argc2, argv2, klass); return dt_new_by_frags(klass, hash, sg); } } /* * call-seq: - * DateTime.rfc3339(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY]) -> datetime + * DateTime.rfc3339(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY], limit: 128) -> datetime * * Creates a new DateTime 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 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing <code>limit: nil</code>, but note + * that it may take a long time to parse. */ static VALUE datetime_s_rfc3339(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); switch (argc) { case 0: - str = rb_str_new2("-4712-01-01T00:00:00+00:00"); + str = rb_str_new2(JULIAN_EPOCH_DATETIME); case 1: sg = INT2FIX(DEFAULT_SG); } { - VALUE hash = date_s__rfc3339(klass, str); + int argc2 = 1; + VALUE argv2[2], hash; + argv2[0] = str; + argv2[1] = opt; + if (!NIL_P(opt)) argc2++; + hash = date_s__rfc3339(argc2, argv2, klass); return dt_new_by_frags(klass, hash, sg); } } /* * call-seq: - * DateTime.xmlschema(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY]) -> datetime + * DateTime.xmlschema(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY], limit: 128) -> datetime * * Creates a new DateTime 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 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing <code>limit: nil</code>, but note + * that it may take a long time to parse. */ static VALUE datetime_s_xmlschema(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); switch (argc) { case 0: - str = rb_str_new2("-4712-01-01T00:00:00+00:00"); + str = rb_str_new2(JULIAN_EPOCH_DATETIME); case 1: sg = INT2FIX(DEFAULT_SG); } { - VALUE hash = date_s__xmlschema(klass, str); + int argc2 = 1; + VALUE argv2[2], hash; + argv2[0] = str; + argv2[1] = opt; + if (!NIL_P(opt)) argc2++; + hash = date_s__xmlschema(argc2, argv2, klass); return dt_new_by_frags(klass, hash, sg); } } /* * call-seq: - * DateTime.rfc2822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY]) -> datetime - * DateTime.rfc822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY]) -> datetime + * DateTime.rfc2822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY], limit: 128) -> datetime + * DateTime.rfc822(string='Mon, 1 Jan -4712 00:00:00 +0000'[, start=Date::ITALY], limit: 128) -> datetime * * Creates a new DateTime 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 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing <code>limit: nil</code>, but note + * that it may take a long time to parse. */ static VALUE datetime_s_rfc2822(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); switch (argc) { case 0: - str = rb_str_new2("Mon, 1 Jan -4712 00:00:00 +0000"); + str = rb_str_new2(JULIAN_EPOCH_DATETIME_RFC3339); case 1: sg = INT2FIX(DEFAULT_SG); } { - VALUE hash = date_s__rfc2822(klass, str); + int argc2 = 1; + VALUE argv2[2], hash; + argv2[0] = str; + argv2[1] = opt; + if (!NIL_P(opt)) argc2++; + hash = date_s__rfc2822(argc2, argv2, klass); return dt_new_by_frags(klass, hash, sg); } } @@ -8102,53 +8810,76 @@ datetime_s_rfc2822(int argc, VALUE *argv, VALUE klass) * * DateTime.httpdate('Sat, 03 Feb 2001 04:05:06 GMT') * #=> #<DateTime: 2001-02-03T04:05:06+00:00 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing <code>limit: nil</code>, but note + * that it may take a long time to parse. */ static VALUE datetime_s_httpdate(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); switch (argc) { case 0: - str = rb_str_new2("Mon, 01 Jan -4712 00:00:00 GMT"); + str = rb_str_new2(JULIAN_EPOCH_DATETIME_HTTPDATE); case 1: sg = INT2FIX(DEFAULT_SG); } { - VALUE hash = date_s__httpdate(klass, str); + int argc2 = 1; + VALUE argv2[2], hash; + argv2[0] = str; + argv2[1] = opt; + if (!NIL_P(opt)) argc2++; + hash = date_s__httpdate(argc2, argv2, klass); return dt_new_by_frags(klass, hash, sg); } } /* * call-seq: - * DateTime.jisx0301(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY]) -> datetime + * DateTime.jisx0301(string='-4712-01-01T00:00:00+00:00'[, start=Date::ITALY], limit: 128) -> datetime * * Creates a new DateTime 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 ...> + * + * For no-era year, legacy format, Heisei is assumed. + * + * DateTime.jisx0301('13.02.03T04:05:06+07:00') + * #=> #<DateTime: 2001-02-03T04:05:06+07:00 ...> + * + * Raise an ArgumentError when the string length is longer than _limit_. + * You can stop this check by passing <code>limit: nil</code>, but note + * that it may take a long time to parse. */ static VALUE datetime_s_jisx0301(int argc, VALUE *argv, VALUE klass) { - VALUE str, sg; + VALUE str, sg, opt; - rb_scan_args(argc, argv, "02", &str, &sg); + argc = rb_scan_args(argc, argv, "02:", &str, &sg, &opt); switch (argc) { case 0: - str = rb_str_new2("-4712-01-01T00:00:00+00:00"); + str = rb_str_new2(JULIAN_EPOCH_DATETIME); case 1: sg = INT2FIX(DEFAULT_SG); } { - VALUE hash = date_s__jisx0301(klass, str); + int argc2 = 1; + VALUE argv2[2], hash; + argv2[0] = str; + argv2[1] = opt; + if (!NIL_P(opt)) argc2++; + hash = date_s__jisx0301(argc2, argv2, klass); return dt_new_by_frags(klass, hash, sg); } } @@ -8171,181 +8902,16 @@ dt_lite_to_s(VALUE self) /* * call-seq: - * dt.strftime([format='%FT%T%:z']) -> string - * - * Formats date according to the directives in the given format - * string. - * The directives begin with a percent (%) character. - * Any text not listed as a directive will be passed through to the - * output string. - * - * A directive consists of a percent (%) character, - * zero or more flags, an optional minimum field width, - * an 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 modifiers are "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 - Equivalent to %:z (e.g. +09:00) - * - * 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 the 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 the 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. + * strftime(format = '%FT%T%:z') -> string + * + * Returns a string representation of +self+, + * formatted according the given +format: + * + * DateTime.now.strftime # => "2022-07-01T11:03:19-05:00" + * + * For other formats, + * see {Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc]: + * */ static VALUE dt_lite_strftime(int argc, VALUE *argv, VALUE self) @@ -8433,6 +8999,47 @@ dt_lite_jisx0301(int argc, VALUE *argv, VALUE self) iso8601_timediv(self, n)); } +/* + * call-seq: + * deconstruct_keys(array_of_names_or_nil) -> hash + * + * Returns a hash of the name/value pairs, to use in pattern matching. + * Possible keys are: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, + * <tt>:wday</tt>, <tt>:yday</tt>, <tt>:hour</tt>, <tt>:min</tt>, + * <tt>:sec</tt>, <tt>:sec_fraction</tt>, <tt>:zone</tt>. + * + * Possible usages: + * + * dt = DateTime.new(2022, 10, 5, 13, 30) + * + * if d in wday: 1..5, hour: 10..18 # uses deconstruct_keys underneath + * puts "Working time" + * end + * #=> prints "Working time" + * + * case dt + * in year: ...2022 + * puts "too old" + * in month: ..9 + * puts "quarter 1-3" + * in wday: 1..5, month: + * puts "working day in month #{month}" + * end + * #=> prints "working day in month 10" + * + * Note that deconstruction by pattern can also be combined with class check: + * + * if d in DateTime(wday: 1..5, hour: 10..18, day: ..7) + * puts "Working time, first week of the month" + * end + * + */ +static VALUE +dt_lite_deconstruct_keys(VALUE self, VALUE keys) +{ + return deconstruct_keys(self, keys, /* is_datetime=true */ 1); +} + /* conversions */ #define f_subsec(x) rb_funcall(x, rb_intern("subsec"), 0) @@ -8511,7 +9118,7 @@ time_to_datetime(VALUE self) ret = d_complex_new_internal(cDateTime, nth, 0, 0, sf, - of, DEFAULT_SG, + of, GREGORIAN, ry, m, d, h, min, s, HAVE_CIVIL | HAVE_TIME); @@ -8524,26 +9131,43 @@ time_to_datetime(VALUE self) /* * call-seq: - * d.to_time -> time + * to_time -> time + * + * Returns a new Time object with the same value as +self+; + * if +self+ is a Julian date, derives its Gregorian date + * for conversion to the \Time object: + * + * Date.new(2001, 2, 3).to_time # => 2001-02-03 00:00:00 -0600 + * Date.new(2001, 2, 3, Date::JULIAN).to_time # => 2001-02-16 00:00:00 -0600 * - * Returns a Time object which denotes self. */ static VALUE date_to_time(VALUE self) { - get_d1(self); + VALUE t; + + get_d1a(self); - return f_local3(rb_cTime, - m_real_year(dat), - INT2FIX(m_mon(dat)), - INT2FIX(m_mday(dat))); + if (m_julian_p(adat)) { + VALUE g = d_lite_gregorian(self); + get_d1b(g); + adat = bdat; + self = g; + } + + t = f_local3(rb_cTime, + m_real_year(adat), + INT2FIX(m_mon(adat)), + INT2FIX(m_mday(adat))); + RB_GC_GUARD(self); /* may be the converted gregorian */ + return t; } /* * call-seq: - * d.to_date -> self + * to_date -> self * - * Returns self. + * Returns +self+. */ static VALUE date_to_date(VALUE self) @@ -8555,7 +9179,10 @@ date_to_date(VALUE self) * call-seq: * d.to_datetime -> datetime * - * Returns a DateTime object which denotes self. + * Returns a DateTime whose value is the same as +self+: + * + * Date.new(2001, 2, 3).to_datetime # => #<DateTime: 2001-02-03T00:00:00+00:00> + * */ static VALUE date_to_datetime(VALUE self) @@ -8600,12 +9227,18 @@ date_to_datetime(VALUE self) static VALUE datetime_to_time(VALUE self) { - volatile VALUE dup = dup_obj(self); + get_d1(self); + + if (m_julian_p(dat)) { + VALUE g = d_lite_gregorian(self); + get_d1a(g); + dat = adat; + self = g; + } + { VALUE t; - get_d1(dup); - t = rb_funcall(rb_cTime, rb_intern("new"), 7, @@ -8617,6 +9250,7 @@ datetime_to_time(VALUE self) f_add(INT2FIX(m_sec(dat)), m_sf_in_sec(dat)), INT2FIX(m_of(dat))); + RB_GC_GUARD(self); /* may be the converted gregorian */ return t; } } @@ -8645,7 +9279,7 @@ datetime_to_date(VALUE self) VALUE new = d_lite_s_alloc_simple(cDate); { get_d1b(new); - copy_complex_to_simple(new, &bdat->s, &adat->c) + 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; @@ -8673,6 +9307,7 @@ datetime_to_datetime(VALUE self) #define MIN_JD -327 #define MAX_JD 366963925 +/* :nodoc: */ static int test_civil(int from, int to, double sg) { @@ -8693,6 +9328,7 @@ test_civil(int from, int to, double sg) return 1; } +/* :nodoc: */ static VALUE date_s_test_civil(VALUE klass) { @@ -8713,6 +9349,7 @@ date_s_test_civil(VALUE klass) return Qtrue; } +/* :nodoc: */ static int test_ordinal(int from, int to, double sg) { @@ -8733,6 +9370,7 @@ test_ordinal(int from, int to, double sg) return 1; } +/* :nodoc: */ static VALUE date_s_test_ordinal(VALUE klass) { @@ -8753,6 +9391,7 @@ date_s_test_ordinal(VALUE klass) return Qtrue; } +/* :nodoc: */ static int test_commercial(int from, int to, double sg) { @@ -8773,6 +9412,7 @@ test_commercial(int from, int to, double sg) return 1; } +/* :nodoc: */ static VALUE date_s_test_commercial(VALUE klass) { @@ -8793,6 +9433,7 @@ date_s_test_commercial(VALUE klass) return Qtrue; } +/* :nodoc: */ static int test_weeknum(int from, int to, int f, double sg) { @@ -8813,6 +9454,7 @@ test_weeknum(int from, int to, int f, double sg) return 1; } +/* :nodoc: */ static VALUE date_s_test_weeknum(VALUE klass) { @@ -8837,6 +9479,7 @@ date_s_test_weeknum(VALUE klass) return Qtrue; } +/* :nodoc: */ static int test_nth_kday(int from, int to, double sg) { @@ -8857,6 +9500,7 @@ test_nth_kday(int from, int to, double sg) return 1; } +/* :nodoc: */ static VALUE date_s_test_nth_kday(VALUE klass) { @@ -8877,6 +9521,7 @@ date_s_test_nth_kday(VALUE klass) return Qtrue; } +/* :nodoc: */ static int test_unit_v2v(VALUE i, VALUE (* conv1)(VALUE), @@ -8888,6 +9533,7 @@ test_unit_v2v(VALUE i, return f_eqeq_p(o, i); } +/* :nodoc: */ static int test_unit_v2v_iter2(VALUE (* conv1)(VALUE), VALUE (* conv2)(VALUE)) @@ -8919,6 +9565,7 @@ test_unit_v2v_iter2(VALUE (* conv1)(VALUE), return 1; } +/* :nodoc: */ static int test_unit_v2v_iter(VALUE (* conv1)(VALUE), VALUE (* conv2)(VALUE)) @@ -8930,6 +9577,7 @@ test_unit_v2v_iter(VALUE (* conv1)(VALUE), return 1; } +/* :nodoc: */ static VALUE date_s_test_unit_conv(VALUE klass) { @@ -8944,6 +9592,7 @@ date_s_test_unit_conv(VALUE klass) return Qtrue; } +/* :nodoc: */ static VALUE date_s_test_all(VALUE klass) { @@ -9006,24 +9655,41 @@ mk_ary_of_str(long len, const char *a[]) } rb_ary_push(o, e); } - rb_obj_freeze(o); + rb_ary_freeze(o); return o; } +/* :nodoc: */ +static VALUE +d_lite_zero(VALUE x) +{ + return INT2FIX(0); +} + 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("=="); + #ifdef HAVE_RB_EXT_RACTOR_SAFE + RB_EXT_RACTOR_SAFE(true); + #endif + id_cmp = rb_intern_const("<=>"); + id_le_p = rb_intern_const("<="); + id_ge_p = rb_intern_const(">="); + id_eqeq_p = rb_intern_const("=="); + + sym_year = ID2SYM(rb_intern_const("year")); + sym_month = ID2SYM(rb_intern_const("month")); + sym_yday = ID2SYM(rb_intern_const("yday")); + sym_wday = ID2SYM(rb_intern_const("wday")); + sym_day = ID2SYM(rb_intern_const("day")); + sym_hour = ID2SYM(rb_intern_const("hour")); + sym_min = ID2SYM(rb_intern_const("min")); + sym_sec = ID2SYM(rb_intern_const("sec")); + sym_sec_fraction = ID2SYM(rb_intern_const("sec_fraction")); + sym_zone = ID2SYM(rb_intern_const("zone")); half_days_in_day = rb_rational_new2(INT2FIX(1), INT2FIX(2)); + rb_gc_register_mark_object(half_days_in_day); #if (LONG_MAX / DAY_IN_SECONDS) > SECOND_IN_NANOSECONDS day_in_nanoseconds = LONG2NUM((long)DAY_IN_SECONDS * @@ -9035,161 +9701,89 @@ Init_date_core(void) 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. + * \Class \Date provides methods for storing and manipulating + * calendar dates. * - * == Terms and Definitions + * Consider using + * {class Time}[rdoc-ref:Time] + * instead of class \Date if: * - * Some terms and definitions are based on ISO 8601 and JIS X 0301. + * - You need both dates and times; \Date handles only dates. + * - You need only Gregorian dates (and not Julian dates); + * see {Julian and Gregorian Calendars}[rdoc-ref:language/calendars.rdoc]. * - * === Calendar Date + * A \Date object, once created, is immutable, and cannot be modified. * - * The calendar date is a particular day of a calendar year, - * identified by its ordinal number within a calendar month within - * that year. + * == Creating a \Date * - * In those classes, this is so-called "civil". + * You can create a date for the current date, using Date.today: * - * === Ordinal Date + * Date.today # => #<Date: 1999-12-31> * - * The ordinal date is a particular day of a calendar year identified - * by its ordinal number within the year. + * You can create a specific date from various combinations of arguments: * - * In those classes, this is so-called "ordinal". + * - Date.new takes integer year, month, and day-of-month: * - * === Week Date + * Date.new(1999, 12, 31) # => #<Date: 1999-12-31> * - * The week date is a date identified by calendar week and day numbers. + * - Date.ordinal takes integer year and day-of-year: * - * 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. + * Date.ordinal(1999, 365) # => #<Date: 1999-12-31> * - * In those classes, this is so-called "commercial". + * - Date.jd takes integer Julian day: * - * === Julian Day Number + * Date.jd(2451544) # => #<Date: 1999-12-31> * - * The Julian day number is in elapsed days since noon (Greenwich Mean - * Time) on January 1, 4713 BCE (in the Julian calendar). + * - Date.commercial takes integer commercial data (year, week, day-of-week): * - * In this document, the astronomical Julian day number is the 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. + * Date.commercial(1999, 52, 5) # => #<Date: 1999-12-31> * - * In this document, when the term "Julian day number" simply appears, - * it just refers to "chronological Julian day number", not the - * original. + * - Date.parse takes a string, which it parses heuristically: * - * In those classes, those are so-called "ajd" and "jd". + * Date.parse('1999-12-31') # => #<Date: 1999-12-31> + * Date.parse('31-12-1999') # => #<Date: 1999-12-31> + * Date.parse('1999-365') # => #<Date: 1999-12-31> + * Date.parse('1999-W52-5') # => #<Date: 1999-12-31> * - * === Modified Julian Day Number + * - Date.strptime takes a date string and a format string, + * then parses the date string according to the format string: * - * The modified Julian day number is in elapsed days since midnight - * (Coordinated Universal Time) on November 17, 1858 CE (in the - * Gregorian calendar). + * Date.strptime('1999-12-31', '%Y-%m-%d') # => #<Date: 1999-12-31> + * Date.strptime('31-12-1999', '%d-%m-%Y') # => #<Date: 1999-12-31> + * Date.strptime('1999-365', '%Y-%j') # => #<Date: 1999-12-31> + * Date.strptime('1999-W52-5', '%G-W%V-%u') # => #<Date: 1999-12-31> + * Date.strptime('1999 52 5', '%Y %U %w') # => #<Date: 1999-12-31> + * Date.strptime('1999 52 5', '%Y %W %u') # => #<Date: 1999-12-31> + * Date.strptime('fri31dec99', '%a%d%b%y') # => #<Date: 1999-12-31> * - * In this document, the astronomical modified Julian day number is - * the 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. + * See also the specialized methods in + * {"Specialized Format Strings" in Formats for Dates and Times}[rdoc-ref:language/strftime_formatting.rdoc@Specialized+Format+Strings] * - * In this document, when the term "modified Julian day number" simply - * appears, it just refers to "chronological modified Julian day - * number", not the original. + * == Argument +limit+ * - * In those classes, those are so-called "amjd" and "mjd". + * Certain singleton methods in \Date that parse string arguments + * also take optional keyword argument +limit+, + * which can limit the length of the string argument. * - * == Date - * - * A subclass of Object that includes the Comparable module and - * easily handles date. - * - * A Date object is created with Date::new, Date::jd, Date::ordinal, - * Date::commercial, Date::parse, Date::strptime, Date::today, - * Time#to_date, etc. - * - * require 'date' + * When +limit+ is: * - * 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 a 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. - * - * A Date object can be created with an optional argument, - * the day of calendar reform as a Julian day number, which - * should be 2298874 to 2426355 or negative/positive infinity. - * 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 - * - * A 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" + * - Non-negative: + * raises ArgumentError if the string length is greater than _limit_. + * - Other numeric or +nil+: ignores +limit+. + * - Other non-numeric: raises TypeError. * */ cDate = rb_define_class("Date", rb_cObject); + /* Exception for invalid date/time */ + eDateError = rb_define_class_under(cDate, "Error", rb_eArgError); + rb_include_module(cDate, rb_mComparable); /* An array of strings of full month names in English. The first @@ -9233,23 +9827,22 @@ Init_date_core(void) */ rb_define_const(cDate, "GREGORIAN", DBL2NUM(GREGORIAN)); - rb_define_alloc_func(cDate, d_lite_s_alloc); + rb_define_alloc_func(cDate, d_lite_s_alloc_simple); #ifndef NDEBUG -#define de_define_private_method rb_define_private_method - de_define_private_method(CLASS_OF(cDate), "_valid_jd?", + rb_define_private_method(CLASS_OF(cDate), "_valid_jd?", date_s__valid_jd_p, -1); - de_define_private_method(CLASS_OF(cDate), "_valid_ordinal?", + rb_define_private_method(CLASS_OF(cDate), "_valid_ordinal?", date_s__valid_ordinal_p, -1); - de_define_private_method(CLASS_OF(cDate), "_valid_civil?", + rb_define_private_method(CLASS_OF(cDate), "_valid_civil?", date_s__valid_civil_p, -1); - de_define_private_method(CLASS_OF(cDate), "_valid_date?", + rb_define_private_method(CLASS_OF(cDate), "_valid_date?", date_s__valid_civil_p, -1); - de_define_private_method(CLASS_OF(cDate), "_valid_commercial?", + rb_define_private_method(CLASS_OF(cDate), "_valid_commercial?", date_s__valid_commercial_p, -1); - de_define_private_method(CLASS_OF(cDate), "_valid_weeknum?", + rb_define_private_method(CLASS_OF(cDate), "_valid_weeknum?", date_s__valid_weeknum_p, -1); - de_define_private_method(CLASS_OF(cDate), "_valid_nth_kday?", + rb_define_private_method(CLASS_OF(cDate), "_valid_nth_kday?", date_s__valid_nth_kday_p, -1); #endif @@ -9262,11 +9855,11 @@ Init_date_core(void) date_s_valid_commercial_p, -1); #ifndef NDEBUG - de_define_private_method(CLASS_OF(cDate), "valid_weeknum?", + rb_define_private_method(CLASS_OF(cDate), "valid_weeknum?", date_s_valid_weeknum_p, -1); - de_define_private_method(CLASS_OF(cDate), "valid_nth_kday?", + rb_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", + rb_define_private_method(CLASS_OF(cDate), "zone_to_diff", date_s_zone_to_diff, 1); #endif @@ -9277,21 +9870,18 @@ Init_date_core(void) 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"); + rb_define_singleton_method(cDate, "new!", date_s_new_bang, -1); + rb_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); + rb_define_singleton_method(cDate, "weeknum", date_s_weeknum, -1); + rb_define_singleton_method(cDate, "nth_kday", date_s_nth_kday, -1); #endif rb_define_singleton_method(cDate, "today", date_s_today, -1); @@ -9299,29 +9889,26 @@ Init_date_core(void) 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, "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, "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, "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, "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, "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); 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", date_initialize, -1); rb_define_method(cDate, "initialize_copy", d_lite_initialize_copy, 1); #ifndef NDEBUG - de_define_method(cDate, "fill", d_lite_fill, 0); + rb_define_method(cDate, "fill", d_lite_fill, 0); #endif rb_define_method(cDate, "ajd", d_lite_ajd, 0); @@ -9343,8 +9930,8 @@ Init_date_core(void) 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); + rb_define_private_method(cDate, "wnum0", d_lite_wnum0, 0); + rb_define_private_method(cDate, "wnum1", d_lite_wnum1, 0); #endif rb_define_method(cDate, "wday", d_lite_wday, 0); @@ -9358,18 +9945,14 @@ Init_date_core(void) rb_define_method(cDate, "saturday?", d_lite_saturday_p, 0); #ifndef NDEBUG - de_define_method(cDate, "nth_kday?", d_lite_nth_kday_p, 2); + rb_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_private_method(cDate, "hour", d_lite_zero, 0); + rb_define_private_method(cDate, "min", d_lite_zero, 0); + rb_define_private_method(cDate, "minute", d_lite_zero, 0); + rb_define_private_method(cDate, "sec", d_lite_zero, 0); + rb_define_private_method(cDate, "second", d_lite_zero, 0); rb_define_method(cDate, "julian?", d_lite_julian_p, 0); rb_define_method(cDate, "gregorian?", d_lite_gregorian_p, 0); @@ -9382,8 +9965,6 @@ Init_date_core(void) 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); @@ -9411,7 +9992,7 @@ Init_date_core(void) rb_define_method(cDate, "to_s", d_lite_to_s, 0); #ifndef NDEBUG - de_define_method(cDate, "inspect_raw", d_lite_inspect_raw, 0); + rb_define_method(cDate, "inspect_raw", d_lite_inspect_raw, 0); #endif rb_define_method(cDate, "inspect", d_lite_inspect, 0); @@ -9427,8 +10008,10 @@ Init_date_core(void) rb_define_method(cDate, "httpdate", d_lite_httpdate, 0); rb_define_method(cDate, "jisx0301", d_lite_jisx0301, 0); + rb_define_method(cDate, "deconstruct_keys", d_lite_deconstruct_keys, 1); + #ifndef NDEBUG - de_define_method(cDate, "marshal_dump_old", d_lite_marshal_dump_old, 0); + rb_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); @@ -9440,6 +10023,8 @@ Init_date_core(void) * A subclass of Date that easily handles date, hour, minute, second, * and offset. * + * DateTime class is considered deprecated. Use Time class. + * * DateTime does not consider any leap seconds, does not track * any summer time rules. * @@ -9500,18 +10085,18 @@ Init_date_core(void) * === 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] + * {William Shakespeare}[https://en.wikipedia.org/wiki/William_Shakespeare] * and - * {Miguel de Cervantes}[http://en.wikipedia.org/wiki/Miguel_de_Cervantes] + * {Miguel de Cervantes}[https://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]. + * {World Book Day because of this fact}[https://en.wikipedia.org/wiki/World_Book_Day]. * However, because England hadn't yet adopted the - * {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]) + * {Gregorian Calendar Reform}[https://en.wikipedia.org/wiki/Gregorian_calendar#Gregorian_reform] + * (and wouldn't until {1752}[https://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] + * {proleptic Gregorian calendar}[https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar] * and has no concept of calendar reform there's no way * to express this with Time objects. This is where DateTime steps in: * @@ -9555,7 +10140,7 @@ Init_date_core(void) * #=> 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] + * {solar years}[https://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. * @@ -9567,14 +10152,15 @@ Init_date_core(void) * 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], + * {local solar times}[https://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] + * {Standard Time}[https://en.wikipedia.org/wiki/Standard_time#Great_Britain] * and eventually timezones. */ cDateTime = rb_define_class("DateTime", cDate); + rb_define_alloc_func(cDateTime, d_lite_s_alloc_complex); rb_define_singleton_method(cDateTime, "jd", datetime_s_jd, -1); rb_define_singleton_method(cDateTime, "ordinal", datetime_s_ordinal, -1); @@ -9584,9 +10170,9 @@ Init_date_core(void) datetime_s_commercial, -1); #ifndef NDEBUG - de_define_singleton_method(cDateTime, "weeknum", + rb_define_singleton_method(cDateTime, "weeknum", datetime_s_weeknum, -1); - de_define_singleton_method(cDateTime, "nth_kday", + rb_define_singleton_method(cDateTime, "nth_kday", datetime_s_nth_kday, -1); #endif @@ -9614,19 +10200,16 @@ Init_date_core(void) 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, "hour", d_lite_hour, 0); + rb_define_method(cDateTime, "min", d_lite_min, 0); + rb_define_method(cDateTime, "minute", d_lite_min, 0); + rb_define_method(cDateTime, "sec", d_lite_sec, 0); + rb_define_method(cDateTime, "second", d_lite_sec, 0); + rb_define_method(cDateTime, "sec_fraction", d_lite_sec_fraction, 0); + rb_define_method(cDateTime, "second_fraction", d_lite_sec_fraction, 0); + rb_define_method(cDateTime, "offset", d_lite_offset, 0); + rb_define_method(cDateTime, "zone", d_lite_zone, 0); + rb_define_method(cDateTime, "new_offset", d_lite_new_offset, -1); rb_define_method(cDateTime, "to_s", dt_lite_to_s, 0); @@ -9637,6 +10220,8 @@ Init_date_core(void) rb_define_method(cDateTime, "rfc3339", dt_lite_rfc3339, -1); rb_define_method(cDateTime, "jisx0301", dt_lite_jisx0301, -1); + rb_define_method(cDateTime, "deconstruct_keys", dt_lite_deconstruct_keys, 1); + /* conversions */ rb_define_method(rb_cTime, "to_time", time_to_time, 0); @@ -9654,15 +10239,15 @@ Init_date_core(void) #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", + rb_define_singleton_method(cDate, "test_civil", date_s_test_civil, 0); + rb_define_singleton_method(cDate, "test_ordinal", date_s_test_ordinal, 0); + rb_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", + rb_define_singleton_method(cDate, "test_weeknum", date_s_test_weeknum, 0); + rb_define_singleton_method(cDate, "test_nth_kday", date_s_test_nth_kday, 0); + rb_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); + rb_define_singleton_method(cDate, "test_all", date_s_test_all, 0); #endif } |
