@canvas-components/utils 0.2.1 → 0.2.3

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/README.md CHANGED
@@ -1,12 +1,14 @@
1
1
  # @canvas-components/utils
2
2
 
3
- `@canvas-components/utils` 提供表格包之间可复用的纯原生工具能力,覆盖数据读取、剪贴板写入和导出文件。
3
+ `@canvas-components/utils` 提供表格包之间可复用的纯原生工具能力,覆盖日期时间、数据读取、剪贴板写入和导出文件。
4
4
 
5
5
  ## 功能作用
6
6
 
7
- - 写入文本、二维表格文本、JSON 到系统剪贴板
8
- - 通过 `dataIndex` 安全读取嵌套数据
9
- - 复用数据路径比较、数值约束和矩形命中等纯函数
7
+ - 写入文本、二维表格文本、JSON 到系统剪贴板
8
+ - 通过 `dataIndex` 安全读取嵌套数据
9
+ - 使用 Day.js 风格的常用 token 格式化日期时间,不依赖 Day.js 运行时
10
+ - 复用数字、金额、百分比、科学计数法和自定义数字格式化
11
+ - 复用数据路径比较、数值约束和矩形命中等纯函数
10
12
  - 把行列数据导出为 `csv`、`txt`、`xlsx`
11
13
  - 生成导出前的中间文本 / 记录结构
12
14
  - 对大批量导出做分片让步,减少主线程长时间阻塞
@@ -25,10 +27,7 @@ pnpm add @canvas-components/utils
25
27
  ## 使用方法
26
28
 
27
29
  ```ts
28
- import {
29
- copyRows,
30
- exportTableToXlsx,
31
- } from "@canvas-components/utils";
30
+ import { copyRows, exportTableToXlsx } from "@canvas-components/utils";
32
31
 
33
32
  await copyRows([
34
33
  ["姓名", "年龄"],
@@ -51,12 +50,30 @@ await exportTableToXlsx({
51
50
 
52
51
  ## 属性说明
53
52
 
53
+ ### XLSX 大数据导出
54
+
55
+ `exportTableToXlsx` 默认在数据达到 1 万行时尝试 Web Worker,工作表构建与 XLSX 编码在后台完成。业务 `value` 回调仍在主线程分批执行;每批确认后才发送下一批,避免一次性复制全部数据。SheetJS 随包内置,不依赖 CDN。
56
+
57
+ - `worker: "auto" | true | false`:默认 `"auto"`;`true` 始终尝试 Worker,`false` 使用主线程。
58
+ - `signal: AbortSignal`:取消导出,Promise 以 `AbortError` 拒绝;Worker 编码期间也可取消。
59
+ - `onProgress(progress)`:阶段为 `preparing`、`encoding`、`saving`、`done`,包含全局 `completedRows`、`totalRows`、实际是否使用 `worker`,以及从 1 开始的 `fileIndex`、`fileCount`。`done` 表示全部文件已交给保存流程,不代表浏览器确认文件已落盘。
60
+ - `footerRows: unknown[][]`:追加汇总等尾行,无需改造原始行对象。
61
+ - `maxRowsPerFile`:每份文件的数据行上限,默认且最多 100,000 行(尾行计入,表头不计入)。
62
+ - `maxCellsPerFile`:每份文件的单元格预算,默认且最多 2,000,020 个(含表头),保证 20 列以内可以按十万行分文件;例如 40 列时每份最多 49,999 行。两个选项只能收紧安全上限。
63
+ - `saveFile(blob, fileName, { fileIndex, fileCount })`:可接管文件保存;等待回调完成后才开始下一份。不要在回调中累积保存所有 Blob。
64
+
65
+ Worker 不可用或启动被 CSP 阻止时回退到主线程分批构建;主线程最终编码仍为同步操作,不能在该阶段即时取消。Worker 运行中失败直接报错,不重复执行业务回调。
66
+
67
+ 大数据自动拆成独立的 `文件名_001.xlsx`、`文件名_002.xlsx` 等文件;单文件保持原文件名。每份重复表头,汇总只在数据末尾追加,不重新按文件计算。只生成一份工作簿,结束后终止 Worker,默认下载撤销 Blob URL 后才开始下一份;不累积全部文件,也不合成大 ZIP。
68
+
69
+ 浏览器可能要求允许“下载多个文件”,脚本不能确认是否被下载策略拦截;也可以通过 `saveFile` 接管保存。取消或失败会停止后续文件,已下载文件不会撤回。单元格文本超过 32,767 字符、单文件累计文本超过 16M UTF-16 码元时会报错,不静默截断;长文本数据可降低 `maxRowsPerFile`。内存安全取决于现有数据源及浏览器状态,千万行、多列数据仍建议服务端导出。
70
+
54
71
  ### `ExportTableColumn<Row>`
55
72
 
56
- | 属性 | 类型 | 说明 |
57
- | --- | --- | --- |
58
- | `key` | `string` | 列唯一标识 |
59
- | `title` | `string` | 导出列标题 |
73
+ | 属性 | 类型 | 说明 |
74
+ | ------- | -------------------------------------- | ------------------ |
75
+ | `key` | `string` | 列唯一标识 |
76
+ | `title` | `string` | 导出列标题 |
60
77
  | `value` | `(row: Row, index: number) => unknown` | 从行数据中取导出值 |
61
78
 
62
79
  ### `ClipboardCellValue`
@@ -71,40 +88,68 @@ type ClipboardCellValue = string | number | boolean | null | undefined;
71
88
 
72
89
  ### 剪贴板
73
90
 
74
- | 方法 | 签名 | 说明 |
75
- | --- | --- | --- |
76
- | `copyText` | `(text: string) => Promise<void>` | 写入纯文本 |
77
- | `copyRows` | `(rows: ClipboardCellValue[][]) => Promise<void>` | 把二维数据写入剪贴板 |
78
- | `copyJson` | `(value: unknown, space = 2) => Promise<void>` | 写入 JSON 文本 |
79
- | `formatRowsForClipboard` | `(rows) => string` | 把二维数据转成制表符文本 |
80
-
81
- ### 导出
82
-
83
- | 方法 | 签名 | 说明 |
84
- | --- | --- | --- |
85
- | `mapRowsToExportRecords` | `({ rows, columns }) => Record<string, unknown>[]` | 生成导出记录数组 |
86
- | `buildCsvContent` | `({ rows, columns }) => string` | 生成 CSV 文本 |
87
- | `serializeCsvRows` | `(rows, lineBreak?) => string` | 将二维数据序列化为 CSV 文本 |
88
- | `buildTxtContent` | `({ rows, columns }) => string` | 生成 TXT 文本 |
89
- | `downloadJsonFile` | `({ fileName, data, space? }) => Promise<void>` | 下载 JSON |
90
- | `exportTableToCsv` | `({ fileName, rows, columns }) => Promise<void>` | 导出 CSV |
91
- | `exportTableToTxt` | `({ fileName, rows, columns }) => Promise<void>` | 导出 TXT |
92
- | `exportTableToXlsx` | `({ fileName, rows, columns, sheetName? }) => Promise<void>` | 导出 XLSX |
93
-
94
- ### 数据读取
95
-
96
- | 方法 | 签名 | 说明 |
97
- | --- | --- | --- |
98
- | `getValueByDataIndex` | `(source, dataIndex?) => unknown` | 通过字符串、数字或属性路径数组安全读取嵌套值 |
99
- | `stringifyDataIndex` | `(dataIndex) => string` | 将属性路径转换为稳定字符串 |
100
- | `isSameDataIndex` | `(left, right) => boolean` | 判断两个属性路径是否一致 |
101
-
102
- ### 基础几何
103
-
104
- | 方法 | 签名 | 说明 |
105
- | --- | --- | --- |
106
- | `clamp` | `(value, minimum, maximum) => number` | 将数值限制在闭区间内 |
107
- | `isPointInRect` | `(x, y, rect) => boolean` | 判断坐标是否位于矩形内 |
91
+ | 方法 | 签名 | 说明 |
92
+ | ------------------------ | ------------------------------------------------- | ------------------------ |
93
+ | `copyText` | `(text: string) => Promise<void>` | 写入纯文本 |
94
+ | `copyRows` | `(rows: ClipboardCellValue[][]) => Promise<void>` | 把二维数据写入剪贴板 |
95
+ | `copyJson` | `(value: unknown, space = 2) => Promise<void>` | 写入 JSON 文本 |
96
+ | `formatRowsForClipboard` | `(rows) => string` | 把二维数据转成制表符文本 |
97
+
98
+ ### 导出
99
+
100
+ | 方法 | 签名 | 说明 |
101
+ | ------------------------ | ------------------------------------------------------------ | --------------------------- |
102
+ | `mapRowsToExportRecords` | `({ rows, columns }) => Record<string, unknown>[]` | 生成导出记录数组 |
103
+ | `buildCsvContent` | `({ rows, columns }) => string` | 生成 CSV 文本 |
104
+ | `serializeCsvRows` | `(rows, lineBreak?) => string` | 将二维数据序列化为 CSV 文本 |
105
+ | `buildTxtContent` | `({ rows, columns }) => string` | 生成 TXT 文本 |
106
+ | `downloadJsonFile` | `({ fileName, data, space? }) => Promise<void>` | 下载 JSON |
107
+ | `exportTableToCsv` | `({ fileName, rows, columns }) => Promise<void>` | 导出 CSV |
108
+ | `exportTableToTxt` | `({ fileName, rows, columns }) => Promise<void>` | 导出 TXT |
109
+ | `exportTableToXlsx` | `({ fileName, rows, columns, sheetName? }) => Promise<void>` | 导出 XLSX |
110
+
111
+ ### 数据读取
112
+
113
+ | 方法 | 签名 | 说明 |
114
+ | --------------------- | --------------------------------- | -------------------------------------------- |
115
+ | `getValueByDataIndex` | `(source, dataIndex?) => unknown` | 通过字符串、数字或属性路径数组安全读取嵌套值 |
116
+ | `stringifyDataIndex` | `(dataIndex) => string` | 将属性路径转换为稳定字符串 |
117
+ | `isSameDataIndex` | `(left, right) => boolean` | 判断两个属性路径是否一致 |
118
+
119
+ ### 日期时间
120
+
121
+ 可从主入口或 `@canvas-components/utils/date` 子路径引入。
122
+
123
+ | 方法 | 签名 | 说明 |
124
+ | ------------------------- | --------------------------------------------- | ---------------------------------------------- |
125
+ | `resolveDateValue` | `(value: unknown) => Date \| null` | 将日期、时间戳或日期字符串解析为有效日期 |
126
+ | `isDateTimeFormatPattern` | `(pattern: string) => boolean` | 判断是否包含日期时间格式 token |
127
+ | `formatDateTimeValue` | `(value, pattern, locale?) => string \| null` | 按格式字符串输出日期时间,默认使用中文区域设置 |
128
+
129
+ 常用 token 包括 `YYYY`、`YY`、`M`、`MM`、`D`、`DD`、`ddd`、`dddd`、`H`、`HH`、`h`、`hh`、`m`、`mm`、`s`、`ss`、`SSS`、`A`。方括号中的内容按普通文本输出,例如 `YYYY年M月D日 [更新]`。
130
+
131
+ ### 数字格式
132
+
133
+ 可从主入口或 `@canvas-components/utils/number` 子路径引入。
134
+
135
+ | 方法 | 签名 | 说明 |
136
+ | --------------------------- | ------------------------------------ | -------------------------------------------- |
137
+ | `parseNumberValue` | `(value: unknown) => number \| null` | 解析数字和带千分位的数字字符串 |
138
+ | `formatNumberValue` | `(value, options?) => string` | 格式化数字、金额和千分位数字 |
139
+ | `formatPercentValue` | `(value, decimalPlaces?) => string` | 将比例数值格式化为百分比 |
140
+ | `formatScientificValue` | `(value, decimalPlaces?) => string` | 格式化科学计数法 |
141
+ | `formatCustomNumberPattern` | `(value, pattern) => string` | 按常见表格数字 pattern 格式化 |
142
+ | `formatChineseNumber` | `(value, style?) => string \| null` | 将数字转换为普通或金融大写中文数字 |
143
+ | `parseChineseNumber` | `(value) => number \| null` | 将规范的普通或金融大写中文数字转为阿拉伯数字 |
144
+
145
+ 金融大写使用规范数字字符,例如 `918000` 输出 `玖拾壹万捌仟`;不兼容形近字或错别字。
146
+
147
+ ### 基础几何
148
+
149
+ | 方法 | 签名 | 说明 |
150
+ | --------------- | ------------------------------------- | ---------------------- |
151
+ | `clamp` | `(value, minimum, maximum) => number` | 将数值限制在闭区间内 |
152
+ | `isPointInRect` | `(x, y, rect) => boolean` | 判断坐标是否位于矩形内 |
108
153
 
109
154
  ## 适用场景
110
155
 
@@ -0,0 +1,162 @@
1
+ 'use strict';
2
+
3
+ // src/date/index.ts
4
+ var DATE_TIME_FORMAT_TOKEN = /Y{2,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|m{1,2}|s{1,2}|S{1,3}|A|a/;
5
+ var FORMAT_TOKEN = /\[[^\]]*\]|YYYY|MMMM|MMM|dddd|ddd|SSS|YY|MM|DD|dd|HH|hh|mm|ss|SS|M|D|d|H|h|m|s|S|A|a/g;
6
+ var ZH_WEEKDAYS = [
7
+ "\u661F\u671F\u65E5",
8
+ "\u661F\u671F\u4E00",
9
+ "\u661F\u671F\u4E8C",
10
+ "\u661F\u671F\u4E09",
11
+ "\u661F\u671F\u56DB",
12
+ "\u661F\u671F\u4E94",
13
+ "\u661F\u671F\u516D"
14
+ ];
15
+ var ZH_WEEKDAYS_SHORT = [
16
+ "\u5468\u65E5",
17
+ "\u5468\u4E00",
18
+ "\u5468\u4E8C",
19
+ "\u5468\u4E09",
20
+ "\u5468\u56DB",
21
+ "\u5468\u4E94",
22
+ "\u5468\u516D"
23
+ ];
24
+ var EN_WEEKDAYS = [
25
+ "Sunday",
26
+ "Monday",
27
+ "Tuesday",
28
+ "Wednesday",
29
+ "Thursday",
30
+ "Friday",
31
+ "Saturday"
32
+ ];
33
+ var EN_MONTHS = [
34
+ "January",
35
+ "February",
36
+ "March",
37
+ "April",
38
+ "May",
39
+ "June",
40
+ "July",
41
+ "August",
42
+ "September",
43
+ "October",
44
+ "November",
45
+ "December"
46
+ ];
47
+ function resolveDateValue(value) {
48
+ if (value instanceof Date) {
49
+ return Number.isNaN(value.getTime()) ? null : value;
50
+ }
51
+ if (typeof value !== "string" && typeof value !== "number") {
52
+ return null;
53
+ }
54
+ if (typeof value === "string") {
55
+ const localDate = resolveLocalDateString(value);
56
+ if (localDate) {
57
+ return localDate;
58
+ }
59
+ }
60
+ const resolved = new Date(value);
61
+ return Number.isNaN(resolved.getTime()) ? null : resolved;
62
+ }
63
+ function isDateTimeFormatPattern(pattern) {
64
+ return DATE_TIME_FORMAT_TOKEN.test(pattern.replace(/\[[^\]]*\]/g, ""));
65
+ }
66
+ function formatDateTimeValue(value, pattern, locale = "zh-cn") {
67
+ const date = resolveDateValue(value);
68
+ const normalizedPattern = pattern.trim();
69
+ if (!date || !normalizedPattern) {
70
+ return null;
71
+ }
72
+ const year = date.getFullYear();
73
+ const monthIndex = date.getMonth();
74
+ const month = monthIndex + 1;
75
+ const day = date.getDate();
76
+ const weekdayIndex = date.getDay();
77
+ const hour = date.getHours();
78
+ const hour12 = hour % 12 || 12;
79
+ const minute = date.getMinutes();
80
+ const second = date.getSeconds();
81
+ const millisecond = date.getMilliseconds();
82
+ const chinese = locale.toLowerCase().startsWith("zh");
83
+ const marker = hour >= 12 ? chinese ? "\u4E0B\u5348" : "PM" : chinese ? "\u4E0A\u5348" : "AM";
84
+ const weekday = chinese ? ZH_WEEKDAYS[weekdayIndex] ?? "" : EN_WEEKDAYS[weekdayIndex] ?? "";
85
+ const weekdayShort = chinese ? ZH_WEEKDAYS_SHORT[weekdayIndex] ?? "" : weekday.slice(0, 3);
86
+ const monthName = chinese ? `${month}\u6708` : EN_MONTHS[monthIndex] ?? "";
87
+ const values = {
88
+ YYYY: String(year).padStart(4, "0"),
89
+ YY: String(year).slice(-2),
90
+ M: String(month),
91
+ MM: padNumber(month),
92
+ MMM: chinese ? monthName : monthName.slice(0, 3),
93
+ MMMM: monthName,
94
+ D: String(day),
95
+ DD: padNumber(day),
96
+ d: String(weekdayIndex),
97
+ dd: chinese ? weekdayShort.slice(-1) : weekday.slice(0, 2),
98
+ ddd: weekdayShort,
99
+ dddd: weekday,
100
+ H: String(hour),
101
+ HH: padNumber(hour),
102
+ h: String(hour12),
103
+ hh: padNumber(hour12),
104
+ m: String(minute),
105
+ mm: padNumber(minute),
106
+ s: String(second),
107
+ ss: padNumber(second),
108
+ S: String(Math.floor(millisecond / 100)),
109
+ SS: String(Math.floor(millisecond / 10)).padStart(2, "0"),
110
+ SSS: String(millisecond).padStart(3, "0"),
111
+ A: marker,
112
+ a: marker.toLowerCase()
113
+ };
114
+ return normalizedPattern.replace(
115
+ FORMAT_TOKEN,
116
+ (token) => token.startsWith("[") && token.endsWith("]") ? token.slice(1, -1) : values[token] ?? token
117
+ );
118
+ }
119
+ function padNumber(value) {
120
+ return String(value).padStart(2, "0");
121
+ }
122
+ function resolveLocalDateString(value) {
123
+ const match = value.trim().match(
124
+ /^(\d{4})[-/](\d{1,2})[-/](\d{1,2})(?:[ T](\d{1,2}):?(\d{2})?(?::?(\d{2}))?(?:\.(\d{1,3}))?)?$/
125
+ );
126
+ if (!match) {
127
+ return null;
128
+ }
129
+ const [
130
+ ,
131
+ yearText,
132
+ monthText,
133
+ dayText,
134
+ hourText,
135
+ minuteText,
136
+ secondText,
137
+ millisecondText
138
+ ] = match;
139
+ const year = Number(yearText);
140
+ const monthIndex = Number(monthText) - 1;
141
+ const day = Number(dayText);
142
+ const hour = Number(hourText ?? 0);
143
+ const minute = Number(minuteText ?? 0);
144
+ const second = Number(secondText ?? 0);
145
+ const millisecond = Number((millisecondText ?? "0").padEnd(3, "0"));
146
+ const resolved = new Date(
147
+ year,
148
+ monthIndex,
149
+ day,
150
+ hour,
151
+ minute,
152
+ second,
153
+ millisecond
154
+ );
155
+ return resolved.getFullYear() === year && resolved.getMonth() === monthIndex && resolved.getDate() === day && resolved.getHours() === hour && resolved.getMinutes() === minute && resolved.getSeconds() === second ? resolved : null;
156
+ }
157
+
158
+ exports.formatDateTimeValue = formatDateTimeValue;
159
+ exports.isDateTimeFormatPattern = isDateTimeFormatPattern;
160
+ exports.resolveDateValue = resolveDateValue;
161
+ //# sourceMappingURL=index.cjs.map
162
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/date/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/** Day.js 风格日期时间格式中支持识别的 token。 */\nconst DATE_TIME_FORMAT_TOKEN =\n /Y{2,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|m{1,2}|s{1,2}|S{1,3}|A|a/;\n\nconst FORMAT_TOKEN =\n /\\[[^\\]]*\\]|YYYY|MMMM|MMM|dddd|ddd|SSS|YY|MM|DD|dd|HH|hh|mm|ss|SS|M|D|d|H|h|m|s|S|A|a/g;\n\nconst ZH_WEEKDAYS = [\n \"星期日\",\n \"星期一\",\n \"星期二\",\n \"星期三\",\n \"星期四\",\n \"星期五\",\n \"星期六\",\n];\nconst ZH_WEEKDAYS_SHORT = [\n \"周日\",\n \"周一\",\n \"周二\",\n \"周三\",\n \"周四\",\n \"周五\",\n \"周六\",\n];\nconst EN_WEEKDAYS = [\n \"Sunday\",\n \"Monday\",\n \"Tuesday\",\n \"Wednesday\",\n \"Thursday\",\n \"Friday\",\n \"Saturday\",\n];\nconst EN_MONTHS = [\n \"January\",\n \"February\",\n \"March\",\n \"April\",\n \"May\",\n \"June\",\n \"July\",\n \"August\",\n \"September\",\n \"October\",\n \"November\",\n \"December\",\n];\n\n/** 将未知值解析为有效日期。 */\nexport function resolveDateValue(value: unknown): Date | null {\n if (value instanceof Date) {\n return Number.isNaN(value.getTime()) ? null : value;\n }\n if (typeof value !== \"string\" && typeof value !== \"number\") {\n return null;\n }\n if (typeof value === \"string\") {\n const localDate = resolveLocalDateString(value);\n if (localDate) {\n return localDate;\n }\n }\n const resolved = new Date(value);\n return Number.isNaN(resolved.getTime()) ? null : resolved;\n}\n\n/** 判断字符串中是否包含 Day.js 风格的日期时间格式 token。 */\nexport function isDateTimeFormatPattern(pattern: string) {\n return DATE_TIME_FORMAT_TOKEN.test(pattern.replace(/\\[[^\\]]*\\]/g, \"\"));\n}\n\n/**\n * 使用 Day.js 风格的常用 token 格式化日期时间,不依赖 Day.js 运行时。\n *\n * 支持年、月、日、星期、24/12 小时、分、秒、毫秒和上下午标记;\n * 默认使用中文区域设置,值或 pattern 无效时返回 `null`。\n */\nexport function formatDateTimeValue(\n value: unknown,\n pattern: string,\n locale = \"zh-cn\",\n): string | null {\n const date = resolveDateValue(value);\n const normalizedPattern = pattern.trim();\n if (!date || !normalizedPattern) {\n return null;\n }\n const year = date.getFullYear();\n const monthIndex = date.getMonth();\n const month = monthIndex + 1;\n const day = date.getDate();\n const weekdayIndex = date.getDay();\n const hour = date.getHours();\n const hour12 = hour % 12 || 12;\n const minute = date.getMinutes();\n const second = date.getSeconds();\n const millisecond = date.getMilliseconds();\n const chinese = locale.toLowerCase().startsWith(\"zh\");\n const marker =\n hour >= 12 ? (chinese ? \"下午\" : \"PM\") : chinese ? \"上午\" : \"AM\";\n const weekday = chinese\n ? (ZH_WEEKDAYS[weekdayIndex] ?? \"\")\n : (EN_WEEKDAYS[weekdayIndex] ?? \"\");\n const weekdayShort = chinese\n ? (ZH_WEEKDAYS_SHORT[weekdayIndex] ?? \"\")\n : weekday.slice(0, 3);\n const monthName = chinese ? `${month}月` : (EN_MONTHS[monthIndex] ?? \"\");\n const values: Record<string, string> = {\n YYYY: String(year).padStart(4, \"0\"),\n YY: String(year).slice(-2),\n M: String(month),\n MM: padNumber(month),\n MMM: chinese ? monthName : monthName.slice(0, 3),\n MMMM: monthName,\n D: String(day),\n DD: padNumber(day),\n d: String(weekdayIndex),\n dd: chinese ? weekdayShort.slice(-1) : weekday.slice(0, 2),\n ddd: weekdayShort,\n dddd: weekday,\n H: String(hour),\n HH: padNumber(hour),\n h: String(hour12),\n hh: padNumber(hour12),\n m: String(minute),\n mm: padNumber(minute),\n s: String(second),\n ss: padNumber(second),\n S: String(Math.floor(millisecond / 100)),\n SS: String(Math.floor(millisecond / 10)).padStart(2, \"0\"),\n SSS: String(millisecond).padStart(3, \"0\"),\n A: marker,\n a: marker.toLowerCase(),\n };\n\n return normalizedPattern.replace(FORMAT_TOKEN, (token) =>\n token.startsWith(\"[\") && token.endsWith(\"]\")\n ? token.slice(1, -1)\n : (values[token] ?? token),\n );\n}\n\nfunction padNumber(value: number) {\n return String(value).padStart(2, \"0\");\n}\n\nfunction resolveLocalDateString(value: string) {\n const match = value\n .trim()\n .match(\n /^(\\d{4})[-/](\\d{1,2})[-/](\\d{1,2})(?:[ T](\\d{1,2}):?(\\d{2})?(?::?(\\d{2}))?(?:\\.(\\d{1,3}))?)?$/,\n );\n if (!match) {\n return null;\n }\n const [\n ,\n yearText,\n monthText,\n dayText,\n hourText,\n minuteText,\n secondText,\n millisecondText,\n ] = match;\n const year = Number(yearText);\n const monthIndex = Number(monthText) - 1;\n const day = Number(dayText);\n const hour = Number(hourText ?? 0);\n const minute = Number(minuteText ?? 0);\n const second = Number(secondText ?? 0);\n const millisecond = Number((millisecondText ?? \"0\").padEnd(3, \"0\"));\n const resolved = new Date(\n year,\n monthIndex,\n day,\n hour,\n minute,\n second,\n millisecond,\n );\n return resolved.getFullYear() === year &&\n resolved.getMonth() === monthIndex &&\n resolved.getDate() === day &&\n resolved.getHours() === hour &&\n resolved.getMinutes() === minute &&\n resolved.getSeconds() === second\n ? resolved\n : null;\n}\n"]}
@@ -0,0 +1,13 @@
1
+ /** 将未知值解析为有效日期。 */
2
+ declare function resolveDateValue(value: unknown): Date | null;
3
+ /** 判断字符串中是否包含 Day.js 风格的日期时间格式 token。 */
4
+ declare function isDateTimeFormatPattern(pattern: string): boolean;
5
+ /**
6
+ * 使用 Day.js 风格的常用 token 格式化日期时间,不依赖 Day.js 运行时。
7
+ *
8
+ * 支持年、月、日、星期、24/12 小时、分、秒、毫秒和上下午标记;
9
+ * 默认使用中文区域设置,值或 pattern 无效时返回 `null`。
10
+ */
11
+ declare function formatDateTimeValue(value: unknown, pattern: string, locale?: string): string | null;
12
+
13
+ export { formatDateTimeValue, isDateTimeFormatPattern, resolveDateValue };
@@ -0,0 +1,13 @@
1
+ /** 将未知值解析为有效日期。 */
2
+ declare function resolveDateValue(value: unknown): Date | null;
3
+ /** 判断字符串中是否包含 Day.js 风格的日期时间格式 token。 */
4
+ declare function isDateTimeFormatPattern(pattern: string): boolean;
5
+ /**
6
+ * 使用 Day.js 风格的常用 token 格式化日期时间,不依赖 Day.js 运行时。
7
+ *
8
+ * 支持年、月、日、星期、24/12 小时、分、秒、毫秒和上下午标记;
9
+ * 默认使用中文区域设置,值或 pattern 无效时返回 `null`。
10
+ */
11
+ declare function formatDateTimeValue(value: unknown, pattern: string, locale?: string): string | null;
12
+
13
+ export { formatDateTimeValue, isDateTimeFormatPattern, resolveDateValue };
@@ -0,0 +1,158 @@
1
+ // src/date/index.ts
2
+ var DATE_TIME_FORMAT_TOKEN = /Y{2,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|m{1,2}|s{1,2}|S{1,3}|A|a/;
3
+ var FORMAT_TOKEN = /\[[^\]]*\]|YYYY|MMMM|MMM|dddd|ddd|SSS|YY|MM|DD|dd|HH|hh|mm|ss|SS|M|D|d|H|h|m|s|S|A|a/g;
4
+ var ZH_WEEKDAYS = [
5
+ "\u661F\u671F\u65E5",
6
+ "\u661F\u671F\u4E00",
7
+ "\u661F\u671F\u4E8C",
8
+ "\u661F\u671F\u4E09",
9
+ "\u661F\u671F\u56DB",
10
+ "\u661F\u671F\u4E94",
11
+ "\u661F\u671F\u516D"
12
+ ];
13
+ var ZH_WEEKDAYS_SHORT = [
14
+ "\u5468\u65E5",
15
+ "\u5468\u4E00",
16
+ "\u5468\u4E8C",
17
+ "\u5468\u4E09",
18
+ "\u5468\u56DB",
19
+ "\u5468\u4E94",
20
+ "\u5468\u516D"
21
+ ];
22
+ var EN_WEEKDAYS = [
23
+ "Sunday",
24
+ "Monday",
25
+ "Tuesday",
26
+ "Wednesday",
27
+ "Thursday",
28
+ "Friday",
29
+ "Saturday"
30
+ ];
31
+ var EN_MONTHS = [
32
+ "January",
33
+ "February",
34
+ "March",
35
+ "April",
36
+ "May",
37
+ "June",
38
+ "July",
39
+ "August",
40
+ "September",
41
+ "October",
42
+ "November",
43
+ "December"
44
+ ];
45
+ function resolveDateValue(value) {
46
+ if (value instanceof Date) {
47
+ return Number.isNaN(value.getTime()) ? null : value;
48
+ }
49
+ if (typeof value !== "string" && typeof value !== "number") {
50
+ return null;
51
+ }
52
+ if (typeof value === "string") {
53
+ const localDate = resolveLocalDateString(value);
54
+ if (localDate) {
55
+ return localDate;
56
+ }
57
+ }
58
+ const resolved = new Date(value);
59
+ return Number.isNaN(resolved.getTime()) ? null : resolved;
60
+ }
61
+ function isDateTimeFormatPattern(pattern) {
62
+ return DATE_TIME_FORMAT_TOKEN.test(pattern.replace(/\[[^\]]*\]/g, ""));
63
+ }
64
+ function formatDateTimeValue(value, pattern, locale = "zh-cn") {
65
+ const date = resolveDateValue(value);
66
+ const normalizedPattern = pattern.trim();
67
+ if (!date || !normalizedPattern) {
68
+ return null;
69
+ }
70
+ const year = date.getFullYear();
71
+ const monthIndex = date.getMonth();
72
+ const month = monthIndex + 1;
73
+ const day = date.getDate();
74
+ const weekdayIndex = date.getDay();
75
+ const hour = date.getHours();
76
+ const hour12 = hour % 12 || 12;
77
+ const minute = date.getMinutes();
78
+ const second = date.getSeconds();
79
+ const millisecond = date.getMilliseconds();
80
+ const chinese = locale.toLowerCase().startsWith("zh");
81
+ const marker = hour >= 12 ? chinese ? "\u4E0B\u5348" : "PM" : chinese ? "\u4E0A\u5348" : "AM";
82
+ const weekday = chinese ? ZH_WEEKDAYS[weekdayIndex] ?? "" : EN_WEEKDAYS[weekdayIndex] ?? "";
83
+ const weekdayShort = chinese ? ZH_WEEKDAYS_SHORT[weekdayIndex] ?? "" : weekday.slice(0, 3);
84
+ const monthName = chinese ? `${month}\u6708` : EN_MONTHS[monthIndex] ?? "";
85
+ const values = {
86
+ YYYY: String(year).padStart(4, "0"),
87
+ YY: String(year).slice(-2),
88
+ M: String(month),
89
+ MM: padNumber(month),
90
+ MMM: chinese ? monthName : monthName.slice(0, 3),
91
+ MMMM: monthName,
92
+ D: String(day),
93
+ DD: padNumber(day),
94
+ d: String(weekdayIndex),
95
+ dd: chinese ? weekdayShort.slice(-1) : weekday.slice(0, 2),
96
+ ddd: weekdayShort,
97
+ dddd: weekday,
98
+ H: String(hour),
99
+ HH: padNumber(hour),
100
+ h: String(hour12),
101
+ hh: padNumber(hour12),
102
+ m: String(minute),
103
+ mm: padNumber(minute),
104
+ s: String(second),
105
+ ss: padNumber(second),
106
+ S: String(Math.floor(millisecond / 100)),
107
+ SS: String(Math.floor(millisecond / 10)).padStart(2, "0"),
108
+ SSS: String(millisecond).padStart(3, "0"),
109
+ A: marker,
110
+ a: marker.toLowerCase()
111
+ };
112
+ return normalizedPattern.replace(
113
+ FORMAT_TOKEN,
114
+ (token) => token.startsWith("[") && token.endsWith("]") ? token.slice(1, -1) : values[token] ?? token
115
+ );
116
+ }
117
+ function padNumber(value) {
118
+ return String(value).padStart(2, "0");
119
+ }
120
+ function resolveLocalDateString(value) {
121
+ const match = value.trim().match(
122
+ /^(\d{4})[-/](\d{1,2})[-/](\d{1,2})(?:[ T](\d{1,2}):?(\d{2})?(?::?(\d{2}))?(?:\.(\d{1,3}))?)?$/
123
+ );
124
+ if (!match) {
125
+ return null;
126
+ }
127
+ const [
128
+ ,
129
+ yearText,
130
+ monthText,
131
+ dayText,
132
+ hourText,
133
+ minuteText,
134
+ secondText,
135
+ millisecondText
136
+ ] = match;
137
+ const year = Number(yearText);
138
+ const monthIndex = Number(monthText) - 1;
139
+ const day = Number(dayText);
140
+ const hour = Number(hourText ?? 0);
141
+ const minute = Number(minuteText ?? 0);
142
+ const second = Number(secondText ?? 0);
143
+ const millisecond = Number((millisecondText ?? "0").padEnd(3, "0"));
144
+ const resolved = new Date(
145
+ year,
146
+ monthIndex,
147
+ day,
148
+ hour,
149
+ minute,
150
+ second,
151
+ millisecond
152
+ );
153
+ return resolved.getFullYear() === year && resolved.getMonth() === monthIndex && resolved.getDate() === day && resolved.getHours() === hour && resolved.getMinutes() === minute && resolved.getSeconds() === second ? resolved : null;
154
+ }
155
+
156
+ export { formatDateTimeValue, isDateTimeFormatPattern, resolveDateValue };
157
+ //# sourceMappingURL=index.js.map
158
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/date/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/** Day.js 风格日期时间格式中支持识别的 token。 */\nconst DATE_TIME_FORMAT_TOKEN =\n /Y{2,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|m{1,2}|s{1,2}|S{1,3}|A|a/;\n\nconst FORMAT_TOKEN =\n /\\[[^\\]]*\\]|YYYY|MMMM|MMM|dddd|ddd|SSS|YY|MM|DD|dd|HH|hh|mm|ss|SS|M|D|d|H|h|m|s|S|A|a/g;\n\nconst ZH_WEEKDAYS = [\n \"星期日\",\n \"星期一\",\n \"星期二\",\n \"星期三\",\n \"星期四\",\n \"星期五\",\n \"星期六\",\n];\nconst ZH_WEEKDAYS_SHORT = [\n \"周日\",\n \"周一\",\n \"周二\",\n \"周三\",\n \"周四\",\n \"周五\",\n \"周六\",\n];\nconst EN_WEEKDAYS = [\n \"Sunday\",\n \"Monday\",\n \"Tuesday\",\n \"Wednesday\",\n \"Thursday\",\n \"Friday\",\n \"Saturday\",\n];\nconst EN_MONTHS = [\n \"January\",\n \"February\",\n \"March\",\n \"April\",\n \"May\",\n \"June\",\n \"July\",\n \"August\",\n \"September\",\n \"October\",\n \"November\",\n \"December\",\n];\n\n/** 将未知值解析为有效日期。 */\nexport function resolveDateValue(value: unknown): Date | null {\n if (value instanceof Date) {\n return Number.isNaN(value.getTime()) ? null : value;\n }\n if (typeof value !== \"string\" && typeof value !== \"number\") {\n return null;\n }\n if (typeof value === \"string\") {\n const localDate = resolveLocalDateString(value);\n if (localDate) {\n return localDate;\n }\n }\n const resolved = new Date(value);\n return Number.isNaN(resolved.getTime()) ? null : resolved;\n}\n\n/** 判断字符串中是否包含 Day.js 风格的日期时间格式 token。 */\nexport function isDateTimeFormatPattern(pattern: string) {\n return DATE_TIME_FORMAT_TOKEN.test(pattern.replace(/\\[[^\\]]*\\]/g, \"\"));\n}\n\n/**\n * 使用 Day.js 风格的常用 token 格式化日期时间,不依赖 Day.js 运行时。\n *\n * 支持年、月、日、星期、24/12 小时、分、秒、毫秒和上下午标记;\n * 默认使用中文区域设置,值或 pattern 无效时返回 `null`。\n */\nexport function formatDateTimeValue(\n value: unknown,\n pattern: string,\n locale = \"zh-cn\",\n): string | null {\n const date = resolveDateValue(value);\n const normalizedPattern = pattern.trim();\n if (!date || !normalizedPattern) {\n return null;\n }\n const year = date.getFullYear();\n const monthIndex = date.getMonth();\n const month = monthIndex + 1;\n const day = date.getDate();\n const weekdayIndex = date.getDay();\n const hour = date.getHours();\n const hour12 = hour % 12 || 12;\n const minute = date.getMinutes();\n const second = date.getSeconds();\n const millisecond = date.getMilliseconds();\n const chinese = locale.toLowerCase().startsWith(\"zh\");\n const marker =\n hour >= 12 ? (chinese ? \"下午\" : \"PM\") : chinese ? \"上午\" : \"AM\";\n const weekday = chinese\n ? (ZH_WEEKDAYS[weekdayIndex] ?? \"\")\n : (EN_WEEKDAYS[weekdayIndex] ?? \"\");\n const weekdayShort = chinese\n ? (ZH_WEEKDAYS_SHORT[weekdayIndex] ?? \"\")\n : weekday.slice(0, 3);\n const monthName = chinese ? `${month}月` : (EN_MONTHS[monthIndex] ?? \"\");\n const values: Record<string, string> = {\n YYYY: String(year).padStart(4, \"0\"),\n YY: String(year).slice(-2),\n M: String(month),\n MM: padNumber(month),\n MMM: chinese ? monthName : monthName.slice(0, 3),\n MMMM: monthName,\n D: String(day),\n DD: padNumber(day),\n d: String(weekdayIndex),\n dd: chinese ? weekdayShort.slice(-1) : weekday.slice(0, 2),\n ddd: weekdayShort,\n dddd: weekday,\n H: String(hour),\n HH: padNumber(hour),\n h: String(hour12),\n hh: padNumber(hour12),\n m: String(minute),\n mm: padNumber(minute),\n s: String(second),\n ss: padNumber(second),\n S: String(Math.floor(millisecond / 100)),\n SS: String(Math.floor(millisecond / 10)).padStart(2, \"0\"),\n SSS: String(millisecond).padStart(3, \"0\"),\n A: marker,\n a: marker.toLowerCase(),\n };\n\n return normalizedPattern.replace(FORMAT_TOKEN, (token) =>\n token.startsWith(\"[\") && token.endsWith(\"]\")\n ? token.slice(1, -1)\n : (values[token] ?? token),\n );\n}\n\nfunction padNumber(value: number) {\n return String(value).padStart(2, \"0\");\n}\n\nfunction resolveLocalDateString(value: string) {\n const match = value\n .trim()\n .match(\n /^(\\d{4})[-/](\\d{1,2})[-/](\\d{1,2})(?:[ T](\\d{1,2}):?(\\d{2})?(?::?(\\d{2}))?(?:\\.(\\d{1,3}))?)?$/,\n );\n if (!match) {\n return null;\n }\n const [\n ,\n yearText,\n monthText,\n dayText,\n hourText,\n minuteText,\n secondText,\n millisecondText,\n ] = match;\n const year = Number(yearText);\n const monthIndex = Number(monthText) - 1;\n const day = Number(dayText);\n const hour = Number(hourText ?? 0);\n const minute = Number(minuteText ?? 0);\n const second = Number(secondText ?? 0);\n const millisecond = Number((millisecondText ?? \"0\").padEnd(3, \"0\"));\n const resolved = new Date(\n year,\n monthIndex,\n day,\n hour,\n minute,\n second,\n millisecond,\n );\n return resolved.getFullYear() === year &&\n resolved.getMonth() === monthIndex &&\n resolved.getDate() === day &&\n resolved.getHours() === hour &&\n resolved.getMinutes() === minute &&\n resolved.getSeconds() === second\n ? resolved\n : null;\n}\n"]}