diff --git a/projects/igniteui-angular/src/lib/date-common/util/date-time.util.ts b/projects/igniteui-angular/src/lib/date-common/util/date-time.util.ts index d4b1094ce97..3893c3627c2 100644 --- a/projects/igniteui-angular/src/lib/date-common/util/date-time.util.ts +++ b/projects/igniteui-angular/src/lib/date-common/util/date-time.util.ts @@ -199,6 +199,25 @@ export abstract class DateTimeUtil { return formattedDate; } + /** Adjusts the given date to or from the specified timezone. */ + public static adjustDate(timezone: string, value: Date, isDateOnly: boolean, reverse: boolean = false): any { + const reverseValue = reverse ? -1 : 1; + const inputTimezoneOffset = value.getTimezoneOffset(); + const columnTimezoneOffset = this.timezoneToOffset(timezone, inputTimezoneOffset); + const offsetDifference = reverseValue * (columnTimezoneOffset - inputTimezoneOffset); + + if (isDateOnly) { + // When column timezone is left of the locale timezone (date is shifted one day before), add 1 day + if (columnTimezoneOffset - inputTimezoneOffset > 0) { + return new Date(value.setDate(value.getDate() + 1)) + } + + return value; + } + + return this.addDateMinutes(value, offsetDifference); + } + /** * Returns the date format based on a provided locale. * Supports Angular's DatePipe format options such as `shortDate`, `longDate`. @@ -619,4 +638,13 @@ export abstract class DateTimeUtil { } } } + + private static timezoneToOffset(timezone: string, fallback: number): number { + const requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; + return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset; + } + + private static addDateMinutes(date: Date, minutes: number): Date { + return new Date(date.getTime() + minutes * 60000); + } } diff --git a/projects/igniteui-angular/src/lib/grids/filtering/base/grid-filtering-row.component.ts b/projects/igniteui-angular/src/lib/grids/filtering/base/grid-filtering-row.component.ts index 4a85b74e271..a5bb9338548 100644 --- a/projects/igniteui-angular/src/lib/grids/filtering/base/grid-filtering-row.component.ts +++ b/projects/igniteui-angular/src/lib/grids/filtering/base/grid-filtering-row.component.ts @@ -15,6 +15,7 @@ import { OnDestroy } from '@angular/core'; import { GridColumnDataType, DataUtil } from '../../../data-operations/data-util'; +import { DateTimeUtil } from '../../../date-common/util/date-time.util'; import { IgxDropDownComponent } from '../../../drop-down/drop-down.component'; import { IFilteringOperation } from '../../../data-operations/filtering-condition'; import { FilteringLogic, IFilteringExpression } from '../../../data-operations/filtering-expression.interface'; @@ -107,7 +108,9 @@ export class IgxGridFilteringRowComponent implements AfterViewInit, OnDestroy { } this._value = val; - this.expression.searchVal = DataUtil.parseValue(this.column.dataType, val); + const isDateOnly = this.column.dataType === GridColumnDataType.Date; + const isDate = isDateOnly || this.column.dataType === GridColumnDataType.DateTime; + this.expression.searchVal = isDate ? DateTimeUtil.adjustDate(this.column.pipeArgs.timezone, val, isDateOnly) : DataUtil.parseValue(this.column.dataType, val); if (this.expressionsList.find(item => item.expression === this.expression) === undefined) { this.addExpression(true); } @@ -241,7 +244,10 @@ export class IgxGridFilteringRowComponent implements AfterViewInit, OnDestroy { const selectedItem = this.expressionsList.find(expr => expr.isSelected === true); if (selectedItem) { this.expression = selectedItem.expression; - this._value = this.expression.searchVal; + const isDateOnly = this.column.dataType === GridColumnDataType.Date; + const isDate = isDateOnly || this.column.dataType === GridColumnDataType.DateTime; + const adjustedSearchVal = isDate ? DateTimeUtil.adjustDate(this.column.pipeArgs.timezone, this.expression.searchVal, isDateOnly, true) : this.expression.searchVal + this._value = adjustedSearchVal; } this.filteringService.grid.localeChange @@ -639,7 +645,10 @@ export class IgxGridFilteringRowComponent implements AfterViewInit, OnDestroy { item.isSelected = !item.isSelected; if (item.isSelected) { this.expression = item.expression; - this._value = this.expression.searchVal; + const isDateOnly = this.column.dataType === GridColumnDataType.Date; + const isDate = isDateOnly || this.column.dataType === GridColumnDataType.DateTime; + const adjustedSearchVal = isDate ? DateTimeUtil.adjustDate(this.column.pipeArgs.timezone, this.expression.searchVal, isDateOnly, true) : this.expression.searchVal + this._value = adjustedSearchVal; this.focusEditElement(); } } diff --git a/projects/igniteui-angular/src/lib/grids/filtering/grid-filtering.service.ts b/projects/igniteui-angular/src/lib/grids/filtering/grid-filtering.service.ts index 2446c743f4f..e20263df5d4 100644 --- a/projects/igniteui-angular/src/lib/grids/filtering/grid-filtering.service.ts +++ b/projects/igniteui-angular/src/lib/grids/filtering/grid-filtering.service.ts @@ -464,7 +464,7 @@ export class IgxFilteringService implements OnDestroy { return formatter(expression.searchVal, undefined); } const pipeArgs = column.pipeArgs; - return formatDate(expression.searchVal, pipeArgs.format, this.grid.locale); + return formatDate(expression.searchVal, pipeArgs.format, this.grid.locale, pipeArgs.timezone); } else { return expression.searchVal; } diff --git a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-ui.spec.ts b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-ui.spec.ts index 2892eccdb7c..2692d222702 100644 --- a/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-ui.spec.ts +++ b/projects/igniteui-angular/src/lib/grids/grid/grid-filtering-ui.spec.ts @@ -374,6 +374,93 @@ describe('IgxGrid - Filtering Row UI actions #grid', () => { expect(grid.rowList.length).toEqual(1); })); + it('should correctly filter dateTime column by \'equals\' filtering condition, with applied timezone', fakeAsync(() => { + const column = grid.getColumnByName('ReleaseDate'); + + column.pipeArgs = { + timezone: "GMT-12" + }; + + GridFunctions.clickFilterCellChip(fix, 'ReleaseDate'); + fix.detectChanges(); + + const filteringRow = fix.debugElement.query(By.directive(IgxGridFilteringRowComponent)); + const reset = filteringRow.queryAll(By.css('button'))[0]; + const inputDebugElement = filteringRow.query(By.directive(IgxInputDirective)); + const input = inputDebugElement.nativeElement; + input.click(); + tick(100); + fix.detectChanges(); + + let outlet = document.getElementsByClassName('igx-grid__outlet')[0]; + let calendar = outlet.getElementsByClassName('igx-calendar')[0]; + let todayDayItem: HTMLElement = calendar.querySelector('.igx-days-view__date--current'); + todayDayItem.firstChild.dispatchEvent(new Event('mousedown')); + grid.filteringRow.onInputGroupFocusout(); + tick(100); + fix.detectChanges(); + + // Clicking today's date should not return any results because GMT-7 would shift today's date in the previous one + expect(grid.rowList.length).toEqual(0); + + // Reset filtering and open the datePicker again + reset.nativeElement.click(); + flush(); + fix.detectChanges(); + + input.click(); + tick(100); + fix.detectChanges(); + + outlet = document.getElementsByClassName('igx-grid__outlet')[0]; + calendar = outlet.getElementsByClassName('igx-calendar')[0]; + todayDayItem = calendar.querySelector('.igx-days-view__date--current'); + + // From today's date, try to select the previous date + const dateRow: HTMLElement = todayDayItem.closest('.igx-days-view__row'); + const daysInRow = Array.from(dateRow.getElementsByClassName('igx-days-view__date')); + const currentIndex = daysInRow.indexOf(todayDayItem); + let previousDay: Element; + + // If previous day in the same week, select it + if (currentIndex > 0) { + previousDay = daysInRow[currentIndex - 1]; + } else { + // If previous day is in last week, select the previous week row and the last date + const previousRow = dateRow.previousElementSibling; + if (previousRow) { + const daysInPreviousRow = Array.from(previousRow.getElementsByClassName('igx-days-view__date')); + previousDay = daysInPreviousRow[daysInPreviousRow.length - 1]; + } else { + // If previous day is in last month, switch the month and select the last active date + const previousMonth = fix.debugElement.query(By.css('.igx-calendar-picker__prev')); + UIInteractions.simulateMouseDownEvent(previousMonth.nativeElement); + tick(100); + fix.detectChanges(); + + const weekRows = Array.from(calendar.querySelectorAll('.igx-days-view__row')); + + // If all days are inactive (from next month), select the previous week and check it instead. + for (let i = weekRows.length - 1; i >= 0; i--) { + const daysInWeek = Array.from(weekRows[i].getElementsByClassName('igx-days-view__date')); + const activeDays = daysInWeek.filter(day => !day.classList.contains('igx-days-view__date--inactive')); + + if (activeDays.length > 0) { + previousDay = activeDays[activeDays.length - 1]; + break; + } + } + } + } + + previousDay.firstChild.dispatchEvent(new Event('mousedown')); + grid.filteringRow.onInputGroupFocusout(); + tick(100); + fix.detectChanges(); + + expect(grid.rowList.length).toEqual(1); + })); + it('Should correctly select month from month view datepicker/calendar component', fakeAsync(() => { pending('This should be tested in the e2e test'); const filteringCells = fix.debugElement.queryAll(By.css(FILTER_UI_CELL)); diff --git a/projects/igniteui-angular/src/lib/test-utils/sample-test-data.spec.ts b/projects/igniteui-angular/src/lib/test-utils/sample-test-data.spec.ts index d1b3abd06f5..5adb0265b75 100644 --- a/projects/igniteui-angular/src/lib/test-utils/sample-test-data.spec.ts +++ b/projects/igniteui-angular/src/lib/test-utils/sample-test-data.spec.ts @@ -1902,7 +1902,7 @@ export class SampleTestData { Downloads: 254, ID: 1, ProductName: 'Ignite UI for JavaScript', - ReleaseDate: SampleTestData.timeGenerator.timedelta(SampleTestData.today, 'day', 1), + ReleaseDate: SampleTestData.timeGenerator.timedelta(SampleTestData.today, 'day', 2), ReleaseDateTime: SampleTestData.timeGenerator.timedelta(SampleTestData.todayFullDate, 'hour', 1), ReleaseTime: SampleTestData.timeGenerator.timedelta(SampleTestData.todayFullDate, 'hour', 1), Released: false, @@ -1957,7 +1957,7 @@ export class SampleTestData { Downloads: 702, ID: 6, ProductName: 'Some other item with Script', - ReleaseDate: SampleTestData.timeGenerator.timedelta(SampleTestData.today, 'day', 1), + ReleaseDate: SampleTestData.timeGenerator.timedelta(SampleTestData.today, 'day', 2), ReleaseDateTime: SampleTestData.timeGenerator.timedelta(SampleTestData.todayFullDate, 'second', 20), ReleaseTime: SampleTestData.timeGenerator.timedelta(SampleTestData.todayFullDate, 'second', 20), Released: null,