概要
とある会社の勤怠をGoogleフォームから社員が各自で行うようにしています。
毎日、出社時と帰社時にフォームを開いて登録しないといけないわけですが、けっこう打刻忘れも多いです。
打刻修正もフォームから申請してもらい、GASで日時等をフォーマットして通常の出社・帰社の時刻をまとめた表に追加し、ピボットテーブルで最終的な勤怠表に整形して〆日になったらGASでCSVファイルを出力するというシステムにしています。
(都合により給与計算シートがエクセルなので、CSVファイルをエクセルに流して計算するシステムも別に作ってます。)
前日までの打刻忘れを検出して毎朝グループLINEに通知するシステムを後から追加したので、打刻忘れがずっと残っている状態は今ではほとんどなくなりましたが、システムを導入した最初のころには毎月の〆日の後から打刻修正をしてあるのに気づかず、給与計算をする人が後から困るという事態がひんぱんに起こっていました。
ということで、日時の修正が毎月の〆日より前か後かを判定し、当月の範囲内であればそのまま修正し、範囲外であれば担当者にメールで知らせるというコードを作ったので、その判定の部分について解説します。
考え方
たとえば、区切りの日(〆日)を「15日」とします。
今日(修正日)が「4月20日」だったとします。
今日を基準にすると「当月」は4月16日~5月15日です。
「前月」は3月16日~4月15日になります。
判定する日が「4月5日」だったら、「前月」に入ります。
では、今日(修正日)が「4月13日」だったらどうなるでしょうか?
この場合「当月」が3月16日~4月15日になり、「前月」が2月16日~3月15日になります。
判定する日が「4月5日」だったら、「当月」に入ります。
このように、今日(修正日)と〆日の関係によって当月か前月かの範囲が変わり、判定する日の内容をどう処理するかが変わるわけです。
また、今日が1月14日だったりすると「当月」の最初の日は前年になるので、年の処理も必要になってきます。
年の処理も修正日と判定日の関係によって判定日が当月かどうかによって処理を変えます。
ということで、今日(修正日)から「当月」の範囲を決定し、判定する日が当月の範囲内に入るのかどうかで処理の条件分けをするという流れになります。
それと、関数new Date()
は今日の日付を取得しますが、今日の月を取得するのにnew Date().getMonth()
とすると1~12ではなく0~11が取得されます。しかしnew Date(today_y,today_m,15).getMonth()
のように数値を入れて日付を作ったものの月を取得するとなぜか0~11ではなく1~12で取得されるようなので、これらを組み合わせて使うと問題が発生します。なのでそのような問題を避けてコードを作る必要があります。
下記は私が作成したものから抽出したコードです。作成時にネットで参考にしたサイトがあったのですが、かなり前に作成したので、そのサイトのURLがわからなくなってしまい、参考として載せることができません。すみません……
GASのコード
function judgeDate_in_aMonth() {
let ss = SpreadsheetApp.getActiveSpreadsheet();
let sh = ss.getSheetByName("シート1");
// ★new Dateに直接数値を入れて日付を作ると、getMonthしたときに数値が-1されない!★
let today = new Date();
let today_y = today.getFullYear();// 今日の「年」(数値)
let today_m = today.getMonth(); // 今日の「月」(数値)
let today_d = today.getDate(); // 今日の「日」(数値)
let first_d = new Date(); // 判定範囲の最初の日(日付)
let end_d = new Date(); // 判定範囲の最後の日(日付)
let linedate = 15; // 区切りの日(〆日)を数値で入力
// 判定日を取得して日付の形式に整形
let jd = new Date(sh.getRange(1,1).getValue());
let judge_d = new Date(jd.getFullYear(),jd.getMonth(),jd.getDate());
console.log(today_y,today_m,today_d);
// 今日の日付により「当月」と「前月」の範囲を作成する(時刻は00:00:00にする)
// 今日の日が判定日より前なら最初の日の月を「前月」にする
if(today_d < linedate+1){
// 今月が「0(1月)」であれば「最初の日」の年を去年、月を12月として処理する
if(today_m == 0){
first_d.setFullYear(today_y-1);
first_d.setMonth(11); // 0~11の設定で12月なので11
first_d.setDate(linedate+1);
end_d.setDate(linedate);
// 1月以外(1~11)の場合は「最初の日」の月は今月から1引く
}else{
first_d.setMonth(today_m-1);
first_d.setDate(linedate+1);
end_d.setDate(linedate);
}
// 今日の日が判定日より後なら最後の日の月を「次月」にする
}else{
// 今月が「11(12月)」であれば「最後の日」の年を来年、月を1月として処理する
if(today_m == 11){
first_d.setFullYear(today_y+1);
first_d.setMonth(0);
first_d.setDate(linedate+1);
end_d.setDate(linedate);
// 12月以外(0~10)の場合は「最後の日」の月は当月に1足す
}else{
first_d.setDate(linedate+1);
end_d.setFullYear(today_y+1);
end_d.setMonth(today_m+1);
end_d.setDate(linedate);
}
}
let fdate = Utilities.formatDate(first_d,"JST","yyyy年M月d日");
let edate = Utilities.formatDate(end_d,"JST","yyyy年M月d日");
let jdate = Utilities.formatDate(judge_d,"JST","yyyy年M月d日");
console.log("判定期間:"+fdate+"~"+edate+"/判定日:"+jdate)
}
判定日が〆日と同日か翌日だった場合の問題
このコードでは「当月」の最初の日と最後の日は「月」と「日」だけを指定しています。
この場合「時間」は最初に設定した「今日(today)」の時間が入ることになります。
判定日が〆日と違う場合は問題ないのですが、同日か翌日だった場合に判定日のデータの設定によってはうまく条件分岐できない場合が出てきます。
仕事で作ったシステムの場合はそういう問題が出てきたので、実際のコードはfirst_d
とend_d
の設定にsetHours()
も入れることで回避しています。
面倒ですが、そのあたりも注意が必要になるかと思います。
参考文献