【GAS】ULID風の一意なIDを生成する方法

### ULIDとは？
ULIDは、UUID（Universally Unique Identifier）の代替として設計された識別子で、次の特徴を持ちます。

- ソート可能：タイムスタンプが含まれており、時間順に並べることが可能
- 一意性：十分にランダムな識別子を生成
- URLセーフ：Base32 でエンコードされ、URL などで扱いやすい

ULIDのフォーマットは以下のようになっています。

```
01H0ZK1WBP8TY5G5VME3TGZ7EX
```

- 最初の10文字：タイムスタンプ部分（ミリ秒単位のUNIX時間）
- 残りの16文字：ランダム部分（エンコードされたランダム値）

GASには crypto ライブラリがないため、Utilities.getUuid() などを使わずに、手動でULIDのようなIDを生成します。

### 処理の流れ
- enerateULID()

    スプレッドシートで選択されたセル範囲を取得
    選択範囲の各セルにULIDを挿入

    ```javascript
    function generateULID() {
      var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
      var range = sheet.getActiveRange();
      
      if (!range) {
        SpreadsheetApp.getUi().alert("セール未選択");
        return;
      }
      
      for (var i = 0; i < range.getNumRows(); i++) {
        for (var j = 0; j < range.getNumColumns(); j++) {
          console.log((i + 1 )+" : "+ (j + 1))
          range.getCell(i + 1, j + 1).setValue(createULID());
        }
      }
    }
    ```

- createULID()

    現在のタイムスタンプを取得
    ランダムな80ビットの値を生成
    エンコード処理を行い、26文字のULIDを作成

    ```javascript
    function createULID() {
      var timestamp = Date.now();  // Get current timestamp in milliseconds
      
      //  80-bit 作成
      var randomPart = [];
      for (var i = 0; i < 10; i++) {
        randomPart.push(Math.floor(Math.random() * 256));
      }
      return encodeULID(timestamp, randomPart);   
    }
    
    ```

- encodeULID(timestamp, randomPart)

    - Base32エンコード文字セットを定義
    - 48ビットのタイムスタンプをBase32に変換（10文字）
    - 80ビットのランダム値をBase32に変換（16文字）
    - 最終的なULID（26文字）を返す

    ```javascript
    function encodeULID(timestamp, randomPart) {
      var encoding = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; 
    
      // Encode timestamp (48-bit = 10 characters in Base32)
      var timeChars = [];
      for (var i = 9; i >= 0; i--) {
        timeChars[i] = encoding.charAt(timestamp % 32);
        timestamp = Math.floor(timestamp / 32);
      }
    
      var randChars = [];
      var value = 0;
      var bitLength = 0;
    
      for (var i = 0; i < randomPart.length; i++) {
        value = (value << 8) | randomPart[i];
        bitLength += 8;
    
        while (bitLength >= 5) {
          bitLength -= 5;
          randChars.push(encoding.charAt((value >> bitLength) & 31));
        }
      }
      
      while (randChars.length < 16) {
        randChars.push(encoding.charAt(0)); // Pad with '0' if needed
      }
    
      // 26-character ULID
      return timeChars.join("") + randChars.join("");
    }  

    ```

### サンプルコード



```javascript
function generateULID() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var range = sheet.getActiveRange();
  
  if (!range) {
    SpreadsheetApp.getUi().alert("セール未選択");
    return;
  }
  
  for (var i = 0; i < range.getNumRows(); i++) {
    for (var j = 0; j < range.getNumColumns(); j++) {
      console.log((i + 1 )+" : "+ (j + 1))
      range.getCell(i + 1, j + 1).setValue(createULID());
    }
  }
}


function createULID() {
  var timestamp = Date.now();  // Get current timestamp in milliseconds
  
  //  80-bit 作成
  var randomPart = [];
  for (var i = 0; i < 10; i++) {
    randomPart.push(Math.floor(Math.random() * 256));
  }
  return encodeULID(timestamp, randomPart);   
}

function encodeULID(timestamp, randomPart) {
  var encoding = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; 

  // Encode timestamp (48-bit = 10 characters in Base32)
  var timeChars = [];
  for (var i = 9; i >= 0; i--) {
    timeChars[i] = encoding.charAt(timestamp % 32);
    timestamp = Math.floor(timestamp / 32);
  }

  var randChars = [];
  var value = 0;
  var bitLength = 0;

  for (var i = 0; i < randomPart.length; i++) {
    value = (value << 8) | randomPart[i];
    bitLength += 8;

    while (bitLength >= 5) {
      bitLength -= 5;
      randChars.push(encoding.charAt((value >> bitLength) & 31));
    }
  }
  
  while (randChars.length < 16) {
    randChars.push(encoding.charAt(0)); // Pad with '0' if needed
  }

  // 26-character ULID
  return timeChars.join("") + randChars.join("");
}

```

### 生成データ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155256/692ac387-c86a-1539-6ca8-ce0b1842d834.png)


### まとめ

- GASでULIDを生成し、スプレッドシートに挿入可能
- 時間順にソート可能なID を作成
- Base32エンコードを利用してUUIDより短く使いやすい
- スプレッドシートの選択範囲に一括挿入 できる