nv-date-y7 1.0.1 → 1.0.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/README.md +207 -0
- package/TEST/check.js +182 -0
- package/TEST/perf-iso-to-mts.js +84 -0
- package/TEST/perf-mts-to-y7.js +326 -0
- package/TEST/perf-y7-to-mts.js +387 -0
- package/index.js +358 -47
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# nv-date-y7
|
|
2
|
+
|
|
3
|
+
High-performance date-string ↔ timestamp converter
|
|
4
|
+
Zero `Date.parse` dependency.
|
|
5
|
+
Pure JS — no bindings, no native addons, no C/C++.
|
|
6
|
+
Pure arithmetic civil calendar implementation.
|
|
7
|
+
|
|
8
|
+
Designed for:
|
|
9
|
+
|
|
10
|
+
- ultra-high fixed-length date-string throughput
|
|
11
|
+
- especially suitable for debug / trace logging
|
|
12
|
+
- zero allocation (buffer reuse)
|
|
13
|
+
- predictable fixed-width string format
|
|
14
|
+
- no locale ambiguity
|
|
15
|
+
- UTC only
|
|
16
|
+
|
|
17
|
+
90% code is generated BY free AI(CHAT-GPT)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Why?
|
|
24
|
+
|
|
25
|
+
Native:
|
|
26
|
+
|
|
27
|
+
new Date(iso).getTime()
|
|
28
|
+
|
|
29
|
+
is:
|
|
30
|
+
|
|
31
|
+
- slow in debug/test enviroment , massive random date-string inserted
|
|
32
|
+
- allocates
|
|
33
|
+
- involves internal parsing
|
|
34
|
+
- unpredictable for extended years
|
|
35
|
+
|
|
36
|
+
nv-date-y7 uses a pure civil calendar algorithm (no built-in date parsing involved)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
# Format Overview
|
|
42
|
+
|
|
43
|
+
## 1. Y7 format (20 chars)
|
|
44
|
+
|
|
45
|
+
Fixed width: 20 chars
|
|
46
|
+
|
|
47
|
+
<3 pad or sign><YYYY or ±YYYYYY><MM><DD><hh><mm><ss><sss>
|
|
48
|
+
|
|
49
|
+
Examples:
|
|
50
|
+
|
|
51
|
+
" 20240213123456789"
|
|
52
|
+
"+01234501010000000000"
|
|
53
|
+
"-00000101010000000000"
|
|
54
|
+
|
|
55
|
+
- 4-digit years use 3 leading spaces
|
|
56
|
+
- extended years use sign + 6 digits
|
|
57
|
+
- always UTC
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 2. ISO format (27 chars)
|
|
62
|
+
|
|
63
|
+
Fixed width: 27 chars
|
|
64
|
+
|
|
65
|
+
<pad or sign>YYYY-MM-DDThh:mm:ss.sssZ
|
|
66
|
+
|
|
67
|
+
Example:
|
|
68
|
+
|
|
69
|
+
" 2024-02-13T11:22:33.123Z"
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
# Installation
|
|
74
|
+
|
|
75
|
+
npm install nv-date-y7
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
# Usage
|
|
80
|
+
|
|
81
|
+
## Default export
|
|
82
|
+
|
|
83
|
+
const y7 = require("nv-date-y7");
|
|
84
|
+
|
|
85
|
+
y7(); // now → y7
|
|
86
|
+
y7(Date.now());
|
|
87
|
+
y7("2024-02-13");
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Y7 API
|
|
92
|
+
|
|
93
|
+
const {
|
|
94
|
+
mts_to_y7,
|
|
95
|
+
y7_to_mts,
|
|
96
|
+
dt_to_y7,
|
|
97
|
+
y7_to_dt
|
|
98
|
+
} = require("nv-date-y7");
|
|
99
|
+
|
|
100
|
+
### Convert timestamp → y7
|
|
101
|
+
|
|
102
|
+
mts_to_y7(Date.now());
|
|
103
|
+
|
|
104
|
+
### Convert y7 → timestamp
|
|
105
|
+
|
|
106
|
+
y7_to_mts(" 20240213123456789");
|
|
107
|
+
|
|
108
|
+
### Date helpers
|
|
109
|
+
|
|
110
|
+
dt_to_y7(new Date());
|
|
111
|
+
y7_to_dt(" 20240213123456789");
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## ISO API
|
|
116
|
+
|
|
117
|
+
const {
|
|
118
|
+
mts_to_iso,
|
|
119
|
+
iso_to_mts
|
|
120
|
+
} = require("nv-date-y7");
|
|
121
|
+
|
|
122
|
+
### timestamp → ISO
|
|
123
|
+
|
|
124
|
+
mts_to_iso(Date.now());
|
|
125
|
+
|
|
126
|
+
### ISO → timestamp
|
|
127
|
+
|
|
128
|
+
iso_to_mts(" 2024-02-13T11:22:33.123Z");
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
# Custom ISO-like format
|
|
133
|
+
|
|
134
|
+
You can customize separators:
|
|
135
|
+
|
|
136
|
+
const { to_str } = require("nv-date-y7");
|
|
137
|
+
|
|
138
|
+
to_str(Date.now(), {
|
|
139
|
+
"-": "/",
|
|
140
|
+
"T": " ",
|
|
141
|
+
":": ".",
|
|
142
|
+
".": ",",
|
|
143
|
+
"Z": ""
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
Example output:
|
|
147
|
+
|
|
148
|
+
" 2024/02/13 11.22.33,123"
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
# Performance
|
|
153
|
+
|
|
154
|
+
Example benchmark (Node 20):
|
|
155
|
+
|
|
156
|
+
builtin_new_date 34.583 sec 2891582 ops/sec
|
|
157
|
+
iso_to_mts 19.680 sec 5081377 ops/sec
|
|
158
|
+
|
|
159
|
+
Y7 decode:
|
|
160
|
+
|
|
161
|
+
y7_to_mts 1.316 sec 15192186 ops/sec
|
|
162
|
+
|
|
163
|
+
~2× faster than native ISO parsing
|
|
164
|
+
~5×–7× faster in optimized paths
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
# Range
|
|
169
|
+
|
|
170
|
+
Supports:
|
|
171
|
+
|
|
172
|
+
- ±100,000,000 days
|
|
173
|
+
- ±6 digit years
|
|
174
|
+
- full proleptic Gregorian calendar
|
|
175
|
+
- UTC only
|
|
176
|
+
|
|
177
|
+
Range check enforced:
|
|
178
|
+
|
|
179
|
+
MIN6 = -100000000 days
|
|
180
|
+
MAX6 = +100000000 days
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# When to use this?
|
|
186
|
+
|
|
187
|
+
- High-frequency logging
|
|
188
|
+
- LogStore needing sortable fixed strings
|
|
189
|
+
- Performance critical services
|
|
190
|
+
- Deterministic UTC-only systems
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
# When NOT to use this?
|
|
195
|
+
|
|
196
|
+
- Local time formatting
|
|
197
|
+
- Timezone conversions
|
|
198
|
+
- Human-friendly display formatting
|
|
199
|
+
|
|
200
|
+
This is a machine format, not UI format.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
# License
|
|
205
|
+
|
|
206
|
+
ANY
|
|
207
|
+
|
package/TEST/check.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
const DAY = 86400000;
|
|
2
|
+
const MIN6 = -DAY * 100000000;
|
|
3
|
+
const MAX6 = DAY * 100000000 + 1;
|
|
4
|
+
|
|
5
|
+
const MIN4 = -DAY * 719528;
|
|
6
|
+
const MAX4 = DAY * 2932897;
|
|
7
|
+
|
|
8
|
+
const pad = (n, w) => String(n).padStart(w, "0");
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
const civil_from_days = (z) => {
|
|
12
|
+
z += 719468;
|
|
13
|
+
|
|
14
|
+
const era = Math.floor(z / 146097);
|
|
15
|
+
const doe = z - era * 146097;
|
|
16
|
+
const yoe = Math.floor((doe - Math.floor(doe/1460) + Math.floor(doe/36524) - Math.floor(doe/146096)) / 365);
|
|
17
|
+
let y = yoe + era * 400;
|
|
18
|
+
|
|
19
|
+
const doy = doe - (365*yoe + Math.floor(yoe/4) - Math.floor(yoe/100));
|
|
20
|
+
const mp = Math.floor((5*doy + 2)/153);
|
|
21
|
+
|
|
22
|
+
const d = doy - Math.floor((153*mp+2)/5) + 1;
|
|
23
|
+
const m = mp + (mp < 10 ? 3 : -9);
|
|
24
|
+
|
|
25
|
+
y += (m <= 2);
|
|
26
|
+
|
|
27
|
+
return [y, m, d];
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const days_from_civil = (y, m, d) => {
|
|
31
|
+
y -= m <= 2 ? 1 : 0;
|
|
32
|
+
const era = Math.floor(y / 400);
|
|
33
|
+
const yoe = y - era * 400;
|
|
34
|
+
const doy = Math.floor((153*(m + (m>2?-3:9)) + 2)/5) + d - 1;
|
|
35
|
+
const doe = yoe*365 + Math.floor(yoe/4) - Math.floor(yoe/100) + doy;
|
|
36
|
+
return era*146097 + doe - 719468;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const mts_to_y7 = (mts = Date.now()) => {
|
|
40
|
+
if (mts < MIN6 || mts >= MAX6)
|
|
41
|
+
throw new RangeError("Out of supported range");
|
|
42
|
+
|
|
43
|
+
const days = Math.floor(mts / DAY);
|
|
44
|
+
let msDay = mts - days * DAY;
|
|
45
|
+
|
|
46
|
+
if (msDay < 0) {
|
|
47
|
+
msDay += DAY;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const [y, m, d] = civil_from_days(days);
|
|
51
|
+
|
|
52
|
+
let ypart;
|
|
53
|
+
if (mts >= MIN4 && mts < MAX4) {
|
|
54
|
+
ypart = " " + pad(y,4);
|
|
55
|
+
} else if (mts >= MAX4) {
|
|
56
|
+
ypart = "+" + pad(y,6);
|
|
57
|
+
} else {
|
|
58
|
+
ypart = "-" + pad(-y,6);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const hh = Math.floor(msDay / 3600000);
|
|
62
|
+
msDay -= hh*3600000;
|
|
63
|
+
|
|
64
|
+
const mm = Math.floor(msDay / 60000);
|
|
65
|
+
msDay -= mm*60000;
|
|
66
|
+
|
|
67
|
+
const ss = Math.floor(msDay / 1000);
|
|
68
|
+
const sss = msDay - ss*1000;
|
|
69
|
+
|
|
70
|
+
return ypart +
|
|
71
|
+
pad(m,2) +
|
|
72
|
+
pad(d,2) +
|
|
73
|
+
pad(hh,2) +
|
|
74
|
+
pad(mm,2) +
|
|
75
|
+
pad(ss,2) +
|
|
76
|
+
pad(sss,3);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
const mts_to_y7_b = (mts=Date.now()) => {
|
|
82
|
+
if (mts < MIN6 || mts >= MAX6)
|
|
83
|
+
throw new RangeError("Out of supported range");
|
|
84
|
+
|
|
85
|
+
const d = new Date(mts);
|
|
86
|
+
const y = d.getUTCFullYear();
|
|
87
|
+
|
|
88
|
+
let ypart;
|
|
89
|
+
|
|
90
|
+
if (mts >= MIN4 && mts < MAX4) {
|
|
91
|
+
// 四位年
|
|
92
|
+
ypart = " " + pad(y, 4);
|
|
93
|
+
} else if (mts >= MAX4) {
|
|
94
|
+
// 正六位
|
|
95
|
+
ypart = "+" + pad(y, 6);
|
|
96
|
+
} else {
|
|
97
|
+
// 负六位
|
|
98
|
+
ypart = "-" + pad(-y, 6);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const MM = pad(d.getUTCMonth() + 1, 2);
|
|
102
|
+
const DD = pad(d.getUTCDate(), 2);
|
|
103
|
+
const hh = pad(d.getUTCHours(), 2);
|
|
104
|
+
const mm = pad(d.getUTCMinutes(), 2);
|
|
105
|
+
const ss = pad(d.getUTCSeconds(), 2);
|
|
106
|
+
const sss = pad(d.getUTCMilliseconds(), 3);
|
|
107
|
+
|
|
108
|
+
return ypart + MM + DD + hh + mm + ss + sss;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const points = [
|
|
112
|
+
MIN6,
|
|
113
|
+
MIN6 + 1,
|
|
114
|
+
MIN4 - 1,
|
|
115
|
+
MIN4,
|
|
116
|
+
MIN4 + 1,
|
|
117
|
+
-1,
|
|
118
|
+
0,
|
|
119
|
+
1,
|
|
120
|
+
MAX4 - 1,
|
|
121
|
+
MAX4,
|
|
122
|
+
MAX4 + 1,
|
|
123
|
+
MAX6 - 1
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
for (const mts of points) {
|
|
127
|
+
if (mts_to_y7(mts) !== mts_to_y7_b(mts)) {
|
|
128
|
+
console.log("Mismatch:", mts,
|
|
129
|
+
new Date(mts),
|
|
130
|
+
mts_to_y7(mts),
|
|
131
|
+
mts_to_y7_b(mts));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
console.log(points,"PASS")
|
|
135
|
+
|
|
136
|
+
for (let i = 0; i < 1_000_000; i++) {
|
|
137
|
+
const mts = Math.floor(
|
|
138
|
+
MIN6 + Math.random() * (MAX6 - MIN6)
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
if (mts_to_y7(mts) !== mts_to_y7_b(mts)) {
|
|
142
|
+
console.log("Mismatch:", mts,
|
|
143
|
+
new Date(mts),
|
|
144
|
+
mts_to_y7(mts),
|
|
145
|
+
mts_to_y7_b(mts));
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
console.log("random","PASS")
|
|
151
|
+
|
|
152
|
+
const CYCLE = 146097 * DAY; // 400年
|
|
153
|
+
|
|
154
|
+
for (let mts = -CYCLE; mts < CYCLE; mts += 1234567) {
|
|
155
|
+
if (mts_to_y7(mts) !== mts_to_y7_b(mts)) {
|
|
156
|
+
console.log("Mismatch:", mts,
|
|
157
|
+
new Date(mts),
|
|
158
|
+
mts_to_y7(mts),
|
|
159
|
+
mts_to_y7_b(mts));
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log("circle","PASS")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
let ok = true;
|
|
168
|
+
|
|
169
|
+
for (let i = 0; i < 5_000_000; i++) {
|
|
170
|
+
const mts = (Math.random() * 2**52 | 0) - 2**51;
|
|
171
|
+
|
|
172
|
+
const a = mts_to_y7(mts);
|
|
173
|
+
const b = mts_to_y7_b(mts);
|
|
174
|
+
|
|
175
|
+
if (a !== b) {
|
|
176
|
+
console.log("Mismatch:", mts, new Date(mts), a, b);
|
|
177
|
+
ok = false;
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (ok) console.log("All good.");
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const { iso_to_mts, mts_to_iso } = require("../index");
|
|
2
|
+
|
|
3
|
+
const builtin_new_date = (iso) => (new Date(iso)).getTime();
|
|
4
|
+
|
|
5
|
+
const SAMPLE_SIZE = 1_000_000;
|
|
6
|
+
const SAMPLES = new Array(SAMPLE_SIZE);
|
|
7
|
+
|
|
8
|
+
// ===== 生成样本 =====
|
|
9
|
+
// 使用原生 Date 生成标准 ISO
|
|
10
|
+
let mts = -86400000 * 200000;
|
|
11
|
+
for (let i = 0; i < SAMPLE_SIZE; i++) {
|
|
12
|
+
const d = new Date(mts);
|
|
13
|
+
SAMPLES[i] = d.toISOString();
|
|
14
|
+
mts += 1234567;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// ===== 校验 =====
|
|
18
|
+
const check = () => {
|
|
19
|
+
for (let i = 0; i < 10000; i++) {
|
|
20
|
+
const iso = SAMPLES[i];
|
|
21
|
+
|
|
22
|
+
const a = builtin_new_date(iso);
|
|
23
|
+
const b = iso_to_mts(" " + iso.slice(0)); // 如果你格式带 3 空格
|
|
24
|
+
|
|
25
|
+
if (a !== b) {
|
|
26
|
+
console.error("Mismatch:");
|
|
27
|
+
console.log(iso, a, b);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const myIso = mts_to_iso(a).trim();
|
|
32
|
+
if (myIso !== iso) {
|
|
33
|
+
console.error("Encode mismatch:");
|
|
34
|
+
console.log(myIso, iso);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
console.log("check ok");
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
var recv = 0;
|
|
43
|
+
|
|
44
|
+
// ===== benchmark =====
|
|
45
|
+
const perf = (ROUNDS) => {
|
|
46
|
+
|
|
47
|
+
check();
|
|
48
|
+
|
|
49
|
+
const bench = (name, fn) => {
|
|
50
|
+
|
|
51
|
+
// 预热
|
|
52
|
+
for (let i = 0; i < SAMPLE_SIZE; i++) {
|
|
53
|
+
recv ^= fn(SAMPLES[i]);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const t0 = process.hrtime.bigint();
|
|
57
|
+
|
|
58
|
+
for (let r = 0; r < ROUNDS; r++) {
|
|
59
|
+
for (let i = 0; i < SAMPLE_SIZE; i++) {
|
|
60
|
+
recv ^= fn(SAMPLES[i]);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const t1 = process.hrtime.bigint();
|
|
65
|
+
|
|
66
|
+
const sec = Number(t1 - t0) / 1e9;
|
|
67
|
+
const calls = ROUNDS * SAMPLE_SIZE;
|
|
68
|
+
const ops = calls / sec;
|
|
69
|
+
|
|
70
|
+
console.log(
|
|
71
|
+
name.padEnd(20),
|
|
72
|
+
sec.toFixed(3), "sec",
|
|
73
|
+
ops.toFixed(0), "ops/sec"
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
bench("builtin_new_date", builtin_new_date);
|
|
78
|
+
bench("iso_to_mts", iso_to_mts);
|
|
79
|
+
|
|
80
|
+
console.log("recv:", recv);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
perf(Number(process.argv[2] ?? 100));
|
|
84
|
+
|