You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Google Apps Script中Properties Service存储end_time异常问题排查

问题分析与解决方案

你的核心问题出在全局共享的Script Properties被并发写入覆盖,加上代码里没必要的读写逻辑放大了问题,以下是具体原因和修复方案:

可能的原因

  1. Script Properties的全局共享特性
    Script Properties是整个脚本的全局存储,所有用户的函数执行都会读写同一个键值对。如果多个用户(或同一用户多次触发)同时调用getEventByMeetingRooms,就会出现并发写入冲突:比如用户A刚把计算好的end_time存入,用户B的执行立刻用自己计算的end_time覆盖掉,而用户A的循环还在读取这个值,自然会拿到错误的日期。

  2. 不必要的全局存储逻辑
    你的代码里,start_time和end_time是当前执行上下文计算出来的临时值,完全没必要存入Script Properties再读取。这种画蛇添足的操作,直接把局部变量的安全逻辑变成了全局共享的风险逻辑。

  3. Date对象的隐式转换风险
    直接把Date对象存入Properties Service时,会自动调用toString()转成字符串,不同执行环境的字符串格式可能存在差异,读取时用new Date()解析可能出现意外(比如时区、格式解析错误),不过这不是你当前问题的主因,但也是潜在隐患。

修复方案

方案1:移除Script Properties的多余使用(最优解)

既然start_time和end_time是当前执行的临时计算值,直接用局部变量即可,完全不需要全局存储。修改后的核心代码如下:

function getEventByMeetingRooms(d = 7) {
  let meeting_rooms = listaSalasDeReunioes_();
  let ss = SpreadsheetApp.openById("xxxx");
  var ws = ss.getSheetByName("xxxxxxxxxx");

  // 直接计算局部变量,不再用Script Properties
  let start_time = new Date(new Date().getFullYear(), new Date().getMonth(), 1);
  let end_time = new Date(new Date().getFullYear(), new Date().getMonth() + d, 0);

  var arr = [];

  if (meeting_rooms.length > 0) {
    for (let i = 0; i < meeting_rooms.length; i++) {
      console.log(meeting_rooms[i][2]);
      try {
        console.log("start_time: ", start_time, " / end_time: ", end_time);

        if (start_time > end_time) {
          console.log("Erro ao processar evento: Data de fim é menor que a data de inicio");
          continue;
        }
        
        var calendar = CalendarApp.subscribeToCalendar(meeting_rooms[i][2]);
        let eventos = calendar.getEvents(start_time, end_time);

        if (eventos.length > 0) {
          eventos.forEach((ev) => {
            arr.push([ev.getId(), ev.getTitle(), ev.getStartTime().toLocaleDateString(), ev.getStartTime().toLocaleTimeString(), ev.getEndTime().toLocaleDateString(), ev.getEndTime().toLocaleTimeString(), ev.getDescription(), meeting_rooms[i][2]]);
          });
        }
      } catch (e) {
        console.log(e);
      }
    }
  } else {
    console.log("O Array contendo as salas de reuniões veio vazio.");
  }

  // 后续的表格更新逻辑不变
  if (arr.length > 0) {
    ws.getRange(2, 1, ws.getLastRow(), ws.getLastColumn()).clearContent();
    ws.getRange(2, 1, arr.length, arr[0].length).setValues(arr);
  } else {
    console.log("Array de eventos vazios, sem atualização na planilha.")
  }
}

方案2:如果确实需要全局共享日期范围(加锁防并发)

如果你有跨执行共享日期范围的需求,必须用Lock Service来保证读写的原子性,避免并发覆盖:

function getEventByMeetingRooms(d = 7) {
  let meeting_rooms = listaSalasDeReunioes_();
  let ss = SpreadsheetApp.openById("xxxx");
  var ws = ss.getSheetByName("xxxxxxxxxx");

  var cache_script = PropertiesService.getScriptProperties();
  // 获取锁,等待最多30秒
  var lock = LockService.getScriptLock();
  lock.waitLock(30000);

  try {
    // 加锁后再计算并存储日期
    let start_time = new Date(new Date().getFullYear(), new Date().getMonth(), 1);
    let end_time = new Date(new Date().getFullYear(), new Date().getMonth() + d, 0);
    // 存储时转成ISO字符串,避免解析问题
    cache_script.setProperty("start_time", start_time.toISOString());
    cache_script.setProperty("end_time", end_time.toISOString());
  } finally {
    // 释放锁
    lock.releaseLock();
  }

  var arr = [];

  if (meeting_rooms.length > 0) {
    for (let i = 0; i < meeting_rooms.length; i++) {
      console.log(meeting_rooms[i][2]);
      try {
        // 读取时解析ISO字符串
        let storedStart = new Date(cache_script.getProperty("start_time"));
        let storedEnd = new Date(cache_script.getProperty("end_time"));
        console.log("start_time: ", storedStart, " / end_time: ", storedEnd);

        if (storedStart > storedEnd) {
          console.log("Erro ao processar evento: Data de fim é menor que a data de inicio");
          continue;
        }
        
        var calendar = CalendarApp.subscribeToCalendar(meeting_rooms[i][2]);
        let eventos = calendar.getEvents(storedStart, storedEnd);

        if (eventos.length > 0) {
          eventos.forEach((ev) => {
            arr.push([ev.getId(), ev.getTitle(), ev.getStartTime().toLocaleDateString(), ev.getStartTime().toLocaleTimeString(), ev.getEndTime().toLocaleDateString(), ev.getEndTime().toLocaleTimeString(), ev.getDescription(), meeting_rooms[i][2]]);
          });
        }
      } catch (e) {
        console.log(e);
      }
    }
  } else {
    console.log("O Array contendo as salas de reuniões veio vazio.");
  }

  // 后续表格更新逻辑不变
  if (arr.length > 0) {
    ws.getRange(2, 1, ws.getLastRow(), ws.getLastColumn()).clearContent();
    ws.getRange(2, 1, arr.length, arr[0].length).setValues(arr);
  } else {
    console.log("Array de eventos vazios, sem atualização na planilha.")
  }
}

关键提醒

  • 永远不要把局部执行的临时值存入全局共享的存储(Script Properties),这是并发问题的根源。
  • 存储日期时,优先用ISO字符串(toISOString())或时间戳(getTime()),避免直接存Date对象导致的解析异常。

内容的提问来源于stack exchange,提问作者João Paiva

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 17:24:54