Skip to content

Commit a9be74b

Browse files
committed
refactor: introduce timestamp.ts
1 parent 7ac4e33 commit a9be74b

File tree

6 files changed

+124
-128
lines changed

6 files changed

+124
-128
lines changed

src/ExtData.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
/**
2+
* ExtData is used to handle Extension Types that are not registered to ExtensionCodec.
3+
*/
14
export class ExtData {
25
constructor(readonly type: number, readonly data: Uint8Array) {}
36
}

src/ExtensionCodec.ts

Lines changed: 5 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,109 +1,9 @@
1-
import { getInt64, setInt64 } from "./utils/int";
2-
import { ExtData } from "./ExtData";
3-
4-
export const EXT_TIMESTAMP = -1;
5-
6-
function isDate(object: unknown): object is Date {
7-
return object instanceof Date;
8-
}
9-
10-
export type TimeSpec = {
11-
sec: number;
12-
nsec: number;
13-
};
14-
15-
const TIMESTAMP32_MAX_SEC = 0x100000000; // 32-bit unsigned int
16-
const TIMESTAMP64_MAX_SEC = 0x400000000; // 34-bit unsigned int
17-
18-
export function encodeTimestampFromTimeSpec({ sec, nsec }: TimeSpec): Uint8Array {
19-
if (sec >= 0 && nsec >= 0 && sec < TIMESTAMP64_MAX_SEC) {
20-
// Here sec >= 0 && nsec >= 0
21-
if (nsec === 0 && sec < TIMESTAMP32_MAX_SEC) {
22-
// timestamp 32 = { sec32 (unsigned) }
23-
const rv = new Uint8Array(4);
24-
const view = new DataView(rv.buffer);
25-
view.setUint32(0, sec);
26-
return rv;
27-
} else {
28-
// timestamp 64 = { nsec30 (unsigned), sec34 (unsigned) }
29-
const secHigh = sec / 0x100000000;
30-
const secLow = sec & 0xffffffff;
31-
const rv = new Uint8Array(8);
32-
const view = new DataView(rv.buffer);
33-
// nsec30 | secHigh2
34-
view.setUint32(0, (nsec << 2) | (secHigh & 0x3));
35-
// secLow32
36-
view.setUint32(4, secLow);
37-
return rv;
38-
}
39-
} else {
40-
// timestamp 96 = { nsec32 (unsigned), sec64 (signed) }
41-
const rv = new Uint8Array(12);
42-
const view = new DataView(rv.buffer);
43-
view.setUint32(0, nsec);
44-
setInt64(view, 4, sec);
45-
return rv;
46-
}
47-
}
48-
49-
export function encodeDateToTimeSpec(date: Date): TimeSpec {
50-
const msec = date.getTime();
51-
const sec = Math.floor(msec / 1000);
52-
const nsec = (msec - sec * 1000) * 1e6;
53-
54-
// Normalizes { sec, nsec } to ensure nsec is unsigned.
55-
const nsecInSec = Math.floor(nsec / 1e9);
56-
return {
57-
sec: sec + nsecInSec,
58-
nsec: nsec - nsecInSec * 1e9,
59-
};
60-
}
61-
62-
export const encodeTimestampExtension: ExtensionEncoderType = (object: unknown) => {
63-
if (isDate(object)) {
64-
const timeSpec = encodeDateToTimeSpec(object);
65-
return encodeTimestampFromTimeSpec(timeSpec);
66-
} else {
67-
return null;
68-
}
69-
};
70-
71-
// https://github.com/msgpack/msgpack/blob/master/spec.md#timestamp-extension-type
72-
export const decodeTimestampExtension: ExtensionDecoderType = (data: Uint8Array) => {
73-
// data may be 32, 64, or 96 bits
74-
switch (data.byteLength) {
75-
case 4: {
76-
// timestamp 32 = { sec32 }
77-
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
78-
const sec = view.getUint32(0);
79-
return new Date(sec * 1000);
80-
}
81-
case 8: {
82-
// timestamp 64 = { nsec30, sec34 }
83-
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
1+
// ExtensionCodec to handle MessagePack extensions
842

85-
const nsec30AndSecHigh2 = view.getUint32(0);
86-
const secLow32 = view.getUint32(4);
87-
const nsec = nsec30AndSecHigh2 >>> 2;
88-
const sec = (nsec30AndSecHigh2 & 0x3) * 0x100000000 + secLow32;
89-
return new Date(sec * 1000 + nsec / 1e6);
90-
}
91-
case 12: {
92-
// timestamp 96 = { nsec32 (unsigned), sec64 (signed) }
93-
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
94-
95-
const nsec = view.getUint32(0);
96-
const sec = getInt64(view, 4);
97-
98-
return new Date(sec * 1000 + nsec / 1e6);
99-
}
100-
default:
101-
throw new Error(`Unrecognized data size for timestamp: ${data.length}`);
102-
}
103-
};
3+
import { ExtData } from "./ExtData";
4+
import { timestampExtension } from "./timestamp";
1045

105-
// extensionType is signed 8-bit integer
106-
export type ExtensionDecoderType = (data: Uint8Array, extensionType: number) => any;
6+
export type ExtensionDecoderType = (data: Uint8Array, extensionType: number) => unknown;
1077

1088
export type ExtensionEncoderType = (input: unknown) => Uint8Array | null;
1099

@@ -125,11 +25,7 @@ export class ExtensionCodec implements ExtensionCodecType {
12525
private readonly decoders: Array<ExtensionDecoderType> = [];
12626

12727
public constructor() {
128-
this.register({
129-
type: EXT_TIMESTAMP,
130-
encode: encodeTimestampExtension,
131-
decode: decodeTimestampExtension,
132-
});
28+
this.register(timestampExtension);
13329
}
13430

13531
public register({

src/index.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1+
// Main Functions:
2+
13
export { encode } from "./encode";
24
export { decode } from "./decode";
35
export { decodeAsync } from "./decodeAsync";
46

5-
// Utilitiies for extensions
7+
// Utilitiies for Extension Types:
8+
9+
export { ExtensionCodec, ExtensionCodecType, ExtensionDecoderType, ExtensionEncoderType } from "./ExtensionCodec";
10+
export { ExtData } from "./ExtData";
611
export {
7-
ExtensionCodec,
8-
ExtensionCodecType,
9-
ExtensionDecoderType,
10-
ExtensionEncoderType,
1112
EXT_TIMESTAMP,
13+
encodeDateToTimeSpec,
14+
encodeTimeSpecToTimestamp,
1215
encodeTimestampExtension,
13-
encodeTimestampFromTimeSpec,
1416
decodeTimestampExtension,
15-
} from "./ExtensionCodec";
16-
17-
export { ExtData } from "./ExtData";
17+
} from "./timestamp";

src/timestamp.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { getInt64, setInt64 } from "./utils/int";
2+
3+
export const EXT_TIMESTAMP = -1;
4+
5+
export type TimeSpec = {
6+
sec: number;
7+
nsec: number;
8+
};
9+
10+
const TIMESTAMP32_MAX_SEC = 0x100000000; // 32-bit unsigned int
11+
const TIMESTAMP64_MAX_SEC = 0x400000000; // 34-bit unsigned int
12+
13+
export function encodeTimeSpecToTimestamp({ sec, nsec }: TimeSpec): Uint8Array {
14+
if (sec >= 0 && nsec >= 0 && sec < TIMESTAMP64_MAX_SEC) {
15+
// Here sec >= 0 && nsec >= 0
16+
if (nsec === 0 && sec < TIMESTAMP32_MAX_SEC) {
17+
// timestamp 32 = { sec32 (unsigned) }
18+
const rv = new Uint8Array(4);
19+
const view = new DataView(rv.buffer);
20+
view.setUint32(0, sec);
21+
return rv;
22+
} else {
23+
// timestamp 64 = { nsec30 (unsigned), sec34 (unsigned) }
24+
const secHigh = sec / 0x100000000;
25+
const secLow = sec & 0xffffffff;
26+
const rv = new Uint8Array(8);
27+
const view = new DataView(rv.buffer);
28+
// nsec30 | secHigh2
29+
view.setUint32(0, (nsec << 2) | (secHigh & 0x3));
30+
// secLow32
31+
view.setUint32(4, secLow);
32+
return rv;
33+
}
34+
} else {
35+
// timestamp 96 = { nsec32 (unsigned), sec64 (signed) }
36+
const rv = new Uint8Array(12);
37+
const view = new DataView(rv.buffer);
38+
view.setUint32(0, nsec);
39+
setInt64(view, 4, sec);
40+
return rv;
41+
}
42+
}
43+
44+
export function encodeDateToTimeSpec(date: Date): TimeSpec {
45+
const msec = date.getTime();
46+
const sec = Math.floor(msec / 1000);
47+
const nsec = (msec - sec * 1000) * 1e6;
48+
49+
// Normalizes { sec, nsec } to ensure nsec is unsigned.
50+
const nsecInSec = Math.floor(nsec / 1e9);
51+
return {
52+
sec: sec + nsecInSec,
53+
nsec: nsec - nsecInSec * 1e9,
54+
};
55+
}
56+
57+
export function encodeTimestampExtension(object: unknown): Uint8Array | null {
58+
if (object instanceof Date) {
59+
const timeSpec = encodeDateToTimeSpec(object);
60+
return encodeTimeSpecToTimestamp(timeSpec);
61+
} else {
62+
return null;
63+
}
64+
}
65+
66+
// https://github.com/msgpack/msgpack/blob/master/spec.md#timestamp-extension-type
67+
export function decodeTimestampExtension(data: Uint8Array): Date {
68+
// data may be 32, 64, or 96 bits
69+
switch (data.byteLength) {
70+
case 4: {
71+
// timestamp 32 = { sec32 }
72+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
73+
const sec = view.getUint32(0);
74+
return new Date(sec * 1000);
75+
}
76+
case 8: {
77+
// timestamp 64 = { nsec30, sec34 }
78+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
79+
80+
const nsec30AndSecHigh2 = view.getUint32(0);
81+
const secLow32 = view.getUint32(4);
82+
const nsec = nsec30AndSecHigh2 >>> 2;
83+
const sec = (nsec30AndSecHigh2 & 0x3) * 0x100000000 + secLow32;
84+
return new Date(sec * 1000 + nsec / 1e6);
85+
}
86+
case 12: {
87+
// timestamp 96 = { nsec32 (unsigned), sec64 (signed) }
88+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
89+
90+
const nsec = view.getUint32(0);
91+
const sec = getInt64(view, 4);
92+
93+
return new Date(sec * 1000 + nsec / 1e6);
94+
}
95+
default:
96+
throw new Error(`Unrecognized data size for timestamp: ${data.length}`);
97+
}
98+
}
99+
100+
export const timestampExtension = {
101+
type: EXT_TIMESTAMP,
102+
encode: encodeTimestampExtension,
103+
decode: decodeTimestampExtension,
104+
};

test/codec-timestamp.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import assert from "assert";
22
import util from "util";
3-
import { encode, decode } from "../src";
4-
import { encodeDateToTimeSpec } from "../src/ExtensionCodec";
3+
import { encode, decode, encodeDateToTimeSpec } from "../src";
54

65
const TIME = 1556636810389;
76

test/msgpack-test-suite.test.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,7 @@ import assert from "assert";
22
import util from "util";
33
import { Exam } from "msgpack-test-js";
44
import { MsgTimestamp } from "msg-timestamp";
5-
import {
6-
encode as _encode,
7-
decode as _decode,
8-
ExtensionCodec,
9-
EXT_TIMESTAMP,
10-
encodeTimestampFromTimeSpec,
11-
} from "../src";
5+
import { encode as _encode, decode as _decode, ExtensionCodec, EXT_TIMESTAMP, encodeTimeSpecToTimestamp } from "../src";
126

137
const { encode, decode }: { encode: typeof _encode; decode: typeof _decode } = (() => {
148
if (process.env.TEST_DIST) {
@@ -28,7 +22,7 @@ extensionCodec.register({
2822
type: EXT_TIMESTAMP,
2923
encode: (input) => {
3024
if (input instanceof MsgTimestamp) {
31-
return encodeTimestampFromTimeSpec({
25+
return encodeTimeSpecToTimestamp({
3226
sec: input.getTime(),
3327
nsec: input.getNano(),
3428
});

0 commit comments

Comments
 (0)