Excel ワークシート：IFを使わず論理値の数値を用いて日付から日本の年度および年度末を求める数式とVBAにおける劇的な効果

#今回の要旨
<Ul>
<li>論理値のTrue,Falseは1，0の値を持っている。なおVBAでは-1,0でTrueはワークシートと符号が異なっている。
<li>IF文を使わなくても論理値の値を利用して計算式を書けば望む結果を得ることができる。IFより式が簡単になる。今回の場合、ワークシートではEDATEを使うと簡便になる。
<li>また、VBAではEDATEが使えないのでDATESerialを使うが、この時もIF文を使わないので、簡便なコードになる。また-1が覚えきれない場合はABSを使って常に1を得るとよい。
<li>この手法は日付から年度、または年度末の日を求めるときに特に効果的である
<li>さらに日付、年数、時間の条件のある計算においてはセル内の式簡略化、VBAのコードの簡略化に使える
<li>論理値が得られるIsError系でも何らかの使用ができると考えられる。
</Ul>
#目標
A1に2019/2/1と入力されている。Excelから日付として認識され、表示形式も日付となっている。
日本の年度Year degree YDは4月から3月までである。
A.すると2019/2/1は2018年度である。
この結果をA2に示す式は何か。
B.すると2018年度末は2019年3月31日である。
この結果をA2に示す式は何か。

##A.年度を求める
###1.エクセルで年度を表示させる方法｜EDATE関数とYEAR関数
https://h1r0-style.net/excelvba/howto-display-fiscal-year
=EDATE(A1,-3)
これはすべて３を引くという手法
###2.If文で3月以前かどうかを出す手法
https://www.relief.jp/docs/003350.html
=IF(Month(A1)<=3,YEAR(A1)-1, YEAR(A1))
###3.誰が思いついたのか知らないけどIF文を使わずに計算する方法
A1に2019/2/1と入っているとします。A2に次の式を入れます
`=YEAR(EDATE(A1,-3*(MONTH(A1)<4)))`
または
`=YEAR(EDATE(A1,-3*((MONTH(A1)/4)<1)))`
このずれる/ずらす数をXすると、Xが1以上の正の数であれば/xとすることで、これで常に1と比較できるため、ミスが少ないからです。これも手法の一つといえると思います。
Month関数は特に1から12までの数しか返らず、12の次は1が返るので、この手法が大変効果的に使えます。
####注意点
1から3の方法をとる場合において、いずれの場合でもA2には必ず標準か数値の書式設定をしてください。1905/4/1みたいなものが出る場合は表示形式が日付になっています。それでは年度のみが表示されません。

```vb
Sub TrueisOne()
'Comment is not Memo!
    Range("A1").Select
    Range(Selection, ActiveCell.SpecialCells(xlLastCell)).Select
    Selection.Clear
    Range("A1") = #2/1/2019#
    Range("B1").Formula = "=YEAR(EDATE(A1,-3))"
    Range("B1").Select
    Selection.NumberFormatLocal = "yyyy/m/d"
    Columns("C:C").ColumnWidth = 47.25
    Range("C1").Select
    ActiveCell.FormulaR1C1 = "B1は2018を日付のシリアル値として日付に換算。"
    Range("B1").Select
    Application.CutCopyMode = False
    Selection.Copy
    Range("B2").Select
    ActiveSheet.Paste
    Application.CutCopyMode = False
    Selection.NumberFormatLocal = "0_);[赤](0)"
    Range("C2").Value = "'=YEAR(EDATE(A1,-3))をいれてB2の表示形式を標準か数値にする"
    Range("B2").Formula = "=YEAR(EDATE(A1,-3))"
    Range("B3").Formula = _
        "=IF(Month(A1)<=3,YEAR(A1)-1, YEAR(A1))"
   Range("B4").Formula = _
        "=YEAR(EDATE(A1,-3*((MONTH(A1)/4)<1)))"
   Range("B5").Formula = _
        "=YEAR(EDATE(A1,-3*(MONTH(A1)<4)))"
    Range("C3").Value = "'=IF(Month(A1)<=3,YEAR(A1)-1, YEAR(A1)) IF文で分岐"
    Range("C4").Value = "'=YEAR(EDATE(A1,-3*((MONTH(A1)/4)<1)))"
    Range("C5").Value = "'=YEAR(EDATE(A1,-3*(MONTH(A1)<4)))"
    Range("B6").Formula = "=(MONTH(A1)<4)"
    Range("C5").Formula = "'=YEAR(EDATE(A1,-3*(MONTH(A1)<4)))"
    Range("C6").Formula = "'=(MONTH(A1)<4) はTrue"
    Range("B7") = 0
    Range("C7").Value = "<-ここに0を入力"
    Range("B8").Select
    Application.CutCopyMode = False
    ActiveCell.FormulaR1C1 = "=R[-2]C=R[-1]C"
    Range("B8").Select
    Application.CutCopyMode = False
    Range("C8").Value = "'=B6=B7 WorksheetではTrueは0ではない。"
    Range("B9") = 1
    Range("C9") = "<-ここに-1を入力"
    Range("B9").FormulaR1C1 = "=(MONTH(R[-8]C[-1]) <4)*1"
    Range("C9").FormulaR1C1 = "'=(MONTH(A1) <4)*1 は1になる。Trueは1"
     Range("B10").Formula = "=YEAR(EDATE(A1,-9*((MONTH(A1)/9)<1)))"
    Range("C10").Value = "'=YEAR(EDATE(A1,-9*((MONTH(A1)/9)<1))) 海外の大学のような9月はじまりで計算した年度"
    Range("B11").Formula = "=DATE(YEAR(A1),MONTH(A1)+1*(DAY(A1)>15),1)"
    Range("C11").Formula = "前月の16日から今月の15日までを今月の1月1日、今月の16日以降を来月の1日とする"
    Range("A1").Select
Debug.Print DateSerial((Month(ActiveCell.Value) > 4) * (-1) * 1 + Year(ActiveCell.Value), 3, 31)
Debug.Print DateSerial(Abs((Month(ActiveCell.Value) / 4 > 1)) + Year(ActiveCell.Value), 3, 31)
Debug.Print DateSerial(Abs((Month(ActiveCell.Value) / 4 > 1)) + Year(ActiveCell.Value), 3, 31)
Debug.Print DateSerial(Year(ActiveCell.Value), Month(ActiveCell.Value) + Abs((Day(ActiveCell.Value) > 15)), 1)
End Sub
```

論理値 - TRUEとFALSE - OfficeTanaka
http://officetanaka.net/excel/function/tips/tips59.htm
>FALSEを「×1」したら「0」となりました。以上のことから、TRUEの実体は1、FALSEの実体は0とわかりました。

>ちなみに、VBAでは少し違います。Falseが0というのは同じですが、Trueは1ではなく-1です。


この、Trueの値が1になる性質を応用して3月までの日付はTrue、つまり1になるので`-3*1`が`-3`となりEdateは3か月前の日付を返す>これをYear関数で年だけ出すので、年度が出てくる。という仕掛けになっている。
##B.年度末3/31を求める
この論理値を使いましょう
2018年度の年度末は2019年3月31日です
A1に入っている日付から年度末を求めてみます
=DATE( (MONTH(A1)>3)+YEAR(A1),3,31)
真のとき1になりますので3月以降は翌年の3月31日が出ればいいわけです。
###VBAでは
A1を選択している状態でActivecellを使ってみます。
この時、VBAではTrueは-1になります。
このため3より大きいときは（-1）*（-1）* 1 が1で1年加算する
3以下のときは 0*（-1）* 1 が0で加算しない
ということで求められます。
Trueの論理値の数値がマイナスなので、こうします。
あるいはABSを使えばよいのです。
これで1つの式で収まります。
ところが、IF文はDateSerialを2回書き、2つの違いは+1だけです。
5行書いている間に2行もかけてしまいます。
しかも4だったかなとかなんども迷わず、End IFが抜けてるとかThenがないとか言われません。

```vb
Sub macro年度末()
Debug.Print DateSerial((Month(ActiveCell.Value) > 4) * (-1) * 1 + Year(ActiveCell.Value), 3, 31)
Debug.Print DateSerial(Abs((Month(ActiveCell.Value) / 4 > 1)) + Year(ActiveCell.Value), 3, 31)
'If文で分岐させるとDateSerialは必ず2回書く必要が出てくる。しかもその違いは+1があるかないなので冗長になる。
If Month(ActiveCell.Value) > 3 Then
Debug.Print DateSerial(Year(ActiveCell.Value) + 1, 3, 31)
ElseIf Month(ActiveCell.Value) < 4 Then
Debug.Print DateSerial(Year(ActiveCell.Value), 3, 31)
End If
End Sub
```
###論理値の必要場面　9月はじまりの年度
これは四捨五入にはならない、ある基準と別の基準の差を解消するという場面で使えそうです。
海外では9月はじまりとかなかったかな。大学なんかがそうだと思いますが。
この場合9を引くので
=YEAR(EDATE(A1,-9*((MONTH(A1)/9)<1)))
=YEAR(EDATE(A1,-9*(MONTH(A1)<9)))
###論理値の必要場面　15日まではその月の1日、16日以降は来月の1日
####ワークシート
`=DATE(YEAR(A1),MONTH(A1)+1*(DAY(A1)>15),1)`
####VBA
`Debug.Print DateSerial(Year(ActiveCell.Value), Month(ActiveCell.Value) + Abs((Day(ActiveCell.Value) > 15)), 1)`

#補足
##EDATE関数は2003以降
日付の加算や減算
https://kokodane.com/kansu_hiduke_plus.htm
>日付に付き単位の n月加算とか減算では、EDATE関数を利用します。EDATE関数は、Excel2007から標準の関数として追加された関数です。
Excel2003で利用するには分析ツールというアドインを選択する必要があります。 
##EDATEはシリアル値で計算
[EDATE 関数](https://support.office.com/ja-jp/article/edate-%E9%96%A2%E6%95%B0-3c920eb2-6e66-44e7-a1f5-753ae47ee4f5)

###説明
開始日から起算して、指定された月数だけ前または後の日付に対応するシリアル値を返します。 この関数を使用すると、伝票の発行日と同じ日に当たる支払日や満期日の日付を計算することができます。
###書式
EDATE(開始日, 月)
EDATE 関数の書式には、次の引数があります。
####開始日    
必ず指定します。
起算日を表す日付を指定します。
日付は、DATE 関数を使って入力するか、他の数式または他の関数の結果を指定します。 たとえば、2008 年 5 月 23 日を入力する場合は、DATE(2008,5,23) を使用します。 日付を文字列として入力した場合、エラーが発生することがあります。
####月  
必ず指定します。
開始日から起算した月数を指定します。 "月" に正の数を指定すると起算日より後の日付を返し、負の数を指定すると起算日より前の日付を返します。
###解説
Microsoft Office Excel では、日付を連続したシリアル値として処理することで、日付の計算が行われています。 既定では、1900 年 1 月 1 日がシリアル値 1 として保存され、2008 年 1 月 1 日は 1900 年 1 月 1 日から 39,448 日後に当たるので、シリアル値は 39448 になります。
###私見
これで言われているのはシリアル値で計算しつつも、EDATEは開始日のNか月後の同一日付を求める。ただし返すのはシリアル値。その間の月末の日数の違いを計算しているようです。
`=EDATE("2016/2/29",12)` この結果は2017/2/28。月末はあくまでも月末として求める。単純な日数、月数加算ではありませんNか月先の末日を超えると末日に収めているようです。
`=DATE(2016,1+1,31)`
`=DATE(2016,1+1,30)`
この2つはどちらも`2016/2/29`のシリアル値を返します。
`=DATE(2016,1+1,31)` 2016/3/2になります
##元ネタが行方不明
この論理値を利用する式は知恵袋でみたと思うのですが、検索したけれど見つかりません。せめてリンクを貼りたいと思っていますが、感謝の意をとりあえず伝えます。