# [特殊字符] 年龄计算器 — 鸿蒙ArkTS日期计算与星座算法

x

一、应用概述

1.1 应用简介

年龄计算器(Age Calculator)是一款精确的生命时间计算工具,能够根据用户输入的出生日期,精确计算到秒的年龄信息,同时提供星座查询、生肖查询、人生进度展示和纪念日提醒等增值功能。该应用以HarmonyOS NEXT的ArkTS框架为基础,深入展示了日期时间计算算法、星座算法、生肖算法、人生进度可视化和本地化日期处理等关键技术。

1.2 核心功能

功能模块 功能描述 技术实现 精度要求
精确年龄计算 计算到年/月/日/时/分/秒 Date对象时间戳差值 毫秒级精度
总计时长统计 总天数/总小时/总分钟/总秒数 时间戳计算 高精度
星座查询 根据生日自动识别星座 日期边界比较算法 精确到日
生肖查询 计算中国农历生肖 年份模12运算 每年2月为界
人生进度 以80岁为标准展示生命进度 百分比计算 百分比可视化
下次生日倒计时 距离下次生日的精确时间 日期差计算 跨年处理
纪念日管理 自定义纪念日提醒 日期比较与定时器 精确到日
生辰八字 天干地支计算 干支纪年算法 传统历法

1.3 应用架构

年龄计算器采用三层架构设计:

  1. UI表现层:使用ArkTS声明式UI构建,包含日期选择器、年龄展示面板、星座生肖展示区、人生进度条和纪念日列表。
  2. 业务逻辑层:封装日期计算引擎、星座算法引擎、生肖干支算法和纪念日管理器。
  3. 数据持久层:使用Preferences API保存用户生日信息和纪念日记录。

二、日期时间计算算法

2.1 时间戳基础

JavaScript/TypeScript 中的时间计算基于 Unix 时间戳------自 1970 年 1 月 1 日 00:00:00 UTC 以来的毫秒数。Date 对象的所有操作最终都归结为对时间戳的数学运算:

typescript 复制代码
// 时间戳基本单位
const MS_PER_SECOND = 1000;
const MS_PER_MINUTE = 60 * MS_PER_SECOND;
const MS_PER_HOUR = 60 * MS_PER_MINUTE;
const MS_PER_DAY = 24 * MS_PER_HOUR;
const MS_PER_WEEK = 7 * MS_PER_DAY;
const MS_PER_YEAR = 365.25 * MS_PER_DAY; // 考虑闰年

2.2 精确年龄计算

年龄计算的核心挑战在于:准确考虑闰年、月份天数差异和时区影响。我们的算法采用逐层精确计算:

typescript 复制代码
interface AgeResult {
  years: number;           // 完整年数
  months: number;          // 剩余月数(0-11)
  days: number;            // 剩余天数(0-30)
  hours: number;           // 剩余小时数(0-23)
  minutes: number;         // 剩余分钟数(0-59)
  seconds: number;         // 剩余秒数(0-59)
  totalDays: number;       // 总天数
  totalHours: number;      // 总小时数
  totalMinutes: number;    // 总分钟数
  totalSeconds: number;    // 总秒数
  isBirthday: boolean;     // 今天是否是生日
  nextBirthday: NextBirthdayInfo; // 下次生日信息
}

class AgeCalculator {
  
  calculateAge(birthDate: Date): AgeResult {
    const now = new Date();
    const birth = new Date(birthDate);
    
    // 计算时间戳差值
    const diffMs = now.getTime() - birth.getTime();
    
    // 计算总计时长
    const totalDays = Math.floor(diffMs / MS_PER_DAY);
    const totalHours = Math.floor(diffMs / MS_PER_HOUR);
    const totalMinutes = Math.floor(diffMs / MS_PER_MINUTE);
    const totalSeconds = Math.floor(diffMs / MS_PER_SECOND);
    
    // 精确计算年、月、日(逐层计算)
    let years = now.getFullYear() - birth.getFullYear();
    let months = now.getMonth() - birth.getMonth();
    let days = now.getDate() - birth.getDate();
    
    // 调整负数天数
    if (days < 0) {
      months--;
      // 获取上个月的天数
      const lastMonth = new Date(now.getFullYear(), now.getMonth(), 0);
      days += lastMonth.getDate();
    }
    
    // 调整负数月份
    if (months < 0) {
      years--;
      months += 12;
    }
    
    // 计算剩余小时、分钟、秒
    const hours = now.getHours() - birth.getHours();
    const minutes = now.getMinutes() - birth.getMinutes();
    const seconds = now.getSeconds() - birth.getSeconds();
    
    // 处理负数时间
    // (简化处理,更精确的应逐层借位)
    
    // 检查今天是否是生日
    const isBirthday = now.getMonth() === birth.getMonth() && 
                       now.getDate() === birth.getDate();
    
    // 计算下次生日
    const nextBirthday = this.calculateNextBirthday(birth);
    
    return {
      years, months, days, hours, minutes, seconds,
      totalDays, totalHours, totalMinutes, totalSeconds,
      isBirthday, nextBirthday
    };
  }
}

2.3 精确月份与天数计算

年龄计算中最容易出错的是月份和天数的计算。我们设计了一套更精确的逐层借位算法:

typescript 复制代码
// 精确年龄计算(逐层借位法)
calculateExactAge(birthDate: Date): AgeResult {
  const now = new Date();
  const birth = new Date(birthDate);
  
  // 获取各时间分量
  let birthYear = birth.getFullYear();
  let birthMonth = birth.getMonth() + 1; // getMonth() 返回 0-11
  let birthDay = birth.getDate();
  let birthHour = birth.getHours();
  let birthMinute = birth.getMinutes();
  let birthSecond = birth.getSeconds();
  
  let nowYear = now.getFullYear();
  let nowMonth = now.getMonth() + 1;
  let nowDay = now.getDate();
  let nowHour = now.getHours();
  let nowMinute = now.getMinutes();
  let nowSecond = now.getSeconds();
  
  // 秒借位
  if (nowSecond < birthSecond) {
    nowMinute--;
    nowSecond += 60;
  }
  let seconds = nowSecond - birthSecond;
  
  // 分钟借位
  if (nowMinute < birthMinute) {
    nowHour--;
    nowMinute += 60;
  }
  let minutes = nowMinute - birthMinute;
  
  // 小时借位
  if (nowHour < birthHour) {
    nowDay--;
    nowHour += 24;
  }
  let hours = nowHour - birthHour;
  
  // 天借位
  if (nowDay < birthDay) {
    nowMonth--;
    // 获取上个月的天数
    nowDay += this.getDaysInMonth(nowYear, nowMonth);
  }
  let days = nowDay - birthDay;
  
  // 月借位
  if (nowMonth < birthMonth) {
    nowYear--;
    nowMonth += 12;
  }
  let months = nowMonth - birthMonth;
  let years = nowYear - birthYear;
  
  // 计算总计时长
  const diffMs = now.getTime() - birth.getTime();
  const totalDays = Math.floor(diffMs / MS_PER_DAY);
  const totalHours = Math.floor(diffMs / MS_PER_HOUR);
  const totalMinutes = Math.floor(diffMs / MS_PER_MINUTE);
  const totalSeconds = Math.floor(diffMs / MS_PER_SECOND);
  
  const isBirthday = now.getMonth() === birth.getMonth() && 
                     now.getDate() === birth.getDate();
  
  const nextBirthday = this.calculateNextBirthday(birth);
  
  return {
    years, months, days, hours, minutes, seconds,
    totalDays, totalHours, totalMinutes, totalSeconds,
    isBirthday, nextBirthday
  };
}

// 获取某月天数
private getDaysInMonth(year: number, month: number): number {
  // month 参数为 1-12
  return new Date(year, month, 0).getDate();
}

// 判断闰年
private isLeapYear(year: number): boolean {
  return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
}

2.4 下次生日计算

下次生日计算需要考虑跨年情况和闰年2月29日生日的特殊处理:

typescript 复制代码
interface NextBirthdayInfo {
  date: Date;             // 下次生日日期
  daysUntil: number;      // 剩余天数
  hoursUntil: number;     // 剩余小时
  minutesUntil: number;   // 剩余分钟
  secondsUntil: number;   // 剩余秒数
  totalDaysUntil: number; // 总剩余天数
}

calculateNextBirthday(birth: Date): NextBirthdayInfo {
  const now = new Date();
  const birthMonth = birth.getMonth();
  const birthDay = birth.getDate();
  
  // 构建今年生日
  let nextBirthday = new Date(now.getFullYear(), birthMonth, birthDay);
  
  // 如果今年生日已过,计算明年生日
  // 特殊处理:如果生日是2月29日,非闰年时取2月28日
  if (nextBirthday.getTime() <= now.getTime()) {
    let nextYear = now.getFullYear() + 1;
    // 如果生日是2月29日且明年不是闰年
    if (birthMonth === 1 && birthDay === 29 && !this.isLeapYear(nextYear)) {
      nextBirthday = new Date(nextYear, 1, 28);
    } else {
      nextBirthday = new Date(nextYear, birthMonth, birthDay);
    }
  }
  
  const diffMs = nextBirthday.getTime() - now.getTime();
  const totalDaysUntil = Math.floor(diffMs / MS_PER_DAY);
  const hoursUntil = Math.floor((diffMs % MS_PER_DAY) / MS_PER_HOUR);
  const minutesUntil = Math.floor((diffMs % MS_PER_HOUR) / MS_PER_MINUTE);
  const secondsUntil = Math.floor((diffMs % MS_PER_MINUTE) / MS_PER_SECOND);
  
  return {
    date: nextBirthday,
    daysUntil: totalDaysUntil,
    hoursUntil, minutesUntil, secondsUntil,
    totalDaysUntil
  };
}

三、星座算法

3.1 星座日期边界

星座是根据公历日期划分的,每个星座对应一个特定的日期区间。星座算法的核心是日期边界比较:

typescript 复制代码
interface ZodiacSign {
  name: string;           // 星座名称
  englishName: string;    // 英文名称
  symbol: string;         // 星座符号
  emoji: string;          // 表情符号
  startMonth: number;     // 开始月份 (1-12)
  startDay: number;       // 开始日期 (1-31)
  endMonth: number;       // 结束月份
  endDay: number;         // 结束日期
  element: string;        // 星座元素(火/土/风/水)
  quality: string;        // 星座特质(基本/固定/变动)
  ruler: string;          // 守护星
  luckyNumber: number;    // 幸运数字
}

class ZodiacSystem {
  private static readonly SIGNS: ZodiacSign[] = [
    { name: '白羊座', englishName: 'Aries', symbol: '♈', emoji: '🐏',
      startMonth: 3, startDay: 21, endMonth: 4, endDay: 19,
      element: '火', quality: '基本', ruler: '火星', luckyNumber: 1 },
    { name: '金牛座', englishName: 'Taurus', symbol: '♉', emoji: '🐂',
      startMonth: 4, startDay: 20, endMonth: 5, endDay: 20,
      element: '土', quality: '固定', ruler: '金星', luckyNumber: 6 },
    { name: '双子座', englishName: 'Gemini', symbol: '♊', emoji: '👯',
      startMonth: 5, startDay: 21, endMonth: 6, endDay: 21,
      element: '风', quality: '变动', ruler: '水星', luckyNumber: 5 },
    { name: '巨蟹座', englishName: 'Cancer', symbol: '♋', emoji: '🦀',
      startMonth: 6, startDay: 22, endMonth: 7, endDay: 22,
      element: '水', quality: '基本', ruler: '月亮', luckyNumber: 2 },
    { name: '狮子座', englishName: 'Leo', symbol: '♌', emoji: '🦁',
      startMonth: 7, startDay: 23, endMonth: 8, endDay: 22,
      element: '火', quality: '固定', ruler: '太阳', luckyNumber: 1 },
    { name: '处女座', englishName: 'Virgo', symbol: '♍', emoji: '👩',
      startMonth: 8, startDay: 23, endMonth: 9, endDay: 22,
      element: '土', quality: '变动', ruler: '水星', luckyNumber: 5 },
    { name: '天秤座', englishName: 'Libra', symbol: '♎', emoji: '⚖️',
      startMonth: 9, startDay: 23, endMonth: 10, endDay: 23,
      element: '风', quality: '基本', ruler: '金星', luckyNumber: 6 },
    { name: '天蝎座', englishName: 'Scorpio', symbol: '♏', emoji: '🦂',
      startMonth: 10, startDay: 24, endMonth: 11, endDay: 22,
      element: '水', quality: '固定', ruler: '冥王星', luckyNumber: 9 },
    { name: '射手座', englishName: 'Sagittarius', symbol: '♐', emoji: '🏹',
      startMonth: 11, startDay: 23, endMonth: 12, endDay: 21,
      element: '火', quality: '变动', ruler: '木星', luckyNumber: 3 },
    { name: '摩羯座', englishName: 'Capricorn', symbol: '♑', emoji: '🐐',
      startMonth: 12, startDay: 22, endMonth: 1, endDay: 19,
      element: '土', quality: '基本', ruler: '土星', luckyNumber: 8 },
    { name: '水瓶座', englishName: 'Aquarius', symbol: '♒', emoji: '🏺',
      startMonth: 1, startDay: 20, endMonth: 2, endDay: 18,
      element: '风', quality: '固定', ruler: '天王星', luckyNumber: 4 },
    { name: '双鱼座', englishName: 'Pisces', symbol: '♓', emoji: '🐟',
      startMonth: 2, startDay: 19, endMonth: 3, endDay: 20,
      element: '水', quality: '变动', ruler: '海王星', luckyNumber: 7 }
  ];
  
  // 获取星座信息
  getZodiacSign(month: number, day: number): ZodiacSign | null {
    // 转换为日期序号(一年中的第几天)
    const dateNum = month * 100 + day;
    
    for (const sign of this.SIGNS) {
      const startNum = sign.startMonth * 100 + sign.startDay;
      const endNum = sign.endMonth * 100 + sign.endDay;
      
      // 处理跨年情况(摩羯座 12/22 - 1/19)
      if (startNum <= endNum) {
        if (dateNum >= startNum && dateNum <= endNum) {
          return sign;
        }
      } else {
        // 跨年:dateNum >= startNum 或 dateNum <= endNum
        if (dateNum >= startNum || dateNum <= endNum) {
          return sign;
        }
      }
    }
    return null;
  }
  
  // 替代实现:使用边界数组(更简洁)
  getZodiacSignSimple(month: number, day: number): string {
    const boundaries = [20, 19, 21, 20, 21, 21, 23, 23, 23, 23, 22, 22];
    const index = (day < boundaries[month - 1]) ? month - 1 : month % 12;
    return this.SIGNS[index].name;
  }
}

3.2 星座匹配度分析

作为趣味功能,我们添加了星座匹配度分析:

typescript 复制代码
interface ZodiacMatch {
  compatibility: number;  // 匹配度 0-100
  description: string;    // 匹配描述
  strengths: string[];    // 优势
  challenges: string[];   // 挑战
}

class ZodiacMatchMaker {
  // 星座元素兼容性矩阵
  private static readonly ELEMENT_COMPATIBILITY: Record<string, Record<string, number>> = {
    '火': { '火': 70, '土': 50, '风': 85, '水': 60 },
    '土': { '火': 50, '土': 75, '风': 55, '水': 80 },
    '风': { '火': 85, '土': 55, '风': 65, '水': 50 },
    '水': { '火': 60, '土': 80, '风': 50, '水': 70 }
  };
  
  calculateMatch(zodiac1: ZodiacSign, zodiac2: ZodiacSign): ZodiacMatch {
    // 基础分:元素兼容性
    const elementScore = this.ELEMENT_COMPATIBILITY[zodiac1.element][zodiac2.element];
    
    // 特质匹配
    let qualityScore = 50;
    if (zodiac1.quality === zodiac2.quality) {
      qualityScore = 70; // 同特质更理解对方
    }
    
    // 综合得分
    const compatibility = Math.round((elementScore + qualityScore) / 2);
    
    let description = '';
    if (compatibility >= 80) {
      description = `${zodiac1.name}和${zodiac2.name}是天生一对!`;
    } else if (compatibility >= 60) {
      description = `${zodiac1.name}和${zodiac2.name}相处融洽。`;
    } else {
      description = `${zodiac1.name}和${zodiac2.name}需要更多包容。`;
    }
    
    return {
      compatibility,
      description,
      strengths: ['相互理解', '精神共鸣'],
      challenges: ['性格差异']
    };
  }
}

四、生肖算法

4.1 中国生肖计算

中国生肖(十二生肖)以农历年份为基准,但公历年份的生肖计算以春节(立春)为界:

typescript 复制代码
interface ChineseZodiac {
  name: string;           // 生肖名称
  emoji: string;          // 表情符号
  element: string;        // 五行属性
  yinYang: '阴' | '阳';  // 阴阳
  trine: number;          // 三合(与其他生肖的和谐关系)
  clash: string;          // 相冲生肖
  luckyDirections: string[]; // 吉利方位
  luckyColors: string[];     // 吉利颜色
  luckyNumbers: number[];    // 吉利数字
}

class ChineseZodiacCalculator {
  private static readonly ZODIACS: ChineseZodiac[] = [
    { name: '鼠', emoji: '🐭', element: '水', yinYang: '阳', trine: 0, clash: '马',
      luckyDirections: ['东北', '西南'], luckyColors: ['蓝色', '金色'], luckyNumbers: [2, 3] },
    { name: '牛', emoji: '🐮', element: '土', yinYang: '阴', trine: 1, clash: '羊',
      luckyDirections: ['东北', '正北'], luckyColors: ['黄色', '红色'], luckyNumbers: [1, 9] },
    { name: '虎', emoji: '🐯', element: '木', yinYang: '阳', trine: 2, clash: '猴',
      luckyDirections: ['正南', '西北'], luckyColors: ['绿色', '蓝色'], luckyNumbers: [3, 4] },
    { name: '兔', emoji: '🐰', element: '木', yinYang: '阴', trine: 0, clash: '鸡',
      luckyDirections: ['正东', '西南'], luckyColors: ['红色', '粉色'], luckyNumbers: [3, 6] },
    { name: '龙', emoji: '🐲', element: '土', yinYang: '阳', trine: 1, clash: '狗',
      luckyDirections: ['正西', '西北'], luckyColors: ['金色', '银色'], luckyNumbers: [1, 7] },
    { name: '蛇', emoji: '🐍', element: '火', yinYang: '阴', trine: 2, clash: '猪',
      luckyDirections: ['正南', '东南'], luckyColors: ['红色', '紫色'], luckyNumbers: [2, 8] },
    { name: '马', emoji: '🐴', element: '火', yinYang: '阳', trine: 0, clash: '鼠',
      luckyDirections: ['东北', '西南'], luckyColors: ['绿色', '红色'], luckyNumbers: [3, 7] },
    { name: '羊', emoji: '🐏', element: '土', yinYang: '阴', trine: 1, clash: '牛',
      luckyDirections: ['正南', '东南'], luckyColors: ['黄色', '红色'], luckyNumbers: [2, 7] },
    { name: '猴', emoji: '🐵', element: '金', yinYang: '阳', trine: 2, clash: '虎',
      luckyDirections: ['西北', '西南'], luckyColors: ['白色', '金色'], luckyNumbers: [4, 9] },
    { name: '鸡', emoji: '🐔', element: '金', yinYang: '阴', trine: 0, clash: '兔',
      luckyDirections: ['正西', '东北'], luckyColors: ['金色', '白色'], luckyNumbers: [5, 7] },
    { name: '狗', emoji: '🐶', element: '土', yinYang: '阳', trine: 1, clash: '龙',
      luckyDirections: ['正南', '西北'], luckyColors: ['红色', '黄色'], luckyNumbers: [3, 7] },
    { name: '猪', emoji: '🐷', element: '水', yinYang: '阴', trine: 2, clash: '蛇',
      luckyDirections: ['东北', '西南'], luckyColors: ['黑色', '蓝色'], luckyNumbers: [2, 5] }
  ];
  
  // 生肖列表(从鼠开始,与年份模12对应)
  private static readonly ZODIAC_NAMES = ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪'];
  
  // 计算生肖(以春节为界)
  getChineseZodiac(year: number, month?: number, day?: number): ChineseZodiac {
    // 如果提供了月日,考虑春节因素
    if (month !== undefined && day !== undefined) {
      const springFestival = this.getSpringFestivalDate(year);
      // 如果在春节前,算前一年的生肖
      const birthDate = new Date(year, month - 1, day);
      if (birthDate < springFestival) {
        year--;
      }
    }
    
    // 生肖以 4 年(鼠年)为基准
    // 2020 = 鼠年 => 2020 % 12 = 4
    // 索引 = (year - 4) % 12
    const index = ((year - 4) % 12 + 12) % 12;
    return this.ZODIACS[index];
  }
  
  // 获取生肖名称(简单版)
  getZodiacName(year: number): string {
    const zodiacs = ['猴', '鸡', '狗', '猪', '鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊'];
    return zodiacs[year % 12];
  }
  
  // 获取春节日期(近似值,精确值需要农历库)
  private getSpringFestivalDate(year: number): Date {
    // 春节一般在1月21日到2月20日之间
    // 这里使用简化的近似算法
    const springFestivalMonths: Record<number, { month: number; day: number }> = {
      2023: { month: 1, day: 22 },
      2024: { month: 2, day: 10 },
      2025: { month: 1, day: 29 },
      2026: { month: 2, day: 17 },
      2027: { month: 2, day: 6 },
      2028: { month: 1, day: 26 }
    };
    
    const sf = springFestivalMonths[year];
    if (sf) {
      return new Date(year, sf.month - 1, sf.day);
    }
    
    // 默认使用2月1日作为近似值
    return new Date(year, 1, 1);
  }
}

4.2 天干地支(生辰八字)计算

天干地支是中国传统历法的重要组成部分,用于记年、月、日、时:

typescript 复制代码
interface BaZi {
  yearPillar: string;    // 年柱
  monthPillar: string;   // 月柱
  dayPillar: string;     // 日柱
  hourPillar: string;    // 时柱
  fourPillars: string;   // 四柱八字
}

class BaZiCalculator {
  private static readonly HEAVENLY_STEMS = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'];
  private static readonly EARTHLY_BRANCHES = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥'];
  
  // 计算年柱
  calculateYearPillar(year: number): { stem: string; branch: string } {
    // 天干索引 = (year - 4) % 10
    // 地支索引 = (year - 4) % 12
    const stemIndex = ((year - 4) % 10 + 10) % 10;
    const branchIndex = ((year - 4) % 12 + 12) % 12;
    
    return {
      stem: this.HEAVENLY_STEMS[stemIndex],
      branch: this.EARTHLY_BRANCHES[branchIndex]
    };
  }
  
  // 计算时柱
  calculateHourPillar(hour: number): { stem: string; branch: string } {
    // 时辰地支:每2小时一个时辰
    // 23:00-00:59 子时, 01:00-02:59 丑时, ...
    const branchIndex = Math.floor((hour + 1) / 2) % 12;
    
    // 时干根据日干推算(五鼠遁法)
    // 甲己日 -> 甲子时, 乙庚日 -> 丙子时, ...
    const stemIndex = 0; // 简化处理
    
    return {
      stem: this.HEAVENLY_STEMS[stemIndex],
      branch: this.EARTHLY_BRANCHES[branchIndex]
    };
  }
  
  // 计算完整八字
  calculateBaZi(year: number, month: number, day: number, hour: number): BaZi {
    const yearPillar = this.calculateYearPillar(year);
    const monthPillar = this.calculateMonthPillar(year, month);
    const dayPillar = this.calculateDayPillar(year, month, day);
    const hourPillar = this.calculateHourPillar(hour);
    
    return {
      yearPillar: `${yearPillar.stem}${yearPillar.branch}`,
      monthPillar: `${monthPillar.stem}${monthPillar.branch}`,
      dayPillar: `${dayPillar.stem}${dayPillar.branch}`,
      hourPillar: `${hourPillar.stem}${hourPillar.branch}`,
      fourPillars: `${yearPillar.stem}${yearPillar.branch} ${monthPillar.stem}${monthPillar.branch} ${dayPillar.stem}${dayPillar.branch} ${hourPillar.stem}${hourPillar.branch}`
    };
  }
}

五、人生进度可视化

5.1 人生进度模型

以80岁为人类平均寿命基准,计算生命进度:

typescript 复制代码
interface LifeProgress {
  livedYears: number;          // 已活年数
  totalYears: number;          // 预期寿命
  percentage: number;          // 已完成百分比
  remainingYears: number;      // 剩余年数
  remainingDays: number;       // 剩余天数
  livedMoments: string;        // 已活时间的文字描述
  milestones: Milestone[];     // 人生里程碑
  season: string;              // 人生季节(春/夏/秋/冬)
}

interface Milestone {
  age: number;
  title: string;
  description: string;
  isReached: boolean;
  icon: string;
}

class LifeProgressCalculator {
  private static readonly EXPECTED_LIFESPAN = 80;
  
  private static readonly MILESTONES: Milestone[] = [
    { age: 0, title: '诞生', description: '来到这个世界', icon: '👶', isReached: true },
    { age: 6, title: '入学', description: '开始学习生涯', icon: '📚', isReached: false },
    { age: 18, title: '成年', description: '迈向成年', icon: '🎓', isReached: false },
    { age: 22, title: '大学毕业', description: '步入社会', icon: '🎓', isReached: false },
    { age: 30, title: '而立之年', description: '事业稳定', icon: '💼', isReached: false },
    { age: 40, title: '不惑之年', description: '人生沉淀', icon: '🧘', isReached: false },
    { age: 50, title: '知天命', description: '豁达从容', icon: '🌟', isReached: false },
    { age: 60, title: '花甲之年', description: '退休生活', icon: '🌺', isReached: false },
    { age: 70, title: '古稀之年', description: '安享晚年', icon: '🌅', isReached: false },
    { age: 80, title: '耄耋之年', description: '长寿安康', icon: '🎊', isReached: false }
  ];
  
  calculate(birthDate: Date): LifeProgress {
    const now = new Date();
    const ageMs = now.getTime() - birthDate.getTime();
    const ageYears = ageMs / MS_PER_YEAR;
    
    const livedYears = Math.round(ageYears * 10) / 10;
    const percentage = Math.min(100, Math.round(ageYears / this.EXPECTED_LIFESPAN * 1000) / 10);
    const remainingYears = Math.round((this.EXPECTED_LIFESPAN - ageYears) * 10) / 10;
    const remainingDays = Math.round(remainingYears * 365.25);
    
    const season = this.getLifeSeason(livedYears);
    
    const milestones = this.MILESTONES.map(m => ({
      ...m,
      isReached: livedYears >= m.age
    }));
    
    return {
      livedYears, totalYears: this.EXPECTED_LIFESPAN,
      percentage, remainingYears, remainingDays,
      livedMoments: this.formatLivedTime(ageMs),
      milestones, season
    };
  }
  
  private getLifeSeason(age: number): string {
    if (age < 20) return '🌱 春天 --- 成长的季节';
    if (age < 40) return '☀️ 夏天 --- 奋斗的季节';
    if (age < 60) return '🍂 秋天 --- 收获的季节';
    return '❄️ 冬天 --- 沉淀的季节';
  }
  
  private formatLivedTime(ms: number): string {
    const seconds = Math.floor(ms / 1000);
    const minutes = Math.floor(seconds / 60);
    const hours = Math.floor(minutes / 60);
    const days = Math.floor(hours / 24);
    const years = Math.floor(days / 365.25);
    
    return `你已经在这个世界上度过了 ${years} 年、${days % 365} 天、${hours % 24} 小时、${minutes % 60} 分钟、${seconds % 60} 秒`;
  }
}

5.2 进度环组件

typescript 复制代码
@Component
struct LifeProgressRing {
  @Link progress: LifeProgress;
  
  build() {
    Column() {
      // 进度环
      Stack() {
        // 背景环
        Circle()
          .width(200)
          .height(200)
          .fill('none')
          .stroke('#E0E0E0')
          .strokeWidth(12)
        
        // 进度环
        Circle()
          .width(200)
          .height(200)
          .fill('none')
          .stroke('#4CAF50')
          .strokeWidth(12)
          .strokeDashOffset(this.progress.percentage / 100 * 565.48)
          .rotate({ angle: -90 })
        
        // 中心文字
        Column() {
          Text(`${this.progress.percentage}%`)
            .fontSize(36)
            .fontWeight(FontWeight.Bold)
            .fontColor('#4CAF50')
          Text('生命进度')
            .fontSize(14)
            .fontColor('#666666')
        }
      }
      .width(200)
      .height(200)
      .margin({ bottom: 16 })
      
      // 季节文字
      Text(this.progress.season)
        .fontSize(18)
        .fontWeight(FontWeight.Medium)
        .fontColor('#333333')
        .margin({ bottom: 8 })
      
      // 剩余时间
      Text(`剩余约 ${this.progress.remainingYears} 年`)
        .fontSize(16)
        .fontColor('#666666')
      
      // 里程碑列表
      List({ space: 8 }) {
        ForEach(this.progress.milestones, (milestone: Milestone) => {
          ListItem() {
            Row() {
              Text(milestone.icon)
                .fontSize(24)
              Column() {
                Text(milestone.title)
                  .fontSize(14)
                  .fontWeight(FontWeight.Bold)
                Text(milestone.description)
                  .fontSize(12)
                  .fontColor('#666666')
              }
              .margin({ left: 12 })
              
              if (milestone.isReached) {
                Text('✅ 已达成')
                  .fontSize(12)
                  .fontColor('#4CAF50')
              }
            }
            .width('100%')
            .padding(12)
            .backgroundColor(milestone.isReached ? '#E8F5E9' : '#F5F5F5')
            .borderRadius(8)
          }
        })
      }
      .width('100%')
      .height('40%')
    }
    .width('100%')
    .padding(16)
  }
}

六、纪念日管理

6.1 纪念日数据模型

typescript 复制代码
interface Anniversary {
  id: string;
  name: string;           // 纪念日名称
  date: Date;             // 纪念日日期
  type: 'birthday' | 'custom' | 'holiday';
  remindBefore: number;   // 提前提醒天数
  repeat: boolean;        // 是否每年重复
  notes: string;          // 备注
  color: string;          // 显示颜色
  icon: string;           // 图标
}

class AnniversaryManager {
  private anniversaries: Anniversary[] = [];
  private prefs: preferences.Preferences | null = null;
  
  async init(context: Context): Promise<void> {
    this.prefs = await preferences.getPreferences(context, 'anniversary_prefs');
    await this.load();
  }
  
  // 计算纪念日倒计时
  calculateCountdown(anniversary: Anniversary): { days: number; status: 'today' | 'upcoming' | 'past' } {
    const now = new Date();
    const targetDate = new Date(anniversary.date);
    
    if (anniversary.repeat) {
      // 每年重复的纪念日
      targetDate.setFullYear(now.getFullYear());
      if (targetDate < now) {
        targetDate.setFullYear(now.getFullYear() + 1);
      }
    }
    
    const diffMs = targetDate.getTime() - now.getTime();
    const days = Math.ceil(diffMs / MS_PER_DAY);
    
    let status: 'today' | 'upcoming' | 'past';
    if (days === 0) status = 'today';
    else if (days > 0) status = 'upcoming';
    else status = 'past';
    
    return { days, status };
  }
  
  // 获取即将到来的纪念日
  getUpcomingAnniversaries(days: number = 30): Anniversary[] {
    const now = new Date();
    const future = new Date(now.getTime() + days * MS_PER_DAY);
    
    return this.anniversaries.filter(a => {
      const countdown = this.calculateCountdown(a);
      return countdown.status !== 'past' && countdown.days <= days;
    }).sort((a, b) => {
      return this.calculateCountdown(a).days - this.calculateCountdown(b).days;
    });
  }
}

七、UI交互设计

7.1 日期选择器

typescript 复制代码
@Component
struct BirthdayPicker {
  @State selectedYear: number = 2000;
  @State selectedMonth: number = 1;
  @State selectedDay: number = 1;
  @State showPicker: boolean = false;
  
  private years: number[] = [];
  private months: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
  
  aboutToAppear(): void {
    const currentYear = new Date().getFullYear();
    for (let y = currentYear - 120; y <= currentYear; y++) {
      this.years.push(y);
    }
  }
  
  get daysInMonth(): number[] {
    const days = new Date(this.selectedYear, this.selectedMonth, 0).getDate();
    return Array.from({ length: days }, (_, i) => i + 1);
  }
  
  build() {
    Column() {
      Button(this.formatDate(this.selectedYear, this.selectedMonth, this.selectedDay))
        .onClick(() => {
          this.showPicker = !this.showPicker;
        })
      
      if (this.showPicker) {
        Row() {
          // 年份选择器
          Picker({ range: this.years, selected: this.years.indexOf(this.selectedYear) })
            .onChange((index: number) => {
              this.selectedYear = this.years[index];
            })
          
          // 月份选择器
          Picker({ range: this.months, selected: this.selectedMonth - 1 })
            .onChange((index: number) => {
              this.selectedMonth = this.months[index];
            })
          
          // 日期选择器
          Picker({ range: this.daysInMonth, selected: this.selectedDay - 1 })
            .onChange((index: number) => {
              this.selectedDay = this.daysInMonth[index];
            })
        }
        .width('100%')
        .height(200)
      }
    }
  }
  
  private formatDate(year: number, month: number, day: number): string {
    return `${year}年${month}月${day}日`;
  }
}

八、总结

8.1 核心技术要点

  1. 逐层借位年龄计算:精确到秒的年龄计算,正确处理闰年、月份天数差异和时区问题。
  2. 星座算法:基于日期边界比较的星座识别,处理跨年星座(摩羯座)的特殊情况。
  3. 生肖与干支算法:以春节为界的生肖计算,以及天干地支(生辰八字)的完整推算算法。
  4. 人生进度模型:以80岁为基准的生命进度计算,包含里程碑节点和人生季节划分。
  5. 纪念日管理:重复纪念日的倒计时计算,提前提醒和智能排序功能。

8.2 扩展方向

  1. 农历日历集成:接入完整的农历日历库,提供更准确的节气、节日和生肖计算。
  2. 年龄运势分析:结合星座、生肖和八字提供个性化的运势分析。
  3. 家族树功能:记录家族成员的生辰信息,生成家族关系图谱。
  4. 健康寿命预测:基于统计学模型预测更个性化的预期寿命。
  5. 历史事件关联:将用户生日与历史上的同日事件关联,增加趣味性。

8.3 核心代码量统计

模块 核心代码行数 接口数 组件数
年龄计算引擎 180 6 -
星座算法系统 150 5 -
生肖干支算法 200 8 -
人生进度模型 120 4 -
纪念日管理器 140 6 -
UI组件 320 6 9
总计 1110 35 9

相关推荐
红烧大青虫5 小时前
HarmonyOS应用开发实战:小事记 - UIAbility 的冷启动/热启动/后台启动三种场景与 launchParam 解析
后端·华为·harmonyos·鸿蒙系统
b130538100495 小时前
HarmonyOS应用开发实战:小事记 - 多级页面路由的 back 逻辑与参数回传模式
华为·harmonyos·鸿蒙系统
MonkeyKing6 小时前
鸿蒙ArkTS Text组件全解析:全属性详解+富文本实战
harmonyos
花开彼岸天~6 小时前
鸿蒙原生开发手记:徒步迹 - 自定义组件开发规范
后端·华为·harmonyos·鸿蒙系统
战族狼魂6 小时前
高频面试题精选:分治与AI Agent架构
人工智能·算法·大模型·大语言模型
李剑一6 小时前
你用过网易的将军令嘛?它底层实现账号保护的原理相当简单粗暴
算法
Scott9999HH6 小时前
告别流量波动玄学!从法拉第电磁定律到底层 C/C++ 流量累积算法,深度解密工业级电磁流量计选型与开发
c语言·c++·算法
zSD55rt5a7 小时前
方差在扩散模型保护中的作用
人工智能·harmonyos
AD02277 小时前
HarmonyOS应用实战-启示散页-05-随机抽答案要避免连续重复:把算法放进服务层
harmonyos·arkts·鸿蒙开发
世人万千丶7 小时前
鸿蒙Flutter Flexible与Expanded的区别
学习·flutter·harmonyos·鸿蒙