From b5c1058970beeaa1950adbeb4957b4abfb0807f7 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Sun, 5 May 2024 02:18:55 -0400 Subject: [PATCH 01/10] feat!: migrate from Moment to Tempo - I can finally migrate away from MomentJS, [Tempo](https://tempo.formkit.com) is simple, ESM, small and has all necessary functions to replace Moment --- .../vite-demo-vanilla-bundle/package.json | 2 +- .../src/examples/example10.ts | 14 +- .../src/examples/example11.ts | 3 +- package.json | 2 +- packages/common/package.json | 2 +- .../commonEditorFilterUtils.ts | 33 ++-- .../src/editors/__tests__/dateEditor.spec.ts | 8 +- packages/common/src/editors/dateEditor.ts | 31 ++-- .../filterConditionProcesses.spec.ts | 11 -- .../filter-conditions/dateFilterCondition.ts | 37 ++-- .../filterConditionProcesses.ts | 2 +- .../__tests__/compoundDateFilter.spec.ts | 17 +- .../filters/__tests__/dateRangeFilter.spec.ts | 16 +- packages/common/src/filters/dateFilter.ts | 36 ++-- .../dateTimeEuroAM_PMFormatter.spec.ts | 6 +- .../dateTimeEuroAmPmFormatter.spec.ts | 6 +- .../dateTimeEuroShortAM_PMFormatter.spec.ts | 6 +- .../dateTimeEuroShortAmPmFormatter.spec.ts | 6 +- .../dateTimeEuroShortFormatter.spec.ts | 6 +- .../dateTimeIsoAM_PMFormatter.spec.ts | 6 +- .../dateTimeIsoAmPmFormatter.spec.ts | 6 +- .../dateTimeUsAM_PMFormatter.spec.ts | 6 +- .../__tests__/dateTimeUsAmPmFormatter.spec.ts | 6 +- .../dateTimeUsShortAM_PMFormatter.spec.ts | 6 +- .../dateTimeUsShortAmPmFormatter.spec.ts | 6 +- .../dateTimeUsShortFormatter.spec.ts | 6 +- .../__tests__/dateUtcFormatter.spec.ts | 6 +- .../src/formatters/formatterUtilities.ts | 18 +- .../src/services/__tests__/dateUtils.spec.ts | 170 +++++++++++++++++ .../src/services/__tests__/utilities.spec.ts | 137 -------------- packages/common/src/services/dateUtils.ts | 173 ++++++++++++++++++ .../common/src/services/filter.service.ts | 4 +- packages/common/src/services/index.ts | 1 + packages/common/src/services/utilities.ts | 124 ------------- .../common/src/sortComparers/dateUtilities.ts | 22 +-- .../src/sortComparers/sortComparers.index.ts | 2 +- packages/custom-footer-component/package.json | 4 +- .../src/slick-footer.component.ts | 6 +- .../src/slick-footer.spec.ts | 12 +- packages/excel-export/package.json | 3 +- packages/graphql/package.json | 3 - .../__tests__/graphqlQueryBuilder.spec.ts | 5 +- .../odata/src/services/grid-odata.service.ts | 4 +- pnpm-lock.yaml | 37 ++-- test/cypress/e2e/example02.cy.ts | 15 +- test/cypress/e2e/example03.cy.ts | 2 +- test/cypress/e2e/example04.cy.ts | 4 +- test/cypress/e2e/example05.cy.ts | 4 +- test/cypress/e2e/example06.cy.ts | 6 +- test/cypress/e2e/example07.cy.ts | 2 +- test/cypress/e2e/example08.cy.ts | 6 +- test/cypress/e2e/example10.cy.ts | 6 +- test/cypress/e2e/example11.cy.ts | 7 +- test/cypress/e2e/example18.cy.ts | 10 +- test/cypress/e2e/example19.cy.ts | 2 +- test/jest-global-mocks.ts | 6 - 56 files changed, 575 insertions(+), 512 deletions(-) create mode 100644 packages/common/src/services/__tests__/dateUtils.spec.ts create mode 100644 packages/common/src/services/dateUtils.ts diff --git a/examples/vite-demo-vanilla-bundle/package.json b/examples/vite-demo-vanilla-bundle/package.json index 8ff60fb9a..3c5430e1d 100644 --- a/examples/vite-demo-vanilla-bundle/package.json +++ b/examples/vite-demo-vanilla-bundle/package.json @@ -12,6 +12,7 @@ "dependencies": { "@faker-js/faker": "^8.4.1", "@fnando/sparkline": "^0.3.10", + "@formkit/tempo": "^0.1.1", "@slickgrid-universal/binding": "workspace:~", "@slickgrid-universal/common": "workspace:~", "@slickgrid-universal/composite-editor-component": "workspace:~", @@ -27,7 +28,6 @@ "bulma": "^1.0.0", "dompurify": "^3.1.2", "fetch-jsonp": "^1.3.0", - "moment-tiny": "^2.30.4", "multiple-select-vanilla": "^3.1.3", "rxjs": "^7.8.1", "vanilla-calendar-picker": "^2.11.4", diff --git a/examples/vite-demo-vanilla-bundle/src/examples/example10.ts b/examples/vite-demo-vanilla-bundle/src/examples/example10.ts index de452dee5..9fcf8b337 100644 --- a/examples/vite-demo-vanilla-bundle/src/examples/example10.ts +++ b/examples/vite-demo-vanilla-bundle/src/examples/example10.ts @@ -13,8 +13,8 @@ import { import { BindingEventService } from '@slickgrid-universal/binding'; import { GraphqlService, type GraphqlPaginatedResult, type GraphqlServiceApi, type GraphqlServiceOption, } from '@slickgrid-universal/graphql'; import { Slicker, type SlickVanillaGridBundle } from '@slickgrid-universal/vanilla-bundle'; +import { addDay, format } from '@formkit/tempo'; import { type MultipleSelectOption } from 'multiple-select-vanilla'; -import moment from 'moment-tiny'; import { ExampleGridOptions } from './example-grid-options'; import type { TranslateService } from '../translate.service'; @@ -130,8 +130,8 @@ export default class Example10 { }, ]; - const presetLowestDay = moment().add(-2, 'days').format('YYYY-MM-DD'); - const presetHighestDay = moment().add(20, 'days').format('YYYY-MM-DD'); + const presetLowestDay = format(addDay(new Date(), -2), 'YYYY-MM-DD'); + const presetHighestDay = format(addDay(new Date(), 20), 'YYYY-MM-DD'); this.gridOptions = { enableAutoTooltip: true, @@ -303,8 +303,8 @@ export default class Example10 { } setFiltersDynamically() { - const presetLowestDay = moment().add(-2, 'days').format('YYYY-MM-DD'); - const presetHighestDay = moment().add(20, 'days').format('YYYY-MM-DD'); + const presetLowestDay = format(addDay(new Date(), -2), 'YYYY-MM-DD'); + const presetHighestDay = format(addDay(new Date(), 20), 'YYYY-MM-DD'); // we can Set Filters Dynamically (or different filters) afterward through the FilterService this.sgb.filterService.updateFilters([ @@ -325,8 +325,8 @@ export default class Example10 { } resetToOriginalPresets() { - const presetLowestDay = moment().add(-2, 'days').format('YYYY-MM-DD'); - const presetHighestDay = moment().add(20, 'days').format('YYYY-MM-DD'); + const presetLowestDay = format(addDay(new Date(), -2), 'YYYY-MM-DD'); + const presetHighestDay = format(addDay(new Date(), 20), 'YYYY-MM-DD'); this.sgb?.filterService.updateFilters([ // you can use OperatorType or type them as string, e.g.: operator: 'EQ' diff --git a/examples/vite-demo-vanilla-bundle/src/examples/example11.ts b/examples/vite-demo-vanilla-bundle/src/examples/example11.ts index ee875ac47..38d521ea9 100644 --- a/examples/vite-demo-vanilla-bundle/src/examples/example11.ts +++ b/examples/vite-demo-vanilla-bundle/src/examples/example11.ts @@ -28,7 +28,6 @@ import { SlickCustomTooltip } from '@slickgrid-universal/custom-tooltip-plugin'; import { ExcelExportService } from '@slickgrid-universal/excel-export'; import { Slicker, type SlickVanillaGridBundle } from '@slickgrid-universal/vanilla-bundle'; import { type MultipleSelectOption } from 'multiple-select-vanilla'; -import moment from 'moment-tiny'; import exampleModal from './example11-modal.html?raw'; import Example11Modal from './example11-modal'; @@ -82,7 +81,7 @@ export default class Example11 { sgb: SlickVanillaGridBundle; gridContainerElm: HTMLDivElement; viewSelectElm: HTMLSelectElement; - currentYear = moment().year(); + currentYear = new Date().getFullYear(); defaultPredefinedPresets = [ { label: 'Tasks Finished in Previous Years (wo/Product,Country)', diff --git a/package.json b/package.json index ebb8a8ef4..0ea3a079e 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "@4tw/cypress-drag-drop": "^2.2.5", "@commitlint/cli": "^19.3.0", "@commitlint/config-conventional": "^19.2.2", + "@formkit/tempo": "^0.1.1", "@jest/types": "^29.6.3", "@lerna-lite/cli": "^3.3.3", "@lerna-lite/publish": "^3.3.3", @@ -81,7 +82,6 @@ "jest-extended": "^4.0.2", "jsdom": "^24.0.0", "jsdom-global": "^3.0.2", - "moment-tiny": "^2.30.4", "npm-run-all2": "^6.1.2", "pnpm": "^8.15.8", "rimraf": "^5.0.5", diff --git a/packages/common/package.json b/packages/common/package.json index eb1bb2ace..ce66a21d8 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -66,6 +66,7 @@ "not dead" ], "dependencies": { + "@formkit/tempo": "^0.1.1", "@slickgrid-universal/binding": "workspace:~", "@slickgrid-universal/event-pub-sub": "workspace:~", "@slickgrid-universal/utils": "workspace:~", @@ -74,7 +75,6 @@ "autocompleter": "^9.2.1", "dequal": "^2.0.3", "excel-builder-vanilla": "3.0.1", - "moment-tiny": "^2.30.4", "multiple-select-vanilla": "^3.1.3", "sortablejs": "^1.15.2", "un-flatten-tree": "^2.0.12", diff --git a/packages/common/src/commonEditorFilter/commonEditorFilterUtils.ts b/packages/common/src/commonEditorFilter/commonEditorFilterUtils.ts index 4650ec100..318df22e7 100644 --- a/packages/common/src/commonEditorFilter/commonEditorFilterUtils.ts +++ b/packages/common/src/commonEditorFilter/commonEditorFilterUtils.ts @@ -1,10 +1,10 @@ +import { format } from '@formkit/tempo'; import type { AutocompleteItem } from 'autocompleter'; import type { IOptions } from 'vanilla-calendar-picker'; -import moment from 'moment-tiny'; import type { AutocompleterOption, Column, ColumnEditor, ColumnFilter } from '../interfaces/index'; -import { formatDateByFieldType, mapMomentDateFormatWithFieldType } from '../services'; import { FieldType } from '../enums'; +import { formatTempoDateByFieldType, mapTempoDateFormatWithFieldType, tryParseDate } from '../services/dateUtils'; /** * add loading class ".slick-autocomplete-loading" to the Kraaden Autocomplete input element @@ -37,23 +37,26 @@ export function setPickerDates(dateInputElm: HTMLInputElement, pickerOptions: IO const currentDateOrDates = dateValues; const outputFieldType = columnDef.outputType || colEditorOrFilter.type || columnDef.type || FieldType.dateUtc; const inputFieldType = colEditorOrFilter.type || columnDef.type; - const isoFormat = mapMomentDateFormatWithFieldType(FieldType.dateIso) as string; - const inputFormat = inputFieldType ? mapMomentDateFormatWithFieldType(inputFieldType) : ''; + const isoFormat = mapTempoDateFormatWithFieldType(FieldType.dateIso) as string; + const inputFormat = inputFieldType ? mapTempoDateFormatWithFieldType(inputFieldType) : undefined; const initialDates = Array.isArray(currentDateOrDates) ? currentDateOrDates : [(currentDateOrDates || '') as string]; if (initialDates.length && initialDates[0]) { - const pickerDates = []; + const pickerDates: Date[] = []; for (const initialDate of initialDates) { - const momentDate = moment(initialDate, inputFormat); - pickerDates.push(momentDate); + const date = initialDate instanceof Date ? initialDate : tryParseDate(initialDate, inputFormat); + if (date) { + pickerDates.push(date); + } } - const singleinputFormat = Array.isArray(inputFormat) ? inputFormat[0] : inputFormat; - pickerOptions.settings!.selected = { - dates: [pickerDates.map(p => p.format(isoFormat)).join(':')], - month: pickerDates[0].month(), - year: pickerDates[0].year(), - time: singleinputFormat.toLowerCase().includes('h') ? pickerDates[0].format('HH:mm') : undefined, - }; - dateInputElm.value = initialDates.length ? pickerDates.map(p => formatDateByFieldType(p, undefined, outputFieldType)).join(' — ') : ''; + if (pickerDates.length) { + pickerOptions.settings!.selected = { + dates: [pickerDates.map(p => format(p, isoFormat)).join(':')], + month: pickerDates[0].getMonth(), + year: pickerDates[0].getFullYear(), + time: (inputFormat || '').toLowerCase().includes('h') ? format(pickerDates[0], 'HH:mm') : undefined, + }; + } + dateInputElm.value = initialDates.length ? pickerDates.map(p => formatTempoDateByFieldType(p, undefined, outputFieldType)).join(' — ') : ''; } } \ No newline at end of file diff --git a/packages/common/src/editors/__tests__/dateEditor.spec.ts b/packages/common/src/editors/__tests__/dateEditor.spec.ts index 7199bb7e4..620ab024e 100644 --- a/packages/common/src/editors/__tests__/dateEditor.spec.ts +++ b/packages/common/src/editors/__tests__/dateEditor.spec.ts @@ -1,4 +1,4 @@ -import moment from 'moment-tiny'; +import { format } from '@formkit/tempo'; import { VanillaCalendar } from 'vanilla-calendar-picker'; import { Editors } from '../index'; @@ -354,7 +354,7 @@ describe('DateEditor', () => { editor.applyValue(mockItemData, newDate); // @ts-ignore:2349 - expect(mockItemData).toEqual({ id: 1, startDate: moment(newDate).format('YYYY-MM-DD'), isActive: true }); + expect(mockItemData).toEqual({ id: 1, startDate: format(newDate, 'YYYY-MM-DD'), isActive: true }); }); it('should apply the value to the startDate property with "outputType" format with a field having dot notation (complex object) that passes validation', () => { @@ -370,7 +370,7 @@ describe('DateEditor', () => { editor.applyValue(mockItemData, newDate); // @ts-ignore:2349 - expect(mockItemData).toEqual({ id: 1, employee: { startDate: moment(newDate).format('DD/MM/YYYY HH:mm') }, isActive: true }); + expect(mockItemData).toEqual({ id: 1, employee: { startDate: format(newDate, 'DD/MM/YYYY HH:mm') }, isActive: true }); }); it('should apply the value to the startDate property with output format defined by "saveOutputType" when it passes validation', () => { @@ -385,7 +385,7 @@ describe('DateEditor', () => { editor.applyValue(mockItemData, newDate); // @ts-ignore:2349 - expect(mockItemData).toEqual({ id: 1, startDate: moment(newDate).format('YYYY-MM-DD hh:mm:ss a'), isActive: true }); + expect(mockItemData).toEqual({ id: 1, startDate: format(newDate, 'YYYY-MM-DD hh:mm:ss a'), isActive: true }); }); it('should return item data with an empty string in its value when it fails the custom validation', () => { diff --git a/packages/common/src/editors/dateEditor.ts b/packages/common/src/editors/dateEditor.ts index 8ae629f3d..87bf0e69f 100644 --- a/packages/common/src/editors/dateEditor.ts +++ b/packages/common/src/editors/dateEditor.ts @@ -1,7 +1,7 @@ import { BindingEventService } from '@slickgrid-universal/binding'; import { createDomElement, emptyElement, extend, setDeepValue } from '@slickgrid-universal/utils'; +import { parse } from '@formkit/tempo'; import { VanillaCalendar, type IOptions } from 'vanilla-calendar-picker'; -import moment from 'moment-tiny'; import { Constants } from './../constants'; import { FieldType } from '../enums/index'; @@ -16,10 +16,11 @@ import type { GridOption, VanillaCalendarOption, } from './../interfaces/index'; -import { formatDateByFieldType, getDescendantProperty, mapMomentDateFormatWithFieldType, } from './../services/utilities'; +import { getDescendantProperty, } from './../services/utilities'; import type { TranslaterService } from '../services/translater.service'; import { SlickEventData, type SlickGrid } from '../core/index'; import { setPickerDates } from '../commonEditorFilter'; +import { formatTempoDateByFieldType, mapTempoDateFormatWithFieldType } from '../services/dateUtils'; /* * An example of a date picker editor using Vanilla-Calendar-Picker @@ -105,10 +106,10 @@ export class DateEditor implements Editor { if (this.args && this.columnDef) { const compositeEditorOptions = this.args.compositeEditorOptions; const columnId = this.columnDef?.id ?? ''; - const gridOptions = (this.args.grid.getOptions() || {}) as GridOption; + const gridOptions: GridOption = this.args.grid.getOptions() || {}; this.defaultDate = this.args.item?.[this.columnDef.field]; const outputFieldType = this.columnDef.outputType || this.columnEditor.type || this.columnDef.type || FieldType.dateUtc; - let outputFormat = mapMomentDateFormatWithFieldType(outputFieldType); + let outputFormat = mapTempoDateFormatWithFieldType(outputFieldType); if (Array.isArray(outputFormat)) { outputFormat = outputFormat[0]; } @@ -118,7 +119,7 @@ export class DateEditor implements Editor { if (outputFormat && (outputFormat === 'Z' || outputFormat.toLowerCase().includes('h'))) { this.hasTimePicker = true; } - const pickerFormat = mapMomentDateFormatWithFieldType(this.hasTimePicker ? FieldType.dateTimeIsoAM_PM : FieldType.dateIso); + const pickerFormat = mapTempoDateFormatWithFieldType(this.hasTimePicker ? FieldType.dateTimeIsoAM_PM : FieldType.dateIso); const pickerOptions: IOptions = { input: true, @@ -131,19 +132,19 @@ export class DateEditor implements Editor { }, changeToInput: (_e, self) => { if (self.HTMLInputElement) { - let chosenDate = ''; + let selectedDate = ''; if (self.selectedDates[0]) { - chosenDate = self.selectedDates[0]; - self.HTMLInputElement.value = formatDateByFieldType(self.selectedDates[0], undefined, outputFieldType); + selectedDate = self.selectedDates[0]; + self.HTMLInputElement.value = formatTempoDateByFieldType(self.selectedDates[0], undefined, outputFieldType); } else { self.HTMLInputElement.value = ''; } - if (this.hasTimePicker) { - const momentDate = moment(chosenDate, pickerFormat); - momentDate.hours(+(self.selectedHours || 0)); - momentDate.minute(+(self.selectedMinutes || 0)); - self.HTMLInputElement.value = formatDateByFieldType(momentDate, undefined, outputFieldType); + if (selectedDate && this.hasTimePicker) { + const tempoDate = parse(selectedDate, pickerFormat); + tempoDate.setHours(+(self.selectedHours || 0)); + tempoDate.setMinutes(+(self.selectedMinutes || 0)); + self.HTMLInputElement.value = formatTempoDateByFieldType(tempoDate, undefined, outputFieldType); } if (this._lastClickIsDate) { @@ -328,7 +329,7 @@ export class DateEditor implements Editor { // validate the value before applying it (if not valid we'll set an empty string) const validation = this.validate(null, state); - const newValue = (state && validation?.valid) ? formatDateByFieldType(state, outputFieldType, saveFieldType) : ''; + const newValue = (state && validation?.valid) ? formatTempoDateByFieldType(state, outputFieldType, saveFieldType) : ''; // set the new value to the item datacontext if (isComplexObject) { @@ -367,7 +368,7 @@ export class DateEditor implements Editor { const inputFieldType = this.columnEditor.type || this.columnDef?.type || FieldType.dateIso; const outputFieldType = this.columnDef.outputType || this.columnEditor.type || this.columnDef.type || FieldType.dateIso; - this._originalDate = formatDateByFieldType(value, inputFieldType, outputFieldType); + this._originalDate = formatTempoDateByFieldType(value, inputFieldType, outputFieldType); this._inputElm.value = this._originalDate; } } diff --git a/packages/common/src/filter-conditions/__tests__/filterConditionProcesses.spec.ts b/packages/common/src/filter-conditions/__tests__/filterConditionProcesses.spec.ts index 877afcc6e..b838316b3 100644 --- a/packages/common/src/filter-conditions/__tests__/filterConditionProcesses.spec.ts +++ b/packages/common/src/filter-conditions/__tests__/filterConditionProcesses.spec.ts @@ -1,5 +1,3 @@ -import moment from 'moment-tiny'; - import { getParsedSearchTermsByFieldType } from '../filterConditionProcesses'; describe('getParsedSearchTermsByFieldType method', () => { @@ -13,15 +11,6 @@ describe('getParsedSearchTermsByFieldType method', () => { expect(result2).toEqual(true); }); - it('should get a moment date object when parsing any date type', () => { - const inputDate = '2001-03-03T10:11:22.456Z'; - const result = getParsedSearchTermsByFieldType([inputDate], 'dateUtc'); - - expect(result![0]).toBeObject(); - expect(moment.isMoment(result![0])).toBeTrue(); - expect(result![0].format('YYYY-MM-DD')).toBe('2001-03-03'); - }); - it('should get parsed result as a number array when providing an array of searchTerms that are string of numbers', () => { const input1 = ['0']; const input2 = ['0', 12]; diff --git a/packages/common/src/filter-conditions/dateFilterCondition.ts b/packages/common/src/filter-conditions/dateFilterCondition.ts index d8dbf9009..89892605a 100644 --- a/packages/common/src/filter-conditions/dateFilterCondition.ts +++ b/packages/common/src/filter-conditions/dateFilterCondition.ts @@ -1,30 +1,31 @@ -import moment from 'moment-tiny'; +import { dayStart } from '@formkit/tempo'; import { FieldType, OperatorType, type SearchTerm } from '../enums/index'; import type { FilterConditionOption } from '../interfaces/index'; -import { mapMomentDateFormatWithFieldType } from '../services/utilities'; import { testFilterCondition } from './filterUtilities'; +import { mapTempoDateFormatWithFieldType, tryParseDate } from '../services'; /** * Execute Date filter condition check on each cell and use correct date format depending on it's field type (or filterSearchType when that is provided) */ -export function executeDateFilterCondition(options: FilterConditionOption, parsedSearchDates: any[]): boolean { +export function executeDateFilterCondition(options: FilterConditionOption, parsedSearchDates: Array): boolean { const filterSearchType = options && (options.filterSearchType || options.fieldType) || FieldType.dateIso; - const FORMAT = mapMomentDateFormatWithFieldType(filterSearchType); - const SINGLE_FORMAT = Array.isArray(FORMAT) ? FORMAT[0] : FORMAT; + const FORMAT = mapTempoDateFormatWithFieldType(filterSearchType); const [searchDate1, searchDate2] = parsedSearchDates; - // cell value in moment format - const dateCell = moment(options.cellValue, FORMAT, true); + // cell value in Date format + const dateCell = tryParseDate(options.cellValue, FORMAT, true); // return when cell value is not a valid date - if ((!searchDate1 && !searchDate2) || !dateCell.isValid()) { + if ((!searchDate1 && !searchDate2) || !dateCell) { return false; } // when comparing with Dates only (without time), we need to disregard the time portion, we can do so by setting our time to start at midnight // ref, see https://stackoverflow.com/a/19699447/1212166 - const dateCellTimestamp = SINGLE_FORMAT.toLowerCase().includes('h') ? dateCell.valueOf() : dateCell.clone().startOf('day').valueOf(); + const dateCellTimestamp = FORMAT === 'ISO8601' || FORMAT.toLowerCase().includes('h') + ? dateCell.valueOf() + : dayStart(new Date(dateCell)).valueOf(); // having 2 search dates, we assume that it's a date range filtering and we'll compare against both dates if (searchDate1 && searchDate2) { @@ -39,18 +40,20 @@ export function executeDateFilterCondition(options: FilterConditionOption, parse } // comparing against a single search date - const dateSearchTimestamp1 = SINGLE_FORMAT.toLowerCase().includes('h') ? searchDate1.valueOf() : searchDate1.clone().startOf('day').valueOf(); + const dateSearchTimestamp1 = FORMAT === 'ISO8601' || FORMAT.toLowerCase().includes('h') + ? searchDate1.valueOf() + : dayStart(new Date(searchDate1)).valueOf(); return testFilterCondition(options.operator || '==', dateCellTimestamp, dateSearchTimestamp1); } /** - * From our search filter value(s), get the parsed value(s), they are parsed as Moment object(s). + * From our search filter value(s), get the parsed value(s), they are parsed as Date objects. * This is called only once per filter before running the actual filter condition check on each cell */ export function getFilterParsedDates(inputSearchTerms: SearchTerm[] | undefined, inputFilterSearchType: typeof FieldType[keyof typeof FieldType]): SearchTerm[] { const searchTerms = Array.isArray(inputSearchTerms) && inputSearchTerms || []; const filterSearchType = inputFilterSearchType || FieldType.dateIso; - const FORMAT = mapMomentDateFormatWithFieldType(filterSearchType); + const FORMAT = mapTempoDateFormatWithFieldType(filterSearchType); const parsedSearchValues: any[] = []; @@ -58,18 +61,18 @@ export function getFilterParsedDates(inputSearchTerms: SearchTerm[] | undefined, const searchValues = (searchTerms.length === 2) ? searchTerms : (searchTerms[0] as string).split('..'); const searchValue1 = (Array.isArray(searchValues) && searchValues[0] || '') as Date | string; const searchValue2 = (Array.isArray(searchValues) && searchValues[1] || '') as Date | string; - const searchDate1 = moment(searchValue1, FORMAT, true); - const searchDate2 = moment(searchValue2, FORMAT, true); + const searchDate1 = tryParseDate(searchValue1, FORMAT, true); + const searchDate2 = tryParseDate(searchValue2, FORMAT, true); // return if any of the 2 values are invalid dates - if (!searchDate1.isValid() || !searchDate2.isValid()) { + if (!searchDate1 || !searchDate2) { return []; } parsedSearchValues.push(searchDate1, searchDate2); } else { // return if the search term is an invalid date - const searchDate1 = moment(searchTerms[0] as Date | string, FORMAT, true); - if (!searchDate1.isValid()) { + const searchDate1 = tryParseDate(searchTerms[0] as Date | string, FORMAT, true); + if (!searchDate1) { return []; } parsedSearchValues.push(searchDate1); diff --git a/packages/common/src/filter-conditions/filterConditionProcesses.ts b/packages/common/src/filter-conditions/filterConditionProcesses.ts index ffae472c1..95b1bd3d2 100644 --- a/packages/common/src/filter-conditions/filterConditionProcesses.ts +++ b/packages/common/src/filter-conditions/filterConditionProcesses.ts @@ -44,7 +44,7 @@ export const executeFilterConditionTest: FilterCondition = ((options: FilterCond }) as FilterCondition; /** - * From our search filter value(s), get their parsed value(s), for example a "dateIso" filter will be parsed as Moment object. + * From our search filter value(s), get their parsed value(s), for example a "dateIso" filter will be parsed as Date object. * Then later when we execute the filtering checks, we won't need to re-parse all search value(s) again and again. * So this is called only once, for each search filter that is, prior to running the actual filter condition checks on each cell afterward. */ diff --git a/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts b/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts index 4c1697f66..fdc7de906 100644 --- a/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts +++ b/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts @@ -1,4 +1,5 @@ import 'jest-extended'; +import { format } from '@formkit/tempo'; import { VanillaCalendar } from 'vanilla-calendar-picker'; import { Filters } from '../filters.index'; @@ -7,6 +8,7 @@ import { Column, FilterArguments, GridOption } from '../../interfaces/index'; import { CompoundDateFilter } from '../compoundDateFilter'; import { TranslateServiceStub } from '../../../../../test/translateServiceStub'; import { SlickGrid } from '../../core/index'; +import { mapTempoDateFormatWithFieldType } from '../../services/dateUtils'; const containerId = 'demo-container'; @@ -303,7 +305,7 @@ describe('CompoundDateFilter', () => { expect(filterFilledElms.length).toBe(1); expect(filter.currentDateOrDates![0].toISOString()).toBe('2000-01-01T05:00:00.000Z'); expect(filterInputElm.value).toBe('2000-01-01T05:00:00.000Z'); - expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '<=', searchTerms: ['2000-01-01T05:00:00.000Z'], shouldTriggerQuery: true }); + expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, operator: '<=', searchTerms: ['2000-01-01T05:00:00.000Z'], shouldTriggerQuery: true }); }); it('should create the input filter with a default input dates when passed as a filter options', () => { @@ -321,7 +323,7 @@ describe('CompoundDateFilter', () => { const filterFilledElms = divContainer.querySelectorAll('.form-group.search-filter.filter-finish.filled'); expect(filterFilledElms.length).toBe(1); - expect(filter.currentDateOrDates![0].toISOString()).toBe('2000-01-02T00:00:00.000Z'); + expect(format(filter.currentDateOrDates![0], mapTempoDateFormatWithFieldType(FieldType.dateTimeIso))).toBe('2000-01-02 00:00:00'); expect(filterInputElm.value).toBe('2000-01-02'); expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, operator: '<=', searchTerms: ['2000-01-02'], shouldTriggerQuery: true }); }); @@ -341,7 +343,7 @@ describe('CompoundDateFilter', () => { const filterFilledElms = divContainer.querySelectorAll('.form-group.search-filter.filter-finish.filled'); expect(filterFilledElms.length).toBe(1); - expect(filter.currentDateOrDates![0].toISOString()).toBe('2000-01-02T00:00:00.000Z'); + expect(format(filter.currentDateOrDates![0], mapTempoDateFormatWithFieldType(FieldType.dateTimeIso))).toBe('2000-01-02 00:00:00'); expect(filterInputElm.value).toBe('2000-01-02'); expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, operator: '<=', searchTerms: ['2000-01-02'], shouldTriggerQuery: true }); }); @@ -401,7 +403,7 @@ describe('CompoundDateFilter', () => { expect(filterFilledElms.length).toBe(1); expect(filter.currentDateOrDates![0].toISOString()).toBe('2000-01-01T05:00:00.000Z'); expect(filterInputElm.value).toBe('2000-01-01T05:00:00.000Z'); - expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '<=', searchTerms: ['2000-01-01T05:00:00.000Z'], shouldTriggerQuery: true }); + expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, operator: '<=', searchTerms: ['2000-01-01T05:00:00.000Z'], shouldTriggerQuery: true }); expect(calendarElm).toBeTruthy(); expect(monthElm).toBeTruthy(); // expect(monthElm.textContent).toBe('janvier'); @@ -449,11 +451,8 @@ describe('CompoundDateFilter', () => { const filterFilledElms = divContainer.querySelectorAll('.form-group.search-filter.filter-finish.filled'); expect(filterFilledElms.length).toBe(1); - // expect(filter.currentDateOrDates.toISOString()).toBe('2001-01-02T21:02'); expect(filterInputElm.value).toBe('02/01/2001 16:02'); - expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { - columnDef: mockColumn, operator: '>', searchTerms: ['2001-01-02'], shouldTriggerQuery: true - }); + expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '>', searchTerms: ['2001-01-02'], shouldTriggerQuery: true }); }); it('should have a value with date & time in the picker when using no "outputType" which will default to UTC date', () => { @@ -474,7 +473,7 @@ describe('CompoundDateFilter', () => { expect(filterFilledElms.length).toBe(1); expect(filter.currentDateOrDates![0].toISOString()).toBe('2000-01-01T05:00:00.000Z'); expect(filterInputElm.value).toBe('2000-01-01T05:00:00.000Z'); - expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '<=', searchTerms: ['2000-01-01T05:00:00.000Z'], shouldTriggerQuery: true }); + expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, operator: '<=', searchTerms: ['2000-01-01T05:00:00.000Z'], shouldTriggerQuery: true }); }); it('should have default English text with operator dropdown options related to dates', () => { diff --git a/packages/common/src/filters/__tests__/dateRangeFilter.spec.ts b/packages/common/src/filters/__tests__/dateRangeFilter.spec.ts index 463028318..bcad81c2e 100644 --- a/packages/common/src/filters/__tests__/dateRangeFilter.spec.ts +++ b/packages/common/src/filters/__tests__/dateRangeFilter.spec.ts @@ -153,7 +153,7 @@ describe('DateRangeFilter', () => { it('should trigger input change event and expect the callback to be called with the date provided in the input', () => { mockColumn.filter!.operator = 'RangeInclusive'; const spyCallback = jest.spyOn(filterArguments, 'callback'); - const selectedDates = ['2001-01-02', '2001-01-03', '2001-01-04', '2001-01-05', '2001-01-06', '2001-01-07', '2001-01-08', '2001-01-09', '2001-01-10', '2001-01-11', '2001-01-12', '2001-01-13']; + const selectedDates = ['2001-01-02', '2001-01-13']; filter.init(filterArguments); const filterInputElm = divContainer.querySelector('div.date-picker.search-filter.filter-finish input.date-picker') as HTMLInputElement; @@ -183,7 +183,7 @@ describe('DateRangeFilter', () => { it('should pass a different operator then trigger an input change event and expect the callback to be called with the date provided in the input', () => { mockColumn.filter!.operator = 'RangeExclusive'; const spyCallback = jest.spyOn(filterArguments, 'callback'); - const selectedDates = ['2001-01-02', '2001-01-03', '2001-01-04', '2001-01-05', '2001-01-06', '2001-01-07', '2001-01-08', '2001-01-09', '2001-01-10', '2001-01-11', '2001-01-12', '2001-01-13']; + const selectedDates = ['2001-01-02', '2001-01-13']; filter.init(filterArguments); const filterInputElm = divContainer.querySelector('.date-picker.search-filter.filter-finish input.date-picker') as HTMLInputElement; @@ -197,7 +197,7 @@ describe('DateRangeFilter', () => { }); it('should create the input filter with a default search terms when passed as a filter argument', () => { - const selectedDates = ['2001-01-02', '2001-01-03', '2001-01-04', '2001-01-05', '2001-01-06', '2001-01-07', '2001-01-08', '2001-01-09', '2001-01-10', '2001-01-11', '2001-01-12', '2001-01-13']; + const selectedDates = ['2001-01-02', '2001-01-13']; filterArguments.searchTerms = ['2001-01-02', '2001-01-13']; mockColumn.filter!.operator = 'RangeInclusive'; const spyCallback = jest.spyOn(filterArguments, 'callback'); @@ -309,7 +309,7 @@ describe('DateRangeFilter', () => { mockColumn.outputType = FieldType.dateTimeIsoAmPm; mockColumn.filter!.operator = '>'; const spyCallback = jest.spyOn(filterArguments, 'callback'); - const selectedDates = ['2001-01-02', '2001-01-03', '2001-01-04', '2001-01-05', '2001-01-06', '2001-01-07', '2001-01-08', '2001-01-09', '2001-01-10', '2001-01-11', '2001-01-12', '2001-01-13']; + const selectedDates = ['2001-01-02', '2001-01-13']; filter.init(filterArguments); const filterInputElm = divContainer.querySelector('.date-picker.search-filter.filter-finish input.date-picker') as HTMLInputElement; @@ -321,7 +321,7 @@ describe('DateRangeFilter', () => { expect(filterFilledElms.length).toBe(1); // expect(filter.currentDateOrDates.map((date) => date.toISOString())).toEqual(['2001-01-01T05:00:00.000Z', '2001-01-31T05:00:00.000Z']); - expect(filterInputElm.value).toBe('2001-01-02 12:00:00 am — 2001-01-13 12:00:00 am'); + expect(filterInputElm.value).toBe('2001-01-02 12:00:00 a.m. — 2001-01-13 12:00:00 a.m.'); expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, operator: '>', searchTerms: ['2001-01-02', '2001-01-13'], shouldTriggerQuery: true }); @@ -333,7 +333,7 @@ describe('DateRangeFilter', () => { mockColumn.outputType = FieldType.dateTimeIsoAmPm; mockColumn.filter!.operator = '>'; const spyCallback = jest.spyOn(filterArguments, 'callback'); - const selectedDates = ['2001-01-02', '2001-01-03', '2001-01-04', '2001-01-05', '2001-01-06', '2001-01-07', '2001-01-08', '2001-01-09', '2001-01-10', '2001-01-11', '2001-01-12', '2001-01-13']; + const selectedDates = ['2001-01-02', '2001-01-13']; filter.init(filterArguments); const filterInputElm = divContainer.querySelector('div.date-picker.search-filter.filter-finish input.date-picker') as HTMLInputElement; @@ -344,7 +344,7 @@ describe('DateRangeFilter', () => { expect(filterFilledElms.length).toBe(1); // expect(filter.currentDateOrDates.map((date) => date.toISOString())).toEqual(['2000-01-01T05:00:00.000Z', '2000-01-31T05:00:00.000Z']); - expect(filterInputElm.value).toBe('2001-01-02 12:00:00 am — 2001-01-13 12:00:00 am'); + expect(filterInputElm.value).toBe('2001-01-02 12:00:00 a.m. — 2001-01-13 12:00:00 a.m.'); expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, operator: '>', searchTerms: ['2001-01-02', '2001-01-13'], shouldTriggerQuery: true }); @@ -355,7 +355,7 @@ describe('DateRangeFilter', () => { filterArguments.searchTerms = ['2000-01-01T05:00:00.000Z', '2000-01-31T05:00:00.000Z']; mockColumn.filter!.operator = '<='; const spyCallback = jest.spyOn(filterArguments, 'callback'); - const selectedDates = ['2001-01-02', '2001-01-03', '2001-01-04', '2001-01-05', '2001-01-06', '2001-01-07', '2001-01-08', '2001-01-09', '2001-01-10', '2001-01-11', '2001-01-12', '2001-01-13']; + const selectedDates = ['2001-01-02', '2001-01-13']; filter.init(filterArguments); const filterInputElm = divContainer.querySelector('div.date-picker.search-filter.filter-finish input.date-picker') as HTMLInputElement; diff --git a/packages/common/src/filters/dateFilter.ts b/packages/common/src/filters/dateFilter.ts index a69115551..c19c984c7 100644 --- a/packages/common/src/filters/dateFilter.ts +++ b/packages/common/src/filters/dateFilter.ts @@ -1,7 +1,7 @@ import { BindingEventService } from '@slickgrid-universal/binding'; import { createDomElement, emptyElement, extend, } from '@slickgrid-universal/utils'; +import { format, parse } from '@formkit/tempo'; import { VanillaCalendar, type IOptions } from 'vanilla-calendar-picker'; -import moment, { type Moment } from 'moment-tiny'; import { FieldType, @@ -19,7 +19,8 @@ import type { OperatorDetail, } from '../interfaces/index'; import { buildSelectOperator, compoundOperatorNumeric } from './filterUtilities'; -import { formatDateByFieldType, mapMomentDateFormatWithFieldType, mapOperatorToShorthandDesignation } from '../services/utilities'; +import { formatTempoDateByFieldType, mapTempoDateFormatWithFieldType } from '../services/dateUtils'; +import { mapOperatorToShorthandDesignation } from '../services/utilities'; import type { TranslaterService } from '../services/translater.service'; import type { SlickGrid } from '../core/index'; import { setPickerDates } from '../commonEditorFilter'; @@ -237,7 +238,7 @@ export class DateFilter implements Filter { const columnId = this.columnDef?.id ?? ''; const columnFieldType = this.columnFilter.type || this.columnDef.type || FieldType.dateIso; const outputFieldType = this.columnDef.outputType || this.columnFilter.type || this.columnDef.type || FieldType.dateUtc; - let outputFormat = mapMomentDateFormatWithFieldType(outputFieldType); + let outputFormat = mapTempoDateFormatWithFieldType(outputFieldType); if (Array.isArray(outputFormat)) { outputFormat = outputFormat[0]; } @@ -247,7 +248,7 @@ export class DateFilter implements Filter { if (outputFormat && this.inputFilterType !== 'range' && outputFormat.toLowerCase().includes('h')) { this.hasTimePicker = true; } - const pickerFormat = mapMomentDateFormatWithFieldType(this.hasTimePicker ? FieldType.dateTimeIsoAM_PM : FieldType.dateIso); + const pickerFormat = mapTempoDateFormatWithFieldType(this.hasTimePicker ? FieldType.dateTimeIsoAM_PM : FieldType.dateIso); // get current locale, if user defined a custom locale just use or get it the Translate Service if it exist else just use English const currentLocale = ((this.filterOptions?.locale ?? this.translaterService?.getCurrentLanguage?.()) || this.gridOptions.locale || 'en') as string; @@ -270,7 +271,7 @@ export class DateFilter implements Filter { // if we are preloading searchTerms, we'll keep them for reference if (Array.isArray(pickerValues)) { this._currentDateOrDates = pickerValues as Date[]; - this._currentDateStrings = pickerValues.map(date => formatDateByFieldType(date, undefined, inputFieldType)); + this._currentDateStrings = pickerValues.map(date => formatTempoDateByFieldType(date, undefined, inputFieldType)); } } @@ -286,7 +287,7 @@ export class DateFilter implements Filter { }, changeToInput: (_e, self) => { if (self.HTMLInputElement) { - let outDates: Array = []; + let outDates: Array = []; let firstDate = ''; let lastDate = ''; // when using date range @@ -294,35 +295,36 @@ export class DateFilter implements Filter { self.selectedDates.sort((a, b) => +new Date(a) - +new Date(b)); firstDate = self.selectedDates[0]; lastDate = self.selectedDates[self.selectedDates.length - 1]; - const firstDisplayDate = moment(self.selectedDates[0]).format(outputFormat); - const lastDisplayDate = moment(lastDate).format(outputFormat); + const firstDisplayDate = format(self.selectedDates[0], outputFormat); + const lastDisplayDate = format(lastDate, outputFormat); self.HTMLInputElement.value = `${firstDisplayDate} — ${lastDisplayDate}`; outDates = [firstDate, lastDate]; } else if (self.selectedDates[0]) { firstDate = self.selectedDates[0]; - self.HTMLInputElement.value = formatDateByFieldType(firstDate, FieldType.dateIso, outputFieldType); + self.HTMLInputElement.value = formatTempoDateByFieldType(firstDate, FieldType.dateIso, outputFieldType); outDates = self.selectedDates; } else { self.HTMLInputElement.value = ''; } if (this.hasTimePicker && firstDate) { - const momentDate = moment(firstDate, pickerFormat); - momentDate.hours(+(self.selectedHours || 0)); - momentDate.minute(+(self.selectedMinutes || 0)); - self.HTMLInputElement.value = formatDateByFieldType(momentDate, undefined, outputFieldType); - outDates = [momentDate]; + const tempoDate = parse(firstDate, pickerFormat); + tempoDate.setHours(+(self.selectedHours || 0)); + tempoDate.setMinutes(+(self.selectedMinutes || 0)); + self.HTMLInputElement.value = formatTempoDateByFieldType(tempoDate, undefined, outputFieldType); + outDates = [tempoDate]; } if (this.inputFilterType === 'compound') { - this._currentValue = formatDateByFieldType(outDates[0], undefined, columnFieldType); + this._currentValue = formatTempoDateByFieldType(outDates[0], undefined, columnFieldType); } else { if (Array.isArray(outDates)) { - this._currentDateStrings = outDates.map(date => formatDateByFieldType(date, undefined, columnFieldType)); + this._currentDateStrings = outDates.map(date => formatTempoDateByFieldType(date, undefined, columnFieldType)); this._currentValue = this._currentDateStrings.join('..'); } } - this._currentDateOrDates = outDates.map(dateStr => dateStr instanceof moment ? (dateStr as Moment).toDate() : new Date(dateStr as string)); + + this._currentDateOrDates = outDates.map(d => d instanceof Date ? d : parse(d, pickerFormat)); // when using the time picker, we can simulate a keyup event to avoid multiple backend request // since backend request are only executed after user start typing, changing the time should be treated the same way diff --git a/packages/common/src/formatters/__tests__/dateTimeEuroAM_PMFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeEuroAM_PMFormatter.spec.ts index 35582603b..5c9fdcf3b 100644 --- a/packages/common/src/formatters/__tests__/dateTimeEuroAM_PMFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeEuroAM_PMFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeEuroAM_PM Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-01 02:36:07'; const result = Formatters.dateTimeEuroAM_PM(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('01/05/2019 02:36:07 AM'); + expect(result).toBe('01/05/2019 02:36:07 A.M.'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeEuroAM_PM(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('01/05/2019 02:36:07 AM'); + expect(result).toBe('01/05/2019 02:36:07 A.M.'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeEuroAM_PM(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('01/05/2019 08:36:07 PM'); + expect(result).toBe('01/05/2019 08:36:07 P.M.'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateTimeEuroAmPmFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeEuroAmPmFormatter.spec.ts index e4e7422c5..40bf413d7 100644 --- a/packages/common/src/formatters/__tests__/dateTimeEuroAmPmFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeEuroAmPmFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeShortEuro Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-01 02:36:07'; const result = Formatters.dateTimeEuroAmPm(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('01/05/2019 02:36:07 am'); + expect(result).toBe('01/05/2019 02:36:07 a.m.'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeEuroAmPm(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('01/05/2019 02:36:07 am'); + expect(result).toBe('01/05/2019 02:36:07 a.m.'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeEuroAmPm(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('01/05/2019 08:36:07 pm'); + expect(result).toBe('01/05/2019 08:36:07 p.m.'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateTimeEuroShortAM_PMFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeEuroShortAM_PMFormatter.spec.ts index 37cf2d41d..e9f2ac1f3 100644 --- a/packages/common/src/formatters/__tests__/dateTimeEuroShortAM_PMFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeEuroShortAM_PMFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeEuroShortAM_PM Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-01 02:36:07'; const result = Formatters.dateTimeEuroShortAM_PM(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('01/05/19 2:36:7 AM'); + expect(result).toBe('01/05/19 02:36:07 A.M.'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeEuroShortAM_PM(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('01/05/19 2:36:7 AM'); + expect(result).toBe('01/05/19 02:36:07 A.M.'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeEuroShortAM_PM(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('01/05/19 8:36:7 PM'); + expect(result).toBe('01/05/19 08:36:07 P.M.'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateTimeEuroShortAmPmFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeEuroShortAmPmFormatter.spec.ts index 2edfdee9f..2347ae566 100644 --- a/packages/common/src/formatters/__tests__/dateTimeEuroShortAmPmFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeEuroShortAmPmFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeEuroShortAmPm Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-01 02:36:07'; const result = Formatters.dateTimeEuroShortAmPm(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('01/05/19 2:36:7 am'); + expect(result).toBe('01/05/19 02:36:07 a.m.'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeEuroShortAmPm(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('01/05/19 2:36:7 am'); + expect(result).toBe('01/05/19 02:36:07 a.m.'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeEuroShortAmPm(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('01/05/19 8:36:7 pm'); + expect(result).toBe('01/05/19 08:36:07 p.m.'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateTimeEuroShortFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeEuroShortFormatter.spec.ts index 68595f5f0..b8a12bb1f 100644 --- a/packages/common/src/formatters/__tests__/dateTimeEuroShortFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeEuroShortFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeEuroShort Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-01 02:36:07'; const result = Formatters.dateTimeEuroShort(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('01/05/19 2:36:7'); + expect(result).toBe('01/05/19 02:36:07'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeEuroShort(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('01/05/19 2:36:7'); + expect(result).toBe('01/05/19 02:36:07'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeEuroShort(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('01/05/19 20:36:7'); + expect(result).toBe('01/05/19 20:36:07'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateTimeIsoAM_PMFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeIsoAM_PMFormatter.spec.ts index d841159e9..b6f85ac41 100644 --- a/packages/common/src/formatters/__tests__/dateTimeIsoAM_PMFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeIsoAM_PMFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeIsoAM_PM Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-01 02:36:07'; const result = Formatters.dateTimeIsoAM_PM(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('2019-05-01 02:36:07 AM'); + expect(result).toBe('2019-05-01 02:36:07 A.M.'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeIsoAM_PM(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('2019-05-01 02:36:07 AM'); + expect(result).toBe('2019-05-01 02:36:07 A.M.'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeIsoAM_PM(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('2019-05-01 08:36:07 PM'); + expect(result).toBe('2019-05-01 08:36:07 P.M.'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateTimeIsoAmPmFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeIsoAmPmFormatter.spec.ts index afb3a0127..82ab84754 100644 --- a/packages/common/src/formatters/__tests__/dateTimeIsoAmPmFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeIsoAmPmFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeIsoAmPm Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-01 02:36:07'; const result = Formatters.dateTimeIsoAmPm(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('2019-05-01 02:36:07 am'); + expect(result).toBe('2019-05-01 02:36:07 a.m.'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeIsoAmPm(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('2019-05-01 02:36:07 am'); + expect(result).toBe('2019-05-01 02:36:07 a.m.'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeIsoAmPm(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('2019-05-01 08:36:07 pm'); + expect(result).toBe('2019-05-01 08:36:07 p.m.'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateTimeUsAM_PMFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeUsAM_PMFormatter.spec.ts index d0df34c4e..209855fcd 100644 --- a/packages/common/src/formatters/__tests__/dateTimeUsAM_PMFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeUsAM_PMFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeUsAM_PM Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-01 02:36:07'; const result = Formatters.dateTimeUsAM_PM(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('05/01/2019 02:36:07 AM'); + expect(result).toBe('05/01/2019 02:36:07 A.M.'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeUsAM_PM(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('05/01/2019 02:36:07 AM'); + expect(result).toBe('05/01/2019 02:36:07 A.M.'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeUsAM_PM(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('05/01/2019 08:36:07 PM'); + expect(result).toBe('05/01/2019 08:36:07 P.M.'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateTimeUsAmPmFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeUsAmPmFormatter.spec.ts index a506e908d..84a4d2b9d 100644 --- a/packages/common/src/formatters/__tests__/dateTimeUsAmPmFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeUsAmPmFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeShortUs Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-03 02:36:07'; const result = Formatters.dateTimeUsAmPm(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('05/03/2019 02:36:07 am'); + expect(result).toBe('05/03/2019 02:36:07 a.m.'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeUsAmPm(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('05/01/2019 02:36:07 am'); + expect(result).toBe('05/01/2019 02:36:07 a.m.'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeUsAmPm(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('05/01/2019 08:36:07 pm'); + expect(result).toBe('05/01/2019 08:36:07 p.m.'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateTimeUsShortAM_PMFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeUsShortAM_PMFormatter.spec.ts index b665b3753..0274bf98d 100644 --- a/packages/common/src/formatters/__tests__/dateTimeUsShortAM_PMFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeUsShortAM_PMFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeUsShortAM_PM Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-01 02:36:07'; const result = Formatters.dateTimeUsShortAM_PM(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('05/01/19 2:36:7 AM'); + expect(result).toBe('05/01/19 02:36:07 A.M.'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeUsShortAM_PM(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('05/01/19 2:36:7 AM'); + expect(result).toBe('05/01/19 02:36:07 A.M.'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeUsShortAM_PM(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('05/01/19 8:36:7 PM'); + expect(result).toBe('05/01/19 08:36:07 P.M.'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateTimeUsShortAmPmFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeUsShortAmPmFormatter.spec.ts index d2b86931d..deb6455d7 100644 --- a/packages/common/src/formatters/__tests__/dateTimeUsShortAmPmFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeUsShortAmPmFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeUsShortAmPm Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-01 02:36:07'; const result = Formatters.dateTimeUsShortAmPm(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('05/01/19 2:36:7 am'); + expect(result).toBe('05/01/19 02:36:07 a.m.'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeUsShortAmPm(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('05/01/19 2:36:7 am'); + expect(result).toBe('05/01/19 02:36:07 a.m.'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeUsShortAmPm(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('05/01/19 8:36:7 pm'); + expect(result).toBe('05/01/19 08:36:07 p.m.'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateTimeUsShortFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeUsShortFormatter.spec.ts index 99001ff1c..265e675b1 100644 --- a/packages/common/src/formatters/__tests__/dateTimeUsShortFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeUsShortFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeUsShort Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-01 02:36:07'; const result = Formatters.dateTimeUsShort(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('05/01/19 2:36:7'); + expect(result).toBe('05/01/19 02:36:07'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeUsShort(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('05/01/19 2:36:7'); + expect(result).toBe('05/01/19 02:36:07'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeUsShort(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('05/01/19 20:36:7'); + expect(result).toBe('05/01/19 20:36:07'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateUtcFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateUtcFormatter.spec.ts index e6369c5d1..ff666b297 100644 --- a/packages/common/src/formatters/__tests__/dateUtcFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateUtcFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateUtc Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07Z'); const result = Formatters.dateUtc(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('2019-04-30T21:36:07.000-05:00'); + expect(result).toBe('2019-05-01T02:36:07.000Z'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07Z'); const result = Formatters.dateUtc(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('2019-04-30T21:36:07.000-05:00'); + expect(result).toBe('2019-05-01T02:36:07.000Z'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07Z'); const result = Formatters.dateUtc(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('2019-05-01T15:36:07.000-05:00'); + expect(result).toBe('2019-05-01T20:36:07.000Z'); }); }); diff --git a/packages/common/src/formatters/formatterUtilities.ts b/packages/common/src/formatters/formatterUtilities.ts index 116168c28..619e3a11d 100644 --- a/packages/common/src/formatters/formatterUtilities.ts +++ b/packages/common/src/formatters/formatterUtilities.ts @@ -1,12 +1,12 @@ +import { format } from '@formkit/tempo'; import { getHtmlStringOutput, isPrimitiveOrHTML, stripTags } from '@slickgrid-universal/utils'; -import moment from 'moment-tiny'; import { FieldType } from '../enums/fieldType.enum'; import type { Column, ExcelExportOption, Formatter, FormatterResultWithHtml, FormatterResultWithText, GridOption, TextExportOption } from '../interfaces/index'; -import { mapMomentDateFormatWithFieldType } from '../services/utilities'; import { multipleFormatter } from './multipleFormatter'; import { Constants } from '../constants'; import { type SlickGrid } from '../core/index'; +import { mapTempoDateFormatWithFieldType, toUtcDate, tryParseDate } from '../services/dateUtils'; export type FormatterType = 'group' | 'cell'; export type NumberType = 'decimal' | 'currency' | 'percent' | 'regular'; @@ -96,7 +96,7 @@ export function getValueFromParamsOrFormatterOptions(optionName: string, columnD /** From a FieldType, return the associated date Formatter */ export function getAssociatedDateFormatter(fieldType: typeof FieldType[keyof typeof FieldType], defaultSeparator: string): Formatter { - let defaultDateFormat = mapMomentDateFormatWithFieldType(fieldType); + let defaultDateFormat = mapTempoDateFormatWithFieldType(fieldType, true); if (Array.isArray(defaultDateFormat)) { defaultDateFormat = defaultDateFormat[0]; } @@ -105,13 +105,17 @@ export function getAssociatedDateFormatter(fieldType: typeof FieldType[keyof typ const gridOptions = ((grid && typeof grid.getOptions === 'function') ? grid.getOptions() : {}) as GridOption; const customSeparator = gridOptions?.formatterOptions?.dateSeparator ?? defaultSeparator; const inputType = columnDef?.type ?? FieldType.date; - const inputDateFormat = mapMomentDateFormatWithFieldType(inputType); + const inputDateFormat = mapTempoDateFormatWithFieldType(inputType, true); const isParsingAsUtc = columnDef?.params?.parseDateAsUtc ?? false; - const isDateValid = moment(value, inputDateFormat, false).isValid(); + const date = tryParseDate(value, inputDateFormat); let outputDate = value; - if (value && isDateValid) { - outputDate = isParsingAsUtc ? moment.utc(value).format(defaultDateFormat) : moment(value).format(defaultDateFormat); + if (date) { + let d = value; + if (isParsingAsUtc) { + d = toUtcDate(date); + } + outputDate = format(d, defaultDateFormat); } // user can customize the separator through the "formatterOptions" diff --git a/packages/common/src/services/__tests__/dateUtils.spec.ts b/packages/common/src/services/__tests__/dateUtils.spec.ts new file mode 100644 index 000000000..205c23839 --- /dev/null +++ b/packages/common/src/services/__tests__/dateUtils.spec.ts @@ -0,0 +1,170 @@ +import 'jest-extended'; + +import { FieldType } from '../../enums/index'; +import { mapTempoDateFormatWithFieldType, parseUtcDate } from '../dateUtils'; + +describe('Service/Utilies', () => { + describe('mapTempoDateFormatWithFieldType method', () => { + it('should return a Date in dateTime/dateTimeIso format', () => { + const output1 = mapTempoDateFormatWithFieldType(FieldType.dateTime); + const output2 = mapTempoDateFormatWithFieldType(FieldType.dateTimeIso); + expect(output1).toBe('YYYY-MM-DD HH:mm:ss'); + expect(output2).toBe('YYYY-MM-DD HH:mm:ss'); + }); + + it('should return a Date in dateTimeShortIso format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeShortIso); + expect(output).toBe('YYYY-MM-DD HH:mm'); + }); + + it('should return a Date in dateTimeIsoAmPm format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeIsoAmPm); + expect(output).toBe('YYYY-MM-DD hh:mm:ss a'); + }); + + it('should return a Date in dateTimeIsoAM_PM format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeIsoAM_PM); + expect(output).toBe('YYYY-MM-DD hh:mm:ss A'); + }); + + it('should return a Date in dateEuro format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateEuro); + expect(output).toBe('DD/MM/YYYY'); + }); + + it('should return a Date in dateEuroShort format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateEuroShort); + expect(output).toEqual('D/M/YY'); + }); + + it('should return a Date in dateEuroShort format with zero padding', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateEuroShort, true); + expect(output).toEqual('DD/MM/YY'); + }); + + it('should return a Date in dateTimeEuro format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeEuro); + expect(output).toBe('DD/MM/YYYY HH:mm:ss'); + }); + + it('should return a Date in dateTimeShortEuro format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeShortEuro); + expect(output).toEqual('D/M/YYYY H:m'); + }); + + it('should return a Date in dateTimeShortEuro format with zero padding', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeShortEuro, true); + expect(output).toEqual('DD/MM/YYYY HH:mm'); + }); + + it('should return a Date in dateTimeEuroAmPm format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeEuroAmPm); + expect(output).toBe('DD/MM/YYYY hh:mm:ss a'); + }); + + it('should return a Date in dateTimeEuroAM_PM format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeEuroAM_PM); + expect(output).toBe('DD/MM/YYYY hh:mm:ss A'); + }); + + it('should return a Date in dateTimeEuroShort format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeEuroShort); + expect(output).toEqual('D/M/YY H:m:s'); + }); + + it('should return a Date in dateTimeEuroShort format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeEuroShort, true); + expect(output).toEqual('DD/MM/YY HH:mm:ss'); + }); + + it('should return a Date in dateTimeEuroShortAmPm format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeEuroShortAmPm); + expect(output).toEqual('D/M/YY h:m:s a'); + }); + + it('should return a Date in dateTimeEuroShortAmPm format with zero padding', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeEuroShortAmPm, true); + expect(output).toEqual('DD/MM/YY hh:mm:ss a'); + }); + + it('should return a Date in dateUs format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateUs); + expect(output).toBe('MM/DD/YYYY'); + }); + + it('should return a Date in dateUsShort format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateUsShort); + expect(output).toEqual('M/D/YY'); + }); + + it('should return a Date in dateUsShort format with zero padding', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateUsShort, true); + expect(output).toEqual('MM/DD/YY'); + }); + + it('should return a Date in dateTimeUs format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeUs); + expect(output).toBe('MM/DD/YYYY HH:mm:ss'); + }); + + it('should return a Date in dateTimeShortUs format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeShortUs); + expect(output).toEqual('M/D/YYYY H:m'); + }); + + it('should return a Date in dateTimeShortUs format with zero padding', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeShortUs, true); + expect(output).toEqual('MM/DD/YYYY HH:mm'); + }); + + it('should return a Date in dateTimeUsAmPm format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeUsAmPm); + expect(output).toBe('MM/DD/YYYY hh:mm:ss a'); + }); + + it('should return a Date in dateTimeUsAM_PM format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeUsAM_PM); + expect(output).toBe('MM/DD/YYYY hh:mm:ss A'); + }); + + it('should return a Date in dateTimeUsShort format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeUsShort); + expect(output).toEqual('M/D/YY H:m:s'); + }); + + it('should return a Date in dateTimeUsShort format with zero padding', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeUsShort, true); + expect(output).toEqual('MM/DD/YY HH:mm:ss'); + }); + + it('should return a Date in dateTimeUsShortAmPm format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeUsShortAmPm); + expect(output).toEqual('M/D/YY h:m:s a'); + }); + + it('should return a Date in dateTimeUsShortAmPm format with zero padding', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateTimeUsShortAmPm, true); + expect(output).toEqual('MM/DD/YY hh:mm:ss a'); + }); + + it('should return a Date in dateUtc format', () => { + const output = mapTempoDateFormatWithFieldType(FieldType.dateUtc); + expect(output).toBe('ISO8601'); + }); + + it('should return a Date in date/dateIso format', () => { + const output1 = mapTempoDateFormatWithFieldType(FieldType.date); + const output2 = mapTempoDateFormatWithFieldType(FieldType.dateIso); + expect(output1).toBe('YYYY-MM-DD'); + expect(output2).toBe('YYYY-MM-DD'); + }); + }); + + describe('parseUtcDate method', () => { + it('should return a TZ date parsed as UTC but without milliseconds', () => { + const input = '2012-01-01'; + const output = parseUtcDate(input); + expect(output).toBe('2012-01-01T00:00:00Z'); + }); + }); +}); diff --git a/packages/common/src/services/__tests__/utilities.spec.ts b/packages/common/src/services/__tests__/utilities.spec.ts index 8b52fdb5e..10139ebe1 100644 --- a/packages/common/src/services/__tests__/utilities.spec.ts +++ b/packages/common/src/services/__tests__/utilities.spec.ts @@ -21,11 +21,9 @@ import { getDescendantProperty, getTranslationPrefix, isColumnDateType, - mapMomentDateFormatWithFieldType, mapOperatorByFieldType, mapOperatorToShorthandDesignation, mapOperatorType, - parseUtcDate, thousandSeparatorFormatted, unsubscribeAll, } from '../utilities'; @@ -708,122 +706,6 @@ describe('Service/Utilies', () => { }); }); - describe('mapMomentDateFormatWithFieldType method', () => { - it('should return a moment.js dateTime/dateTimeIso format', () => { - const output1 = mapMomentDateFormatWithFieldType(FieldType.dateTime); - const output2 = mapMomentDateFormatWithFieldType(FieldType.dateTimeIso); - expect(output1).toBe('YYYY-MM-DD HH:mm:ss'); - expect(output2).toBe('YYYY-MM-DD HH:mm:ss'); - }); - - it('should return a moment.js dateTimeShortIso format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeShortIso); - expect(output).toBe('YYYY-MM-DD HH:mm'); - }); - - it('should return a moment.js dateTimeIsoAmPm format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeIsoAmPm); - expect(output).toBe('YYYY-MM-DD hh:mm:ss a'); - }); - - it('should return a moment.js dateTimeIsoAM_PM format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeIsoAM_PM); - expect(output).toBe('YYYY-MM-DD hh:mm:ss A'); - }); - - it('should return a moment.js dateEuro format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateEuro); - expect(output).toBe('DD/MM/YYYY'); - }); - - it('should return a moment.js dateEuroShort format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateEuroShort); - expect(output).toEqual(['DD/MM/YY', 'D/M/YY']); - }); - - it('should return a moment.js dateTimeEuro format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeEuro); - expect(output).toBe('DD/MM/YYYY HH:mm:ss'); - }); - - it('should return a moment.js dateTimeShortEuro format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeShortEuro); - expect(output).toEqual(['DD/MM/YYYY HH:mm', 'D/M/YYYY HH:mm']); - }); - - it('should return a moment.js dateTimeEuroAmPm format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeEuroAmPm); - expect(output).toBe('DD/MM/YYYY hh:mm:ss a'); - }); - - it('should return a moment.js dateTimeEuroAM_PM format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeEuroAM_PM); - expect(output).toBe('DD/MM/YYYY hh:mm:ss A'); - }); - - it('should return a moment.js dateTimeEuroShort format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeEuroShort); - expect(output).toEqual(['DD/MM/YY H:m:s', 'D/M/YY H:m:s']); - }); - - it('should return a moment.js dateTimeEuroShortAmPm format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeEuroShortAmPm); - expect(output).toEqual(['DD/MM/YY h:m:s a', 'D/M/YY h:m:s a']); - }); - - it('should return a moment.js dateUs format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateUs); - expect(output).toBe('MM/DD/YYYY'); - }); - - it('should return a moment.js dateUsShort format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateUsShort); - expect(output).toEqual(['MM/DD/YY', 'M/D/YY']); - }); - - it('should return a moment.js dateTimeUs format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeUs); - expect(output).toBe('MM/DD/YYYY HH:mm:ss'); - }); - - it('should return a moment.js dateTimeShortUs format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeShortUs); - expect(output).toEqual(['MM/DD/YYYY HH:mm', 'M/D/YYYY HH:mm']); - }); - - it('should return a moment.js dateTimeUsAmPm format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeUsAmPm); - expect(output).toBe('MM/DD/YYYY hh:mm:ss a'); - }); - - it('should return a moment.js dateTimeUsAM_PM format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeUsAM_PM); - expect(output).toBe('MM/DD/YYYY hh:mm:ss A'); - }); - - it('should return a moment.js dateTimeUsShort format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeUsShort); - expect(output).toEqual(['MM/DD/YY H:m:s', 'M/D/YY H:m:s']); - }); - - it('should return a moment.js dateTimeUsShortAmPm format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateTimeUsShortAmPm); - expect(output).toEqual(['MM/DD/YY h:m:s a', 'M/D/YY h:m:s a']); - }); - - it('should return a moment.js dateUtc format', () => { - const output = mapMomentDateFormatWithFieldType(FieldType.dateUtc); - expect(output).toBe('YYYY-MM-DDTHH:mm:ss.SSSZ'); - }); - - it('should return a moment.js date/dateIso format', () => { - const output1 = mapMomentDateFormatWithFieldType(FieldType.date); - const output2 = mapMomentDateFormatWithFieldType(FieldType.dateIso); - expect(output1).toBe('YYYY-MM-DD'); - expect(output2).toBe('YYYY-MM-DD'); - }); - }); - describe('mapOperatorType method', () => { it('should return OperatoryType associated to "<"', () => { const expectation = OperatorType.lessThan; @@ -1121,25 +1003,6 @@ describe('Service/Utilies', () => { }); }); - describe('parseUtcDate method', () => { - it('should return null when date provided is not an ISO date (date only accepted)', () => { - const input1 = '2012-01-01 02:02:02'; - const input2 = '2012-01-01T02:02:02Z'; - - const output1 = parseUtcDate(input1); - const output2 = parseUtcDate(input2); - - expect(output1).toBe(''); - expect(output2).toBe(''); - }); - - it('should return a date parsed as UTC when input is a date (without time) of ISO format', () => { - const input = '2012-01-01'; - const output = parseUtcDate(input, true); - expect(output).toBe('2012-01-01T00:00:00Z'); - }); - }); - describe('thousandSeparatorFormatted method', () => { it('should return original value when input provided is null', () => { const input = null as any; diff --git a/packages/common/src/services/dateUtils.ts b/packages/common/src/services/dateUtils.ts new file mode 100644 index 000000000..6060ef312 --- /dev/null +++ b/packages/common/src/services/dateUtils.ts @@ -0,0 +1,173 @@ +import { format, parse, tzDate } from '@formkit/tempo'; + +import { FieldType } from '../enums/index'; + +/** + * From a Date FieldType, return it's equivalent TempoJS format + * refer to TempoJS for the format standard used: https://tempo.formkit.com/#format + * @param fieldType + * @param withZeroPadding - should we include zero padding in format (e.g.: 03:04:54) + */ +export function mapTempoDateFormatWithFieldType(fieldType: typeof FieldType[keyof typeof FieldType], withZeroPadding = false): string { + let map: string; + switch (fieldType) { + case FieldType.dateTime: + case FieldType.dateTimeIso: + map = 'YYYY-MM-DD HH:mm:ss'; + break; + case FieldType.dateTimeIsoAmPm: + map = 'YYYY-MM-DD hh:mm:ss a'; + break; + case FieldType.dateTimeIsoAM_PM: + map = 'YYYY-MM-DD hh:mm:ss A'; + break; + case FieldType.dateTimeShortIso: + map = 'YYYY-MM-DD HH:mm'; + break; + // all Euro Formats (date/month/year) + case FieldType.dateEuro: + map = 'DD/MM/YYYY'; + break; + case FieldType.dateEuroShort: + map = withZeroPadding + ? 'DD/MM/YY' + : 'D/M/YY'; + break; + case FieldType.dateTimeEuro: + map = 'DD/MM/YYYY HH:mm:ss'; + break; + case FieldType.dateTimeShortEuro: + map = withZeroPadding + ? 'DD/MM/YYYY HH:mm' + : 'D/M/YYYY H:m'; + break; + case FieldType.dateTimeEuroAmPm: + map = 'DD/MM/YYYY hh:mm:ss a'; + break; + case FieldType.dateTimeEuroAM_PM: + map = 'DD/MM/YYYY hh:mm:ss A'; + break; + case FieldType.dateTimeEuroShort: + map = withZeroPadding + ? 'DD/MM/YY HH:mm:ss' + : 'D/M/YY H:m:s'; + break; + case FieldType.dateTimeEuroShortAmPm: + map = withZeroPadding + ? 'DD/MM/YY hh:mm:ss a' + : 'D/M/YY h:m:s a'; + break; + case FieldType.dateTimeEuroShortAM_PM: + map = withZeroPadding + ? 'DD/MM/YY hh:mm:ss A' + : 'D/M/YY h:m:s A'; + break; + // all US Formats (month/date/year) + case FieldType.dateUs: + map = 'MM/DD/YYYY'; + break; + case FieldType.dateUsShort: + map = withZeroPadding + ? 'MM/DD/YY' + : 'M/D/YY'; + break; + case FieldType.dateTimeUs: + map = 'MM/DD/YYYY HH:mm:ss'; + break; + case FieldType.dateTimeUsAmPm: + map = 'MM/DD/YYYY hh:mm:ss a'; + break; + case FieldType.dateTimeUsAM_PM: + map = 'MM/DD/YYYY hh:mm:ss A'; + break; + case FieldType.dateTimeUsShort: + map = withZeroPadding + ? 'MM/DD/YY HH:mm:ss' + : 'M/D/YY H:m:s'; + break; + case FieldType.dateTimeUsShortAmPm: + map = withZeroPadding + ? 'MM/DD/YY hh:mm:ss a' + : 'M/D/YY h:m:s a'; + break; + case FieldType.dateTimeUsShortAM_PM: + map = withZeroPadding + ? 'MM/DD/YY hh:mm:ss A' + : 'M/D/YY h:m:s A'; + break; + case FieldType.dateTimeShortUs: + map = withZeroPadding + ? 'MM/DD/YYYY HH:mm' + : 'M/D/YYYY H:m'; + break; + case FieldType.dateUtc: + map = 'ISO8601'; + break; + case FieldType.date: + case FieldType.dateIso: + default: + map = 'YYYY-MM-DD'; + break; + } + return map; +} + +export function formatTempoDateByFieldType(inputDate: Date | string, inputFieldType: typeof FieldType[keyof typeof FieldType] | undefined, outputFieldType: typeof FieldType[keyof typeof FieldType]): string { + const inputFormat = inputFieldType ? mapTempoDateFormatWithFieldType(inputFieldType) : undefined; + const outputFormat = mapTempoDateFormatWithFieldType(outputFieldType); + const date = inputDate instanceof Date ? inputDate : tryParseDate(inputDate, inputFormat as string); + + if (date && inputDate !== undefined) { + if (outputFieldType === FieldType.dateUtc) { + return date.toISOString(); + } + return format(date, Array.isArray(outputFormat) ? outputFormat[0] : outputFormat); + } + return ''; +} + +export function tryParseDate(inputDate?: string | Date, inputFormat?: string, strict = false): Date | false { + try { + if (!inputDate) { + return false; + } + return inputDate instanceof Date ? inputDate : parse({ + date: inputDate, + format: inputFormat as string, + dateOverflow: strict ? 'throw' : 'backward', + locale: 'en' + }); + } catch (_e) { + return false; + } +} + +export function toUtcDate(inputDate: Date) { + const localOffset = new Date().getTimezoneOffset() * 60 * 1000; + return new Date(inputDate.getTime() + localOffset); +}; + +export function toUtc(inputDate: Date) { + return new Date(Date.UTC(inputDate.getUTCFullYear(), inputDate.getUTCMonth(), + inputDate.getUTCDate(), inputDate.getUTCHours(), + inputDate.getUTCMinutes(), inputDate.getUTCSeconds())); +} +/** + * Parse a date passed as a string (Date only, without time) and return a TZ Date (without milliseconds) + * @param inputDateString + * @returns TZ UTC date formatted + */ +export function parseUtcDate(inputDateString: string): string { + let outputFormattedDate = ''; + + if (typeof inputDateString === 'string' && /^[0-9\-/]*$/.test(inputDateString)) { + // get the UTC datetime but make sure to decode the value so that it's valid text + const dateString = decodeURIComponent(inputDateString); + const date = tzDate(dateString, 'utc'); + if (date) { + outputFormattedDate = date.toISOString().replace(/(.*)([.\d]{4})(Z)/gi, '$1$3'); + } + } + + return outputFormattedDate; +} \ No newline at end of file diff --git a/packages/common/src/services/filter.service.ts b/packages/common/src/services/filter.service.ts index 159701e05..996fc07c2 100644 --- a/packages/common/src/services/filter.service.ts +++ b/packages/common/src/services/filter.service.ts @@ -393,7 +393,7 @@ export class FilterService { /** * Loop through each form input search filter and parse their searchTerms, - * for example a CompoundDate Filter will be parsed as a Moment object. + * for example a CompoundDate Filter will be parsed as a Date object. * Also if we are dealing with a text filter input, * an operator can optionally be part of the filter itself and we need to extract it from there, * for example a filter of "John*" will be analyzed as { operator: StartsWith, searchTerms: ['John'] } @@ -570,7 +570,7 @@ export class FilterService { delete (treeObj as any)[inputItem[primaryDataId]].__used; }); - // Step 1. prepare search filter by getting their parsed value(s), for example if it's a date filter then parse it to a Moment object + // Step 1. prepare search filter by getting their parsed value(s), for example if it's a date filter then parse it to a Date object // loop through all column filters once and get parsed filter search value then save a reference in the columnFilter itself // it is much more effective to do it outside and prior to Step 2 so that we don't re-parse search filter for no reason while checking every row if (typeof columnFilters === 'object') { diff --git a/packages/common/src/services/index.ts b/packages/common/src/services/index.ts index dd3701f89..9d1ed0024 100644 --- a/packages/common/src/services/index.ts +++ b/packages/common/src/services/index.ts @@ -1,6 +1,7 @@ export * from './backendUtility.service'; export * from './collection.service'; export * from './container.service'; +export * from './dateUtils'; export * from './domUtilities'; export * from './excelExport.service'; export * from './extension.service'; diff --git a/packages/common/src/services/utilities.ts b/packages/common/src/services/utilities.ts index 32feaf5d8..d26b97a86 100644 --- a/packages/common/src/services/utilities.ts +++ b/packages/common/src/services/utilities.ts @@ -1,6 +1,5 @@ import type { EventSubscription } from '@slickgrid-universal/event-pub-sub'; import { flatten } from 'un-flatten-tree'; -import moment, { type Moment } from 'moment-tiny'; import { Constants } from '../constants'; import { FieldType, type OperatorString, OperatorType } from '../enums/index'; @@ -399,109 +398,6 @@ export function isColumnDateType(fieldType: typeof FieldType[keyof typeof FieldT } } -export function formatDateByFieldType(inputDate: Date | string | Moment, inputFieldType: typeof FieldType[keyof typeof FieldType] | undefined, outputFieldType: typeof FieldType[keyof typeof FieldType]): string { - const inputFormat = inputFieldType ? mapMomentDateFormatWithFieldType(inputFieldType) : undefined; - const outputFormat = mapMomentDateFormatWithFieldType(outputFieldType); - const momentDate = (inputDate instanceof moment ? inputDate : moment(inputDate, inputFormat)) as Moment; - - if (momentDate.isValid() && inputDate !== undefined) { - if (outputFieldType === FieldType.dateUtc) { - return momentDate.toISOString(); - } - return momentDate.format(Array.isArray(outputFormat) ? outputFormat[0] : outputFormat); - } - return ''; -} - -/** - * From a Date FieldType, return it's equivalent moment.js format - * refer to moment.js for the format standard used: https://momentjs.com/docs/#/parsing/string-format/ - * @param fieldType - */ -export function mapMomentDateFormatWithFieldType(fieldType: typeof FieldType[keyof typeof FieldType]): string | string[] { - let map: string | string[]; - switch (fieldType) { - case FieldType.dateTime: - case FieldType.dateTimeIso: - map = 'YYYY-MM-DD HH:mm:ss'; - break; - case FieldType.dateTimeIsoAmPm: - map = 'YYYY-MM-DD hh:mm:ss a'; - break; - case FieldType.dateTimeIsoAM_PM: - map = 'YYYY-MM-DD hh:mm:ss A'; - break; - case FieldType.dateTimeShortIso: - map = 'YYYY-MM-DD HH:mm'; - break; - // all Euro Formats (date/month/year) - case FieldType.dateEuro: - map = 'DD/MM/YYYY'; - break; - case FieldType.dateEuroShort: - map = ['DD/MM/YY', 'D/M/YY']; - break; - case FieldType.dateTimeEuro: - map = 'DD/MM/YYYY HH:mm:ss'; - break; - case FieldType.dateTimeShortEuro: - map = ['DD/MM/YYYY HH:mm', 'D/M/YYYY HH:mm']; - break; - case FieldType.dateTimeEuroAmPm: - map = 'DD/MM/YYYY hh:mm:ss a'; - break; - case FieldType.dateTimeEuroAM_PM: - map = 'DD/MM/YYYY hh:mm:ss A'; - break; - case FieldType.dateTimeEuroShort: - map = ['DD/MM/YY H:m:s', 'D/M/YY H:m:s']; - break; - case FieldType.dateTimeEuroShortAmPm: - map = ['DD/MM/YY h:m:s a', 'D/M/YY h:m:s a']; - break; - case FieldType.dateTimeEuroShortAM_PM: - map = ['DD/MM/YY h:m:s A', 'D/M/YY h:m:s A']; - break; - // all US Formats (month/date/year) - case FieldType.dateUs: - map = 'MM/DD/YYYY'; - break; - case FieldType.dateUsShort: - map = ['MM/DD/YY', 'M/D/YY']; - break; - case FieldType.dateTimeUs: - map = 'MM/DD/YYYY HH:mm:ss'; - break; - case FieldType.dateTimeUsAmPm: - map = 'MM/DD/YYYY hh:mm:ss a'; - break; - case FieldType.dateTimeUsAM_PM: - map = 'MM/DD/YYYY hh:mm:ss A'; - break; - case FieldType.dateTimeUsShort: - map = ['MM/DD/YY H:m:s', 'M/D/YY H:m:s']; - break; - case FieldType.dateTimeUsShortAmPm: - map = ['MM/DD/YY h:m:s a', 'M/D/YY h:m:s a']; - break; - case FieldType.dateTimeUsShortAM_PM: - map = ['MM/DD/YY h:m:s A', 'M/D/YY h:m:s A']; - break; - case FieldType.dateTimeShortUs: - map = ['MM/DD/YYYY HH:mm', 'M/D/YYYY HH:mm']; - break; - case FieldType.dateUtc: - map = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; - break; - case FieldType.date: - case FieldType.dateIso: - default: - map = 'YYYY-MM-DD'; - break; - } - return map; -} - /** * Mapper for query operators (ex.: <= is "le", > is "gt") * @param string operator @@ -666,26 +562,6 @@ export function objectWithoutKey(obj: T, omitKey: keyof T): T { }, {}) as unknown as T; } -/** - * Parse a date passed as a string (Date only, without time) and return a Date object (if valid) - * @param inputDateString - * @returns string date formatted - */ -export function parseUtcDate(inputDateString: any, useUtc?: boolean): string { - let date = ''; - - if (typeof inputDateString === 'string' && /^[0-9\-/]*$/.test(inputDateString)) { - // get the UTC datetime with moment.js but we need to decode the value so that it's valid text - const dateString = decodeURIComponent(inputDateString); - const dateMoment = moment(new Date(dateString)); - if (dateMoment.isValid() && dateMoment.year().toString().length === 4) { - date = (useUtc) ? dateMoment.utc().format() : dateMoment.format(); - } - } - - return date; -} - /** * Format a number or a string into a string that is separated every thousand, * the default separator is a comma but user can optionally pass a different one diff --git a/packages/common/src/sortComparers/dateUtilities.ts b/packages/common/src/sortComparers/dateUtilities.ts index 2ebcffa98..2a03bb397 100644 --- a/packages/common/src/sortComparers/dateUtilities.ts +++ b/packages/common/src/sortComparers/dateUtilities.ts @@ -1,28 +1,26 @@ -import moment, { type Moment, type MomentBuiltinFormat } from 'moment-tiny'; - import { FieldType } from '../enums/fieldType.enum'; import type { SortComparer } from '../interfaces/index'; -import { mapMomentDateFormatWithFieldType } from '../services/utilities'; +import { mapTempoDateFormatWithFieldType, tryParseDate } from '../services/dateUtils'; -export function compareDates(value1: any, value2: any, sortDirection: number, format?: string | string[] | MomentBuiltinFormat, strict?: boolean) { +export function compareDates(value1: any, value2: any, sortDirection: number, format?: string, strict?: boolean) { let diff = 0; if (value1 === value2) { diff = 0; } else { - // use moment to validate the date - let date1: Moment | Date = moment(value1, format, strict); - let date2: Moment | Date = moment(value2, format, strict); + // try to parse the Date and validate it + let date1: Date | boolean = tryParseDate(value1, format, strict); + let date2: Date | boolean = tryParseDate(value2, format, strict); - // when moment date is invalid, we'll create a temporary old Date - if (!(date1 as Moment).isValid()) { + // when date is invalid (false), we'll create a temporary old Date + if (!date1) { date1 = new Date(1001, 1, 1); } - if (!(date2 as Moment).isValid()) { + if (!date2) { date2 = new Date(1001, 1, 1); } - // we can use valueOf on both moment & Date to sort + // we can use Date valueOf to sort diff = date1.valueOf() - date2.valueOf(); } @@ -31,7 +29,7 @@ export function compareDates(value1: any, value2: any, sortDirection: number, fo /** From a FieldType, return the associated Date SortComparer */ export function getAssociatedDateSortComparer(fieldType: typeof FieldType[keyof typeof FieldType]): SortComparer { - const FORMAT = (fieldType === FieldType.date) ? undefined : mapMomentDateFormatWithFieldType(fieldType); + const FORMAT = (fieldType === FieldType.date) ? undefined : mapTempoDateFormatWithFieldType(fieldType); return ((value1: any, value2: any, sortDirection: number) => { if (FORMAT === undefined) { diff --git a/packages/common/src/sortComparers/sortComparers.index.ts b/packages/common/src/sortComparers/sortComparers.index.ts index 6d08376a1..0256c7b90 100644 --- a/packages/common/src/sortComparers/sortComparers.index.ts +++ b/packages/common/src/sortComparers/sortComparers.index.ts @@ -12,7 +12,7 @@ export const SortComparers = { /** SortComparer method to sort values as regular strings */ boolean: booleanSortComparer, - /** SortComparer method to sort values by Date object type (uses Moment.js ISO_8601 standard format, optionally include time) */ + /** SortComparer method to sort values by Date object type (uses Tempo ISO_8601 standard format, optionally include time) */ date: getAssociatedDateSortComparer(FieldType.date), /** diff --git a/packages/custom-footer-component/package.json b/packages/custom-footer-component/package.json index 39e9372b4..c6d4bd404 100644 --- a/packages/custom-footer-component/package.json +++ b/packages/custom-footer-component/package.json @@ -49,9 +49,9 @@ "not dead" ], "dependencies": { + "@formkit/tempo": "^0.1.1", "@slickgrid-universal/binding": "workspace:~", - "@slickgrid-universal/common": "workspace:~", - "moment-tiny": "^2.30.4" + "@slickgrid-universal/common": "workspace:~" }, "devDependencies": { "@slickgrid-universal/event-pub-sub": "workspace:~" diff --git a/packages/custom-footer-component/src/slick-footer.component.ts b/packages/custom-footer-component/src/slick-footer.component.ts index d485ca9e2..6a8bea6a2 100644 --- a/packages/custom-footer-component/src/slick-footer.component.ts +++ b/packages/custom-footer-component/src/slick-footer.component.ts @@ -1,4 +1,4 @@ -import moment from 'moment-tiny'; +import { format } from '@formkit/tempo'; import type { CustomFooterOption, GridOption, @@ -110,7 +110,7 @@ export class SlickFooterComponent { /** Render element attribute values */ renderMetrics(metrics: Metrics) { // get translated text & last timestamp - const lastUpdateTimestamp = moment(metrics.endTime).format(this.customFooterOptions.dateFormat); + const lastUpdateTimestamp = metrics?.endTime ? format(metrics.endTime, this.customFooterOptions.dateFormat) : ''; this._bindingHelper.setElementAttributeValue('span.last-update-timestamp', 'textContent', lastUpdateTimestamp); this._bindingHelper.setElementAttributeValue('span.item-count', 'textContent', metrics.itemCount); this._bindingHelper.setElementAttributeValue('span.total-count', 'textContent', metrics.totalItemCount); @@ -233,7 +233,7 @@ export class SlickFooterComponent { protected createFooterLastUpdate(): HTMLSpanElement { // get translated text & last timestamp const lastUpdateText = this.customFooterOptions?.metricTexts?.lastUpdate ?? 'Last Update'; - const lastUpdateTimestamp = moment(this.metrics?.endTime).format(this.customFooterOptions.dateFormat); + const lastUpdateTimestamp = this.metrics?.endTime ? format(this.metrics?.endTime, this.customFooterOptions.dateFormat) : ''; const lastUpdateContainerElm = createDomElement('span'); lastUpdateContainerElm.appendChild(createDomElement('span', { className: 'text-last-update', textContent: lastUpdateText })); diff --git a/packages/custom-footer-component/src/slick-footer.spec.ts b/packages/custom-footer-component/src/slick-footer.spec.ts index 1c616d6f8..72bda67ac 100644 --- a/packages/custom-footer-component/src/slick-footer.spec.ts +++ b/packages/custom-footer-component/src/slick-footer.spec.ts @@ -113,7 +113,7 @@ describe('Slick-Footer Component', () => { expect(leftFooterElm.innerHTML).toBe(''); expect(removeExtraSpaces(rightFooterElm.innerHTML)).toBe(removeExtraSpaces( `some last update - 2019-05-03, 12:00:01am + 2019-05-03, 12:00:01a.m. | 7some of99 some items`)); @@ -181,7 +181,7 @@ describe('Slick-Footer Component', () => { expect(leftFooterElm.innerHTML).toBe(''); expect(removeExtraSpaces(rightFooterElm.innerHTML)).toBe(removeExtraSpaces( `Last Update - 2019-05-03, 12:00:01am + 2019-05-03, 12:00:01a.m. | 7of99 items`)); @@ -215,7 +215,7 @@ describe('Slick-Footer Component', () => { expect(leftFooterElm.innerHTML).toBe(''); expect(removeExtraSpaces(rightFooterElm.innerHTML)).toBe(removeExtraSpaces( `Dernière mise à jour - 2019-05-03, 12:00:01am | + 2019-05-03, 12:00:01a.m. | 7de99 éléments`)); }); @@ -254,7 +254,7 @@ describe('Slick-Footer Component', () => { expect(rightFooterElm).toBeTruthy(); expect(leftFooterElm.innerHTML).toBe('custom left footer text'); expect(removeExtraSpaces(rightFooterElm.innerHTML)).toBe(removeExtraSpaces( - `some last update2019-05-03, 12:00:01am | + `some last update2019-05-03, 12:00:01a.m. | 7some of99 some items`)); }); @@ -277,7 +277,7 @@ describe('Slick-Footer Component', () => { expect(leftFooterElm.innerHTML).toBe('1 items selected'); expect(removeExtraSpaces(rightFooterElm.innerHTML)).toBe(removeExtraSpaces( `some last update - 2019-05-03, 12:00:01am | + 2019-05-03, 12:00:01a.m. | 7some of99 some items`)); @@ -304,7 +304,7 @@ describe('Slick-Footer Component', () => { expect(leftFooterElm.innerHTML).toBe(''); expect(removeExtraSpaces(rightFooterElm.innerHTML)).toBe(removeExtraSpaces( `some last update - 2019-05-03, 12:00:01am | + 2019-05-03, 12:00:01a.m. | 7some of99 some items`)); }); diff --git a/packages/excel-export/package.json b/packages/excel-export/package.json index 6ab0cdf68..4dc765aa2 100644 --- a/packages/excel-export/package.json +++ b/packages/excel-export/package.json @@ -51,8 +51,7 @@ "dependencies": { "@slickgrid-universal/common": "workspace:~", "@slickgrid-universal/utils": "workspace:~", - "excel-builder-vanilla": "^3.0.1", - "moment-tiny": "^2.30.4" + "excel-builder-vanilla": "^3.0.1" }, "devDependencies": { "@slickgrid-universal/event-pub-sub": "workspace:~" diff --git a/packages/graphql/package.json b/packages/graphql/package.json index d534f1499..3851d696e 100644 --- a/packages/graphql/package.json +++ b/packages/graphql/package.json @@ -52,9 +52,6 @@ "@slickgrid-universal/common": "workspace:~", "@slickgrid-universal/utils": "workspace:~" }, - "devDependencies": { - "moment-tiny": "^2.30.4" - }, "funding": { "type": "ko_fi", "url": "https://ko-fi.com/ghiscoding" diff --git a/packages/graphql/src/services/__tests__/graphqlQueryBuilder.spec.ts b/packages/graphql/src/services/__tests__/graphqlQueryBuilder.spec.ts index 96c5cbf65..d009fb0f7 100644 --- a/packages/graphql/src/services/__tests__/graphqlQueryBuilder.spec.ts +++ b/packages/graphql/src/services/__tests__/graphqlQueryBuilder.spec.ts @@ -1,4 +1,3 @@ -import moment from 'moment-tiny'; import GraphqlQueryBuilder from '../graphqlQueryBuilder'; function removeSpaces(textS) { @@ -81,8 +80,8 @@ describe('GraphqlQueryBuilder', () => { it('should be able to Query a Date field', () => { const now = new Date(); - const expectation = `FetchLeeAndSam { lee: user(modified: "${moment(now).toISOString()}") { name, modified }, - sam: user(modified: "${moment(now).toISOString()}") { name, modified } }`; + const expectation = `FetchLeeAndSam { lee: user(modified: "${now.toISOString()}") { name, modified }, + sam: user(modified: "${now.toISOString()}") { name, modified } }`; const fetchLeeAndSam = new GraphqlQueryBuilder('FetchLeeAndSam'); diff --git a/packages/odata/src/services/grid-odata.service.ts b/packages/odata/src/services/grid-odata.service.ts index f54cace12..ee60952c1 100644 --- a/packages/odata/src/services/grid-odata.service.ts +++ b/packages/odata/src/services/grid-odata.service.ts @@ -323,7 +323,7 @@ export class GridOdataService implements BackendService { fieldName = stripTags(fieldName.innerHTML); } const fieldType = columnDef.type || FieldType.string; - let searchTerms = (columnFilter && columnFilter.searchTerms ? [...columnFilter.searchTerms] : null) || []; + let searchTerms = (columnFilter?.searchTerms ? [...columnFilter.searchTerms] : null) || []; let fieldSearchValue = (Array.isArray(searchTerms) && searchTerms.length === 1) ? searchTerms[0] : ''; if (typeof fieldSearchValue === 'undefined') { fieldSearchValue = ''; @@ -643,7 +643,7 @@ export class GridOdataService implements BackendService { protected normalizeSearchValue(fieldType: typeof FieldType[keyof typeof FieldType], searchValue: any, version: number) { switch (fieldType) { case FieldType.date: - searchValue = parseUtcDate(searchValue as string, true); + searchValue = parseUtcDate(searchValue as string); searchValue = version >= 4 ? searchValue : `DateTime'${searchValue}'`; break; case FieldType.string: diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f65dfe597..baef0f867 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: '@commitlint/config-conventional': specifier: ^19.2.2 version: 19.2.2 + '@formkit/tempo': + specifier: ^0.1.1 + version: 0.1.1 '@jest/types': specifier: ^29.6.3 version: 29.6.3 @@ -86,9 +89,6 @@ importers: jsdom-global: specifier: ^3.0.2 version: 3.0.2(jsdom@24.0.0) - moment-tiny: - specifier: ^2.30.4 - version: 2.30.4 npm-run-all2: specifier: ^6.1.2 version: 6.1.2 @@ -134,6 +134,9 @@ importers: '@fnando/sparkline': specifier: ^0.3.10 version: 0.3.10 + '@formkit/tempo': + specifier: ^0.1.1 + version: 0.1.1 '@slickgrid-universal/binding': specifier: workspace:~ version: link:../../packages/binding @@ -179,9 +182,6 @@ importers: fetch-jsonp: specifier: ^1.3.0 version: 1.3.0 - moment-tiny: - specifier: ^2.30.4 - version: 2.30.4 multiple-select-vanilla: specifier: ^3.1.3 version: 3.1.3 @@ -222,6 +222,9 @@ importers: packages/common: dependencies: + '@formkit/tempo': + specifier: ^0.1.1 + version: 0.1.1 '@slickgrid-universal/binding': specifier: workspace:~ version: link:../binding @@ -246,9 +249,6 @@ importers: excel-builder-vanilla: specifier: 3.0.1 version: 3.0.1 - moment-tiny: - specifier: ^2.30.4 - version: 2.30.4 multiple-select-vanilla: specifier: ^3.1.3 version: 3.1.3 @@ -301,15 +301,15 @@ importers: packages/custom-footer-component: dependencies: + '@formkit/tempo': + specifier: ^0.1.1 + version: 0.1.1 '@slickgrid-universal/binding': specifier: workspace:~ version: link:../binding '@slickgrid-universal/common': specifier: workspace:~ version: link:../common - moment-tiny: - specifier: ^2.30.4 - version: 2.30.4 devDependencies: '@slickgrid-universal/event-pub-sub': specifier: workspace:~ @@ -351,9 +351,6 @@ importers: excel-builder-vanilla: specifier: ^3.0.1 version: 3.0.1 - moment-tiny: - specifier: ^2.30.4 - version: 2.30.4 devDependencies: '@slickgrid-universal/event-pub-sub': specifier: workspace:~ @@ -367,10 +364,6 @@ importers: '@slickgrid-universal/utils': specifier: workspace:~ version: link:../utils - devDependencies: - moment-tiny: - specifier: ^2.30.4 - version: 2.30.4 packages/odata: dependencies: @@ -1350,6 +1343,9 @@ packages: resolution: {integrity: sha512-Rwz2swatdSU5F4sCOvYG8EOWdjtLgq5d8nmnqlZ3PXdWJI9Zq9BRUvJ/9ygjajJG8qOyNpMFX3GEVFjZIuB1Jg==} dev: false + /@formkit/tempo@0.1.1: + resolution: {integrity: sha512-nepRwKnCIjukLkblqh339sSXYI6P3cqktF/T7ECj3vURW+Pd+YzTv/I/lwjRqPGmlQJG93LORodIAkvgoExIAg==} + /@humanwhocodes/config-array@0.13.0: resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} @@ -6893,9 +6889,6 @@ packages: hasBin: true dev: true - /moment-tiny@2.30.4: - resolution: {integrity: sha512-8m0gGjP9pfhzYPL07NJrnS5qHJIE9x/cVlI3no2X9QI0r/vDHKJ4aVf7D+eUxSryMXZKr5P3hDBStmesr6wzhA==} - /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} dev: true diff --git a/test/cypress/e2e/example02.cy.ts b/test/cypress/e2e/example02.cy.ts index 773bab389..6f87492e0 100644 --- a/test/cypress/e2e/example02.cy.ts +++ b/test/cypress/e2e/example02.cy.ts @@ -1,4 +1,5 @@ -import moment from 'moment-tiny'; +import { format } from '@formkit/tempo'; + import { removeExtraSpaces } from '../plugins/utilities'; describe('Example 02 - Grouping & Aggregators', () => { @@ -37,7 +38,7 @@ describe('Example 02 - Grouping & Aggregators', () => { .find('.right-footer') .should($span => { const text = removeExtraSpaces($span.text()); // remove all white spaces - expect(text).to.eq(`Last Update ${moment().format('YYYY-MM-DD, hh:mm a')} | 500 of 500 items`); + expect(text).to.eq(`Last Update ${format(new Date(), 'YYYY-MM-DD, hh:mm a')} | 500 of 500 items`); }); }); @@ -51,7 +52,7 @@ describe('Example 02 - Grouping & Aggregators', () => { .find('.right-footer') .should($span => { const text = removeExtraSpaces($span.text()); // remove all white spaces - expect(text).to.eq(`Last Update ${moment().format('YYYY-MM-DD, hh:mm a')} | 176 of 500 items`); + expect(text).to.eq(`Last Update ${format(new Date(), 'YYYY-MM-DD, hh:mm a')} | 176 of 500 items`); }); }); @@ -63,7 +64,7 @@ describe('Example 02 - Grouping & Aggregators', () => { .find('span:nth(1)') .click(); - const currentDateTime = moment().format('YYYY-MM-DD, hh:mm a'); + const currentDateTime = format(new Date(), 'YYYY-MM-DD, hh:mm a'); cy.get('.grid2') .find('.slick-custom-footer') .find('.right-footer') @@ -86,7 +87,7 @@ describe('Example 02 - Grouping & Aggregators', () => { .find('.right-footer') .should($span => { const text = removeExtraSpaces($span.text()); // remove all white spaces - expect(text).to.eq(`Last Update ${moment().format('YYYY-MM-DD, hh:mm a')} | 148 of 500 items`); + expect(text).to.eq(`Last Update ${format(new Date(), 'YYYY-MM-DD, hh:mm a')} | 148 of 500 items`); }); }); @@ -103,7 +104,7 @@ describe('Example 02 - Grouping & Aggregators', () => { .find('.right-footer') .should($span => { const text = removeExtraSpaces($span.text()); // remove all white spaces - expect(text).to.eq(`Last Update ${moment().format('YYYY-MM-DD, hh:mm a')} | 176 of 500 items`); + expect(text).to.eq(`Last Update ${format(new Date(), 'YYYY-MM-DD, hh:mm a')} | 176 of 500 items`); }); cy.get('.ms-drop').should('contain', ''); @@ -181,7 +182,7 @@ describe('Example 02 - Grouping & Aggregators', () => { cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"].slick-group-level-2 .slick-group-toggle.collapsed`).should('have.length', 1); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"].slick-group-level-2 .slick-group-title`).contains(/^% Complete: [0-9]/); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"].slick-group-totals.slick-group-level-2 .slick-cell:nth(3)`).contains(/^Avg: [0-9]\%$/); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"].slick-group-totals.slick-group-level-2 .slick-cell:nth(3)`).contains(/^Avg: [0-9]%$/); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"].slick-group-totals.slick-group-level-2`) .find('.slick-cell:nth(3)').contains('Avg: '); }); diff --git a/test/cypress/e2e/example03.cy.ts b/test/cypress/e2e/example03.cy.ts index 5b79063cc..7a0cdddb1 100644 --- a/test/cypress/e2e/example03.cy.ts +++ b/test/cypress/e2e/example03.cy.ts @@ -63,7 +63,7 @@ describe('Example 03 - Draggable Grouping', () => { cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); }); - it('should collapse all rows and make sure Duration group is sorted in descending order', () => { + it('should collapse all rows again and make sure Duration group is sorted in descending order', () => { cy.get('.slick-preheader-panel .slick-group-toggle-all').click(); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); diff --git a/test/cypress/e2e/example04.cy.ts b/test/cypress/e2e/example04.cy.ts index 1ae72e0f3..df67f17d4 100644 --- a/test/cypress/e2e/example04.cy.ts +++ b/test/cypress/e2e/example04.cy.ts @@ -173,7 +173,7 @@ describe('Example 04 - Frozen Grid', () => { cy.get('.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(4)').should('contain', '2009-05-05'); }); - it('should have exact Column Header Titles in the grid', () => { + it('should expect to have exact Column Header Titles in the grid', () => { cy.get('.grid4') .find('.slick-header-columns:nth(0)') .children() @@ -195,7 +195,7 @@ describe('Example 04 - Frozen Grid', () => { cy.get('.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(1)').should('contain', '2009-05-05'); }); - it('should have exact Column Header Titles in the grid', () => { + it('should recheck again and still have exact Column Header Titles in the grid', () => { cy.get('.grid4') .find('.slick-header-columns:nth(0)') .children() diff --git a/test/cypress/e2e/example05.cy.ts b/test/cypress/e2e/example05.cy.ts index 3abe612de..0e2baaa99 100644 --- a/test/cypress/e2e/example05.cy.ts +++ b/test/cypress/e2e/example05.cy.ts @@ -189,7 +189,7 @@ describe('Example 05 - Tree Data (from a flat dataset with parentId references)' } }); - it('should open the Grid Menu "Clear all Filters" command', () => { + it('should open the Grid Menu "Clear all Filters" command a second time', () => { cy.get('.grid5') .find('button.slick-grid-menu-button') .trigger('click') @@ -261,7 +261,7 @@ describe('Example 05 - Tree Data (from a flat dataset with parentId references)' .contains(/^((?!Task 500).)*$/); }); - it('should open the Grid Menu "Clear all Filters" command', () => { + it('should open the Grid Menu "Clear all Filters" command again', () => { cy.get('.grid5') .find('button.slick-grid-menu-button') .trigger('click') diff --git a/test/cypress/e2e/example06.cy.ts b/test/cypress/e2e/example06.cy.ts index 471e36bc1..412e9a7e3 100644 --- a/test/cypress/e2e/example06.cy.ts +++ b/test/cypress/e2e/example06.cy.ts @@ -348,7 +348,7 @@ describe('Example 06 - Tree Data with Aggregators (from a Hierarchical Dataset)' .click(); }); - it('should have pop songs folder with updated aggregations including 4 pop songs of Sum(400.3MB) / Avg(66.72MB)', () => { + it('should have again the pop songs folder with updated aggregations including 4 pop songs of Sum(400.3MB) / Avg(66.72MB)', () => { cy.get('.slick-viewport-top.slick-viewport-left') .scrollTo('center', { force: true } as any); @@ -415,7 +415,7 @@ describe('Example 06 - Tree Data with Aggregators (from a Hierarchical Dataset)' cy.get('.right-footer .total-count').contains('31'); }); - it('should enable auto-recalc Tree Totals', () => { + it('should re-enable auto-recalc Tree Totals', () => { cy.get('[data-test="clear-filters-btn"]') .click(); }); @@ -440,7 +440,7 @@ describe('Example 06 - Tree Data with Aggregators (from a Hierarchical Dataset)' cy.get('.right-footer .total-count').contains('31'); }); - it('should type filter "b" and expect totals to be updated with a lower Sum(6MB) / Avg(3MB) of only what is displayed', () => { + it('should type filter "b" again and still expect totals to be updated with a lower Sum(6MB) / Avg(3MB) of only what is displayed', () => { cy.get('.search-filter.filter-file') .type('i'); // will become "bi" diff --git a/test/cypress/e2e/example07.cy.ts b/test/cypress/e2e/example07.cy.ts index 113740a5b..7dc0c15bc 100644 --- a/test/cypress/e2e/example07.cy.ts +++ b/test/cypress/e2e/example07.cy.ts @@ -554,7 +554,7 @@ describe('Example 07 - Row Move & Checkbox Selector Selector Plugins', () => { }); }); - it('should expect "Clear all Filters" command to be hidden in the Grid Menu', () => { + it('should expect "Clear all Filters" command to be hidden again in the Grid Menu', () => { const expectedFullHeaderMenuCommands = ['Clear all Filters', 'Clear all Sorting', 'Toggle Filter Row', 'Export to Excel']; cy.get('.grid7') diff --git a/test/cypress/e2e/example08.cy.ts b/test/cypress/e2e/example08.cy.ts index 446557580..4e627c022 100644 --- a/test/cypress/e2e/example08.cy.ts +++ b/test/cypress/e2e/example08.cy.ts @@ -36,7 +36,7 @@ describe('Example 08 - Column Span & Header Grouping', () => { cy.get('.grid2').find(`.grid-canvas-right > [style="top: ${GRID_ROW_HEIGHT * 0}px;"]> .slick-cell:nth(1)`).should('contain', '01/05/2009'); }); - it('should have exact Column Pre-Header & Column Header Titles in the grid', () => { + it('should have exact Column Pre-Header & Column Header Titles in the grid again', () => { cy.get('.grid2') .find('.slick-header-columns:nth(0)') .children() @@ -62,7 +62,7 @@ describe('Example 08 - Column Span & Header Grouping', () => { cy.get('.grid2').find(`.grid-canvas-left > [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(4)`).should('contain', '01/05/2009'); }); - it('should have exact Column Pre-Header & Column Header Titles in the grid', () => { + it('should have exact Column Pre-Header & Column Header Titles in the grid once again', () => { cy.get('.grid2') .find('.slick-header-columns:nth(0)') .children() @@ -90,7 +90,7 @@ describe('Example 08 - Column Span & Header Grouping', () => { cy.get('.grid2').find(`.grid-canvas-right > [style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', '01/05/2009'); }); - it('should have exact Column Pre-Header & Column Header Titles in the grid', () => { + it('should have still exact Column Pre-Header & Column Header Titles in the grid', () => { cy.get('.grid2') .find('.slick-header-columns:nth(0)') .children() diff --git a/test/cypress/e2e/example10.cy.ts b/test/cypress/e2e/example10.cy.ts index 0e9dd16be..141eafe66 100644 --- a/test/cypress/e2e/example10.cy.ts +++ b/test/cypress/e2e/example10.cy.ts @@ -1,11 +1,11 @@ -import moment from 'moment-tiny'; +import { addDay, format } from '@formkit/tempo'; function removeSpaces(textS) { return `${textS}`.replace(/\s+/g, ''); } -const presetLowestDay = moment().add(-2, 'days').format('YYYY-MM-DD'); -const presetHighestDay = moment().add(20, 'days').format('YYYY-MM-DD'); +const presetLowestDay = format(addDay(new Date(), -2), 'YYYY-MM-DD'); +const presetHighestDay = format(addDay(new Date(), 20), 'YYYY-MM-DD'); describe('Example 10 - GraphQL Grid', () => { it('should display Example title', () => { diff --git a/test/cypress/e2e/example11.cy.ts b/test/cypress/e2e/example11.cy.ts index ef133f210..7a52b5e70 100644 --- a/test/cypress/e2e/example11.cy.ts +++ b/test/cypress/e2e/example11.cy.ts @@ -1,4 +1,3 @@ -import moment from 'moment-tiny'; import { changeTimezone, zeroPadding } from '../plugins/utilities'; describe('Example 11 - Batch Editing', () => { @@ -7,7 +6,7 @@ describe('Example 11 - Batch Editing', () => { const EDITABLE_CELL_RGB_COLOR = 'rgba(227, 240, 251, 0.57)'; const UNSAVED_RGB_COLOR = 'rgb(251, 253, 209)'; const fullTitles = ['', 'Title', 'Duration', 'Cost', '% Complete', 'Start', 'Finish', 'Completed', 'Product', 'Country of Origin', 'Action']; - const currentYear = moment().year(); + const currentYear = new Date().getFullYear(); beforeEach(() => { // create a console.log spy for later use @@ -644,7 +643,7 @@ describe('Example 11 - Batch Editing', () => { cy.get('.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(1)').contains(/^TASK [0-9]*$/i); cy.get('.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(2)').contains(/^[0-9]*\sday[s]?$/); - cy.get('.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(0)').contains(/\$[0-9\.]*/); + cy.get('.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(0)').contains(/\$[0-9.]*/); cy.get('.slick-pane-left') .find('.slick-grid-menu-button') @@ -740,7 +739,7 @@ describe('Example 11 - Batch Editing', () => { cy.get('.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(1)').contains(/^TASK [0-9]*$/i); cy.get('.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(2)').contains(/^[0-9]*\sday[s]?$/); - cy.get('.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(0)').contains(/\$?[0-9\.]*/); + cy.get('.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(0)').contains(/\$?[0-9.]*/); cy.get('.slick-pane-left') .find('.slick-grid-menu-button') diff --git a/test/cypress/e2e/example18.cy.ts b/test/cypress/e2e/example18.cy.ts index 93ae93e19..12f7f3b1a 100644 --- a/test/cypress/e2e/example18.cy.ts +++ b/test/cypress/e2e/example18.cy.ts @@ -18,10 +18,10 @@ describe('Example 18 - Real-Time Trading Platform', () => { for (let i = 0; i < 5; i++) { cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(0)`).contains(/CAD|USD$/); cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(4)`).contains(/Buy|Sell$/); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(5)`).contains(/\$\(?[0-9\.]*\)?/); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(6)`).contains(/\$[0-9\.]*/); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(5)`).contains(/\$\(?[0-9.]*\)?/); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(6)`).contains(/\$[0-9.]*/); cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(7)`).contains(/\d$/); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(8)`).contains(/\$[0-9\.]*/); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(8)`).contains(/\$[0-9.]*/); } }); @@ -54,10 +54,10 @@ describe('Example 18 - Real-Time Trading Platform', () => { cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Currency: CAD'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(8)`).contains(/\$[0-9\,\.]*/); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(8)`).contains(/\$[0-9,.]*/); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Currency: USD'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(8)`).contains(/\$[0-9\,\.]*/); + cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(8)`).contains(/\$[0-9,.]*/); }); }); diff --git a/test/cypress/e2e/example19.cy.ts b/test/cypress/e2e/example19.cy.ts index 07e37f9d5..98bd53eee 100644 --- a/test/cypress/e2e/example19.cy.ts +++ b/test/cypress/e2e/example19.cy.ts @@ -184,7 +184,7 @@ describe('Example 19 - ExcelCopyBuffer with Cell Selection', () => { .should('have.text', '{"fromRow":0,"fromCell":0,"toRow":95,"toCell":98}'); }); - it('should click on cell CR5 then Ctrl+Home keys and expect to scroll back to cell A0 without any selection range', () => { + it('should click on cell CR5 again then Ctrl+Home keys and expect to scroll back to cell A0 without any selection range', () => { cy.getCell(5, 95, '', { parentSelector: '.grid19', rowHeight: GRID_ROW_HEIGHT }) .as('cell_CR95') .click(); diff --git a/test/jest-global-mocks.ts b/test/jest-global-mocks.ts index fc87b1809..df4472510 100644 --- a/test/jest-global-mocks.ts +++ b/test/jest-global-mocks.ts @@ -37,9 +37,3 @@ Object.defineProperty(window, 'matchMedia', { dispatchEvent: jest.fn(), })), }); - -// Jest has a hard time with MomentJS because they export as default, to bypass this problem we can mock the require .default -jest.mock('moment-tiny', () => { - const actual = jest.requireActual('moment-tiny'); - return { __esModule: true, ...actual, default: actual }; -}); \ No newline at end of file From 516e71d13693b25446968c7bd4b5954e6a3c7eee Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Sun, 5 May 2024 03:30:56 -0400 Subject: [PATCH 02/10] chore: use "en-US" as locale for consistent (am/pm) and not (a.m./p.m.) --- .../common/src/editors/__tests__/dateEditor.spec.ts | 6 +++--- packages/common/src/editors/dateEditor.ts | 5 +---- .../src/filters/__tests__/compoundDateFilter.spec.ts | 2 +- .../src/filters/__tests__/dateRangeFilter.spec.ts | 10 ++++++++-- packages/common/src/filters/dateFilter.ts | 9 +++------ .../__tests__/dateTimeEuroAM_PMFormatter.spec.ts | 6 +++--- .../__tests__/dateTimeEuroAmPmFormatter.spec.ts | 6 +++--- .../dateTimeEuroShortAM_PMFormatter.spec.ts | 6 +++--- .../__tests__/dateTimeEuroShortAmPmFormatter.spec.ts | 6 +++--- .../__tests__/dateTimeIsoAM_PMFormatter.spec.ts | 6 +++--- .../__tests__/dateTimeIsoAmPmFormatter.spec.ts | 6 +++--- .../__tests__/dateTimeUsAM_PMFormatter.spec.ts | 6 +++--- .../__tests__/dateTimeUsAmPmFormatter.spec.ts | 6 +++--- .../__tests__/dateTimeUsShortAM_PMFormatter.spec.ts | 6 +++--- .../__tests__/dateTimeUsShortAmPmFormatter.spec.ts | 6 +++--- packages/common/src/formatters/formatterUtilities.ts | 7 ++----- packages/common/src/services/dateUtils.ts | 4 ++-- .../src/slick-footer.component.ts | 4 ++-- .../custom-footer-component/src/slick-footer.spec.ts | 12 ++++++------ 19 files changed, 58 insertions(+), 61 deletions(-) diff --git a/packages/common/src/editors/__tests__/dateEditor.spec.ts b/packages/common/src/editors/__tests__/dateEditor.spec.ts index 620ab024e..ac29ef48e 100644 --- a/packages/common/src/editors/__tests__/dateEditor.spec.ts +++ b/packages/common/src/editors/__tests__/dateEditor.spec.ts @@ -364,13 +364,13 @@ describe('DateEditor', () => { mockColumn.field = 'employee.startDate'; mockItemData = { id: 1, employee: { startDate: '2001-04-05T11:33:42.000Z' }, isActive: true }; - const newDate = new Date(Date.UTC(2001, 0, 2, 16, 2, 2, 0)); + const newDate = new Date(Date.UTC(2001, 10, 23, 16, 2, 2, 0)); editor = new DateEditor(editorArguments); jest.runAllTimers(); editor.applyValue(mockItemData, newDate); // @ts-ignore:2349 - expect(mockItemData).toEqual({ id: 1, employee: { startDate: format(newDate, 'DD/MM/YYYY HH:mm') }, isActive: true }); + expect(mockItemData).toEqual({ id: 1, employee: { startDate: format(newDate, 'D/M/YYYY HH:mm') }, isActive: true }); }); it('should apply the value to the startDate property with output format defined by "saveOutputType" when it passes validation', () => { @@ -385,7 +385,7 @@ describe('DateEditor', () => { editor.applyValue(mockItemData, newDate); // @ts-ignore:2349 - expect(mockItemData).toEqual({ id: 1, startDate: format(newDate, 'YYYY-MM-DD hh:mm:ss a'), isActive: true }); + expect(mockItemData).toEqual({ id: 1, startDate: format(newDate, 'YYYY-MM-DD hh:mm:ss a', 'en-US'), isActive: true }); }); it('should return item data with an empty string in its value when it fails the custom validation', () => { diff --git a/packages/common/src/editors/dateEditor.ts b/packages/common/src/editors/dateEditor.ts index 87bf0e69f..948c4892b 100644 --- a/packages/common/src/editors/dateEditor.ts +++ b/packages/common/src/editors/dateEditor.ts @@ -109,10 +109,7 @@ export class DateEditor implements Editor { const gridOptions: GridOption = this.args.grid.getOptions() || {}; this.defaultDate = this.args.item?.[this.columnDef.field]; const outputFieldType = this.columnDef.outputType || this.columnEditor.type || this.columnDef.type || FieldType.dateUtc; - let outputFormat = mapTempoDateFormatWithFieldType(outputFieldType); - if (Array.isArray(outputFormat)) { - outputFormat = outputFormat[0]; - } + const outputFormat = mapTempoDateFormatWithFieldType(outputFieldType); const currentLocale = this._translaterService?.getCurrentLanguage?.() || gridOptions.locale || 'en'; // add the time picker when format is UTC (Z) or has the 'h' (meaning hours) diff --git a/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts b/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts index fdc7de906..a937eaa80 100644 --- a/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts +++ b/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts @@ -451,7 +451,7 @@ describe('CompoundDateFilter', () => { const filterFilledElms = divContainer.querySelectorAll('.form-group.search-filter.filter-finish.filled'); expect(filterFilledElms.length).toBe(1); - expect(filterInputElm.value).toBe('02/01/2001 16:02'); + expect(filterInputElm.value).toBe('2/1/2001 16:02'); expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '>', searchTerms: ['2001-01-02'], shouldTriggerQuery: true }); }); diff --git a/packages/common/src/filters/__tests__/dateRangeFilter.spec.ts b/packages/common/src/filters/__tests__/dateRangeFilter.spec.ts index bcad81c2e..f3cc3d914 100644 --- a/packages/common/src/filters/__tests__/dateRangeFilter.spec.ts +++ b/packages/common/src/filters/__tests__/dateRangeFilter.spec.ts @@ -55,6 +55,12 @@ describe('DateRangeFilter', () => { }; filter = new DateRangeFilter(translateService); + + // jest.spyOn(Intl, 'DateTimeFormat').mockImplementation(() => ({ + // resolvedOptions: () => ({ + // timeZone: 'America/New_yorkpp' + // }) + // })); }); afterEach(() => { @@ -321,7 +327,7 @@ describe('DateRangeFilter', () => { expect(filterFilledElms.length).toBe(1); // expect(filter.currentDateOrDates.map((date) => date.toISOString())).toEqual(['2001-01-01T05:00:00.000Z', '2001-01-31T05:00:00.000Z']); - expect(filterInputElm.value).toBe('2001-01-02 12:00:00 a.m. — 2001-01-13 12:00:00 a.m.'); + expect(filterInputElm.value).toBe('2001-01-02 12:00:00 am — 2001-01-13 12:00:00 am'); expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, operator: '>', searchTerms: ['2001-01-02', '2001-01-13'], shouldTriggerQuery: true }); @@ -344,7 +350,7 @@ describe('DateRangeFilter', () => { expect(filterFilledElms.length).toBe(1); // expect(filter.currentDateOrDates.map((date) => date.toISOString())).toEqual(['2000-01-01T05:00:00.000Z', '2000-01-31T05:00:00.000Z']); - expect(filterInputElm.value).toBe('2001-01-02 12:00:00 a.m. — 2001-01-13 12:00:00 a.m.'); + expect(filterInputElm.value).toBe('2001-01-02 12:00:00 am — 2001-01-13 12:00:00 am'); expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, operator: '>', searchTerms: ['2001-01-02', '2001-01-13'], shouldTriggerQuery: true }); diff --git a/packages/common/src/filters/dateFilter.ts b/packages/common/src/filters/dateFilter.ts index c19c984c7..6c54f2812 100644 --- a/packages/common/src/filters/dateFilter.ts +++ b/packages/common/src/filters/dateFilter.ts @@ -238,10 +238,7 @@ export class DateFilter implements Filter { const columnId = this.columnDef?.id ?? ''; const columnFieldType = this.columnFilter.type || this.columnDef.type || FieldType.dateIso; const outputFieldType = this.columnDef.outputType || this.columnFilter.type || this.columnDef.type || FieldType.dateUtc; - let outputFormat = mapTempoDateFormatWithFieldType(outputFieldType); - if (Array.isArray(outputFormat)) { - outputFormat = outputFormat[0]; - } + const outputFormat = mapTempoDateFormatWithFieldType(outputFieldType); const inputFieldType = this.columnFilter.type || this.columnDef.type || FieldType.dateIso; // add the time picker when format is UTC (Z) or has the 'h' (meaning hours) @@ -295,8 +292,8 @@ export class DateFilter implements Filter { self.selectedDates.sort((a, b) => +new Date(a) - +new Date(b)); firstDate = self.selectedDates[0]; lastDate = self.selectedDates[self.selectedDates.length - 1]; - const firstDisplayDate = format(self.selectedDates[0], outputFormat); - const lastDisplayDate = format(lastDate, outputFormat); + const firstDisplayDate = format(self.selectedDates[0], outputFormat, 'en-US'); + const lastDisplayDate = format(lastDate, outputFormat, 'en-US'); self.HTMLInputElement.value = `${firstDisplayDate} — ${lastDisplayDate}`; outDates = [firstDate, lastDate]; } else if (self.selectedDates[0]) { diff --git a/packages/common/src/formatters/__tests__/dateTimeEuroAM_PMFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeEuroAM_PMFormatter.spec.ts index 5c9fdcf3b..35582603b 100644 --- a/packages/common/src/formatters/__tests__/dateTimeEuroAM_PMFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeEuroAM_PMFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeEuroAM_PM Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-01 02:36:07'; const result = Formatters.dateTimeEuroAM_PM(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('01/05/2019 02:36:07 A.M.'); + expect(result).toBe('01/05/2019 02:36:07 AM'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeEuroAM_PM(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('01/05/2019 02:36:07 A.M.'); + expect(result).toBe('01/05/2019 02:36:07 AM'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeEuroAM_PM(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('01/05/2019 08:36:07 P.M.'); + expect(result).toBe('01/05/2019 08:36:07 PM'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateTimeEuroAmPmFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeEuroAmPmFormatter.spec.ts index 40bf413d7..e4e7422c5 100644 --- a/packages/common/src/formatters/__tests__/dateTimeEuroAmPmFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeEuroAmPmFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeShortEuro Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-01 02:36:07'; const result = Formatters.dateTimeEuroAmPm(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('01/05/2019 02:36:07 a.m.'); + expect(result).toBe('01/05/2019 02:36:07 am'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeEuroAmPm(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('01/05/2019 02:36:07 a.m.'); + expect(result).toBe('01/05/2019 02:36:07 am'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeEuroAmPm(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('01/05/2019 08:36:07 p.m.'); + expect(result).toBe('01/05/2019 08:36:07 pm'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateTimeEuroShortAM_PMFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeEuroShortAM_PMFormatter.spec.ts index e9f2ac1f3..78831202d 100644 --- a/packages/common/src/formatters/__tests__/dateTimeEuroShortAM_PMFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeEuroShortAM_PMFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeEuroShortAM_PM Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-01 02:36:07'; const result = Formatters.dateTimeEuroShortAM_PM(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('01/05/19 02:36:07 A.M.'); + expect(result).toBe('01/05/19 02:36:07 AM'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeEuroShortAM_PM(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('01/05/19 02:36:07 A.M.'); + expect(result).toBe('01/05/19 02:36:07 AM'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeEuroShortAM_PM(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('01/05/19 08:36:07 P.M.'); + expect(result).toBe('01/05/19 08:36:07 PM'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateTimeEuroShortAmPmFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeEuroShortAmPmFormatter.spec.ts index 2347ae566..0e80bfe7b 100644 --- a/packages/common/src/formatters/__tests__/dateTimeEuroShortAmPmFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeEuroShortAmPmFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeEuroShortAmPm Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-01 02:36:07'; const result = Formatters.dateTimeEuroShortAmPm(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('01/05/19 02:36:07 a.m.'); + expect(result).toBe('01/05/19 02:36:07 am'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeEuroShortAmPm(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('01/05/19 02:36:07 a.m.'); + expect(result).toBe('01/05/19 02:36:07 am'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeEuroShortAmPm(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('01/05/19 08:36:07 p.m.'); + expect(result).toBe('01/05/19 08:36:07 pm'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateTimeIsoAM_PMFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeIsoAM_PMFormatter.spec.ts index b6f85ac41..d841159e9 100644 --- a/packages/common/src/formatters/__tests__/dateTimeIsoAM_PMFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeIsoAM_PMFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeIsoAM_PM Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-01 02:36:07'; const result = Formatters.dateTimeIsoAM_PM(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('2019-05-01 02:36:07 A.M.'); + expect(result).toBe('2019-05-01 02:36:07 AM'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeIsoAM_PM(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('2019-05-01 02:36:07 A.M.'); + expect(result).toBe('2019-05-01 02:36:07 AM'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeIsoAM_PM(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('2019-05-01 08:36:07 P.M.'); + expect(result).toBe('2019-05-01 08:36:07 PM'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateTimeIsoAmPmFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeIsoAmPmFormatter.spec.ts index 82ab84754..afb3a0127 100644 --- a/packages/common/src/formatters/__tests__/dateTimeIsoAmPmFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeIsoAmPmFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeIsoAmPm Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-01 02:36:07'; const result = Formatters.dateTimeIsoAmPm(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('2019-05-01 02:36:07 a.m.'); + expect(result).toBe('2019-05-01 02:36:07 am'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeIsoAmPm(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('2019-05-01 02:36:07 a.m.'); + expect(result).toBe('2019-05-01 02:36:07 am'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeIsoAmPm(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('2019-05-01 08:36:07 p.m.'); + expect(result).toBe('2019-05-01 08:36:07 pm'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateTimeUsAM_PMFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeUsAM_PMFormatter.spec.ts index 209855fcd..d0df34c4e 100644 --- a/packages/common/src/formatters/__tests__/dateTimeUsAM_PMFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeUsAM_PMFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeUsAM_PM Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-01 02:36:07'; const result = Formatters.dateTimeUsAM_PM(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('05/01/2019 02:36:07 A.M.'); + expect(result).toBe('05/01/2019 02:36:07 AM'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeUsAM_PM(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('05/01/2019 02:36:07 A.M.'); + expect(result).toBe('05/01/2019 02:36:07 AM'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeUsAM_PM(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('05/01/2019 08:36:07 P.M.'); + expect(result).toBe('05/01/2019 08:36:07 PM'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateTimeUsAmPmFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeUsAmPmFormatter.spec.ts index 84a4d2b9d..a506e908d 100644 --- a/packages/common/src/formatters/__tests__/dateTimeUsAmPmFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeUsAmPmFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeShortUs Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-03 02:36:07'; const result = Formatters.dateTimeUsAmPm(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('05/03/2019 02:36:07 a.m.'); + expect(result).toBe('05/03/2019 02:36:07 am'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeUsAmPm(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('05/01/2019 02:36:07 a.m.'); + expect(result).toBe('05/01/2019 02:36:07 am'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeUsAmPm(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('05/01/2019 08:36:07 p.m.'); + expect(result).toBe('05/01/2019 08:36:07 pm'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateTimeUsShortAM_PMFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeUsShortAM_PMFormatter.spec.ts index 0274bf98d..ad4c64f24 100644 --- a/packages/common/src/formatters/__tests__/dateTimeUsShortAM_PMFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeUsShortAM_PMFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeUsShortAM_PM Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-01 02:36:07'; const result = Formatters.dateTimeUsShortAM_PM(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('05/01/19 02:36:07 A.M.'); + expect(result).toBe('05/01/19 02:36:07 AM'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeUsShortAM_PM(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('05/01/19 02:36:07 A.M.'); + expect(result).toBe('05/01/19 02:36:07 AM'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeUsShortAM_PM(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('05/01/19 08:36:07 P.M.'); + expect(result).toBe('05/01/19 08:36:07 PM'); }); }); diff --git a/packages/common/src/formatters/__tests__/dateTimeUsShortAmPmFormatter.spec.ts b/packages/common/src/formatters/__tests__/dateTimeUsShortAmPmFormatter.spec.ts index deb6455d7..e47f4f1ab 100644 --- a/packages/common/src/formatters/__tests__/dateTimeUsShortAmPmFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/dateTimeUsShortAmPmFormatter.spec.ts @@ -17,18 +17,18 @@ describe('the DateTimeUsShortAmPm Formatter', () => { it('should provide a dateIso formatted input and return a formatted date value without time when valid date value is provided', () => { const value = '2019-05-01 02:36:07'; const result = Formatters.dateTimeUsShortAmPm(0, 0, value, { type: 'dateIso' } as unknown as Column, {}, {} as any); - expect(result).toBe('05/01/19 02:36:07 a.m.'); + expect(result).toBe('05/01/19 02:36:07 am'); }); it('should return a formatted date value in the morning when valid date value is provided', () => { const value = new Date('2019-05-01T02:36:07'); const result = Formatters.dateTimeUsShortAmPm(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('05/01/19 02:36:07 a.m.'); + expect(result).toBe('05/01/19 02:36:07 am'); }); it('should return a formatted date value in the afternoon when valid date value is provided', () => { const value = new Date('2019-05-01T20:36:07'); const result = Formatters.dateTimeUsShortAmPm(0, 0, value, {} as Column, {}, {} as any); - expect(result).toBe('05/01/19 08:36:07 p.m.'); + expect(result).toBe('05/01/19 08:36:07 pm'); }); }); diff --git a/packages/common/src/formatters/formatterUtilities.ts b/packages/common/src/formatters/formatterUtilities.ts index 619e3a11d..c0f970311 100644 --- a/packages/common/src/formatters/formatterUtilities.ts +++ b/packages/common/src/formatters/formatterUtilities.ts @@ -96,10 +96,7 @@ export function getValueFromParamsOrFormatterOptions(optionName: string, columnD /** From a FieldType, return the associated date Formatter */ export function getAssociatedDateFormatter(fieldType: typeof FieldType[keyof typeof FieldType], defaultSeparator: string): Formatter { - let defaultDateFormat = mapTempoDateFormatWithFieldType(fieldType, true); - if (Array.isArray(defaultDateFormat)) { - defaultDateFormat = defaultDateFormat[0]; - } + const defaultDateFormat = mapTempoDateFormatWithFieldType(fieldType, true); return (_row: number, _cell: number, value: any, columnDef: Column, _dataContext: any, grid: SlickGrid) => { const gridOptions = ((grid && typeof grid.getOptions === 'function') ? grid.getOptions() : {}) as GridOption; @@ -115,7 +112,7 @@ export function getAssociatedDateFormatter(fieldType: typeof FieldType[keyof typ if (isParsingAsUtc) { d = toUtcDate(date); } - outputDate = format(d, defaultDateFormat); + outputDate = format(d, defaultDateFormat, 'en-US'); } // user can customize the separator through the "formatterOptions" diff --git a/packages/common/src/services/dateUtils.ts b/packages/common/src/services/dateUtils.ts index 6060ef312..daf560412 100644 --- a/packages/common/src/services/dateUtils.ts +++ b/packages/common/src/services/dateUtils.ts @@ -121,7 +121,7 @@ export function formatTempoDateByFieldType(inputDate: Date | string, inputFieldT if (outputFieldType === FieldType.dateUtc) { return date.toISOString(); } - return format(date, Array.isArray(outputFormat) ? outputFormat[0] : outputFormat); + return format(date, outputFormat, 'en-US'); } return ''; } @@ -135,7 +135,7 @@ export function tryParseDate(inputDate?: string | Date, inputFormat?: string, st date: inputDate, format: inputFormat as string, dateOverflow: strict ? 'throw' : 'backward', - locale: 'en' + locale: 'en-US' }); } catch (_e) { return false; diff --git a/packages/custom-footer-component/src/slick-footer.component.ts b/packages/custom-footer-component/src/slick-footer.component.ts index 6a8bea6a2..88df52939 100644 --- a/packages/custom-footer-component/src/slick-footer.component.ts +++ b/packages/custom-footer-component/src/slick-footer.component.ts @@ -110,7 +110,7 @@ export class SlickFooterComponent { /** Render element attribute values */ renderMetrics(metrics: Metrics) { // get translated text & last timestamp - const lastUpdateTimestamp = metrics?.endTime ? format(metrics.endTime, this.customFooterOptions.dateFormat) : ''; + const lastUpdateTimestamp = metrics?.endTime ? format(metrics.endTime, this.customFooterOptions.dateFormat, 'en-US') : ''; this._bindingHelper.setElementAttributeValue('span.last-update-timestamp', 'textContent', lastUpdateTimestamp); this._bindingHelper.setElementAttributeValue('span.item-count', 'textContent', metrics.itemCount); this._bindingHelper.setElementAttributeValue('span.total-count', 'textContent', metrics.totalItemCount); @@ -233,7 +233,7 @@ export class SlickFooterComponent { protected createFooterLastUpdate(): HTMLSpanElement { // get translated text & last timestamp const lastUpdateText = this.customFooterOptions?.metricTexts?.lastUpdate ?? 'Last Update'; - const lastUpdateTimestamp = this.metrics?.endTime ? format(this.metrics?.endTime, this.customFooterOptions.dateFormat) : ''; + const lastUpdateTimestamp = this.metrics?.endTime ? format(this.metrics?.endTime, this.customFooterOptions.dateFormat, 'en-US') : ''; const lastUpdateContainerElm = createDomElement('span'); lastUpdateContainerElm.appendChild(createDomElement('span', { className: 'text-last-update', textContent: lastUpdateText })); diff --git a/packages/custom-footer-component/src/slick-footer.spec.ts b/packages/custom-footer-component/src/slick-footer.spec.ts index 72bda67ac..1c616d6f8 100644 --- a/packages/custom-footer-component/src/slick-footer.spec.ts +++ b/packages/custom-footer-component/src/slick-footer.spec.ts @@ -113,7 +113,7 @@ describe('Slick-Footer Component', () => { expect(leftFooterElm.innerHTML).toBe(''); expect(removeExtraSpaces(rightFooterElm.innerHTML)).toBe(removeExtraSpaces( `some last update - 2019-05-03, 12:00:01a.m. + 2019-05-03, 12:00:01am | 7some of99 some items`)); @@ -181,7 +181,7 @@ describe('Slick-Footer Component', () => { expect(leftFooterElm.innerHTML).toBe(''); expect(removeExtraSpaces(rightFooterElm.innerHTML)).toBe(removeExtraSpaces( `Last Update - 2019-05-03, 12:00:01a.m. + 2019-05-03, 12:00:01am | 7of99 items`)); @@ -215,7 +215,7 @@ describe('Slick-Footer Component', () => { expect(leftFooterElm.innerHTML).toBe(''); expect(removeExtraSpaces(rightFooterElm.innerHTML)).toBe(removeExtraSpaces( `Dernière mise à jour - 2019-05-03, 12:00:01a.m. | + 2019-05-03, 12:00:01am | 7de99 éléments`)); }); @@ -254,7 +254,7 @@ describe('Slick-Footer Component', () => { expect(rightFooterElm).toBeTruthy(); expect(leftFooterElm.innerHTML).toBe('custom left footer text'); expect(removeExtraSpaces(rightFooterElm.innerHTML)).toBe(removeExtraSpaces( - `some last update2019-05-03, 12:00:01a.m. | + `some last update2019-05-03, 12:00:01am | 7some of99 some items`)); }); @@ -277,7 +277,7 @@ describe('Slick-Footer Component', () => { expect(leftFooterElm.innerHTML).toBe('1 items selected'); expect(removeExtraSpaces(rightFooterElm.innerHTML)).toBe(removeExtraSpaces( `some last update - 2019-05-03, 12:00:01a.m. | + 2019-05-03, 12:00:01am | 7some of99 some items`)); @@ -304,7 +304,7 @@ describe('Slick-Footer Component', () => { expect(leftFooterElm.innerHTML).toBe(''); expect(removeExtraSpaces(rightFooterElm.innerHTML)).toBe(removeExtraSpaces( `some last update - 2019-05-03, 12:00:01a.m. | + 2019-05-03, 12:00:01am | 7some of99 some items`)); }); From dc4533544791041604be5b384693d3a902100ced Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Sun, 5 May 2024 03:43:09 -0400 Subject: [PATCH 03/10] chore: add missing unit tests & also add possible ISO8601 time condition --- .../common/src/commonEditorFilter/commonEditorFilterUtils.ts | 2 +- packages/common/src/editors/__tests__/dateEditor.spec.ts | 4 ++-- packages/common/src/editors/dateEditor.ts | 2 +- packages/common/src/filters/dateFilter.ts | 2 +- packages/common/src/services/dateUtils.ts | 5 ----- 5 files changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/common/src/commonEditorFilter/commonEditorFilterUtils.ts b/packages/common/src/commonEditorFilter/commonEditorFilterUtils.ts index 318df22e7..f771543e8 100644 --- a/packages/common/src/commonEditorFilter/commonEditorFilterUtils.ts +++ b/packages/common/src/commonEditorFilter/commonEditorFilterUtils.ts @@ -54,7 +54,7 @@ export function setPickerDates(dateInputElm: HTMLInputElement, pickerOptions: IO dates: [pickerDates.map(p => format(p, isoFormat)).join(':')], month: pickerDates[0].getMonth(), year: pickerDates[0].getFullYear(), - time: (inputFormat || '').toLowerCase().includes('h') ? format(pickerDates[0], 'HH:mm') : undefined, + time: inputFormat === 'ISO8601' || (inputFormat || '').toLowerCase().includes('h') ? format(pickerDates[0], 'HH:mm') : undefined, }; } dateInputElm.value = initialDates.length ? pickerDates.map(p => formatTempoDateByFieldType(p, undefined, outputFieldType)).join(' — ') : ''; diff --git a/packages/common/src/editors/__tests__/dateEditor.spec.ts b/packages/common/src/editors/__tests__/dateEditor.spec.ts index ac29ef48e..622f3d670 100644 --- a/packages/common/src/editors/__tests__/dateEditor.spec.ts +++ b/packages/common/src/editors/__tests__/dateEditor.spec.ts @@ -258,8 +258,8 @@ describe('DateEditor', () => { editor.focus(); const editorInputElm = editor.editorDomElement; editorInputElm.value = '2024-04-02T16:02:02.239Z'; - editor.calendarInstance!.actions!.clickDay!(new MouseEvent('click'), { HTMLInputElement: editorInputElm, selectedDates: [dateMock] } as unknown as VanillaCalendar); - editor.calendarInstance!.actions!.changeToInput!(new MouseEvent('click'), { HTMLInputElement: editorInputElm, selectedDates: [dateMock], hide: jest.fn() } as unknown as VanillaCalendar); + editor.calendarInstance!.actions!.clickDay!(new MouseEvent('click'), { HTMLInputElement: editorInputElm, selectedDates: [dateMock], selectedHours: 11, selectedMinutes: 2 } as unknown as VanillaCalendar); + editor.calendarInstance!.actions!.changeToInput!(new MouseEvent('click'), { HTMLInputElement: editorInputElm, selectedDates: [dateMock], selectedHours: 11, selectedMinutes: 2, hide: jest.fn() } as unknown as VanillaCalendar); expect(editor.isValueChanged()).toBe(true); expect(editor.isValueTouched()).toBe(true); diff --git a/packages/common/src/editors/dateEditor.ts b/packages/common/src/editors/dateEditor.ts index 948c4892b..ad804aeee 100644 --- a/packages/common/src/editors/dateEditor.ts +++ b/packages/common/src/editors/dateEditor.ts @@ -113,7 +113,7 @@ export class DateEditor implements Editor { const currentLocale = this._translaterService?.getCurrentLanguage?.() || gridOptions.locale || 'en'; // add the time picker when format is UTC (Z) or has the 'h' (meaning hours) - if (outputFormat && (outputFormat === 'Z' || outputFormat.toLowerCase().includes('h'))) { + if (outputFormat && (outputFormat === 'ISO8601' || outputFormat.toLowerCase().includes('h'))) { this.hasTimePicker = true; } const pickerFormat = mapTempoDateFormatWithFieldType(this.hasTimePicker ? FieldType.dateTimeIsoAM_PM : FieldType.dateIso); diff --git a/packages/common/src/filters/dateFilter.ts b/packages/common/src/filters/dateFilter.ts index 6c54f2812..10af2fd57 100644 --- a/packages/common/src/filters/dateFilter.ts +++ b/packages/common/src/filters/dateFilter.ts @@ -242,7 +242,7 @@ export class DateFilter implements Filter { const inputFieldType = this.columnFilter.type || this.columnDef.type || FieldType.dateIso; // add the time picker when format is UTC (Z) or has the 'h' (meaning hours) - if (outputFormat && this.inputFilterType !== 'range' && outputFormat.toLowerCase().includes('h')) { + if (outputFormat && this.inputFilterType !== 'range' && (outputFormat === 'ISO8601' || outputFormat.toLowerCase().includes('h'))) { this.hasTimePicker = true; } const pickerFormat = mapTempoDateFormatWithFieldType(this.hasTimePicker ? FieldType.dateTimeIsoAM_PM : FieldType.dateIso); diff --git a/packages/common/src/services/dateUtils.ts b/packages/common/src/services/dateUtils.ts index daf560412..3d81f9584 100644 --- a/packages/common/src/services/dateUtils.ts +++ b/packages/common/src/services/dateUtils.ts @@ -147,11 +147,6 @@ export function toUtcDate(inputDate: Date) { return new Date(inputDate.getTime() + localOffset); }; -export function toUtc(inputDate: Date) { - return new Date(Date.UTC(inputDate.getUTCFullYear(), inputDate.getUTCMonth(), - inputDate.getUTCDate(), inputDate.getUTCHours(), - inputDate.getUTCMinutes(), inputDate.getUTCSeconds())); -} /** * Parse a date passed as a string (Date only, without time) and return a TZ Date (without milliseconds) * @param inputDateString From 473c6a27e0a8c1d5ea94a42ae2697c931a7e1a19 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Mon, 6 May 2024 10:43:26 -0400 Subject: [PATCH 04/10] chore: fix failing unit tests and add docs about Tempo --- docs/column-functionalities/Formatters.md | 41 +++---------------- .../editors/date-editor-(vanilla-calendar).md | 4 +- .../filters/Compound-Filters.md | 4 +- .../filters/Range-Filters.md | 2 + docs/getting-started/quick-start.md | 2 +- docs/migrations/migration-to-5.x.md | 9 +++- .../__tests__/compoundDateFilter.spec.ts | 6 +-- 7 files changed, 26 insertions(+), 42 deletions(-) diff --git a/docs/column-functionalities/Formatters.md b/docs/column-functionalities/Formatters.md index 3d8608901..e2d8f3f82 100644 --- a/docs/column-functionalities/Formatters.md +++ b/docs/column-functionalities/Formatters.md @@ -20,7 +20,7 @@ For a [UI sample](#ui-sample), scroll down below. ### Provided Formatters `Slickgrid-Universal` ships with a few `Formatters` by default which helps with common fields, you can see the [entire list here](https://github.com/ghiscoding/slickgrid-universal/blob/master/packages/common/src/formatters/index.ts#L37). -> **Note** you might not need a Formatter when a simple CSS style is needed, think about using `cssClass` column property instead. +> **Note** you might not need a Formatter when a simple CSS style and class might be enough, think about using `cssClass` column property as much as possible since it has much better perf. #### List of provided `Formatters` - `arrayObjectToCsv`: Takes an array of complex objects converts it to a comma delimited string. @@ -46,6 +46,7 @@ For a [UI sample](#ui-sample), scroll down below. - `dateTimeUs` : Takes a Date object and displays it as an US Date+Time format (MM/DD/YYYY HH:mm:ss) - `dateTimeShortUs`: Takes a Date object and displays it as an US Date+Time (without seconds) format (MM/DD/YYYY HH:mm:ss) - `dateTimeUsAmPm` : Takes a Date object and displays it as an US Date+Time+(am/pm) format (MM/DD/YYYY hh:mm:ss a) +- `dateUtc` : Takes a Date object and displays it as a TZ format (YYYY-MM-DDThh:mm:ssZ) - `decimal`: Display the value as x decimals formatted, defaults to 2 decimals. You can pass "minDecimal" and/or "maxDecimal" to the "params" property. - `dollar`: Display the value as 2 decimals formatted with dollar sign '$' at the end of of the value. - `dollarColored`: Display the value as 2 decimals formatted with dollar sign '$' at the end of of the value, change color of text to red/green on negative/positive value @@ -70,10 +71,12 @@ For a [UI sample](#ui-sample), scroll down below. - `translateBoolean`: Takes a boolean value, cast it to upperCase string and finally translates it (i18n). - `tree`: Formatter that must be used when the column is a Tree Data column -**Note:** The list might not always be up to date, you can refer to the [Formatters export](https://github.com/ghiscoding/slickgrid-universal/blob/master/packages/common/src/formatters/index.ts#L37) to know exactly which ones are available. +> **Note:** The list is certainly not up to date (especially for Dates), please refer to the [Formatters export](https://github.com/ghiscoding/slickgrid-universal/blob/master/packages/common/src/formatters/index.ts#L37) to know exactly which formatters are available. + +> **Note** all Date formatters are formatted using [Tempo](https://tempo.formkit.com/#format-tokens). There are also many more Date formats not shown above, simply visit the [formatters.index](https://github.com/ghiscoding/slickgrid-universal/blob/master/packages/common/src/formatters/formatters.index.ts#L101) to see all available Date/Time formats. ### Usage -To use any of them, you need to import `Formatters` from `Slickgrid-Universal` and add a `formatter: ...` in your column definitions as shown below: +To use any of them, you simply need to import `Formatters` from `Slickgrid-Universal` and add a `formatter: Formatters.xyz` (where `xyx` is the name of the built-in formatter) in your column definitions as shown below: #### TypeSript ```ts @@ -102,38 +105,6 @@ export class Example { } ``` -#### SalesForce (ES6) -For SalesForce the code is nearly the same, the only difference is to add the `Slicker` prefix, so instead of `Formatters.abc` we need to use `Slicker.Formatters.abc` - -```ts -// ... SF_Slickgrid import - - -export class Example { - const Slicker = window.Slicker; - - columnDefinitions: Column[]; - gridOptions: GridOption; - dataset: any[]; - - constructor() { - // define the grid options & columns and then create the grid itself - this.defineGrid(); - } - - defineGrid() { - this.columnDefinitions = [ - { id: 'title', name: 'Title', field: 'title' }, - { id: 'duration', name: 'Duration (days)', field: 'duration' }, - { id: '%', name: '% Complete', field: 'percentComplete', formatter: Slicker.Formatters.percentComplete }, - { id: 'start', name: 'Start', field: 'start', formatter: Slicker.Formatters.dateIso }, - { id: 'finish', name: 'Finish', field: 'finish', formatter: Slicker.Formatters.dateIso }, - { id: 'effort-driven', name: 'Effort Driven', field: 'effortDriven', formatter: Slicker.Formatters.checkmarkMaterial } - ]; - } -} -``` - ### Extra Arguments/Params What if you want to pass extra arguments that you want to use within the Formatter? You should use `params` for that. For example, let say you have a custom formatter to build a select list (dropdown), you could do it this way: ```ts diff --git a/docs/column-functionalities/editors/date-editor-(vanilla-calendar).md b/docs/column-functionalities/editors/date-editor-(vanilla-calendar).md index df1ecbec5..9b0e29d1d 100644 --- a/docs/column-functionalities/editors/date-editor-(vanilla-calendar).md +++ b/docs/column-functionalities/editors/date-editor-(vanilla-calendar).md @@ -4,7 +4,9 @@ - See the [Editors - Wiki](../Editors.md) for more general info about Editors (validators, event handlers, ...) ### Information -The Date Editor is provided through an external library named [Vanilla-Calendar-Picker](https://github.com/ghiscoding/vanilla-calendar-picker) (a fork of [Vanilla-Calendar-Pro](https://vanilla-calendar.pro)) and all options from that library can be added to your `editorOptions` (see below), so in order to add things like minimum date, disabling dates, ... just review all the [Vanilla-Calendar-Pro](https://vanilla-calendar.pro/docs/reference/additionally/settings) and then add them into `editorOptions`. Also just so you know, `editorOptions` is use by all other editors as well to expose external library like Autocompleter, Multiple-Select, etc... +The Date Editor is provided through an external library named [Vanilla-Calendar-Picker](https://github.com/ghiscoding/vanilla-calendar-picker) (a fork of [Vanilla-Calendar-Pro](https://vanilla-calendar.pro)) and all options from that library can be added to your `editorOptions` (see below), so in order to add things like minimum date, disabling dates, ... just review all the [Vanilla-Calendar-Pro](https://vanilla-calendar.pro/docs/reference/additionally/settings) and then add them into `editorOptions`. We use [Tempo](https://tempo.formkit.com/) to parse and format Dates to the chosen format (when `type`, `outputType` and/or `saveType` are provided in your column definition) + +> **Note** Also just so you know, `editorOptions` is used by all other editors as well to expose external library like Autocompleter, Multiple-Select, etc... ### Demo [Demo Page](https://ghiscoding.github.io/slickgrid-universal/#/example12) | [Demo Component](https://github.com/ghiscoding/slickgrid-universal/blob/master/examples/webpack-demo-vanilla-bundle/src/examples/example12.ts) diff --git a/docs/column-functionalities/filters/Compound-Filters.md b/docs/column-functionalities/filters/Compound-Filters.md index adfa04e1d..bd268a26c 100644 --- a/docs/column-functionalities/filters/Compound-Filters.md +++ b/docs/column-functionalities/filters/Compound-Filters.md @@ -51,7 +51,7 @@ The column definition `type` will affect the list of Operators shown, for exampl ### How to use CompoundDate Filter -Again set the column definition flag `filterable: true` and use the filter type `Filters.compoundDate`. Here is an example with a full column definition: +As any other columns, set the column definition flag `filterable: true` and use the filter type `Filters.compoundDate`. Here is an example with a full column definition: ```ts // define you columns, in this demo Effort Driven will use a Select Filter this.columnDefinitions = [ @@ -75,6 +75,8 @@ this.gridOptions = { }; ``` +> **Note** we use [Tempo](https://tempo.formkit.com/) to parse and format Dates to the chosen format via the `type` option when provided in your column definition. + #### Dealing with different input/ouput dates (example: UTC) What if your date input (from your dataset) has a different output on the screen (UI)? In that case, you will most probably have a Formatter and type representing the input type, we also provided an `outputType` that can be used to deal with that case. diff --git a/docs/column-functionalities/filters/Range-Filters.md b/docs/column-functionalities/filters/Range-Filters.md index f0b3f37ad..0a204da01 100644 --- a/docs/column-functionalities/filters/Range-Filters.md +++ b/docs/column-functionalities/filters/Range-Filters.md @@ -134,6 +134,8 @@ this.gridOptions = { ### Using a Date Range Filter The date range filter allows you to search data between 2 dates (it uses [Vanilla-Calendar Range](https://vanilla-calendar.pro/) feature). +> **Note** we use [Tempo](https://tempo.formkit.com/) to parse and format Dates to the chosen format via the `type` option when provided in your column definition. + ##### Component import { Filters, Formatters, GridOption, OperatorType, VanillaCalendarOption } from '@slickgrid-universal/common'; diff --git a/docs/getting-started/quick-start.md b/docs/getting-started/quick-start.md index e5fbfad0f..19afdd861 100644 --- a/docs/getting-started/quick-start.md +++ b/docs/getting-started/quick-start.md @@ -9,7 +9,7 @@ To get started follow any of these instruction Wikis depending on your choice of | Angular | [Wiki - HOWTO (Step by Step)](https://ghiscoding.gitbook.io/angular-slickgrid/getting-started/quick-start) | [Live Demo](https://ghiscoding.github.io/Angular-Slickgrid/) | | Aurelia | [Wiki - HOWTO (Step by Step)](https://ghiscoding.gitbook.io/aurelia-slickgrid/getting-started/quick-start) | [Live Demo](https://ghiscoding.github.io/aurelia-slickgrid/) | | React | [Wiki - HOWTO (Step by Step)](https://ghiscoding.gitbook.io/slickgrid-react/getting-started/quick-start) | [Live Demo](https://ghiscoding.github.io/slickgrid-react/) | -| Salesforce | Installation | [Print Screen](https://github.com/ghiscoding/slickgrid-universal/wiki#salesforce-demo---print-screen) | +| Salesforce | [Salesforce Installation](getting-started/installation-salesforce.md) | [Print Screen](https://github.com/ghiscoding/slickgrid-universal/wiki#salesforce-demo---print-screen) | | ---- | | | **General Subjects** diff --git a/docs/migrations/migration-to-5.x.md b/docs/migrations/migration-to-5.x.md index 5c5f250d2..241e22910 100644 --- a/docs/migrations/migration-to-5.x.md +++ b/docs/migrations/migration-to-5.x.md @@ -31,6 +31,7 @@ The goal of this new release was mainly to improve UI/UX (mostly for Dark Mode) - Bootstrap >=v5.x (or any other UI framework) - SASS >=v1.35 (`dart-sass`) - migrated from Flatpickr to Vanilla-Calendar (visit [Vanilla-Calendar-Pro](https://vanilla-calendar.pro/) for demos and docs) + - migrated from MomentJS to [Tempo](https://tempo.formkit.com/) > **Note** for the entire list of tasks & code changes applied in this release, you may want to take a look at the [Roadmap to 5.0](https://github.com/ghiscoding/slickgrid-universal/discussions/1482) Discussion. @@ -228,6 +229,7 @@ if you want to read the Editor class (e.g. `Editors.longText`), you can now refe ## Grid Functionalities +### Sanitizer (DOMPurify) `DOMPurify` is now completely optional via the `sanitizer` grid option and you must now provide it yourself. The main reason to make it optional was because most users would use `dompurify` but some users who require SSR support would want to use `isomorphic-dompurify`. You could also skip the `sanitizer` configuration, but that is not recommended. > **Note** even if the `sanitizer` is optional, we **strongly suggest** that you configure it as a global grid option to avoid possible XSS attacks from your data and also to be CSP compliant. Note that for Salesforce users, you do not have to configure it since Salesforce already use DOMPurify internally. @@ -239,4 +241,9 @@ this.gridOptions = { }; ``` -> **Note** If you're wondering about the `ADD_ATTR: ['level']`, well the "level" is a custom attribute used by SlickGrid Grouping/Draggable Grouping to track the grouping level depth and it must be kept. \ No newline at end of file +> **Note** If you're wondering about the `ADD_ATTR: ['level']`, well the "level" is a custom attribute used by SlickGrid Grouping/Draggable Grouping to track the grouping level depth and it must be kept. + +### From MomentJS to Tempo +I wanted to replace MomentJS for a long time now (it's been deprecated for years and is CJS only), but it was really hard to find a good replacement (I tried DayJS, Luxon, date-fns and they all had problems)... and here comes [Tempo](https://tempo.formkit.com/)! With Tempo, I was finally able to migrate with the 2 most important functions for our usage in a datagrid are the `parse()` and `format()` functions. The library also has plenty of extra optional functions like `addDay()`, `diffDays()`, ... Another great thing about Tempo is that they use the same format tokens as MomentJS, so the conversion on that side was easy. + +This migration should be transparent to the user, however if you were using MomentJS then please consider trying [Tempo](https://tempo.formkit.com/) in your project as well to modernize your project and also lower your dependencies count. \ No newline at end of file diff --git a/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts b/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts index a937eaa80..7c03a7901 100644 --- a/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts +++ b/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts @@ -305,7 +305,7 @@ describe('CompoundDateFilter', () => { expect(filterFilledElms.length).toBe(1); expect(filter.currentDateOrDates![0].toISOString()).toBe('2000-01-01T05:00:00.000Z'); expect(filterInputElm.value).toBe('2000-01-01T05:00:00.000Z'); - expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, operator: '<=', searchTerms: ['2000-01-01T05:00:00.000Z'], shouldTriggerQuery: true }); + expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '<=', searchTerms: ['2000-01-01T05:00:00.000Z'], shouldTriggerQuery: true }); }); it('should create the input filter with a default input dates when passed as a filter options', () => { @@ -403,7 +403,7 @@ describe('CompoundDateFilter', () => { expect(filterFilledElms.length).toBe(1); expect(filter.currentDateOrDates![0].toISOString()).toBe('2000-01-01T05:00:00.000Z'); expect(filterInputElm.value).toBe('2000-01-01T05:00:00.000Z'); - expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, operator: '<=', searchTerms: ['2000-01-01T05:00:00.000Z'], shouldTriggerQuery: true }); + expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '<=', searchTerms: ['2000-01-01T05:00:00.000Z'], shouldTriggerQuery: true }); expect(calendarElm).toBeTruthy(); expect(monthElm).toBeTruthy(); // expect(monthElm.textContent).toBe('janvier'); @@ -473,7 +473,7 @@ describe('CompoundDateFilter', () => { expect(filterFilledElms.length).toBe(1); expect(filter.currentDateOrDates![0].toISOString()).toBe('2000-01-01T05:00:00.000Z'); expect(filterInputElm.value).toBe('2000-01-01T05:00:00.000Z'); - expect(spyCallback).toHaveBeenCalledWith(undefined, { columnDef: mockColumn, operator: '<=', searchTerms: ['2000-01-01T05:00:00.000Z'], shouldTriggerQuery: true }); + expect(spyCallback).toHaveBeenCalledWith(expect.anything(), { columnDef: mockColumn, operator: '<=', searchTerms: ['2000-01-01T05:00:00.000Z'], shouldTriggerQuery: true }); }); it('should have default English text with operator dropdown options related to dates', () => { From 1892916f34cba839c2625520fc38be2602d3dfc4 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Mon, 6 May 2024 10:47:59 -0400 Subject: [PATCH 05/10] chore: fix failing unit tests and add docs about Tempo --- docs/migrations/migration-to-5.x.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/migrations/migration-to-5.x.md b/docs/migrations/migration-to-5.x.md index 241e22910..98e54062a 100644 --- a/docs/migrations/migration-to-5.x.md +++ b/docs/migrations/migration-to-5.x.md @@ -246,4 +246,4 @@ this.gridOptions = { ### From MomentJS to Tempo I wanted to replace MomentJS for a long time now (it's been deprecated for years and is CJS only), but it was really hard to find a good replacement (I tried DayJS, Luxon, date-fns and they all had problems)... and here comes [Tempo](https://tempo.formkit.com/)! With Tempo, I was finally able to migrate with the 2 most important functions for our usage in a datagrid are the `parse()` and `format()` functions. The library also has plenty of extra optional functions like `addDay()`, `diffDays()`, ... Another great thing about Tempo is that they use the same format tokens as MomentJS, so the conversion on that side was easy. -This migration should be transparent to the user, however if you were using MomentJS then please consider trying [Tempo](https://tempo.formkit.com/) in your project as well to modernize your project and also lower your dependencies count. \ No newline at end of file +This migration should be transparent to the user, however if you were using MomentJS then please consider trying [Tempo](https://tempo.formkit.com/) in your project as well to modernize your project and also lower your dependencies count. The other great advantage of Tempo is that it's ESM and it helps a lot in decreasing our build size footprint because of ESM Tree Shacking feature. \ No newline at end of file From f825800250b97226cbd7b1b302ed06e23745a926 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Mon, 6 May 2024 11:10:11 -0400 Subject: [PATCH 06/10] docs: update comments with more appropriate refs --- packages/common/src/editors/dateEditor.ts | 4 ++-- packages/common/src/filters/dateFilter.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/common/src/editors/dateEditor.ts b/packages/common/src/editors/dateEditor.ts index ad804aeee..ceb6d2bfb 100644 --- a/packages/common/src/editors/dateEditor.ts +++ b/packages/common/src/editors/dateEditor.ts @@ -112,7 +112,7 @@ export class DateEditor implements Editor { const outputFormat = mapTempoDateFormatWithFieldType(outputFieldType); const currentLocale = this._translaterService?.getCurrentLanguage?.() || gridOptions.locale || 'en'; - // add the time picker when format is UTC (Z) or has the 'h' (meaning hours) + // add the time picker when format is UTC (TZ - ISO8601) or has the 'h' (meaning hours) if (outputFormat && (outputFormat === 'ISO8601' || outputFormat.toLowerCase().includes('h'))) { this.hasTimePicker = true; } @@ -162,7 +162,7 @@ export class DateEditor implements Editor { }, }; - // add the time picker when format is UTC (Z) or has the 'h' (meaning hours) + // add the time picker when format includes time (hours/minutes) if (this.hasTimePicker) { pickerOptions.settings!.selection = { time: 24 diff --git a/packages/common/src/filters/dateFilter.ts b/packages/common/src/filters/dateFilter.ts index 10af2fd57..6643f8fc2 100644 --- a/packages/common/src/filters/dateFilter.ts +++ b/packages/common/src/filters/dateFilter.ts @@ -241,7 +241,7 @@ export class DateFilter implements Filter { const outputFormat = mapTempoDateFormatWithFieldType(outputFieldType); const inputFieldType = this.columnFilter.type || this.columnDef.type || FieldType.dateIso; - // add the time picker when format is UTC (Z) or has the 'h' (meaning hours) + // add the time picker when format is UTC (TZ - ISO8601) or has the 'h' (meaning hours) if (outputFormat && this.inputFilterType !== 'range' && (outputFormat === 'ISO8601' || outputFormat.toLowerCase().includes('h'))) { this.hasTimePicker = true; } @@ -372,7 +372,7 @@ export class DateFilter implements Filter { }; } - // add the time picker when format is UTC (Z) or has the 'h' (meaning hours) + // add the time picker when format includes time (hours/minutes) if (this.hasTimePicker) { pickerOptions.settings!.selection ??= {}; pickerOptions.settings!.selection.time = 24; From d470aaba804947726a5e742e652232a625e3a79f Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Mon, 6 May 2024 11:29:51 -0400 Subject: [PATCH 07/10] docs: add functions docs in comments --- packages/common/src/services/dateUtils.ts | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/common/src/services/dateUtils.ts b/packages/common/src/services/dateUtils.ts index 3d81f9584..e0188e7c8 100644 --- a/packages/common/src/services/dateUtils.ts +++ b/packages/common/src/services/dateUtils.ts @@ -3,8 +3,8 @@ import { format, parse, tzDate } from '@formkit/tempo'; import { FieldType } from '../enums/index'; /** - * From a Date FieldType, return it's equivalent TempoJS format - * refer to TempoJS for the format standard used: https://tempo.formkit.com/#format + * From a Date FieldType, return it's equivalent TempoJS format, + * refer to TempoJS docs for the format tokens being used: https://tempo.formkit.com/#format * @param fieldType * @param withZeroPadding - should we include zero padding in format (e.g.: 03:04:54) */ @@ -112,6 +112,13 @@ export function mapTempoDateFormatWithFieldType(fieldType: typeof FieldType[keyo return map; } +/** + * Format a date using Tempo and a defined input/output field types + * @param {string|Date} inputDate + * @param {FieldType} inputFieldType + * @param {FieldType} outputFieldType + * @returns + */ export function formatTempoDateByFieldType(inputDate: Date | string, inputFieldType: typeof FieldType[keyof typeof FieldType] | undefined, outputFieldType: typeof FieldType[keyof typeof FieldType]): string { const inputFormat = inputFieldType ? mapTempoDateFormatWithFieldType(inputFieldType) : undefined; const outputFormat = mapTempoDateFormatWithFieldType(outputFieldType); @@ -126,6 +133,15 @@ export function formatTempoDateByFieldType(inputDate: Date | string, inputFieldT return ''; } +/** + * Try to parse date with Tempo or return `false` (instead of throwing) if Date is invalid. + * When using strict mode, it will detect if the date is invalid when outside of the calendar (e.g. "2011-11-31"). + * However in non-strict mode, it will roll the date backward if out of calendar (e.g. "2011-11-31" would return "2011-11-30"). + * @param {string|Date} [inputDate] - input date (or null) + * @param {string} [inputFormat] - optional input format to use when parsing + * @param {Boolean} [strict] - are we using strict mode? + * @returns + */ export function tryParseDate(inputDate?: string | Date, inputFormat?: string, strict = false): Date | false { try { if (!inputDate) { From aac351c1d6593312fb84aa824f923b6fcaaabad2 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Mon, 6 May 2024 12:05:53 -0400 Subject: [PATCH 08/10] chore: use Tempo for parsing as UTC date --- packages/common/src/services/dateUtils.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/common/src/services/dateUtils.ts b/packages/common/src/services/dateUtils.ts index e0188e7c8..fe1dba371 100644 --- a/packages/common/src/services/dateUtils.ts +++ b/packages/common/src/services/dateUtils.ts @@ -1,4 +1,4 @@ -import { format, parse, tzDate } from '@formkit/tempo'; +import { format, offset, parse, removeOffset, tzDate } from '@formkit/tempo'; import { FieldType } from '../enums/index'; @@ -158,9 +158,14 @@ export function tryParseDate(inputDate?: string | Date, inputFormat?: string, st } } -export function toUtcDate(inputDate: Date) { - const localOffset = new Date().getTimezoneOffset() * 60 * 1000; - return new Date(inputDate.getTime() + localOffset); +/** + * Parse a Date as a UTC date (without local TZ offset) + * @param inputDate + * @returns + */ +export function toUtcDate(inputDate: string | Date) { + // to parse as UTC in Tempo, we need to remove the offset (which is a simple inversed offset to cancel itself) + return removeOffset(inputDate, offset(inputDate, 'utc')); }; /** From 10a0a286baa99cf5e76a708a589b2f9472dacdd8 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Mon, 6 May 2024 13:19:02 -0400 Subject: [PATCH 09/10] chore: cleanup code and rename date util function --- .../commonEditorFilter/commonEditorFilterUtils.ts | 4 ++-- packages/common/src/editors/dateEditor.ts | 10 +++++----- .../src/filters/__tests__/dateRangeFilter.spec.ts | 6 ------ packages/common/src/filters/dateFilter.ts | 12 ++++++------ packages/common/src/services/dateUtils.ts | 2 +- 5 files changed, 14 insertions(+), 20 deletions(-) diff --git a/packages/common/src/commonEditorFilter/commonEditorFilterUtils.ts b/packages/common/src/commonEditorFilter/commonEditorFilterUtils.ts index f771543e8..b433620e2 100644 --- a/packages/common/src/commonEditorFilter/commonEditorFilterUtils.ts +++ b/packages/common/src/commonEditorFilter/commonEditorFilterUtils.ts @@ -4,7 +4,7 @@ import type { IOptions } from 'vanilla-calendar-picker'; import type { AutocompleterOption, Column, ColumnEditor, ColumnFilter } from '../interfaces/index'; import { FieldType } from '../enums'; -import { formatTempoDateByFieldType, mapTempoDateFormatWithFieldType, tryParseDate } from '../services/dateUtils'; +import { formatDateByFieldType, mapTempoDateFormatWithFieldType, tryParseDate } from '../services/dateUtils'; /** * add loading class ".slick-autocomplete-loading" to the Kraaden Autocomplete input element @@ -57,6 +57,6 @@ export function setPickerDates(dateInputElm: HTMLInputElement, pickerOptions: IO time: inputFormat === 'ISO8601' || (inputFormat || '').toLowerCase().includes('h') ? format(pickerDates[0], 'HH:mm') : undefined, }; } - dateInputElm.value = initialDates.length ? pickerDates.map(p => formatTempoDateByFieldType(p, undefined, outputFieldType)).join(' — ') : ''; + dateInputElm.value = initialDates.length ? pickerDates.map(p => formatDateByFieldType(p, undefined, outputFieldType)).join(' — ') : ''; } } \ No newline at end of file diff --git a/packages/common/src/editors/dateEditor.ts b/packages/common/src/editors/dateEditor.ts index ceb6d2bfb..48a548188 100644 --- a/packages/common/src/editors/dateEditor.ts +++ b/packages/common/src/editors/dateEditor.ts @@ -20,7 +20,7 @@ import { getDescendantProperty, } from './../services/utilities'; import type { TranslaterService } from '../services/translater.service'; import { SlickEventData, type SlickGrid } from '../core/index'; import { setPickerDates } from '../commonEditorFilter'; -import { formatTempoDateByFieldType, mapTempoDateFormatWithFieldType } from '../services/dateUtils'; +import { formatDateByFieldType, mapTempoDateFormatWithFieldType } from '../services/dateUtils'; /* * An example of a date picker editor using Vanilla-Calendar-Picker @@ -132,7 +132,7 @@ export class DateEditor implements Editor { let selectedDate = ''; if (self.selectedDates[0]) { selectedDate = self.selectedDates[0]; - self.HTMLInputElement.value = formatTempoDateByFieldType(self.selectedDates[0], undefined, outputFieldType); + self.HTMLInputElement.value = formatDateByFieldType(self.selectedDates[0], undefined, outputFieldType); } else { self.HTMLInputElement.value = ''; } @@ -141,7 +141,7 @@ export class DateEditor implements Editor { const tempoDate = parse(selectedDate, pickerFormat); tempoDate.setHours(+(self.selectedHours || 0)); tempoDate.setMinutes(+(self.selectedMinutes || 0)); - self.HTMLInputElement.value = formatTempoDateByFieldType(tempoDate, undefined, outputFieldType); + self.HTMLInputElement.value = formatDateByFieldType(tempoDate, undefined, outputFieldType); } if (this._lastClickIsDate) { @@ -326,7 +326,7 @@ export class DateEditor implements Editor { // validate the value before applying it (if not valid we'll set an empty string) const validation = this.validate(null, state); - const newValue = (state && validation?.valid) ? formatTempoDateByFieldType(state, outputFieldType, saveFieldType) : ''; + const newValue = (state && validation?.valid) ? formatDateByFieldType(state, outputFieldType, saveFieldType) : ''; // set the new value to the item datacontext if (isComplexObject) { @@ -365,7 +365,7 @@ export class DateEditor implements Editor { const inputFieldType = this.columnEditor.type || this.columnDef?.type || FieldType.dateIso; const outputFieldType = this.columnDef.outputType || this.columnEditor.type || this.columnDef.type || FieldType.dateIso; - this._originalDate = formatTempoDateByFieldType(value, inputFieldType, outputFieldType); + this._originalDate = formatDateByFieldType(value, inputFieldType, outputFieldType); this._inputElm.value = this._originalDate; } } diff --git a/packages/common/src/filters/__tests__/dateRangeFilter.spec.ts b/packages/common/src/filters/__tests__/dateRangeFilter.spec.ts index f3cc3d914..4035d0ec2 100644 --- a/packages/common/src/filters/__tests__/dateRangeFilter.spec.ts +++ b/packages/common/src/filters/__tests__/dateRangeFilter.spec.ts @@ -55,12 +55,6 @@ describe('DateRangeFilter', () => { }; filter = new DateRangeFilter(translateService); - - // jest.spyOn(Intl, 'DateTimeFormat').mockImplementation(() => ({ - // resolvedOptions: () => ({ - // timeZone: 'America/New_yorkpp' - // }) - // })); }); afterEach(() => { diff --git a/packages/common/src/filters/dateFilter.ts b/packages/common/src/filters/dateFilter.ts index 6643f8fc2..0fbefdc3a 100644 --- a/packages/common/src/filters/dateFilter.ts +++ b/packages/common/src/filters/dateFilter.ts @@ -19,7 +19,7 @@ import type { OperatorDetail, } from '../interfaces/index'; import { buildSelectOperator, compoundOperatorNumeric } from './filterUtilities'; -import { formatTempoDateByFieldType, mapTempoDateFormatWithFieldType } from '../services/dateUtils'; +import { formatDateByFieldType, mapTempoDateFormatWithFieldType } from '../services/dateUtils'; import { mapOperatorToShorthandDesignation } from '../services/utilities'; import type { TranslaterService } from '../services/translater.service'; import type { SlickGrid } from '../core/index'; @@ -268,7 +268,7 @@ export class DateFilter implements Filter { // if we are preloading searchTerms, we'll keep them for reference if (Array.isArray(pickerValues)) { this._currentDateOrDates = pickerValues as Date[]; - this._currentDateStrings = pickerValues.map(date => formatTempoDateByFieldType(date, undefined, inputFieldType)); + this._currentDateStrings = pickerValues.map(date => formatDateByFieldType(date, undefined, inputFieldType)); } } @@ -298,7 +298,7 @@ export class DateFilter implements Filter { outDates = [firstDate, lastDate]; } else if (self.selectedDates[0]) { firstDate = self.selectedDates[0]; - self.HTMLInputElement.value = formatTempoDateByFieldType(firstDate, FieldType.dateIso, outputFieldType); + self.HTMLInputElement.value = formatDateByFieldType(firstDate, FieldType.dateIso, outputFieldType); outDates = self.selectedDates; } else { self.HTMLInputElement.value = ''; @@ -308,15 +308,15 @@ export class DateFilter implements Filter { const tempoDate = parse(firstDate, pickerFormat); tempoDate.setHours(+(self.selectedHours || 0)); tempoDate.setMinutes(+(self.selectedMinutes || 0)); - self.HTMLInputElement.value = formatTempoDateByFieldType(tempoDate, undefined, outputFieldType); + self.HTMLInputElement.value = formatDateByFieldType(tempoDate, undefined, outputFieldType); outDates = [tempoDate]; } if (this.inputFilterType === 'compound') { - this._currentValue = formatTempoDateByFieldType(outDates[0], undefined, columnFieldType); + this._currentValue = formatDateByFieldType(outDates[0], undefined, columnFieldType); } else { if (Array.isArray(outDates)) { - this._currentDateStrings = outDates.map(date => formatTempoDateByFieldType(date, undefined, columnFieldType)); + this._currentDateStrings = outDates.map(date => formatDateByFieldType(date, undefined, columnFieldType)); this._currentValue = this._currentDateStrings.join('..'); } } diff --git a/packages/common/src/services/dateUtils.ts b/packages/common/src/services/dateUtils.ts index fe1dba371..f70a411ac 100644 --- a/packages/common/src/services/dateUtils.ts +++ b/packages/common/src/services/dateUtils.ts @@ -119,7 +119,7 @@ export function mapTempoDateFormatWithFieldType(fieldType: typeof FieldType[keyo * @param {FieldType} outputFieldType * @returns */ -export function formatTempoDateByFieldType(inputDate: Date | string, inputFieldType: typeof FieldType[keyof typeof FieldType] | undefined, outputFieldType: typeof FieldType[keyof typeof FieldType]): string { +export function formatDateByFieldType(inputDate: Date | string, inputFieldType: typeof FieldType[keyof typeof FieldType] | undefined, outputFieldType: typeof FieldType[keyof typeof FieldType]): string { const inputFormat = inputFieldType ? mapTempoDateFormatWithFieldType(inputFieldType) : undefined; const outputFormat = mapTempoDateFormatWithFieldType(outputFieldType); const date = inputDate instanceof Date ? inputDate : tryParseDate(inputDate, inputFormat as string); From 9249c7b6060d1a250ccd71670fc4aad3039d3a72 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Mon, 6 May 2024 13:25:25 -0400 Subject: [PATCH 10/10] docs: improve migration docs --- docs/migrations/migration-to-5.x.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/migrations/migration-to-5.x.md b/docs/migrations/migration-to-5.x.md index 98e54062a..5e0f3b810 100644 --- a/docs/migrations/migration-to-5.x.md +++ b/docs/migrations/migration-to-5.x.md @@ -31,7 +31,8 @@ The goal of this new release was mainly to improve UI/UX (mostly for Dark Mode) - Bootstrap >=v5.x (or any other UI framework) - SASS >=v1.35 (`dart-sass`) - migrated from Flatpickr to Vanilla-Calendar (visit [Vanilla-Calendar-Pro](https://vanilla-calendar.pro/) for demos and docs) - - migrated from MomentJS to [Tempo](https://tempo.formkit.com/) + - migrated from MomentJS to [Tempo](https://tempo.formkit.com/) (by the FormKit +team) > **Note** for the entire list of tasks & code changes applied in this release, you may want to take a look at the [Roadmap to 5.0](https://github.com/ghiscoding/slickgrid-universal/discussions/1482) Discussion. @@ -244,6 +245,6 @@ this.gridOptions = { > **Note** If you're wondering about the `ADD_ATTR: ['level']`, well the "level" is a custom attribute used by SlickGrid Grouping/Draggable Grouping to track the grouping level depth and it must be kept. ### From MomentJS to Tempo -I wanted to replace MomentJS for a long time now (it's been deprecated for years and is CJS only), but it was really hard to find a good replacement (I tried DayJS, Luxon, date-fns and they all had problems)... and here comes [Tempo](https://tempo.formkit.com/)! With Tempo, I was finally able to migrate with the 2 most important functions for our usage in a datagrid are the `parse()` and `format()` functions. The library also has plenty of extra optional functions like `addDay()`, `diffDays()`, ... Another great thing about Tempo is that they use the same format tokens as MomentJS, so the conversion on that side was easy. +I wanted to replace MomentJS for a long time now (it's been deprecated for years and is CJS only), but it was really hard to find a good replacement (I tried DayJS, Luxon, date-fns and they all had problems)... and here comes [Tempo](https://tempo.formkit.com/)! With Tempo, I was finally able to migrate by taking advantage of `parse()` and `format()` Tempo functions which are the most important for our use case. The library also has plenty of extra optional functions as well, like `addDay()`, `diffDays()`, ... Another great thing about Tempo is that they use the same format [tokens](https://tempo.formkit.com/#format-tokens) as MomentJS, so the conversion on that side was super easy. -This migration should be transparent to the user, however if you were using MomentJS then please consider trying [Tempo](https://tempo.formkit.com/) in your project as well to modernize your project and also lower your dependencies count. The other great advantage of Tempo is that it's ESM and it helps a lot in decreasing our build size footprint because of ESM Tree Shacking feature. \ No newline at end of file +This migration should be transparent to most users like you, however if you were using MomentJS then I would suggest to consider trying [Tempo](https://tempo.formkit.com/) in your project in order to modernize your project and also lower your dependencies count. The other great advantage of Tempo is that it's ESM and it helps a lot in decreasing our build size footprint because of ESM Tree Shacking feature. \ No newline at end of file