I'm new to Ruby and am trying to figure out why the following doesn't work as expected:
2.2.1 :010 > user_date = Date.today
=> Sun, 31 May 2015
2.2.1 :011 > user_date.today?
=> false
I'm using the Rails console and the commands are executed one after the other (with maybe a second between executions). I'm sure there is nuance that I'm not understanding, but shouldn't the second command return true instead of false? If not, why?
Thanks in advance!
Edit #1 - Additional information requested by Arup
2.2.1 :013 > puts user_date.method(:today?).owner
DateAndTime::Calculations
=> nil
Edit #2 - So I had a hunch. I'm on US Eastern time and it was coming up to midnight when I ran into the original issue. I waited for the turn of midnight, and now the following works.
2.2.1 :004 > user_date = Date.today
=> Mon, 01 Jun 2015
2.2.1 :005 > user_date.today?
=> true
Date.today belongs to core Ruby while today? belongs to Rails.
Under the hood, today? calls Date.current(Rails as well) instead of Date.today.
Going a bit further, we find that Date.current takes the current Rails time zone into account if one is configured. That should be the source of your mismatch.
Related
My rspec test fails on local machine between 6:00pm and 6:59pm, but passes at before 5:59pm and after 7:00pm on the local machine,
It seems to pass on CircleCI and Heroku, but I do not know if CircleCI or Heroku fail at specific times.
I set my local computer time to UTC, and it seems #subscription.current_period_end is incorrect by 1 hour.
I suspect it has something to do with how my local machine handles the time vs CircleCi and Heroku. Any ideas how to address this issue?
Code:
def get_proration_date
Time.zone.now.to_i
end
Application.rb
config.active_record.default_timezone = :utc
config.time_zone = "UTC"
Rspec
it "should create upcoming_invoice with existing plan for the next_month if there are no changes to subscription(Mock Version)", live: false do
create_stripe_elements
stripe_gold_plan
#subscription = payment_gateway.create_subscription(plan: silver_plan, user: user,
coupon_id: #coupon.stripe_id, source_id_or_token: default_card_token)
invoice = payment_gateway.upcoming_invoice_for_update_plan(subscription: #subscription, to_plan: silver_plan,
coupon_id: "", proration_date: get_proration_date)
expect(invoice.lines.total_count).to eql(1)
expect(invoice.amount_due).to eql(silver_plan.amount)
expect(invoice.total).to eql(silver_plan.amount)
expect(Time.zone.at(invoice.next_payment_attempt)).to eql(#subscription.current_period_end)
delete_stripe_elements
end
Error
Failure/Error: expect(Time.zone.at(invoice.next_payment_attempt)).to eql(#subscription.current_period_end)
expected: 2020-09-27 00:20:20.000000000 +0000
got: 2020-09-27 01:20:20.000000000 +0000
(compared using eql?)
Diff:
## -1,2 +1,2 ##
-Sun, 27 Sep 2020 00:20:20 UTC +00:00
+Sun, 27 Sep 2020 01:20:20 UTC +00:00
Most likely you have a bug in time calculations, potentially a time zone-related one.
Fire up pry or byebug on your local machine when the time comparison fails in the test and figure out which time is the correct one. Then figure out why the other time is wrong and fix that.
Suggest that you always store everything as UTC. Log timestamps, payment timestamps, audit dates, whatever. Do all your date and time calculations and intervals using the UTC values. That way you never ever have an issue with figuring out what happened, and the order in which it happened, even when your users are in different time zones, or they check out their shopping cart at 01:59:59 on the morning when you switch out of daylight saving time in the autumn. If you have a requirement to display dates and times to your users, this is a presentation issue and you can always convert the UTC values to their local timezone values for display purposes.
It was a stripe issue. They do not finalize an invoice until 1 hour after invoice is created, hence I needed to add an hour. #subscription.current_period_end+1.hour
I am trying to upgrade from rails 4.2.7 to 4.2.8 and found a case where the DateTime method of rails switches the time zone to local on reload from database which was not seen in rails 4.2.7. On rails 4.2.8, the unit test of my app rely on the DateTime method to get beginning of the year,
DateTime.new(2019,1,1,0,0,0)
Tue, 01 Jan 2019 00:00:00 +0000
This resultant value, is passed as part of an object to,
Class:Machinist::ActiveRecord::Blueprint where the reload happens.
def reload(options = nil)
clear_aggregation_cache
clear_association_cache
self.class.connection.clear_query_cache
fresh_object =
if options && options[:lock]
self.class.unscoped { self.class.lock(options[:lock]).find(id) }
else
self.class.unscoped { self.class.find(id) }
end
#attributes = fresh_object.instance_variable_get('#attributes')
#new_record = false
self
end
The instance_variable_get method at,
#attributes = fresh_object.instance_variable_get('#attributes')
switches the time zone on Rails 4.2.8 to local(Mon, 31st Dec 2018 16:00:00 -0800) but not on 4.2.7 where just the time zone is switched and not the time (Tue, 1st Jan 2019 00:00:00 -0800). A similar method, Time.new() has got no problem with this as the time zone is set to local by default,
Time.new(2019,1,1,0,0,0)
2019-01-01 00:00:00 -0800
Trying to pinpoint what changed on rails 4.2.8 that doesn't let the time zones persist to UTC for the DateTime method on the reload.
Any insights on this are helpful.
Thanks
I have a JavaScript component (e.g. a date picker) that heavily depends on -
The current system time
The current system time zone
In Ruby and Capybara it's possible to stub any time with the help of libraries such as Timecop.
Is it also possible to stub these values in the headless browser that Capybara controls?
Thanks!
Edit: Here's an example of how Ruby is stubbed but Capybara's browser still uses the system time
before do
now = Time.zone.parse("Apr 15, 2018 12:00PM")
Timecop.freeze(now)
visit root_path
binding.pry
end
> Time.zone.now
=> Sun, 15 Apr 2018 12:00:00 UTC +00:00
> page.evaluate_script("new Date();")
=> "2018-03-27T04:15:44Z"
As you've discovered, Timecop only affects the time in the tests and application under test. The browser is run as a separate process and completely unaffected by Timecop. Because of that you need to stub/mock the time in the browser as well using one of many JS libraries designed to do that. The one I generally use is sinon - http://sinonjs.org/ - , which I conditionally install in the pages head using something like
- if defined?(Timecop) && Timecop.top_stack_item
= javascript_include_tag "sinon.js" # adjust based on where you're loading sinon from
- unix_millis = (Time.now.to_f * 1000.0).to_i
:javascript
sinon.useFakeTimers(#{unix_millis});
That should work in a haml template (adjust if using erb) and would install and mock the browsers time whenever a page is visited while Timecop is being used to mock the apps time.
I know that the question is a bit old, but we had the same request and found the following solution to work with rails 6:
context 'different timezones between back-end and front-end' do
it 'shows the event timestamp according to front-end timezone' do
# Arrange
previous_timezone = Time.zone
previous_timezone_env = ENV['TZ']
server_timezone = "Europe/Copenhagen"
browser_timezone = "America/Godthab"
Time.zone = server_timezone
ENV['TZ'] = browser_timezone
Capybara.using_session(browser_timezone) do
freeze_time do
# Act
# ... Code here
# Assert
server_clock = Time.zone.now.strftime('%H:%M')
client_clock = Time.zone.now.in_time_zone(browser_timezone).strftime('%H:%M')
expect(page).not_to have_content(server_clock)
expect(page).to have_content(client_clock)
end
end
# (restore)
Time.zone = previous_timezone
ENV['TZ'] = previous_timezone_env
end
end
This helps me for usage in pair with zonebie gem
RSpec.configure do |config|
config.before(:suite) do
ENV['TZ'] = Time.zone.tzinfo.name
# ...
end
end
I have an assertion in a test that looks like this:
assert_equals object.sent_at, Time.now
When I run this test, I keep getting an error that looks like this
--- expected
+++ actual
## -1 +1 ##
-Fri, 04 Mar 2016 18:57:47 UTC +00:00
+Fri, 04 Mar 2016
I've tried a few combinations to make this test pass.
My actual code updates the sent_at value with a Time.now but its not quite in the perfect format. It is close but not enough to pass. How can I make this test pass.
Here are some combinations I've tried in my assertions:
Time.now.utc
Date.today
Time.now
and a lot of to_time , to_datetime etc. How can I make the test pass?
Old but still valid... The output shows that the comparison is against UTC which would be Time.current
At this time you would probably use:
assert_in_delta object.sent_at, Time.current, 1
To tolerate <1 second difference
Using Time#to_i isn't the best solution. If the task you are running takes more than a second the comparison would fail. Even if your task is fast enough, this comparison would fail:
time = Time.now # 2018-04-18 3:00:00.990
# after 20ms
assert_equal Time.now.to_i, time.to_i # Fails
Time.now would be 2018-04-18 3:00:01.010 and to_i would give you 2018-04-18 3:00:01 and time was 2018-04-18 3:00:00.990 and to_i: 2018-04-18 3:00:00. So the assert fails.
So, sometimes the test would pass and others would fail, depending on when (in miliseconds) it starts.
The better solution is to freeze the Time. You could use a gem like Timecop or write your own code, like (using MiniTest):
current_time = Time.now
# You need Mocha gem to use #stubs
Time.stubs(:now).returns(current_time)
You can also use a block, so that after the block the clock is back to normal
# For this you don't need Mocha
Time.stub :now, current_time do # stub goes away once the block is done
assert your_task
end
I think it is easiest to use Time#to_i to compare the time in seconds.
assert_equals object.sent_at.to_i, Time.now.to_i # seconds
You can use Timecop gem: https://github.com/travisjeffery/timecop
def test
Timecop.freeze do # Freeze current time
Time.now # some value
...
Time.now # The same value as previous
end
end
Mongoid 3.1.6
Rails 3.2.21
MongoDB 2.4.9
We're seeing strange performance issues with find() vs where().first:
$ rails c
2.1.5 :001 > Benchmark.ms { User.find('5091e4beccbce30200000006') }
=> 7.95
2.1.5 :002 > Benchmark.ms { User.find('5091e4beccbce30200000006') }
=> 0.27599999999999997
2.1.5 :003 > Benchmark.ms { User.find('5091e4beccbce30200000006') }
=> 0.215
2.1.5 :004 > exit
$ rails c
2.1.5 :001 > Benchmark.ms { User.where(id: '5091e4beccbce30200000006').first }
=> 7.779999999999999
2.1.5 :002 > Benchmark.ms { User.where(id: '5091e4beccbce30200000006').first }
=> 4.84
2.1.5 :003 > Benchmark.ms { User.where(id: '5091e4beccbce30200000006').first }
=> 5.297
2.1.5 :004 > exit
These both appear to be firing off the same queries. Can someone explain why we're seeing such a huge difference in performance?
Configuration:
production:
sessions:
default:
uri: <%= REDACTED %>
options:
consistency: :strong
safe: true
max_retries: 1
retry_interval: 0
options:
identity_map_enabled: true
Here is my assumption why the first one was few orders of magnitude slower (I am writing it from mongo point of view and have zero knowledge about ruby).
The first time you fired the query it was not in the working set and this caused slower performance. The consecutive times it was already there and thus performance is better. If you have small number of documents, I would find this behavior strange (because I would expect that all of them would be in a working set).
The second part with $where surprises me because I would expect all the numbers be bigger than with find() (it is not the case with the first event) because:
The $where provides greater flexibility, but requires that the
database processes the JavaScript expression or function for each
document in the collection.
It appears that find uses the identity map, while where does not. If I set identity_map_enabled to false, then the performance of find vs where is identical.
Moral of the story: use find instead of where when possible.
I've heard that the identity map is removed in Mongoid 4.x. So maybe this issue only affects folks on older versions.