Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6,264 changes: 6,264 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

34 changes: 33 additions & 1 deletion src/cbor/CborDecoderBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {JsonPackExtension} from '../JsonPackExtension';
import {JsonPackValue} from '../JsonPackValue';
import {Reader} from '@jsonjoy.com/util/lib/buffers/Reader';
import sharedCachedUtf8Decoder from '@jsonjoy.com/util/lib/buffers/utf8/sharedCachedUtf8Decoder';
import {daysSinceEpochToDate, rfc3339StringToDate} from './dates';
import type {CachedUtf8Decoder} from '@jsonjoy.com/util/lib/buffers/utf8/CachedUtf8Decoder';
import type {IReader, IReaderResettable} from '@jsonjoy.com/util/lib/buffers';
import type {BinaryJsonDecoder, PackValue} from '../types';
Expand Down Expand Up @@ -314,7 +315,38 @@ export class CborDecoderBase<R extends IReader & IReaderResettable = IReader & I
}

public readTagRaw(tag: number): JsonPackExtension<unknown> | unknown {
return new JsonPackExtension<unknown>(tag, this.val());
const value = this.val();

// Handle CBOR date tags as defined in RFC 8943
switch (tag) {
case 100: {
// Tag 100: Number of days since the epoch date 1970-01-01
if (typeof value === 'number') {
try {
return daysSinceEpochToDate(value);
} catch (error) {
// If conversion fails, return as JsonPackExtension
return new JsonPackExtension<unknown>(tag, value);
}
}
break;
}
case 1004: {
// Tag 1004: RFC 3339 full-date string
if (typeof value === 'string') {
try {
return rfc3339StringToDate(value);
} catch (error) {
// If conversion fails, return as JsonPackExtension
return new JsonPackExtension<unknown>(tag, value);
}
}
break;
}
}

// For all other tags, return as JsonPackExtension
return new JsonPackExtension<unknown>(tag, value);
}

// ------------------------------------------------------------ Token reading
Expand Down
23 changes: 23 additions & 0 deletions src/cbor/CborEncoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {isFloat32} from '@jsonjoy.com/util/lib/buffers/isFloat32';
import {JsonPackExtension} from '../JsonPackExtension';
import {CborEncoderFast} from './CborEncoderFast';
import {JsonPackValue} from '../JsonPackValue';
import {dateToDaysSinceEpoch, dateToRfc3339String} from './dates';
import type {IWriter, IWriterGrowable} from '@jsonjoy.com/util/lib/buffers';

export class CborEncoder<W extends IWriter & IWriterGrowable = IWriter & IWriterGrowable> extends CborEncoderFast<W> {
Expand Down Expand Up @@ -39,6 +40,8 @@ export class CborEncoder<W extends IWriter & IWriterGrowable = IWriter & IWriter
case JsonPackValue:
const buf = (value as JsonPackValue).val;
return this.writer.buf(buf, buf.length);
case Date:
return this.writeDate(value as Date);
default:
if (value instanceof Uint8Array) return this.writeBin(value);
if (Array.isArray(value)) return this.writeArr(value);
Expand Down Expand Up @@ -68,6 +71,26 @@ export class CborEncoder<W extends IWriter & IWriterGrowable = IWriter & IWriter
});
}

/**
* Encodes a JavaScript Date object as a CBOR date tag.
* Uses tag 100 (days since epoch) for most dates, or tag 1004 (RFC 3339 string)
* when needed for better precision or compatibility.
*
* For now, we'll default to tag 100 (days since epoch) as it's more compact.
*
* @param date - The Date object to encode
*/
public writeDate(date: Date): void {
// Validate that the date is valid
if (isNaN(date.getTime())) {
throw new Error('Invalid Date object');
}

// Use tag 100 (days since epoch) by default for compactness
const daysSinceEpoch = dateToDaysSinceEpoch(date);
this.writeTag(100, daysSinceEpoch);
}

public writeUndef(): void {
this.writer.u8(0xf7);
}
Expand Down
68 changes: 68 additions & 0 deletions src/cbor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,74 @@ shallow decoding features, like skipping, one level at-a-time decoding.
- Half-precision `f16` floats are decoded to JavaScript `number`, however,
encoder does not support half-precision floats&mdash;floats are encoded as
`f32` or `f64`.
- **Date support**: JavaScript `Date` objects are automatically encoded using
CBOR date tags as defined in [RFC 8943](https://tools.ietf.org/rfc/rfc8943.txt):
- Tag 100: Number of days since the epoch date 1970-01-01 (default encoding)
- Tag 1004: RFC 3339 full-date string (supported for decoding)
- Only the date component is preserved (time is ignored)
- Encoded dates are automatically decoded back to JavaScript `Date` objects

## Date Support

The CBOR codec includes built-in support for JavaScript `Date` objects following
[RFC 8943](https://tools.ietf.org/rfc/rfc8943.txt) "Tags for Date" specification.

### Encoding Dates

JavaScript `Date` objects are automatically encoded using tag 100 (days since epoch):

```ts
import {CborEncoder, CborDecoder} from '@jsonjoy.com/json-pack/lib/cbor';

const encoder = new CborEncoder();
const decoder = new CborDecoder();

const data = {
event: 'Birthday',
date: new Date(1940, 9, 9), // October 9, 1940
participants: ['John', 'Paul']
};

const encoded = encoder.encode(data);
const decoded = decoder.decode(encoded);

console.log(decoded.date instanceof Date); // true
console.log(decoded.date.toDateString()); // "Wed Oct 09 1940"
```

### Supported Date Tags

- **Tag 100**: Days since 1970-01-01 (compact integer representation)
- Used by default when encoding `Date` objects
- Example: October 9, 1940 → -10676 days
- **Tag 1004**: RFC 3339 full-date string (YYYY-MM-DD format)
- Supported for decoding tagged strings
- Example: "1940-10-09"

### Date Utilities

The codec also exports utility functions for manual date conversion:

```ts
import {
dateToDaysSinceEpoch,
daysSinceEpochToDate,
dateToRfc3339String,
rfc3339StringToDate
} from '@jsonjoy.com/json-pack/lib/cbor';

// Convert Date to days since epoch
const days = dateToDaysSinceEpoch(new Date(1940, 9, 9)); // -10676

// Convert days back to Date
const date = daysSinceEpochToDate(-10676); // October 9, 1940

// Convert Date to RFC 3339 string
const dateString = dateToRfc3339String(new Date(1940, 9, 9)); // "1940-10-09"

// Parse RFC 3339 string to Date
const parsedDate = rfc3339StringToDate("1940-10-09"); // October 9, 1940
```


## Benchmarks
Expand Down
Loading