Previously `ActiveSupport::Duration.parse` used `Time.current` and
`Time#advance` to calculate the number of seconds in the duration
from an arbitrary collection of parts. However as `advance` tries to
be consistent across DST boundaries this meant that either the
duration was shorter or longer depending on the time of year.
This was fixed by using an absolute reference point in UTC which
isn't subject to DST transitions. An arbitrary date of Jan 1st, 2000
was chosen for no other reason that it seemed appropriate.
Additionally, duration parsing should now be marginally faster as we
are no longer creating instances of `ActiveSupport::TimeWithZone`
every time we parse a duration string.
Fixes#26941.
Regexp#match? should be considered to be part of the Ruby core library. We are
emulating it for < 2.4, but not having to require the extension is part of the
illusion of the emulation.
On Ruby 2.4, naitive `Hash#transform_values` is implemented.
`Hash#transform_values` uses an instance of Hash (`rb_hash_new`) to
collect returned values of a block.
For ensuring `#transform_values` of HWIDA to return HWIDA, we should
define `#transform_values` on HWIDA.
This reverts commit a01cf703 as explained in the comment to #26826:
Realized that this PR caused the following warning in Travis CI:
```
/home/travis/build/rails/rails/activesupport/lib/active_support/dependencies.rb:293: warning: loading in progress, circular require considered harmful - /home/travis/build/rails/rails/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
```
Indeed, `active_support/core_ext/hash/indifferent_access.rb` **needs** to require `active_support/hash_with_indifferent_access.rb` in order to access the class `ActiveSupport::HashWithIndifferentAccess`.
The other way around, though, is not _strictly_ required, unless someone tries (like I did in the [gist above](https://gist.github.com/claudiob/43cc7fe77ff95951538af2825a71e5ec)) to use `ActiveSupport::HashWithIndifferentAccess` by only requiring `active_support/hash_with_indifferent_access.rb` without first requiring `active_support/core_ext/hash/indifferent_access.rb`.
I think the solution to this is to revert this PR and instead change the documentation to explicitly state that **developers should not require 'active_support/hash_with_indifferent_access'** if all they want is to use `ActiveSupport::HashWithIndifferentAccess` – instead they should require `active_support/core_ext/hash/indifferent_access.rb`.
In https://bugs.ruby-lang.org/issues/12860 I argue that MRI's
execution order here is incorrect. The splatting of the 'c' args
should happen before the shift, but it happens after. On JRuby, it
behaves the way you would expect, leading to the 'c' args splat
still containing the block and producing an error like "cannot
convert proc to symbol" when the send attempts to coerce
it.
This patch makes the unpacking order explicit with a multi-assign,
which behaves properly on all implementations I tested.
This is a follow up to #25681, specifically this comment:
https://github.com/rails/rails/pull/25681#issuecomment-238294002
The way the thread local variable is stored is an implementation detail
and subject to change. It makes no sense to only generate a reader or
writer as you'd have to know where to read from or where it writes to.
`copy_time_to` is a helper function for date and time calculations.
It's being used by `prev_week`, `next_week` and `prev_weekday` to keep
the time fraction when jumping around between days.
Previously the nanoseconds part was lost during the operation. This
lead to problems in practice if you were using the `end_of_day`
calculation. Resulting in the time fraction of `end_of_day` not being
the same as next week's `end_of_day`.
With this fix `copy_time_to` doesn't forget the `nsec` digits.
The methods Hash#transform_values and Hash#transform_values! have been
implemented in Ruby and they'll be available as part of the standard
library.
Here's the link to the discussion in Ruby's issue tracker:
https://bugs.ruby-lang.org/issues/12512
These methods are implemented in C so they're expected to perform
better.
@amatsuda, during his RailsConf talk this past year, presented a
benchmark that showed `Time.zone.now` (an Active Support joint)
performing 24.97x slower than Ruby's `Time.now`. Rails master appears to
be a _bit_ faster than that, currently clocking in at 18.25x slower than
`Time.now`. Here's the exact benchmark data for that:
```
Warming up --------------------------------------
Time.now 127.923k i/100ms
Time.zone.now 10.275k i/100ms
Calculating -------------------------------------
Time.now 1.946M (± 5.9%) i/s - 9.722M in 5.010236s
Time.zone.now 106.625k (± 4.3%) i/s - 534.300k in 5.020343s
Comparison:
Time.now: 1946220.1 i/s
Time.zone.now: 106625.5 i/s - 18.25x slower
```
What if I told you we could make `Time.zone.now` _even_ faster? Well,
that's exactly what this patch accomplishes. When creating `ActiveSupport::TimeWithZone`
objects, we try to convert the provided time to be in a UTC format. All
this patch does is, in the method where we convert a provided time to
UTC, check if the provided time is already UTC, and is a `Time` object
and then return early if that is the case, This sidesteps having to continue on,
and create a new `Time` object from scratch. Here's the exact benchmark
data for my patch:
```
Warming up --------------------------------------
Time.now 124.136k i/100ms
Time.zone.now 26.260k i/100ms
Calculating -------------------------------------
Time.now 1.894M (± 6.4%) i/s - 9.434M in 5.000153s
Time.zone.now 301.654k (± 4.3%) i/s - 1.523M in 5.058328s
Comparison:
Time.now: 1893958.0 i/s
Time.zone.now: 301653.7 i/s - 6.28x slower
```
With this patch, we go from `Time.zone.now` being 18.25x slower than
`Time.now` to only being 6.28x slower than `Time.now`. I'd obviously love some
verification on this patch, since these numbers sound pretty interesting... :)
This is the benchmark-ips report I have been using while working on this:
```ruby
require 'benchmark/ips'
Time.zone = 'Eastern Time (US & Canada)'
Benchmark.ips do |x|
x.report('Time.now') {
Time.now
}
x.report('Time.zone.now') {
Time.zone.now
}
x.compare!
end
```
cc @amatsuda
cc performance folks @tenderlove and @schneems
![Pretty... pretty... pretty good.](https://media.giphy.com/media/bWeR8tA1QV4cM/giphy.gif)
In #25880 we tried to cache localtime to fix the performance
regression but that proved to be difficult due to the fact that
localtime/getlocal can take a utc_offset argument. We tried
caching based on the argument but since the argument can be nil
sometimes that meant that if the TZ environment variable changed
then the cached value for nil became invalid. By moving the
caching to DateAndTime#compatibility we don't have to worry about
arguments since it doesn't take any.
There is a possible edge condition where preserve_timezone is set
to false and the system timezone changes then it could result in
a cached value being incorrect but the only way to fix this would
be to remove all caching and live with the performance issue.
Turns out trying to cache on localtime with arguments is too hard
so we'll do it on DateAndTime::Compatibility#to_time instead.
This reverts commit 3132fa6b7d9585e04eb44b25b55d298391b040b5, reversing
changes made to 6949f8e5e7dc901d4e04ebab6c975afb33ca44c9.
Turns out trying to cache on localtime with arguments is too hard
so we'll do it on DateAndTime::Compatibility#to_time instead.
This reverts commit 9ce2d1b1a43fc4ef3db59849b7412d30583a4074, reversing
changes made to 53ede1aff2025d4391d0e05ba471fdaf3110a99c.
Previously memoization in `localtime` wasn't taking the `utc_offset`
parameter into account when returning a cached value. It now caches the
computed value depending on the `utc_offset` parameter, e.g:
Time.zone = "US/Eastern"
t = Time.zone.local(2016,5,2,11)
# => Mon, 02 May 2016 11:00:00 EDT -04:00
t.localtime(-7200)
# => 2016-05-02 13:00:00 -0200
t.localtime(-3600)
# => 2016-05-02 14:00:00 -0100
Callbacks are everywhere, so it's better if we can avoid making a mess
of the backtrace just because we've passed through a callback hook.
I'm making no effort to the before/after invocations: those only affect
backtraces while they're running. The calls that matter are the ones
that remain on the call stack after run_callbacks yields: around
callbacks, and internal book-keeping around the before/afters.
The Rails test runner supports three ways to run tests: directly, via rake, or ruby.
When Running with Ruby ala `ruby -Itest test/models/post_test.rb` our test file would
be evaluated first, requiring `test_helper` and then `active_support/testing/autorun`
that would then require the test file (which it hadn't been before) thus reevaluating
it. This caused exceptions if using Active Support's declarative syntax.
Fix this by shifting around when we set the how we're run to closer mimick the require
order.
If we're running with `bin/rails test` the test command file is run first and we then
set `run_with_rails_extension`, later we hit `active_support/testing/autorun` and do
nothing — because we've been run elsewhere.
If we at this point haven't set `run_with_rails_extension` we've been running with
`ruby` this whole time and thus we set that.
We should always trigger `Minitest.autorun` as it doesn't hurt to call it twice.
Consolidate the two methods into a single one that better brings out the intent of
why they're there.
Previously calls to `in` were being sent to the non-DST aware
method `Time#since` via `method_missing`. It is now aliased to
the DST aware `ActiveSupport::TimeWithZone#+` which handles
transitions across DST boundaries, e.g:
Time.zone = "US/Eastern"
t = Time.zone.local(2016,11,6,1)
# => Sun, 06 Nov 2016 01:00:00 EDT -05:00
t.in(1.hour)
# => Sun, 06 Nov 2016 01:00:00 EST -05:00