Edited at

Goで今月の第4土曜日を計算する

PHPerだったので、これで済んでいた計算をGoに置き換えようとして、ぱっとライブラリが無さそうだったので真面目に実装した。

<?php

echo date_create("fourth saturday of this month")->format("c");

使う素材は公式のtimeパッケージのみとする。


考えたアルゴリズム


  1. 今月の1日を取得する

  2. 1日の曜日を調べて、土曜日までの日数を出す

  3. 最初の土曜日を計算

  4. 7日 * 3 = 21日うしろが第4土曜日のはず

package main

import (
"errors"
"fmt"
"time"
)

func main() {
jst := time.FixedZone("Asia/Tokyo", 9*60*60)
fmt.Println(NthSaturday(4, jst))
}

func NthSaturday(n int, loc *time.Location) (time.Time, error) {
now := time.Now().In(loc)

if n <= 0 || 4 < n {
return now, errors.New("there is no 5th saturday.")
}

year, month, _ := now.Date()
firstDay := time.Date(year, month, 1, 0, 0, 0, 0, loc)
firstSaturday := int(time.Saturday-firstDay.Weekday()) + 1
nthSaturday := firstSaturday + (n-1)*7

return time.Date(year, month, nthSaturday, 0, 0, 0, 0, loc), nil
}

汎用化して、任意の曜日で計算できるようにすると、こんな感じかな。

package main

import (
"errors"
"fmt"
"time"
)

func main() {
jst := time.FixedZone("Asia/Tokyo", 9*60*60)
fmt.Println(NthWeekday(4, time.Monday, jst))
}

func NthWeekday(n int, wd time.Weekday, loc *time.Location) (time.Time, error) {
now := time.Now().In(loc)

if n <= 0 || 4 < n {
return now, errors.New("there is no 5th weekday.")
}

year, month, _ := now.Date()
firstDay := time.Date(year, month, 1, 0, 0, 0, 0, loc)
first := int(wd-firstDay.Weekday()) + 1
if first <= 0 {
first += 7
}
nth := first + (n-1)*7

return time.Date(year, month, nth, 0, 0, 0, 0, loc), nil
}

第5曜日を計算しようと思ったら、月によっては存在しない可能性があるので、エラーチェックがもう少し必要になる。