Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The method from_now of Class: BusinessTime::BusinessDays returns different results under the same context #221

Open
ArminBeda opened this issue Dec 30, 2022 · 7 comments

Comments

@ArminBeda
Copy link

ArminBeda commented Dec 30, 2022

The Method from_now of Class: BusinessTime::BusinessDays is used to calculate a specific date in a Ruby on Rails application. Sometimes (only sometimes!) wrong date was delivered back at a specific point. I just could not find out what the problem is, until I placed the following lines into my code, just for the sake of experiementing:

puts "500.business_days&.from_now1: #{500.business_days&.from_now}"
puts "500.business_days&.from_now2: #{500.business_days&.from_now}"
puts "500.business_days&.from_now3: #{500.business_days&.from_now}"
puts "500.business_days&.from_now4: #{500.business_days&.from_now}"
puts "500.business_days&.from_now5: #{500.business_days&.from_now}"
puts "500.business_days&.from_now6: #{500.business_days&.from_now}"
puts "500.business_days&.from_now7: #{500.business_days&.from_now}"

This is the screenshot, what was returned on the console:

image

Here is a content of the console as text:

500.business_days&.from_now1: 2024-12-30 09:00:00 +0100
500.business_days&.from_now2: 2024-12-30 09:00:00 +0100
500.business_days&.from_now3: 2024-11-29 09:00:00 +0100
500.business_days&.from_now4: 2024-12-19 09:00:00 +0100
500.business_days&.from_now5: 2024-12-30 09:00:00 +0100
500.business_days&.from_now6: 2024-12-30 09:00:00 +0100
500.business_days&.from_now7: 2024-12-30 09:00:00 +0100

As you see, sometimes the Date 2024-11-29 is returned but sometimes 2024-12-30.

This might be a bug, or something I do not understand.

@bokmann
Copy link
Owner

bokmann commented Dec 30, 2022

What version of Ruby are you using?

@ArminBeda
Copy link
Author

What version of Ruby are you using?

3.0.5

@rmm5t
Copy link
Collaborator

rmm5t commented Dec 31, 2022

I have not been able to reproduce this yet:

>> RUBY_VERSION
=> "3.0.5"
?> def run
?>   50.times { |i| puts "500.business_days.from_now#{i}: #{500.business_days.from_now}" }
>> end
=> :run
>> run
500.business_days.from_now0: 2024-12-02 09:00:00 -0700
# (... all the same)
500.business_days.from_now49: 2024-12-02 09:00:00 -0700
=> 50

What kind of holiday and/or other configuration are you using?

@ArminBeda
Copy link
Author

ArminBeda commented Jan 2, 2023

@rmm5t

If I run the line 50.times { |i| puts "500.business_days.from_now#{i}: #{500.business_days.from_now}" } on the console as you did, I also cannot reproduce the effect.

But if I just randomly put the line into a method in the code which is triggered in a cucumber test scenario, then I can reproduce the effect:

image

@ArminBeda
Copy link
Author

@rmm5t is there any update on the issue? the bug is still present

@rmm5t
Copy link
Collaborator

rmm5t commented Mar 1, 2023

No updates.

Again, What kind of holiday and/or other configuration are you using?

There’s not much I can do here without a way to reproduce this. If you can submit a PR with a test that exhibits the behavior that you’re seeing, that would go a long way.

@cgunther
Copy link

cgunther commented Feb 5, 2025

I've been seeing something similar to this, I think, and I've got a working theory of what might be happening, which I wanted to share, though I haven't gotten a reproducible example yet that might lead to a fix.

In short, I'm seeing this issue in my Capybara/Selenium specs for a Rails app, where I generate a business date in the application as a default value for a field (ie. 3.business_days.from_now), then assert the value of that field in my spec, calculating it again (ie. 3.business_days.from_now), yet sometimes the values are off by a day or so, thus the test fails.

Since it's a Selenium spec, it's launching a Chrome browser hitting an instance of Puma that specs spin up, so I believe there's multiple threads at play, at least one for executing the spec itself, then at least another for Puma serving the requests to the browser.

For background, I configure the beginning/end of the workday to essentially be midnight-to-midnight in a Rails initializer, effectively negating the "business hours" aspect, focusing just on "business days":

BusinessTime::Config.beginning_of_workday = "12:00:00 am"
BusinessTime::Config.end_of_workday = "11:59:59 pm"

In debugging this, I noticed that sometimes the beginning of the workday is actually set to 9am, which is curiously the gem's default, not my 12am. I didn't test it yet, but I'm assuming when the start is set to the default 9am, the end is probably also set to the default 5pm. That'd mean when I'm calling business_days.from_now when the current time is outside those working hours, the result could be off by a day when it rolls to the next business day/hour, which is essentially what I'm seeing in my tests.

Peeking at the variables this gem sets on the thread, it seems like sometimes the config is missing, leading the gem to set the config again, but to the gem defaults (9am-5pm). My theory is that something is clearing the variables on the thread, hence my customized config gets lost and replaced with the gem default config.

I can see the config variable being set on the thread when the app boots, yet sporadically that same thread no longer has the config when trying to access a setting, thereby triggering this code to re-apply the defaults:

Thread.main[:business_time_config] ||= default_config

When the config is missing, it seems thread variables set by other gems are also missing, which makes me curious whether something is clearing the thread's variables altogether.

@rmm5t 's attempt at reproducing this may not have surfaced the issue as there's only one thread at play there, whereas @ArminBeda 's notes about reproducing may demonstrate it if that is in the application code that Cucumber indirectly exercises, assuming there might be multiple threads at play here again, like I'm seeing.

I'll keep digging on this, time permitting, to see if I can surface anything else, or distill this into a reproducible example, but the sporadic nature of it certainly makes it a challenge. I'm sharing my notes incase it sparks an idea for anyone else.

I'm on Ruby v3.4.1 now, but having seen this for months, I saw this on at least v3.3 as well. I'm using business_time v0.13.0. My config (set in an Rails initializer) looks like this:

HOLIDAYS = {
  # 2025
  Date.new(2025, 1, 1) => "New Year's Day",
  Date.new(2025, 5, 26) => "Memorial Day",
  Date.new(2025, 7, 4) => "Fourth of July",
  Date.new(2025, 9, 1) => "Labor Day",
  Date.new(2025, 11, 27) => "Thanksgiving",
  Date.new(2025, 11, 28) => "Day after Thanksgiving",
  Date.new(2025, 12, 24) => "Christmas Eve",
  Date.new(2025, 12, 25) => "Christmas Day",
  Date.new(2025, 12, 26) => "Day after Christmas",
  Date.new(2025, 12, 31) => "New Year's Eve",

  # ...
}

BusinessTime::Config.holidays += HOLIDAYS.keys
BusinessTime::Config.beginning_of_workday = "12:00:00 am"
BusinessTime::Config.end_of_workday = "11:59:59 pm"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants