🌸

【Go】春分の日と秋分の日をロジックで算出する

2022/02/19に公開

説明

以下にある通り、正式には前年の2月1日にならないと確定されないようです。
質問3-1)何年後かの春分の日・秋分の日はわかるの? | 国立天文台(NAOJ)

ただ、計算して予想することは可能みたいです。ここに書いてるロジックをGoで書きました。
春分・秋分の日 - 仕事に役立つエクセル実践問題集

実装

date.go
package equinox

import (
	"math"
	"time"
)

const (
	JSTOffset = 9 * 60 * 60
	asiaTokyo = "Asia/Tokyo"
)

var locationJST = time.FixedZone(asiaTokyo, JSTOffset)

// VernalEquinoxDate は春分の日を算出する
func VernalEquinoxDate(year int) time.Time {
	return time.Date(year, time.March, calcVernalEquinoxDate(year), 0, 0, 0, 0, locationJST)
}

// AutumnalEquinoxDate は秋分の日を算出する
func AutumnalEquinoxDate(year int) time.Time {
	return time.Date(year, time.September, calcAutumnalEquinoxDate(year), 0, 0, 0, 0, locationJST)
}

func calcVernalEquinoxDate(year int) int {
	val := calcEquinoxBase(year)

	switch {
	case 1851 <= year && year <= 1899:
		val += 19.8277
	case 1900 <= year && year <= 1979:
		val += 20.8357
	case 1980 <= year && year <= 2099:
		val += 20.8431
	case 2100 <= year && year <= 2150:
		val += 21.8510
	}

	return int(math.Floor(val))
}

func calcAutumnalEquinoxDate(year int) int {
	val := calcEquinoxBase(year)

	switch {
	case 1851 <= year && year <= 1899:
		val += 22.2588
	case 1900 <= year && year <= 1979:
		val += 23.2588
	case 1980 <= year && year <= 2099:
		val += 23.2488
	case 2100 <= year && year <= 2150:
		val += 24.2488
	}

	return int(math.Floor(val))
}

func calcEquinoxBase(year int) float64 {
	return 0.242194*float64(year-1980) - math.Floor(float64(year-1980)/4.0)
}

テスト

date_test.go
package equinox

import (
	"strconv"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func d(y, m, d int) time.Time {
	return time.Date(y, time.Month(m), d, 0, 0, 0, 0, locationJST)
}

func TestVernalEquinoxDate(t *testing.T) {
	tests := []struct {
		year   int
		output time.Time
	}{
		{2015, d(2015, 3, 21)},
		{2016, d(2016, 3, 20)},
		{2017, d(2017, 3, 20)},
		{2018, d(2018, 3, 21)},
		{2019, d(2019, 3, 21)},
		{2020, d(2020, 3, 20)},
		{2021, d(2021, 3, 20)},
		{2022, d(2022, 3, 21)},
		{2023, d(2023, 3, 21)},
		{2024, d(2024, 3, 20)},
		{2025, d(2025, 3, 20)},
		{2026, d(2026, 3, 20)},
		{2027, d(2027, 3, 21)},
		{2028, d(2028, 3, 20)},
		{2029, d(2029, 3, 20)},
		{2030, d(2030, 3, 20)},
	}

	for _, test := range tests {
		t.Run(strconv.Itoa(test.year), func(t *testing.T) {
			assert.Equal(t, test.output, VernalEquinoxDate(test.year))
		})
	}
}

func TestAutumnalEquinoxDate(t *testing.T) {
	tests := []struct {
		year   int
		output time.Time
	}{
		{2015, d(2015, 9, 23)},
		{2016, d(2016, 9, 22)},
		{2017, d(2017, 9, 23)},
		{2018, d(2018, 9, 23)},
		{2019, d(2019, 9, 23)},
		{2020, d(2020, 9, 22)},
		{2021, d(2021, 9, 23)},
		{2022, d(2022, 9, 23)},
		{2023, d(2023, 9, 23)},
		{2024, d(2024, 9, 22)},
		{2025, d(2025, 9, 23)},
		{2026, d(2026, 9, 23)},
		{2027, d(2027, 9, 23)},
		{2028, d(2028, 9, 22)},
		{2029, d(2029, 9, 23)},
		{2030, d(2030, 9, 23)},
	}

	for _, test := range tests {
		t.Run(strconv.Itoa(test.year), func(t *testing.T) {
			assert.Equal(t, test.output, AutumnalEquinoxDate(test.year))
		})
	}
}

注意点

ロジックを見れば分かる通り、1850年以前と2151年以降には対応していません。1850年以前のシステムを開発する皆さんは十分気を付けましょう!

Discussion