查看: 187|回复: 0

鸿蒙元服务ASCF日历日程:授权、时间戳类型与重复规则避坑

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
在鸿蒙元服务开发中,预约成功、会议提醒、生日或健身计划等场景,经常需要把日程自动写入系统日历。ASCF 提供了两个日历 API:addPhoneCalendar 用于添加单次日程,addPhoneRepeatCalendar 用于添加重复日程。它们能减少用户手动打开日历应用的操作,但实际接入时,授权、时间戳类型和重复规则很容易踩坑。下面按从权限到场景的顺序梳理。

一、权限要两步:声明 + 运行时授权

日历 API 的权限比较特殊,不是只在 module.json5 声明就能用。第一步是在 module.json5 中声明 ohos.permission.READ_CALENDAR 和 ohos.permission.WRITE_CALENDAR:
  1. {
  2.   "requestPermissions": [
  3.     {
  4.       "name": "ohos.permission.READ_CALENDAR",
  5.       "reason": "用于读取日历信息",
  6.       "usedScene": {
  7.         "abilities": ["EntryAbility"],
  8.         "when": "inuse"
  9.       }
  10.     },
  11.     {
  12.       "name": "ohos.permission.WRITE_CALENDAR",
  13.       "reason": "用于添加日历日程",
  14.       "usedScene": {
  15.         "abilities": ["EntryAbility"],
  16.         "when": "inuse"
  17.       }
  18.     }
  19.   ]
  20. }
复制代码

第二步是在调用 API 前申请 scope.addPhoneCalendar 运行时授权。原文作者一开始以为声明权限就够了,结果调用一直报错,后来才发现还需要这一步:
  1. has.authorize({
  2.   scope: 'scope.addPhoneCalendar',
  3.   success: () => {
  4.     console.info('授权成功');
  5.     // 这里才能调用日历 API
  6.   },
  7.   fail: (err) => {
  8.     console.error('授权失败:', err);
  9.   }
  10. });
复制代码

二、添加单次日程:addPhoneCalendar

授权成功后,可以用 addPhoneCalendar 添加单次日程。下面示例把当前时间往后推 1 小时作为开始时间,2 小时后作为结束时间,并提前 5 分钟提醒:
  1. has.authorize({
  2.   scope: 'scope.addPhoneCalendar',
  3.   success: () => {
  4.     const now = Math.floor(Date.now() / 1000);
  5.     const startTime = now + 3600; // 1小时后
  6.     const endTime = now + 7200; // 2小时后
  7.     has.addPhoneCalendar({
  8.       title: '项目评审会议',
  9.       startTime: startTime,
  10.       endTime: endTime,
  11.       description: '讨论 Q4 项目进展',
  12.       location: '会议室 A301',
  13.       alarm: true,
  14.       alarmOffset: 300, // 提前 5 分钟提醒
  15.       success: () => {
  16.         console.info('日程添加成功');
  17.         has.showToast({ title: '已添加到日历' });
  18.       },
  19.       fail: (err) => {
  20.         console.error('添加失败:', err);
  21.       }
  22.     });
  23.   }
  24. });
复制代码

这里最关键的是 startTime 和 endTime 都是 unix 时间戳,单位是秒,不是毫秒。JavaScript 的 Date.now() 返回毫秒,因此要除以 1000 并取整。

三、添加重复日程:addPhoneRepeatCalendar

重复日程使用 addPhoneRepeatCalendar。示例创建一个每周重复的周会,提前 10 分钟提醒,并设置 3 个月后结束重复:
  1. has.authorize({
  2.   scope: 'scope.addPhoneCalendar',
  3.   success: () => {
  4.     const now = Math.floor(Date.now() / 1000);
  5.     const startTime = now + 3600;
  6.     const endTime = now + 7200;
  7.     // 重复结束时间:3个月后
  8.     const repeatEndTime = now + 90 * 24 * 3600;
  9.     has.addPhoneRepeatCalendar({
  10.       title: '周会',
  11.       startTime: startTime,
  12.       endTime: endTime,
  13.       description: '每周例会',
  14.       location: '线上会议',
  15.       alarm: true,
  16.       alarmOffset: 600, // 提前 10 分钟
  17.       repeatInterval: 'week',
  18.       repeatEndTime: repeatEndTime,
  19.       success: () => {
  20.         console.info('重复日程添加成功');
  21.         has.showToast({ title: '已添加到日历' });
  22.       },
  23.       fail: (err) => {
  24.         console.error('添加失败:', err);
  25.       }
  26.     });
  27.   }
  28. });
复制代码

repeatInterval 默认是 month。如果不传 repeatEndTime,重复日程会一直重复下去。

四、常见业务场景

预约成功后添加日程:预约系统可以在用户预约成功后,把服务名、预约时间、地址、订单号写进日程,并提前 30 分钟提醒。
  1. Page({
  2.   handleAppointmentSuccess(appointment) {
  3.     has.authorize({
  4.       scope: 'scope.addPhoneCalendar',
  5.       success: () => {
  6.         has.addPhoneCalendar({
  7.           title: '预约: ' + appointment.serviceName,
  8.           startTime: appointment.timestamp,
  9.           endTime: appointment.timestamp + 3600,
  10.           location: appointment.address,
  11.           description: '预约编号: ' + appointment.orderNo,
  12.           alarm: true,
  13.           alarmOffset: 1800, // 提前 30 分钟
  14.           success: () => {
  15.             console.info('预约日程已添加');
  16.           }
  17.         });
  18.       }
  19.     });
  20.   }
  21. });
复制代码

生日提醒(每年重复):根据生日字符串构造今年的生日时间戳;如果今年生日已过,就用明年的。然后以 year 为重复周期添加,allDay 设为 true,提前 1 天提醒。
  1. Page({
  2.   addBirthdayReminder(name, birthday) {
  3.     // birthday 格式: '01-15' 表示 1月15日
  4.     const parts = birthday.split('-');
  5.     const now = new Date();
  6.     const year = now.getFullYear();
  7.     // 构造今年的生日时间戳
  8.     const birthdayDate = new Date(year, parseInt(parts[0]) - 1, parseInt(parts[1]), 9, 0, 0);
  9.     let startTime = Math.floor(birthdayDate.getTime() / 1000);
  10.     // 如果今年的生日已过,用明年的
  11.     if (startTime < Math.floor(Date.now() / 1000)) {
  12.       birthdayDate.setFullYear(year + 1);
  13.       startTime = Math.floor(birthdayDate.getTime() / 1000);
  14.     }
  15.     has.authorize({
  16.       scope: 'scope.addPhoneCalendar',
  17.       success: () => {
  18.         has.addPhoneRepeatCalendar({
  19.           title: name + ' 的生日',
  20.           startTime: startTime,
  21.           allDay: true,
  22.           alarm: true,
  23.           alarmOffset: 86400, // 提前1天
  24.           repeatInterval: 'year',
  25.           success: () => {
  26.             has.showToast({ title: '生日提醒已添加' });
  27.           }
  28.         });
  29.       }
  30.     });
  31.   }
  32. });
复制代码

健身计划(每周重复):从下周周一早上 8 点开始,持续 1 小时,重复 3 个月。
  1. Page({
  2.   addFitnessPlan() {
  3.     // 下周一早上 8 点
  4.     const now = new Date();
  5.     const dayOfWeek = now.getDay();
  6.     const daysUntilMonday = dayOfWeek === 0 ? 1 : 8 - dayOfWeek;
  7.     const nextMonday = new Date(now.getFullYear(), now.getMonth(), now.getDate() + daysUntilMonday, 8, 0, 0);
  8.     const startTime = Math.floor(nextMonday.getTime() / 1000);
  9.     const endTime = startTime + 3600; // 1小时
  10.     const repeatEndTime = startTime + 90 * 24 * 3600; // 3个月后
  11.     has.authorize({
  12.       scope: 'scope.addPhoneCalendar',
  13.       success: () => {
  14.         has.addPhoneRepeatCalendar({
  15.           title: '健身时间',
  16.           startTime: startTime,
  17.           endTime: endTime,
  18.           location: '健身房',
  19.           description: '坚持就是胜利',
  20.           alarm: true,
  21.           alarmOffset: 1800,
  22.           repeatInterval: 'week',
  23.           repeatEndTime: repeatEndTime,
  24.           success: () => {
  25.             has.showToast({ title: '健身计划已添加' });
  26.           }
  27.         });
  28.       }
  29.     });
  30.   }
  31. });
复制代码

五、踩坑与排障

1. 时间戳单位是秒,不是毫秒。这是最常见的坑。Date.now() 和 new Date().getTime() 返回毫秒,但 API 要秒。需要:
  1. const startTime = Math.floor(Date.now() / 1000);
复制代码

如果直接用毫秒值,日程会被加到几十年后。

2. 时间参数类型不一致。原文作者前前后后踩了三次:第一次全用 number,报错 endTime is not type string;第二次全改成 String,又报错 startTime is not type number。后来才发现 endTime 要 string,startTime 和 repeatEndTime 要 number。
  1. const now = Math.floor(Date.now() / 1000);
  2. const startTime = now + 3600; // number
  3. const endTime = String(now + 7200); // string,注意要转换
  4. const repeatEndTime = now + 180 * 24 * 3600; // number
复制代码

3. 每月重复不能超过 28 号。repeatInterval 为 month 时,日程日期不能大于 28 号。因为有些月份只有 28 天,系统无法确定“31 号”在这些月份该在哪天触发。如果要在月底重复,用 28 号更安全。

4. 授权必须在运行时完成。日历 API 不像有些 API 声明权限就行,必须通过 has.authorize 申请 scope.addPhoneCalendar。不授权直接调用会失败。

5. 不支持修改和删除。ASCF 的日历 API 只能添加,不能修改和删除已有日程。如果需要修改,得引导用户去系统日历应用手动操作。

6. allDay 事件仍要传 startTime。即使 allDay 为 true,startTime 还是需要 unix 时间戳;系统会根据这个时间戳的日期来确定是哪一天,时间部分会被忽略。

六、完整示例与 API 速查

原文还给出一个较完整的 Page 示例:data 中包含 title、description、location、repeatMode、repeatOptions、alarmOffset、lastResult;通过输入事件更新表单;addCalendar 先授权,再根据 repeatMode 是否为 none,决定调用 addPhoneCalendar 还是 addPhoneRepeatCalendar,重复结束时间设为半年后;addQuickMeeting 则快速添加一个 5 分钟后开始、1 小时 5 分钟后结束、提前 60 秒提醒的会议。核心逻辑与上文单次/重复示例一致。

对于大多数元服务场景,添加日程已经够用。如果确实需要查询或修改已有日程,ASCF 目前不支持,得使用系统的 @ohos.calendarManager 模块。总体来看,日历 API 适合预约、提醒、计划类场景,落地时重点处理授权和时间戳这两个容易出错的点。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-9-14 15:53 , Processed in 0.028497 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部