+5
.changeset/beige-pens-like.md
+5
.changeset/beige-pens-like.md
+7
packages/misc/time-ms/CHANGELOG.md
+7
packages/misc/time-ms/CHANGELOG.md
+17
packages/misc/time-ms/README.md
+17
packages/misc/time-ms/README.md
···
1
+
# @atcute/time-ms
2
+
3
+
high precision system time
4
+
5
+
```sh
6
+
npm install @atcute/time-ms
7
+
```
8
+
9
+
## usage
10
+
11
+
```ts
12
+
import { now } from '@atcute/time-ms';
13
+
14
+
const timestamp = now();
15
+
// ^ 1766739339478426
16
+
// returns microseconds since unix epoch
17
+
```
+18
packages/misc/time-ms/binding.gyp
+18
packages/misc/time-ms/binding.gyp
···
1
+
{
2
+
"targets": [
3
+
{
4
+
"target_name": "time_ms",
5
+
"sources": ["src/time_ms.c"],
6
+
"cflags": ["-Wall", "-Wextra", "-O3"],
7
+
"xcode_settings": {
8
+
"OTHER_CFLAGS": ["-Wall", "-Wextra", "-O3"]
9
+
},
10
+
"msvs_settings": {
11
+
"VCCLCompilerTool": {
12
+
"Optimization": 2,
13
+
"WarnAsError": "false"
14
+
}
15
+
}
16
+
}
17
+
]
18
+
}
+31
packages/misc/time-ms/lib/index.node.ts
+31
packages/misc/time-ms/lib/index.node.ts
···
1
+
import { join } from 'node:path';
2
+
3
+
type TimeBinding = {
4
+
now: () => number;
5
+
};
6
+
7
+
let binding: TimeBinding | null = null;
8
+
9
+
try {
10
+
// node-gyp-build handles platform/arch detection, libc variants, etc.
11
+
binding = require('node-gyp-build')(join(import.meta.dirname, '..')) as TimeBinding;
12
+
} catch {
13
+
binding = null;
14
+
}
15
+
16
+
/**
17
+
* whether the native module is available for the current runtime.
18
+
*/
19
+
export const hasNative = binding !== null;
20
+
21
+
/**
22
+
* returns the current time in microseconds since unix epoch.
23
+
* @returns timestamp in microseconds
24
+
*/
25
+
export const now = (): number => {
26
+
if (binding === null) {
27
+
return Date.now() * 1_000;
28
+
}
29
+
30
+
return binding.now();
31
+
};
+12
packages/misc/time-ms/lib/index.ts
+12
packages/misc/time-ms/lib/index.ts
···
1
+
/**
2
+
* whether the native module is available for the current runtime.
3
+
*/
4
+
export const hasNative = false;
5
+
6
+
/**
7
+
* returns the current time in microseconds since unix epoch.
8
+
* @returns timestamp in microseconds
9
+
*/
10
+
export const now = (): number => {
11
+
return Date.now() * 1_000;
12
+
};
+45
packages/misc/time-ms/package.json
+45
packages/misc/time-ms/package.json
···
1
+
{
2
+
"type": "module",
3
+
"name": "@atcute/time-ms",
4
+
"version": "1.0.0",
5
+
"description": "high precision system time helper",
6
+
"license": "0BSD",
7
+
"repository": {
8
+
"url": "https://github.com/mary-ext/atcute",
9
+
"directory": "packages/utilities/time-ms"
10
+
},
11
+
"publishConfig": {
12
+
"access": "public"
13
+
},
14
+
"files": [
15
+
"dist/",
16
+
"lib/",
17
+
"prebuilds/",
18
+
"src/",
19
+
"binding.gyp",
20
+
"!lib/**/*.bench.ts",
21
+
"!lib/**/*.test.ts"
22
+
],
23
+
"exports": {
24
+
".": {
25
+
"node": "./dist/index.node.js",
26
+
"default": "./dist/index.js"
27
+
}
28
+
},
29
+
"sideEffects": false,
30
+
"scripts": {
31
+
"install": "node-gyp-build",
32
+
"build:native": "prebuildify --napi --strip",
33
+
"build": "tsgo --project tsconfig.build.json",
34
+
"test": "vitest",
35
+
"prepublish": "rm -rf dist; pnpm run build:native; pnpm run build"
36
+
},
37
+
"devDependencies": {
38
+
"prebuildify": "^6.0.1",
39
+
"vitest": "^4.0.16"
40
+
},
41
+
"dependencies": {
42
+
"@types/node": "^22.19.3",
43
+
"node-gyp-build": "^4.8.4"
44
+
}
45
+
}
+38
packages/misc/time-ms/src/time_ms.c
+38
packages/misc/time-ms/src/time_ms.c
···
1
+
#include <node_api.h>
2
+
#include <stdint.h>
3
+
4
+
#ifdef _WIN32
5
+
#include <windows.h>
6
+
#else
7
+
#include <time.h>
8
+
#endif
9
+
10
+
static napi_value now(napi_env env, napi_callback_info info) {
11
+
int64_t microseconds;
12
+
13
+
#ifdef _WIN32
14
+
FILETIME ft;
15
+
ULARGE_INTEGER ul;
16
+
GetSystemTimePreciseAsFileTime(&ft);
17
+
ul.LowPart = ft.dwLowDateTime;
18
+
ul.HighPart = ft.dwHighDateTime;
19
+
// convert from 100-nanosecond intervals since 1601 to microseconds since 1970
20
+
microseconds = (int64_t)((ul.QuadPart - 116444736000000000ULL) / 10);
21
+
#else
22
+
struct timespec ts;
23
+
clock_gettime(CLOCK_REALTIME, &ts);
24
+
microseconds = (int64_t)ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
25
+
#endif
26
+
27
+
napi_value result;
28
+
napi_create_int64(env, microseconds, &result);
29
+
return result;
30
+
}
31
+
32
+
static napi_value init(napi_env env, napi_value exports) {
33
+
napi_property_descriptor desc = { "now", NULL, now, NULL, NULL, NULL, napi_default, NULL };
34
+
napi_define_properties(env, exports, 1, &desc);
35
+
return exports;
36
+
}
37
+
38
+
NAPI_MODULE(NODE_GYP_MODULE_NAME, init)
+4
packages/misc/time-ms/tsconfig.build.json
+4
packages/misc/time-ms/tsconfig.build.json
+24
packages/misc/time-ms/tsconfig.json
+24
packages/misc/time-ms/tsconfig.json
···
1
+
{
2
+
"compilerOptions": {
3
+
"types": ["node"],
4
+
"outDir": "dist/",
5
+
"esModuleInterop": true,
6
+
"skipLibCheck": true,
7
+
"target": "ESNext",
8
+
"allowJs": true,
9
+
"resolveJsonModule": true,
10
+
"moduleDetection": "force",
11
+
"isolatedModules": true,
12
+
"verbatimModuleSyntax": true,
13
+
"strict": true,
14
+
"noImplicitOverride": true,
15
+
"noUnusedLocals": true,
16
+
"noUnusedParameters": true,
17
+
"noFallthroughCasesInSwitch": true,
18
+
"module": "NodeNext",
19
+
"sourceMap": true,
20
+
"declaration": true,
21
+
"declarationMap": true,
22
+
},
23
+
"include": ["lib"],
24
+
}
+14
-9
packages/utilities/tid/lib/index.ts
+14
-9
packages/utilities/tid/lib/index.ts
···
1
+
import { now as getNow } from '@atcute/time-ms';
1
2
import { s32decode, s32encode } from './s32.js';
2
3
3
-
let lastTimestamp: number = 0;
4
+
let lastTimestamp = 0;
5
+
let lastCurrentTime = 0;
4
6
5
7
const TID_RE = /^[234567abcdefghij][234567abcdefghijklmnopqrstuvwxyz]{12}$/;
6
8
···
30
32
* Return a TID based on current time
31
33
*/
32
34
export const now = (): string => {
33
-
// we need these two aspects, which Date.now() doesn't provide:
34
-
// - monotonically increasing time
35
-
// - microsecond precision
35
+
const currentTime = getNow();
36
+
let timestamp: number;
36
37
37
-
// while `performance.timeOrigin + performance.now()` could be used here, they
38
-
// seem to have cross-browser differences, not sure on that yet.
38
+
if (currentTime === lastCurrentTime) {
39
+
// same time; increment to avoid collision
40
+
timestamp = lastTimestamp + 1;
41
+
} else {
42
+
// time changed
43
+
timestamp = currentTime;
44
+
lastCurrentTime = currentTime;
45
+
}
39
46
40
-
let timestamp = Math.max(Date.now() * 1_000, lastTimestamp);
41
-
lastTimestamp = timestamp + 1;
42
-
47
+
lastTimestamp = timestamp;
43
48
return createRaw(timestamp, Math.floor(Math.random() * 1023));
44
49
};
45
50
+3
packages/utilities/tid/package.json
+3
packages/utilities/tid/package.json
+56
-4
pnpm-lock.yaml
+56
-4
pnpm-lock.yaml
···
454
454
'@atcute/uint8array':
455
455
specifier: workspace:^
456
456
version: link:../../misc/uint8array
457
+
'@atcute/util-fetch':
458
+
specifier: workspace:^
459
+
version: link:../../misc/util-fetch
457
460
'@badrap/valita':
458
461
specifier: ^0.4.6
459
462
version: 0.4.6
···
697
700
vitest:
698
701
specifier: ^4.0.16
699
702
version: 4.0.16(@types/node@25.0.3)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(tsx@4.20.6)(yaml@2.8.0)
703
+
704
+
packages/misc/time-ms:
705
+
dependencies:
706
+
'@types/node':
707
+
specifier: ^22.19.3
708
+
version: 22.19.3
709
+
node-gyp-build:
710
+
specifier: ^4.8.4
711
+
version: 4.8.4
712
+
devDependencies:
713
+
prebuildify:
714
+
specifier: ^6.0.1
715
+
version: 6.0.1
716
+
vitest:
717
+
specifier: ^4.0.16
718
+
version: 4.0.16(@types/node@22.19.3)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(tsx@4.20.6)(yaml@2.8.0)
700
719
701
720
packages/misc/uint8array:
702
721
devDependencies:
···
1100
1119
version: 4.0.16(@types/node@25.0.3)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(tsx@4.20.6)(yaml@2.8.0)
1101
1120
1102
1121
packages/utilities/tid:
1122
+
dependencies:
1123
+
'@atcute/time-ms':
1124
+
specifier: workspace:^
1125
+
version: link:../../misc/time-ms
1103
1126
devDependencies:
1104
1127
vitest:
1105
1128
specifier: ^4.0.16
···
3593
3616
resolution: {integrity: sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==}
3594
3617
hasBin: true
3595
3618
3619
+
node-gyp-build@4.8.4:
3620
+
resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
3621
+
hasBin: true
3622
+
3596
3623
nodemailer-html-to-text@3.2.0:
3597
3624
resolution: {integrity: sha512-RJUC6640QV1PzTHHapOrc6IzrAJUZtk2BdVdINZ9VTLm+mcQNyBO9LYyhrnufkzqiD9l8hPLJ97rSyK4WanPNg==}
3598
3625
engines: {node: '>= 10.23.0'}
···
3601
3628
resolution: {integrity: sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==}
3602
3629
engines: {node: '>=6.0.0'}
3603
3630
3631
+
npm-run-path@3.1.0:
3632
+
resolution: {integrity: sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==}
3633
+
engines: {node: '>=8'}
3634
+
3604
3635
object-assign@4.1.1:
3605
3636
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
3606
3637
engines: {node: '>=0.10.0'}
···
3825
3856
prebuild-install@7.1.3:
3826
3857
resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
3827
3858
engines: {node: '>=10'}
3859
+
hasBin: true
3860
+
3861
+
prebuildify@6.0.1:
3862
+
resolution: {integrity: sha512-8Y2oOOateom/s8dNBsGIcnm6AxPmLH4/nanQzL5lQMU+sC0CMhzARZHizwr36pUPLdvBnOkCNQzxg4djuFSgIw==}
3828
3863
hasBin: true
3829
3864
3830
3865
prettier@2.8.8:
···
6435
6470
6436
6471
'@types/bn.js@5.2.0':
6437
6472
dependencies:
6438
-
'@types/node': 25.0.3
6473
+
'@types/node': 22.19.3
6439
6474
6440
6475
'@types/bun@1.3.5':
6441
6476
dependencies:
···
6467
6502
'@types/node@25.0.3':
6468
6503
dependencies:
6469
6504
undici-types: 7.16.0
6505
+
optional: true
6470
6506
6471
6507
'@types/ws@8.18.1':
6472
6508
dependencies:
6473
-
'@types/node': 25.0.3
6509
+
'@types/node': 22.19.3
6474
6510
6475
6511
'@typescript/native-preview-darwin-arm64@7.0.0-dev.20251221.1':
6476
6512
optional: true
···
6787
6823
6788
6824
bun-types@1.3.5:
6789
6825
dependencies:
6790
-
'@types/node': 25.0.3
6826
+
'@types/node': 22.19.3
6791
6827
6792
6828
bytes@3.1.2: {}
6793
6829
···
7562
7598
detect-libc: 2.1.2
7563
7599
optional: true
7564
7600
7601
+
node-gyp-build@4.8.4: {}
7602
+
7565
7603
nodemailer-html-to-text@3.2.0:
7566
7604
dependencies:
7567
7605
html-to-text: 7.1.1
7568
7606
7569
7607
nodemailer@6.10.1: {}
7608
+
7609
+
npm-run-path@3.1.0:
7610
+
dependencies:
7611
+
path-key: 3.1.1
7570
7612
7571
7613
object-assign@4.1.1: {}
7572
7614
···
7797
7839
tar-fs: 2.1.4
7798
7840
tunnel-agent: 0.6.0
7799
7841
7842
+
prebuildify@6.0.1:
7843
+
dependencies:
7844
+
minimist: 1.2.8
7845
+
mkdirp-classic: 0.5.3
7846
+
node-abi: 3.85.0
7847
+
npm-run-path: 3.1.0
7848
+
pump: 3.0.3
7849
+
tar-fs: 2.1.4
7850
+
7800
7851
prettier@2.8.8: {}
7801
7852
7802
7853
prettier@3.7.4: {}
···
8213
8264
8214
8265
undici-types@6.21.0: {}
8215
8266
8216
-
undici-types@7.16.0: {}
8267
+
undici-types@7.16.0:
8268
+
optional: true
8217
8269
8218
8270
undici@6.22.0: {}
8219
8271