diff --git a/packages/gatsby/src/schema/types/__tests__/date.js b/packages/gatsby/src/schema/types/__tests__/date.js new file mode 100644 index 0000000000000..313d5e60fdedf --- /dev/null +++ b/packages/gatsby/src/schema/types/__tests__/date.js @@ -0,0 +1,383 @@ +const { store } = require(`../../../redux`) +const { build } = require(`../..`) +const { isDate } = require(`../date`) +require(`../../../db/__tests__/fixtures/ensure-loki`)() + +// Timestamps grabbed from https://github.com/moment/moment/blob/2e2a5b35439665d4b0200143d808a7c26d6cd30f/src/test/moment/is_valid.js + +describe(`isDate`, () => { + it.each([ + `1970`, + `2019`, + `1970-01`, + `2019-01`, + `1970-01-01`, + `2010-01-01`, + `2010-01-30`, + `19700101`, + `20100101`, + `20100130`, + `2010-01-30T23+00:00`, + `2010-01-30T23:59+00:00`, + `2010-01-30T23:59:59+00:00`, + `2010-01-30T23:59:59.999+00:00`, + `2010-01-30T23:59:59.999-07:00`, + `2010-01-30T00:00:00.000+07:00`, + `2010-01-30T23:59:59.999-07`, + `2010-01-30T00:00:00.000+07`, + `2010-01-30T23Z`, + `2010-01-30T23:59Z`, + `2010-01-30T23:59:59Z`, + `2010-01-30T23:59:59.999Z`, + `2010-01-30T00:00:00.000Z`, + `1970-01-01T00:00:00.000001Z`, + `2012-04-01T00:00:00-05:00`, + `2012-11-12T00:00:00+01:00`, + ])(`should return true for valid ISO 8601: %s`, dateString => { + expect(isDate(dateString)).toBeTruthy() + }) + + it.each([ + `2010-01-30 23+00:00`, + `2010-01-30 23:59+00:00`, + `2010-01-30 23:59:59+00:00`, + `2010-01-30 23:59:59.999+00:00`, + `2010-01-30 23:59:59.999-07:00`, + `2010-01-30 00:00:00.000+07:00`, + `2010-01-30 23:59:59.999-07`, + `2010-01-30 00:00:00.000+07`, + `1970-01-01 00:00:00.000Z`, + `2012-04-01 00:00:00-05:00`, + `2012-11-12 00:00:00+01:00`, + `1970-01-01 00:00:00.0000001 Z`, + `1970-01-01 00:00:00.000 Z`, + `1970-01-01 00:00:00 Z`, + `1970-01-01 000000 Z`, + `1970-01-01 00:00 Z`, + `1970-01-01 00 Z`, + ])(`should return true for ISO 8601 (no T, extra space): %s`, dateString => { + expect(isDate(dateString)).toBeTruthy() + }) + + it.each([`1970-W31`, `2006-W01`, `1970W31`, `2009-W53-7`, `2009W537`])( + `should return true for ISO 8601 week dates: %s`, + dateString => { + expect(isDate(dateString)).toBeTruthy() + } + ) + + it.each([`1970-334`, `1970334`, `2090-001`, `2090001`])( + `should return true for ISO 8601 ordinal dates: %s`, + dateString => { + expect(isDate(dateString)).toBeTruthy() + } + ) + + it.each([`2018-08-31T23:25:16.019345+02:00`, `2018-08-31T23:25:16.019345Z`])( + `should return true for microsecond precision: %s`, + dateString => { + expect(isDate(dateString)).toBeTruthy() + } + ) + + it.skip.each([ + `2018-08-31T23:25:16.019345123+02:00`, + `2018-08-31T23:25:16.019345123Z`, + ])(`should return true for nanosecond precision: %s`, dateString => { + expect(isDate(dateString)).toBeTruthy() + }) + + it.skip.each([`2018-08-31T23:25:16.012345678901+02:00`])( + `should return false for precision beyond 9 digits: %s`, + dateString => { + expect(isDate(dateString)).toBeFalsy() + } + ) + + it.each([ + `2010-00-00`, + `2010-01-00`, + `2010-01-40`, + `2010-01-01T24:01`, // 24:00:00 is actually valid + `2010-01-01T23:60`, + `2010-01-01T23:59:60`, + `2010-01-40T23:59:59.9999`, + `2010-00-00T+00:00`, + `2010-01-00T+00:00`, + `2010-01-40T+00:00`, + `2010-01-40T24:01+00:00`, + `2010-01-40T23:60+00:00`, + `2010-01-40T23:59:60+00:00`, + `2010-01-40T23:59:59.9999+00:00`, + `2010-01-40T23:59:59,9999+00:00`, + `2012-04-01T00:00:00-5:00`, // should be -05:00 + `2012-04-01T00:00:00+1:00`, // should be +01:00 + undefined, + `undefined`, + null, + `null`, + [], + {}, + ``, + ` `, + `2012-04-01T00:basketball`, + ])(`should return false for invalid ISO 8601: %s`, dateString => { + expect(isDate(dateString)).toBeFalsy() + }) + + it.skip.each([ + 1371065286, + 1379066897.0, + 1379066897.7, + 1379066897.0, + 1379066897.07, + 1379066897.17, + 1379066897.0, + 1379066897.007, + 1379066897.017, + 1379066897.157, + `1371065286`, + `1379066897.`, + `1379066897.0`, + `1379066897.7`, + `1379066897.00`, + `1379066897.07`, + `1379066897.17`, + `1379066897.000`, + `1379066897.007`, + `1379066897.017`, + `1379066897.157`, + ])(`should return true for unix timestamps: %s`, dateString => { + expect(isDate(dateString)).toBeTruthy() + }) +}) + +const nodes = [ + { + id: `id1`, + internal: { type: `Test` }, + testDate: new Date(), + explicitValidDate: `2010-01-30T23:59:59.999-07:00`, + inferredValidDate: `1970-01-01T00:00:00.000Z`, + validYYYY: `1970`, + validYYMM: `2019-01`, + validYYMMDD: `2010-01-30`, + validYYMMDDNoDash: `20100101`, + validISO1: `2010-01-30T23:59:59.999+00:00`, + validISO2: `2010-01-30T23:59:59.999-07:00`, + validISO3: `2010-01-30T00:00:00.000+07:00`, + validISO4: `2010-01-30T23:59:59.999-07`, + validISO5: `2010-01-30T00:00:00.000+07`, + validISO6: `2010-01-30 00:00:00.000Z`, + validISO7: `1970-01-01T00:00:00.000Z`, + validISO8: `2012-04-01T00:00:00-05:00`, + validISO9: `2012-11-12T00:00:00+01:00`, + + validOrdinal1: `1970-334`, + validOrdinal2: `1970334`, + validOrdinal3: `2090-001`, + validOrdinal4: `2090001`, + + validWeek1: `1970-W31`, + validWeek2: `2006-W01`, + validWeek3: `1970W31`, + validWeek4: `2009-W53-7`, + validWeek5: `2009W537`, + + validMicrosecond1: `2018-08-31T23:25:16.019345+02:00`, + validMicrosecond2: `2018-08-31T23:25:16.019345Z`, + + validNanosecond1: `2018-08-31T23:25:16.019345123+02:00`, + validNanosecond2: `2018-08-31T23:25:16.019345123Z`, + + invalidHighPrecision: `2018-08-31T23:25:16.01234567899993+02:00`, + + invalidDate1: `2010-00-00`, + invalidDate2: `2010-01-00`, + invalidDate3: `2010-01-40`, + invalidDate4: `2010-01-01T24:01`, // 24:00:00 is actually valid + invalidDate5: `2010-01-01T23:60`, + invalidDate6: `2010-01-01T23:59:60`, + invalidDate7: `2010-01-40T23:59:59.9999`, + + invalidDate8: undefined, + invalidDate9: `undefined`, + invalidDate10: null, + invalidDate11: `null`, + invalidDate12: [], + invalidDate13: {}, + invalidDate14: ``, + invalidDate15: ` `, + invalidDate16: `2012-04-01T00:basketball`, + }, +] + +describe(`dateResolver`, () => { + beforeEach(() => { + store.dispatch({ type: `DELETE_CACHE` }) + nodes.forEach(node => + store.dispatch({ type: `CREATE_NODE`, payload: { ...node } }) + ) + }) + + const buildTestSchema = async ({ + infer = true, + addDefaultResolvers = true, + }) => { + const inferDirective = infer ? `@infer` : `@dontInfer` + const noDefaultResolvers = addDefaultResolvers ? `false` : `true` + const typeDefs = [ + ` + type Test implements Node ${inferDirective}(noDefaultResolvers: ${noDefaultResolvers}) { + testDate: Date + explicitValidDate: Date + invalidHighPrecision: Date + invalidDate8: Date + invalidDate9: Date + invalidDate10: Date + invalidDate11: Date + invalidDate12: Date + invalidDate13: Date + invalidDate14: Date + invalidDate15: Date + invalidDate16: Date + }`, + ] + typeDefs.forEach(def => + store.dispatch({ type: `CREATE_TYPES`, payload: def }) + ) + + await build({}) + return store.getState().schema + } + + it(`can properly resolve datetimes`, async () => { + const schema = await buildTestSchema({}) + const fields = schema.getType(`Test`).getFields() + + expect(fields.testDate.resolve).toBeDefined() + expect(fields.explicitValidDate.resolve).toBeDefined() + expect(fields.inferredValidDate.resolve).toBeDefined() + expect(fields.validYYYY.resolve).toBeDefined() + expect(fields.validYYMM.resolve).toBeDefined() + expect(fields.validYYMMDD.resolve).toBeDefined() + expect(fields.validYYMMDDNoDash.resolve).toBeDefined() + + expect(fields.validISO1.resolve).toBeDefined() + expect(fields.validISO2.resolve).toBeDefined() + expect(fields.validISO3.resolve).toBeDefined() + expect(fields.validISO4.resolve).toBeDefined() + expect(fields.validISO5.resolve).toBeDefined() + expect(fields.validISO6.resolve).toBeDefined() + expect(fields.validISO7.resolve).toBeDefined() + expect(fields.validISO8.resolve).toBeDefined() + expect(fields.validISO9.resolve).toBeDefined() + + expect(fields.validOrdinal1.resolve).toBeDefined() + expect(fields.validOrdinal2.resolve).toBeDefined() + expect(fields.validOrdinal3.resolve).toBeDefined() + expect(fields.validOrdinal4.resolve).toBeDefined() + + expect(fields.validWeek1.resolve).toBeDefined() + expect(fields.validWeek2.resolve).toBeDefined() + expect(fields.validWeek3.resolve).toBeDefined() + expect(fields.validWeek4.resolve).toBeDefined() + expect(fields.validWeek5.resolve).toBeDefined() + + expect(fields.validMicrosecond1.resolve).toBeDefined() + expect(fields.validMicrosecond2.resolve).toBeDefined() + + expect(fields.validNanosecond1.resolve).toBeDefined() + expect(fields.validNanosecond2.resolve).toBeDefined() + // expect(fields.invalidHighPrecision.resolve).toBeDefined() + expect(fields.invalidDate1.resolve).toBeUndefined() + expect(fields.invalidDate2.resolve).toBeUndefined() + expect(fields.invalidDate3.resolve).toBeUndefined() + expect(fields.invalidDate4.resolve).toBeUndefined() + expect(fields.invalidDate5.resolve).toBeUndefined() + expect(fields.invalidDate6.resolve).toBeUndefined() + expect(fields.invalidDate7.resolve).toBeUndefined() + expect(fields.invalidDate8.resolve).toBeUndefined() + expect(fields.invalidDate9.resolve).toBeUndefined() + expect(fields.invalidDate10.resolve).toBeUndefined() + expect(fields.invalidDate11.resolve).toBeUndefined() + expect(fields.invalidDate12.resolve).toBeUndefined() + expect(fields.invalidDate13.resolve).toBeUndefined() + expect(fields.invalidDate14.resolve).toBeUndefined() + expect(fields.invalidDate15.resolve).toBeUndefined() + expect(fields.invalidDate16.resolve).toBeUndefined() + }) + + it.each([ + `2018-01-28T23:59:59.999-07:00`, + `2018-01-29T00:00:00.000Z`, + `2018-01-29`, + `20180129`, + `2018-01-29T23:59:59.999+00:00`, + `2018-01-28T19:59:59.999-07:00`, + `2018-01-30T06:00:00.001+07:00`, + `2018-01-28 17:00:00.001-07`, + `2018-01-30 04:00:00.001+07`, + `2018-01-29 04:00:00.001Z`, + `2018-01-29 04:00:00.001 Z`, + `2018-01-29 04:00:00 Z`, + `2018-01-29 04:00 Z`, + `2018-01-29 04 Z`, + `2018-01-28T17:00:00.001-07`, + `2018-01-30T04:00:00.001+07`, + `2018-01-29 00:00:00.001Z`, + `2018-01-29T00:00:00.001Z`, + `2018-01-28 23:00:00-05:00`, + `2018-01-29 23:00:00+01:00`, + `2018-01-28T23:00:00-05:00`, + `2018-01-29T23:00:00+01:00`, + `2018-029`, + `2018029`, + `2018-W05`, + `2018W05`, + `2018-W05-1`, + `2018-01-29T23:25:16.019345+02:00`, + `2018-01-29T23:25:16.019345Z`, + // Seems to not require nanosecond definition to not fail + `2018-01-29T23:25:16.019345123+02:00`, + `2018-01-29T23:25:16.019345123Z`, + ])(`should return "Jan 29, 2018": %s`, async dateString => { + const schema = await buildTestSchema({}) + const fields = schema.getType(`Test`).getFields() + expect( + fields[`testDate`].resolve( + { date: dateString }, + { formatString: `MMM DD, YYYY` }, + {}, + { + fieldName: `date`, + } + ) + ).toEqual(`Jan 29, 2018`) + }) + + it.each([ + `2010-00-00`, + `2010-01-00`, + `2010-01-40`, + `2010-01-01T24:01`, + `2010-01-01T23:60`, + `2010-01-01T23:59:60`, + `2010-01-40T23:59:59.9999`, + // Combine with above statement once we figure out why it passes + // `2018-08-31T23:25:16.01234567899993+02:00`, + ])(`should return "Invalid Date": %s`, async dateString => { + const schema = await buildTestSchema({}) + const fields = schema.getType(`Test`).getFields() + expect( + fields[`testDate`].resolve( + { date: dateString }, + { formatString: `MMM DD, YYYY` }, + {}, + { + fieldName: `date`, + } + ) + ).toEqual(`Invalid date`) + }) +}) diff --git a/packages/gatsby/src/schema/types/date.js b/packages/gatsby/src/schema/types/date.js index fedd02fa4a45b..230df593029dc 100644 --- a/packages/gatsby/src/schema/types/date.js +++ b/packages/gatsby/src/schema/types/date.js @@ -21,6 +21,23 @@ const ISO_8601_FORMAT = [ `YYYY-MM-DDTHHmmss`, `YYYY-MM-DDTHH:mm:ss.SSS`, `YYYY-MM-DDTHHmmss.SSS`, + `YYYY-MM-DDTHH:mm:ss.SSSSSS`, + `YYYY-MM-DDTHHmmss.SSSSSS`, + // `YYYY-MM-DDTHH:mm:ss.SSSSSSSSS`, + // `YYYY-MM-DDTHHmmss.SSSSSSSSS`, + + // Local Time (Omit T) + `YYYY-MM-DD HH`, + `YYYY-MM-DD HH:mm`, + `YYYY-MM-DD HHmm`, + `YYYY-MM-DD HH:mm:ss`, + `YYYY-MM-DD HHmmss`, + `YYYY-MM-DD HH:mm:ss.SSS`, + `YYYY-MM-DD HHmmss.SSS`, + `YYYY-MM-DD HH:mm:ss.SSSSSS`, + `YYYY-MM-DD HHmmss.SSSSSS`, + // `YYYY-MM-DD HH:mm:ss.SSSSSSSSS`, + // `YYYY-MM-DD HHmmss.SSSSSSSSS`, // Coordinated Universal Time (UTC) `YYYY-MM-DDTHHZ`, @@ -30,6 +47,34 @@ const ISO_8601_FORMAT = [ `YYYY-MM-DDTHHmmssZ`, `YYYY-MM-DDTHH:mm:ss.SSSZ`, `YYYY-MM-DDTHHmmss.SSSZ`, + `YYYY-MM-DDTHH:mm:ss.SSSSSSZ`, + `YYYY-MM-DDTHHmmss.SSSSSSZ`, + // `YYYY-MM-DDTHH:mm:ss.SSSSSSSSSZ`, + // `YYYY-MM-DDTHHmmss.SSSSSSSSSZ`, + + // Coordinated Universal Time (UTC) (Omit T) + `YYYY-MM-DD HHZ`, + `YYYY-MM-DD HH:mmZ`, + `YYYY-MM-DD HHmmZ`, + `YYYY-MM-DD HH:mm:ssZ`, + `YYYY-MM-DD HHmmssZ`, + `YYYY-MM-DD HH:mm:ss.SSSZ`, + `YYYY-MM-DD HHmmss.SSSZ`, + `YYYY-MM-DD HH:mm:ss.SSSSSSZ`, + `YYYY-MM-DD HHmmss.SSSSSSZ`, + // `YYYY-MM-DD HH:mm:ss.SSSSSSSSSZ`, + // `YYYY-MM-DD HHmmss.SSSSSSSSSZ`, + + // Coordinated Universal Time (UTC) (Omit T, Extra Space before Z) + `YYYY-MM-DD HH Z`, + `YYYY-MM-DD HH:mm Z`, + `YYYY-MM-DD HHmm Z`, + `YYYY-MM-DD HH:mm:ss Z`, + `YYYY-MM-DD HHmmss Z`, + `YYYY-MM-DD HH:mm:ss.SSS Z`, + `YYYY-MM-DD HHmmss.SSS Z`, + `YYYY-MM-DD HH:mm:ss.SSSSSS Z`, + `YYYY-MM-DD HHmmss.SSSSSS Z`, `YYYY-[W]WW`, `YYYY[W]WW`,