{"id":13483840,"url":"https://github.com/intrepidd/working_hours","last_synced_at":"2025-05-14T05:10:37.287Z","repository":{"id":16946086,"uuid":"19708200","full_name":"Intrepidd/working_hours","owner":"Intrepidd","description":"⏰ A modern ruby gem allowing to do time calculation with business / working hours.","archived":false,"fork":false,"pushed_at":"2024-11-24T20:43:39.000Z","size":143,"stargazers_count":531,"open_issues_count":2,"forks_count":30,"subscribers_count":16,"default_branch":"master","last_synced_at":"2025-05-13T12:58:17.853Z","etag":null,"topics":["business-hours"],"latest_commit_sha":null,"homepage":"https://github.com/Intrepidd/working_hours","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Intrepidd.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-05-12T17:26:08.000Z","updated_at":"2025-03-09T12:46:28.000Z","dependencies_parsed_at":"2023-12-28T19:30:37.226Z","dependency_job_id":"92d0ff78-3d0b-4138-855b-a632a74a6c0a","html_url":"https://github.com/Intrepidd/working_hours","commit_stats":{"total_commits":128,"total_committers":17,"mean_commits":7.529411764705882,"dds":0.7421875,"last_synced_commit":"87302601f5e57dc9e88cfec31fe4dbec58c3f70b"},"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Intrepidd%2Fworking_hours","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Intrepidd%2Fworking_hours/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Intrepidd%2Fworking_hours/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Intrepidd%2Fworking_hours/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Intrepidd","download_url":"https://codeload.github.com/Intrepidd/working_hours/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254076850,"owners_count":22010611,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["business-hours"],"created_at":"2024-07-31T17:01:15.812Z","updated_at":"2025-05-14T05:10:37.218Z","avatar_url":"https://github.com/Intrepidd.png","language":"Ruby","readme":"# WorkingHours\n\n[![build](https://github.com/Intrepidd/working_hours/actions/workflows/build.yml/badge.svg)](https://github.com/Intrepidd/working_hours/actions/workflows/build.yml)\n\nA modern ruby gem allowing to do time calculation with working hours.\n\nCompatible and tested with:\n- Ruby `3.1`, `3.2`, `3.3`, JRuby `9.4`\n- ActiveSupport `7.x`, `8.x`\n\n## Installation\n\nGemfile:\n\n```ruby\ngem 'working_hours'\n```\n\n## Usage\n\n```ruby\nrequire 'working_hours'\n\n# Move forward\n1.working.day.from_now\n2.working.hours.from_now\n15.working.minutes.from_now\n\n# Move backward\n1.working.day.ago\n2.working.hours.ago\n15.working.minutes.ago\n\n# Start from custom Date or Time\nDate.new(2014, 12, 31) + 8.working.days # =\u003e Mon, 12 Jan 2015\nTime.utc(2014, 8, 4, 8, 32) - 4.working.hours # =\u003e 2014-08-01 13:00:00\n\n# Compute working days between two dates\nfriday = Date.new(2014, 10, 17)\nmonday = Date.new(2014, 10, 20)\nfriday.working_days_until(monday) # =\u003e 1\n# Time is considered at end of day, so:\n# - friday to saturday = 0 working days\n# - sunday to monday = 1 working days\n\n# Compute working duration (in seconds) between two times\nfrom = Time.utc(2014, 8, 3, 8, 32) # sunday 8:32am\nto = Time.utc(2014, 8, 4, 10, 32) # monday 10:32am\nfrom.working_time_until(to) # =\u003e 5520 (1.hour + 32.minutes)\n\n# Know if a day is worked\nDate.new(2014, 12, 28).working_day? # =\u003e false\n\n# Know if a time is worked\nTime.utc(2014, 8, 4, 7, 16).in_working_hours? # =\u003e false\n\n# Advance to next working time\nWorkingHours.advance_to_working_time(Time.utc(2014, 8, 4, 7, 16)) # =\u003e Mon, 04 Aug 2014 09:00:00 UTC +00:00\n\n# Advance to next closing time\nWorkingHours.advance_to_closing_time(Time.utc(2014, 8, 4, 7, 16)) # =\u003e Mon, 04 Aug 2014 17:00:00 UTC +00:00\nWorkingHours.advance_to_closing_time(Time.utc(2014, 8, 4, 10, 16)) # =\u003e Mon, 04 Aug 2014 17:00:00 UTC +00:00\nWorkingHours.advance_to_closing_time(Time.utc(2014, 8, 4, 18, 16)) # =\u003e Tue, 05 Aug 2014 17:00:00 UTC +00:00\n\n# Next working time\nsunday = Time.utc(2014, 8, 3)\nmonday = WorkingHours.next_working_time(sunday) # =\u003e Mon, 04 Aug 2014 09:00:00 UTC +00:00\ntuesday = WorkingHours.next_working_time(monday) # =\u003e Tue, 05 Aug 2014 09:00:00 UTC +00:00\n\n# Return to previous working time\nWorkingHours.return_to_working_time(Time.utc(2014, 8, 4, 7, 16)) # =\u003e Fri, 01 Aug 2014 17:00:00 UTC +00:00\n```\n\n## Configuration\n\nThe 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:\n\n```ruby\n# Configure working hours\nWorkingHours::Config.working_hours = {\n  :tue =\u003e {'09:00' =\u003e '12:00', '13:00' =\u003e '17:00'},\n  :wed =\u003e {'09:00' =\u003e '12:00', '13:00' =\u003e '17:00'},\n  :thu =\u003e {'09:00' =\u003e '12:00', '13:00' =\u003e '17:00'},\n  :fri =\u003e {'09:00' =\u003e '12:00', '13:00' =\u003e '17:05:30'},\n  :sat =\u003e {'19:00' =\u003e '24:00'}\n}\n\n# Configure timezone (uses activesupport, defaults to UTC)\nWorkingHours::Config.time_zone = 'Paris'\n\n# Configure holidays\nWorkingHours::Config.holidays = [Date.new(2014, 12, 31)]\n```\n\nOr you can set it for the duration of a block with the `with_config` method, this is particularly useful with `around_filter`:\n\n```ruby\nWorkingHours::Config.with_config(working_hours: {mon:{'09:00' =\u003e '18:00'}}, holidays: [], time_zone: 'Paris') do\n  # Intense calculations\nend\n```\n``with_config`` uses keyword arguments, you can pass all or some of the supported arguments :\n- ``working_hours``\n- ``holidays``\n- ``time_zone``\n\n### Holiday hours\nSometimes you need to configure different working hours as a one-off, e.g. the working day might end earlier on Christmas Eve.\n\nYou 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.\n\nIf *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.\n\n```ruby\n# Configure holiday hours\nWorkingHours::Config.holiday_hours = {Date.new(2020, 12, 24) =\u003e {'09:00' =\u003e '12:00', '13:00' =\u003e '15:00'}}\n```\n\n### Handling errors\n\nIf the configuration is erroneous, an ``WorkingHours::InvalidConfiguration`` exception will be raised containing the appropriate error message.\n\nYou can also access the error code in case you want to implement custom behavior or changing one specific message, e.g:\n\n```ruby\nrescue WorkingHours::InvalidConfiguration =\u003e e\n  if e.error_code == :empty\n    raise StandardError.new \"Config is required\"\n  end\n  raise e\nend\n```\n\n## No core extensions / monkey patching\n\nCore 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**:\n\n```ruby\nrequire 'working_hours/module'\n\n# Move forward\nWorkingHours::Duration.new(1, :days).from_now\nWorkingHours::Duration.new(2, :hours).from_now\nWorkingHours::Duration.new(15, :minutes).from_now\n\n# Move backward\nWorkingHours::Duration.new(1, :days).ago\nWorkingHours::Duration.new(2, :hours).ago\nWorkingHours::Duration.new(15, :minutes).ago\n\n# Start from custom Date or Time\nWorkingHours::Duration.new(8, :days).since(Date.new(2014, 12, 31)) # =\u003e Mon, 12 Jan 2015\nWorkingHours::Duration.new(4, :hours).until(Time.utc(2014, 8, 4, 8, 32)) # =\u003e 2014-08-01 13:00:00\n\n# Compute working days between two dates\nfriday = Date.new(2014, 10, 17)\nmonday = Date.new(2014, 10, 20)\nWorkingHours.working_days_between(friday, monday) # =\u003e 1\n# Time is considered at end of day, so:\n# - friday to saturday = 0 working days\n# - sunday to monday = 1 working days\n\n# Compute working duration (in seconds) between two times\nfrom = Time.utc(2014, 8, 3, 8, 32) # sunday 8:32am\nto = Time.utc(2014, 8, 4, 10, 32) # monday 10:32am\nWorkingHours.working_time_between(from, to) # =\u003e 5520 (1.hour + 32.minutes)\n\n# Know if a day is worked\nWorkingHours.working_day?(Date.new(2014, 12, 28)) # =\u003e false\n\n# Know if a time is worked\nWorkingHours.in_working_hours?(Time.utc(2014, 8, 4, 7, 16)) # =\u003e false\n```\n\n## Use in your class/module\n\nIf you want to use working hours only inside a specific class or module, you can include its computation methods like this:\n\n```ruby\nrequire 'working_hours/module'\n\nclass Order\n  include WorkingHours\n\n  def shipping_date_estimate\n    Duration.new(2, :days).since(payment_received_at)\n  end\n\n  def payment_delay\n    working_days_between(created_at, payment_received_at)\n  end\nend\n```\n\n## Timezones\n\nThis 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 :)\n\n## Alternatives\n\nThere is a gem called [business_time](https://github.com/bokmann/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](https://github.com/bokmann/business_time/pull/84) and [inconsistencies](https://github.com/bokmann/business_time/issues/50). It also lacks essential features to us (like working minutes computation).\n\nAnother gem called [biz](https://github.com/zendesk/biz) was released after working_hours to bring some alternative.\n\n## Contributing\n\n1. Fork it ( http://github.com/intrepidd/working_hours/fork )\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create new Pull Request\n","funding_links":[],"categories":["Date and Time Processing"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fintrepidd%2Fworking_hours","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fintrepidd%2Fworking_hours","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fintrepidd%2Fworking_hours/lists"}