Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Time#deconstruct_keys #6594

Merged
merged 1 commit into from Nov 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions NEWS.md
Expand Up @@ -225,6 +225,10 @@ Note: We're only listing outstanding class updates.
* A Struct class can also be initialized with keyword arguments
without `keyword_init: true` on `Struct.new` [[Feature #16806]]

* Time
* `Time#deconstruct_keys` is added, allowing to use `Time` instances
in pattern-matching expressions [[Feature #19071]]

* TracePoint
* TracePoint#binding now returns `nil` for `c_call`/`c_return` TracePoints.
[[Bug #18487]]
Expand Down Expand Up @@ -448,3 +452,4 @@ The following deprecated APIs are removed.
[Feature #19070]: https://bugs.ruby-lang.org/issues/19070
[Bug #19100]: https://bugs.ruby-lang.org/issues/19100
[Feature #19135]: https://bugs.ruby-lang.org/issues/19135
[Feature #19071]: https://bugs.ruby-lang.org/issues/19071
1 change: 1 addition & 0 deletions common.mk
Expand Up @@ -15833,6 +15833,7 @@ time.$(OBJEXT): $(top_srcdir)/internal/compar.h
time.$(OBJEXT): $(top_srcdir)/internal/compilers.h
time.$(OBJEXT): $(top_srcdir)/internal/fixnum.h
time.$(OBJEXT): $(top_srcdir)/internal/gc.h
time.$(OBJEXT): $(top_srcdir)/internal/hash.h
time.$(OBJEXT): $(top_srcdir)/internal/numeric.h
time.$(OBJEXT): $(top_srcdir)/internal/rational.h
time.$(OBJEXT): $(top_srcdir)/internal/serial.h
Expand Down
14 changes: 14 additions & 0 deletions test/ruby/test_time.rb
Expand Up @@ -1329,4 +1329,18 @@ def test_memsize
rescue LoadError => e
omit "failed to load objspace: #{e.message}"
end

def test_deconstruct_keys
t = in_timezone('JST-9') { Time.local(2022, 10, 16, 14, 1, 30, 500) }
assert_equal(
{year: 2022, month: 10, day: 16, wday: 0, yday: 289,
hour: 14, min: 1, sec: 30, subsec: 1/2000r, dst: false, zone: 'JST'},
t.deconstruct_keys(nil)
)

assert_equal(
{year: 2022, month: 10, sec: 30},
t.deconstruct_keys(%i[year month sec nonexistent])
)
end
end
108 changes: 108 additions & 0 deletions time.c
Expand Up @@ -34,6 +34,7 @@
#include "id.h"
#include "internal.h"
#include "internal/array.h"
#include "internal/hash.h"
jeremyevans marked this conversation as resolved.
Show resolved Hide resolved
#include "internal/compar.h"
#include "internal/numeric.h"
#include "internal/rational.h"
Expand All @@ -51,6 +52,10 @@ static ID id_local_to_utc, id_utc_to_local, id_find_timezone;
static ID id_year, id_mon, id_mday, id_hour, id_min, id_sec, id_isdst;
static VALUE str_utc, str_empty;

// 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_subsec, sym_dst, sym_zone;

#define id_quo idQuo
#define id_div idDiv
#define id_divmod idDivmod
Expand Down Expand Up @@ -4834,6 +4839,96 @@ time_to_a(VALUE time)
time_zone(time));
}

/*
* 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 the same as returned by #to_h.
*
* Possible usages:
*
* t = Time.utc(2022, 10, 5, 21, 25, 30)
*
* if t in wday: 3, day: ..7 # uses deconstruct_keys underneath
* puts "first Wednesday of the month"
* end
* #=> prints "first Wednesday of the month"
*
* case t
* 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 t in Time(wday: 3, day: ..7)
* puts "first Wednesday of the month"
* end
*
*/
static VALUE
time_deconstruct_keys(VALUE time, VALUE keys)
{
struct time_object *tobj;
VALUE h;
long i;

GetTimeval(time, tobj);
MAKE_TM_ENSURE(time, tobj, tobj->vtm.yday != 0);

if (NIL_P(keys)) {
h = rb_hash_new_with_size(11);

rb_hash_aset(h, sym_year, tobj->vtm.year);
rb_hash_aset(h, sym_month, INT2FIX(tobj->vtm.mon));
rb_hash_aset(h, sym_day, INT2FIX(tobj->vtm.mday));
rb_hash_aset(h, sym_yday, INT2FIX(tobj->vtm.yday));
rb_hash_aset(h, sym_wday, INT2FIX(tobj->vtm.wday));
rb_hash_aset(h, sym_hour, INT2FIX(tobj->vtm.hour));
rb_hash_aset(h, sym_min, INT2FIX(tobj->vtm.min));
rb_hash_aset(h, sym_sec, INT2FIX(tobj->vtm.sec));
rb_hash_aset(h, sym_subsec,
quov(w2v(wmod(tobj->timew, WINT2FIXWV(TIME_SCALE))), INT2FIX(TIME_SCALE)));
rb_hash_aset(h, sym_dst, RBOOL(tobj->vtm.isdst));
rb_hash_aset(h, sym_zone, time_zone(time));

return h;
}
if (UNLIKELY(!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_with_size(RARRAY_LEN(keys));

for (i=0; i<RARRAY_LEN(keys); i++) {
VALUE key = RARRAY_AREF(keys, i);

if (sym_year == key) rb_hash_aset(h, key, tobj->vtm.year);
if (sym_month == key) rb_hash_aset(h, key, INT2FIX(tobj->vtm.mon));
if (sym_day == key) rb_hash_aset(h, key, INT2FIX(tobj->vtm.mday));
if (sym_yday == key) rb_hash_aset(h, key, INT2FIX(tobj->vtm.yday));
if (sym_wday == key) rb_hash_aset(h, key, INT2FIX(tobj->vtm.wday));
if (sym_hour == key) rb_hash_aset(h, key, INT2FIX(tobj->vtm.hour));
if (sym_min == key) rb_hash_aset(h, key, INT2FIX(tobj->vtm.min));
if (sym_sec == key) rb_hash_aset(h, key, INT2FIX(tobj->vtm.sec));
if (sym_subsec == key) {
rb_hash_aset(h, key, quov(w2v(wmod(tobj->timew, WINT2FIXWV(TIME_SCALE))), INT2FIX(TIME_SCALE)));
}
if (sym_dst == key) rb_hash_aset(h, key, RBOOL(tobj->vtm.isdst));
if (sym_zone == key) rb_hash_aset(h, key, time_zone(time));
}
return h;
}

static VALUE
rb_strftime_alloc(const char *format, size_t format_len, rb_encoding *enc,
VALUE time, struct vtm *vtm, wideval_t timew, int gmt)
Expand Down Expand Up @@ -5537,6 +5632,18 @@ Init_Time(void)
id_isdst = rb_intern_const("isdst");
id_find_timezone = rb_intern_const("find_timezone");

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_subsec = ID2SYM(rb_intern_const("subsec"));
sym_dst = ID2SYM(rb_intern_const("dst"));
sym_zone = ID2SYM(rb_intern_const("zone"));

str_utc = rb_fstring_lit("UTC");
rb_gc_register_mark_object(str_utc);
str_empty = rb_fstring_lit("");
Expand Down Expand Up @@ -5572,6 +5679,7 @@ Init_Time(void)
rb_define_method(rb_cTime, "to_s", time_to_s, 0);
rb_define_method(rb_cTime, "inspect", time_inspect, 0);
rb_define_method(rb_cTime, "to_a", time_to_a, 0);
rb_define_method(rb_cTime, "deconstruct_keys", time_deconstruct_keys, 1);

rb_define_method(rb_cTime, "+", time_plus, 1);
rb_define_method(rb_cTime, "-", time_minus, 1);
Expand Down
1 change: 1 addition & 0 deletions timev.rb
Expand Up @@ -201,6 +201,7 @@
# - #getlocal: Returns a new time converted to local time.
# - #utc (aliased as #gmtime): Converts time to UTC in place.
# - #localtime: Converts time to local time in place.
# - #deconstruct_keys: Returns a hash of time components used in pattern-matching.
#
# === Methods for Rounding
#
Expand Down