Skip to content

Commit 9dfeb48

Browse files
authored
Merge pull request rails#51920 from justinko/issue-51745
Support PG unbounded inclusive date/time range
2 parents 3da8993 + 1bf7b67 commit 9dfeb48

File tree

2 files changed

+24
-7
lines changed

2 files changed

+24
-7
lines changed

activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,17 @@ def encode_array(array_data)
205205
end
206206

207207
def encode_range(range)
208-
"[#{type_cast_range_value(range.begin)},#{type_cast_range_value(range.end)}#{range.exclude_end? ? ')' : ']'}"
208+
lower_bound = type_cast_range_value(range.begin)
209+
upper_bound = if date_or_time_range?(range)
210+
# Postgres will convert `[today,]` to `[today,)`, making it exclusive.
211+
# We can use the special timestamp value `infinity` to force inclusion.
212+
# https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-INFINITE
213+
range.end.nil? ? "infinity" : type_cast(range.end)
214+
else
215+
type_cast_range_value(range.end)
216+
end
217+
218+
"[#{lower_bound},#{upper_bound}#{range.exclude_end? ? ')' : ']'}"
209219
end
210220

211221
def determine_encoding_of_strings_in_array(value)
@@ -229,6 +239,10 @@ def type_cast_range_value(value)
229239
def infinity?(value)
230240
value.respond_to?(:infinite?) && value.infinite?
231241
end
242+
243+
def date_or_time_range?(range)
244+
[range.begin.class, range.end.class].intersect?([Date, DateTime, Time])
245+
end
232246
end
233247
end
234248
end

activerecord/test/cases/adapters/postgresql/range_test.rb

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -196,14 +196,14 @@ def test_timezone_awareness_endless_tzrange
196196
time_string = Time.current.to_s
197197
time = Time.zone.parse(time_string)
198198

199-
record = PostgresqlRange.new(tstz_range: time_string...)
200-
assert_equal time..., record.tstz_range
199+
record = PostgresqlRange.new(tstz_range: time_string..)
200+
assert_equal time.., record.tstz_range
201201
assert_equal ActiveSupport::TimeZone[tz], record.tstz_range.begin.time_zone
202202

203203
record.save!
204204
record.reload
205205

206-
assert_equal time..., record.tstz_range
206+
assert_equal time.., record.tstz_range
207207
assert_equal ActiveSupport::TimeZone[tz], record.tstz_range.begin.time_zone
208208
end
209209
end
@@ -302,9 +302,12 @@ def test_escaped_tsrange
302302
end
303303

304304
def test_unbounded_tsrange
305-
tz = ::ActiveRecord.default_timezone
306-
assert_equal_round_trip @first_range, :ts_range, Time.public_send(tz, 2010, 1, 1, 14, 30, 0)...nil
307-
assert_equal_round_trip @first_range, :ts_range, nil..Time.public_send(tz, 2010, 1, 1, 14, 30, 0)
305+
time = Time.public_send(::ActiveRecord.default_timezone, 2010, 1, 1, 14, 30, 0)
306+
307+
assert_equal_round_trip @first_range, :ts_range, time..nil
308+
assert_equal_round_trip @first_range, :ts_range, time...nil
309+
assert_equal_round_trip @first_range, :ts_range, nil..time
310+
assert_equal_round_trip @first_range, :ts_range, nil...time
308311
end
309312

310313
def test_timezone_awareness_tsrange

0 commit comments

Comments
 (0)