HarmonyOs应用《重要日》开发第7篇 - 日期处理:dayjs 集成与 DateUtil 封装
本篇深入讲解 ImportantDays 项目中日期处理的核心工具类 DateUtil,它基于 dayjs 库封装了所有日期计算逻辑,包括倒数计算、重复事件处理、日期格式化等。
一、为什么选择 dayjs?
1.1 dayjs 简介
dayjs 是一个轻量级的 JavaScript 日期处理库,API 设计与 moment.js 兼容,但体积只有 2KB。在 HarmonyOS 的 ArkTS 环境中,dayjs 通过 OpenHarmony 包管理器安装使用。
1.2 对比原生 Date API
// 原生 Date:计算两个日期相差天数constdiff=Math.floor((newDate('2026-03-15').getTime()-newDate('2026-01-01').getTime())/86400000);// dayjs:同样的计算constdiff=dayjs('2026-03-15').diff(dayjs('2026-01-01'),'day');dayjs 的优势:
- API 简洁:链式调用,代码可读性高
- 功能丰富:日期加减、比较、格式化一站式解决
- 不可变性:操作返回新对象,不修改原对象
- 轻量:压缩后仅 2KB
二、DateUtil 工具类
2.1 完整实现
// utils/DateUtil.etsimportdayjsfrom'dayjs';import{YearMonth}from'../types/Types';exportclassDateUtil{// 获取今天的日期字符串statictoday():string{returndayjs().format('YYYY-MM-DD');}// 格式化日期staticformatDate(date:string,format:string='YYYY-MM-DD'):string{returndayjs(date).format(format);}// 计算两个日期之间的天数差staticdaysBetween(fromDate:string,toDate:string):number{constfrom=dayjs(fromDate);constto=dayjs(toDate);returnto.diff(from,'day');}// 计算从今天到目标日期的天数(正=未来,负=过去)staticdaysFromToday(targetDate:string):number{returnDateUtil.daysBetween(DateUtil.today(),targetDate);}// 判断是否是今天staticisToday(date:string):boolean{returndayjs(date).isSame(dayjs(),'day');}// 判断是否在本月staticisThisMonth(date:string):boolean{returndayjs(date).isSame(dayjs(),'month');}// 根据偏移量获取年月staticgetYearMonth(offset:number):YearMonth{constd=dayjs().add(offset,'month');return{year:d.year(),month:d.month()+1};}// 从日期字符串获取年月staticgetYearMonthFromDate(date:string):YearMonth{constd=dayjs(date);return{year:d.year(),month:d.month()+1};}// 获取某月的天数staticgetDaysInMonth(year:number,month:number):number{returndayjs().year(year).month(month-1).daysInMonth();}// 获取某月第一天是星期几(0=周日,6=周六)staticgetFirstDayOfMonth(year:number,month:number):number{returndayjs().year(year).month(month-1).date(1).day();}// 格式化年月显示staticformatYearMonth(year:number,month:number):string{return`${year}年${month}月`;}// 格式化中文日期staticformatDateChinese(date:string):string{constd=dayjs(date);return`${d.year()}年${d.month()+1}月${d.date()}日`;}// 获取下一次重复日期staticgetNextOccurrence(date:string,repeatType:number):string{consttarget=dayjs(date);constnow=dayjs();if(repeatType===1){// 每年letnext=target.year(now.year());if(next.isBefore(now,'day')||next.isSame(now,'day')){next=next.year(now.year()+1);}returnnext.format('YYYY-MM-DD');}elseif(repeatType===2){// 每月letnext=target.year(now.year()).month(now.month());if(next.isBefore(now,'day')||next.isSame(now,'day')){next=next.add(1,'month');}returnnext.format('YYYY-MM-DD');}returndate;}// 获取星期标签staticgetWeekLabel(date:string):string{constday=dayjs(date).day();constlabels=['周日','周一','周二','周三','周四','周五','周六'];returnlabels[day];}// 日期加减天数staticaddDays(date:string,days:number):string{returndayjs(date).add(days,'day').format('YYYY-MM-DD');}// 生成唯一IDstaticgenerateId():string{return`${Date.now()}_${Math.floor(Math.random()*10000)}`;}}三、核心方法详解
3.1 日期差计算
staticdaysFromToday(targetDate:string):number{returnDateUtil.daysBetween(DateUtil.today(),targetDate);}staticdaysBetween(fromDate:string,toDate:string):number{constfrom=dayjs(fromDate);constto=dayjs(toDate);returnto.diff(from,'day');}返回值含义:
- 正数:目标日期在未来(还有 X 天)
- 负数:目标日期在过去(已过 X 天)
- 零:就是今天
这个方法是整个应用倒数/正数功能的基础。
3.2 重复事件的下一次日期
staticgetNextOccurrence(date:string,repeatType:number):string{consttarget=dayjs(date);constnow=dayjs();if(repeatType===1){// 每年重复letnext=target.year(now.year());// 替换为今年if(next.isBefore(now,'day')||next.isSame(now,'day')){next=next.year(now.year()+1);// 如果今年已过或就是今天,推到明年}returnnext.format('YYYY-MM-DD');}if(repeatType===2){// 每月重复letnext=target.year(now.year()).month(now.month());// 替换为本月if(next.isBefore(now,'day')||next.isSame(now,'day')){next=next.add(1,'month');// 如果本月已过或就是今天,推到下月}returnnext.format('YYYY-MM-DD');}returndate;// 不重复,返回原日期}逻辑流程(以每年重复为例):
原始日期: 2020-03-15,今天: 2026-07-15 1. target = dayjs('2020-03-15') 2. next = target.year(2026) = 2026-03-15 3. 2026-03-15 < 2026-07-15(已过)→ next = next.year(2027) = 2027-03-15 4. 返回 '2027-03-15'3.3 闰年处理
staticgetDaysInMonth(year:number,month:number):number{returndayjs().year(year).month(month-1).daysInMonth();}dayjs 内部自动处理闰年。例如:
DateUtil.getDaysInMonth(2024,2);// 29(闰年)DateUtil.getDaysInMonth(2025,2);// 28(平年)3.4 月初星期计算
staticgetFirstDayOfMonth(year:number,month:number):number{returndayjs().year(year).month(month-1).date(1).day();}返回值 0-6 对应周日到周六。用于日历视图中确定第一行需要留多少空位。
3.5 中文格式化
staticformatDateChinese(date:string):string{constd=dayjs(date);return`${d.year()}年${d.month()+1}月${d.date()}日`;}staticformatYearMonth(year:number,month:number):string{return`${year}年${month}月`;}注意 dayjs 的month()返回 0-11,所以需要 +1。
四、在项目中的使用场景
4.1 列表页:计算倒数天数
// MainViewModel.getCountText()consteffectiveDate=this.getEffectiveDate(day);constdays=DateUtil.daysFromToday(effectiveDate);// days > 0: "X天后"// days < 0: "X天前"// days === 0: "今天"4.2 日历页:生成月份数据
// CalendarDataSource.generateMonth()constdaysInMonth=DateUtil.getDaysInMonth(d.year,d.month);constfirstDayOfWeek=DateUtil.getFirstDayOfMonth(d.year,d.month);// 用这些数据生成 42 格日历(6行 x 7列)4.3 日历页:标记今天
// CalendarGridView.dayCell()privateisToday(date:string):boolean{returnDateUtil.isToday(date);}4.4 统计:本月重要日
// MainViewModel.getStats()if(DateUtil.isThisMonth(effectiveDate)){thisMonth++;}4.5 日历查询:按月日匹配
// MainViewModel.getDaysForDate()if(day.repeatType===RepeatType.YEARLY){constdayMonth=DateUtil.formatDate(day.date,'MM-DD');consttargetMonth=DateUtil.formatDate(date,'MM-DD');returndayMonth===targetMonth;}五、dayjs 在 ArkTS 中的注意事项
5.1 导入方式
importdayjsfrom'dayjs';在 oh-package.json5 中声明依赖:
{ "dependencies": { "dayjs": "^1.11.13" } }5.2 链式调用
dayjs 支持链式调用,但需要注意 ArkTS 的类型推断:
// 正确:分步调用constd=dayjs().year(year).month(month-1);constdays=d.daysInMonth();// 也可以:链式调用constdays=dayjs().year(year).month(month-1).daysInMonth();5.3 不可变性
dayjs 对象是不可变的,每次操作返回新对象:
consta=dayjs('2026-01-01');constb=a.add(1,'month');// a 仍然是 2026-01-01// b 是 2026-02-01这在 ArkUI V2 的响应式系统中很重要——不会意外修改被@Trace观察的对象。
六、扩展思考
6.1 时区处理
当前项目没有考虑时区问题。如果需要国际化,可以使用 dayjs 的 UTC 插件:
importutcfrom'dayjs/plugin/utc';dayjs.extend(utc);constutcDate=dayjs.utc('2026-03-15');6.2 相对时间
如果需要显示"3小时后"这样的相对时间,可以使用 dayjs 的 relativeTime 插件。但本项目只精确到天,不需要。
6.3 自定义解析
dayjs 默认能解析YYYY-MM-DD格式。如果需要解析其他格式:
dayjs('2026年3月15日','YYYY年M月D日');七、小结
DateUtil 作为项目的日期处理核心,封装了 dayjs 的常用操作,提供了简洁的静态方法接口。从倒数计算到重复事件处理,从日期格式化到日历数据生成,所有日期相关的逻辑都集中在这里,提高了代码的可维护性和可测试性。