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

Next.js生产环境Google Calendar API时区偏移致预约冲突错误

解决Google Calendar API时间偏移4小时的实操方案

问题根源

  • Vercel生产服务器默认使用UTC时区,而本地开发环境是UTC+4时区,时间处理时未统一时区标准,要么把API返回的UTC时间当成了本地时区,要么本地时间转UTC时未对齐,直接差出4小时。
  • 使用date-fns指定时区但未覆盖全流程:数据库取时间、API请求传参、返回结果解析,只要某一步没按统一时区处理,冲突校验就会失效。

分步解决

1. 给Google Calendar API明确指定时区

调用freebusy.query接口时,必须同时完成两件事:

  • 将请求的timeMin/timeMax转成UTC后传参
  • 给API指定timeZone参数(和业务使用的时区一致,比如"Asia/Shanghai"),让API返回对应时区的时间,或返回带时区标识的UTC字符串

修正后的服务器端代码示例:

// 服务器端处理函数
import { format, utcToZonedTime, zonedTimeToUtc } from 'date-fns-tz';

// 固定业务时区,建议存入环境变量而非硬编码
const BUSINESS_TIMEZONE = 'Asia/Shanghai';

export async function filterConflictingSlots(availableSlots) {
  // 把当前时间和7天后的时间转成UTC,作为API请求的时间范围
  const timeMin = zonedTimeToUtc(new Date(), BUSINESS_TIMEZONE);
  const timeMax = zonedTimeToUtc(new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), BUSINESS_TIMEZONE);

  const calendarResponse = await google.calendar('v3').freebusy.query({
    auth: yourCalendarAuth,
    requestBody: {
      timeMin: timeMin.toISOString(),
      timeMax: timeMax.toISOString(),
      timeZone: BUSINESS_TIMEZONE, // 关键:告诉API返回对应时区的时间
      items: [{ id: 'primary' }]
    }
  });

  // 把API返回的忙碌时段转成业务时区的Date对象
  const busySlots = calendarResponse.data.calendars.primary.busy.map(busy => ({
    start: utcToZonedTime(new Date(busy.start), BUSINESS_TIMEZONE),
    end: utcToZonedTime(new Date(busy.end), BUSINESS_TIMEZONE)
  }));

  // 过滤冲突:只要时段有重叠就排除
  return availableSlots.filter(slot => {
    const slotStart = utcToZonedTime(new Date(slot.start), BUSINESS_TIMEZONE);
    const slotEnd = utcToZonedTime(new Date(slot.end), BUSINESS_TIMEZONE);
    return !busySlots.some(busy => slotStart < busy.end && slotEnd > busy.start);
  });
}

2. 数据库时间存读要统一

  • 数据库里的可用时段建议存储UTC时间(比如PostgreSQL用TIMESTAMPTZ类型),不要存本地时区的时间,避免环境差异导致的偏差。
  • 读取数据库时间时,统一转成业务时区再处理,不要直接用new Date()自动转换。

3. 客户端时间处理对齐服务器

客户端展示或校验时间时,必须和服务器使用同一个时区转换逻辑,不要依赖浏览器本地时区:

// 客户端组件示例
import { format, utcToZonedTime } from 'date-fns-tz';

const BUSINESS_TIMEZONE = 'Asia/Shanghai';

export default function AvailableSlots({ slots }) {
  return (
    <div className="slots-container">
      {slots.map(slot => (
        <div key={slot.id} className="slot-item">
          {/* 把UTC时间转成业务时区后再格式化展示 */}
          {format(utcToZonedTime(new Date(slot.start), BUSINESS_TIMEZONE), 'yyyy-MM-dd HH:mm', { timeZone: BUSINESS_TIMEZONE })}
          -
          {format(utcToZonedTime(new Date(slot.end), BUSINESS_TIMEZONE), 'yyyy-MM-dd HH:mm', { timeZone: BUSINESS_TIMEZONE })}
        </div>
      ))}
    </div>
  );
}

4. 给Vercel服务器指定时区

在Vercel项目的环境变量中添加TZ,值设为你的业务时区(比如Asia/Shanghai),确保服务器运行时使用指定时区,避免默认UTC导致的时间计算偏差。

验证方式

  • 在生产环境日志里同时打印UTC时间和业务时区时间,对比确认是否对齐:
console.log('UTC时间:', new Date().toISOString());
console.log('业务时区时间:', format(utcToZonedTime(new Date(), BUSINESS_TIMEZONE), 'yyyy-MM-dd HH:mm', { timeZone: BUSINESS_TIMEZONE }));
  • 检查Google Calendar API返回的busy时段,看start/end是否带时区标识(比如2024-05-20T10:00:00+08:00),如果是纯UTC字符串(2024-05-20T02:00:00Z),必须手动转成业务时区再处理。

内容的提问来源于stack exchange,提问作者Cip

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 20:12:01