omg-activesupport 8.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (289) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +86 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +40 -0
  5. data/lib/active_support/actionable_error.rb +50 -0
  6. data/lib/active_support/all.rb +5 -0
  7. data/lib/active_support/array_inquirer.rb +50 -0
  8. data/lib/active_support/backtrace_cleaner.rb +163 -0
  9. data/lib/active_support/benchmark.rb +21 -0
  10. data/lib/active_support/benchmarkable.rb +53 -0
  11. data/lib/active_support/broadcast_logger.rb +251 -0
  12. data/lib/active_support/builder.rb +8 -0
  13. data/lib/active_support/cache/coder.rb +153 -0
  14. data/lib/active_support/cache/entry.rb +134 -0
  15. data/lib/active_support/cache/file_store.rb +244 -0
  16. data/lib/active_support/cache/mem_cache_store.rb +290 -0
  17. data/lib/active_support/cache/memory_store.rb +262 -0
  18. data/lib/active_support/cache/null_store.rb +62 -0
  19. data/lib/active_support/cache/redis_cache_store.rb +492 -0
  20. data/lib/active_support/cache/serializer_with_fallback.rb +152 -0
  21. data/lib/active_support/cache/strategy/local_cache.rb +201 -0
  22. data/lib/active_support/cache/strategy/local_cache_middleware.rb +45 -0
  23. data/lib/active_support/cache.rb +1104 -0
  24. data/lib/active_support/callbacks.rb +944 -0
  25. data/lib/active_support/class_attribute.rb +26 -0
  26. data/lib/active_support/code_generator.rb +79 -0
  27. data/lib/active_support/concern.rb +217 -0
  28. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +72 -0
  29. data/lib/active_support/concurrency/null_lock.rb +13 -0
  30. data/lib/active_support/concurrency/share_lock.rb +225 -0
  31. data/lib/active_support/configurable.rb +159 -0
  32. data/lib/active_support/configuration_file.rb +60 -0
  33. data/lib/active_support/core_ext/array/access.rb +100 -0
  34. data/lib/active_support/core_ext/array/conversions.rb +213 -0
  35. data/lib/active_support/core_ext/array/extract.rb +21 -0
  36. data/lib/active_support/core_ext/array/extract_options.rb +31 -0
  37. data/lib/active_support/core_ext/array/grouping.rb +109 -0
  38. data/lib/active_support/core_ext/array/inquiry.rb +19 -0
  39. data/lib/active_support/core_ext/array/wrap.rb +48 -0
  40. data/lib/active_support/core_ext/array.rb +9 -0
  41. data/lib/active_support/core_ext/benchmark.rb +13 -0
  42. data/lib/active_support/core_ext/big_decimal/conversions.rb +14 -0
  43. data/lib/active_support/core_ext/big_decimal.rb +3 -0
  44. data/lib/active_support/core_ext/class/attribute.rb +122 -0
  45. data/lib/active_support/core_ext/class/attribute_accessors.rb +6 -0
  46. data/lib/active_support/core_ext/class/subclasses.rb +24 -0
  47. data/lib/active_support/core_ext/class.rb +4 -0
  48. data/lib/active_support/core_ext/date/acts_like.rb +10 -0
  49. data/lib/active_support/core_ext/date/blank.rb +18 -0
  50. data/lib/active_support/core_ext/date/calculations.rb +161 -0
  51. data/lib/active_support/core_ext/date/conversions.rb +98 -0
  52. data/lib/active_support/core_ext/date/zones.rb +8 -0
  53. data/lib/active_support/core_ext/date.rb +7 -0
  54. data/lib/active_support/core_ext/date_and_time/calculations.rb +374 -0
  55. data/lib/active_support/core_ext/date_and_time/compatibility.rb +58 -0
  56. data/lib/active_support/core_ext/date_and_time/zones.rb +40 -0
  57. data/lib/active_support/core_ext/date_time/acts_like.rb +16 -0
  58. data/lib/active_support/core_ext/date_time/blank.rb +18 -0
  59. data/lib/active_support/core_ext/date_time/calculations.rb +215 -0
  60. data/lib/active_support/core_ext/date_time/compatibility.rb +18 -0
  61. data/lib/active_support/core_ext/date_time/conversions.rb +106 -0
  62. data/lib/active_support/core_ext/date_time.rb +7 -0
  63. data/lib/active_support/core_ext/digest/uuid.rb +76 -0
  64. data/lib/active_support/core_ext/digest.rb +3 -0
  65. data/lib/active_support/core_ext/enumerable.rb +267 -0
  66. data/lib/active_support/core_ext/erb/util.rb +201 -0
  67. data/lib/active_support/core_ext/file/atomic.rb +72 -0
  68. data/lib/active_support/core_ext/file.rb +3 -0
  69. data/lib/active_support/core_ext/hash/conversions.rb +262 -0
  70. data/lib/active_support/core_ext/hash/deep_merge.rb +42 -0
  71. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  72. data/lib/active_support/core_ext/hash/except.rb +12 -0
  73. data/lib/active_support/core_ext/hash/indifferent_access.rb +24 -0
  74. data/lib/active_support/core_ext/hash/keys.rb +143 -0
  75. data/lib/active_support/core_ext/hash/reverse_merge.rb +25 -0
  76. data/lib/active_support/core_ext/hash/slice.rb +27 -0
  77. data/lib/active_support/core_ext/hash.rb +10 -0
  78. data/lib/active_support/core_ext/integer/inflections.rb +31 -0
  79. data/lib/active_support/core_ext/integer/multiple.rb +12 -0
  80. data/lib/active_support/core_ext/integer/time.rb +22 -0
  81. data/lib/active_support/core_ext/integer.rb +5 -0
  82. data/lib/active_support/core_ext/kernel/concern.rb +14 -0
  83. data/lib/active_support/core_ext/kernel/reporting.rb +45 -0
  84. data/lib/active_support/core_ext/kernel/singleton_class.rb +8 -0
  85. data/lib/active_support/core_ext/kernel.rb +5 -0
  86. data/lib/active_support/core_ext/load_error.rb +9 -0
  87. data/lib/active_support/core_ext/module/aliasing.rb +31 -0
  88. data/lib/active_support/core_ext/module/anonymous.rb +30 -0
  89. data/lib/active_support/core_ext/module/attr_internal.rb +49 -0
  90. data/lib/active_support/core_ext/module/attribute_accessors.rb +214 -0
  91. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +175 -0
  92. data/lib/active_support/core_ext/module/concerning.rb +140 -0
  93. data/lib/active_support/core_ext/module/delegation.rb +225 -0
  94. data/lib/active_support/core_ext/module/deprecation.rb +25 -0
  95. data/lib/active_support/core_ext/module/introspection.rb +62 -0
  96. data/lib/active_support/core_ext/module/redefine_method.rb +40 -0
  97. data/lib/active_support/core_ext/module/remove_method.rb +17 -0
  98. data/lib/active_support/core_ext/module.rb +13 -0
  99. data/lib/active_support/core_ext/name_error.rb +59 -0
  100. data/lib/active_support/core_ext/numeric/bytes.rb +75 -0
  101. data/lib/active_support/core_ext/numeric/conversions.rb +145 -0
  102. data/lib/active_support/core_ext/numeric/time.rb +66 -0
  103. data/lib/active_support/core_ext/numeric.rb +5 -0
  104. data/lib/active_support/core_ext/object/acts_like.rb +45 -0
  105. data/lib/active_support/core_ext/object/blank.rb +199 -0
  106. data/lib/active_support/core_ext/object/conversions.rb +6 -0
  107. data/lib/active_support/core_ext/object/deep_dup.rb +71 -0
  108. data/lib/active_support/core_ext/object/duplicable.rb +69 -0
  109. data/lib/active_support/core_ext/object/inclusion.rb +37 -0
  110. data/lib/active_support/core_ext/object/instance_variables.rb +32 -0
  111. data/lib/active_support/core_ext/object/json.rb +260 -0
  112. data/lib/active_support/core_ext/object/to_param.rb +3 -0
  113. data/lib/active_support/core_ext/object/to_query.rb +87 -0
  114. data/lib/active_support/core_ext/object/try.rb +158 -0
  115. data/lib/active_support/core_ext/object/with.rb +46 -0
  116. data/lib/active_support/core_ext/object/with_options.rb +101 -0
  117. data/lib/active_support/core_ext/object.rb +17 -0
  118. data/lib/active_support/core_ext/pathname/blank.rb +20 -0
  119. data/lib/active_support/core_ext/pathname/existence.rb +23 -0
  120. data/lib/active_support/core_ext/pathname.rb +4 -0
  121. data/lib/active_support/core_ext/range/compare_range.rb +57 -0
  122. data/lib/active_support/core_ext/range/conversions.rb +62 -0
  123. data/lib/active_support/core_ext/range/each.rb +24 -0
  124. data/lib/active_support/core_ext/range/overlap.rb +40 -0
  125. data/lib/active_support/core_ext/range.rb +6 -0
  126. data/lib/active_support/core_ext/regexp.rb +14 -0
  127. data/lib/active_support/core_ext/securerandom.rb +41 -0
  128. data/lib/active_support/core_ext/string/access.rb +95 -0
  129. data/lib/active_support/core_ext/string/behavior.rb +8 -0
  130. data/lib/active_support/core_ext/string/conversions.rb +60 -0
  131. data/lib/active_support/core_ext/string/exclude.rb +13 -0
  132. data/lib/active_support/core_ext/string/filters.rb +151 -0
  133. data/lib/active_support/core_ext/string/indent.rb +45 -0
  134. data/lib/active_support/core_ext/string/inflections.rb +300 -0
  135. data/lib/active_support/core_ext/string/inquiry.rb +16 -0
  136. data/lib/active_support/core_ext/string/multibyte.rb +58 -0
  137. data/lib/active_support/core_ext/string/output_safety.rb +228 -0
  138. data/lib/active_support/core_ext/string/starts_ends_with.rb +6 -0
  139. data/lib/active_support/core_ext/string/strip.rb +27 -0
  140. data/lib/active_support/core_ext/string/zones.rb +16 -0
  141. data/lib/active_support/core_ext/string.rb +15 -0
  142. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  143. data/lib/active_support/core_ext/symbol.rb +3 -0
  144. data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
  145. data/lib/active_support/core_ext/time/acts_like.rb +10 -0
  146. data/lib/active_support/core_ext/time/calculations.rb +386 -0
  147. data/lib/active_support/core_ext/time/compatibility.rb +32 -0
  148. data/lib/active_support/core_ext/time/conversions.rb +75 -0
  149. data/lib/active_support/core_ext/time/zones.rb +97 -0
  150. data/lib/active_support/core_ext/time.rb +7 -0
  151. data/lib/active_support/core_ext.rb +5 -0
  152. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  153. data/lib/active_support/current_attributes.rb +233 -0
  154. data/lib/active_support/deep_mergeable.rb +53 -0
  155. data/lib/active_support/delegation.rb +202 -0
  156. data/lib/active_support/dependencies/autoload.rb +72 -0
  157. data/lib/active_support/dependencies/interlock.rb +49 -0
  158. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  159. data/lib/active_support/dependencies.rb +98 -0
  160. data/lib/active_support/deprecation/behaviors.rb +148 -0
  161. data/lib/active_support/deprecation/constant_accessor.rb +74 -0
  162. data/lib/active_support/deprecation/deprecators.rb +104 -0
  163. data/lib/active_support/deprecation/disallowed.rb +54 -0
  164. data/lib/active_support/deprecation/method_wrappers.rb +68 -0
  165. data/lib/active_support/deprecation/proxy_wrappers.rb +189 -0
  166. data/lib/active_support/deprecation/reporting.rb +179 -0
  167. data/lib/active_support/deprecation.rb +81 -0
  168. data/lib/active_support/deprecator.rb +7 -0
  169. data/lib/active_support/descendants_tracker.rb +112 -0
  170. data/lib/active_support/digest.rb +22 -0
  171. data/lib/active_support/duration/iso8601_parser.rb +123 -0
  172. data/lib/active_support/duration/iso8601_serializer.rb +64 -0
  173. data/lib/active_support/duration.rb +520 -0
  174. data/lib/active_support/encrypted_configuration.rb +126 -0
  175. data/lib/active_support/encrypted_file.rb +133 -0
  176. data/lib/active_support/environment_inquirer.rb +40 -0
  177. data/lib/active_support/error_reporter/test_helper.rb +15 -0
  178. data/lib/active_support/error_reporter.rb +265 -0
  179. data/lib/active_support/evented_file_update_checker.rb +182 -0
  180. data/lib/active_support/execution_context/test_helper.rb +13 -0
  181. data/lib/active_support/execution_context.rb +53 -0
  182. data/lib/active_support/execution_wrapper.rb +150 -0
  183. data/lib/active_support/executor/test_helper.rb +7 -0
  184. data/lib/active_support/executor.rb +8 -0
  185. data/lib/active_support/file_update_checker.rb +164 -0
  186. data/lib/active_support/fork_tracker.rb +43 -0
  187. data/lib/active_support/gem_version.rb +17 -0
  188. data/lib/active_support/gzip.rb +40 -0
  189. data/lib/active_support/hash_with_indifferent_access.rb +445 -0
  190. data/lib/active_support/html_safe_translation.rb +56 -0
  191. data/lib/active_support/i18n.rb +17 -0
  192. data/lib/active_support/i18n_railtie.rb +138 -0
  193. data/lib/active_support/inflections.rb +72 -0
  194. data/lib/active_support/inflector/inflections.rb +273 -0
  195. data/lib/active_support/inflector/methods.rb +387 -0
  196. data/lib/active_support/inflector/transliterate.rb +149 -0
  197. data/lib/active_support/inflector.rb +9 -0
  198. data/lib/active_support/isolated_execution_state.rb +75 -0
  199. data/lib/active_support/json/decoding.rb +76 -0
  200. data/lib/active_support/json/encoding.rb +120 -0
  201. data/lib/active_support/json.rb +4 -0
  202. data/lib/active_support/key_generator.rb +66 -0
  203. data/lib/active_support/lazy_load_hooks.rb +107 -0
  204. data/lib/active_support/locale/en.rb +33 -0
  205. data/lib/active_support/locale/en.yml +141 -0
  206. data/lib/active_support/log_subscriber/test_helper.rb +106 -0
  207. data/lib/active_support/log_subscriber.rb +192 -0
  208. data/lib/active_support/logger.rb +55 -0
  209. data/lib/active_support/logger_silence.rb +21 -0
  210. data/lib/active_support/logger_thread_safe_level.rb +47 -0
  211. data/lib/active_support/message_encryptor.rb +374 -0
  212. data/lib/active_support/message_encryptors.rb +141 -0
  213. data/lib/active_support/message_pack/cache_serializer.rb +23 -0
  214. data/lib/active_support/message_pack/extensions.rb +305 -0
  215. data/lib/active_support/message_pack/serializer.rb +63 -0
  216. data/lib/active_support/message_pack.rb +50 -0
  217. data/lib/active_support/message_verifier.rb +368 -0
  218. data/lib/active_support/message_verifiers.rb +135 -0
  219. data/lib/active_support/messages/codec.rb +65 -0
  220. data/lib/active_support/messages/metadata.rb +146 -0
  221. data/lib/active_support/messages/rotation_configuration.rb +23 -0
  222. data/lib/active_support/messages/rotation_coordinator.rb +93 -0
  223. data/lib/active_support/messages/rotator.rb +59 -0
  224. data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
  225. data/lib/active_support/multibyte/chars.rb +178 -0
  226. data/lib/active_support/multibyte/unicode.rb +42 -0
  227. data/lib/active_support/multibyte.rb +23 -0
  228. data/lib/active_support/notifications/fanout.rb +446 -0
  229. data/lib/active_support/notifications/instrumenter.rb +240 -0
  230. data/lib/active_support/notifications.rb +281 -0
  231. data/lib/active_support/number_helper/number_converter.rb +190 -0
  232. data/lib/active_support/number_helper/number_to_currency_converter.rb +46 -0
  233. data/lib/active_support/number_helper/number_to_delimited_converter.rb +30 -0
  234. data/lib/active_support/number_helper/number_to_human_converter.rb +69 -0
  235. data/lib/active_support/number_helper/number_to_human_size_converter.rb +60 -0
  236. data/lib/active_support/number_helper/number_to_percentage_converter.rb +16 -0
  237. data/lib/active_support/number_helper/number_to_phone_converter.rb +60 -0
  238. data/lib/active_support/number_helper/number_to_rounded_converter.rb +59 -0
  239. data/lib/active_support/number_helper/rounding_helper.rb +46 -0
  240. data/lib/active_support/number_helper.rb +479 -0
  241. data/lib/active_support/option_merger.rb +38 -0
  242. data/lib/active_support/ordered_hash.rb +50 -0
  243. data/lib/active_support/ordered_options.rb +147 -0
  244. data/lib/active_support/parameter_filter.rb +157 -0
  245. data/lib/active_support/proxy_object.rb +20 -0
  246. data/lib/active_support/rails.rb +26 -0
  247. data/lib/active_support/railtie.rb +161 -0
  248. data/lib/active_support/reloader.rb +138 -0
  249. data/lib/active_support/rescuable.rb +176 -0
  250. data/lib/active_support/secure_compare_rotator.rb +58 -0
  251. data/lib/active_support/security_utils.rb +38 -0
  252. data/lib/active_support/string_inquirer.rb +35 -0
  253. data/lib/active_support/subscriber.rb +146 -0
  254. data/lib/active_support/syntax_error_proxy.rb +60 -0
  255. data/lib/active_support/tagged_logging.rb +152 -0
  256. data/lib/active_support/test_case.rb +304 -0
  257. data/lib/active_support/testing/assertions.rb +332 -0
  258. data/lib/active_support/testing/autorun.rb +5 -0
  259. data/lib/active_support/testing/constant_lookup.rb +51 -0
  260. data/lib/active_support/testing/constant_stubbing.rb +54 -0
  261. data/lib/active_support/testing/declarative.rb +28 -0
  262. data/lib/active_support/testing/deprecation.rb +82 -0
  263. data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
  264. data/lib/active_support/testing/file_fixtures.rb +38 -0
  265. data/lib/active_support/testing/isolation.rb +121 -0
  266. data/lib/active_support/testing/method_call_assertions.rb +69 -0
  267. data/lib/active_support/testing/parallelization/server.rb +85 -0
  268. data/lib/active_support/testing/parallelization/worker.rb +103 -0
  269. data/lib/active_support/testing/parallelization.rb +55 -0
  270. data/lib/active_support/testing/parallelize_executor.rb +81 -0
  271. data/lib/active_support/testing/setup_and_teardown.rb +57 -0
  272. data/lib/active_support/testing/stream.rb +41 -0
  273. data/lib/active_support/testing/strict_warnings.rb +43 -0
  274. data/lib/active_support/testing/tagged_logging.rb +27 -0
  275. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  276. data/lib/active_support/testing/time_helpers.rb +269 -0
  277. data/lib/active_support/time.rb +20 -0
  278. data/lib/active_support/time_with_zone.rb +609 -0
  279. data/lib/active_support/values/time_zone.rb +614 -0
  280. data/lib/active_support/version.rb +10 -0
  281. data/lib/active_support/xml_mini/jdom.rb +175 -0
  282. data/lib/active_support/xml_mini/libxml.rb +80 -0
  283. data/lib/active_support/xml_mini/libxmlsax.rb +83 -0
  284. data/lib/active_support/xml_mini/nokogiri.rb +83 -0
  285. data/lib/active_support/xml_mini/nokogirisax.rb +86 -0
  286. data/lib/active_support/xml_mini/rexml.rb +137 -0
  287. data/lib/active_support/xml_mini.rb +211 -0
  288. data/lib/active_support.rb +144 -0
  289. metadata +526 -0
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "strscan"
4
+
5
+ module ActiveSupport
6
+ class Duration
7
+ # Parses a string formatted according to ISO 8601 Duration into the hash.
8
+ #
9
+ # See {ISO 8601}[https://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
10
+ #
11
+ # This parser allows negative parts to be present in pattern.
12
+ class ISO8601Parser # :nodoc:
13
+ class ParsingError < ::ArgumentError; end
14
+
15
+ PERIOD_OR_COMMA = /\.|,/
16
+ PERIOD = "."
17
+ COMMA = ","
18
+
19
+ SIGN_MARKER = /\A-|\+|/
20
+ DATE_MARKER = /P/
21
+ TIME_MARKER = /T/
22
+ DATE_COMPONENT = /(-?\d+(?:[.,]\d+)?)(Y|M|D|W)/
23
+ TIME_COMPONENT = /(-?\d+(?:[.,]\d+)?)(H|M|S)/
24
+
25
+ DATE_TO_PART = { "Y" => :years, "M" => :months, "W" => :weeks, "D" => :days }
26
+ TIME_TO_PART = { "H" => :hours, "M" => :minutes, "S" => :seconds }
27
+
28
+ DATE_COMPONENTS = [:years, :months, :days]
29
+ TIME_COMPONENTS = [:hours, :minutes, :seconds]
30
+
31
+ attr_reader :parts, :scanner
32
+ attr_accessor :mode, :sign
33
+
34
+ def initialize(string)
35
+ @scanner = StringScanner.new(string)
36
+ @parts = {}
37
+ @mode = :start
38
+ @sign = 1
39
+ end
40
+
41
+ def parse!
42
+ while !finished?
43
+ case mode
44
+ when :start
45
+ if scan(SIGN_MARKER)
46
+ self.sign = (scanner.matched == "-") ? -1 : 1
47
+ self.mode = :sign
48
+ else
49
+ raise_parsing_error
50
+ end
51
+
52
+ when :sign
53
+ if scan(DATE_MARKER)
54
+ self.mode = :date
55
+ else
56
+ raise_parsing_error
57
+ end
58
+
59
+ when :date
60
+ if scan(TIME_MARKER)
61
+ self.mode = :time
62
+ elsif scan(DATE_COMPONENT)
63
+ parts[DATE_TO_PART[scanner[2]]] = number * sign
64
+ else
65
+ raise_parsing_error
66
+ end
67
+
68
+ when :time
69
+ if scan(TIME_COMPONENT)
70
+ parts[TIME_TO_PART[scanner[2]]] = number * sign
71
+ else
72
+ raise_parsing_error
73
+ end
74
+
75
+ end
76
+ end
77
+
78
+ validate!
79
+ parts
80
+ end
81
+
82
+ private
83
+ def finished?
84
+ scanner.eos?
85
+ end
86
+
87
+ # Parses number which can be a float with either comma or period.
88
+ def number
89
+ PERIOD_OR_COMMA.match?(scanner[1]) ? scanner[1].tr(COMMA, PERIOD).to_f : scanner[1].to_i
90
+ end
91
+
92
+ def scan(pattern)
93
+ scanner.scan(pattern)
94
+ end
95
+
96
+ def raise_parsing_error(reason = nil)
97
+ raise ParsingError, "Invalid ISO 8601 duration: #{scanner.string.inspect} #{reason}".strip
98
+ end
99
+
100
+ # Checks for various semantic errors as stated in ISO 8601 standard.
101
+ def validate!
102
+ raise_parsing_error("is empty duration") if parts.empty?
103
+
104
+ # Mixing any of Y, M, D with W is invalid.
105
+ if parts.key?(:weeks) && parts.keys.intersect?(DATE_COMPONENTS)
106
+ raise_parsing_error("mixing weeks with other date parts not allowed")
107
+ end
108
+
109
+ # Specifying an empty T part is invalid.
110
+ if mode == :time && !parts.keys.intersect?(TIME_COMPONENTS)
111
+ raise_parsing_error("time part marker is present but time part is empty")
112
+ end
113
+
114
+ fractions = parts.values.reject(&:zero?).select { |a| (a % 1) != 0 }
115
+ unless fractions.empty? || (fractions.size == 1 && fractions.last == @parts.values.reject(&:zero?).last)
116
+ raise_parsing_error "(only last part can be fractional)"
117
+ end
118
+
119
+ true
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ class Duration
5
+ # Serializes duration to string according to ISO 8601 Duration format.
6
+ class ISO8601Serializer # :nodoc:
7
+ DATE_COMPONENTS = %i(years months days)
8
+
9
+ def initialize(duration, precision: nil)
10
+ @duration = duration
11
+ @precision = precision
12
+ end
13
+
14
+ # Builds and returns output string.
15
+ def serialize
16
+ parts = normalize
17
+ return "PT0S" if parts.empty?
18
+
19
+ output = +"P"
20
+ output << "#{parts[:years]}Y" if parts.key?(:years)
21
+ output << "#{parts[:months]}M" if parts.key?(:months)
22
+ output << "#{parts[:days]}D" if parts.key?(:days)
23
+ output << "#{parts[:weeks]}W" if parts.key?(:weeks)
24
+ time = +""
25
+ time << "#{parts[:hours]}H" if parts.key?(:hours)
26
+ time << "#{parts[:minutes]}M" if parts.key?(:minutes)
27
+ if parts.key?(:seconds)
28
+ time << "#{format_seconds(parts[:seconds])}S"
29
+ end
30
+ output << "T#{time}" unless time.empty?
31
+ output
32
+ end
33
+
34
+ private
35
+ # Return pair of duration's parts and whole duration sign.
36
+ # Parts are summarized (as they can become repetitive due to addition, etc).
37
+ # Zero parts are removed as not significant.
38
+ def normalize
39
+ parts = @duration.parts.each_with_object(Hash.new(0)) do |(k, v), p|
40
+ p[k] += v unless v.zero?
41
+ end
42
+
43
+ # Convert weeks to days and remove weeks if mixed with date parts
44
+ if week_mixed_with_date?(parts)
45
+ parts[:days] += parts.delete(:weeks) * SECONDS_PER_WEEK / SECONDS_PER_DAY
46
+ end
47
+
48
+ parts
49
+ end
50
+
51
+ def week_mixed_with_date?(parts)
52
+ parts.key?(:weeks) && parts.keys.intersect?(DATE_COMPONENTS)
53
+ end
54
+
55
+ def format_seconds(seconds)
56
+ if @precision
57
+ sprintf("%0.0#{@precision}f", seconds)
58
+ else
59
+ seconds.to_s
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,520 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/array/conversions"
4
+ require "active_support/core_ext/module/delegation"
5
+ require "active_support/core_ext/object/acts_like"
6
+
7
+ module ActiveSupport
8
+ # = Active Support \Duration
9
+ #
10
+ # Provides accurate date and time measurements using Date#advance and
11
+ # Time#advance, respectively. It mainly supports the methods on Numeric.
12
+ #
13
+ # 1.month.ago # equivalent to Time.now.advance(months: -1)
14
+ class Duration
15
+ class Scalar < Numeric # :nodoc:
16
+ attr_reader :value
17
+ delegate :to_i, :to_f, :to_s, to: :@value
18
+
19
+ def initialize(value)
20
+ @value = value
21
+ end
22
+
23
+ def coerce(other)
24
+ [Scalar.new(other), self]
25
+ end
26
+
27
+ def -@
28
+ Scalar.new(-value)
29
+ end
30
+
31
+ def <=>(other)
32
+ if Scalar === other || Duration === other
33
+ value <=> other.value
34
+ elsif Numeric === other
35
+ value <=> other
36
+ else
37
+ nil
38
+ end
39
+ end
40
+
41
+ def +(other)
42
+ if Duration === other
43
+ seconds = value + other._parts.fetch(:seconds, 0)
44
+ new_parts = other._parts.merge(seconds: seconds)
45
+ new_value = value + other.value
46
+
47
+ Duration.new(new_value, new_parts, other.variable?)
48
+ else
49
+ calculate(:+, other)
50
+ end
51
+ end
52
+
53
+ def -(other)
54
+ if Duration === other
55
+ seconds = value - other._parts.fetch(:seconds, 0)
56
+ new_parts = other._parts.transform_values(&:-@)
57
+ new_parts = new_parts.merge(seconds: seconds)
58
+ new_value = value - other.value
59
+
60
+ Duration.new(new_value, new_parts, other.variable?)
61
+ else
62
+ calculate(:-, other)
63
+ end
64
+ end
65
+
66
+ def *(other)
67
+ if Duration === other
68
+ new_parts = other._parts.transform_values { |other_value| value * other_value }
69
+ new_value = value * other.value
70
+
71
+ Duration.new(new_value, new_parts, other.variable?)
72
+ else
73
+ calculate(:*, other)
74
+ end
75
+ end
76
+
77
+ def /(other)
78
+ if Duration === other
79
+ value / other.value
80
+ else
81
+ calculate(:/, other)
82
+ end
83
+ end
84
+
85
+ def %(other)
86
+ if Duration === other
87
+ Duration.build(value % other.value)
88
+ else
89
+ calculate(:%, other)
90
+ end
91
+ end
92
+
93
+ def variable? # :nodoc:
94
+ false
95
+ end
96
+
97
+ private
98
+ def calculate(op, other)
99
+ if Scalar === other
100
+ Scalar.new(value.public_send(op, other.value))
101
+ elsif Numeric === other
102
+ Scalar.new(value.public_send(op, other))
103
+ else
104
+ raise_type_error(other)
105
+ end
106
+ end
107
+
108
+ def raise_type_error(other)
109
+ raise TypeError, "no implicit conversion of #{other.class} into #{self.class}"
110
+ end
111
+ end
112
+
113
+ SECONDS_PER_MINUTE = 60
114
+ SECONDS_PER_HOUR = 3600
115
+ SECONDS_PER_DAY = 86400
116
+ SECONDS_PER_WEEK = 604800
117
+ SECONDS_PER_MONTH = 2629746 # 1/12 of a gregorian year
118
+ SECONDS_PER_YEAR = 31556952 # length of a gregorian year (365.2425 days)
119
+
120
+ PARTS_IN_SECONDS = {
121
+ seconds: 1,
122
+ minutes: SECONDS_PER_MINUTE,
123
+ hours: SECONDS_PER_HOUR,
124
+ days: SECONDS_PER_DAY,
125
+ weeks: SECONDS_PER_WEEK,
126
+ months: SECONDS_PER_MONTH,
127
+ years: SECONDS_PER_YEAR
128
+ }.freeze
129
+
130
+ PARTS = [:years, :months, :weeks, :days, :hours, :minutes, :seconds].freeze
131
+ VARIABLE_PARTS = [:years, :months, :weeks, :days].freeze
132
+
133
+ attr_reader :value
134
+
135
+ autoload :ISO8601Parser, "active_support/duration/iso8601_parser"
136
+ autoload :ISO8601Serializer, "active_support/duration/iso8601_serializer"
137
+
138
+ class << self
139
+ # Creates a new Duration from string formatted according to ISO 8601 Duration.
140
+ #
141
+ # See {ISO 8601}[https://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
142
+ # This method allows negative parts to be present in pattern.
143
+ # If invalid string is provided, it will raise +ActiveSupport::Duration::ISO8601Parser::ParsingError+.
144
+ def parse(iso8601duration)
145
+ parts = ISO8601Parser.new(iso8601duration).parse!
146
+ new(calculate_total_seconds(parts), parts)
147
+ end
148
+
149
+ def ===(other) # :nodoc:
150
+ other.is_a?(Duration)
151
+ rescue ::NoMethodError
152
+ false
153
+ end
154
+
155
+ def seconds(value) # :nodoc:
156
+ new(value, { seconds: value }, false)
157
+ end
158
+
159
+ def minutes(value) # :nodoc:
160
+ new(value * SECONDS_PER_MINUTE, { minutes: value }, false)
161
+ end
162
+
163
+ def hours(value) # :nodoc:
164
+ new(value * SECONDS_PER_HOUR, { hours: value }, false)
165
+ end
166
+
167
+ def days(value) # :nodoc:
168
+ new(value * SECONDS_PER_DAY, { days: value }, true)
169
+ end
170
+
171
+ def weeks(value) # :nodoc:
172
+ new(value * SECONDS_PER_WEEK, { weeks: value }, true)
173
+ end
174
+
175
+ def months(value) # :nodoc:
176
+ new(value * SECONDS_PER_MONTH, { months: value }, true)
177
+ end
178
+
179
+ def years(value) # :nodoc:
180
+ new(value * SECONDS_PER_YEAR, { years: value }, true)
181
+ end
182
+
183
+ # Creates a new Duration from a seconds value that is converted
184
+ # to the individual parts:
185
+ #
186
+ # ActiveSupport::Duration.build(31556952).parts # => {:years=>1}
187
+ # ActiveSupport::Duration.build(2716146).parts # => {:months=>1, :days=>1}
188
+ #
189
+ def build(value)
190
+ unless value.is_a?(::Numeric)
191
+ raise TypeError, "can't build an #{self.name} from a #{value.class.name}"
192
+ end
193
+
194
+ parts = {}
195
+ remainder_sign = value <=> 0
196
+ remainder = value.round(9).abs
197
+ variable = false
198
+
199
+ PARTS.each do |part|
200
+ unless part == :seconds
201
+ part_in_seconds = PARTS_IN_SECONDS[part]
202
+ parts[part] = remainder.div(part_in_seconds) * remainder_sign
203
+ remainder %= part_in_seconds
204
+
205
+ unless parts[part].zero?
206
+ variable ||= VARIABLE_PARTS.include?(part)
207
+ end
208
+ end
209
+ end unless value == 0
210
+
211
+ parts[:seconds] = remainder * remainder_sign
212
+
213
+ new(value, parts, variable)
214
+ end
215
+
216
+ private
217
+ def calculate_total_seconds(parts)
218
+ parts.inject(0) do |total, (part, value)|
219
+ total + value * PARTS_IN_SECONDS[part]
220
+ end
221
+ end
222
+ end
223
+
224
+ Delegation.generate(self, [:to_f, :positive?, :negative?, :zero?, :abs], to: :@value, as: Integer, nilable: false)
225
+
226
+ def initialize(value, parts, variable = nil) # :nodoc:
227
+ @value, @parts = value, parts
228
+ @parts.reject! { |k, v| v.zero? } unless value == 0
229
+ @parts.freeze
230
+ @variable = variable
231
+
232
+ if @variable.nil?
233
+ @variable = @parts.any? { |part, _| VARIABLE_PARTS.include?(part) }
234
+ end
235
+ end
236
+
237
+ # Returns a copy of the parts hash that defines the duration.
238
+ #
239
+ # 5.minutes.parts # => {:minutes=>5}
240
+ # 3.years.parts # => {:years=>3}
241
+ def parts
242
+ @parts.dup
243
+ end
244
+
245
+ def coerce(other) # :nodoc:
246
+ case other
247
+ when Scalar
248
+ [other, self]
249
+ when Duration
250
+ [Scalar.new(other.value), self]
251
+ else
252
+ [Scalar.new(other), self]
253
+ end
254
+ end
255
+
256
+ # Compares one Duration with another or a Numeric to this Duration.
257
+ # Numeric values are treated as seconds.
258
+ def <=>(other)
259
+ if Duration === other
260
+ value <=> other.value
261
+ elsif Numeric === other
262
+ value <=> other
263
+ end
264
+ end
265
+
266
+ # Adds another Duration or a Numeric to this Duration. Numeric values
267
+ # are treated as seconds.
268
+ def +(other)
269
+ if Duration === other
270
+ parts = @parts.merge(other._parts) do |_key, value, other_value|
271
+ value + other_value
272
+ end
273
+ Duration.new(value + other.value, parts, @variable || other.variable?)
274
+ else
275
+ seconds = @parts.fetch(:seconds, 0) + other
276
+ Duration.new(value + other, @parts.merge(seconds: seconds), @variable)
277
+ end
278
+ end
279
+
280
+ # Subtracts another Duration or a Numeric from this Duration. Numeric
281
+ # values are treated as seconds.
282
+ def -(other)
283
+ self + (-other)
284
+ end
285
+
286
+ # Multiplies this Duration by a Numeric and returns a new Duration.
287
+ def *(other)
288
+ if Scalar === other || Duration === other
289
+ Duration.new(value * other.value, @parts.transform_values { |number| number * other.value }, @variable || other.variable?)
290
+ elsif Numeric === other
291
+ Duration.new(value * other, @parts.transform_values { |number| number * other }, @variable)
292
+ else
293
+ raise_type_error(other)
294
+ end
295
+ end
296
+
297
+ # Divides this Duration by a Numeric and returns a new Duration.
298
+ def /(other)
299
+ if Scalar === other
300
+ Duration.new(value / other.value, @parts.transform_values { |number| number / other.value }, @variable)
301
+ elsif Duration === other
302
+ value / other.value
303
+ elsif Numeric === other
304
+ Duration.new(value / other, @parts.transform_values { |number| number / other }, @variable)
305
+ else
306
+ raise_type_error(other)
307
+ end
308
+ end
309
+
310
+ # Returns the modulo of this Duration by another Duration or Numeric.
311
+ # Numeric values are treated as seconds.
312
+ def %(other)
313
+ if Duration === other || Scalar === other
314
+ Duration.build(value % other.value)
315
+ elsif Numeric === other
316
+ Duration.build(value % other)
317
+ else
318
+ raise_type_error(other)
319
+ end
320
+ end
321
+
322
+ def -@ # :nodoc:
323
+ Duration.new(-value, @parts.transform_values(&:-@), @variable)
324
+ end
325
+
326
+ def +@ # :nodoc:
327
+ self
328
+ end
329
+
330
+ def is_a?(klass) # :nodoc:
331
+ Duration == klass || value.is_a?(klass)
332
+ end
333
+ alias :kind_of? :is_a?
334
+
335
+ def instance_of?(klass) # :nodoc:
336
+ Duration == klass || value.instance_of?(klass)
337
+ end
338
+
339
+ # Returns +true+ if +other+ is also a Duration instance with the
340
+ # same +value+, or if <tt>other == value</tt>.
341
+ def ==(other)
342
+ if Duration === other
343
+ other.value == value
344
+ else
345
+ other == value
346
+ end
347
+ end
348
+
349
+ # Returns the amount of seconds a duration covers as a string.
350
+ # For more information check to_i method.
351
+ #
352
+ # 1.day.to_s # => "86400"
353
+ def to_s
354
+ @value.to_s
355
+ end
356
+
357
+ # Returns the number of seconds that this Duration represents.
358
+ #
359
+ # 1.minute.to_i # => 60
360
+ # 1.hour.to_i # => 3600
361
+ # 1.day.to_i # => 86400
362
+ #
363
+ # Note that this conversion makes some assumptions about the
364
+ # duration of some periods, e.g. months are always 1/12 of year
365
+ # and years are 365.2425 days:
366
+ #
367
+ # # equivalent to (1.year / 12).to_i
368
+ # 1.month.to_i # => 2629746
369
+ #
370
+ # # equivalent to 365.2425.days.to_i
371
+ # 1.year.to_i # => 31556952
372
+ #
373
+ # In such cases, Ruby's core
374
+ # Date[https://docs.ruby-lang.org/en/master/Date.html] and
375
+ # Time[https://docs.ruby-lang.org/en/master/Time.html] should be used for precision
376
+ # date and time arithmetic.
377
+ def to_i
378
+ @value.to_i
379
+ end
380
+ alias :in_seconds :to_i
381
+
382
+ # Returns the amount of minutes a duration covers as a float
383
+ #
384
+ # 1.day.in_minutes # => 1440.0
385
+ def in_minutes
386
+ in_seconds / SECONDS_PER_MINUTE.to_f
387
+ end
388
+
389
+ # Returns the amount of hours a duration covers as a float
390
+ #
391
+ # 1.day.in_hours # => 24.0
392
+ def in_hours
393
+ in_seconds / SECONDS_PER_HOUR.to_f
394
+ end
395
+
396
+ # Returns the amount of days a duration covers as a float
397
+ #
398
+ # 12.hours.in_days # => 0.5
399
+ def in_days
400
+ in_seconds / SECONDS_PER_DAY.to_f
401
+ end
402
+
403
+ # Returns the amount of weeks a duration covers as a float
404
+ #
405
+ # 2.months.in_weeks # => 8.696
406
+ def in_weeks
407
+ in_seconds / SECONDS_PER_WEEK.to_f
408
+ end
409
+
410
+ # Returns the amount of months a duration covers as a float
411
+ #
412
+ # 9.weeks.in_months # => 2.07
413
+ def in_months
414
+ in_seconds / SECONDS_PER_MONTH.to_f
415
+ end
416
+
417
+ # Returns the amount of years a duration covers as a float
418
+ #
419
+ # 30.days.in_years # => 0.082
420
+ def in_years
421
+ in_seconds / SECONDS_PER_YEAR.to_f
422
+ end
423
+
424
+ # Returns +true+ if +other+ is also a Duration instance, which has the
425
+ # same parts as this one.
426
+ def eql?(other)
427
+ Duration === other && other.value.eql?(value)
428
+ end
429
+
430
+ def hash
431
+ @value.hash
432
+ end
433
+
434
+ # Calculates a new Time or Date that is as far in the future
435
+ # as this Duration represents.
436
+ def since(time = ::Time.current)
437
+ sum(1, time)
438
+ end
439
+ alias :from_now :since
440
+ alias :after :since
441
+
442
+ # Calculates a new Time or Date that is as far in the past
443
+ # as this Duration represents.
444
+ def ago(time = ::Time.current)
445
+ sum(-1, time)
446
+ end
447
+ alias :until :ago
448
+ alias :before :ago
449
+
450
+ def inspect # :nodoc:
451
+ return "#{value} seconds" if @parts.empty?
452
+
453
+ @parts.
454
+ sort_by { |unit, _ | PARTS.index(unit) }.
455
+ map { |unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}" }.
456
+ to_sentence(locale: false)
457
+ end
458
+
459
+ def as_json(options = nil) # :nodoc:
460
+ to_i
461
+ end
462
+
463
+ def init_with(coder) # :nodoc:
464
+ initialize(coder["value"], coder["parts"])
465
+ end
466
+
467
+ def encode_with(coder) # :nodoc:
468
+ coder.map = { "value" => @value, "parts" => @parts }
469
+ end
470
+
471
+ # Build ISO 8601 Duration string for this duration.
472
+ # The +precision+ parameter can be used to limit seconds' precision of duration.
473
+ def iso8601(precision: nil)
474
+ ISO8601Serializer.new(self, precision: precision).serialize
475
+ end
476
+
477
+ def variable? # :nodoc:
478
+ @variable
479
+ end
480
+
481
+ def _parts # :nodoc:
482
+ @parts
483
+ end
484
+
485
+ private
486
+ def sum(sign, time = ::Time.current)
487
+ unless time.acts_like?(:time) || time.acts_like?(:date)
488
+ raise ::ArgumentError, "expected a time or date, got #{time.inspect}"
489
+ end
490
+
491
+ if @parts.empty?
492
+ time.since(sign * value)
493
+ else
494
+ @parts.inject(time) do |t, (type, number)|
495
+ if type == :seconds
496
+ t.since(sign * number)
497
+ elsif type == :minutes
498
+ t.since(sign * number * 60)
499
+ elsif type == :hours
500
+ t.since(sign * number * 3600)
501
+ else
502
+ t.advance(type => sign * number)
503
+ end
504
+ end
505
+ end
506
+ end
507
+
508
+ def respond_to_missing?(method, _)
509
+ value.respond_to?(method)
510
+ end
511
+
512
+ def method_missing(...)
513
+ value.public_send(...)
514
+ end
515
+
516
+ def raise_type_error(other)
517
+ raise TypeError, "no implicit conversion of #{other.class} into #{self.class}"
518
+ end
519
+ end
520
+ end