activesupport 7.1.6 → 8.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +256 -1133
  3. data/README.rdoc +1 -1
  4. data/lib/active_support/array_inquirer.rb +1 -1
  5. data/lib/active_support/backtrace_cleaner.rb +81 -3
  6. data/lib/active_support/benchmark.rb +21 -0
  7. data/lib/active_support/benchmarkable.rb +3 -2
  8. data/lib/active_support/broadcast_logger.rb +65 -78
  9. data/lib/active_support/cache/file_store.rb +29 -14
  10. data/lib/active_support/cache/mem_cache_store.rb +42 -102
  11. data/lib/active_support/cache/memory_store.rb +11 -6
  12. data/lib/active_support/cache/null_store.rb +2 -2
  13. data/lib/active_support/cache/redis_cache_store.rb +58 -46
  14. data/lib/active_support/cache/serializer_with_fallback.rb +0 -23
  15. data/lib/active_support/cache/strategy/local_cache.rb +72 -27
  16. data/lib/active_support/cache/strategy/local_cache_middleware.rb +7 -7
  17. data/lib/active_support/cache.rb +146 -86
  18. data/lib/active_support/callbacks.rb +102 -126
  19. data/lib/active_support/class_attribute.rb +33 -0
  20. data/lib/active_support/code_generator.rb +9 -0
  21. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +8 -62
  22. data/lib/active_support/concurrency/share_lock.rb +0 -1
  23. data/lib/active_support/concurrency/thread_monitor.rb +55 -0
  24. data/lib/active_support/configurable.rb +34 -0
  25. data/lib/active_support/configuration_file.rb +15 -6
  26. data/lib/active_support/continuous_integration.rb +145 -0
  27. data/lib/active_support/core_ext/array/conversions.rb +3 -5
  28. data/lib/active_support/core_ext/array.rb +7 -7
  29. data/lib/active_support/core_ext/benchmark.rb +4 -14
  30. data/lib/active_support/core_ext/big_decimal.rb +1 -1
  31. data/lib/active_support/core_ext/class/attribute.rb +26 -19
  32. data/lib/active_support/core_ext/class/subclasses.rb +15 -35
  33. data/lib/active_support/core_ext/class.rb +2 -2
  34. data/lib/active_support/core_ext/date/blank.rb +4 -0
  35. data/lib/active_support/core_ext/date/conversions.rb +2 -2
  36. data/lib/active_support/core_ext/date.rb +5 -5
  37. data/lib/active_support/core_ext/date_and_time/compatibility.rb +1 -9
  38. data/lib/active_support/core_ext/date_time/blank.rb +4 -0
  39. data/lib/active_support/core_ext/date_time/compatibility.rb +3 -5
  40. data/lib/active_support/core_ext/date_time/conversions.rb +4 -6
  41. data/lib/active_support/core_ext/date_time.rb +5 -5
  42. data/lib/active_support/core_ext/digest/uuid.rb +6 -0
  43. data/lib/active_support/core_ext/digest.rb +1 -1
  44. data/lib/active_support/core_ext/enumerable.rb +25 -8
  45. data/lib/active_support/core_ext/erb/util.rb +10 -5
  46. data/lib/active_support/core_ext/file.rb +1 -1
  47. data/lib/active_support/core_ext/hash/deep_merge.rb +1 -0
  48. data/lib/active_support/core_ext/hash/except.rb +0 -12
  49. data/lib/active_support/core_ext/hash/keys.rb +4 -4
  50. data/lib/active_support/core_ext/hash.rb +8 -8
  51. data/lib/active_support/core_ext/integer.rb +3 -3
  52. data/lib/active_support/core_ext/kernel.rb +3 -3
  53. data/lib/active_support/core_ext/module/attr_internal.rb +16 -6
  54. data/lib/active_support/core_ext/module/delegation.rb +20 -163
  55. data/lib/active_support/core_ext/module/deprecation.rb +1 -4
  56. data/lib/active_support/core_ext/module/introspection.rb +3 -0
  57. data/lib/active_support/core_ext/module.rb +11 -11
  58. data/lib/active_support/core_ext/numeric/conversions.rb +3 -3
  59. data/lib/active_support/core_ext/numeric.rb +3 -3
  60. data/lib/active_support/core_ext/object/blank.rb +45 -1
  61. data/lib/active_support/core_ext/object/instance_variables.rb +11 -19
  62. data/lib/active_support/core_ext/object/json.rb +24 -11
  63. data/lib/active_support/core_ext/object/to_query.rb +7 -1
  64. data/lib/active_support/core_ext/object/try.rb +2 -2
  65. data/lib/active_support/core_ext/object/with.rb +5 -3
  66. data/lib/active_support/core_ext/object.rb +13 -13
  67. data/lib/active_support/core_ext/pathname/blank.rb +4 -0
  68. data/lib/active_support/core_ext/pathname.rb +2 -2
  69. data/lib/active_support/core_ext/range/overlap.rb +4 -4
  70. data/lib/active_support/core_ext/range/sole.rb +17 -0
  71. data/lib/active_support/core_ext/range.rb +4 -4
  72. data/lib/active_support/core_ext/securerandom.rb +4 -4
  73. data/lib/active_support/core_ext/string/conversions.rb +1 -1
  74. data/lib/active_support/core_ext/string/filters.rb +4 -4
  75. data/lib/active_support/core_ext/string/multibyte.rb +13 -4
  76. data/lib/active_support/core_ext/string/output_safety.rb +19 -19
  77. data/lib/active_support/core_ext/string.rb +13 -13
  78. data/lib/active_support/core_ext/symbol.rb +1 -1
  79. data/lib/active_support/core_ext/thread/backtrace/location.rb +2 -7
  80. data/lib/active_support/core_ext/time/calculations.rb +25 -30
  81. data/lib/active_support/core_ext/time/compatibility.rb +2 -3
  82. data/lib/active_support/core_ext/time/conversions.rb +2 -2
  83. data/lib/active_support/core_ext/time/zones.rb +1 -1
  84. data/lib/active_support/core_ext/time.rb +5 -5
  85. data/lib/active_support/core_ext.rb +1 -2
  86. data/lib/active_support/current_attributes/test_helper.rb +2 -2
  87. data/lib/active_support/current_attributes.rb +58 -50
  88. data/lib/active_support/delegation.rb +200 -0
  89. data/lib/active_support/dependencies/autoload.rb +0 -12
  90. data/lib/active_support/dependencies/interlock.rb +11 -5
  91. data/lib/active_support/dependencies.rb +6 -2
  92. data/lib/active_support/deprecation/constant_accessor.rb +47 -26
  93. data/lib/active_support/deprecation/proxy_wrappers.rb +9 -12
  94. data/lib/active_support/deprecation/reporting.rb +5 -17
  95. data/lib/active_support/deprecation.rb +8 -5
  96. data/lib/active_support/descendants_tracker.rb +9 -87
  97. data/lib/active_support/duration/iso8601_parser.rb +2 -2
  98. data/lib/active_support/duration/iso8601_serializer.rb +1 -2
  99. data/lib/active_support/duration.rb +25 -16
  100. data/lib/active_support/editor.rb +70 -0
  101. data/lib/active_support/encrypted_configuration.rb +20 -2
  102. data/lib/active_support/encrypted_file.rb +1 -1
  103. data/lib/active_support/error_reporter.rb +121 -6
  104. data/lib/active_support/event_reporter/test_helper.rb +32 -0
  105. data/lib/active_support/event_reporter.rb +592 -0
  106. data/lib/active_support/evented_file_update_checker.rb +5 -3
  107. data/lib/active_support/execution_context.rb +64 -7
  108. data/lib/active_support/execution_wrapper.rb +1 -2
  109. data/lib/active_support/file_update_checker.rb +9 -7
  110. data/lib/active_support/fork_tracker.rb +2 -38
  111. data/lib/active_support/gem_version.rb +2 -2
  112. data/lib/active_support/gzip.rb +1 -0
  113. data/lib/active_support/hash_with_indifferent_access.rb +66 -45
  114. data/lib/active_support/html_safe_translation.rb +3 -0
  115. data/lib/active_support/i18n_railtie.rb +19 -11
  116. data/lib/active_support/inflector/inflections.rb +31 -15
  117. data/lib/active_support/inflector/transliterate.rb +6 -8
  118. data/lib/active_support/isolated_execution_state.rb +12 -17
  119. data/lib/active_support/json/decoding.rb +6 -4
  120. data/lib/active_support/json/encoding.rb +157 -21
  121. data/lib/active_support/lazy_load_hooks.rb +1 -1
  122. data/lib/active_support/log_subscriber.rb +2 -18
  123. data/lib/active_support/logger.rb +15 -2
  124. data/lib/active_support/logger_thread_safe_level.rb +4 -9
  125. data/lib/active_support/message_encryptors.rb +54 -2
  126. data/lib/active_support/message_pack/extensions.rb +20 -2
  127. data/lib/active_support/message_verifier.rb +21 -0
  128. data/lib/active_support/message_verifiers.rb +57 -3
  129. data/lib/active_support/messages/rotation_coordinator.rb +9 -0
  130. data/lib/active_support/messages/rotator.rb +10 -0
  131. data/lib/active_support/multibyte/chars.rb +14 -4
  132. data/lib/active_support/multibyte.rb +4 -0
  133. data/lib/active_support/notifications/fanout.rb +68 -50
  134. data/lib/active_support/notifications/instrumenter.rb +22 -19
  135. data/lib/active_support/notifications.rb +28 -27
  136. data/lib/active_support/number_helper/number_converter.rb +2 -2
  137. data/lib/active_support/number_helper.rb +22 -0
  138. data/lib/active_support/option_merger.rb +2 -2
  139. data/lib/active_support/ordered_options.rb +53 -15
  140. data/lib/active_support/railtie.rb +36 -20
  141. data/lib/active_support/string_inquirer.rb +1 -1
  142. data/lib/active_support/structured_event_subscriber.rb +99 -0
  143. data/lib/active_support/subscriber.rb +1 -5
  144. data/lib/active_support/syntax_error_proxy.rb +3 -0
  145. data/lib/active_support/tagged_logging.rb +5 -1
  146. data/lib/active_support/test_case.rb +63 -6
  147. data/lib/active_support/testing/assertions.rb +113 -27
  148. data/lib/active_support/testing/constant_stubbing.rb +30 -8
  149. data/lib/active_support/testing/deprecation.rb +5 -12
  150. data/lib/active_support/testing/error_reporter_assertions.rb +18 -1
  151. data/lib/active_support/testing/event_reporter_assertions.rb +227 -0
  152. data/lib/active_support/testing/isolation.rb +19 -9
  153. data/lib/active_support/testing/method_call_assertions.rb +2 -16
  154. data/lib/active_support/testing/notification_assertions.rb +92 -0
  155. data/lib/active_support/testing/parallelization/server.rb +18 -2
  156. data/lib/active_support/testing/parallelization/worker.rb +4 -2
  157. data/lib/active_support/testing/parallelization.rb +25 -1
  158. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  159. data/lib/active_support/testing/time_helpers.rb +11 -6
  160. data/lib/active_support/time_with_zone.rb +39 -26
  161. data/lib/active_support/values/time_zone.rb +26 -17
  162. data/lib/active_support/xml_mini.rb +14 -4
  163. data/lib/active_support.rb +22 -9
  164. metadata +31 -17
  165. data/lib/active_support/core_ext/range/each.rb +0 -24
  166. data/lib/active_support/deprecation/instance_delegator.rb +0 -65
  167. data/lib/active_support/proxy_object.rb +0 -17
  168. data/lib/active_support/ruby_features.rb +0 -7
  169. data/lib/active_support/testing/strict_warnings.rb +0 -39
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ module Testing
5
+ module NotificationAssertions
6
+ # Assert a notification was emitted with a given +pattern+ and optional +payload+.
7
+ #
8
+ # You can assert that a notification was emitted by passing a pattern, which accepts
9
+ # either a string or regexp, an optional payload, and a block. While the block
10
+ # is executed, if a matching notification is emitted, the assertion will pass
11
+ # and the notification will be returned.
12
+ #
13
+ # Note that the payload is matched as a subset, meaning that the notification must
14
+ # contain at least the specified keys and values, but may contain additional ones.
15
+ #
16
+ # assert_notification("post.submitted", title: "Cool Post") do
17
+ # post.submit(title: "Cool Post", body: "Cool Body") # => emits matching notification
18
+ # end
19
+ #
20
+ # Using the returned notification, you can make more customized assertions.
21
+ #
22
+ # notification = assert_notification("post.submitted", title: "Cool Post") do
23
+ # ActiveSupport::Notifications.instrument("post.submitted", title: "Cool Post", body: Body.new("Cool Body"))
24
+ # end
25
+ #
26
+ # assert_instance_of(Body, notification.payload[:body])
27
+ #
28
+ def assert_notification(pattern, payload = nil, &block)
29
+ notifications = capture_notifications(pattern, &block)
30
+ assert_not_empty(notifications, "No #{pattern} notifications were found")
31
+
32
+ return notifications.first if payload.nil?
33
+
34
+ notification = notifications.find { |notification| notification.payload.slice(*payload.keys) == payload }
35
+ assert_not_nil(notification, "No #{pattern} notification with payload #{payload} was found")
36
+
37
+ notification
38
+ end
39
+
40
+ # Assert the number of notifications emitted with a given +pattern+.
41
+ #
42
+ # You can assert the number of notifications emitted by passing a pattern, which accepts
43
+ # either a string or regexp, a count, and a block. While the block is executed,
44
+ # the number of matching notifications emitted will be counted. After the block's
45
+ # execution completes, the assertion will pass if the count matches.
46
+ #
47
+ # assert_notifications_count("post.submitted", 1) do
48
+ # post.submit(title: "Cool Post") # => emits matching notification
49
+ # end
50
+ #
51
+ def assert_notifications_count(pattern, count, &block)
52
+ actual_count = capture_notifications(pattern, &block).count
53
+ assert_equal(count, actual_count, "Expected #{count} instead of #{actual_count} notifications for #{pattern}")
54
+ end
55
+
56
+ # Assert no notifications were emitted for a given +pattern+.
57
+ #
58
+ # You can assert no notifications were emitted by passing a pattern, which accepts
59
+ # either a string or regexp, and a block. While the block is executed, if no
60
+ # matching notifications are emitted, the assertion will pass.
61
+ #
62
+ # assert_no_notifications("post.submitted") do
63
+ # post.destroy # => emits non-matching notification
64
+ # end
65
+ #
66
+ def assert_no_notifications(pattern = nil, &block)
67
+ notifications = capture_notifications(pattern, &block)
68
+ error_message = if pattern
69
+ "Expected no notifications for #{pattern} but found #{notifications.size}"
70
+ else
71
+ "Expected no notifications but found #{notifications.size}"
72
+ end
73
+ assert_empty(notifications, error_message)
74
+ end
75
+
76
+ # Capture emitted notifications, optionally filtered by a +pattern+.
77
+ #
78
+ # You can capture emitted notifications, optionally filtered by a pattern,
79
+ # which accepts either a string or regexp, and a block.
80
+ #
81
+ # notifications = capture_notifications("post.submitted") do
82
+ # post.submit(title: "Cool Post") # => emits matching notification
83
+ # end
84
+ #
85
+ def capture_notifications(pattern = nil, &block)
86
+ notifications = []
87
+ ActiveSupport::Notifications.subscribed(->(n) { notifications << n }, pattern, &block)
88
+ notifications
89
+ end
90
+ end
91
+ end
92
+ end
@@ -6,12 +6,15 @@ require "drb/unix" unless Gem.win_platform?
6
6
  module ActiveSupport
7
7
  module Testing
8
8
  class Parallelization # :nodoc:
9
+ PrerecordResultClass = Struct.new(:name)
10
+
9
11
  class Server
10
12
  include DRb::DRbUndumped
11
13
 
12
14
  def initialize
13
15
  @queue = Queue.new
14
16
  @active_workers = Concurrent::Map.new
17
+ @worker_pids = Concurrent::Map.new
15
18
  @in_flight = Concurrent::Map.new
16
19
  end
17
20
 
@@ -21,6 +24,7 @@ module ActiveSupport
21
24
  @in_flight.delete([result.klass, result.name])
22
25
 
23
26
  reporter.synchronize do
27
+ reporter.prerecord(PrerecordResultClass.new(result.klass), result.name)
24
28
  reporter.record(result)
25
29
  end
26
30
  end
@@ -37,12 +41,24 @@ module ActiveSupport
37
41
  end
38
42
  end
39
43
 
40
- def start_worker(worker_id)
44
+ def start_worker(worker_id, worker_pid)
41
45
  @active_workers[worker_id] = true
46
+ @worker_pids[worker_id] = worker_pid
42
47
  end
43
48
 
44
- def stop_worker(worker_id)
49
+ def stop_worker(worker_id, worker_pid)
45
50
  @active_workers.delete(worker_id)
51
+ @worker_pids.delete(worker_id)
52
+ end
53
+
54
+ def remove_dead_workers(dead_pids)
55
+ dead_pids.each do |dead_pid|
56
+ worker_id = @worker_pids.key(dead_pid)
57
+ if worker_id
58
+ @active_workers.delete(worker_id)
59
+ @worker_pids.delete(worker_id)
60
+ end
61
+ end
46
62
  end
47
63
 
48
64
  def active_workers?
@@ -18,7 +18,7 @@ module ActiveSupport
18
18
  DRb.stop_service
19
19
 
20
20
  @queue = DRbObject.new_with_uri(@url)
21
- @queue.start_worker(@id)
21
+ @queue.start_worker(@id, Process.pid)
22
22
 
23
23
  begin
24
24
  after_fork
@@ -29,7 +29,7 @@ module ActiveSupport
29
29
  set_process_title("(stopping)")
30
30
 
31
31
  run_cleanup
32
- @queue.stop_worker(@id)
32
+ @queue.stop_worker(@id, Process.pid)
33
33
  end
34
34
  end
35
35
 
@@ -78,6 +78,8 @@ module ActiveSupport
78
78
  end
79
79
 
80
80
  def after_fork
81
+ ActiveSupport::TestCase.parallel_worker_id = @number
82
+
81
83
  Parallelization.after_fork_hooks.each do |cb|
82
84
  cb.call(@number)
83
85
  end
@@ -9,6 +9,14 @@ require "active_support/testing/parallelization/worker"
9
9
  module ActiveSupport
10
10
  module Testing
11
11
  class Parallelization # :nodoc:
12
+ @@before_fork_hooks = []
13
+
14
+ def self.before_fork_hook(&blk)
15
+ @@before_fork_hooks << blk
16
+ end
17
+
18
+ cattr_reader :before_fork_hooks
19
+
12
20
  @@after_fork_hooks = []
13
21
 
14
22
  def self.after_fork_hook(&blk)
@@ -32,7 +40,12 @@ module ActiveSupport
32
40
  @url = DRb.start_service("drbunix:", @queue_server).uri
33
41
  end
34
42
 
43
+ def before_fork
44
+ Parallelization.before_fork_hooks.each(&:call)
45
+ end
46
+
35
47
  def start
48
+ before_fork
36
49
  @worker_pool = @worker_count.times.map do |worker|
37
50
  Worker.new(worker, @url).start
38
51
  end
@@ -47,8 +60,19 @@ module ActiveSupport
47
60
  end
48
61
 
49
62
  def shutdown
63
+ dead_worker_pids = @worker_pool.filter_map do |pid|
64
+ Process.waitpid(pid, Process::WNOHANG)
65
+ rescue Errno::ECHILD
66
+ pid
67
+ end
68
+ @queue_server.remove_dead_workers(dead_worker_pids)
69
+
50
70
  @queue_server.shutdown
51
- @worker_pool.each { |pid| Process.waitpid pid }
71
+ @worker_pool.each do |pid|
72
+ Process.waitpid(pid)
73
+ rescue Errno::ECHILD
74
+ nil
75
+ end
52
76
  end
53
77
  end
54
78
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ module Testing
5
+ # Warns when a test case does not perform any assertions.
6
+ #
7
+ # This is helpful in detecting broken tests that do not perform intended assertions.
8
+ module TestsWithoutAssertions # :nodoc:
9
+ def after_teardown
10
+ super
11
+
12
+ if assertions.zero? && !skipped? && !error?
13
+ file, line = method(name).source_location
14
+ warn "Test is missing assertions: `#{name}` #{File.expand_path(file)}:#{line}"
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -163,12 +163,13 @@ module ActiveSupport
163
163
  now = date_or_time.midnight.to_time
164
164
  elsif date_or_time.is_a?(String)
165
165
  now = Time.zone.parse(date_or_time)
166
- elsif with_usec
167
- now = date_or_time.to_time
168
166
  else
169
- now = date_or_time.to_time.change(usec: 0)
167
+ now = date_or_time
168
+ now = now.to_time unless now.is_a?(Time)
170
169
  end
171
170
 
171
+ now = now.change(usec: 0) unless with_usec
172
+
172
173
  # +now+ must be in local system timezone, because +Time.at(now)+
173
174
  # and +now.to_date+ (see stubs below) will use +now+'s timezone too!
174
175
  now = now.getlocal
@@ -237,12 +238,16 @@ module ActiveSupport
237
238
  end
238
239
  alias_method :unfreeze_time, :travel_back
239
240
 
240
- # Calls +travel_to+ with +Time.now+. Forwards optional <tt>with_usec</tt> argument.
241
+ # Calls +travel_to+ with +date_or_time+, which defaults to +Time.now+.
242
+ # Forwards optional <tt>with_usec</tt> argument.
241
243
  #
242
244
  # Time.current # => Sun, 09 Jul 2017 15:34:49 EST -05:00
243
245
  # freeze_time
244
246
  # sleep(1)
245
247
  # Time.current # => Sun, 09 Jul 2017 15:34:49 EST -05:00
248
+ # freeze_time Time.current + 1.day
249
+ # sleep(1)
250
+ # Time.current # => Mon, 10 Jul 2017 15:34:49 EST -05:00
246
251
  #
247
252
  # This method also accepts a block, which will return the current time back to its original
248
253
  # state at the end of the block:
@@ -253,8 +258,8 @@ module ActiveSupport
253
258
  # User.create.created_at # => Sun, 09 Jul 2017 15:34:49 EST -05:00
254
259
  # end
255
260
  # Time.current # => Sun, 09 Jul 2017 15:34:50 EST -05:00
256
- def freeze_time(with_usec: false, &block)
257
- travel_to Time.now, with_usec: with_usec, &block
261
+ def freeze_time(date_or_time = Time.now, with_usec: false, &block)
262
+ travel_to date_or_time, with_usec: with_usec, &block
258
263
  end
259
264
 
260
265
  private
@@ -49,9 +49,15 @@ module ActiveSupport
49
49
  attr_reader :time_zone
50
50
 
51
51
  def initialize(utc_time, time_zone, local_time = nil, period = nil)
52
- @utc = utc_time ? transfer_time_values_to_utc_constructor(utc_time) : nil
53
52
  @time_zone, @time = time_zone, local_time
54
- @period = @utc ? period : get_period_and_ensure_valid_local_time(period)
53
+ if utc_time
54
+ @utc = transfer_time_values_to_utc_constructor(utc_time)
55
+ @period = period
56
+ else
57
+ @utc = nil
58
+ @period = get_period_and_ensure_valid_local_time(period)
59
+ end
60
+ @is_utc = zone == "UTC" || zone == "UCT"
55
61
  end
56
62
 
57
63
  # Returns a <tt>Time</tt> instance that represents the time in +time_zone+.
@@ -85,7 +91,7 @@ module ActiveSupport
85
91
  end
86
92
  alias_method :getlocal, :localtime
87
93
 
88
- # Returns true if the current time is within Daylight Savings Time for the
94
+ # Returns true if the current time is within Daylight Savings \Time for the
89
95
  # specified time zone.
90
96
  #
91
97
  # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
@@ -103,7 +109,7 @@ module ActiveSupport
103
109
  # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
104
110
  # Time.zone.now.utc? # => false
105
111
  def utc?
106
- zone == "UTC" || zone == "UCT"
112
+ @is_utc
107
113
  end
108
114
  alias_method :gmt?, :utc?
109
115
 
@@ -136,9 +142,9 @@ module ActiveSupport
136
142
 
137
143
  # Returns a string of the object's date, time, zone, and offset from UTC.
138
144
  #
139
- # Time.zone.now.inspect # => "Thu, 04 Dec 2014 11:00:25.624541392 EST -05:00"
145
+ # Time.zone.now.inspect # => "2024-11-13 07:00:10.528054960 UTC +00:00"
140
146
  def inspect
141
- "#{time.strftime('%a, %d %b %Y %H:%M:%S.%9N')} #{zone} #{formatted_offset}"
147
+ "#{time.strftime('%F %H:%M:%S.%9N')} #{zone} #{formatted_offset}"
142
148
  end
143
149
 
144
150
  # Returns a string of the object's date and time in the ISO 8601 standard
@@ -146,7 +152,13 @@ module ActiveSupport
146
152
  #
147
153
  # Time.zone.now.xmlschema # => "2014-12-04T11:02:37-05:00"
148
154
  def xmlschema(fraction_digits = 0)
149
- "#{time.strftime(PRECISIONS[fraction_digits.to_i])}#{formatted_offset(true, 'Z')}"
155
+ if @is_utc
156
+ utc.iso8601(fraction_digits || 0)
157
+ else
158
+ str = time.iso8601(fraction_digits || 0)
159
+ str[-1] = formatted_offset(true, "Z")
160
+ str
161
+ end
150
162
  end
151
163
  alias_method :iso8601, :xmlschema
152
164
  alias_method :rfc3339, :xmlschema
@@ -157,11 +169,11 @@ module ActiveSupport
157
169
  # to +false+.
158
170
  #
159
171
  # # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = true
160
- # Time.utc(2005,2,1,15,15,10).in_time_zone("Hawaii").to_json
172
+ # Time.utc(2005,2,1,15,15,10).in_time_zone("Hawaii").as_json
161
173
  # # => "2005-02-01T05:15:10.000-10:00"
162
174
  #
163
175
  # # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = false
164
- # Time.utc(2005,2,1,15,15,10).in_time_zone("Hawaii").to_json
176
+ # Time.utc(2005,2,1,15,15,10).in_time_zone("Hawaii").as_json
165
177
  # # => "2005/02/01 05:15:10 -1000"
166
178
  def as_json(options = nil)
167
179
  if ActiveSupport::JSON::Encoding.use_standard_json_time_format
@@ -215,8 +227,7 @@ module ActiveSupport
215
227
  elsif formatter = ::Time::DATE_FORMATS[format]
216
228
  formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
217
229
  else
218
- # Change to to_s when deprecation is gone.
219
- "#{time.strftime("%Y-%m-%d %H:%M:%S")} #{formatted_offset(false, 'UTC')}"
230
+ to_s
220
231
  end
221
232
  end
222
233
  alias_method :to_formatted_s, :to_fs
@@ -300,7 +311,8 @@ module ActiveSupport
300
311
  if duration_of_variable_length?(other)
301
312
  method_missing(:+, other)
302
313
  else
303
- result = utc.acts_like?(:date) ? utc.since(other) : utc + other rescue utc.since(other)
314
+ result = utc + other
315
+
304
316
  result.in_time_zone(time_zone)
305
317
  end
306
318
  end
@@ -332,11 +344,11 @@ module ActiveSupport
332
344
  #
333
345
  def -(other)
334
346
  if other.acts_like?(:time)
335
- to_time - other.to_time
347
+ getutc - other.getutc
336
348
  elsif duration_of_variable_length?(other)
337
349
  method_missing(:-, other)
338
350
  else
339
- result = utc.acts_like?(:date) ? utc.ago(other) : utc - other rescue utc.ago(other)
351
+ result = utc - other
340
352
  result.in_time_zone(time_zone)
341
353
  end
342
354
  end
@@ -375,8 +387,8 @@ module ActiveSupport
375
387
  #
376
388
  # t = Time.zone.now # => Fri, 14 Apr 2017 11:45:15.116992711 EST -05:00
377
389
  # t.change(year: 2020) # => Tue, 14 Apr 2020 11:45:15.116992711 EST -05:00
378
- # t.change(hour: 12) # => Fri, 14 Apr 2017 12:00:00.116992711 EST -05:00
379
- # t.change(min: 30) # => Fri, 14 Apr 2017 11:30:00.116992711 EST -05:00
390
+ # t.change(hour: 12) # => Fri, 14 Apr 2017 12:00:00.000000000 EST -05:00
391
+ # t.change(min: 30) # => Fri, 14 Apr 2017 11:30:00.000000000 EST -05:00
380
392
  # t.change(offset: "-10:00") # => Fri, 14 Apr 2017 11:45:15.116992711 HST -10:00
381
393
  # t.change(zone: "Hawaii") # => Fri, 14 Apr 2017 11:45:15.116992711 HST -10:00
382
394
  def change(options)
@@ -479,15 +491,11 @@ module ActiveSupport
479
491
  @to_datetime ||= utc.to_datetime.new_offset(Rational(utc_offset, 86_400))
480
492
  end
481
493
 
482
- # Returns an instance of +Time+, either with the same UTC offset
483
- # as +self+ or in the local system timezone depending on the setting
484
- # of +ActiveSupport.to_time_preserves_timezone+.
494
+ # Returns an instance of +Time+, either with the same timezone as +self+,
495
+ # with the same UTC offset as +self+ or in the local system timezone
496
+ # depending on the setting of +ActiveSupport.to_time_preserves_timezone+.
485
497
  def to_time
486
- if preserve_timezone
487
- @to_time_with_instance_offset ||= getlocal(utc_offset)
488
- else
489
- @to_time_with_system_offset ||= getlocal
490
- end
498
+ @to_time_with_timezone ||= getlocal(time_zone)
491
499
  end
492
500
 
493
501
  # So that +self+ <tt>acts_like?(:time)</tt>.
@@ -506,6 +514,10 @@ module ActiveSupport
506
514
  false
507
515
  end
508
516
 
517
+ def present? # :nodoc:
518
+ true
519
+ end
520
+
509
521
  def freeze
510
522
  # preload instance variables before freezing
511
523
  period; utc; time; to_datetime; to_time
@@ -531,7 +543,6 @@ module ActiveSupport
531
543
  # Ensure proxy class responds to all methods that underlying time instance
532
544
  # responds to.
533
545
  def respond_to_missing?(sym, include_priv)
534
- return false if sym.to_sym == :acts_like_date?
535
546
  time.respond_to?(sym, include_priv)
536
547
  end
537
548
 
@@ -547,7 +558,9 @@ module ActiveSupport
547
558
  SECONDS_PER_DAY = 86400
548
559
 
549
560
  def incorporate_utc_offset(time, offset)
550
- if time.kind_of?(Date)
561
+ if offset.zero?
562
+ time
563
+ elsif time.kind_of?(Date)
551
564
  time + Rational(offset, SECONDS_PER_DAY)
552
565
  else
553
566
  time + offset
@@ -12,7 +12,7 @@ module ActiveSupport
12
12
  # * Limit the set of zones provided by TZInfo to a meaningful subset of 134
13
13
  # zones.
14
14
  # * Retrieve and display zones with a friendlier name
15
- # (e.g., "Eastern Time (US & Canada)" instead of "America/New_York").
15
+ # (e.g., "Eastern \Time (US & Canada)" instead of "America/New_York").
16
16
  # * Lazily load +TZInfo::Timezone+ instances only when they're needed.
17
17
  # * Create ActiveSupport::TimeWithZone instances via TimeZone's +local+,
18
18
  # +parse+, +at+, and +now+ methods.
@@ -57,13 +57,14 @@ module ActiveSupport
57
57
  "Caracas" => "America/Caracas",
58
58
  "La Paz" => "America/La_Paz",
59
59
  "Santiago" => "America/Santiago",
60
+ "Asuncion" => "America/Asuncion",
60
61
  "Newfoundland" => "America/St_Johns",
61
62
  "Brasilia" => "America/Sao_Paulo",
62
63
  "Buenos Aires" => "America/Argentina/Buenos_Aires",
63
64
  "Montevideo" => "America/Montevideo",
64
65
  "Georgetown" => "America/Guyana",
65
66
  "Puerto Rico" => "America/Puerto_Rico",
66
- "Greenland" => "America/Godthab",
67
+ "Greenland" => "America/Nuuk",
67
68
  "Mid-Atlantic" => "Atlantic/South_Georgia",
68
69
  "Azores" => "Atlantic/Azores",
69
70
  "Cape Verde Is." => "Atlantic/Cape_Verde",
@@ -134,10 +135,10 @@ module ActiveSupport
134
135
  "Mumbai" => "Asia/Kolkata",
135
136
  "New Delhi" => "Asia/Kolkata",
136
137
  "Kathmandu" => "Asia/Kathmandu",
137
- "Astana" => "Asia/Dhaka",
138
138
  "Dhaka" => "Asia/Dhaka",
139
139
  "Sri Jayawardenepura" => "Asia/Colombo",
140
140
  "Almaty" => "Asia/Almaty",
141
+ "Astana" => "Asia/Almaty",
141
142
  "Novosibirsk" => "Asia/Novosibirsk",
142
143
  "Rangoon" => "Asia/Rangoon",
143
144
  "Bangkok" => "Asia/Bangkok",
@@ -208,9 +209,7 @@ module ActiveSupport
208
209
  TZInfo::Timezone.get(MAPPING[name] || name)
209
210
  end
210
211
 
211
- # :stopdoc:
212
- alias_method :create, :new
213
- # :startdoc:
212
+ alias_method :create, :new # :nodoc:
214
213
 
215
214
  # Returns a TimeZone instance with the given name, or +nil+ if no
216
215
  # such TimeZone instance exists. (This exists to support the use of
@@ -315,6 +314,12 @@ module ActiveSupport
315
314
  end
316
315
  # :startdoc:
317
316
 
317
+ # Returns a standard time zone name defined by IANA
318
+ # https://www.iana.org/time-zones
319
+ def standard_name
320
+ MAPPING[name] || name
321
+ end
322
+
318
323
  # Returns the offset of this time zone from UTC in seconds.
319
324
  def utc_offset
320
325
  @utc_offset || tzinfo&.current_period&.base_utc_offset
@@ -357,7 +362,7 @@ module ActiveSupport
357
362
  "(GMT#{formatted_offset}) #{name}"
358
363
  end
359
364
 
360
- # Method for creating new ActiveSupport::TimeWithZone instance in time zone
365
+ # \Method for creating new ActiveSupport::TimeWithZone instance in time zone
361
366
  # of +self+ from given values.
362
367
  #
363
368
  # Time.zone = 'Hawaii' # => "Hawaii"
@@ -367,7 +372,7 @@ module ActiveSupport
367
372
  ActiveSupport::TimeWithZone.new(nil, self, time)
368
373
  end
369
374
 
370
- # Method for creating new ActiveSupport::TimeWithZone instance in time zone
375
+ # \Method for creating new ActiveSupport::TimeWithZone instance in time zone
371
376
  # of +self+ from number of seconds since the Unix epoch.
372
377
  #
373
378
  # Time.zone = 'Hawaii' # => "Hawaii"
@@ -382,7 +387,7 @@ module ActiveSupport
382
387
  Time.at(*args).utc.in_time_zone(self)
383
388
  end
384
389
 
385
- # Method for creating new ActiveSupport::TimeWithZone instance in time zone
390
+ # \Method for creating new ActiveSupport::TimeWithZone instance in time zone
386
391
  # of +self+ from an ISO 8601 string.
387
392
  #
388
393
  # Time.zone = 'Hawaii' # => "Hawaii"
@@ -434,7 +439,7 @@ module ActiveSupport
434
439
  raise ArgumentError, "invalid date"
435
440
  end
436
441
 
437
- # Method for creating new ActiveSupport::TimeWithZone instance in time zone
442
+ # \Method for creating new ActiveSupport::TimeWithZone instance in time zone
438
443
  # of +self+ from parsed string.
439
444
  #
440
445
  # Time.zone = 'Hawaii' # => "Hawaii"
@@ -456,7 +461,7 @@ module ActiveSupport
456
461
  parts_to_time(Date._parse(str, false), now)
457
462
  end
458
463
 
459
- # Method for creating new ActiveSupport::TimeWithZone instance in time zone
464
+ # \Method for creating new ActiveSupport::TimeWithZone instance in time zone
460
465
  # of +self+ from an RFC 3339 string.
461
466
  #
462
467
  # Time.zone = 'Hawaii' # => "Hawaii"
@@ -554,15 +559,11 @@ module ActiveSupport
554
559
  tzinfo.local_to_utc(time, dst)
555
560
  end
556
561
 
557
- # Available so that TimeZone instances respond like +TZInfo::Timezone+
558
- # instances.
559
- def period_for_utc(time)
562
+ def period_for_utc(time) # :nodoc:
560
563
  tzinfo.period_for_utc(time)
561
564
  end
562
565
 
563
- # Available so that TimeZone instances respond like +TZInfo::Timezone+
564
- # instances.
565
- def period_for_local(time, dst = true)
566
+ def period_for_local(time, dst = true) # :nodoc:
566
567
  tzinfo.period_for_local(time, dst) { |periods| periods.last }
567
568
  end
568
569
 
@@ -570,6 +571,14 @@ module ActiveSupport
570
571
  tzinfo.periods_for_local(time)
571
572
  end
572
573
 
574
+ def abbr(time) # :nodoc:
575
+ tzinfo.abbr(time)
576
+ end
577
+
578
+ def dst?(time) # :nodoc:
579
+ tzinfo.dst?(time)
580
+ end
581
+
573
582
  def init_with(coder) # :nodoc:
574
583
  initialize(coder["name"])
575
584
  end
@@ -12,7 +12,7 @@ module ActiveSupport
12
12
  # = \XmlMini
13
13
  #
14
14
  # To use the much faster libxml parser:
15
- # gem 'libxml-ruby'
15
+ # gem "libxml-ruby"
16
16
  # XmlMini.backend = 'LibXML'
17
17
  module XmlMini
18
18
  extend self
@@ -46,6 +46,7 @@ module ActiveSupport
46
46
  "Date" => "date",
47
47
  "DateTime" => "dateTime",
48
48
  "Time" => "dateTime",
49
+ "ActiveSupport::Duration" => "duration",
49
50
  "Array" => "array",
50
51
  "Hash" => "hash"
51
52
  }
@@ -56,21 +57,24 @@ module ActiveSupport
56
57
  "symbol" => Proc.new { |symbol| symbol.to_s },
57
58
  "date" => Proc.new { |date| date.to_fs(:db) },
58
59
  "dateTime" => Proc.new { |time| time.xmlschema },
60
+ "duration" => Proc.new { |duration| duration.iso8601 },
59
61
  "binary" => Proc.new { |binary| ::Base64.encode64(binary) },
60
62
  "yaml" => Proc.new { |yaml| yaml.to_yaml }
61
63
  } unless defined?(FORMATTING)
62
64
 
63
- # TODO use regexp instead of Date.parse
64
65
  unless defined?(PARSING)
65
66
  PARSING = {
66
67
  "symbol" => Proc.new { |symbol| symbol.to_s.to_sym },
67
- "date" => Proc.new { |date| ::Date.parse(date) },
68
+ "date" => Proc.new { |date| ::Date.strptime(date, "%Y-%m-%d") },
68
69
  "datetime" => Proc.new { |time| Time.xmlschema(time).utc rescue ::DateTime.parse(time).utc },
70
+ "duration" => Proc.new { |duration| Duration.parse(duration) },
69
71
  "integer" => Proc.new { |integer| integer.to_i },
70
72
  "float" => Proc.new { |float| float.to_f },
71
73
  "decimal" => Proc.new do |number|
72
74
  if String === number
73
75
  number.to_d
76
+ elsif Float === number
77
+ BigDecimal(number, 0)
74
78
  else
75
79
  BigDecimal(number)
76
80
  end
@@ -79,6 +83,7 @@ module ActiveSupport
79
83
  "string" => Proc.new { |string| string.to_s },
80
84
  "yaml" => Proc.new { |yaml| YAML.load(yaml) rescue yaml },
81
85
  "base64Binary" => Proc.new { |bin| ::Base64.decode64(bin) },
86
+ "hexBinary" => Proc.new { |bin| _parse_hex_binary(bin) },
82
87
  "binary" => Proc.new { |bin, entity| _parse_binary(bin, entity) },
83
88
  "file" => Proc.new { |file, entity| _parse_file(file, entity) }
84
89
  }
@@ -162,11 +167,12 @@ module ActiveSupport
162
167
  "#{left}#{middle.tr('_ ', '--')}#{right}"
163
168
  end
164
169
 
165
- # TODO: Add support for other encodings
166
170
  def _parse_binary(bin, entity)
167
171
  case entity["encoding"]
168
172
  when "base64"
169
173
  ::Base64.decode64(bin)
174
+ when "hex", "hexBinary"
175
+ _parse_hex_binary(bin)
170
176
  else
171
177
  bin
172
178
  end
@@ -180,6 +186,10 @@ module ActiveSupport
180
186
  f
181
187
  end
182
188
 
189
+ def _parse_hex_binary(bin)
190
+ [bin].pack("H*")
191
+ end
192
+
183
193
  def current_thread_backend
184
194
  IsolatedExecutionState[:xml_mini_backend]
185
195
  end