3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

民法第143条が定める暦による期間の計算を実装してみる

Last updated at Posted at 2021-12-20

KINTO Technologies Advent Calendar 2021 - Qiita の20日目の記事です。

概要

当社が企画/開発を行う、トヨタ車のサブスク「KINTO」は月々定額のサブスクリプションサービスとなり、1ヶ月単位のご利用料の期間は民法第143条が定める「暦による期間」に従って計算されています。

この「暦による期間」の計算、ちょっと複雑であり、なんかコード書いてみたいなという気分になったため書いてみる次第です。

民法第143条が定める「暦による期間」の計算とは?

WIKIBOOKSによると、民法第143条の条文は以下のとおりです。

第143条の条文

  • 週、月又は年によって期間を定めたときは、その期間は、暦に従って計算する。
  • 週、月又は年の初めから期間を起算しないときは、その期間は、最後の週、月又は年においてその起算日に応当する日の前日に満了する。ただし、月又は年によって期間を定めた場合において、最後の月に応当する日がないときは、その月の末日に満了する。

1行目はいいとして、2行目については文字数が多すぎて頭に入ってこないですね。

条文を理解する

週、月又は年の初めから期間を起算しないときは、その期間は、最後の週、月又は年においてその起算日に応当する日の前日に満了する。

月でいうと起算日が11/21だった場合、応当する日は12/21になるってことですかね。「応当する日の前日に満了する」とあるので満了日は21 - 1 = 20 -> 12/20 ということになりそうです。単純っすね。

しかし起算日が月の初日だった場合、満了日は応当する日 - 1日 = 月末になるため、月によって30日になったり31日になったりと日付が変動しそうです。

ただし、月又は年によって期間を定めた場合において、最後の月に応当する日がないときは、その月の末日に満了する。

月でいうと起算日が3/31だった場合、応当する日は4/31日か... 思いっきり存在しない日付になりましたね。「最後の月に応当する日がないときは、その月の末日に満了」なので満了日は4月の末日である4/30になりそうです。うるう年があることなどを考えるとちょっとめんどくさそうです。

ここまででなんとなく内容は理解できたので実装してみましょー!

実装

以下のような内容で実装していきたいと思います。今回はJava11を使います。

  • 与えられた起算日、満了日から、暦に従って計算された1ヶ月単位の期間の一覧を作成する
  • 1ヶ月単位の期間については起算日から見て何ヶ月目か・期間開始日・期間終了日で構成する

こんな感じですかね。

  • 起算日:2021/3/15
  • 満了日:2022/3/14
起算日から見て何ヶ月目か 期間開始日 期間終了日
1 2021/3/15 2021/4/14
2 2021/4/15 2021/5/14
... ... ...
11 2022/1/15 2022/2/14
12 2022/2/15 2022/3/14

期間計算のベース部分

最後の月に応当する日があるケース・ないケースなどがあるため、期間終了日はケースに応じた計算が必要そうです。しかし、期間開始日については以下とすることで、都度複雑な計算をしなくてよさそうです。

  • 起算日から見て1ヶ月目(初月の場合)は起算日、それ以外の場合は前月の期間終了日 + 1日

コードにすると以下の通りです。期間終了日はケースに応じた計算をしたいため、計算結果を外から関数で渡すことにしました。

private List<MonthlyPeriod> calcMonthlyPeriods(UnaryOperator<LocalDate> endDateFunc) {

  List<MonthlyPeriod> calculatedPeriods = new ArrayList<>();

  for (int i = 0; i < numberOfMonths; i++) {

    var startDate = i == 0 ? this.from : calculatedPeriods.get(i - 1).getTo().plusDays(1);
    var endDate = endDateFunc.apply(startDate);

    calculatedPeriods.add(
        new MonthlyPeriod(
            i + 1,
            startDate,
            endDate
        )
    );
  }

  return calculatedPeriods;
}

期間終了日のケースに応じた計算

期間終了日の計算について、以下の3通りに分けてみました

  1. 起算日が月の初日
    • 期間終了日は必ず期間開始日がある月の末日
  2. 起算日に応当する日が最後の月にない
    • 期間終了日は最後の月の末日
  3. 起算日に応当する日が最後の月にある
    • 期間終了日は応当する日 - 1日
private List<MonthlyPeriod> calcMonthlyPeriods() {

  if (from.getDayOfMonth() == 1) {
    // 1.
    return calcMonthlyPeriods(
        monthlyPeriodFrom -> monthlyPeriodFrom.withDayOfMonth(monthlyPeriodFrom.lengthOfMonth())
    );

  } else if (from.getDayOfMonth() >= 29) {
    // 2. 
    return calcMonthlyPeriods(
        monthlyPeriodFrom -> {
          
          var lastMonth = monthlyPeriodFrom.getDayOfMonth() == 1
              ? monthlyPeriodFrom
              : monthlyPeriodFrom.plusMonths(1);
          return lastMonth
              .withDayOfMonth(Math.min(to.getDayOfMonth(), lastMonth.lengthOfMonth()));
        }
    );
  }
  // 3.
  return calcMonthlyPeriods(
      monthlyPeriodFrom -> monthlyPeriodFrom.plusMonths(1).minusDays(1)
  );
}

1.3.について特記することはないですが、2. については期間開始日が月の初日に変動してしまうケースがあるため、それを考慮した内容にしています。

起算日から見て何ヶ月目か 期間開始日 期間終了日 備考
1 2021/1/31 2021/2/28 応当する日がないため、月の末日が期間終了日
2 2021/3/1 2021/3/30 前月の期間終了日 + 1日から開始のため、月の初日になる
3 2021/3/31 2021/4/30
... ... ...

実行結果

条件満たせてそうです

起算日が月の初日.sh
2021-01-01 2021-06-30

> Task :Main.main()
1
2021-01-01
2021-01-31
2
2021-02-01
2021-02-28
3
2021-03-01
2021-03-31
4
2021-04-01
2021-04-30
5
2021-05-01
2021-05-31
6
2021-06-01
2021-06-30
起算日に応当する日が最後の月にない.sh
2021-01-31 2021-07-30

> Task :Main.main()
1
2021-01-31
2021-02-28
2
2021-03-01
2021-03-30
3
2021-03-31
2021-04-30
4
2021-05-01
2021-05-30
5
2021-05-31
2021-06-30
6
2021-07-01
2021-07-30
起算日に応当する日が最後の月にある.sh
2021-01-15 2021-07-14

> Task :Main.main()
1
2021-01-15
2021-02-14
2
2021-02-15
2021-03-14
3
2021-03-15
2021-04-14
4
2021-04-15
2021-05-14
5
2021-05-15
2021-06-14
6
2021-06-15
2021-07-14

まとめ

当初想定していたより、考慮しないといけないケースが多く、勉強になりました。

今回実装したソースコードはこちらにあります。

さいごに

当社では、トヨタ車のサブスク「KINTO」等の企画/開発を行っており、エンジニアを募集中です。
KINTO Technologies コーポレートサイト

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?