Skip to content

Commit

Permalink
Merge branch 'main' into Feature/6-holiday-definition-files
Browse files Browse the repository at this point in the history
  • Loading branch information
kaklakariada authored Jun 19, 2021
2 parents 83cf621 + b1536bf commit 66b0dac
Show file tree
Hide file tree
Showing 35 changed files with 1,695 additions and 435 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.1 - Unreleased]
## [0.2.0] - 2021-06-19

- [#5](https://github.com/itsallcode/holiday-calculator/issues/5) Support month names.
- [#7](https://github.com/itsallcode/holiday-calculator/issues/7) Added documentation for API users.
- [#21](https://github.com/itsallcode/holiday-calculator/issues/21) Use java.time.MonthDay instead of integers.
- [#10](https://github.com/itsallcode/holiday-calculator/issues/10) Support holidays with conditions and alternative dates.
- [#23](https://github.com/itsallcode/holiday-calculator/issues/23) Support negated list of days of week for holidays with alternative dates.
- [#9](https://github.com/itsallcode/holiday-calculator/issues/9) Support floating holidays with additional offset in days.
- [#6](https://github.com/itsallcode/holiday-calculator/issues/6) Added holiday-definition files for various countries.

## [0.1.0] - 2021-06-05
Expand Down
149 changes: 120 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ repositories {
mavenCentral()
}
dependencies {
compile 'org.itsallcode:holiday-calculator:0.1.0'
compile 'org.itsallcode:holiday-calculator:0.2.0'
}
```

Expand All @@ -40,15 +40,15 @@ dependencies {
<dependency>
<groupId>org.itsallcode</groupId>
<artifactId>holiday-calculator</artifactId>
<version>0.1.0</version>
<version>0.2.0</version>
</dependency>
```

### Calculating holidays

In order to calculate holidays you must create a holiday definition.
In order to calculate holidays you must create a holiday definition.
Holiday-calculator supports 4 different flavors of holiday definitions.
For each flavor there is a dedicated class in package `org.itsallcode.holidays.calculator.logic`.
For each flavor there is a dedicated class in package `org.itsallcode.holidays.calculator.logic`.

1. class `FixedDateHoliday`: Fixed date holiday definition
2. class `FloatingHoliday`: Floating holiday definition
Expand All @@ -62,34 +62,58 @@ Section [Configuration file](README.md#flavors) describes the details and parame
The following code sample instantiates one holiday for each of these flavors:

```
import org.itsallcode.holidays.calculator.logic.FixedDateHoliday;
import org.itsallcode.holidays.calculator.logic.FloatingHoliday;
import org.itsallcode.holidays.calculator.logic.EasterBasedHoliday;
import org.itsallcode.holidays.calculator.logic.OrthodoxEasterBasedHoliday;
import org.itsallcode.holidays.calculator.logic.variants.FixedDateHoliday;
import org.itsallcode.holidays.calculator.logic.variants.FloatingHoliday;
import org.itsallcode.holidays.calculator.logic.variants.EasterBasedHoliday;
import org.itsallcode.holidays.calculator.logic.variants.OrthodoxEasterBasedHoliday;
import org.itsallcode.holidays.calculator.logic.conditions.DayOfWeekCondition;
import static org.itsallcode.holidays.calculator.logic.conditions.Condition.not;
class MyClass {
public MyClass() {
FixedDateHoliday h1 = new FixedDateHoliday("holiday", "Christmas Eve", 12, 24);
FloatingHoliday h2 = new FloatingHoliday(
"holiday", "Father's Day", 3, DayOfWeek.SUNDAY, Direction.AFTER, 6, 1);
EasterBasedHoliday h3 = new EasterBasedHoliday("holiday", "Good Friday", -2);
OrthodoxEasterBasedHoliday h4 = new OrthodoxEasterBasedHoliday(
"holiday", "Orthodox Good Friday", -2);
}
public MyClass() {
Holiday h1 = new FixedDateHoliday("holiday", "Christmas Eve", MonthDay.of(12, 24));
Holiday h2 = new FloatingHoliday(
"holiday", "Father's Day", 3, DayOfWeek.SUNDAY, Direction.AFTER, MonthDay.of(6, 1));
Holiday h3 = new EasterBasedHoliday("holiday", "Good Friday", -2);
Holiday h4 = new OrthodoxEasterBasedHoliday(
"holiday", "Orthodox Good Friday", -2);
Condition dec25FriSat = new DayOfWeekCondition(
MonthDay.of(12, 25), DayOfWeek.FRIDAY, DayOfWeek.SATURDAY);
Holiday h5 = new FixedDateHoliday(
"holiday", "Boxing day is extra day off", MonthDay.of(12, 26))
.withCondition(not(dec25FriSat)));
Condition isSunday = new DayOfWeekCondition(DayOfWeek.SUNDAY);
Holiday h6 = new FixedDateHoliday("holiday", "Koningsdag", MonthDay.of(4, 27))
.withAlternative(isSunday, MonthDay.of(4, 26));
Holiday h7 = new FloatingHoliday("holiday", "Midsommarafton",
1, DayOfWeek.SATURDAY, Direction.BEFORE, MonthDay.of(6, 26))
.withOffsetInDays(-1);
}
}
```

Holiday `h5` is a conditional holiday with a negated condition, see [below](README.md#conditional-holidays).
Holiday `h6` is a holiday with an alternative date, see [below](README.md#alternative-holidays).
Holiday `h6` is a floating holiday with an additional offset in days, see [below](README.md#floating-holidays).

#### Parsing a configuration File

Besides creating instances of the subclasses of Holiday you can also use a configuration file to define your personal selection of holidays. Class `HolidaysFileParser` then parses the file and returns a list of holidays:
Besides creating instances of the subclasses of Holiday you can also use a configuration file to define your personal selection of holidays. Class `HolidaysFileParser` then parses the file and returns a list of holidays:

```
import org.itsallcode.holidays.calculator.logic.parser.HolidaysFileParser
class MyClass {
public MyClass() {
List<Holiday> holidays = new HolidaysFileParser.parse("/path/to/holidays.cfg")
}
public MyClass() {
List<Holiday> holidays = new HolidaysFileParser.parse("/path/to/holidays.cfg")
}
}
```

Expand All @@ -98,9 +122,10 @@ For the four example holidays instantiated above, the content of the file could
```
# my holidays
holiday fixed 12 24 Christmas Eve
holiday float 3 SUN after 6 1 Father's Day
holiday easter -2 Good Friday
holiday fixed 12 24 Christmas Eve
holiday if DEC 25 is Sat,Sun then fixed DEC 27 Bank Holiday
holiday float 3 SUN after 6 1 Father's Day
holiday easter -2 Good Friday
holiday orthodox-easter -2 Orthodox Good Friday
```

Expand All @@ -111,8 +136,8 @@ Section [Configuration file](README.md#flavors) describes the syntax in detail.
In order to evaluate a holiday for the current or any other year and hence get an instance of this holiday, you can just call method `Holiday.of()`, supplying the year as argument:

```
EasterBasedHoliday goodFriday = new EasterBasedHoliday("holiday", "Good Friday", -2);
LocalDate goodFriday2021 = goodFriday.of(2021); // 2021 April 4th
Holiday gf = new EasterBasedHoliday("holiday", "Good Friday", -2);
LocalDate gf_2021 = goodFriday.of(2021); // 2021 April 4th
```


Expand Down Expand Up @@ -141,6 +166,8 @@ types of content:
3. Floating holiday definition
4. Easter-based holiday definition
5. Orthodox-Easter-based holiday definition
6. Conditional Fixed date holiday definition
7. Alternative date holiday definition

All other lines are rated as illegal and ignored by holiday-calculator,
logging an error message.
Expand Down Expand Up @@ -191,14 +218,16 @@ the holiday definition:

##### Fixed date holiday definition

A fixed date holiday definition has the tag "fixed", followed by the numbers
of month and day of month.
A fixed date holiday definition has the tag "fixed", followed by the month and day of month.
Month may be a number from 1 to 12 or the (abbreviated) English name like "December".

Syntax: `holiday fixed <month> <day> <name>`

Sample: `holiday fixed 1 1 New Year`
Samples:
- `holiday fixed 1 1 New Year`
- `holiday fixed Dec 12 Christmas Eve`

##### Floating holiday definition
##### <a name="floating-holidays"></a>Floating holiday definition

A floating holiday definition has the tag "float", followed by the offset, the
day of week, the direction "before" or "after", the numbers of month and day
Expand All @@ -217,6 +246,15 @@ Samples:
- `holiday float 2 MON before 12 last-day Second Monday before New Year's eve, December the 31st`
- `holiday float 4 SUNDAY before 12 24 First Advent`

##### Floating holiday with additional offset in days

Optionally a floating holiday definition can contain an additional offset in days. This is especially required for Swedish holiday "Midsommarafton", which is 1 day before midsummer day, which in turn is the first Saturday be June, 26th.

Syntax: `holiday float <offset2> day[s] <direction2> <offset> <direction> <day of week> <month> <day> <name>`

Samples:
- `holiday float 1 day before 1 Sat before JUN 26 Midsommarafton`

##### Easter-based holiday definition

An Easter-based holiday definition has the tag "easter", followed by the
Expand All @@ -243,6 +281,59 @@ Samples:
- `holiday orthodox-easter -2 Orthodox Good Friday`
- `holiday orthodox-easter +49 Orthodox Pentecost Monday`


##### <a name="conditional-holidays"></a>Conditional holidays

Some countries have a few holidays that do not occur every year but only if a specific condition is met.
Examples are the bank holidays in the UK.
Holiday-calculator calls such holidays *conditional holidays*.

A conditional holiday definition has the tag "if", followed by
- pivot month: (abbreviated) name or number 1-12
- pivot day: 1-31
- "is"
- "not" (optional)
- comma-separated list of (abbreviated) pivot days of week, e.g. "Wed,Fri", not containing any whitespace.
- "then"
- "fixed"
- month: (abbreviated) name or number 1-12
- day of month: number
- name

The conditional holiday is only effective, if the pivot-date of the conditions falls on one of the specified days of week.
If the definition contains the optional "not", then the pivot day must *not fall* on one of the specified days of week.

Syntax: `holiday if <pivot month> <pivot day> is [not] <days of week> then fixed <month> <day> <name>`

Samples:
- `holiday if DEC 25 is Sat,Sun then fixed DEC 27 Bank Holiday`
- `holiday if DEC 25 is not Fri,Sat then fixed DEC 26 Boxing day is extra day off`

##### <a name="alternative-holidays"></a>Alternative date Holidays

Some countries have holidays that are moved to another date in case a specific condition is met.
An example is the "Koningsdag" in the Netherlands which occurs on April, 27th but is moved to April, 26 in case April, 27th is a Sunday.

A holiday with an alternative has the tag "either", followed by
- month: (abbreviated) name or number 1-12
- day: 1-31
- "or"
- "if"
- "not" (optional)
- comma-separated list of (abbreviated) pivot days of week, e.g. "Wed,Fri", not containing any whitespace.
- "then"
- "fixed"
- alternative month: (abbreviated) name or number 1-12
- alternative day of month: number
- name

Syntax: `holiday either <month> <day> of if [not] <days of week> then fixed <alternative month> <alternative day> <name>`

Samples:
- `holiday either 4 27 or if SUN then fixed 4 26 Koningsdag`
- `holiday either 4 27 or if not Mon,Tue,We,Thu,Fri,Sat then fixed 4 26 Koningsdag`


## Development

### Generate / update license header
Expand Down
23 changes: 11 additions & 12 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ plugins {
}

group 'org.itsallcode'
version = '0.1.1'
version = '0.2.0'

java {
toolchain {
Expand Down Expand Up @@ -38,37 +38,36 @@ test {

ext {
junitVersion = '5.7.2'
log4jVersion = '2.14.1'
log4jVersion = '2.14.1'
mockitoVersion = '3.10.0'
lockbackVersion = '1.2.3'
lockbackVersion = '1.2.3'
}

dependencies {
implementation 'org.slf4j:slf4j-api:1.7.30'

testImplementation "org.mockito:mockito-core:$mockitoVersion"
testImplementation 'org.assertj:assertj-core:3.19.0'
testImplementation "org.mockito:mockito-junit-jupiter:$mockitoVersion"
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion"
testImplementation "org.mockito:mockito-junit-jupiter:$mockitoVersion"
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion"

testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
// testRuntimeOnly "org.apache.logging.log4j:log4j-core:$log4jVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
testRuntimeOnly "ch.qos.logback:logback-classic:$lockbackVersion"
}

license {
header = file('gradle/license-header.txt')
ext.project = 'holiday-calculator'
ext.year = Calendar.getInstance().get(Calendar.YEAR)
ext.project = 'holiday-calculator'
ext.year = Calendar.getInstance().get(Calendar.YEAR)
ext.name = 'itsallcode'
ext.email = '[email protected]'
include '**/*.java'
include '**/*.java'
}

jacocoTestReport {
reports {
xml.enabled true
xml.required = true
}
}

Expand Down
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
2 changes: 1 addition & 1 deletion gradlew
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ case "`uname`" in
Darwin* )
darwin=true
;;
MINGW* )
MSYS* | MINGW* )
msys=true
;;
NONSTOP* )
Expand Down
Loading

0 comments on commit 66b0dac

Please sign in to comment.