Skip to content

Commit

Permalink
Update readme to detail how to load calendars
Browse files Browse the repository at this point in the history
  • Loading branch information
JoeSouthan committed May 4, 2020
1 parent f8acfdc commit e9b40c3
Showing 1 changed file with 46 additions and 73 deletions.
119 changes: 46 additions & 73 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,70 +15,73 @@ $ gem install business

### Getting started

Get started with business by creating an instance of the calendar class,
passing in a hash that specifies with days of the week are considered working
days, and which days are holidays.
Get started with business by creating an instance of the calendar class, passing in a hash that specifies with days of the week are considered working days, and which days are holidays.

```ruby
calendar = Business::Calendar.new(
working_days: %w( mon tue wed thu fri ),
holidays: ["01/01/2014", "03/01/2014"] # array items are either parseable date strings, or real Date objects
extra_working_dates: [nil], # Makes the calendar to consider a weekend day as a working day.
)
```

`extra_working_dates` key makes the calendar to consider a weekend day as a working day.
### Load a calendar from a file

A few calendar configs are bundled with the gem (see [lib/business/data]((lib/business/data)) for
details). Load them by calling the `load` class method on `Calendar`. The
`load_cached` variant of this method caches the calendars by name after loading
them, to avoid reading and parsing the config file multiple times.
#### Calendar file definition

```ruby
calendar = Business::Calendar.load("weekdays")
calendar = Business::Calendar.load_cached("weekdays")
```
Defining a calendar as a Ruby object may not be convient, to load it from a YAML file follow and customise the example below. All keys are optional and will default to the following:

- If `working_days` is missing, then common default is used (mon-fri).
- If `holidays` is missing, "no holidays" assumed.
- If `extra_working_dates` is missing, then no changes in `working_days` will happen.

If `working_days` is missing, then common default is used (mon-fri).
If `holidays` is missing, "no holidays" assumed.
If `extra_working_dates` is missing, then no changes in `working_days` will happen.
> Note: Elements of `holidays` and `extra_working_dates` may be eiter strings that `Date.parse()` can understand, or YYYY-MM-DD (which is considered as a Date by Ruby YAML itself).
Elements of `holidays` and `extra_working_dates` may be
eiter strings that `Date.parse()` can understand,
or YYYY-MM-DD (which is considered as a Date by Ruby YAML itself).
#### Example

```yaml
working_days:
- Monday
- Wednesday
- Friday
holidays:
- 2017-01-08 # Same as January 8th, 2017
- 1st April 2020
- 2021-04-01
extra_working_dates:
- 9th March 2020 # A Saturday
```
### Checking for business days
#### Using the calendar
To check whether a given date is a business day (falls on one of the specified
working days or working dates, and is not a holiday), use the `business_day?`
method on `Calendar`.
Ensure the calendar file is saved to a directory that will hold all your calendars, eg; `path/to/your/calendar/directory` then add this directory to your code before you call your calendar:

```ruby
calendar.business_day?(Date.parse("Monday, 9 June 2014"))
# => true
calendar.business_day?(Date.parse("Sunday, 8 June 2014"))
# => false
Business::Calendar.load_paths = ["path/to/your/calendar/directory"]
```

### Custom calendars

To use a calendar you've written yourself, you need to add the directory it's
stored in as an additional calendar load path:
Now you can load the calendar by calling the `load` class method on `Business::Calendar`. The
`load_cached` variant of this method caches the calendars by name after loading them, to avoid reading and parsing the config file multiple times.

```ruby
Business::Calendar.additional_load_paths = ['path/to/your/calendar/directory']
calendar = Business::Calendar.load("my_calendars")
# or
calendar = Business::Calendar.load_cached("my_calendars")
```

You can then load the calendar as normal.
### Checking for business days

To check whether a given date is a business day (falls on one of the specified working days or working dates, and is not a holiday), use the `business_day?` method on `Business::Calendar`.

```ruby
calendar.business_day?(Date.parse("Monday, 9 June 2014"))
# => true
calendar.business_day?(Date.parse("Sunday, 8 June 2014"))
# => false
```

### Business day arithmetic

The `add_business_days` and `subtract_business_days` are used to perform
business day arithmetic on dates.
The `add_business_days` and `subtract_business_days` are used to perform business day arithmetic on dates.

```ruby
date = Date.parse("Thursday, 12 June 2014")
Expand All @@ -88,10 +91,7 @@ calendar.subtract_business_days(date, 4).strftime("%A, %d %B %Y")
# => "Friday, 06 June 2014"
```

The `roll_forward` and `roll_backward` methods snap a date to a nearby business
day. If provided with a business day, they will return that date. Otherwise,
they will advance (forward for `roll_forward` and backward for `roll_backward`)
until a business day is found.
The `roll_forward` and `roll_backward` methods snap a date to a nearby business day. If provided with a business day, they will return that date. Otherwise, they will advance (forward for `roll_forward` and backward for `roll_backward`) until a business day is found.

```ruby
date = Date.parse("Saturday, 14 June 2014")
Expand All @@ -101,51 +101,24 @@ calendar.roll_backward(date).strftime("%A, %d %B %Y")
# => "Friday, 13 June 2014"
```

To count the number of business days between two dates, pass the dates to
`business_days_between`. This method counts from start of the first date to
start of the second date. So, assuming no holidays, there would be two business
days between a Monday and a Wednesday.
To count the number of business days between two dates, pass the dates to `business_days_between`. This method counts from start of the first date to start of the second date. So, assuming no holidays, there would be two business days between a Monday and a Wednesday.

```ruby
date = Date.parse("Saturday, 14 June 2014")
calendar.business_days_between(date, date + 7)
# => 5
```

### Included Calendars
## But other libraries already do this

We include some calendar data with this Gem but give no guarantees of its
accuracy.
The calendars that we include are:
Another gem, [business_time](https://github.com/bokmann/business_time), also exists for this purpose. We previously used business_time, but encountered several issues that prompted us to start business.

* Bacs
* Bankgirot
* BECS (Australia)
* BECSNZ (New Zealand)
* PAD (Canada)
* Betalingsservice
* Target (SEPA)
* TargetFrance (SEPA + French bank holidays)
* US Banking (ACH)
Firstly, business_time works by monkey-patching `Date`, `Time`, and `FixNum`. While this enables syntax like `Time.now + 1.business_day`, it means that all configuration has to be global. GoCardless handles payments across several geographies, so being able to work with multiple working-day calendars is
essential for us. Business provides a simple `Calendar` class, that is initialized with a configuration that specifies which days of the week are considered to be working days, and which dates are holidays.

## But other libraries already do this
Secondly, business_time supports calculations on times as well as dates. For our purposes, date-based calculations are sufficient. Supporting time-based calculations as well makes the code significantly more complex. We chose to avoid this extra complexity by sticking solely to date-based mathematics.

Another gem, [business_time](https://github.com/bokmann/business_time), also
exists for this purpose. We previously used business_time, but encountered
several issues that prompted us to start business.

Firstly, business_time works by monkey-patching `Date`, `Time`, and `FixNum`.
While this enables syntax like `Time.now + 1.business_day`, it means that all
configuration has to be global. GoCardless handles payments across several
geographies, so being able to work with multiple working-day calendars is
essential for us. Business provides a simple `Calendar` class, that is
initialized with a configuration that specifies which days of the week are
considered to be working days, and which dates are holidays.

Secondly, business_time supports calculations on times as well as dates. For
our purposes, date-based calculations are sufficient. Supporting time-based
calculations as well makes the code significantly more complex. We chose to
avoid this extra complexity by sticking solely to date-based mathematics.
---


![I'm late for business](http://3.bp.blogspot.com/-aq4iOz2OZzs/Ty8xaQwMhtI/AAAAAAAABrM/-vn4tcRA9-4/s1600/daily-morning-awesomeness-243.jpeg)

0 comments on commit e9b40c3

Please sign in to comment.