過去の同じ曜日の日付を取得したい

過去の同じ曜日の日付を取得したい

こんにちは。kkです。

~例えばこんなシチュエーション~

今年の来場者数の推移を分析しています。
やはり休日、特に祝日含めた連休は来場者数が多いです。
さて、じゃあ去年の同じ時期の休日と比べてどうだろうか?

~~~~~~~~~~~~~~~~

ここで必要になるのは、「去年の同じ時期の休日」の日付です。
人間であれば、カレンダーをめくりながら、あぁこの日だねーと目で見て確認することが出来ますが、プログラムだとそうはいきません。
ロジックで算出する必要があります。
このようなロジックを考える機会がちょっとあったので、今回はその紹介をしたいと思います。
※Javascript で作成します

動作要件をまとめるとこんな感じです
・ある特定の日付を指定する(例:2015年11月21日(土))
・その日付に対し、過去の同じ時期、同じ曜日の日付を取得する
(例:2015年11月21日(土)に対し、2014年11月22日(土)を取得)

1.去年の同じ曜日の日付を取ってみる

同じ日付であれば、単に年を引けば良いだけですが、「同じ曜日」となるとそうはいきません。
1年は365日で、1週間は7日です。
なので、1年が何週間か?というのは、
365 ÷ 7 = 52週 余り 1日(うるう年の場合は、366日となるので余り2日)
となります。
つまり、今日(対象)の日付から52周間分(364日)を引くと、去年の同じ曜日の日付になります。
コードで書くとこんな感じでしょうか。

  • 去年の同じ曜日の日を計算する関数
[javascript]
    // 減算する日数
    var SUBTRACT_DAYS_WEEK = 7;
    var SUBTRACT_DAYS_YEAR = SUBTRACT_DAYS_WEEK * 52;
    
    function getDateOfSameDayOfWeek(inputDate) {
        
        // 基準日 日付取得
        var date = new Date(inputDate);

        // 現在の日から、52週間分引く(364日、前年の同じ曜日となる)
        var dayOfMonth = date.getDate();
        date.setDate(dayOfMonth - SUBTRACT_DAYS_YEAR);
        
        return date;
    }
[/javascript]

動作確認用のHTMLはこちら
→getDateOfSameDayOfWeek

2.n年前の同じ曜日の日付を取りたい

去年の日付は上記の通り取れましたが、指定した年数分さかのぼった同じ曜日の日付はどうでしょう?
※例えば、過去5年分をまとめて表示して比較したい
上記関数をただ単にループして計算してしまうと、そのうち週がズレてしまいます。
また、たとえ1年前であったとしても、指定した日付によっては1日~2日ずれるだけで週がずれる場合もあります。
(例)2013年1月14日(第2週月曜)
   →上記計算を行うと、2012年1月16日(第3週月曜)になる
   →仕様的には、2012年1月9日(第2週月曜)となって欲しい

そこで、対象日が年初から数えて第何週であるか計算しておきます。
昨年の同じ曜日の日付を計算後、同様にその年の第何週であるか計算し、ずれている場合に1週分戻します。
  • 週数を計算する関数
こちらの記事を参考にさせて頂きました。
Javascriptで年初からの日数/週数を取得する
[javascript] /**
* 年初からの日数を取得
* 1月1日を1日目とする
*/
function getDayOfYear(source) {
var date = new Date(source);
date.setHours(9); // 繰り上げ用

// 対象年の1月1日
var january_1 = new Date(date.getFullYear(), 0, 1);

// 対象の年月日から同年1月1日を引いた値 の日表記(86400000 = 24h * 60m * 60s * 1000ms) 切り上げ
return Math.ceil((date – january_1) / 86400000);
};

/**
* 年初からの週数を取得
*/
function getWeekOfYear(source) {
var date = new Date(source);

// 対象年の1月1日を取得
var january_1 = new Date(date.getFullYear(), 0, 1);

// 年初からの日数
var dayOfYear = getDayOfYear(source);

// 1月1日の週を0週目、翌日曜の週を1週目となる計算にするため、日にちをずらす
// Date.getDay() → 日:0、月:1、火:2、水:3、木:4、金:5、土:6
var offset = january_1.getDay() – 1;

// 週数を計算
var weeks = Math.floor((dayOfYear + offset) / 7);

// 1月1日の週を第1週と数える場合は加算する
if (isFirstWeek(january_1)) {
weeks += 1;
}
return weeks;
};

/**
* 1月1日が1週目であるか判定する
* 週の始まりは日曜とする
*/
function isFirstWeek(january_1) {

// 月曜を含む週が1週目(1月のハッピーマンデーを考慮するため)※これで全ての月を対応出来る訳では無い
return (january_1.getDay() <= 1)

// 【以下参考までに】
// 日曜を含む週が1週目
//return (january_1.getDay() <= 0)
// 本年の4日以上を含む週が1週目
//return (january_1.getDay() <= 3)
}
[/javascript]

内容はコード内コメントを参照してください。 Line:30のoffsetの目的ですが、文章での説明が難しかったので図にしてみました。

つまり、その週のどの日にちで週数を計算しても同じ結果となるように、日付の補正をしています。 1月1日を含む週を第1週と数えるかは、考え方により変わるので別関数として定義し、加算することにしてます。 参考:MySQLのWEEK関数

  • 上記週数を使い、過去の同じ曜日の日を計算する関数
[javascript] // 減算する日数
var SUBTRACT_DAYS_WEEK = 7;
var SUBTRACT_DAYS_YEAR = SUBTRACT_DAYS_WEEK * 52;

/**
* 同じ曜日の過去日付を取得
*/
function getDateOfPastOfSameDayOfWeek(inputDate, inputYears) {

// 基準日 日付取得
var date = new Date(inputDate);

// 1年ずつ計算する
for (var i = 0; i < inputYears; i++) {

// 現在の日の、年初からの週数を算出しておく
var weekOfYear = getWeekOfYear(date);

// 現在の日から、52週間分引く(364日、前年の同じ曜日となる)
var dayOfMonth = date.getDate();
date.setDate(dayOfMonth – SUBTRACT_DAYS_YEAR);

// 前年の、年初からの週数を算出
var lastWeekOfYear = getWeekOfYear(date);

// 前年の週数が繰り上がっている場合、1週間分引く
if (weekOfYear < lastWeekOfYear) {
date.setDate(date.getDate() – SUBTRACT_DAYS_WEEK);
}
// 算出した日を基準として、指定年数分繰り返す
}
return date;
}
[/javascript]

関数名にやたらOfが付いて長くなってしまいました。 動作確認用のHTMLはこちら →getDateOfPastOfSameDayOfWeek1 上記は1年ずつ繰り下げていく場合で作成しましたが、年初からの週数と曜日をもとに、過去年の1月1日から加算して計算していく方法も考えられます。

[javascript] /**
* 同じ曜日の過去日付を取得
*/
function getDateOfPastOfSameDayOfWeek(inputDate, inputYears) {

// 基準日 日付取得
var date = new Date(inputDate);
// 基準日の、年初からの週数を算出しておく
var weekOfYear = getWeekOfYear(date);
// 現在の曜日を取得しておく
var day = date.getDay();

// 指定年数前の1月1日を取得
var pastDate = new Date(date.getFullYear() – inputYears, 0, 1);
// 1月1日が1週目の場合、計算後に1週間分引く
var sub = 0;
if (isFirstWeek(pastDate)) {
sub = 7;
}
// 1月1日を日曜にする(元々日曜であっても、1週間ずれる)
pastDate.setDate(pastDate.getDate() + (7 – pastDate.getDay()))

// 基準日と同じ週数、曜日になるよう加算する
pastDate.setDate(pastDate.getDate() + ((weekOfYear – 1) * 7) + day – sub);

return pastDate;
}
[/javascript]

動作確認用のHTMLはこちら →getDateOfPastOfSameDayOfWeek2 年初からの週数で判定しているため、個々の月での第何週かは考慮していません。 なので、冒頭で触れたような2015年9月21日(敬老の日)では、 2015年9月21日:38週目、9月第3月曜 2014年9月22日:38週目、9月第4月曜 となり、時期としては合ってますが、祝日としてはズレてしまいます。 こういった要件で対応したい場合は、月単位で対応する範囲を固定した上で、ロジックを組む必要があるでしょう。 ※9月の第3月曜を、過去何年分算出する…みたいな * オマケ Javaで何か便利なものないかなーと思って調べてみた時に、見つけて試したものです。 [java] package jo.co.opentone.tk; import java.time.DayOfWeek; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.time.temporal.WeekFields; public class Main { public static void main(String[] args) { WeekFields week = WeekFields.of(DayOfWeek.SUNDAY, 1); DateTimeFormatter dtf = new DateTimeFormatterBuilder() .appendPattern(“yyyy/MM/dd(E) “) .appendValue(week.weekOfYear()) .appendLiteral(“週目”) .toFormatter(); LocalDate date = LocalDate.of(2015, 11, 1); System.out.println(date.format(dtf)); } } 出力 2015/11/01(日) 45週目 [/java]

参考:
Java8日時APIのちょっと特殊なクラスたち
WeekFields