跨端: feat(P6): 日历纯逻辑 model/Calendar.ts + 6 条可跑判据(第 1 步的逻辑那一半,不需要设备)
This commit is contained in:
91
client/harmony/entry/src/main/ets/model/Calendar.ts
Normal file
91
client/harmony/entry/src/main/ets/model/Calendar.ts
Normal file
@ -0,0 +1,91 @@
|
||||
/**
|
||||
* 日历的**纯逻辑**(无 `@ohos` 依赖 ⇒ 判据能用 node `--experimental-strip-types` 直接跑它)。
|
||||
*
|
||||
* 为什么先做这一层:P6 的第 1、2 步(结构、圆角与裁剪)**不需要设备**,
|
||||
* 而日历真正会悄悄错的是"月份网格"这类算术 —— 闰年、月首落在星期几、6 行还是 5 行、
|
||||
* 跨年翻月、"今天"标在哪一格。这些错了,界面上是"看起来正常但日子错位",
|
||||
* 属于最难靠肉眼发现的一类,所以把它放在可跑判据下面。
|
||||
*
|
||||
* 时区:一律用 **UTC** 构造与取星期(`Date.UTC` + `getUTCDay`),
|
||||
* 免得同一份代码在 UTC+8 与 UTC-5 上给出不同的列。
|
||||
*/
|
||||
export class DayCell {
|
||||
/** 该格属于本月的第几天;不在本月则为 0 */
|
||||
day: number = 0;
|
||||
/** 格里显示的数字(上月尾/下月头也要显示,便于对照) */
|
||||
label: number = 0;
|
||||
inMonth: boolean = false;
|
||||
isToday: boolean = false;
|
||||
/** `YYYY-MM-DD`,格子的唯一键 */
|
||||
iso: string = '';
|
||||
}
|
||||
|
||||
export function isLeapYear(year: number): boolean {
|
||||
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
||||
}
|
||||
|
||||
export function daysInMonth(year: number, month: number): number {
|
||||
const table: number[] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
||||
if (month === 2 && isLeapYear(year)) {
|
||||
return 29;
|
||||
}
|
||||
return table[month - 1];
|
||||
}
|
||||
|
||||
function isoOf(year: number, month: number, day: number): string {
|
||||
const mm: string = month < 10 ? `0${month}` : `${month}`;
|
||||
const dd: string = day < 10 ? `0${day}` : `${day}`;
|
||||
return `${year}-${mm}-${dd}`;
|
||||
}
|
||||
|
||||
/** 该月 1 号落在星期几:0=周日 … 6=周六(UTC,与本地时区无关) */
|
||||
export function firstWeekday(year: number, month: number): number {
|
||||
return new Date(Date.UTC(year, month - 1, 1)).getUTCDay();
|
||||
}
|
||||
|
||||
/**
|
||||
* 月首之前要先留几个空格 —— 由 `weekStart`(0=周日,1=周一)决定。
|
||||
* 这是"周一起始"与"周日起始"唯一的分叉点,所以单独抽出来判。
|
||||
*/
|
||||
export function leadingBlanks(year: number, month: number, weekStart: number): number {
|
||||
const w: number = firstWeekday(year, month);
|
||||
return (w - weekStart + 7) % 7;
|
||||
}
|
||||
|
||||
/** 翻月(可跨年):`addMonths(2026, 12, 1)` → `2027-01` */
|
||||
export function addMonths(year: number, month: number, delta: number): number[] {
|
||||
const total: number = (year * 12 + (month - 1)) + delta;
|
||||
return [Math.floor(total / 12), (total % 12) + 1];
|
||||
}
|
||||
|
||||
export function monthLabel(year: number, month: number): string {
|
||||
return `${year}年${month}月`;
|
||||
}
|
||||
|
||||
/** 整月网格:每行 7 格,行数 4~6(由月长与月首星期共同决定) */
|
||||
export function monthGrid(year: number, month: number, weekStart: number, today?: string): DayCell[][] {
|
||||
const blanks: number = leadingBlanks(year, month, weekStart);
|
||||
const len: number = daysInMonth(year, month);
|
||||
const cells: DayCell[] = [];
|
||||
for (let i = 0; i < blanks; i++) {
|
||||
cells.push(new DayCell());
|
||||
}
|
||||
for (let d = 1; d <= len; d++) {
|
||||
const c = new DayCell();
|
||||
c.day = d;
|
||||
c.label = d;
|
||||
c.inMonth = true;
|
||||
c.iso = isoOf(year, month, d);
|
||||
c.isToday = today !== undefined && c.iso === today;
|
||||
cells.push(c);
|
||||
}
|
||||
// 补齐最后一行(上月尾/下月头也填上日期,界面才好画连续的日子)
|
||||
while (cells.length % 7 !== 0) {
|
||||
cells.push(new DayCell());
|
||||
}
|
||||
const rows: DayCell[][] = [];
|
||||
for (let i = 0; i < cells.length; i += 7) {
|
||||
rows.push(cells.slice(i, i + 7));
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
Reference in New Issue
Block a user