Open-source project
bokmann/business_time avatar
bokmann/business_time

business_time counts working hours, and admits the weekend maths is odd

Support for doing time math in business hours and days

1,330 stars218 forksRubyMIT

At a glance

What is it?
business_time adds working-hour and working-day calculations to Ruby, with configurable workdays, work weeks, holidays and a Rails generator. Its most useful documentation is the worked example where a Friday-evening to Saturday-morning gap reports six hours and twenty minutes instead of eight, with the author's own explanation of why.
Who is it for?
Adopt business_time if you compute SLAs, due dates or support hours in Ruby and want the weekday and holiday rules in one place rather than in your own arithmetic. Do not adopt it expecting a hard SLA calculation engine, because the weekend rollover behaviour the README documents shows it rounds to the start of the next working day rather than measuring partial days.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 6, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • bokmann/business_time1,330 ★ · +0
Star history
Repository2026-10-172026-11-06
bokmann/business_time1.3k1.3k

0 Stars in the last 20 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

What it adds to ActiveSupport

ActiveSupport already gives you `5.days.ago` and `8.hours.from_now`, plus helpers for the same arithmetic from a given date or time. The author's stated problem is that those count elapsed time, not working time, and he needed the difference.

So the gem adds parallel units. In a console you get `1.business_hour.from_now`, `4.business_hours.from_now`, `8.business_hours.from_now`, the same three with `.ago`, and `1.business_day` through `8.business_days` in both directions.

The business-hour units change the calculation to respect configured working periods, holidays and the work week.

The methods also work from any Date or Time object rather than only from now, using `after` and `before`:

ruby
my_birthday = Date.parse("August 4th, 1969")
8.business_days.after(my_birthday)

There is also a predicate for whether a date is a working day, both on the current date and on a parsed one:

ruby
Date.today.workday?
Date.parse("2015-12-09").workday?

Installation is one gem command, and the examples assume you `require 'business_time'` in irb.

Configuration is global, and there is a scoped override

The workday is configurable through `BusinessTime::Config`, and the two settings you will change first are the boundaries:

ruby
BusinessTime::Config.beginning_of_workday = "8:30 am"
BusinessTime::Config.end_of_workday = "5:30 pm"

For code that must not permanently change those values, there is a block form:

ruby
BusinessTime::Config.with(beginning_of_workday: "8 am", end_of_workday: "6 pm") do
  1.business_hour.from_now
end

That `with` method is the piece that makes the gem usable in a library rather than only in a Rails initializer, where global mutation is harmless because it happens once at boot.

Holidays are appended to a list, and the example given is July 5 in 2010, a Monday the US takes off because Independence Day falls on the Sunday:

ruby
three_day_weekend = Date.parse("July 5th, 2010")
BusinessTime::Config.holidays << three_day_weekend

The work week itself can be changed, and the example sets it to Sunday through Thursday:

ruby
BusinessTime::Config.work_week = [:sun, :mon, :tue, :wed, :thu]

And hours can differ per day, which is what most real work patterns actually need:

ruby
BusinessTime::Config.work_hours = {
  mon: ["9:00","17:00"],
  fri: ["9:00","17:00"],
  sat: ["10:00","15:00"]
}

So a Saturday is a working day with a five-hour day, and the arithmetic follows that per-day definition.

Measuring between two points, and the weekend surprise

The measurement methods are the other half of the gem, and they are where you should read the documentation most carefully.

`business_time_until` gives the working duration between two Times, `business_days_until` gives the working days between two Dates, `business_dates_until` returns the dates in between, and `during_business_hours?` answers whether a moment falls inside working hours. There is also `Time.first_business_day`, which returns the first working day at or after a time, and `Time.previous_business_day`, which looks backwards.

The caveat the README leads with is the important one. It notes that counterintuitively, durations might not be quite what you expect when involving weekends, and works the example: a ticket reported at 10:40am on Friday and resolved at 10:40am the following day returns six hours and twenty minutes rather than eight.

The author's explanation is that February 4 2012 was a Saturday, so the end time rolls over to Monday February 6 at 9:00am, and the working time between Friday 10:40 and Monday 9:00 is six hours and twenty minutes. They add, from a quick inspection of the code, that it looks like it should be eight.

So the behaviour is that an end time outside working hours is snapped forward to the start of the next working period, rather than being treated as an instant inside a period that was never open. That is defensible as a design, and it is also the kind of thing that produces a surprising SLA number unless you know about it. Reading this section is worth more than any example above it.

For day counts, `business_days_until` from December 24 to December 27 2010 returns 1, and `business_dates_until` from December 20 to December 22 returns the two dates in between.

The Rails generator and the initializer trap

There is a Rails generator:

sh
rails generate business_time:config

It creates `./config/business_time.yml` and `./config/initializers/business_time.rb`, so the start of business day, the end of business day and your holidays are loaded from the YAML file.

The README then says something that is easy to miss and expensive to ignore. You might want to load holidays programmatically from a database table, but you have to pay attention to how the initializer works, making sure it sets things up appropriately so Rails instances on mongrels or passenger have the appropriate data as they come up and down.

That is a warning about mutable global state. The configuration is process-wide, and the holiday list in particular is the kind of thing that goes stale: a date added at boot is correct until the process restarts, and in a multi-process deployment each process may have loaded a different snapshot of the holiday table.

So if your holidays live in a database, the question is when they are loaded, not just that they are loaded. An initializer that reads a table once at boot will be wrong after a holiday is added, and correct only after a deploy.

Timezones and the holidays gem

The gem states it strives to be timezone-agnostic, then spends a paragraph admitting that took longer than expected due to complications in the built-in Time class and complexities in the TimeWithZone class.

The design is coherent. When you configure a start time like 9:00am, that value is agnostic of time zone. When you are dealing with a Time or TimeWithZone object, the timezone is preserved and the beginning and end of the business day are referenced in that time zone.

The consequence, stated plainly, is that if you are in Eastern time but doing everything in UTC times, your business day will appear to start and end at 9:00 and 5:00 UTC. The author warns that if this seems perplexing you are probably in over your head with timezones in other ways too, and that this is simply the first place you encountered it.

That framing is unusually candid, and it is accurate. Business-hours arithmetic across timezones is one of those problems that looks like a library feature until the first user in another region reports an off-by-several-hours SLA.

For holidays, the README points to an article by Chris Wise about using the gem with the holidays gem, which reduces to iterating over the holidays a region observes in a date range and pushing each date into `BusinessTime::Config.holidays`, with a comment noting that long weekends need to be implemented separately if the region has them. That last point matters: observed holidays such as Independence Day falling on a weekend need the adjacent weekday added manually, which is exactly the July 5 2010 example in the configuration section.

Versions and the default branch

The licence is MIT, with a LICENSE file at the root, and the repository carries a CHANGELOG.md, a CODE_OF_CONDUCT.md, a Rakefile, a `business_time.gemspec`, a `gemfiles/` directory for testing against multiple dependency sets, a `rails_generators/` directory matching the generator feature, and a `test/` directory.

Two facts about the release history deserve attention. The default branch is `develop`, not master or main, so a clone lands on the development line.

And the newest tag is v0.13.0, dated 2022-06-27, with v0.12.0 tagged the same day and v0.11.0 in November 2021. The last push was on 2026-04-19, so commits are still landing, but no new gem release has been published since June 2022.

That gap is the main thing to weigh. For a new integration, confirm the chosen version and test it with the current ActiveSupport and Ruby versions you plan to use.

Editorial conclusion

Adopt business_time if you compute SLAs, due dates or support hours in Ruby and want the weekday and holiday rules in one place rather than in your own arithmetic. Do not adopt it expecting a hard SLA calculation engine, because the weekend rollover behaviour the README documents shows it rounds to the start of the next working day rather than measuring partial days. Verify first the gem version you are installing, since the newest tag is v0.13.0 from 2022-06-27 while the repository's default branch is develop, and test the rollover cases your SLA depends on before trusting them.

Frequently asked questions

What does the business_time gem add to ActiveSupport?

It adds business-aware units alongside the built-in ones, so 8.business_hours.from_now or 8.business_days.after(some_date) count working time rather than elapsed time, respecting a configured workday, work week and holiday list.

How do I configure business hours and holidays?

Through BusinessTime::Config: set beginning_of_workday and end_of_workday, assign work_week to an array of day symbols, assign work_hours to a hash of per-day hour ranges, and push dates onto BusinessTime::Config.holidays. For a temporary change, pass beginning_of_workday and end_of_workday to BusinessTime::Config.with inside a block.

Why is business_time_until between Friday 10:40am and Saturday 10:40am not eight hours?

Because the end time falls outside working hours and rolls forward to the start of the next working period, Monday at 9:00am, which leaves six hours and twenty minutes of working time from Friday 10:40am. The README documents this behaviour and notes that from a quick look at the code it appears it should be eight hours.

What does the business_time Rails generator create?

A config/business_time.yml and a config/initializers/business_time.rb file, so the start of business day, the end of business day and holidays are loaded from the YAML file. The README warns that if you load holidays from a database instead, you need the initializer to work correctly across processes such as mongrel or passenger instances.

Official sources

  1. bokmann/business_time on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/bokmann-business-time.svg)](https://hysenlabs.com/projects/bokmann-business-time)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/bokmann-business-time.svg)](https://hysenlabs.com/projects/bokmann-business-time)

Was this useful?