ComponentOne Information

ComponentOne Studio/Wijmo/Xuniの最新情報を公開中

毎月最終金曜日を判定してカレンダーに表示するには?

「プレミアムフライデー」
2017年2月からはじまった、日本政府が推進する消費拡大を目的としたキャンペーンです。

平日に勤務する従業員が月に一度、仕事を早く切り上げて早く帰宅することで、一足早い週末に突入して人生を謳歌しようではありませんか。という施策です。
はじまったばかりで未知数ですが、何もやらないよりはプラスに働くのではないでしょうか。

この制度を普及させるにはカレンダーに表示することも大切です。
ということでXuni(ズーニー)のコンポーネントであるCalendarを利用してモバイルアプリにプレミアムフライデーを表示してみます。

Xamarin.FormsでXuniを利用する方法や、カレンダーを表示する方法については、以前の記事で紹介しています。

今回のメインテーマは、プレミアムフライデーがいつであるか?です。
サンプルアプリの中に組み込むことに挑戦します。

最終金曜日はいつ?

当然ですが、最終金曜日の日付はその時によって異なります。 2017年3月であれば2017年3月31日が金曜日で、4月は28日、5月は26日です。 これを求める関数は用意されていませんので、自分で作成する必要があります。

それではC#で対象月の最終金曜日を求める関数を作ります。 考え方はシンプルです。

手順
1. 対象となる月の最終日とその曜日を求める
2. その日から月初方向にさかのぼる
3. 最も近い金曜日が最終金曜日

最終日と曜日はシステムで提供されている関数を利用すれば求めることができます。 また、最終日は「翌月の1日の前日」として求めます。 コードは以下です。

// 対象月の最初の日である1日を求める 例)2017/3/1
var firstDate = new DateTime(date.Year, date.Month, 1); 
// 求めた日の翌月1日の前日(当月の最終日)を求める 
// 例) 2017/3/1の翌月2017/4/1の全日2017/3/31
var lastDate = firstDate.AddMonths(1).AddDays(-1);

これで最終日がわかりました。前述の手順 1. が完了しました。 ここから、手順2-3を実装すればプレミアムフライデーがわかります。

まず、最終日の曜日は金曜日とは何日離れているかを求めます。 DayOfWeekクラスの各曜日は、0[日曜日]~7[土曜日]までの整数です。これをint型にして引き算します。

曜日対応表

曜日
数値 0 1 2 3 4 5 6

金曜日との差分を求めるコード

// 金曜日(6)から対象日付の曜日差を求める 例) 6-6[3/31] = 0
var subs =  (int)DayOfWeek.Friday -(int)lastDate.DayOfWeek;

差分は0なので、さかのぼる必要なく3月31日が最終金曜日であることがわかります。

2017年4月の場合は、この計算式で求める差分は6-0[4/30は日曜日]で6になります。しかし、この差分を利用すると日曜日から6日さかのぼってしまい月曜日になってしまいます。正しくは日曜日と金曜日の差、つまりさかのぼる日数は-2です。

曜日
結果 0 4 3 2 1 0 -1

最終日からさかのぼる

これは、日曜日からはじまるDayOfWeekのテーブルと、金曜日を基準にさかのぼる日数の並びが合わないために発生します。金曜日を軸にするとさかのぼる日数のテーブルは以下です。

曜日
日数 -2 -3 -4 -5 -6 0 -1

やむを得ないのでこれを補正してさかのぼる日数を表すテーブルを作り、インデックスでアクセスしやすいよう、土曜日スタートで逆順にしました。曜日の最大値である土曜日だけが金曜日との差分を求めるとマイナスの値になるためです。

曜日
差分 -1 0 1 2 3 4 5
日数 -1 0 -6 -5 -4 -3 -2
Index 0 1 2 3 4 5 6

プレミアムフライデー用関数?

最終的な関数のコードは以下になりました。

// 対象日付を引数に、その月の最終金曜日をDateTimi型で返す
private DateTime getPremiumFriday(DateTime date)
{
    // さかのぼる日数のテーブル 順番は、土金木水火月日
    int[] minus = new int[] { -1,0, -6, -5, -4, -3, -2, };
    
    // 対象月の初日をもとに、その月の最終日を求める    
    var firstDate = new DateTime(date.Year, date.Month, 1); 
    var lastDate = firstDate.AddMonths(1).AddDays(-1);

    // 金曜日(6)から対象日付の曜日差を求める 例) 6-6[3/31] = 0
    var subs =  (int)DayOfWeek.Friday -(int)lastDate.DayOfWeek;
    // 差分に1を加えた数が日数テーブルのインデックスになる
    return lastDate.AddDays(minus[subs +1]);
}

このコードをXuniのサンプルに適用し、プレミアムフライデーを表示した結果が以下です。なおプレミアムフライデーのロゴは、冒頭のWebサイトで申請すると利用可能になります。

f:id:ComponentOne_JP:20170329153905p:plain

モヤモヤが残る

なんとなくモヤモヤした感じが残るコードになってしまいました。
やむを得ず、テーブルを作ったのがいまひとつです。他の最終〇曜日を求めるような汎用性も欠けています。
もし、もっとスマートな方法をご存じの方は、FacebookやTwitterのアカウントにコメントをいただけると助かります。


※追記(2017/3/30)
Excel関数で実現する方法をお知らせいただきました。

こちらをもとに作成した関数に変更を加え、最終〇曜日を調べるようにしたコードを追加しました。

// 対象日付と調べる曜日を引数に、その月の最終〇曜日をDateTimi型で返す
private DateTime getPremiumDayOfWeek(DateTime date, DayOfWeek dayofweek)
{
    // 対象月の初日をもとに、その月の最終日を求める    
    var firstDate = new DateTime(date.Year, date.Month, 1);
    var lastDate = firstDate.AddMonths(1).AddDays(-1);

    // その月の最後の7日間の曜日を調べ、該当する日付を特定する
    DateTime premiumDay = new DateTime();
    for (int i = 0; i < 8; i++)
    {
        premiumDay = lastDate.AddDays(i * -1);
        if (premiumDay.DayOfWeek == dayofweek)
            break;
    }
    return premiumDay;
}
ComponentOne