mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 21:27:20 +00:00
feat(server): time duration helper (#12562)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced support for parsing and converting duration strings (e.g., "1h30m") into milliseconds and seconds. - Added utility methods to handle a wide range of time units and their combinations. - Added functions to calculate dates offset before or after a given date by specified durations. - **Tests** - Implemented comprehensive automated tests to ensure accurate parsing and conversion of duration strings. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
# Snapshot report for `src/base/utils/__tests__/duration.spec.ts`
|
||||
|
||||
The actual snapshot is saved in `duration.spec.ts.snap`.
|
||||
|
||||
Generated by [AVA](https://avajs.dev).
|
||||
|
||||
## should parse duration strings correctly
|
||||
|
||||
> parser - 1ms
|
||||
|
||||
'{"ms":1}'
|
||||
|
||||
> ms - 1ms
|
||||
|
||||
1
|
||||
|
||||
> parser - 1s
|
||||
|
||||
'{"s":1}'
|
||||
|
||||
> ms - 1s
|
||||
|
||||
1000
|
||||
|
||||
> parser - 1m
|
||||
|
||||
'{"m":1}'
|
||||
|
||||
> ms - 1m
|
||||
|
||||
60000
|
||||
|
||||
> parser - 1h
|
||||
|
||||
'{"h":1}'
|
||||
|
||||
> ms - 1h
|
||||
|
||||
3600000
|
||||
|
||||
> parser - 1d
|
||||
|
||||
'{"d":1}'
|
||||
|
||||
> ms - 1d
|
||||
|
||||
86400000
|
||||
|
||||
> parser - 1w
|
||||
|
||||
'{"w":1}'
|
||||
|
||||
> ms - 1w
|
||||
|
||||
604800000
|
||||
|
||||
> parser - 1M
|
||||
|
||||
'{"M":1}'
|
||||
|
||||
> ms - 1M
|
||||
|
||||
2592000000
|
||||
|
||||
> parser - 1y
|
||||
|
||||
'{"y":1}'
|
||||
|
||||
> ms - 1y
|
||||
|
||||
31536000000
|
||||
|
||||
> parser - 1000ms
|
||||
|
||||
'{"ms":1000}'
|
||||
|
||||
> ms - 1000ms
|
||||
|
||||
1000
|
||||
|
||||
> parser - 60s
|
||||
|
||||
'{"s":60}'
|
||||
|
||||
> ms - 60s
|
||||
|
||||
60000
|
||||
|
||||
> parser - 30m
|
||||
|
||||
'{"m":30}'
|
||||
|
||||
> ms - 30m
|
||||
|
||||
1800000
|
||||
|
||||
> parser - 1h30m
|
||||
|
||||
'{"h":1,"m":30}'
|
||||
|
||||
> ms - 1h30m
|
||||
|
||||
5400000
|
||||
|
||||
> parser - 15d
|
||||
|
||||
'{"d":15}'
|
||||
|
||||
> ms - 15d
|
||||
|
||||
1296000000
|
||||
|
||||
> parser - 1y
|
||||
|
||||
'{"y":1}'
|
||||
|
||||
> ms - 1y
|
||||
|
||||
31536000000
|
||||
|
||||
> parser - 12M
|
||||
|
||||
'{"M":12}'
|
||||
|
||||
> ms - 12M
|
||||
|
||||
31104000000
|
||||
|
||||
> parser - 1y1M1d1h1m1s1ms
|
||||
|
||||
'{"y":1,"M":1,"d":1,"h":1,"m":1,"s":1,"ms":1}'
|
||||
|
||||
> ms - 1y1M1d1h1m1s1ms
|
||||
|
||||
34218061001
|
||||
Binary file not shown.
@@ -0,0 +1,37 @@
|
||||
import test from 'ava';
|
||||
|
||||
import { Due } from '../duration';
|
||||
|
||||
test('should parse duration strings correctly', t => {
|
||||
const testcases = [
|
||||
'1ms',
|
||||
'1s',
|
||||
'1m',
|
||||
'1h',
|
||||
'1d',
|
||||
'1w',
|
||||
'1M',
|
||||
'1y',
|
||||
'1000ms',
|
||||
'60s',
|
||||
'30m',
|
||||
'1h30m',
|
||||
'15d',
|
||||
'1y',
|
||||
'12M',
|
||||
'1y1M1d1h1m1s1ms',
|
||||
];
|
||||
|
||||
for (const str of testcases) {
|
||||
t.snapshot(JSON.stringify(Due.parse(str)), `parser - ${str}`);
|
||||
t.snapshot(Due.ms(str), `ms - ${str}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('should calc relative time correctly', t => {
|
||||
const date = new Date();
|
||||
t.is(Due.before('1d', date).getTime(), date.getTime() - 1000 * 60 * 60 * 24);
|
||||
|
||||
const date2 = new Date();
|
||||
t.is(Due.after('1d', date2).getTime(), date2.getTime() + 1000 * 60 * 60 * 24);
|
||||
});
|
||||
78
packages/backend/server/src/base/utils/duration.ts
Normal file
78
packages/backend/server/src/base/utils/duration.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
type DurationUnit = 'd' | 'w' | 'M' | 'y' | 'h' | 'm' | 's' | 'ms';
|
||||
type DurationInput = Partial<Record<DurationUnit, number>>;
|
||||
|
||||
const UnitToSecMap: Record<DurationUnit, number> = {
|
||||
ms: 0.001,
|
||||
s: 1,
|
||||
m: 60,
|
||||
h: 3600,
|
||||
d: 24 * 3600,
|
||||
w: 7 * 24 * 3600,
|
||||
M: 30 * 24 * 3600,
|
||||
y: 365 * 24 * 3600,
|
||||
};
|
||||
|
||||
const KnownCharCodeToCharMap: Record<number, DurationUnit> = {
|
||||
100: 'd',
|
||||
119: 'w',
|
||||
77: 'M',
|
||||
121: 'y',
|
||||
104: 'h',
|
||||
109: 'm',
|
||||
115: 's',
|
||||
};
|
||||
|
||||
function parse(str: string): DurationInput {
|
||||
let input: DurationInput = {};
|
||||
|
||||
let acc = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const ch = str[i];
|
||||
const code = ch.charCodeAt(0);
|
||||
|
||||
// number [0..9]
|
||||
if (code >= 48 && code <= 57) {
|
||||
acc = acc * 10 + code - 48;
|
||||
} else {
|
||||
let unit = KnownCharCodeToCharMap[code];
|
||||
if (!unit) {
|
||||
throw new Error(`Invalid duration string unit ${ch}`);
|
||||
}
|
||||
|
||||
// look ahead a char for 'ms' checking if unit met 'm'
|
||||
if (unit === 'm' && str[i + 1] === 's') {
|
||||
unit = 'ms';
|
||||
i++;
|
||||
}
|
||||
|
||||
input[unit] = acc;
|
||||
acc = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
export const Due = {
|
||||
ms: (dueStr: string | DurationInput) => {
|
||||
const input = typeof dueStr === 'string' ? parse(dueStr) : dueStr;
|
||||
return Object.entries(input).reduce((duration, [unit, val]) => {
|
||||
return duration + UnitToSecMap[unit as DurationUnit] * (val || 0) * 1000;
|
||||
}, 0);
|
||||
},
|
||||
s: (dueStr: string | DurationInput) => {
|
||||
const input = typeof dueStr === 'string' ? parse(dueStr) : dueStr;
|
||||
return Object.entries(input).reduce((duration, [unit, val]) => {
|
||||
return duration + UnitToSecMap[unit as DurationUnit] * (val || 0);
|
||||
}, 0);
|
||||
},
|
||||
parse,
|
||||
after: (dueStr: string | number | DurationInput, date?: Date) => {
|
||||
const timestamp = typeof dueStr === 'number' ? dueStr : Due.ms(dueStr);
|
||||
return new Date((date?.getTime() ?? Date.now()) + timestamp);
|
||||
},
|
||||
before: (dueStr: string | number | DurationInput, date?: Date) => {
|
||||
const timestamp = typeof dueStr === 'number' ? dueStr : Due.ms(dueStr);
|
||||
return new Date((date?.getTime() ?? Date.now()) - timestamp);
|
||||
},
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './duration';
|
||||
export * from './promise';
|
||||
export * from './request';
|
||||
export * from './stream';
|
||||
|
||||
Reference in New Issue
Block a user