dsh-calendar 0.10.1 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,10 @@
4
4
 
5
5
  [历史英文记录](docs/CHANGELOG.en.md)
6
6
 
7
+ ## 0.10.2 (2026-10-05)
8
+
9
+ - 严格验证真实 ISO 日期,拒绝不存在的日期、模糊地域格式与缺少时区的日期时间,防止日程悄悄改期。
10
+
7
11
  ## 0.10.1 (2026-10-03)
8
12
 
9
13
  - 修复语言切换时菜单和悬浮按钮不更新;日期、星期、重复规则、ICS 告警与连接引导支持英文。
package/README.en.md CHANGED
@@ -2,9 +2,11 @@
2
2
 
3
3
  [中文](README.md)
4
4
 
5
+ ![dsh-calendar whale girl plugin cover](https://raw.githubusercontent.com/STARDUSTLC666/dsh-calendar/main/assets/cover-whale-girl.png)
6
+
5
7
  View, create and reschedule CalDAV events from DSH.
6
8
 
7
- [![npm](https://img.shields.io/npm/v/dsh-calendar)](https://www.npmjs.com/package/dsh-calendar) [![downloads](https://img.shields.io/npm/dm/dsh-calendar)](https://www.npmjs.com/package/dsh-calendar)
9
+ [![npm](https://img.shields.io/npm/v/dsh-calendar)](https://www.npmjs.com/package/dsh-calendar) [![downloads](https://raw.githubusercontent.com/STARDUSTLC666/dsh-suite/npm-downloads/assets/dsh-calendar-downloads.svg)](https://www.npmjs.com/package/dsh-calendar)
8
10
 
9
11
  ## What it does
10
12
 
package/README.md CHANGED
@@ -2,9 +2,11 @@
2
2
 
3
3
  [English](README.en.md)
4
4
 
5
+ ![dsh-calendar 鲸鱼娘插件封面](https://raw.githubusercontent.com/STARDUSTLC666/dsh-calendar/main/assets/cover-whale-girl.png)
6
+
5
7
  连接 CalDAV 日历,在 DSH 中查看、创建和调整日程。
6
8
 
7
- [![npm](https://img.shields.io/npm/v/dsh-calendar)](https://www.npmjs.com/package/dsh-calendar) [![downloads](https://img.shields.io/npm/dm/dsh-calendar)](https://www.npmjs.com/package/dsh-calendar)
9
+ [![npm](https://img.shields.io/npm/v/dsh-calendar)](https://www.npmjs.com/package/dsh-calendar) [![downloads](https://raw.githubusercontent.com/STARDUSTLC666/dsh-suite/npm-downloads/assets/dsh-calendar-downloads.svg)](https://www.npmjs.com/package/dsh-calendar)
8
10
 
9
11
  ## 功能
10
12
 
@@ -4,6 +4,10 @@
4
4
 
5
5
  These English notes preserve the earlier translations. The main changelog contains the consolidated version history.
6
6
 
7
+ ## 0.10.2 (2026-10-05)
8
+
9
+ - Validate real ISO calendar dates, rejecting nonexistent dates, ambiguous locale formats and datetimes without a timezone.
10
+
7
11
  ## 0.10.0 (2026-10-03)
8
12
 
9
13
  - Import ICS files or pasted content with an explicit preview, selection and batch confirmation. Preserve recurrence rules, excluded dates, moved exceptions and timezone definitions.
package/docs/USAGE.en.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  [Overview](../README.en.md) · [Changelog](../CHANGELOG.md) · [Validation](VALIDATION.md)
4
4
 
5
+ ## Current improvements
6
+
7
+ Use YYYY-MM-DD for dates and ISO datetimes with Z or a UTC offset. Invalid dates fail before remote creation or updates. Existing events are preserved.
8
+
5
9
  ## Installation
6
10
 
7
11
  ```bash
package/docs/USAGE.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  [返回简介](../README.md) · [更新记录](../CHANGELOG.md) · [验证记录](VALIDATION.md)
4
4
 
5
+ ## 本次改进
6
+
7
+ 日期使用 YYYY-MM-DD;带时间使用带 Z 或 UTC 偏移的 ISO 格式。错误日期会在创建或更新远端日程前报错,原日程保留。
8
+
5
9
  ## 安装
6
10
 
7
11
  ```bash
package/lib/ical.js CHANGED
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { randomUUID } from 'node:crypto';
8
8
  import ICAL from 'ical.js';
9
+ import { assertIsoTime } from './time.js';
9
10
  /** 展开重复事件超出迭代预算:显式报错,避免把「没走到窗口」静默当成「窗口内没有实例」。 */
10
11
  export class ExpansionLimitError extends Error {
11
12
  constructor(message) {
@@ -445,6 +446,7 @@ export function updateICalString(data, changes) {
445
446
  }
446
447
  /** 把 ISO 字符串解析成 ICAL.Time(全天 YYYY-MM-DD 或转 UTC 的定时时间)。 */
447
448
  function parseTime(value, allDay) {
449
+ assertIsoTime(value, '时间');
448
450
  if (allDay || isDateOnly(value)) {
449
451
  return ICAL.Time.fromDateString(value);
450
452
  }
package/lib/time.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ /** Validate calendar dates before Date/ical.js can silently normalize them. */
2
+ export declare const DATE_ONLY_PATTERN: RegExp;
3
+ export declare function assertIsoTime(value: string, label: string): void;
package/lib/time.js ADDED
@@ -0,0 +1,26 @@
1
+ /** Validate calendar dates before Date/ical.js can silently normalize them. */
2
+ export const DATE_ONLY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
3
+ const DATETIME_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,9}))?)?(Z|[+-]\d{2}:\d{2})$/;
4
+ export function assertIsoTime(value, label) {
5
+ const dateOnly = DATE_ONLY_PATTERN.exec(value);
6
+ const parts = dateOnly ?? DATETIME_PATTERN.exec(value);
7
+ const invalid = () => new Error(label + ' 不是合法的 ISO 8601 时间:' + value + '。必须是真实存在的日期;定时事件须含时区,例如 2025-01-01T09:00:00+08:00 或 2025-01-01T09:00:00Z。');
8
+ if (!parts)
9
+ throw invalid();
10
+ const year = Number(parts[1]), month = Number(parts[2]), day = Number(parts[3]);
11
+ const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
12
+ const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
13
+ if (year < 1 || month < 1 || month > 12 || day < 1 || day > days[month - 1])
14
+ throw invalid();
15
+ if (dateOnly)
16
+ return;
17
+ const hour = Number(parts[4]), minute = Number(parts[5]), second = Number(parts[6] ?? 0);
18
+ // ISO 8601 permits 24:00 only as midnight at the end of this valid date.
19
+ if (hour > 24 || minute > 59 || second > 59 || (hour === 24 && (minute !== 0 || second !== 0 || /[1-9]/.test(parts[7] ?? ''))))
20
+ throw invalid();
21
+ const zone = parts[8];
22
+ if (zone !== 'Z' && (Number(zone.slice(1, 3)) > 23 || Number(zone.slice(4)) > 59))
23
+ throw invalid();
24
+ if (Number.isNaN(new Date(value).getTime()))
25
+ throw invalid();
26
+ }
package/lib/tools.d.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { type CalendarConfig } from './config.js';
8
8
  import { type CalendarEvent } from './ical.js';
9
+ export { assertIsoTime } from './time.js';
9
10
  /** 模型可见的内容块。 */
10
11
  export interface ContentBlock {
11
12
  type: 'text';
@@ -29,7 +30,6 @@ export interface CalendarToolDefinition {
29
30
  }
30
31
  export declare function asRecord(args: unknown): Record<string, unknown>;
31
32
  export declare function optionalString(args: Record<string, unknown>, key: string): string | undefined;
32
- export declare function assertIsoTime(value: string, label: string): void;
33
33
  export declare function assertTimeRange(start: string, end: string): void;
34
34
  export declare function isoNoMillis(value: string): string;
35
35
  /** 按开始时间升序稳定排序(CalDAV 服务端返回顺序不保证稳定)。 */
package/lib/tools.js CHANGED
@@ -7,6 +7,8 @@
7
7
  import { CalendarService } from './caldav.js';
8
8
  import { CALENDAR_PROVIDERS, buildCaldavUrl, resolveAuthMethod, resolveConfig, resolveCredentials, validateOAuthUrl, } from './config.js';
9
9
  import { compileParameters } from './parameters.js';
10
+ import { assertIsoTime, DATE_ONLY_PATTERN } from './time.js';
11
+ export { assertIsoTime } from './time.js';
10
12
  const EVENT_SCHEMA = { type: 'object', additionalProperties: true };
11
13
  const TIMEOUT_MS = 60000;
12
14
  export function asRecord(args) {
@@ -48,28 +50,11 @@ function clampedInteger(args, key, fallback, min, max) {
48
50
  return fallback;
49
51
  return Math.min(max, Math.max(min, value));
50
52
  }
51
- const DATE_ONLY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
52
- export function assertIsoTime(value, label) {
53
- const dateOnly = DATE_ONLY_PATTERN.exec(value);
54
- if (dateOnly !== null) {
55
- const year = Number(dateOnly[1]);
56
- const month = Number(dateOnly[2]);
57
- const day = Number(dateOnly[3]);
58
- const date = new Date(Date.UTC(year, month - 1, day));
59
- if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) {
60
- throw new Error(label + ' 不是合法的日期:' + value + '(YYYY-MM-DD 必须是真实存在的日期)。');
61
- }
62
- return;
63
- }
64
- if (Number.isNaN(new Date(value).getTime())) {
65
- throw new Error(label + ' 不是合法的 ISO 8601 时间,例如 2025-01-01T09:00:00+08:00 或 2025-01-01T09:00:00Z。');
66
- }
67
- }
68
53
  /** 日期/时间统一转 epoch 毫秒(date-only 按 UTC 零点),用于范围比较。 */
69
54
  function toEpochMs(value) {
70
55
  const dateOnly = DATE_ONLY_PATTERN.exec(value);
71
56
  if (dateOnly !== null) {
72
- return Date.UTC(Number(dateOnly[1]), Number(dateOnly[2]) - 1, Number(dateOnly[3]));
57
+ return new Date(value + 'T00:00:00Z').getTime();
73
58
  }
74
59
  return new Date(value).getTime();
75
60
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-calendar",
3
- "version": "0.10.1",
3
+ "version": "0.10.2",
4
4
  "description": "连接 CalDAV 日历,在 DSH 中查看、创建和调整日程。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",