Skip to content

Commit 73b1a15

Browse files
committed
Fix supporting timezone awareness for tsrange and tstzrange array columns.
Before the fix, the error was thrown "TypeError: can't iterate from ActiveSupport::TimeWithZone".
1 parent 7f5e899 commit 73b1a15

File tree

3 files changed

+69
-0
lines changed

3 files changed

+69
-0
lines changed

activerecord/CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
* Fix supporting timezone awareness for `tsrange` and `tstzrange` array columns.
2+
3+
```ruby
4+
# In database migrations
5+
add_column :shops, :open_hours, :tsrange, array: true
6+
# In app config
7+
ActiveRecord::Base.time_zone_aware_types += [:tsrange]
8+
# In the code times are properly converted to app time zone
9+
Shop.create!(open_hours: [Time.current..8.hour.from_now])
10+
```
11+
12+
*Wojciech Wnętrzak*
13+
114
* Introduce strategy pattern for executing migrations.
215

316
By default, migrations will use a strategy object that delegates the method

activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ def cast(value)
1919

2020
if value.is_a?(Hash)
2121
set_time_zone_without_conversion(super)
22+
elsif value.is_a?(Range)
23+
Range.new(user_input_in_time_zone(value.begin), user_input_in_time_zone(value.end), value.exclude_end?)
2224
elsif value.respond_to?(:in_time_zone)
2325
begin
2426
super(user_input_in_time_zone(value)) || super
@@ -40,6 +42,8 @@ def convert_time_to_time_zone(value)
4042
value.in_time_zone
4143
elsif value.respond_to?(:infinite?) && value.infinite?
4244
value
45+
elsif value.is_a?(Range)
46+
Range.new(convert_time_to_time_zone(value.begin), convert_time_to_time_zone(value.end), value.exclude_end?)
4347
else
4448
map_avoiding_infinite_recursion(value) { |v| convert_time_to_time_zone(v) }
4549
end

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ def setup
3131
t.daterange :date_range
3232
t.numrange :num_range
3333
t.tsrange :ts_range
34+
t.tsrange :ts_ranges, array: true
3435
t.tstzrange :tstz_range
36+
t.tstzrange :tstz_ranges, array: true
3537
t.int4range :int4_range
3638
t.int8range :int8_range
3739
end
@@ -187,6 +189,31 @@ def test_timezone_awareness_tzrange
187189
end
188190
end
189191

192+
def test_timezone_array_awareness_tzrange
193+
tz = "Pacific Time (US & Canada)"
194+
195+
in_time_zone tz do
196+
PostgresqlRange.reset_column_information
197+
198+
from_time_string = Time.current.to_s
199+
from_time = Time.zone.parse(from_time_string)
200+
to_time_string = (from_time + 1.hour).to_s
201+
to_time = Time.zone.parse(to_time_string)
202+
203+
record = PostgresqlRange.new(tstz_ranges: [from_time_string...to_time_string, from_time_string..to_time_string])
204+
assert_equal [from_time...to_time, from_time..to_time], record.tstz_ranges
205+
assert_equal ActiveSupport::TimeZone[tz], record.tstz_ranges.first.begin.time_zone
206+
assert_equal ActiveSupport::TimeZone[tz], record.tstz_ranges.last.begin.time_zone
207+
208+
record.save!
209+
record.reload
210+
211+
assert_equal [from_time...to_time, from_time..to_time], record.tstz_ranges
212+
assert_equal ActiveSupport::TimeZone[tz], record.tstz_ranges.first.begin.time_zone
213+
assert_equal ActiveSupport::TimeZone[tz], record.tstz_ranges.last.begin.time_zone
214+
end
215+
end
216+
190217
def test_create_tstzrange
191218
tstzrange = Time.parse("2010-01-01 14:30:00 +0100")...Time.parse("2011-02-02 14:30:00 CDT")
192219
round_trip(@new_range, :tstz_range, tstzrange)
@@ -257,6 +284,31 @@ def test_timezone_awareness_tsrange
257284
end
258285
end
259286

287+
def test_timezone_array_awareness_tsrange
288+
tz = "Pacific Time (US & Canada)"
289+
290+
in_time_zone tz do
291+
PostgresqlRange.reset_column_information
292+
293+
from_time_string = Time.current.to_s
294+
from_time = Time.zone.parse(from_time_string)
295+
to_time_string = (from_time + 1.hour).to_s
296+
to_time = Time.zone.parse(to_time_string)
297+
298+
record = PostgresqlRange.new(ts_ranges: [from_time_string...to_time_string, from_time_string..to_time_string])
299+
assert_equal [from_time...to_time, from_time..to_time], record.ts_ranges
300+
assert_equal ActiveSupport::TimeZone[tz], record.ts_ranges.first.begin.time_zone
301+
assert_equal ActiveSupport::TimeZone[tz], record.ts_ranges.last.begin.time_zone
302+
303+
record.save!
304+
record.reload
305+
306+
assert_equal [from_time...to_time, from_time..to_time], record.ts_ranges
307+
assert_equal ActiveSupport::TimeZone[tz], record.ts_ranges.first.begin.time_zone
308+
assert_equal ActiveSupport::TimeZone[tz], record.ts_ranges.last.begin.time_zone
309+
end
310+
end
311+
260312
def test_create_tstzrange_preserve_usec
261313
tstzrange = Time.parse("2010-01-01 14:30:00.670277 +0100")...Time.parse("2011-02-02 14:30:00.745125 CDT")
262314
round_trip(@new_range, :tstz_range, tstzrange)

0 commit comments

Comments
 (0)