Skip to content

Commit 4bdc2f7

Browse files
committed
fix: DateTime: timestamps are expected to be number of milliseconds
graphql-iso-date operated on Unix timestamp values, but graphql-scalars operates on ECMAScript timestamps (number of milliseconds since January 1, 1970, UTC) as decided in #387 (comment) It has to be clear which is used. Certainly values are not Unix timestamps and all references must be removed. Docs are updated. ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#the_ecmascript_epoch_and_timestamps
1 parent 9f31c95 commit 4bdc2f7

File tree

8 files changed

+76
-65
lines changed

8 files changed

+76
-65
lines changed

src/scalars/iso-date/DateTime.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { validateJSDate, validateDateTime } from './validator.js';
1313
import {
1414
serializeDateTime,
1515
serializeDateTimeString,
16-
serializeUnixTimestamp,
16+
serializeTimestamp,
1717
parseDateTime,
1818
} from './formatter.js';
1919
import { createGraphQLError } from '../../error.js';
@@ -38,9 +38,9 @@ export const GraphQLDateTimeConfig: GraphQLScalarTypeConfig<Date, string> = /*#_
3838
throw createGraphQLError(`DateTime cannot represent an invalid date-time-string ${value}.`);
3939
} else if (typeof value === 'number') {
4040
try {
41-
return serializeUnixTimestamp(value);
41+
return serializeTimestamp(value);
4242
} catch (e) {
43-
throw createGraphQLError('DateTime cannot represent an invalid Unix timestamp ' + value);
43+
throw createGraphQLError('DateTime cannot represent an invalid timestamp ' + value);
4444
}
4545
} else {
4646
throw createGraphQLError(
@@ -93,7 +93,7 @@ export const GraphQLDateTimeConfig: GraphQLScalarTypeConfig<Date, string> = /*#_
9393
*
9494
* Output:
9595
* This scalar serializes javascript Dates,
96-
* RFC 3339 date-time strings and unix timestamps
96+
* RFC 3339 date-time strings and ECMAScript timestamps (number of milliseconds)
9797
* to RFC 3339 UTC date-time strings.
9898
*/
9999
export const GraphQLDateTime: GraphQLScalarType = /*#__PURE__*/ new GraphQLScalarType(GraphQLDateTimeConfig);

src/scalars/iso-date/formatter.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,8 @@ export const serializeDateTimeString = (dateTime: string): string => {
131131
}
132132
};
133133

134-
// Serializes a Unix timestamp to an RFC 3339 compliant date-time-string
134+
// Serializes ECMAScript timestamp (number of milliseconds) to an RFC 3339 compliant date-time-string
135135
// in the format YYYY-MM-DDThh:mm:ss.sssZ
136-
export const serializeUnixTimestamp = (timestamp: number): string => {
137-
return new Date(timestamp * 1000).toISOString();
136+
export const serializeTimestamp = (timestamp: number): string => {
137+
return new Date(timestamp).toISOString();
138138
};

src/scalars/iso-date/validator.ts

+14-9
Original file line numberDiff line numberDiff line change
@@ -140,17 +140,22 @@ export const validateDateTime = (dateTimeString: string): boolean => {
140140
};
141141

142142
// Function that checks whether a given number is a valid
143-
// Unix timestamp.
143+
// ECMAScript timestamp.
144144
//
145-
// Unix timestamps are signed 32-bit integers. They are interpreted
146-
// as the number of seconds since 00:00:00 UTC on 1 January 1970.
145+
// ECMAScript are interpreted as the number of milliseconds
146+
// since 00:00:00 UTC on 1 January 1970.
147147
//
148-
export const validateUnixTimestamp = (timestamp: number): boolean => {
149-
const MAX_INT = 2147483647;
150-
const MIN_INT = -2147483648;
151-
return (
152-
timestamp === timestamp && timestamp <= MAX_INT && timestamp >= MIN_INT
153-
); // eslint-disable-line
148+
// It is defined in ECMA-262 that a maximum of ±100,000,000 days relative to
149+
// January 1, 1970 UTC (that is, April 20, 271821 BCE ~ September 13, 275760 CE)
150+
// can be represented by the standard Date object
151+
// (equivalent to ±8,640,000,000,000,000 milliseconds).
152+
//
153+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#the_ecmascript_epoch_and_timestamps
154+
//
155+
export const validateTimestamp = (timestamp: number): boolean => {
156+
const MAX = 8640000000000000;
157+
const MIN = -8640000000000000;
158+
return timestamp === timestamp && timestamp <= MAX && timestamp >= MIN; // eslint-disable-line
154159
};
155160

156161
// Function that checks whether a javascript Date instance

tests/iso-date/DateTime.integration.test.ts

+11-11
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ const schema = new GraphQLSchema({
2626
type: GraphQLDateTime,
2727
resolve: () => '2016-02-01T00:00:00-11:00',
2828
},
29-
validUnixTimestamp: {
29+
validTimestamp: {
3030
type: GraphQLDateTime,
31-
resolve: () => 854325678,
31+
resolve: () => 854325678000,
3232
},
3333
invalidDateString: {
3434
type: GraphQLDateTime,
@@ -38,13 +38,13 @@ const schema = new GraphQLSchema({
3838
type: GraphQLDateTime,
3939
resolve: () => new Date('wrong'),
4040
},
41-
invalidType: {
41+
invalidTimestamp: {
4242
type: GraphQLDateTime,
43-
resolve: () => [],
43+
resolve: () => Number.POSITIVE_INFINITY,
4444
},
45-
invalidUnixTimestamp: {
45+
invalidType: {
4646
type: GraphQLDateTime,
47-
resolve: () => Number.POSITIVE_INFINITY,
47+
resolve: () => [],
4848
},
4949
input: {
5050
type: GraphQLDateTime,
@@ -65,7 +65,7 @@ it('executes a query that includes a DateTime', async () => {
6565
validDate
6666
validUTCDateString
6767
validDateString
68-
validUnixTimestamp
68+
validTimestamp
6969
input(date: $date)
7070
inputNull: input
7171
}
@@ -81,7 +81,7 @@ it('executes a query that includes a DateTime', async () => {
8181
validUTCDateString: '1991-12-24T00:00:00Z',
8282
validDateString: '2016-02-01T11:00:00Z',
8383
input: '2017-10-01T00:00:00.000Z',
84-
validUnixTimestamp: '1997-01-27T00:41:18.000Z',
84+
validTimestamp: '1997-01-27T00:41:18.000Z',
8585
inputNull: null,
8686
},
8787
});
@@ -145,8 +145,8 @@ it('errors if an invalid date-time is returned from the resolver', async () => {
145145
{
146146
invalidDateString
147147
invalidDate
148+
invalidTimestamp
148149
invalidType
149-
invalidUnixTimestamp
150150
}
151151
`;
152152

@@ -157,14 +157,14 @@ it('errors if an invalid date-time is returned from the resolver', async () => {
157157
"data": {
158158
"invalidDate": null,
159159
"invalidDateString": null,
160+
"invalidTimestamp": null,
160161
"invalidType": null,
161-
"invalidUnixTimestamp": null,
162162
},
163163
"errors": [
164164
[GraphQLError: DateTime cannot represent an invalid date-time-string 2017-01-001T00:00:00Z.],
165165
[GraphQLError: DateTime cannot represent an invalid Date instance],
166+
[GraphQLError: DateTime cannot represent an invalid timestamp Infinity],
166167
[GraphQLError: DateTime cannot be serialized from a non string, non numeric or non Date type []],
167-
[GraphQLError: DateTime cannot represent an invalid Unix timestamp Infinity],
168168
],
169169
}
170170
`);

tests/iso-date/DateTime.test.ts

+10-10
Original file line numberDiff line numberDiff line change
@@ -88,24 +88,24 @@ describe('GraphQLDateTime', () => {
8888
});
8989
});
9090

91-
// Serializes Unix timestamp
91+
// Serializes ECMAScript timestamp
9292
[
93-
[854325678, '1997-01-27T00:41:18.000Z'],
94-
[854325678.123, '1997-01-27T00:41:18.123Z'],
95-
[876535, '1970-01-11T03:28:55.000Z'],
96-
// The maximum representable unix timestamp
97-
[2147483647, '2038-01-19T03:14:07.000Z'],
98-
// The minimum representable unit timestamp
99-
[-2147483648, '1901-12-13T20:45:52.000Z'],
93+
[854325678000, '1997-01-27T00:41:18.000Z'],
94+
[854325678123, '1997-01-27T00:41:18.123Z'],
95+
[876535000, '1970-01-11T03:28:55.000Z'],
96+
// The maximum representable ECMAScript timestamp
97+
[8640000000000000, '+275760-09-13T00:00:00.000Z'],
98+
// The minimum representable ECMAScript timestamp
99+
[-8640000000000000, '-271821-04-20T00:00:00.000Z'],
100100
].forEach(([value, expected]) => {
101-
it(`serializes unix timestamp ${stringify(value)} into date-string ${expected}`, () => {
101+
it(`serializes timestamp ${stringify(value)} into date-time-string ${expected}`, () => {
102102
expect(GraphQLDateTime.serialize(value)).toEqual(expected);
103103
});
104104
});
105105
});
106106

107107
[Number.NaN, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY].forEach(value => {
108-
it(`throws an error serializing the invalid unix timestamp ${stringify(value)}`, () => {
108+
it(`throws an error serializing the invalid timestamp ${stringify(value)}`, () => {
109109
expect(() => GraphQLDateTime.serialize(value)).toThrowErrorMatchingSnapshot();
110110
});
111111
});

tests/iso-date/__snapshots__/DateTime.test.ts.snap

+3-3
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@ exports[`GraphQLDateTime serialization throws error when serializing true 1`] =
4242

4343
exports[`GraphQLDateTime serialization throws error when serializing undefined 1`] = `"DateTime cannot be serialized from a non string, non numeric or non Date type undefined"`;
4444

45-
exports[`GraphQLDateTime throws an error serializing the invalid unix timestamp Infinity 1`] = `"DateTime cannot represent an invalid Unix timestamp Infinity"`;
45+
exports[`GraphQLDateTime throws an error serializing the invalid timestamp Infinity 1`] = `"DateTime cannot represent an invalid timestamp Infinity"`;
4646

47-
exports[`GraphQLDateTime throws an error serializing the invalid unix timestamp Infinity 2`] = `"DateTime cannot represent an invalid Unix timestamp Infinity"`;
47+
exports[`GraphQLDateTime throws an error serializing the invalid timestamp Infinity 2`] = `"DateTime cannot represent an invalid timestamp Infinity"`;
4848

49-
exports[`GraphQLDateTime throws an error serializing the invalid unix timestamp NaN 1`] = `"DateTime cannot represent an invalid Unix timestamp NaN"`;
49+
exports[`GraphQLDateTime throws an error serializing the invalid timestamp NaN 1`] = `"DateTime cannot represent an invalid timestamp NaN"`;
5050

5151
exports[`GraphQLDateTime value parsing throws an error parsing an invalid date-string "2015-02-24T00:00:00.000+0100" 1`] = `"DateTime cannot represent an invalid date-time-string 2015-02-24T00:00:00.000+0100."`;
5252

tests/iso-date/formatter.test.ts

+12-12
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
serializeDate,
1414
serializeDateTime,
1515
serializeDateTimeString,
16-
serializeUnixTimestamp,
16+
serializeTimestamp,
1717
parseTime,
1818
parseDate,
1919
parseDateTime,
@@ -85,20 +85,20 @@ describe('formatting', () => {
8585

8686
(
8787
[
88-
[854325678, '1997-01-27T00:41:18.000Z'],
89-
[876535, '1970-01-11T03:28:55.000Z'],
90-
[876535.8, '1970-01-11T03:28:55.800Z'],
91-
[876535.8321, '1970-01-11T03:28:55.832Z'],
92-
[-876535.8, '1969-12-21T20:31:04.200Z'],
88+
[854325678000, '1997-01-27T00:41:18.000Z'],
89+
[876535000, '1970-01-11T03:28:55.000Z'],
90+
[876535800, '1970-01-11T03:28:55.800Z'],
91+
[876535832.1, '1970-01-11T03:28:55.832Z'],
92+
[-876535800, '1969-12-21T20:31:04.200Z'],
9393
[0, '1970-01-01T00:00:00.000Z'],
94-
// The maximum representable unix timestamp
95-
[2147483647, '2038-01-19T03:14:07.000Z'],
96-
// The minimum representable unit timestamp
97-
[-2147483648, '1901-12-13T20:45:52.000Z'],
94+
// The maximum representable ECMAScript timestamp
95+
[8640000000000000, '+275760-09-13T00:00:00.000Z'],
96+
// The minimum representable ECMAScript timestamp
97+
[-8640000000000000, '-271821-04-20T00:00:00.000Z'],
9898
] as [number, string][]
9999
).forEach(([timestamp, dateTimeString]) => {
100-
it(`serializes Unix timestamp ${stringify(timestamp)} into date-time-string ${dateTimeString}`, () => {
101-
expect(serializeUnixTimestamp(timestamp)).toEqual(dateTimeString);
100+
it(`serializes timestamp ${stringify(timestamp)} into date-time-string ${dateTimeString}`, () => {
101+
expect(serializeTimestamp(timestamp)).toEqual(dateTimeString);
102102
});
103103
});
104104

tests/iso-date/validator.test.ts

+19-13
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
validateTime,
1212
validateDate,
1313
validateDateTime,
14-
validateUnixTimestamp,
14+
validateTimestamp,
1515
validateJSDate,
1616
} from '../../src/scalars/iso-date/validator.js';
1717

@@ -117,22 +117,28 @@ describe('validator', () => {
117117
});
118118
});
119119

120-
describe('validateUnixTimestamp', () => {
120+
describe('validateTimestamp', () => {
121121
[
122-
854325678, 876535, 876535.8, 876535.8321, -876535.8,
123-
// The maximum representable unix timestamp
124-
2147483647,
125-
// The minimum representable unit timestamp
126-
-2147483648,
127-
].forEach(timestamp => {
128-
it(`identifies ${timestamp} as a valid Unix timestamp`, () => {
129-
expect(validateUnixTimestamp(timestamp)).toEqual(true);
122+
854325678000, 876535000, 876535800, 876535832.1, -876535800,
123+
// The maximum representable ECMAScript timestamp
124+
8640000000000000,
125+
// The minimum representable ECMAScript timestamp
126+
-8640000000000000,
127+
].forEach((timestamp) => {
128+
it(`identifies ${timestamp} as a valid timestamp`, () => {
129+
expect(validateTimestamp(timestamp)).toEqual(true);
130130
});
131131
});
132132

133-
[Number.NaN, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, 2147483648, -2147483649].forEach(timestamp => {
134-
it(`identifies ${timestamp} as an invalid Unix timestamp`, () => {
135-
expect(validateUnixTimestamp(timestamp)).toEqual(false);
133+
[
134+
Number.NaN,
135+
Number.POSITIVE_INFINITY,
136+
Number.POSITIVE_INFINITY,
137+
8640000000000001,
138+
-8640000000000001,
139+
].forEach((timestamp) => {
140+
it(`identifies ${timestamp} as an invalid ECMAScript timestamp`, () => {
141+
expect(validateTimestamp(timestamp)).toEqual(false);
136142
});
137143
});
138144
});

0 commit comments

Comments
 (0)