[Java] [Java] Holiday judgment sample

3 minute read

Overview

I want to run batch only on weekdays, I can easily exclude Saturdays and Sundays with cron, but I can not exclude holidays quickly, so I decided on Java side as a holiday. Feeling that I searched a little I couldn’t find any Java samples, so I will publish them.

Note

  • Transfer holidays are a little complicated so we do not consider them
  • Does not consider past years
  • This year (2020) is special so I do not consider it
  • The formulas for spring equinox and autumn equinox are valid only from 1980 to 2099

Environment

  • Java 1.8
  • SpringBoot 2.2.1.RELEASE

1. Create a holiday definition class

  • 4 patterns of fixed date, happy Monday, spring equinox, autumn equinox day

PublicHoliday.java


package com.tamorieeeen.sample.consts;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * public holiday
 *
 * @author tamorieeeen
 *
 */
@Getter
@AllArgsConstructor
public enum Public Holiday {

    NEWYEAR("New Year's Day", "fixed", 1, 1, 0),
    ADULT("Adults Day", "monday", 1, 0, 2),
    FOUNDATION("Foundation Day", "fixed", 2, 11, 0),
    BIRTHDAY("Emperor's Birthday", "fixed", 2, 23, 0),
    SPRING("Spring equinox", "spring", 3, 0, 0),
    SHOWA("Showa Day", "fixed", 4, 29, 0),
    CONSTITUTION("Constitution Day", "fixed", 5, 3, 0),
    GREEN("Green Day", "fixed", 5, 4, 0),
    CHILDREN("Children's Day", "fixed", 5, 5, 0),
    SEA("Sea Day", "monday", 7, 0, 3),
    MOUNTAIN("Yama no Hi", "fixed", 8, 11, 0),
    AGED ("Respect for the Aged Day", "monday", 9, 0, 3),
    AUTUMN("autumn day", "autumn", 9, 0, 0),
    SPORTS("Sports Day", "monday", 10, 0, 2),
    CULTURE("Culture Day", "fixed", 11, 3, 0),
    THANKSGIVING("Work Thanks Day", "fixed", 11, 23, 0);

    private String name;
    // fixed: fixed date
    // monday: What Monday
    // spring: Equinox day
    // autumn: Autumn equinox day
    private String type;
    // Moon
    private int month;
    // Date type: day
    private int dayOfMonth;
    // What Monday type: Something
    private int weekOfMonth;

    /**
     * Get fixed holidays
     */
    public static List<PublicHoliday> getFixedHoliday() {

        return Stream.of(values())
                .filter(v -> v.type.equals("fixed"))
                .collect(Collectors.toList());
    }

    /**
     * Get Happy Monday
     */
    public static List<PublicHoliday> getHappyMonday() {

        return Stream.of(values())
                .filter(v -> v.type.equals("monday"))
                .collect(Collectors.toList());
    }

    /**
     * Get Equinox Day
     */
    public static PublicHoliday getSpringDay() {

        return Stream.of(values())
                .filter(v -> v.type.equals("spring"))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException());
    }

    /**
     * Get Autumn Equinox Day
     */
    public static PublicHoliday getAutumnDay() {

        return Stream.of(values())
                .filter(v -> v.type.equals("autumn"))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException());
    }
}

2. Create holiday class

  • Use Calendar.DAY_OF_WEEK_IN_MONTH for the first Monday
  • Equinox day: int(20.8431+0.242194*(year-1980)-int((year-1980)/4))
  • Autumn equinox day: int(23.2488+0.242194*(year-1980)-int((year-1980)/4))

HolidayService.java


package com.tamorieeeen.sample.service;

import java.time.LocalDate;
import java.util.Calendar;
import org.springframework.stereotype.Service;

import com.tamorieeeen.sample.consts.PublicHoliday;

/**
 *
 * @author tamorieeeen
 *
 */
@Service
public class HolidayService {

    // 365.242194 days a year, so advance 0.242194 days per year
    private static final double DIFF_OF_YEAR = 0.242194;
    // Calculated from the leap year 1980
    private static final int LEAP_YEAR = 1980;
    // The time of spring equinox in 1980 "20.8431 days"
    private static final double SPRING_DAY_BASE = 20.8431;
    // Time for autumn equinox in 1980 "23.2488 days"
    private static final double AUTMN_DAY_BASE = 23.2488;

    /**
     * Whether it is a holiday
     */
    public boolean isHoliday() {

        LocalDate today = LocalDate.now();

        if (this.isFixedHoliday(today) || this.isHappyMonday(today) ||
                this.isSpringDay(today) || this.isAutmnDay(today)) {

            return true;
        }

        return false;
    }

    /**
     * Whether it is a fixed holiday
     */
    private boolean isFixedHoliday(LocalDate today) {

        return PublicHoliday.getFixedHoliday()
                .stream()
                .map(h -> LocalDate.of(
                        today.getYear(), h.getMonth(), h.getDayOfMonth()))
                .anyMatch(h -> h.isEqual(today));
    }

    /**
     * Whether it's Happy Monday
     */
    private boolean isHappyMonday(LocalDate today) {

        return PublicHoliday.getHappyMonday()
                .stream()
                .map(h -> LocalDate.of(
                        today.getYear(), h.getMonth(),
                        this.getDayOfMonth(h.getMonth(), h.getWeekOfMonth())))
                .anyMatch(h -> h.isEqual(today));
    }

    /**
     * Get Happy Monday Date
     */
    private int getDayOfMonth(int month, int weekOfMonth) {

        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.MONTH, month-1);
        cal.set(Calendar.DAY_OF_WEEK, 2);
        cal.set(Calendar.DAY_OF_WEEK_IN_MONTH, weekOfMonth);

        return cal.get(Calendar.DAY_OF_MONTH);
    }

    /**
     * Whether it is spring equinox
     */
    private boolean isSpringDay(LocalDate today) {

        int dayOfMonth =
                this.getMovedDays(today.getYear(), SPRING_DAY_BASE)
                -this.getLeapYearDays(today.getYear());LocalDate springDay = LocalDate.of(
                today.getYear(),
                PublicHoliday.getSpringDay().getMonth(),
                dayOfMonth);

        return today.isEqual(springDay);
    }

    /**
     * 秋分の日かどうか
     */
    private boolean isAutmnDay(LocalDate today) {

        int dayOfMonth =
                this.getMovedDays(today.getYear(), AUTMN_DAY_BASE)
                - this.getLeapYearDays(today.getYear());
        LocalDate autmnDay = LocalDate.of(
                today.getYear(),
                PublicHoliday.getAutumnDay().getMonth(),
                dayOfMonth);

        return today.isEqual(autmnDay);
    }

    /**
     * 進んでる分を取得
     */
    private int getMovedDays(int year, double baseDay) {

        return (int) Math.floor(baseDay + this.getDiffDays(year));
    }

    /**
     * 1980年からの差分を取得
     */
    private double getDiffDays(int year) {

        return DIFF_OF_YEAR * (year - LEAP_YEAR);
    }

    /**
     * うるう年の分を取得
     */
    private int getLeapYearDays(int year) {

        return (int) Math.floor((year - LEAP_YEAR) / 4.0);
    }
}

3. 呼び出しclassを作成

  • holidayService.isHoliday()がtrueなら祝日、falseなら平日

SampleController.java


package com.tamorieeeen.sample.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import com.tamorieeeen.sample.service.HolidayService;

import lombok.extern.slf4j.Slf4j;

/**
 *
 * @author tamorieeeen
 *
 */
@Slf4j
@Controller
public class SampleController {

    @Autowired
    private HolidayService holidayService;

    @GetMapping("/sample")
    public String top() {

        if (holidayService.isHoliday()) {
            log.info("today is holiday.");
        } else {
            log.info("today is weekday.");
        }

        return "sample";
    }
}

感想

春分の日と秋分の日の計算がめんどくさい!

参考

内閣府「国民の祝日」について 「今月の第3月曜日」の日付を求める PHPで春分の日、秋分の日を計算