Excel Access 春分秋分(訂正)夏至冬至の日が何日かを求める関数 1900年以前の曜日　閏年を求める関数

#秋分の日が間違っていました
2018年8月11日以前に取得した方は、書き換えをお願いします。

#春分・秋分・夏至・冬至を求める関数
##基本原理
ネットでは春分・秋分を求める関数があり、1980年を基準年として、協定世界時の春分・秋分・夏至・冬至の時間から9時間足したものを基準にして
さらにうるう年を考慮したもの。
しかし春分・秋分・夏至・冬至は日付だけでなく協定世界時で時刻まで決まっているというのは驚きです。
##UTCとGMTは同義
協定世界時(UTC)はGMTと一般的には同義[http://citizen.jp/support/useful/gmt_utc.html](http://citizen.jp/support/useful/gmt_utc.html)
しかしながら地球の自転はこれだけ精密に観測すると変化していることがわかっている。一般的には次第に遅くなっているという。

##関数の動作原理
1980年以降2099年(2100年というサイトもあり)の4桁の西暦または日付を入れると、その年の日付が返ってくるというものです。
ネットでは春分・秋分は休日のため、VBA も組まれているが、これとまったく同じ理屈で、夏至と冬至も求めることができます。
#基準となる日時
[【Excel Tips】春分の日や秋分の日を求めるには？-Enterprizine](https://enterprisezine.jp/iti/detail/865)
>1980年3月20日20時14分4秒が春分の瞬間の時刻であり、同年9月23日5時58分16秒が秋分の瞬間の時刻となります。日付より小さい時刻の部分をシリアル値で表すと、春分の日の場合は0.8431、秋分の日の場合は0.2488になります。

[夏至・冬至点通過日時の計算（精度：±2分程度）](http://park12.wakwak.com/~maki/solstice.htm)
>1980/6/21　 14:47　　1980/12/22　 01:56


##1日の長さは平等ではなかった
　このことから昔の人の1日の方が長かったということがわかります。
　人類はみな過去から未来まで時間が平等にある、というのは厳密には正しくないということです。確かに昔の方が1日が長くてのんびりしていたのは間違いない。

##春分秋分から応用
　実はこの方法を夏至と冬至にも使えます。
いずれも西暦を入れると、日付が返ってくるという仕組みです
閏年はNikkeiBPのサイトのものを使っていますので、過去にさかのぼるのは問題がありますが、1980年以前は扱わないのでこれでOKです。

```vb

Function fnDateOfSpringEquinox1981_2099(iyear) As Date
'https://oshiete.goo.ne.jp/qa/1454974.html
'1981年から2099年の春分の日を返します
'春分の日は1980年3月20日
'日付が入ってきてもその年の春分の日を返します
’それ以外は小数点以下を切り捨てます
Debug.Print TypeName(iyear)
Dim i As Long
If TypeName(iyear) = "Date" Or TypeName(iyear) = "Integer" Or TypeName(iyear) = "Long" Or TypeName(iyear) = "Double" Then
Select Case TypeName(iyear)
Case Is = "Date"
i = Year(iyear)
Case Is = "Long"
i = iyear
Case Is = "Double"
i = CLng(iyear)
Case Is = "Integer"
i = CLng(iyear)
End Select
End If
If i > 1980 And i <= 2099 Then
Debug.Print Int(20.8431 + 0.242194 * (i - 1980) - Int((i - 1980) / 4))
'20.8431 整数部20は1980年3月の春分が20日
'0.8431 何時か世界時で11時05分 精度+-15分 UTC+9 20：05 +-15 がJTCの春分の時刻
' 0.242194 は 「 昔使われていた1年の日数 365.241294日 」 の小数点以下です。　要するに 毎年この端数（はすう）のぶん、日数が遅れてるのです
'カレンダーの方でも日数が遅れるのに合わせて「カレンダーを遅く」してますよね、4年に1回ずつ1日を加えてます。　そのぶん、上記の計算から「遅くした分」を引いてるのが　int((Y-1980)/4)
fnDateOfSpringEquinox1981_2099 = i & "/" & 3 & "/" & Int(20.8431 + 0.242194 * (i - 1980) - Int((i - 1980) / 4)): Exit Function
End If
End Function

Function fnDateOfAutumnEquinox1981_2099(iyear) As Date
'1981年から2099年の秋分の日を求める
'2018/8/10修正 係数が違うので修正 外Selectでデータ型を変換していた部分を修正(日付が入った時がおかしい)。演算誤差修正
'Debug.Print TypeName(iyear)
Dim i As Long
If TypeName(iyear) = "Date" Or TypeName(iyear) = "Integer" Or TypeName(iyear) = "Long" Or TypeName(iyear) = "Double" Then
i = CLng(iyear)
ElseIf TypeName(iyear) = "Date" Then
i = CLng(Year(iyear))
Else
fnDateOfAutumnEquinox1980_2099 = 0
Exit Function
End If
If i > 1980 And i <= 2099 Then
Debug.Print Int(23.2488@ + 0.242194! * (i - 1980) - Int((i - 1980) / 4)):
fnDateOfAutumnEquinox1980_2099 = i & "/" & 9 & "/" & Int(23.2488@ + 0.242194! * (i - 1980) - Int((i - 1980) / 4)): Exit Function
End If
End Function

Function fnDateOfSummerSolstice1980_2099(iyear) As Date
Debug.Print TypeName(iyear)
'1980-2099年までの夏至を求める
'1980年を基準にして求める手法。厳密には決まらないが、近似値は求められる（1年の長さも1日の長さも変動するため）
' https://enterprisezine.jp/iti/detail/865
Const Japanes19800621TimeSeria1447 As Date = #2:47:00 PM#
Const Japanes19801221TimeSeria0156 = 0.080556
Dim i As Long

If TypeName(iyear) = "Date" Or TypeName(iyear) = "Integer" Or TypeName(iyear) = "Long" Or TypeName(iyear) = "Double" Then

Select Case TypeName(iyear)
Case Is = "Date"
i = Year(iyear)
Case Is = "Long"
i = iyear
Case Is = "Double"
i = CLng(iyear)
Case Is = "Integer"
i = CLng(iyear)
End Select
End If
If i >= 1980 And i <= 2099 Then
'Debug.Print Int(21.615972 + 0.242194 * (i - 1980) - Int((i - 1980) / 4)):
fnDateOfSummerSolstice1980_2099 = i & "/" & 6 & "/" & Int(21.615972 + 0.242194 * (i - 1980) - Int((i - 1980) / 4)): Exit Function
End If
End Function

Function fnDateOfWinterSolstice1981_2099(iyear) As Date
'1981-2099年までの冬至を求める
'1980年を基準にして求める手法。厳密には決まらないが、近似値は求められる（1年の長さも1日の長さも変動するため）
' https://enterprisezine.jp/iti/detail/865
'Debug.Print TypeName(iyear)
Const Japanes19800621TimeSeria1447 = #2:47:00 PM#
Const Japanes19801221TimeSeria0156 = 0.080556
Dim i As Long
If TypeName(iyear) = "Integer" Or TypeName(iyear) = "Long" Or TypeName(iyear) = "Double" Then
i = CLng(iyear)
ElseIf TypeName(iyear) = "Date" Then
i = CLng(Year(iyear))
Else
fnDateOfWinterSolstice1981_2099 = 0
Exit Function
End If
If i >= 1980 And i <= 2099 Then
'Debug.Print Int(21.080556 + 0.242194 * (i - 1980) - Int((i - 1980) / 4)):
fnDateOfWinterSolstice1981_2099 = i & "/" & 12 & "/" & Int(21.080556 + 0.242194 * (i - 1980) - Int((i - 1980) / 4)): Exit Function
End If
End Function

Function fnIsLeepYear(iyear) As Boolean
'http://tech.nikkeibp.co.jp/it/atcl/column/15/090100207/090100044/
Debug.Print TypeName(iyear)
Dim i As Long
Dim Dt As Date
Dim preDt As Date
If TypeName(iyear) = "Date" Or TypeName(iyear) = "Integer" Or TypeName(iyear) = "Long" Or TypeName(iyear) = "Double" Then
Select Case TypeName(iyear)
Case Is = "Date"
i = Year(iyear)
Case Is = "Long"
i = iyear
Case Is = "Double"
i = CLng(iyear)
Case Is = "Integer"
i = CLng(iyear)
End Select
End If
Dt = DateSerial(iyear, 3, 1) - 1
preDt = Day(Dt - 1)
If Day(Dt) = 28 Then fnIsLeepYear = False Else fnIsLeepYear = True
End Function
```
#1900年以前の曜日と閏年を求める関数
##精密な閏年かどうか判定する関数
4桁の西暦を入れると、うるう年の法則に従って閏年かどうかを判定します。閏年の場合Trueを返します。Excelの内部カレンダーに従わないので、正確に判定します。

```vb

'うるう年か判定する関数を用意します。
'これで偽物のうるう日があればエラーで終了します。
'次に、西暦が1900年以降になるまで400を足します。
'なぜなら暦は400年で一周するからです。
'なので1900年よりあと(1900年は含まない)に持ち込んで曜日'を返します。
'なぜなら1900年はうるう年ですが、Excelは1900年のうるう'年は判定しないのです。つまり1900年3月1日以前の暦が正しく'ないので1900年より後にする必要があるわけです。
'正確な閏年を判定する関数
Function fnIsLeepYear(iYear) As Boolean
'http://tech.nikkeibp.co.jp/it/atcl/column/15/090100207/090100044/
'Debug.Print TypeName(iyear)
Dim i As Long
Dim dt As Date
Dim preDt As Date
If TypeName(iYear) = "Date" Or TypeName(iYear) = "Integer" Or TypeName(iYear) = "Long" Or TypeName(iYear) = "Double" Then
Select Case TypeName(iYear)
Case Is = "Date"
i = Year(iYear)
Case Is = "Long"
i = iYear
Case Is = "Double"
i = CLng(iYear)
Case Is = "Integer"
i = CLng(iYear)
End Select
End If
dt = DateSerial(iYear, 3, 1) - 1
preDt = Day(dt - 1)
If iYear = 1900 Then fnIsLeepYear = False: Exit Function
If iYear > 1900 Then
If Day(dt) = 28 Then
fnIsLeepYear = False
Exit Function
Else
fnIsLeepYear = True
Exit Function
End If
Else
If iYear Mod 4 = 0 Then
If iYear Mod 100 = 0 Then
If iYear Mod 400 = 0 Then
fnIsLeepYear = True
Exit Function
Else
fnIsLeepYear = False
Exit Function
End If
Else
fnIsLeepYear = True
Exit Function
End If 'If iyear Mod 100 = 0 Then
Else
fnIsLeepYear = False
End If 'If iyear Mod 4 = 0
End If 'iyear <= 1899
End Function
```
##曜日を判定する関数
これもすごいなと思うのですが、暦は400年周期なのだそうです。
なので1900年以前は1900年を超えるまで400を足してずらし、それで得られた曜日を返せばよいのだそうです。
なぜ1900年を超えるかというと、1900年のExcelの内部処理が正しくないからです。

```vb
Function SuperWeekDay(iYear, iMonth, iday)
Dim icnt As Long, iYear400 As Long
Dim dt As Date
If iMonth = 2 And iday = 29 Then
If fnIsLeepYear(iYear) = False Then Debug.Print "この年はうるう年ではありません": Exit Function
End If
icnt = 1
Do
iYear400 = 400 * icnt + iYear
If iYear400 > 1900 Then Exit Do Else icnt = icnt + 1
Loop
dt = CDate(iMonth & "/" & iday & "/" & iYear400)
Select Case Weekday(dt)
Case Is = 1
SuperWeekDay = "vbSunday": Exit Function
Case Is = 2
SuperWeekDay = "vbMonday": Exit Function
Case Is = 3
SuperWeekDay = "vbTuesday": Exit Function
Case Is = 4
SuperWeekDay = "vbWednesday": Exit Function
Case Is = 5
SuperWeekDay = "vbThursday": Exit Function
Case Is = 6
SuperWeekDay = "vbFriday": Exit Function
Case Is = 7
SuperWeekDay = "vbSaturday": Exit Function
End Select
SuperWeekDay = Weekday(dt)
End Function
```
#振替休日判定(2007年以降)

```vb
Function fnIsFurikaeAfter2007(dt As Date)
Dim bl As Boolean
bl = False
'有効期間が2007年より後のため、そうでなければはねる
If Year(dt) < 2007 Then Exit Function
'有効期間が2008年以降なら振替休日を判定する日かを検討する
If Month(dt) = 1 And Day(dt) = 1 Then bl = True
If Month(dt) = 2 And Day(dt) = 11 Then bl = True
If Year(dt) >= 2020 And Month(dt) = 2 And Day(dt) = 23 Then bl = True
If Month(dt) = 5 Then
If Day(dt) = 3 Or Day(dt) = 5 Then
If Weekday(dt) = vbSunday Then
fnIsFurikaeAfter2007 = True
Exit Function
Else
fnIsFurikaeAfter2007 = False
End If
If Year(dt) < 2000 And Month(dt) = 1 And Day(dt) = 15 Then bl = True
If Year(dt) < 2000 And Month(dt) = 10 And Day(dt) = 10 Then bl = True
If Month(dt) = 3 And Day(dt) = 20 Then bl = True
If Month(dt) = 3 And Day(dt) = 21 Then bl = Tr
If Month(dt) = 4 And Day(dt) = 29 Then bl = True
If Month(dt) = 8 And Day(dt) = 11 Then bl = True
If Month(dt) = 9 And Day(dt) = 22 Then bl = True
If Month(dt) = 9 And Day(dt) = 23 Then bl = True
If Month(dt) = 11 And Day(dt) = 3 Then bl = True
If Month(dt) = 11 And Day(dt) = 23 Then bl = True
If Year(dt) < 2020 And Month(dt) = 12 And Day(dt) = 23 Then bl = True
'振替休日を検討する祝祭日なでなければFalseを返す
If bl = False Then fnIsFurikae = False: Exit Function
'振替休日を検討する祝祭日の場合、日曜ならTrueを返す
If bl = True And Weekday(dt) = vbSunday Then
fnIsFurikaeAfter2007 = True
Exit Function
Else
fnIsFurikaeAfter2007 = False
Exit Function
End If
End Function
```

#Microsoftは歴史修正主義者であることを採録
1900年よりあとであれば、fnIsLeepYearは日経BPのサイトのものを使ってもよいです。これはExcel自体のカレンダー機能を使うものです。
ただし[日経BP](tech.nikkeibp.co.jp/it/atcl/column/15/090100207/090100044)のサイトにはこの1900年問題は書いていないので問題です。
なお、公式でもマクロを使わない方法が紹介されており、これで組んでみました。間違っていたらすいません。
[うるう年を判定する方法 ](https://support.microsoft.com/ja-jp/help/214019/method-to-determine-whether-a-year-is-a-leap-year)
でここにさらっとこんなことが書いてあるわけです。


>Excel 95 までのバージョンの Excel が日付データとして扱えるのは 1900 〜 2078 年であるため、うるう年に関する 100 および 400 の除外規則が適用される年は 1900 年だけです。ただし *Excel では、他のプログラムとの互換性のため、1900 年をうるう年として扱います。*
しかし公式サイトは以前ウソをついていました。
[http://suwatdee.cocolog-nifty.com/blog/2008/02/excel19002029_13e8.html]( http://suwatdee.cocolog-nifty.com/blog/2008/02/excel19002029_13e8.html)

>【WEEKDAY関数で1900年3月1日より前の正しい曜日が返らない】
http://support.microsoft.com/kb/106339/ja

このように書かれている。
『1900年は事実に反して閏(うるう)年として解釈されています』
Excelの日付システムが誤りだということはMicrosoftの公式見解。
しかし、その後に言い訳が書いてある。
『Excelの日付システムは、他の表計算ソフトとの互換性を
　完全に満たすように定義されています。』

つまり、「間違いと知っていたが別の表計算ソフトに合わせた」。
ここでの「別の表計算ソフト」とは何か。Lotus1-2-3である。

機械翻訳版なので読みにくいがこんな説明ページもある。
【Excel2000は、1900年が閏年なのを前提と間違ってします。】
http://support.microsoft.com/kb/214326/ja
そこには「Lotus 3」と「Microsoft Multiplan」が登場する。
「Lotus 3」は英語版では「Lotus1-2-3」と書いてある。
MultiplanとはExcel以前のMicrosoft社の表計算ソフトである。

翻訳前の英語ページに基づいて、この説明を要約しよう。
>Lotus1-2-3が登場した時、1900年をうるう年と見なしていました。
そうしたほうがプログラムが楽だったからです。
誤りであっても現実にはそれほど問題は生じませんでした。
MicrosoftがMultiplanそしてExcelを出した時も、1900年をうるう年と
扱いました。そうすることによってLotus1-2-3との互換性が取れました。
バージョンアップ時にその問題を解決することも可能でしたが、
旧システムとの互換性を優先したため、現状でも解決していません。
>----

>実は、この記述に嘘がある。
Multiplanが登場したのは1982年で、Lotusは1983年。
MultiplanがLotusの日付システムに合わせたことはありえない。
合わせたのならVisiCalcという世界初の表計算ソフト(1979)だろう。

>Excelは、Lotus1-2-3に合わせたのか、Multiplanに合わせたのか。
どちらにせよ、MicrosoftではLotus社のせいにして責任逃れ。

>この仕様はしかたない面もあるが、VBAと同じ仕様にもできるはずだ。
事実、OpenOffice.orgのCalcではVBAと同じ処理をしているのに。
やはり責任逃れ。
1900年問題はMicrosoft社の体質としか思えない。

>====

>別のページにも嘘を見つけた。
http://office.microsoft.com/ja-jp/excel/HP100541411041.aspx

>ここに以下のように書いてある。

>> 1900年1月1日はシリアル値1となり、2008年1月1日はシリアル値39448と
> なります。これは、1900年1月1日から39,448日が経過しているためです。

>これは、私が授業で言っている嘘と同じ。
>嘘だけど許してあげよう。

>これを発見したのは、実は別のことを調べたから。
>それは、西暦を2桁入力したときの解釈のしかた。
>同じページの下のほうに書いてある。

>> 00から29までの年 2000年から2029年になります。
>> 「19/5/28」と入力した場合は、2019年5月28日と解釈されます。
>> 30から99までの年 1930年から1999年になります。
>> 「98/5/28」と入力した場合は、1998年5月28日と解釈されます。

>Excel2007でもそうなっているらしい。
>2000年の時に、Excel97以前のバージョンで問題になった。
>興味があれば「Excel 2000年問題」で検索してほしい。

>とすると、2029年問題も生ずるわけである。
>その何年前かのバージョンアップで変更されるのだろう。

>ユーザーとしては、西暦の2桁表示は避けるべきだ。
>今のうちに2029年問題を解消しておいたほうがいい。

====

>1900年問題にせよ、2029年問題にせよ、『互換性』の問題。
>この問題の根本は以下のようにまとめられよう。

>《現実への適合性と過去の資産との互換性のどちらを望むか》

>現実は時代に応じて変化するから、この問題はまた起こる。
>「コンピュータ」というものを考えるとき、
>このことは常に意識しておく必要がある。

====

>Excelの日付連番を正確に教えるのはどうしたらよいのか。
>それをこの文章の締めくくりにしよう。

>| Excelの日付は1900年3月1日を61とした連番です。
>| 1900年3月1日以前の日付がありうる場合、日付形式は使えません。
>| それがなぜなのか知りたい人は「Excel 1900年」でWeb検索してみよう。

>このように教えないと正確とは言えない。
>しかし、現実的ではない。
>だから、今後も以下のように教えるだろう。

>| 日付連番は1900年1月1日から何日目かを表わします。
>| じつは正確ではありませんが…

>1900年問題や2029年問題は興味深いがトホホでもある。
>Excelを教える限り続くのか。

>2029年までExcelは生き残るのだろうか。
>そもそも2029年までMicrosoftは生き残るのだろうか。
>そもそも2029年まで私は生き残るのだろうか。
>そもそも2029年まで人類は生き残るのだろうか。

すわッディは2015年12月から2017年4月まで記事がなく、2017年10月以降の更新はありません。
つまりそういうことかもしれません。
なのでこのことを書き継いでいくべきだと思い、引用しました。