java-beginner.com ブログ

プログラミングを学習するブログ(Javaをメインに)

年月日から直近の営業日を求める練習

投稿日:

最終更新日:2018年12月02日

アイキャッチ

こんにちは。「Javaを復習する初心者」です。

今回は与えられた年月日から直近の営業日を求めるというメソッドを作ってみました。営業日は平日ではなく祝日でもない日とします。ただし、求める営業日は2018年限定にします。また、祝日は5月分だけ使います。

まず、Calendarクラスを使ってみました。そのあと、Time APIを使ってみました。

Calendarクラスで作る

前日を判定してみる

与えられた年月日は、年、月、日がint型で与えられるとします。まず、その日付の対応するCalendarクラスのインスタンスに設定することを考えます。

CalendarクラスのインスタンスはCalendar#getInstance()メソッドで取得します。このインスタンスに年月日を設定するには、以下のメソッドを使います。

  • set(int year, int month, int date)

作成するメソッドの引数をint year, int month, int dateとすると日付設定までは以下のようになります。

日付設定まで

Calendar calendar = Calendar.getInstance();
calendar.set(year, month, date);

目的は直近の営業日を求めることですが、その全段階として、calendarに前日を設定して、曜日を判定することを考えました。

前日を設定するには、次のメソッドを使います。

  • add(int field, int amount)

API仕様書によると、fieldにはカレンダ・フィールドを指定します。日を操作したい場合、Calendar.DATEを指定するようです。前日を求めるので、今回はamountには「-1」を指定します。

曜日の情報を取得するのは次のメソッドを使います。

  • get(int field)

曜日の情報を取得したい場合は、引数にCalendar.DAY_OF_WEEKを指定するようです。返却値が何曜日なのかというのは、Calendarクラスに定義されている以下の定数と比較することで判定できます。

  • SUNDAY
  • MONDAY
  • TUESDAY
  • WEDNESDAY
  • THURSDAY
  • FRIDAY
  • SATURDAY

前日が平日か判定するというのは以下のようになります。

前日が平日か

calendar.add(Calendar.DATE, -1);

boolean isBusinessDay = false;
int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
if (dayOfWeek != Calendar.SUNDAY && dayOfWeek != Calendar.SATURDAY) {
    isBusinessDay = true;
}

if文で土曜日でもなく日曜日でもないということを判定しています。

直近の営業日を求める

営業日を求めるには祝日の情報が必要なのですが、今回は2018年5月の祝日だけ判定することにしました。以下の3つだけ、祝日扱いにします。

  • 5月03日(木) 憲法記念日
  • 5月04日(金) みどりの日
  • 5月05日(土) こどもの日

こういう情報はプロパティファイルに記述したり、データベースに保持しておくのが普通の方法だと思うのですが、今回はBusinessDayクラスにカンマ区切り文字列として定義しておくことにします。

List型の変数publicHolidaysをメンバ変数として定義して、コンストラクタで次のように祝日を文字列として格納しておきます。また、祝日であるかを判定するために文字列に変換するので、フォーマット文字列を定義しておきます。

ということで、BusinessDayクラスというものを作ります。最初の方は以下のようになります。

BusinessDayクラス前半

public class BusinessDay {

    private static String FORMAT_YYYYMMDD = "%d%02d%02d";

    private List<String> publicHolidays;
    
    public BusinessDay() {
        publicHolidays = Arrays.asList("20180503,20180504,20180505".split(","));
    }
    ・
    ・
    ・
}

Arrays.asList()メソッドを使って、List型オブジェクトを取得しています。祝日判定は対象日がこのList型に含まれるかを判定するという事になります。

後半は以下のようにしました。

BusinessDayクラス後半

public class BusinessDay {
    ・
    ・
    ・
    public Calendar findBusinessDay(int year, int month, int date) {

        Calendar calendar = Calendar.getInstance();
        calendar.set(year, month - 1, date);

        // 与えられた年月日が営業日である場合、返却
        if(isBusinessDay(calendar)) {
            return calendar;
        }

        // 直近の営業日を求める
        while (true) {
            calendar.add(Calendar.DATE, -1);
            if (isBusinessDay(calendar)) {
                break;
            }
        }

        return calendar;
    }

    private boolean isBusinessDay(Calendar calendar) {

        int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);

        String yyyyMMdd = String.format(FORMAT_YYYYMMDD,
                calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.DATE));

        return (dayOfWeek != Calendar.SUNDAY && dayOfWeek != Calendar.SATURDAY) && !publicHolidays.contains(yyyyMMdd);
    }
}

isBusinessDay()メソッドは営業日であるかを判定するメソッドです。最初に、引数のCalendarオブジェクトが保持している日付から、曜日を取得し、「20180401」のような文字列を取得しています。return文に条件文を記述しています。土曜日でないことと日曜日でないこと、そして、祝日でないことを判定しています。

祝日でないことを、List#contains()メソッドで判定しています。List型変数publicHolidaysが対象日の文字列を含むかどうかということを見ています。

以下はテストと結果です。

テスト

public class BusinessDayTest {

    public static void main(String[] args) {

        test(2018, 11, 9);

        test(2018, 11, 10);

        test(2018, 11, 11);

        test(2018, 12, 1);

        test(2018, 5, 3);

        test(2018, 5, 4);

        test(2018, 5, 5);
    }

    private static void test(int year, int month, int date) {
        System.out.println("--------------------------------------------");

        System.out.println("対象日: " + String.format("%d%02d%02d", year, month, date));

        BusinessDay businessDay = new BusinessDay();

        Calendar calendar;
        calendar = businessDay.findBusinessDay(year, month, date);

        String yyyyMMdd = String.format("%d%02d%02d",
                calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.DATE));
        System.out.println("結果: " + yyyyMMdd);

    }

}

結果

--------------------------------------------
対象日: 20181109
結果: 20181109
--------------------------------------------
対象日: 20181110
結果: 20181109
--------------------------------------------
対象日: 20181111
結果: 20181109
--------------------------------------------
対象日: 20181201
結果: 20181130
--------------------------------------------
対象日: 20180503
結果: 20180502
--------------------------------------------
対象日: 20180504
結果: 20180502
--------------------------------------------
対象日: 20180505
結果: 20180502

上手く出来たようです。

Time APIを使う

Time APIのLocalDateクラスを使って、上記のプログラムを変えてみました。

LocalDateクラスを使った場合

public class BusinessDay {

    private List<String> publicHolidays;

    public BusinessDay() {
        publicHolidays = Arrays.asList("20180503,20180504,20180505".split(","));
    }

    public LocalDate findBusinessDay(int year, int month, int dayOfMonth) {

        LocalDate localDate = LocalDate.of(year, month, dayOfMonth);

        // 与えられた年月日が営業日である場合、返却
        if(isBusinessDay(localDate)) {
            return localDate;
        }

        // 直近の営業日を求める
        while (true) {
            localDate = localDate.minusDays(1L);
            if (isBusinessDay(localDate)) {
                break;
            }
        }

        return localDate;
    }

    private boolean isBusinessDay(LocalDate localDate) {

        DayOfWeek dayOfWeek = localDate.getDayOfWeek();

        String yyyyMMdd = localDate.format(DateTimeFormatter.BASIC_ISO_DATE);

        return (dayOfWeek != DayOfWeek.SUNDAY && dayOfWeek != DayOfWeek.SATURDAY) && !publicHolidays.contains(yyyyMMdd);
    }


}

いくつか、ポイントを説明します。

前日を計算するのは以下の箇所です。

  • localDate = localDate.minusDays(1L);

minusDays()メソッドを使っています。

引数に指定された分だけ前の日に対応するLocalDateクラスのインスタンスのコピーが返却されます。なので、変数に格納しなおす必要があります。

曜日の取得は以下の箇所です。

  • DayOfWeek dayOfWeek = localDate.getDayOfWeek();

getDayOfWeek()メソッドを使っています。返却されるのはDayOfWeek型オブジェクトです。DayOfWeekクラスは列挙型です。後の判定でDayOfWeekで定義されているSUNDAY、SATURDAYと比較しています。

日付の文字列は以下で取得しています。

  • localDate.format(DateTimeFormatter.BASIC_ISO_DATE)

format()メソッドを使っています。引数にはDateTimeFormatterのインスタンスを指定します。DateTimeFormatterクラスに定義されているBASIC_ISO_DATEを指定することで、年月日が4桁2桁2桁のString型オブジェクトで返却されます。

以下はテストクラスと結果です。

テスト

public class BusinessDayTest {

    public static void main(String[] args) {

        System.out.println("LocalDateクラスを使った場合");

        test(2018, 11, 9);

        test(2018, 11, 10);

        test(2018, 11, 11);

        test(2018, 12, 1);

        test(2018, 5, 3);

        test(2018, 5, 4);

        test(2018, 5, 5);
    }

    private static void test(int year, int month, int dayOfMonth) {
        System.out.println("--------------------------------------------");

        LocalDate localDate = LocalDate.of(year, month, dayOfMonth);

        System.out.println("対象日: " + localDate.format(DateTimeFormatter.BASIC_ISO_DATE));

        BusinessDay businessDay = new BusinessDay();

        System.out.println("結果: "
                + businessDay.findBusinessDay(year, month, dayOfMonth).format(DateTimeFormatter.BASIC_ISO_DATE));

    }

}

結果

LocalDateクラスを使った場合
--------------------------------------------
対象日: 20181109
結果: 20181109
--------------------------------------------
対象日: 20181110
結果: 20181109
--------------------------------------------
対象日: 20181111
結果: 20181109
--------------------------------------------
対象日: 20181201
結果: 20181130
--------------------------------------------
対象日: 20180503
結果: 20180502
--------------------------------------------
対象日: 20180504
結果: 20180502
--------------------------------------------
対象日: 20180505
結果: 20180502

上手く行ったようです。