📚

GAS開発者のための日付操作ガイド - GAS + TypeScriptによる日付操作ユーティリティの実装と応用

に公開

GAS開発者のための日付操作ガイド - GAS + TypeScriptによる日付操作ユーティリティの実装と応用

1. はじめに

  • GAS(Google Apps Script)のプロジェクトにおいて、日付の操作は頻繁に必要となる処理です。
  • 本記事では、GASとTypeScriptを使用した日付操作ユーティリティの実装について解説します。
  • 初学者の方にも理解しやすいよう、概念から実装、応用例まで順を追って説明します。

2. 準備:GASでTypeScriptを使うための環境設定

本記事で紹介するコードを実行するには、GAS開発環境にTypeScriptを導入する必要があります。

Clasp(Command Line Apps Script Projects)の設定

ClaspはGASプロジェクトをローカルで開発するためのツールです。以下の手順で設定します。

  1. Node.jsをインストール(未導入の場合)
  2. npmを使ってClaspをインストール
    npm install -g @google/clasp
    
  3. Googleアカウントでログイン
    clasp login
    

TypeScriptプロジェクトの初期化方法については、Google公式ドキュメントを参照してください:TypeScript を使用する

3. JavaScriptにおける日付の基本概念

TypeScriptによる日付操作の実装に入る前に、JavaScriptにおける日付の扱い方について簡単に解説します。

Dateオブジェクトの基本

JavaScriptでは、Dateオブジェクトを使って日付と時刻を扱います。

// 現在の日時でDateオブジェクトを作成
const now = new Date();

// 特定の日付を指定してDateオブジェクトを作成(フォーマット:YYYY-MM-DD)
const specificDate = new Date('2025-01-18');

// 年、月、日を指定してDateオブジェクトを作成(注意:月は0から始まります)
const anotherDate = new Date(2025, 0, 18); // 2025年1月18日

日付の取得と設定

const date = new Date('2025-01-18');

// 日付情報の取得
const year = date.getFullYear();      // 2025
const month = date.getMonth() + 1;    // 1(getMonthは0始まりなので+1する)
const day = date.getDate();           // 18
const dayOfWeek = date.getDay();      // 6(0:日曜, 1:月曜, ... 6:土曜)

// 日付の設定
date.setFullYear(2026);               // 年を2026に変更
date.setMonth(1);                     // 月を2月(1)に変更
date.setDate(15);                     // 日を15日に変更

GASにおける日付操作の注意点

GASでは、JavaScriptの標準的な日付操作に加えて、Utilitiesクラスを使った独自の日付操作機能も提供されています。

// GAS独自の日付フォーマット関数
const formattedDate = Utilities.formatDate(
  new Date(), 
  'Asia/Tokyo',       // タイムゾーン
  'yyyy/MM/dd HH:mm:ss' // フォーマットパターン
);

これらの基本的な知識を踏まえた上で、より実用的な日付操作ユーティリティを作成していきましょう。

4. 日付操作ユーティリティ - DateUtilクラスの実装

DateUtilクラスは、GASアプリケーションでよく使う日付操作をまとめたユーティリティクラスです。機能ごとに分けて解説します。

4.1 基本的な日付計算機能

日付に対して日数の加算・減算を行う基本的な機能を実装します。

/**
 * 日付操作のユーティリティクラス
 */
export class DateUtil {
  /**
   * 基準日から指定日数を加算または減算した日付を取得します
   * @param date 基準日
   * @param days 加算する日数(負数の場合は減算)
   * @param resetTime 時刻をリセットするかどうか(デフォルトはtrue)
   * @returns 計算後の日付
   */
  public static addDaysToDate(
    date: Date,
    days: number,
    resetTime: boolean = true
  ): Date {
    const resultDate = new Date(date);
    resultDate.setDate(resultDate.getDate() + days);

    if (resetTime) {
      resultDate.setHours(0, 0, 0, 0);
    }

    return resultDate;
  }
  
  /**
   * 現在日時から指定日数前の日付を取得します
   * @param days 遡る日数
   * @returns 指定日数前の日付
   */
  public static getDaysAgo(days: number): Date {
    return this.addDaysToDate(new Date(), -days);
  }
}

ポイント解説

  • addDaysToDateメソッドは、基準日に対して日数を加減算します
  • resetTimeパラメータを使うと、時刻部分を00:00:00にリセットできます(日付比較に便利)
  • getDaysAgoメソッドは、現在から指定日数前の日付を簡単に取得できる便利メソッドです

4.2 曜日に基づく日付取得機能

週の開始日(月曜日)や終了日(日曜日)を取得する機能を実装します。

/**
 * 来週の月曜日の日付を取得します
 * @returns 来週月曜日の日付
 */
public static getNextMonday(): Date {
  const today = new Date();
  const nextMonday = new Date(today);
  // 今日の曜日を基に来週の月曜日までの日数を計算
  // getDay()は0が日曜、1が月曜...6が土曜を返す
  nextMonday.setDate(today.getDate() + (8 - today.getDay()) % 7);
  nextMonday.setHours(0, 0, 0, 0);
  return nextMonday;
}

/**
 * 来週日曜日の日付を取得します
 * @returns 来週日曜日の日付
 */
public static getNextSunday(): Date {
  const nextMonday = this.getNextMonday();
  const nextSunday = new Date(nextMonday);
  nextSunday.setDate(nextMonday.getDate() + 6);
  nextSunday.setHours(0, 0, 0, 0);
  return nextSunday;
}

/**
 * 来週の日付範囲を取得します
 * @returns 来週の月曜日と日曜日の日付
 */
public static getNextWeekRange(): { startDate: Date; endDate: Date } {
  return {
    startDate: this.getNextMonday(),
    endDate: this.getNextSunday(),
  };
}

/**
 * 先週の月曜日の日付を取得します
 * @returns 先週月曜日の日付
 */
public static getLastMonday(): Date {
  const today = new Date();
  const lastMonday = new Date(today);
  // 今日の曜日に基づいて、先週の月曜日までの日数を計算
  lastMonday.setDate(today.getDate() - (today.getDay() === 0 ? 6 : today.getDay() - 1) - 7);
  lastMonday.setHours(0, 0, 0, 0);
  return lastMonday;
}

/**
 * 先週の日曜日の日付を取得します
 * @returns 先週日曜日の日付
 */
public static getLastSunday(): Date {
  const lastMonday = this.getLastMonday();
  const lastSunday = new Date(lastMonday);
  lastSunday.setDate(lastMonday.getDate() + 6);
  lastSunday.setHours(0, 0, 0, 0);
  return lastSunday;
}

/**
 * 先週の日付範囲を取得します
 * @returns 先週の月曜日と日曜日の日付
 */
public static getLastWeekRange(): { startDate: Date; endDate: Date } {
  return {
    startDate: this.getLastMonday(),
    endDate: this.getLastSunday(),
  };
}

ポイント解説

  • getDay()メソッドは、日曜日を0、月曜日を1とする数値を返します
  • getNextMonday()は、現在の曜日を考慮して来週の月曜日までの日数を計算しています
  • getLastMonday()は、現在の曜日から先週の月曜日までの日数を引き算しています
  • 日曜日(getDay() = 0)の場合、特別な処理が必要なことに注意しています

4.3 日付の文字列変換機能

日付をさまざまな形式の文字列に変換する機能を提供します。

/**
 * 基準日から指定日数後の日付を文字列形式で取得します
 * @param baseDate 基準日
 * @param days 進める日数
 * @returns yyyy/MM/dd形式の日付文字列
 */
public static formatAddDays(baseDate: Date, days: number): string {
  return this.toFormatString(this.addDaysToDate(baseDate, days));
}

/**
 * 基準日から指定日数前の日付を文字列形式で取得します
 * @param baseDate 基準日
 * @param days 遡る日数
 * @returns yyyy/MM/dd形式の日付文字列
 */
public static formatRemoveDays(baseDate: Date, days: number): string {
  return this.toFormatString(this.addDaysToDate(baseDate, -days));
}

/**
 * 日付を指定フォーマットの文字列に変換します。
 * @param date 変換する日付(JavaScriptのDateオブジェクトまたはGASのDateオブジェクト)
 * @returns yyyy/MM/dd形式の日付文字列
 */
public static toFormatString(
  date: Date | GoogleAppsScript.Base.Date
): string {
  // GAS独自のUtilitiesクラスを使ってフォーマットします
  return Utilities.formatDate(date, 'Asia/Tokyo', 'yyyy/MM/dd');
}

/**
 * 日付を「MM/DD (曜日)」形式でフォーマットします
 * @param date フォーマットする日付
 * @returns MM/DD (曜日) 形式の文字列
 */
public static formatDateWithDayOfWeek(date: Date): string {
  const dayOfWeek = ['日', '月', '火', '水', '木', '金', '土'][date.getDay()];
  return `${date.getMonth() + 1}/${date.getDate()} (${dayOfWeek})`;
}

/**
 * 日付範囲を「MM/DD~MM/DD」形式でフォーマットします
 * @param startDate 開始日
 * @param endDate 終了日
 * @returns MM/DD~MM/DD 形式の文字列
 */
public static formatDateRange(startDate: Date, endDate: Date): string {
  return `${startDate.getMonth() + 1}/${startDate.getDate()}${
    endDate.getMonth() + 1
  }/${endDate.getDate()}`;
}

ポイント解説

  • 元の記事のaddDaysremoveDaysメソッドは、機能が分かりやすいようにformatAddDaysformatRemoveDaysに変更しました
  • toFormatStringメソッドでは、GAS独自のUtilities.formatDate関数を使用しています
  • GoogleAppsScript.Base.Date型は、GAS内部で使われる日付型で、JavaScriptの標準Date型も受け付けられるようにしています
  • formatDateWithDayOfWeekメソッドで日本語の曜日表示を実装しています

4.4 日付の比較・検証機能

日付の比較や範囲チェックを行う機能を実装します。

/**
 * 指定された日付が来週以降の日付かどうかを判定します
 * @param date 判定対象の日付
 * @returns 来週以降の日付の場合はtrue
 */
public static isDateAfterNextWeek(date: Date): boolean {
  const nextMonday = this.getNextMonday();
  return date >= nextMonday;
}

/**
 * 2つの日付が同じ日かどうかを判定します
 * @param date1 比較する日付1
 * @param date2 比較する日付2
 * @returns 同じ日の場合はtrue、それ以外はfalse
 */
public static isSameDate(date1: Date, date2: Date): boolean {
  return (
    date1.getFullYear() === date2.getFullYear() &&
    date1.getMonth() === date2.getMonth() &&
    date1.getDate() === date2.getDate()
  );
}

/**
 * 指定された日付が範囲内かどうかを判定します
 * @param date 判定する日付
 * @param startDate 範囲開始日
 * @param endDate 範囲終了日(この日を含む)
 * @returns 範囲内の場合はtrue、それ以外はfalse
 */
public static isDateInRange(
  date: Date,
  startDate: Date,
  endDate: Date
): boolean {
  const normalizedDate = this.normalizeDate(date);
  const normalizedStart = this.normalizeDate(startDate);
  const normalizedEnd = this.normalizeDate(endDate);

  return normalizedDate >= normalizedStart && normalizedDate <= normalizedEnd;
}

/**
 * 日付の時刻部分を0時0分0秒に正規化します
 * 日付比較を行う際、時刻部分を無視して純粋に日付だけを比較するために使用します
 * @param date 正規化する日付
 * @returns 時刻部分がリセットされた新しい日付オブジェクト
 */
private static normalizeDate(date: Date): Date {
  return new Date(date.getFullYear(), date.getMonth(), date.getDate());
}

ポイント解説

  • isSameDateメソッドは、日付部分のみを比較し時刻は無視します
  • normalizeDateはプライベートメソッドで、時刻部分をリセットした新しい日付オブジェクトを作成します
  • 日付比較では、JavaScriptの標準比較演算子(>=, <=など)が利用できます
  • 日付範囲チェックでは、開始日と終了日を含む範囲でチェックしています

5. DateUtilクラスの使い方 - 基本編

ここでは、DateUtilクラスのメソッドを使った基本的な日付操作の例を紹介します。

5.1 日付の取得と計算

// 実行日から7日前の日付を取得
const sevenDaysAgo = DateUtil.getDaysAgo(7);
console.log("7日前の日付:", DateUtil.toFormatString(sevenDaysAgo));
// 出力例: 7日前の日付: 2025/01/11

// 来週の日付範囲を取得
const nextWeekRange = DateUtil.getNextWeekRange();
console.log("来週の期間:", 
  DateUtil.toFormatString(nextWeekRange.startDate) + " から " + 
  DateUtil.toFormatString(nextWeekRange.endDate)
);
// 出力例: 来週の期間: 2025/01/20 から 2025/01/26

// 特定の日付から3日後を計算
const baseDate = new Date('2025-01-18');
const afterThreeDays = DateUtil.formatAddDays(baseDate, 3);
console.log("3日後の日付:", afterThreeDays);
// 出力例: 3日後の日付: 2025/01/21

5.2 日付の比較とフォーマット

// 日付範囲チェック
const targetDate = new Date('2025-01-20');
const startDate = new Date('2025-01-18');
const endDate = new Date('2025-01-25');

const isInRange = DateUtil.isDateInRange(targetDate, startDate, endDate);
console.log("期間内か:", isInRange); // 出力例: 期間内か: true

// 日付の曜日付きフォーマット
const date = new Date('2025-01-18');
const formattedDate = DateUtil.formatDateWithDayOfWeek(date);
console.log("フォーマット済み日付:", formattedDate); 
// 出力例: フォーマット済み日付: 1/18 (土)

// 日付範囲のフォーマット
const start = new Date('2025-01-20');
const end = new Date('2025-01-26');
const range = DateUtil.formatDateRange(start, end);
console.log("期間表示:", range); 
// 出力例: 期間表示: 1/20~1/26

5.3 よくあるエラーと対処法

DateUtilクラスを使用する際によく起こるエラーとその対処法を紹介します。

1. 日付が意図しない値になる

// 問題のコード
const date = new Date('2025/1/32'); // 無効な日付
console.log(date); // 出力例: 2025-02-01 ...

// 解決策:日付の妥当性チェック
function isValidDate(date) {
  return date instanceof Date && !isNaN(date.getTime());
}

const testDate = new Date('2025/1/32');
if (!isValidDate(testDate)) {
  console.log("無効な日付です");
}

2. 月の表記に関する注意

// 問題のコード
const date = new Date(2025, 1, 1);
console.log(DateUtil.toFormatString(date)); // 出力例: 2025/02/01(2月になる)

// 解決策:月は0始まりであることを意識する
const januaryDate = new Date(2025, 0, 1); // 1月を表すには0を指定
console.log(DateUtil.toFormatString(januaryDate)); // 出力例: 2025/01/01

3. タイムゾーンの問題

// 問題のコード
const date = new Date('2025-01-18T00:00:00');
console.log(date); // ブラウザの環境によっては時差が適用される

// 解決策:GASのUtilities.formatDateを使用
const formattedDate = Utilities.formatDate(
  date, 
  'Asia/Tokyo', // タイムゾーンを明示的に指定
  'yyyy/MM/dd HH:mm:ss'
);
console.log(formattedDate); // 出力例: 2025/01/18 00:00:00

6. DateUtilクラスの応用例 - 週次売上データの分析

DateUtilクラスを使った実践的な応用例として、スプレッドシートの週次売上データを分析するアプリケーションを作成します。

6.1 アプリケーションの目的と概要

  • 目的:週次の売上データを日付範囲でフィルタリングし、商品別・担当者別に集計する
  • 入力:スプレッドシートに記録された売上データ
  • 出力:指定期間の売上データ、商品別集計、担当者別集計

6.2 分析対象データ

スプレッドシートには以下のような形式で売上データが記録されています。

日付 商品名 売上金額 担当者
2025/01/06 商品A 1,200 山田
2025/01/07 商品B 2,300 鈴木
2025/01/08 商品A 1,500 田中
2025/01/13 商品C 3,000 山田
2025/01/14 商品B 2,100 鈴木
2025/01/15 商品A 1,800 田中

6.3 実装コード

/**
 * アプリケーションのエントリーポイント
 * 手動実行またはトリガーから呼び出されます
 */
function runSalesAnalysis() {
  const analysis = analyzeWeeklySales();
  displayResults(analysis);
}

/**
 * 週次売上データの分析を実行し、集計結果を返却します。
 * 
 * @returns 以下の3つの分析結果を含むオブジェクト
 *   - lastWeekData: 先週の売上データ
 *   - salesByProduct: 商品別の売上集計
 *   - salesByPerson: 担当者別の売上集計
 */
function analyzeWeeklySales() {
  // スプレッドシートからデータを取得
  const sheet = SpreadsheetApp.getActiveSheet();
  const dataRange = sheet.getDataRange();
  const values = dataRange.getValues();

  // 先週の日付範囲を取得
  const lastWeekRange = DateUtil.getLastWeekRange();
  
  // ヘッダー行を除いたデータ行を取得
  const dataRows = values.slice(1);
  
  // 分析結果を返却
  return {
    lastWeekData: filterByDateRange(dataRows, lastWeekRange),
    salesByProduct: analyzeSalesByProduct(dataRows),
    salesByPerson: analyzeSalesByPerson(dataRows)
  };
}

/**
 * 指定された期間内の売上データを抽出します。
 * 
 * @param dataRows 売上データの2次元配列(日付、商品名、売上金額、担当者)
 * @param dateRange 抽出対象期間(開始日と終了日を含むオブジェクト)
 * @returns 指定期間内の売上データ
 */
function filterByDateRange(dataRows: any[][], dateRange: { startDate: Date; endDate: Date }) {
  return dataRows.filter(row => {
    // 文字列の日付をDateオブジェクトに変換
    const rowDateStr = row[0]; // 例: "2025/01/06"
    let rowDate: Date;
    
    // 日付がすでにDateオブジェクトの場合とそうでない場合を処理
    if (rowDateStr instanceof Date) {
      rowDate = rowDateStr;
    } else {
      // 日付文字列からDateオブジェクトを作成
      rowDate = new Date(rowDateStr);
      
      // 無効な日付の場合はスキップ
      if (isNaN(rowDate.getTime())) {
        console.log(`無効な日付形式: ${rowDateStr}`);
        return false;
      }
    }
    
    // 指定された日付範囲内かどうかをチェック
    return DateUtil.isDateInRange(rowDate, dateRange.startDate, dateRange.endDate);
  });
}

/**
 * 商品別の売上合計を集計します。
 * 
 * @param dataRows 売上データの2次元配列(日付、商品名、売上金額、担当者)
 * @returns 商品名をキー、売上合計を値とするオブジェクト
 */
function analyzeSalesByProduct(dataRows: any[][]) {
  const salesByProduct = {};
  
  dataRows.forEach(row => {
    const productName = row[1]; // 商品名
    const salesAmount = parseFloat(String(row[2]).replace(/,/g, '')); // カンマを除去して数値化
    
    // 無効な金額はスキップ
    if (isNaN(salesAmount)) {
      console.log(`無効な売上金額: ${row[2]} (商品: ${productName})`);
      return;
    }
    
    // 商品ごとの売上を集計
    salesByProduct[productName] = (salesByProduct[productName] || 0) + salesAmount;
  });
  
  return salesByProduct;
}

/**
 * 担当者別の売上合計を集計します。
 * 
 * @param dataRows 売上データの2次元配列(日付、商品名、売上金額、担当者)
 * @returns 担当者名をキー、売上合計を値とするオブジェクト
 */
function analyzeSalesByPerson(dataRows: any[][]) {
  const salesByPerson = {};
  
  dataRows.forEach(row => {
    const personName = row[3]; // 担当者名
    const salesAmount = parseFloat(String(row[2]).replace(/,/g, '')); // カンマを除去して数値化
    
    // 無効な金額はスキップ
    if (isNaN(salesAmount)) {
      console.log(`無効な売上金額: ${row[2]} (担当者: ${personName})`);
      return;
    }
    
    // 担当者ごとの売上を集計
    salesByPerson[personName] = (salesByPerson[personName] || 0) + salesAmount;
  });
  
  return salesByPerson;
}

/**
 * 分析結果をシートに表示する
 * @param analysis 分析結果オブジェクト
 */
function displayResults(analysis) {
  // 結果表示用の新しいシートを作成(既に存在する場合は取得)
  let resultSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('分析結果');
  if (!resultSheet) {
    resultSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet('分析結果');
  } else {
    resultSheet.clear(); // 既存のデータをクリア
  }
  
  // 先週の日付範囲を取得(見出し用)
  const lastWeekRange = DateUtil.getLastWeekRange();
  const rangeTitle = `${DateUtil.formatDateWithDayOfWeek(lastWeekRange.startDate)}${DateUtil.formatDateWithDayOfWeek(lastWeekRange.endDate)}の分析結果`;
  
  // タイトルを設定
  resultSheet.getRange(1, 1).setValue(rangeTitle);
  resultSheet.getRange(1, 1).setFontWeight('bold');
  
  // セクション1: 先週のデータ
  resultSheet.getRange(3, 1).setValue('1. 先週の売上データ');
  resultSheet.getRange(3, 1).setFontWeight('bold');
  
  // ヘッダー
  resultSheet.getRange(4, 1, 1, 4).setValues([['日付', '商品名', '売上金額', '担当者']]);
  resultSheet.getRange(4, 1, 1, 4).setFontWeight('bold');
  
  // データがあれば表示
  if (analysis.lastWeekData.length > 0) {
    // 日付をフォーマット
    const formattedData = analysis.lastWeekData.map(row => {
      const date = row[0] instanceof Date ? row[0] : new Date(row[0]);
      return [
        DateUtil.formatDateWithDayOfWeek(date),
        row[1], // 商品名
        row[2], // 売上金額
        row[3]  // 担当者
      ];
    });
    resultSheet.getRange(5, 1, formattedData.length, 4).setValues(formattedData);
  } else {
    resultSheet.getRange(5, 1).setValue('該当するデータはありません');
  }
  
  // セクション2: 商品別集計
  resultSheet.getRange(7 + (analysis.lastWeekData.length || 1), 1).setValue('2. 商品別売上集計');
  resultSheet.getRange(7 + (analysis.lastWeekData.length || 1), 1).setFontWeight('bold');
  
  const productRow = 8 + (analysis.lastWeekData.length || 1);
  resultSheet.getRange(productRow, 1, 1, 2).setValues([['商品名', '売上合計']]);
  resultSheet.getRange(productRow, 1, 1, 2).setFontWeight('bold');
  
  const productData = Object.entries(analysis.salesByProduct).map(([product, sales]) => [product, sales]);
  resultSheet.getRange(productRow + 1, 1, productData.length, 2).setValues(productData);
  
  // セクション3: 担当者別集計
  resultSheet.getRange(productRow + 3 + productData.length, 1).setValue('3. 担当者別売上集計');
  resultSheet.getRange(productRow + 3 + productData.length, 1).setFontWeight('bold');
  
  const personRow = productRow + 4 + productData.length;
  resultSheet.getRange(personRow, 1, 1, 2).setValues([['担当者', '売上合計']]);
  resultSheet.getRange(personRow, 1, 1, 2).setFontWeight('bold');
  
  const personData = Object.entries(analysis.salesByPerson).map(([person, sales]) => [person, sales]);
  resultSheet.getRange(personRow + 1, 1, personData.length, 2).setValues(personData);
  
  // シートの列幅を自動調整
  resultSheet.autoResizeColumns(1, 4);
  
  // 完了メッセージを表示
  SpreadsheetApp.getUi().alert('分析結果を「分析結果」シートに出力しました。');
}

6.4 実装上のポイント

  1. エラーハンドリング

    • 無効な日付や数値の処理
    • データ不足の場合の対応
  2. パフォーマンス最適化

    • 日付処理における時刻のリセット
    • 繰り返し処理を最小限に抑える
  3. ユーザーインターフェース

    • 結果を見やすく整形
    • 日付に曜日を追加して可読性を向上
  4. メンテナンス性

    • 機能ごとに関数を分割
    • 明確な変数名と豊富なコメント

6.5 実行方法とカスタマイズ

この分析ツールは、次のいずれかの方法で実行できます。

  1. 手動実行

    • スプレッドシートのメニューから「ツール」→「スクリプトエディタ」を開き、runSalesAnalysis関数を実行
  2. 定期実行

    • トリガーを設定して毎週自動実行
    function createWeeklyTrigger() {
      ScriptApp.newTrigger('runSalesAnalysis')
        .timeBased()
        .onWeekDay(ScriptApp.WeekDay.MONDAY)
        .atHour(9)
        .create();
    }
    
  3. カスタムメニュー

    • スプレッドシートにカスタムメニューを追加して実行
    function onOpen() {
      SpreadsheetApp.getUi()
        .createMenu('売上分析')
        .addItem('週次分析を実行', 'runSalesAnalysis')
        .addToUi();
    }
    

7. DateUtilクラスの全コード

参考のため、DateUtilクラスの完全なコードを以下に示します。

/**
 * 日付操作のユーティリティクラス
 */
export class DateUtil {
  /**
   * 基準日から指定日数を加算または減算した日付を取得します
   * @param date 基準日
   * @param days 加算する日数(負数の場合は減算)
   * @param resetTime 時刻をリセットするかどうか(デフォルトはtrue)
   * @returns 計算後の日付
   */
  public static addDaysToDate(
    date: Date,
    days: number,
    resetTime: boolean = true
  ): Date {
    const resultDate = new Date(date);
    resultDate.setDate(resultDate.getDate() + days);

    if (resetTime) {
      resultDate.setHours(0, 0, 0, 0);
    }

    return resultDate;
  }
  
  /**
   * 現在日時から指定日数前の日付を取得します
   * @param days 遡る日数
   * @returns 指定日数前の日付
   */
  public static getDaysAgo(days: number): Date {
    return this.addDaysToDate(new Date(), -days);
  }

  /**
   * 来週の月曜日の日付を取得します
   * @returns 来週月曜日の日付
   */
  public static getNextMonday(): Date {
    const today = new Date();
    const nextMonday = new Date(today);
    nextMonday.setDate(today.getDate() + (8 - today.getDay()) % 7);
    nextMonday.setHours(0, 0, 0, 0);
    return nextMonday;
  }

  /**
   * 来週日曜日の日付を取得します
   * @returns 来週日曜日の日付
   */
  public static getNextSunday(): Date {
    const nextMonday = this.getNextMonday();
    const nextSunday = new Date(nextMonday);
    nextSunday.setDate(nextMonday.getDate() + 6);
    nextSunday.setHours(0, 0, 0, 0);
    return nextSunday;
  }

  /**
   * 来週の日付範囲を取得します
   * @returns 来週の月曜日と日曜日の日付
   */
  public static getNextWeekRange(): { startDate: Date; endDate: Date } {
    return {
      startDate: this.getNextMonday(),
      endDate: this.getNextSunday(),
    };
  }

  /**
   * 指定された日付が来週以降の日付かどうかを判定します
   * @param date 判定対象の日付
   * @returns 来週以降の日付の場合はtrue
   */
  public static isDateAfterNextWeek(date: Date): boolean {
    const nextMonday = this.getNextMonday();
    return date >= nextMonday;
  }

  /**
   * 基準日から指定日数後の日付を文字列形式で取得します
   * @param baseDate 基準日
   * @param days 進める日数
   * @returns yyyy/MM/dd形式の日付文字列
   */
  public static formatAddDays(baseDate: Date, days: number): string {
    return this.toFormatString(this.addDaysToDate(baseDate, days));
  }

  /**
   * 基準日から指定日数前の日付を文字列形式で取得します
   * @param baseDate 基準日
   * @param days 遡る日数
   * @returns yyyy/MM/dd形式の日付文字列
   */
  public static formatRemoveDays(baseDate: Date, days: number): string {
    return this.toFormatString(this.addDaysToDate(baseDate, -days));
  }

  /**
   * 日付を指定フォーマットの文字列に変換します。
   * @param date JavaScriptのDateオブジェクトまたはGASのDateオブジェクト
   * @returns yyyy/MM/dd形式の日付文字列
   */
  public static toFormatString(
    date: Date | GoogleAppsScript.Base.Date
  ): string {
    return Utilities.formatDate(date, 'Asia/Tokyo', 'yyyy/MM/dd');
  }

  /**
   * 2つの日付が同じ日かどうかを判定します
   * @param date1 比較する日付1
   * @param date2 比較する日付2
   * @returns 同じ日の場合はtrue、それ以外はfalse
   */
  public static isSameDate(date1: Date, date2: Date): boolean {
    return (
      date1.getFullYear() === date2.getFullYear() &&
      date1.getMonth() === date2.getMonth() &&
      date1.getDate() === date2.getDate()
    );
  }

  /**
   * 指定された日付が範囲内かどうかを判定します
   * @param date 判定する日付
   * @param startDate 範囲開始日
   * @param endDate 範囲終了日(この日を含む)
   * @returns boolean
   */
  public static isDateInRange(
    date: Date,
    startDate: Date,
    endDate: Date
  ): boolean {
    const normalizedDate = this.normalizeDate(date);
    const normalizedStart = this.normalizeDate(startDate);
    const normalizedEnd = this.normalizeDate(endDate);

    return normalizedDate >= normalizedStart && normalizedDate <= normalizedEnd;
  }

  /**
   * 日付の時刻部分を0時0分0秒に正規化します
   * 日付比較を行う際、時刻部分を無視して純粋に日付だけを比較するために使用します
   */
  private static normalizeDate(date: Date): Date {
    return new Date(date.getFullYear(), date.getMonth(), date.getDate());
  }

  /**
   * 日付を「MM/DD (曜日)」形式でフォーマットします
   * @param date フォーマットする日付
   * @returns MM/DD (曜日) 形式の文字列
   */
  public static formatDateWithDayOfWeek(date: Date): string {
    const dayOfWeek = ['日', '月', '火', '水', '木', '金', '土'][date.getDay()];
    return `${date.getMonth() + 1}/${date.getDate()} (${dayOfWeek})`;
  }

  /**
   * 日付範囲を「MM/DD~MM/DD」形式でフォーマットします
   * @param startDate 開始日
   * @param endDate 終了日
   * @returns MM/DD~MM/DD 形式の文字列
   */
  public static formatDateRange(startDate: Date, endDate: Date): string {
    return `${startDate.getMonth() + 1}/${startDate.getDate()}${
      endDate.getMonth() + 1
    }/${endDate.getDate()}`;
  }

  /**
   * 先週の月曜日の日付を取得します
   * @returns 先週月曜日の日付
   */
  public static getLastMonday(): Date {
    const today = new Date();
    const lastMonday = new Date(today);
    lastMonday.setDate(today.getDate() - (today.getDay() === 0 ? 6 : today.getDay() - 1) - 7);
    lastMonday.setHours(0, 0, 0, 0);
    return lastMonday;
  }

  /**
   * 先週の日曜日の日付を取得します
   * @returns 先週日曜日の日付
   */
  public static getLastSunday(): Date {
    const lastMonday = this.getLastMonday();
    const lastSunday = new Date(lastMonday);
    lastSunday.setDate(lastMonday.getDate() + 6);
    lastSunday.setHours(0, 0, 0, 0);
    return lastSunday;
  }

  /**
   * 先週の日付範囲を取得します
   * @returns 先週の月曜日と日曜日の日付
   */
  public static getLastWeekRange(): { startDate: Date; endDate: Date } {
    return {
      startDate: this.getLastMonday(),
      endDate: this.getLastSunday(),
    };
  }
}

8. メソッド一覧表

DateUtilクラスの各メソッドを機能別にまとめた一覧表です。

メソッド名 戻り値の型 機能説明 使用例
日付計算系
addDaysToDate Date 基準日から指定日数を加算/減算 DateUtil.addDaysToDate(date, 3)
getDaysAgo Date 現在から指定日数前の日付 DateUtil.getDaysAgo(7)
週関連
getNextMonday Date 来週の月曜日を取得 DateUtil.getNextMonday()
getNextSunday Date 来週の日曜日を取得 DateUtil.getNextSunday()
getNextWeekRange Object 来週の日付範囲を取得 DateUtil.getNextWeekRange()
getLastMonday Date 先週の月曜日を取得 DateUtil.getLastMonday()
getLastSunday Date 先週の日曜日を取得 DateUtil.getLastSunday()
getLastWeekRange Object 先週の日付範囲を取得 DateUtil.getLastWeekRange()
文字列変換系
formatAddDays string 基準日から指定日数後の日付を文字列で取得 DateUtil.formatAddDays(date, 3)
formatRemoveDays string 基準日から指定日数前の日付を文字列で取得 DateUtil.formatRemoveDays(date, 5)
toFormatString string 日付をYYYY/MM/DD形式に変換 DateUtil.toFormatString(date)
formatDateWithDayOfWeek string 日付をMM/DD (曜日)形式で取得 DateUtil.formatDateWithDayOfWeek(date)
formatDateRange string 日付範囲をMM/DD~MM/DD形式で取得 DateUtil.formatDateRange(start, end)
比較検証系
isDateAfterNextWeek boolean 指定日が来週以降かどうか判定 DateUtil.isDateAfterNextWeek(date)
isSameDate boolean 2つの日付が同じ日かどうか判定 DateUtil.isSameDate(date1, date2)
isDateInRange boolean 指定日が日付範囲内かどうか判定 DateUtil.isDateInRange(date, start, end)
内部メソッド
normalizeDate Date 日付の時刻部分を0時0分0秒に正規化 (内部使用)

9. まとめ

本記事では、GASとTypeScriptを使った日付操作ユーティリティ「DateUtil」クラスの実装と活用方法について解説しました。

主なポイント

  1. 日付操作の基本と応用

    • JavaScriptのDateオブジェクトの基本
    • GAS特有の日付操作機能
    • 実務で役立つメソッドの実装
  2. コードの可読性と再利用性

    • TypeScriptによる型安全な実装
    • 一貫した命名規則
    • 豊富なコメントとドキュメント
  3. 実践的な応用例

    • 週次データの分析
    • スプレッドシートとの連携
    • エラーハンドリングの実装

活用シーン

  • 定期レポート作成:週次、月次の売上集計や業務レポートの自動化
  • スケジュール管理:日付範囲に基づくタスク管理や予定表作成
  • データ分析:特定期間のデータ抽出と集計
  • カレンダー連携:Googleカレンダーへのイベント登録自動化

DateUtilクラスを使いこなすことで、GASアプリケーションの開発効率が大幅に向上します。本記事で紹介したコードやテクニックを、ぜひ皆様のプロジェクトにお役立てください。

最後までお読みいただき、ありがとうございました。

参考リンク

Discussion