Skip to content

Commit

Permalink
Implement Date#deconstruct_keys and DateTime#deconstruct_keys
Browse files Browse the repository at this point in the history
  • Loading branch information
zverok committed Dec 10, 2022
1 parent 634390a commit c4708bb
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 0 deletions.
151 changes: 151 additions & 0 deletions ext/date/date_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ 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)
Expand Down Expand Up @@ -7432,6 +7436,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;
long i;

get_d1(self);

if (NIL_P(keys)) {
h = rb_hash_new();

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

}

h = rb_hash_new();

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
Expand Down Expand Up @@ -8740,6 +8836,46 @@ 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)
Expand Down Expand Up @@ -9370,6 +9506,17 @@ Init_date_core(void)
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));

#if (LONG_MAX / DAY_IN_SECONDS) > SECOND_IN_NANOSECONDS
Expand Down Expand Up @@ -9691,6 +9838,8 @@ 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
rb_define_method(cDate, "marshal_dump_old", d_lite_marshal_dump_old, 0);
#endif
Expand Down Expand Up @@ -9901,6 +10050,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);
Expand Down
19 changes: 19 additions & 0 deletions test/date/test_date.rb
Original file line number Diff line number Diff line change
Expand Up @@ -175,4 +175,23 @@ def test_infinity_comparison
assert_equal(-1, -Float::INFINITY <=> Date::Infinity.new)
assert_equal(-1, -Date::Infinity.new <=> Float::INFINITY)
end

def test_deconstruct_keys
d = Date.new(1999,5,23)
assert_equal({year: 1999, month: 5, day: 23, wday: 0, yday: 143}, d.deconstruct_keys(nil))
assert_equal({year: 1999}, d.deconstruct_keys([:year, :century]))

dt = DateTime.new(1999, 5, 23, 4, 20, Rational(1, 10000))

assert_equal(
{year: 1999, month: 5, day: 23, wday: 0, yday: 143,
hour: 4, min: 20, sec: 13, sec: 0, sec_fraction: Rational(1, 10000), zone: "+00:00"},
dt.deconstruct_keys(nil)
)

assert_equal({year: 1999}, dt.deconstruct_keys([:year, :century]))

dtz = DateTime.parse('3rd Feb 2001 04:05:06+03:30')
assert_equal({zone: '+03:30'}, dtz.deconstruct_keys([:zone]))
end
end

0 comments on commit c4708bb

Please sign in to comment.