JavaScriptで年を基準に月、四半期、半期、年単位の期間を生成する方法

JavaScriptで年を基準に、月、四半期、半期、年単位の期間データを動的に生成する関数を実装します。この関数は、指定された開始年から現在の年までの期間データを生成し、表示形式をカスタマイズできます。

function TimePeriodGenerator() {
  let today = new Date();
  let currentYear = today.getFullYear();
  let currentMonth = today.getMonth() + 1;
  
  this.data = [];
  this.periodType = "";
  this.startYear = "";
  this.currentMonth = currentMonth;
  this.currentYear = currentYear;

  /**
   * 関数を初期化し、指定されたパラメータに基づいて期間データを生成します
   * @param {number} year - 4桁の開始年
   * @param {number} type - 期間タイプ(1:月度, 2:四半期, 3:半期, 4:年度)
   * @param {boolean} includeCurrent - 現在の期間を含めるかどうか
   * @param {boolean} isAscending - 昇順ソートかどうか
   * @param {number} sortRule - ソートルール(1:月単位, 4:年単位)
   * @returns {Array} 生成された期間データ
   */
  this.initialize = function(year, type, includeCurrent = false, isAscending = false, sortRule = 4) {
    this.periodType = type;
    this.startYear = year;
    this.includeCurrent = includeCurrent;
    this.data = [];
    this.setStartYear();
    this.setPeriodType();
    let copiedData = JSON.parse(JSON.stringify(this.data));
    let sortKey = sortRule === 1 ? 'value' : 'year';
    this.data = copiedData.sort(this.sortByDate(sortKey, isAscending));
    return this.data;
  };

  /**
   * 期間タイプを設定し、対応するデータ生成メソッドを呼び出します
   */
  this.setPeriodType = function() {
    let type = this.periodType || 4;
    switch(type) {
      case 1:
        this.periodLabel = '月';
        this.generateMonthlyData();
        break;
      case 2:
        this.periodLabel = '四半期';
        this.generateQuarterlyData();
        break;
      case 3:
        this.periodLabel = '半期';
        this.generateSemiannualData();
        break;
      case 4:
        this.periodLabel = '年度';
        this.generateYearlyData();
        break;
    }
  };

  /**
   * 開始年を設定します。指定がない場合は現在の年を使用します
   */
  this.setStartYear = function() {
    let year = this.startYear;
    if (year && Object.prototype.toString.call(year) !== "[object Number]") {
      year = Number(year);
    } else if (!year) {
      year = currentYear;
    }
    this.startYear = year;
  };

  /**
   * 配列内の重複オブジェクトを削除します
   * @param {Array} array - 処理対象の配列
   * @param {string} key - 重複判定のためのキー
   * @returns {Array} 重複削除後の配列
   */
  this.removeDuplicates = function(array, key) {
    const uniqueMap = {};
    return array.filter(item => {
      const keyValue = item[key];
      if (!uniqueMap[keyValue]) {
        uniqueMap[keyValue] = true;
        return true;
      }
      return false;
    });
  };

  /**
   * 配列を指定されたキーでソートします
   * @param {Array} dataArray - ソート対象の配列
   * @param {string} key - ソートキー
   * @returns {Array} ソート後の配列
   */
  this.sortArray = function(dataArray, key) {
    return dataArray.sort((a, b) => {
      if (a[key].toString() !== b[key].toString()) {
        return a[key].toString().localeCompare(b[key].toString());
      }
      return 0;
    });
  };

  /**
   * 日付データをソートします
   * @param {string} property - ソート対象のプロパティ
   * @param {boolean} ascending - 昇順かどうか
   * @returns {Function} ソート関数
   */
  this.sortByDate = function(property, ascending) {
    return function(a, b) {
      const value1 = a[property];
      const value2 = b[property];
      if (ascending) {
        return Date.parse(value1) - Date.parse(value2);
      } else {
        return Date.parse(value2) - Date.parse(value1);
      }
    };
  };

  /**
   * 年度データを生成します
   */
  this.generateYearlyData = function() {
    if (this.startYear === currentYear) {
      this.data.push({
        year: this.startYear,
        value: 1,
        type: this.periodType,
        label: this.startYear + '年'
      });
    } else if (this.startYear < currentYear) {
      for (let i = this.startYear; i <= currentYear; i++) {
        this.data.push({
          year: i,
          value: 1,
          type: this.periodType,
          label: i + '年'
        });
      }

      if (!this.includeCurrent && this.periodType === 4) {
        this.data = this.data.filter(item => item.year !== currentYear);
      }
    }
  };

  /**
   * 半期データを生成します
   */
  this.generateSemiannualData = function() {
    this.generateYearlyData();
    let copiedData = JSON.parse(JSON.stringify(this.data));
    copiedData = copiedData.concat(this.data);
    let sortedData = this.sortArray(copiedData);
    
    let deleteIndex = null;
    let periodValue = 1;
    let periodLabel = "上半期";
    
    for (let i = 0; i < sortedData.length; i++) {
      if (currentMonth > 6 && i % 2 === 0) {
        periodValue = 1;
      } else if (currentMonth > 6 && currentMonth < 12 && i % 2 !== 0) {
        periodValue = 2;
      }
      
      periodLabel = periodValue === 1 ? '上半期' : '下半期';
      sortedData[i].value = periodValue;
      sortedData[i].label = sortedData[i].year + '年' + periodLabel;
      
      if (sortedData[i].year === currentYear && sortedData[i].value === 2) {
        deleteIndex = i;
      }
    }
    
    if (!this.includeCurrent) {
      sortedData.splice(deleteIndex, 1);
    }
    
    this.data = sortedData;
  };

  /**
   * 四半期データを生成します
   */
  this.generateQuarterlyData = function() {
    this.generateMonthlyData();
    let processedData = this.data.map(item => {
      let quarter = Math.ceil(item.value / 3);
      item.value = quarter;
      item.label = item.year + '年' + quarter + '四半期';
      return item;
    });
    
    this.data = this.removeDuplicates(processedData, 'label');
  };

  /**
   * 月度データを生成します
   */
  this.generateMonthlyData = function() {
    this.generateYearlyData();
    let monthlyData = [];
    let copiedData = JSON.parse(JSON.stringify(this.data));
    let maxMonth = currentMonth;
    
    for (let i = 0; i < copiedData.length; i++) {
      if (copiedData[i].year < currentYear) {
        maxMonth = 12;
      } else if (copiedData[i].year === currentYear) {
        maxMonth = currentMonth;
      }
      
      for (let m = 1; m <= maxMonth; m++) {
        monthlyData.push({
          year: copiedData[i].year,
          value: m,
          type: this.periodType,
          label: copiedData[i].year + '年' + m + '月'
        });
      }
    }
    
    if (!this.includeCurrent) {
      monthlyData = monthlyData.filter(item => 
        !(item.year === currentYear && item.value === currentMonth)
      );
    }
    
    this.data = monthlyData;
  };
}

関数の使い方

この関数を使用する前に、まずTimePeriodGeneratorをインスタンス化する必要があります。

// 関数をインスタンス化
const timeGenerator = new TimePeriodGenerator();

// データを生成(現在の月を含めない場合)
const monthlyData = timeGenerator.initialize(2023, 1, false);

// データを生成(現在の月を含める場合)
const monthlyDataWithCurrent = timeGenerator.initialize(2023, 1, true);

console.log("生成されたデータ:", timeGenerator.data);
console.log("月度データ:", monthlyData);
console.log("現在の月を含むデータ:", monthlyDataWithCurrent);

パラメータの説明

  • year: 4桁の開始年を文字列または数値で指定
  • type: 期間タイプ(1:月度, 2:四半期, 3:半期, 4:年度)
  • includeCurrent: 現在の期間を含めるかどうか(true:含める, false:含めない)
  • isAscending: ソート順(true:昇順, false:降順)
  • sortRule: ソートルール(1:月単位, 4:年単位)

データ生成メソッドの詳細

  1. generateYearlyData: 年度データを生成します。半期、月度データの生成にも呼び出されます。
  2. generateSemiannualData: 半期データを生成します。年度データを基にして上半期と下半期を生成します。
  3. generateQuarterlyData: 四半期データを生成します。月度データを基にして四半期を計算します。
  4. generateMonthlyData: 月度データを生成します。年度データを基にして各月のデータを生成します。

この関数は、ビジネスアプリケーションでの期間選択、レポート生成、データ分析など、様々なシナリオで活用できます。

タグ: javascript date-handling time-periods data-generation utility-functions

6月10日 20:13 投稿