以下の端数調整(EOM 月末/1日開始)**を正しく考慮できています。大筋で問題ありません。
YEAR/ MONTH差で基礎となる総月数を計算
開始日が1日 & 終了日が月末 → その月を丸々1か月としてカウント(-1 を引いているので実質 +1 調整)
終了日が月末 & 開始日の「日」が終了日の「日」より大きい → 本来は -1 されるところを減らさない
上記以外で 開始日の「日」> 終了日の「日」 → -1 か月(満月に達していないため)
結果の文字列は TEXT(FLOOR(.../12)) & "年" & TEXT(MOD(...,12)) & "ヶ月" で「n年mヶ月」と表記されます。
- 2024-01-01 ~ 2024-01-31(同月・月末) → 1ヶ月(調整により 0→1)
- 2024-01-31 ~ 2024-02-29(閏年・月末) → 1ヶ月(EOM ルールで月欠け扱いしない)
- 2023-01-31 ~ 2023-02-28(平年・月末) → 1ヶ月
- 2024-01-01 ~ 2024-02-29(1日開始・月末) → 2ヶ月(両月を丸々カウント)
- 2024-01-15 ~ 2024-02-14 → 0ヶ月(満月未満なので -1 調整)
- 2024-01-15 ~ 2024-02-15 → 1ヶ月(ちょうど満月)
いずれも、「1日開始」「終了が月末」の特殊処理と、
**「開始日の方が日付が大きい場合は -1」**という通常処理が矛盾なく効いています。
IF(
AND(NOT(ISBLANK( StartDate__c )), NOT(ISBLANK( EndDate__c ))),
/* 年の計算 */
TEXT(
FLOOR(
(
(YEAR(EndDate__c )-YEAR(StartDate__c ))*12 +
(MONTH(EndDate__c )-MONTH(StartDate__c )) -
/* 端数調整 */
IF(
AND(DAY(StartDate__c )=1,
EndDate__c = ADDMONTHS(DATE(YEAR(EndDate__c ), MONTH(EndDate__c ), 1),1)-1
),-1,
IF(
AND(EndDate__c = ADDMONTHS(DATE(YEAR(EndDate__c ), MONTH(EndDate__c ), 1),1)-1,
DAY(StartDate__c ) > DAY(EndDate__c )
),0,
IF( DAY(StartDate__c ) > DAY(EndDate__c ),1,0 )))
) / 12
)
) & "年"&
/* 月の計算 */
TEXT(
MOD(
(YEAR(EndDate__c )-YEAR(StartDate__c ))*12 +
(MONTH(EndDate__c )-MONTH(StartDate__c )) -
IF(
AND(DAY(StartDate__c )=1,
EndDate__c = ADDMONTHS(DATE(YEAR(EndDate__c ), MONTH(EndDate__c ), 1),1)-1
),-1,
IF(
AND(EndDate__c = ADDMONTHS(DATE(YEAR(EndDate__c ), MONTH(EndDate__c ), 1),1)-1,
DAY(StartDate__c ) > DAY(EndDate__c )
),0,
IF( DAY(StartDate__c ) > DAY(EndDate__c ),1,0 ))),
12
)
)
& "ヶ月",
"")
改善したソースコード:
- 開始日 > 終了日の場合の扱い
現行式だと負の値(「-1年◯ヶ月」のような表示)になり得ます。
ビジネスルール的に許可しないなら、ガードを入れて空文字や 0 にできます。
- ゼロ抑制(任意)
「0年5ヶ月」はよいが、「0年0ヶ月」は空白にしたい・「5ヶ月」とだけ表示したい等の表記ルールがある場合は表示部分を調整します。
- マイナス総月の MOD
終了 < 開始 のときに負の総月を許す設計だと、MOD(負の数, 12) は負の剰余になることがあります。見栄えを崩す要因なので、(1) のようにガードするか、負値のときは絶対値化・空文字にするのが無難です。
/* 総月数を一回だけ計算し、それを使って表示を分岐(可読性向上のための書き方例) */
IF(
AND(NOT(ISBLANK(StartDate__c)), NOT(ISBLANK(EndDate__c))),
/* 総月数(調整後) */
IF(
/* 終了 < 開始 のときは空文字を返す(任意) */
EndDate__c < StartDate__c,
"",
/* 表示生成 */
IF(
FLOOR(
(
(YEAR(EndDate__c)-YEAR(StartDate__c))*12 +
(MONTH(EndDate__c)-MONTH(StartDate__c)) -
IF(
AND(
DAY(StartDate__c)=1,
EndDate__c = ADDMONTHS(DATE(YEAR(EndDate__c), MONTH(EndDate__c), 1),1)-1
),
-1,
IF(
AND(
EndDate__c = ADDMONTHS(DATE(YEAR(EndDate__c), MONTH(EndDate__c), 1),1)-1,
DAY(StartDate__c) > DAY(EndDate__c)
),
0,
IF(DAY(StartDate__c) > DAY(EndDate__c), 1, 0)
)
)
) / 12
) > 0,
/* 年が1以上のときは「n年mヶ月」 */
TEXT(
FLOOR(
(
(YEAR(EndDate__c)-YEAR(StartDate__c))*12 +
(MONTH(EndDate__c)-MONTH(StartDate__c)) -
IF(
AND(
DAY(StartDate__c)=1,
EndDate__c = ADDMONTHS(DATE(YEAR(EndDate__c), MONTH(EndDate__c), 1),1)-1
),
-1,
IF(
AND(
EndDate__c = ADDMONTHS(DATE(YEAR(EndDate__c), MONTH(EndDate__c), 1),1)-1,
DAY(StartDate__c) > DAY(EndDate__c)
),
0,
IF(DAY(StartDate__c) > DAY(EndDate__c), 1, 0)
)
)
) / 12
)
) & "年" &
TEXT(
MOD(
(YEAR(EndDate__c)-YEAR(StartDate__c))*12 +
(MONTH(EndDate__c)-MONTH(StartDate__c)) -
IF(
AND(
DAY(StartDate__c)=1,
EndDate__c = ADDMONTHS(DATE(YEAR(EndDate__c), MONTH(EndDate__c), 1),1)-1
),
-1,
IF(
AND(
EndDate__c = ADDMONTHS(DATE(YEAR(EndDate__c), MONTH(EndDate__c), 1),1)-1,
DAY(StartDate__c) > DAY(EndDate__c)
),
0,
IF(DAY(StartDate__c) > DAY(EndDate__c), 1, 0)
)
),
12
)
) & "ヶ月",
/* 年が0のときは「mヶ月」のみ */
TEXT(
MOD(
(YEAR(EndDate__c)-YEAR(StartDate__c))*12 +
(MONTH(EndDate__c)-MONTH(StartDate__c)) -
IF(
AND(
DAY(StartDate__c)=1,
EndDate__c = ADDMONTHS(DATE(YEAR(EndDate__c), MONTH(EndDate__c), 1),1)-1
),
-1,
IF(
AND(
EndDate__c = ADDMONTHS(DATE(YEAR(EndDate__c), MONTH(EndDate__c), 1),1)-1,
DAY(StartDate__c) > DAY(EndDate__c)
),
0,
IF(DAY(StartDate__c) > DAY(EndDate__c), 1, 0)
)
),
12
)
) & "ヶ月"
)
),
""
)
よくテストをしてから、使ってください。
まとめ
現状ロジックは 月末・1日開始・満月未満の調整を適切にカバーしており、誤りはありません。
実運用では **「終了日が開始日より前」**のケースの扱いと、表示のゼロ抑制を整えると、より堅牢・見やすくなります。
もし「日」まで出したい(例:1年2ヶ月と14日)や、2/29 を含む特殊契約のビジネスルールがあるなど、さらに要件があれば教えてください。要件に合わせて式を調整します。