diff --git a/projects/igniteui-angular/calendar/src/calendar/day-digit.pipe.ts b/projects/igniteui-angular/calendar/src/calendar/day-digit.pipe.ts new file mode 100644 index 00000000000..392ddbf98b5 --- /dev/null +++ b/projects/igniteui-angular/calendar/src/calendar/day-digit.pipe.ts @@ -0,0 +1,26 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { IFormattingViews } from "./calendar"; + +@Pipe({ + name: 'dayDigit', + standalone: true +}) +export class DayDigitPipe implements PipeTransform { + public transform(value: string, formatViews: IFormattingViews): string { + if (!value) { + return ''; + } + + // strip non-numeric characters that might have been added by the locale formatter (e.g., "25日" -> "25"). + if (formatViews.day) { + // Use regex to extract the numeric day value. + // This handles locales that include non-numeric characters (e.g. '25日' in zh-CN). + // match(/\d+/) is preferred over parseInt() as it robustly finds the digits regardless + // of their position (prefix/suffix) in the localized string. + const match = value.match(/\d+/); + return match ? match[0] : value; + } + + return value; + } +} diff --git a/projects/igniteui-angular/calendar/src/calendar/days-view/days-view.component.html b/projects/igniteui-angular/calendar/src/calendar/days-view/days-view.component.html index 96a403d93b3..a1ee994727d 100644 --- a/projects/igniteui-angular/calendar/src/calendar/days-view/days-view.component.html +++ b/projects/igniteui-angular/calendar/src/calendar/days-view/days-view.component.html @@ -81,7 +81,7 @@ (mouseEnter)="changePreviewRange(day.native)" (mouseLeave)="clearPreviewRange()" > - {{ formattedDate(day.native) }} + {{ formattedDate(day.native) | dayDigit:formatViews }} } diff --git a/projects/igniteui-angular/calendar/src/calendar/days-view/days-view.component.spec.ts b/projects/igniteui-angular/calendar/src/calendar/days-view/days-view.component.spec.ts index cf2fac8ad27..33397177da8 100644 --- a/projects/igniteui-angular/calendar/src/calendar/days-view/days-view.component.spec.ts +++ b/projects/igniteui-angular/calendar/src/calendar/days-view/days-view.component.spec.ts @@ -7,6 +7,7 @@ import { ScrollDirection } from "../calendar"; import { KeyboardNavigationService } from '../calendar.services'; import { CalendarDay } from 'igniteui-angular/core'; import { UIInteractions } from '../../../../test-utils/ui-interactions.spec'; +import { DayDigitPipe } from "igniteui-angular/calendar/src/calendar/day-digit.pipe"; const TODAY = new Date(2024, 6, 12); @@ -114,6 +115,37 @@ describe("Days View Component", () => { } }); + it("should format date correctly for zh-CN locale programmatically vs template pipe", () => { + const fixture = TestBed.createComponent(InitDaysViewComponent); + const daysView = fixture.componentInstance.instance; + const pipe = new DayDigitPipe(); + const date = new Date(2020, 10, 25); // Nov 25 + + // Initialize component + daysView.formatViews = { day: true, month: true, year: true }; + fixture.detectChanges(); + + // Mock the formatter behavior + // Simulate a locale (like zh-CN) that adds a suffix to the day number. + // Cast to 'any' to overwrite the protected 'formatterDay' property used by formattedDate() + (daysView as any).formatterDay = { + format: () => '25日', + } as Intl.DateTimeFormat; + + // 1. Verify Programmatic Access (formattedDate method) + // Should return the raw formatted string from the formatter (with suffix) + const programmaticResult = daysView.formattedDate(date); + expect(programmaticResult).toBe('25日', 'Programmatic API should return the full locale string (including suffix, in this case 日)'); + + // 2. Verify Pipe Logic + // The pipe takes the formatted string "25日" and strips non-digits to return "25" + const pipeResult = pipe.transform(programmaticResult, daysView.formatViews); + expect(pipeResult).toBe('25', 'Pipe should strip non-numeric characters from the input string'); + + // 3. Confirm the difference implies the pipe did its job + expect(programmaticResult).not.toEqual(pipeResult); + }); + describe("Keyboard navigation", () => { let fixture: ComponentFixture; let el: HTMLElement; diff --git a/projects/igniteui-angular/calendar/src/calendar/days-view/days-view.component.ts b/projects/igniteui-angular/calendar/src/calendar/days-view/days-view.component.ts index d704c218e5f..56343ead24e 100644 --- a/projects/igniteui-angular/calendar/src/calendar/days-view/days-view.component.ts +++ b/projects/igniteui-angular/calendar/src/calendar/days-view/days-view.component.ts @@ -37,6 +37,7 @@ import { import { IgxCalendarBaseDirective } from '../calendar-base'; import { IViewChangingEventArgs } from './days-view.interface'; import { KeyboardNavigationService } from '../calendar.services'; +import { DayDigitPipe } from "../day-digit.pipe"; let NEXT_ID = 0; @@ -52,7 +53,7 @@ let NEXT_ID = 0; selector: 'igx-days-view', templateUrl: 'days-view.component.html', changeDetection: ChangeDetectionStrategy.OnPush, - imports: [IgxDayItemComponent, TitleCasePipe] + imports: [IgxDayItemComponent, TitleCasePipe, DayDigitPipe] }) export class IgxDaysViewComponent extends IgxCalendarBaseDirective implements AfterContentChecked { protected el = inject(ElementRef); diff --git a/projects/igniteui-angular/core/src/core/styles/components/calendar/_calendar-theme.scss b/projects/igniteui-angular/core/src/core/styles/components/calendar/_calendar-theme.scss index cf67ef36662..9492cf98962 100644 --- a/projects/igniteui-angular/core/src/core/styles/components/calendar/_calendar-theme.scss +++ b/projects/igniteui-angular/core/src/core/styles/components/calendar/_calendar-theme.scss @@ -2516,6 +2516,7 @@ letter-spacing: sizable(var(--ig-body-2-letter-spacing), var(--ig-body-2-letter-spacing), var(--ig-body-1-letter-spacing)); text-transform: sizable(var(--ig-body-2-text-transform), var(--ig-body-2-text-transform), var(--ig-body-1-text-transform)); margin: 0; + white-space: nowrap; } } @@ -2528,6 +2529,7 @@ letter-spacing: sizable(var(--ig-body-2-letter-spacing), var(--ig-body-2-letter-spacing), var(--ig-body-1-letter-spacing)); text-transform: sizable(var(--ig-body-2-text-transform), var(--ig-body-2-text-transform), var(--ig-body-1-text-transform)); margin: 0; + white-space: nowrap; } } } diff --git a/src/app/calendar/calendar.sample.html b/src/app/calendar/calendar.sample.html index b381413e398..f56f8b3fff0 100644 --- a/src/app/calendar/calendar.sample.html +++ b/src/app/calendar/calendar.sample.html @@ -14,6 +14,7 @@ [showWeekNumbers]="properties.showWeekNumbers" [hasHeader]="!properties.hideHeader" [formatOptions]="formatOptions" + [formatViews]="formatViews" [disabledDates]="disabledDates" [specialDates]="specialDates" (selected)="onSelection($event)" diff --git a/src/app/calendar/calendar.sample.ts b/src/app/calendar/calendar.sample.ts index b24ae47380c..83f6c5b9b6d 100644 --- a/src/app/calendar/calendar.sample.ts +++ b/src/app/calendar/calendar.sample.ts @@ -19,6 +19,7 @@ import { DateRange, DateRangeDescriptor, DateRangeType, + IFormattingViews, } from 'igniteui-angular'; import { Properties, @@ -62,6 +63,12 @@ export class CalendarSampleComponent implements OnInit { year: 'numeric', }; + protected formatViews: IFormattingViews = { + day: true, + month: true, + year: true + }; + public panelConfig: PropertyPanelConfig = { locale: { label: 'Change Locale', @@ -87,6 +94,10 @@ export class CalendarSampleComponent implements OnInit { { value: 'ja-JP', label: 'JP' + }, + { + value: 'zh-CN', + label: 'CN' } ], defaultValue: 'en-US'