diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e794b53..ac67542f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # @digitalbazaar/cborld ChangeLog +## 8.0.2 - 2025-mm-dd + +### Fixed +- Properly encode/decode dates from before the epoch. + ## 8.0.1 - 2025-05-21 ### Fixed diff --git a/lib/codecs/ValueEncoder.js b/lib/codecs/ValueEncoder.js index 642ca83e..86c79ce1 100644 --- a/lib/codecs/ValueEncoder.js +++ b/lib/codecs/ValueEncoder.js @@ -1,5 +1,5 @@ /*! - * Copyright (c) 2024 Digital Bazaar, Inc. All rights reserved. + * Copyright (c) 2024-2025 Digital Bazaar, Inc. All rights reserved. */ import { bytesFromInt, @@ -37,7 +37,8 @@ export class ValueEncoder extends CborldEncoder { const bytes = toBytes({intValue}); return new Token(Type.bytes, bytes); } - return new Token(Type.uint, intValue); + const tokenType = intValue >= 0 ? Type.uint : Type.negint; + return new Token(tokenType, intValue); } static createEncoder({value, converter, termInfo, termType} = {}) { diff --git a/lib/codecs/XsdDateEncoder.js b/lib/codecs/XsdDateEncoder.js index cca0d4cf..ecada4bf 100644 --- a/lib/codecs/XsdDateEncoder.js +++ b/lib/codecs/XsdDateEncoder.js @@ -1,5 +1,5 @@ /*! - * Copyright (c) 2021-2024 Digital Bazaar, Inc. All rights reserved. + * Copyright (c) 2021-2025 Digital Bazaar, Inc. All rights reserved. */ import {Token, Type} from 'cborg'; import {CborldEncoder} from './CborldEncoder.js'; @@ -21,7 +21,8 @@ export class XsdDateEncoder extends CborldEncoder { // compression would be lossy, do not compress return new Token(Type.string, value); } - return new Token(Type.uint, secondsSinceEpoch); + const tokenType = secondsSinceEpoch >= 0 ? Type.uint : Type.negint; + return new Token(tokenType, secondsSinceEpoch); } static createEncoder({value} = {}) { diff --git a/lib/codecs/XsdDateTimeEncoder.js b/lib/codecs/XsdDateTimeEncoder.js index 37e539bb..a3172047 100644 --- a/lib/codecs/XsdDateTimeEncoder.js +++ b/lib/codecs/XsdDateTimeEncoder.js @@ -1,5 +1,5 @@ /*! - * Copyright (c) 2021-2024 Digital Bazaar, Inc. All rights reserved. + * Copyright (c) 2021-2025 Digital Bazaar, Inc. All rights reserved. */ import {Token, Type} from 'cborg'; import {CborldEncoder} from './CborldEncoder.js'; @@ -14,7 +14,8 @@ export class XsdDateTimeEncoder extends CborldEncoder { encode() { const {value, parsed} = this; const secondsSinceEpoch = Math.floor(parsed / 1000); - const secondsToken = new Token(Type.uint, secondsSinceEpoch); + const tokenType = secondsSinceEpoch >= 0 ? Type.uint : Type.negint; + const secondsToken = new Token(tokenType, secondsSinceEpoch); const millisecondIndex = value.indexOf('.'); if(millisecondIndex === -1) { const expectedDate = new Date( diff --git a/tests/roundtrip.spec.js b/tests/roundtrip.spec.js index de8a369d..d0e27a12 100644 --- a/tests/roundtrip.spec.js +++ b/tests/roundtrip.spec.js @@ -854,6 +854,108 @@ describe('cborld round trip', () => { expect(decodedDocument).to.eql(jsonldDocument); }); + it('should handle wide dateTime range', async () => { + const CONTEXT = { + '@context': { + date: { + '@id': 'ex:date', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime' + }, + } + }; + const jsonldDocument = { + '@context': CONTEXT['@context'], + date: [ + '-1000-01-01T00:00:00Z', + '0-01-01T00:00:00Z', + '1000-01-01T00:00:00Z', + '1000-01-01T00:00:00.123Z', + '1960-01-01T00:00:00Z', + '1960-01-01T23:59:59Z', + '1969-12-31T23:59:59Z', + '1970-01-01T00:00:00Z', + '1970-01-01T00:00:01Z', + '1970-12-31T23:59:59Z', + '2000-01-01T00:00:00Z', + '3000-01-01T00:00:00Z', + '3000-01-01T00:00:00.123Z', + '10000-01-01T00:00:00Z' + // TODO: increase possible xsd:dateTime value coverage + ] + }; + + const documentLoader = url => { + throw new Error(`Refused to load URL "${url}".`); + }; + + const typeTable = new Map(TYPE_TABLE); + + const cborldBytes = await encode({ + jsonldDocument, + format: 'cbor-ld-1.0', + registryEntryId: 1, + documentLoader, + typeTableLoader: () => typeTable + }); + + const decodedDocument = await decode({ + cborldBytes, + documentLoader, + typeTableLoader: () => typeTable + }); + expect(decodedDocument).to.eql(jsonldDocument); + }); + + it('should handle wide date range', async () => { + const CONTEXT = { + '@context': { + date: { + '@id': 'ex:date', + '@type': 'http://www.w3.org/2001/XMLSchema#date' + }, + } + }; + const jsonldDocument = { + '@context': CONTEXT['@context'], + date: [ + '-1000-01-01', + '0-01-01', + '1000-01-01', + '1960-01-01', + '1960-01-01', + '1969-12-31', + '1970-01-01', + '1970-01-01', + '1970-12-31', + '2000-01-01', + '3000-01-01', + '10000-01-01' + // TODO: increase possible xsd:date value coverage + ] + }; + + const documentLoader = url => { + throw new Error(`Refused to load URL "${url}".`); + }; + + const typeTable = new Map(TYPE_TABLE); + + const cborldBytes = await encode({ + jsonldDocument, + format: 'cbor-ld-1.0', + registryEntryId: 1, + documentLoader, + typeTableLoader: () => typeTable + }); + + const decodedDocument = await decode({ + cborldBytes, + documentLoader, + typeTableLoader: () => typeTable + }); + expect(decodedDocument).to.eql(jsonldDocument); + }); + it('should revert remote type-scope', async () => { const CONTEXT_URL = 'urn:foo'; const CONTEXT = {