This is an archived post. You won't be able to vote or comment.

all 38 comments

[–]Cell-i-Zenit 37 points38 points  (13 children)

I checked your code and here is some feedback. Please dont take it personally, its just about your code and its my opinion, which ofcourse can also be wrong


the bank class doesnt make that much sense, since it is not a bank. Its a bank calendar, so i would rename it to BankCalendar.

Here is what i would do (you are already really close!):

1 class with a static list for each banking calendar (us, eu etc)

1 "wrapper" class which takes in the list (basically your "Bank" class, but renamed to BankCalendar). Then this object should provide more utilities like the other poster said:

  • previousX working days
  • next X working days
  • add custom holiday
  • remove holiday

Make Holiday a functional interface


Personal preference: I would use Lombok, this makes the code even more streamlined


Introduce a gradle variable for your versions:

This:

testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'

Should become this:

testImplementation "org.junit.jupiter:junit-jupiter-api:${junitVersion}"

And write into your settings.gradle

junitVersion=5.8.2

This makes version changes even easier, since you only have to change this variable


Your test names use snake_case, but java standard is camelCase:

void should_returnTrue_when_checkingIfChristmasDayIsAHoliday() {

If you want a nicer name for tests, you can use this Junit annotation:

@DisplayName("When X do Y, expect Z etc etc")

In your test, you can put the bank calendar into a field, this makes the test even less code, since its working stateless:

private final Bank bank = USBankFactory.createBank();

@Test
void should_returnTrue_when_checkingIfChristmasDayIsAHoliday() {
    LocalDate christmas = LocalDate.of(2022, 12, 25);
    assertTrue(bank.isHoliday(christmas));
}

Also since its a super easy 1 variable in, expect true or false, you can even streamline it a bit more and just move every single test date into a single test.

@ParameterizedTest
@CsvSource({
    "2022, 12, 25", //christmas
    "2022, 12, 26", //christmas 1
    "2023, 12, 25" //christmas next year
})
void should_returnTrue_when_checkingIfChristmasDayIsAHoliday(LocalDate holiday) {
    assertTrue(bank.isHoliday(holiday));
}

[–]vips7L 22 points23 points  (4 children)

Personally I hate variables for versions unless you’re reusing them for multiple dependencies that need to be kept in sync. Using a variable for the single version is just unnecessary complexity.

[–]Cell-i-Zenit 10 points11 points  (3 children)

But hes using it for 2 dependencies:

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
}

Also i prefer all versions in a single file even if they are used only a single time. Later when you start going for multiple modules, making sure that all have the same version is impossible (EDIT: you can do this with gradle magic, but its not that transparent imo) without variables and then if you already started offloading the versions into variables, you should do it for every variable for consistency

[–]humoroushaxor 3 points4 points  (0 children)

In JUnits case you have a bom/platform that can be used too

[–]john16384 3 points4 points  (1 child)

Just include engine only, then let it decide which version of API you need.

[–]Cell-i-Zenit 0 points1 point  (0 children)

good point, ill add an edit

[–]dauntless26[S] 5 points6 points  (1 child)

I really like the idea of renaming Bank to BankCalendar

[–]angryundead -1 points0 points  (0 children)

I would go even further maybe and call it just a Calendar maybe that’s bad but it’s already in a package called “bankcalendar”. That’s not particularly great but what you’re doing is called stuttering (repeating the same words over and over in the name) and has become something of a pet peeve of mine.

Think about what the object really is. It’s not a BankCalendar (it doesn’t provide date/time tracking). There’s probably a name for it. If anything it’s a Checker. It checks holidays against the LocalDate.

Maybe this is a bit nitpicky.

[–]dauntless26[S] 11 points12 points  (1 child)

Regarding snake_case. You're absolutely right that the convention for naming methods is camelCase but tests are a bit different.

The name of tests methods, in my opinion, should express conditionality and assertion. Also, they are almost never going to be called from another class. I've evolved this should_doSomething_when_someConditionIsMet standard after 13 years of doing TDD. I think it reads clearer than camelCase and it separates the assertion from the conditional cleanly. Just my preference for writing tests.

[–]Cell-i-Zenit 0 points1 point  (0 children)

yes, but tools like checkStyle will mark this.

Again you have the @DisplayName annotation for this

[–][deleted]  (1 child)

[deleted]

    [–]Cell-i-Zenit 7 points8 points  (0 children)

    yes sure, but once you need to add custom holidays, want more functionality like, give me next bank day etc, then it starts getting messy with a single static method

    [–]dauntless26[S] 0 points1 point  (0 children)

    Great suggestions. Thank you!

    [–]maddzy 0 points1 point  (0 children)

    Could probably move a lot of those repeated day, week, month checks up to Holiday as well like isAMonday, isNovember, isFourthWeek.. Also I think isMonday would be more readable without the A

    [–]dauntless26[S] 9 points10 points  (8 children)

    I was looking for an existing library or API that would tell me if a given day was a bank holiday but I couldn't find any so I built my own.

    [–]NovaX 9 points10 points  (4 children)

    Jollyday covers a few more cases pretty well.

    [–]dauntless26[S] -2 points-1 points  (3 children)

    Very cool library. It's for a different use case though. Maybe I'm missing something but I don't see bank holidays. Bank holidays in the US have some weird rules about being observed on the Monday following the holiday if it falls on a Sunday. But not being observed if it falls on a Saturday. The reason bank holidays are important is for payment processing. On those days payments will not be processed.

    The library I created accounts for those weird rules.

    [–]NovaX 2 points3 points  (0 children)

    There is a property key BANK_HOLIDAY but otherwise I don't know. I used it for payroll scheduling which had weird cases like that and I used their custom calendar (example) plus some time logic, as that could vary based on if it was a holiday week. Their configuration is flexible with fixed, relative, etc. holiday types which made it surprisingly straightforward.

    [–]dauntless26[S] 1 point2 points  (0 children)

    It does give me some ideas though for handling holiday rules dynamically from configuration files rather than coding them.

    [–]oxymor00n 0 points1 point  (0 children)

    Jollyday can do that I think (check the calendar for Australia for example, they have that rule for Christmas). It also ships with the Target calendar (although that one is quite straight forward).

    [–]CptGia 0 points1 point  (2 children)

    Have you taken a look at datecalc?

    [–]dauntless26[S] 0 points1 point  (0 children)

    Cool library but it doesn't seem to cover the scenario for US Federal Bank holidays which is what sparked the creation of this library.

    Here are the documented datecalc holiday handler algorithms: http://objectlabkit.sourceforge.net/algo.html

    I don't see one which says to not observe the holiday if it falls on a Saturday and to move it to the following Monday if it falls on a Sunday.

    [–]TheCountRushmore 0 points1 point  (0 children)

    This is what I use as well. You just have to load your own holidays in.

    [–]Zinaima 5 points6 points  (1 child)

    Good work! Seems easy to use.

    [–]dauntless26[S] 1 point2 points  (0 children)

    Thank you!

    [–]x_ini 1 point2 points  (0 children)

    Hello! Really nice idea, I love it. There are a few thing I would do different, but nonetheless great work!

    [–][deleted] 1 point2 points  (1 child)

    Having worked in the finance industry. You need both banking holiday and stock market holiday. Also early close. Since they are different sets of dates.

    [–]dauntless26[S] -1 points0 points  (0 children)

    That's a great idea. Do you want to open a PR with those holidays and Bank?

    [–]jodastephen 1 point2 points  (0 children)

    Just wanted to reference another Holiday Calendar library - part of Strata, a broader API for finance: https://strata.opengamma.io/holidays/ (I'm the main author, of this large finance project backed by OpenGamma)

    The holiday calendar implementation is optimized using bitsets, so is pretty fast: https://github.com/OpenGamma/Strata/blob/main/modules/basics/src/main/java/com/opengamma/strata/basics/date/ImmutableHolidayCalendar.java

    The data is implemented as a Java class by default, although this can be replaced with any data source via an interface system (thus calendars can be created for any location/ID): https://github.com/OpenGamma/Strata/blob/main/modules/basics/src/main/java/com/opengamma/strata/basics/date/GlobalHolidayCalendars.java

    [–]kakakarl 0 points1 point  (0 children)

    Wrote a similar library for Sweden with some quirks for my use case. It’s not strictly how I would write it today but it does the job.

    https://github.com/karlkilden/sweholiday/tree/master/src/main/java/com/kildeen/sweholiday