A modern ruby gem allowing to do time calculation with working hours.
Compatible and tested with:
- Ruby
3.1,3.2,3.3,4.0,JRuby 9.4 - ActiveSupport
7.x,8.x
Gemfile:
gem'working_hours'require'working_hours'# Move forward1.working.day.from_now2.working.hours.from_now15.working.minutes.from_now# Move backward1.working.day.ago2.working.hours.ago15.working.minutes.ago# Start from custom Date or TimeDate.new(2014,12,31) + 8.working.days# => Mon, 12 Jan 2015Time.utc(2014,8,4,8,32) - 4.working.hours# => 2014-08-01 13:00:00# Compute working days between two datesfriday=Date.new(2014,10,17)monday=Date.new(2014,10,20)friday.working_days_until(monday)# => 1# Time is considered at end of day, so:# - friday to saturday = 0 working days# - sunday to monday = 1 working days# Compute working duration (in seconds) between two timesfrom=Time.utc(2014,8,3,8,32)# sunday 8:32amto=Time.utc(2014,8,4,10,32)# monday 10:32amfrom.working_time_until(to)# => 5520 (1.hour + 32.minutes)# Know if a day is workedDate.new(2014,12,28).working_day?# => false# Know if a time is workedTime.utc(2014,8,4,7,16).in_working_hours?# => false# Advance to next working timeWorkingHours.advance_to_working_time(Time.utc(2014,8,4,7,16))# => Mon, 04 Aug 2014 09:00:00 UTC +00:00# Advance to next closing timeWorkingHours.advance_to_closing_time(Time.utc(2014,8,4,7,16))# => Mon, 04 Aug 2014 17:00:00 UTC +00:00WorkingHours.advance_to_closing_time(Time.utc(2014,8,4,10,16))# => Mon, 04 Aug 2014 17:00:00 UTC +00:00WorkingHours.advance_to_closing_time(Time.utc(2014,8,4,18,16))# => Tue, 05 Aug 2014 17:00:00 UTC +00:00# Next working timesunday=Time.utc(2014,8,3)monday=WorkingHours.next_working_time(sunday)# => Mon, 04 Aug 2014 09:00:00 UTC +00:00tuesday=WorkingHours.next_working_time(monday)# => Tue, 05 Aug 2014 09:00:00 UTC +00:00# Return to previous working timeWorkingHours.return_to_working_time(Time.utc(2014,8,4,7,16))# => Fri, 01 Aug 2014 17:00:00 UTC +00:00The working hours configuration is thread safe and consists of a hash defining working periods for each day, a time zone and a list of days off. You can set it once, for example in a initializer for rails:
# Configure working hoursWorkingHours::Config.working_hours={:tue=>{'09:00'=>'12:00','13:00'=>'17:00'},:wed=>{'09:00'=>'12:00','13:00'=>'17:00'},:thu=>{'09:00'=>'12:00','13:00'=>'17:00'},:fri=>{'09:00'=>'12:00','13:00'=>'17:05:30'},:sat=>{'19:00'=>'24:00'}}# Configure timezone (uses activesupport, defaults to UTC)WorkingHours::Config.time_zone='Paris'# Configure holidaysWorkingHours::Config.holidays=[Date.new(2014,12,31)]Or you can set it for the duration of a block with the with_config method, this is particularly useful with around_filter:
WorkingHours::Config.with_config(working_hours: {mon:{'09:00'=>'18:00'}},holidays: [],time_zone: 'Paris')do# Intense calculationsendwith_config uses keyword arguments, you can pass all or some of the supported arguments :
working_hoursholidaystime_zone
Sometimes you need to configure different working hours as a one-off, e.g. the working day might end earlier on Christmas Eve.
You can configure this with the holiday_hours option, either as an override on the existing working hours, or as a set of hours that are being worked on a holiday day.
If any hours are set for a calendar day in holiday_hours, then the working_hours for that day will be ignored, and only the entries in holiday_hours taken into consideration.
# Configure holiday hoursWorkingHours::Config.holiday_hours={Date.new(2020,12,24)=>{'09:00'=>'12:00','13:00'=>'15:00'}}If the configuration is erroneous, an WorkingHours::InvalidConfiguration exception will be raised containing the appropriate error message.
You can also access the error code in case you want to implement custom behavior or changing one specific message, e.g:
rescueWorkingHours::InvalidConfiguration=>eife.error_code == :emptyraiseStandardError.new"Config is required"endraiseeendCore extensions (monkey patching to add methods on Time, Date, Numbers, etc.) are handy but not appreciated by everyone. WorkingHours can also be used without any monkey patching:
require'working_hours/module'# Move forwardWorkingHours::Duration.new(1,:days).from_nowWorkingHours::Duration.new(2,:hours).from_nowWorkingHours::Duration.new(15,:minutes).from_now# Move backwardWorkingHours::Duration.new(1,:days).agoWorkingHours::Duration.new(2,:hours).agoWorkingHours::Duration.new(15,:minutes).ago# Start from custom Date or TimeWorkingHours::Duration.new(8,:days).since(Date.new(2014,12,31))# => Mon, 12 Jan 2015WorkingHours::Duration.new(4,:hours).until(Time.utc(2014,8,4,8,32))# => 2014-08-01 13:00:00# Compute working days between two datesfriday=Date.new(2014,10,17)monday=Date.new(2014,10,20)WorkingHours.working_days_between(friday,monday)# => 1# Time is considered at end of day, so:# - friday to saturday = 0 working days# - sunday to monday = 1 working days# Compute working duration (in seconds) between two timesfrom=Time.utc(2014,8,3,8,32)# sunday 8:32amto=Time.utc(2014,8,4,10,32)# monday 10:32amWorkingHours.working_time_between(from,to)# => 5520 (1.hour + 32.minutes)# Know if a day is workedWorkingHours.working_day?(Date.new(2014,12,28))# => false# Know if a time is workedWorkingHours.in_working_hours?(Time.utc(2014,8,4,7,16))# => falseIf you want to use working hours only inside a specific class or module, you can include its computation methods like this:
require'working_hours/module'classOrderincludeWorkingHoursdefshipping_date_estimateDuration.new(2,:days).since(payment_received_at)enddefpayment_delayworking_days_between(created_at,payment_received_at)endendThis gem uses a simple but efficient approach in dealing with timezones. When you define your working hours you have to choose a timezone associated with it (in the config example, the working hours are in Paris time). Then, any time used in calculation will be converted to this timezone first, so you don't have to worry if your times are local or UTC as long as they are correct :)
There is a gem called business_time already available to do this kind of computation and it was of great help to us. But we decided to start another one because business_time is suffering from a few bugs and inconsistencies. It also lacks essential features to us (like working minutes computation).
Another gem called biz was released after working_hours to bring some alternative.
- Fork it ( http://github.com/intrepidd/working_hours/fork )
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create new Pull Request