diff --git a/src/tools/date-time-converter/date-time-converter.e2e.spec.ts b/src/tools/date-time-converter/date-time-converter.e2e.spec.ts index 34ee74951..249dd754e 100644 --- a/src/tools/date-time-converter/date-time-converter.e2e.spec.ts +++ b/src/tools/date-time-converter/date-time-converter.e2e.spec.ts @@ -29,5 +29,6 @@ test.describe('Date time converter - json to yaml', () => { expect((await page.getByTestId('Timestamp').inputValue()).trim()).toEqual('1681333824000'); expect((await page.getByTestId('UTC format').inputValue()).trim()).toEqual('Wed, 12 Apr 2023 21:10:24 GMT'); expect((await page.getByTestId('Mongo ObjectID').inputValue()).trim()).toEqual('64371e400000000000000000'); + expect((await page.getByTestId('Excel date/time').inputValue()).trim()).toEqual('45028.88222222222'); }); }); diff --git a/src/tools/date-time-converter/date-time-converter.models.test.ts b/src/tools/date-time-converter/date-time-converter.models.test.ts index 502cdc67b..c2c7bee99 100644 --- a/src/tools/date-time-converter/date-time-converter.models.test.ts +++ b/src/tools/date-time-converter/date-time-converter.models.test.ts @@ -1,5 +1,8 @@ import { describe, expect, test } from 'vitest'; import { + dateToExcelFormat, + excelFormatToDate, + isExcelFormat, isISO8601DateTimeString, isISO9075DateString, isMongoObjectId, @@ -139,4 +142,39 @@ describe('date-time-converter models', () => { expect(isMongoObjectId('')).toBe(false); }); }); + + describe('isExcelFormat', () => { + test('an Excel format string is a floating number that can be negative', () => { + expect(isExcelFormat('0')).toBe(true); + expect(isExcelFormat('1')).toBe(true); + expect(isExcelFormat('1.1')).toBe(true); + expect(isExcelFormat('-1.1')).toBe(true); + expect(isExcelFormat('-1')).toBe(true); + + expect(isExcelFormat('')).toBe(false); + expect(isExcelFormat('foo')).toBe(false); + expect(isExcelFormat('1.1.1')).toBe(false); + }); + }); + + describe('dateToExcelFormat', () => { + test('a date in Excel format is the number of days since 01/01/1900', () => { + expect(dateToExcelFormat(new Date('2016-05-20T00:00:00.000Z'))).toBe('42510'); + expect(dateToExcelFormat(new Date('2016-05-20T12:00:00.000Z'))).toBe('42510.5'); + expect(dateToExcelFormat(new Date('2023-10-31T09:26:06.421Z'))).toBe('45230.39312987268'); + expect(dateToExcelFormat(new Date('1970-01-01T00:00:00.000Z'))).toBe('25569'); + expect(dateToExcelFormat(new Date('1800-01-01T00:00:00.000Z'))).toBe('-36522'); + }); + }); + + describe('excelFormatToDate', () => { + test('a date in Excel format is the number of days since 01/01/1900', () => { + expect(excelFormatToDate('0')).toEqual(new Date('1899-12-30T00:00:00.000Z')); + expect(excelFormatToDate('1')).toEqual(new Date('1899-12-31T00:00:00.000Z')); + expect(excelFormatToDate('2')).toEqual(new Date('1900-01-01T00:00:00.000Z')); + expect(excelFormatToDate('4242.4242')).toEqual(new Date('1911-08-12T10:10:50.880Z')); + expect(excelFormatToDate('42738.22626859954')).toEqual(new Date('2017-01-03T05:25:49.607Z')); + expect(excelFormatToDate('-1000')).toEqual(new Date('1897-04-04T00:00:00.000Z')); + }); + }); }); diff --git a/src/tools/date-time-converter/date-time-converter.models.ts b/src/tools/date-time-converter/date-time-converter.models.ts index 173b8a87a..f5eedbfa2 100644 --- a/src/tools/date-time-converter/date-time-converter.models.ts +++ b/src/tools/date-time-converter/date-time-converter.models.ts @@ -9,6 +9,9 @@ export { isTimestamp, isUTCDateString, isMongoObjectId, + dateToExcelFormat, + excelFormatToDate, + isExcelFormat, }; const ISO8601_REGEX @@ -21,6 +24,8 @@ const RFC3339_REGEX const RFC7231_REGEX = /^[A-Za-z]{3},\s[0-9]{2}\s[A-Za-z]{3}\s[0-9]{4}\s[0-9]{2}:[0-9]{2}:[0-9]{2}\sGMT$/; +const EXCEL_FORMAT_REGEX = /^-?\d+(\.\d+)?$/; + function createRegexMatcher(regex: RegExp) { return (date?: string) => !_.isNil(date) && regex.test(date); } @@ -33,6 +38,8 @@ const isUnixTimestamp = createRegexMatcher(/^[0-9]{1,10}$/); const isTimestamp = createRegexMatcher(/^[0-9]{1,13}$/); const isMongoObjectId = createRegexMatcher(/^[0-9a-fA-F]{24}$/); +const isExcelFormat = createRegexMatcher(EXCEL_FORMAT_REGEX); + function isUTCDateString(date?: string) { if (_.isNil(date)) { return false; @@ -45,3 +52,11 @@ function isUTCDateString(date?: string) { return false; } } + +function dateToExcelFormat(date: Date) { + return String(((date.getTime()) / (1000 * 60 * 60 * 24)) + 25569); +} + +function excelFormatToDate(excelFormat: string | number) { + return new Date((Number(excelFormat) - 25569) * 86400 * 1000); +} diff --git a/src/tools/date-time-converter/date-time-converter.vue b/src/tools/date-time-converter/date-time-converter.vue index 241c9cf6f..5636ed462 100644 --- a/src/tools/date-time-converter/date-time-converter.vue +++ b/src/tools/date-time-converter/date-time-converter.vue @@ -14,6 +14,9 @@ import { } from 'date-fns'; import type { DateFormat, ToDateMapper } from './date-time-converter.types'; import { + dateToExcelFormat, + excelFormatToDate, + isExcelFormat, isISO8601DateTimeString, isISO9075DateString, isMongoObjectId, @@ -85,6 +88,12 @@ const formats: DateFormat[] = [ toDate: objectId => new Date(Number.parseInt(objectId.substring(0, 8), 16) * 1000), formatMatcher: date => isMongoObjectId(date), }, + { + name: 'Excel date/time', + fromDate: date => dateToExcelFormat(date), + toDate: excelFormatToDate, + formatMatcher: isExcelFormat, + }, ]; const formatIndex = ref(6);