Skip to content

Commit 267eae7

Browse files
authored
Merge pull request #40 from msgpack/std-temporal
add decodeTimestampToTimeSpec() to register custom timestamp ext handlers
2 parents fa2012b + ab92625 commit 267eae7

File tree

6 files changed

+111
-10
lines changed

6 files changed

+111
-10
lines changed

README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,50 @@ const encoded: = encode(value, { extensionCodec });
152152
deepStrictEqual(decode(encoded, { extensionCodec }), value);
153153
```
154154

155+
#### The temporal module as timestamp extensions
156+
157+
This library maps `Date` to the MessagePack timestamp extension, but you re-map the [temporal module](https://github.com/tc39/proposal-temporal) to the timestamp ext like this:
158+
159+
```typescript
160+
import { Instant } from "@std-proposal/temporal";
161+
import { deepStrictEqual } from "assert";
162+
import {
163+
encode,
164+
decode,
165+
ExtensionCodec,
166+
EXT_TIMESTAMP,
167+
encodeTimeSpecToTimestamp,
168+
decodeTimestampToTimeSpec,
169+
} from "@msgpack/msgpack";
170+
171+
const extensionCodec = new ExtensionCodec();
172+
extensionCodec.register({
173+
type: EXT_TIMESTAMP, // override the default behavior!
174+
encode: (input: any) => {
175+
if (input instanceof Instant) {
176+
const sec = input.seconds;
177+
const nsec = Number(input.nanoseconds - BigInt(sec) * BigInt(1e9));
178+
return encodeTimeSpecToTimestamp({ sec, nsec });
179+
} else {
180+
return null;
181+
}
182+
},
183+
decode: (data: Uint8Array) => {
184+
const timeSpec = decodeTimestampToTimeSpec(data);
185+
const sec = BigInt(timeSpec.sec);
186+
const nsec = BigInt(timeSpec.nsec);
187+
return Instant.fromEpochNanoseconds(sec * BigInt(1e9) + nsec);
188+
},
189+
});
190+
191+
const instant = Instant.fromEpochMilliseconds(Date.now());
192+
const encoded = encode(instant, { extensionCodec });
193+
const decoded = decode(encoded, { extensionCodec });
194+
deepStrictEqual(decoded, instant);
195+
```
196+
197+
This will be default after the temporal module is implemented in major browsers, which is not a near-future, though.
198+
155199
## MessagePack Mapping Table
156200

157201
The following table shows how JavaScript values are mapped to [MessagePack formats](https://github.com/msgpack/msgpack/blob/master/spec.md) and vice versa.

package-lock.json

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"homepage": "https://msgpack.org/",
5454
"devDependencies": {
5555
"@bitjourney/check-es-version-webpack-plugin": "^1.0.2",
56+
"@std-proposal/temporal": "0.0.1",
5657
"@types/base64-js": "^1.2.5",
5758
"@types/mocha": "^5.2.6",
5859
"@types/node": "^11.13.12",

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export {
1212
EXT_TIMESTAMP,
1313
encodeDateToTimeSpec,
1414
encodeTimeSpecToTimestamp,
15+
decodeTimestampToTimeSpec,
1516
encodeTimestampExtension,
1617
decodeTimestampExtension,
1718
} from "./timestamp";

src/timestamp.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ export function encodeTimeSpecToTimestamp({ sec, nsec }: TimeSpec): Uint8Array {
4444

4545
export function encodeDateToTimeSpec(date: Date): TimeSpec {
4646
const msec = date.getTime();
47-
const sec = Math.floor(msec / 1000);
48-
const nsec = (msec - sec * 1000) * 1e6;
47+
const sec = Math.floor(msec / 1e3);
48+
const nsec = (msec - sec * 1e3) * 1e6;
4949

5050
// Normalizes { sec, nsec } to ensure nsec is unsigned.
5151
const nsecInSec = Math.floor(nsec / 1e9);
@@ -64,39 +64,44 @@ export function encodeTimestampExtension(object: unknown): Uint8Array | null {
6464
}
6565
}
6666

67-
export function decodeTimestampExtension(data: Uint8Array): Date {
67+
export function decodeTimestampToTimeSpec(data: Uint8Array): TimeSpec {
6868
// data may be 32, 64, or 96 bits
6969
switch (data.byteLength) {
7070
case 4: {
7171
// timestamp 32 = { sec32 }
7272
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
7373
const sec = view.getUint32(0);
74-
return new Date(sec * 1000);
74+
const nsec = 0;
75+
return { sec, nsec };
7576
}
7677
case 8: {
7778
// timestamp 64 = { nsec30, sec34 }
7879
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
7980

8081
const nsec30AndSecHigh2 = view.getUint32(0);
8182
const secLow32 = view.getUint32(4);
82-
const nsec = nsec30AndSecHigh2 >>> 2;
8383
const sec = (nsec30AndSecHigh2 & 0x3) * 0x100000000 + secLow32;
84-
return new Date(sec * 1000 + nsec / 1e6);
84+
const nsec = nsec30AndSecHigh2 >>> 2;
85+
return { sec, nsec };
8586
}
8687
case 12: {
8788
// timestamp 96 = { nsec32 (unsigned), sec64 (signed) }
8889
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
8990

90-
const nsec = view.getUint32(0);
9191
const sec = getInt64(view, 4);
92-
93-
return new Date(sec * 1000 + nsec / 1e6);
92+
const nsec = view.getUint32(0);
93+
return { sec, nsec };
9494
}
9595
default:
9696
throw new Error(`Unrecognized data size for timestamp: ${data.length}`);
9797
}
9898
}
9999

100+
export function decodeTimestampExtension(data: Uint8Array): Date {
101+
const timeSpec = decodeTimestampToTimeSpec(data);
102+
return new Date(timeSpec.sec * 1e3 + timeSpec.nsec / 1e6);
103+
}
104+
100105
export const timestampExtension = {
101106
type: EXT_TIMESTAMP,
102107
encode: encodeTimestampExtension,

test/readme.test.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import { deepStrictEqual } from "assert";
2-
import { encode, decode } from "../src";
2+
import {
3+
encode,
4+
decode,
5+
ExtensionCodec,
6+
EXT_TIMESTAMP,
7+
encodeTimeSpecToTimestamp,
8+
decodeTimestampToTimeSpec,
9+
} from "../src";
310

411
describe("README", () => {
512
context("#synopsis", () => {
@@ -21,4 +28,41 @@ describe("README", () => {
2128
deepStrictEqual(decode(encoded), object);
2229
});
2330
});
31+
32+
context("timestamp/temporal", () => {
33+
before(function() {
34+
if (typeof BigInt === "undefined") {
35+
this.skip();
36+
}
37+
});
38+
39+
it("overrides timestamp-ext with std-temporal", () => {
40+
const Instant = require("@std-proposal/temporal").Instant;
41+
42+
const extensionCodec = new ExtensionCodec();
43+
extensionCodec.register({
44+
type: EXT_TIMESTAMP,
45+
encode: (input: any) => {
46+
if (input instanceof Instant) {
47+
const sec = input.seconds;
48+
const nsec = Number(input.nanoseconds - BigInt(sec) * BigInt(1e9));
49+
return encodeTimeSpecToTimestamp({ sec, nsec });
50+
} else {
51+
return null;
52+
}
53+
},
54+
decode: (data: Uint8Array) => {
55+
const timeSpec = decodeTimestampToTimeSpec(data);
56+
const sec = BigInt(timeSpec.sec);
57+
const nsec = BigInt(timeSpec.nsec);
58+
return Instant.fromEpochNanoseconds(sec * BigInt(1e9) + nsec);
59+
},
60+
});
61+
62+
const instant = Instant.fromEpochMilliseconds(Date.now());
63+
const encoded = encode(instant, { extensionCodec });
64+
const decoded = decode(encoded, { extensionCodec });
65+
deepStrictEqual(decoded, instant);
66+
});
67+
});
2468
});

0 commit comments

Comments
 (0)