Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ describe(`DateTimeUtil Unit tests`, () => {

it('should properly build input formats based on locale for dateTime data type ', () => {
let result = DateTimeUtil.getDefaultInputFormat('en-US', DataType.DateTime);
expect(result.normalize('NFKC')).toEqual('MM/dd/yyyy, hh:mm:ss tt');
expect(result.normalize('NFKC')).toEqual('MM/dd/yyyy, hh:mm:ss a');

result = DateTimeUtil.getDefaultInputFormat('bg-BG', DataType.DateTime);
expect(result.normalize('NFKC')).toEqual('dd.MM.yyyy г., HH:mm:ss');
Expand All @@ -250,7 +250,7 @@ describe(`DateTimeUtil Unit tests`, () => {

it('should properly build input formats based on locale for time data type ', () => {
let result = DateTimeUtil.getDefaultInputFormat('en-US', DataType.Time);
expect(result.normalize('NFKC')).toEqual('hh:mm tt');
expect(result.normalize('NFKC')).toEqual('hh:mm a');

result = DateTimeUtil.getDefaultInputFormat('bg-BG', DataType.Time);
expect(result.normalize('NFKC')).toEqual('HH:mm');
Expand Down
215 changes: 6 additions & 209 deletions projects/igniteui-angular/src/lib/date-common/util/date-time.util.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { DatePart, DatePartInfo } from '../../directives/date-time-editor/date-time-editor.common';
import { formatDate, FormatWidth, getLocaleDateFormat } from '@angular/common';
import { ValidationErrors } from '@angular/forms';
import { isDate } from '../../core/utils';
import { formatDate, getLocaleDateFormat, isDate } from '../../core/utils';
import { DataType } from '../../data-operations/data-util';
import { getDateFormatter } from 'igniteui-i18n-core';

/** @hidden */
const enum FormatDesc {
Expand Down Expand Up @@ -250,55 +250,16 @@ export abstract class DateTimeUtil {
/** Builds a date-time editor's default input format based on provided locale settings and data type. */
public static getDefaultInputFormat(locale: string, dataType: DataType = DataType.Date): string {
locale = locale || DateTimeUtil.DEFAULT_LOCALE;
if (!Intl || !Intl.DateTimeFormat || !Intl.DateTimeFormat.prototype.formatToParts) {
// TODO: fallback with Intl.format for IE?
return DateTimeUtil.DEFAULT_INPUT_FORMAT;
}
const parts = DateTimeUtil.getDefaultLocaleMask(locale, dataType);
parts.forEach(p => {
if (p.type !== DatePart.Year && p.type !== DateTimeUtil.SEPARATOR && p.type !== DatePart.AmPm) {
p.formatType = FormatDesc.TwoDigits;
}
});

return DateTimeUtil.getMask(parts);
return getDateFormatter().getLocaleDateTimeFormat(locale, true, DateTimeUtil.getFormatOptions(dataType));
}

/** Tries to format a date using Angular's DatePipe. Fallbacks to `Intl` if no locale settings have been loaded. */
public static formatDate(value: number | Date, format: string, locale: string, timezone?: string): string {
let formattedDate: string;
try {
formattedDate = formatDate(value, format, locale, timezone);
} catch {
DateTimeUtil.logMissingLocaleSettings(locale);
const formatter = new Intl.DateTimeFormat(locale);
formattedDate = formatter.format(value);
}

return formattedDate;
}

/**
* Returns the date format based on a provided locale.
* Supports Angular's DatePipe format options such as `shortDate`, `longDate`.
*/
public static getLocaleDateFormat(locale: string, displayFormat?: string): string {
Copy link
Member

@skrustev skrustev Sep 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are also a few methods using Intl.DateTimeFormat that can be replaced to use the new date formatters from igniteui-i18n-core. For example formatDate(should use the util.js format date instead), getDefaultInputFormat, getLocaleInputFormatFromParts and methods that use them maybe as well.

const formatKeys = Object.keys(FormatWidth) as (keyof FormatWidth)[];
const targetKey = formatKeys.find(k => k.toLowerCase() === displayFormat?.toLowerCase().replace('date', ''));
if (!targetKey) {
// if displayFormat is not shortDate, longDate, etc.
// or if it is not set by the user
return displayFormat;
}
let format: string;
try {
format = getLocaleDateFormat(locale, FormatWidth[targetKey]);
} catch {
DateTimeUtil.logMissingLocaleSettings(locale);
format = DateTimeUtil.getDefaultInputFormat(locale);
}

return format;
return getLocaleDateFormat(locale, displayFormat);
}

/** Determines if a given character is `d/M/y` or `h/m/s`. */
Expand Down Expand Up @@ -578,10 +539,8 @@ export abstract class DateTimeUtil {
options[p] = FormatDesc.TwoDigits;
}
});
const formatter = new Intl.DateTimeFormat(locale, options);
const dateStruct = DateTimeUtil.getDateStructFromParts(formatter.formatToParts(new Date()), formatter);
DateTimeUtil.fillDatePartsPositions(dateStruct);
return DateTimeUtil.getMask(dateStruct);

return getDateFormatter().getLocaleDateTimeFormat(locale, true, options);
}

private static addCurrentPart(currentPart: DatePartInfo, dateTimeParts: DatePartInfo[]): void {
Expand All @@ -599,70 +558,6 @@ export abstract class DateTimeUtil {
return result;
}

private static getMask(dateStruct: any[]): string {
const mask = [];
for (const part of dateStruct) {
if (part.formatType === FormatDesc.Numeric) {
switch (part.type) {
case DateParts.Day:
mask.push('d');
break;
case DateParts.Month:
mask.push('M');
break;
case DateParts.Year:
mask.push('yyyy');
break;
case DateParts.Hour:
mask.push(part.hour12 ? 'h' : 'H');
break;
case DateParts.Minute:
mask.push('m');
break;
case DateParts.Second:
mask.push('s');
break;
}
} else if (part.formatType === FormatDesc.TwoDigits) {
switch (part.type) {
case DateParts.Day:
mask.push('dd');
break;
case DateParts.Month:
mask.push('MM');
break;
case DateParts.Year:
mask.push('yy');
break;
case DateParts.Hour:
mask.push(part.hour12 ? 'hh' : 'HH');
break;
case DateParts.Minute:
mask.push('mm');
break;
case DateParts.Second:
mask.push('ss');
break;
}
}

if (part.type === DateParts.AmPm) {
mask.push('tt');
}

if (part.type === DateTimeUtil.SEPARATOR) {
mask.push(part.value);
}
}

return mask.join('');
}

private static logMissingLocaleSettings(locale: string): void {
console.warn(`Missing locale data for the locale ${locale}. Please refer to https://angular.io/guide/i18n#i18n-pipes`);
console.warn('Using default browser locale settings.');
}

private static prependValue(value: number, partLength: number, prependChar: string): string {
return (prependChar + value.toString()).slice(-partLength);
}
Expand Down Expand Up @@ -752,102 +647,4 @@ export abstract class DateTimeUtil {
return { };
}
}

private static getDefaultLocaleMask(locale: string, dataType: DataType = DataType.Date) {
const options = DateTimeUtil.getFormatOptions(dataType);
const formatter = new Intl.DateTimeFormat(locale, options);
const formatToParts = formatter.formatToParts(new Date());
const dateStruct = DateTimeUtil.getDateStructFromParts(formatToParts, formatter);
DateTimeUtil.fillDatePartsPositions(dateStruct);
return dateStruct;
}

private static getDateStructFromParts(parts: Intl.DateTimeFormatPart[], formatter: Intl.DateTimeFormat): any[] {
const dateStruct = [];
for (const part of parts) {
if (part.type === DateTimeUtil.SEPARATOR) {
dateStruct.push({
type: DateTimeUtil.SEPARATOR,
value: part.value
});
} else {
dateStruct.push({
type: part.type
});
}
}
const formatterOptions = formatter.resolvedOptions();
for (const part of dateStruct) {
switch (part.type) {
case DateParts.Day: {
part.formatType = formatterOptions.day;
break;
}
case DateParts.Month: {
part.formatType = formatterOptions.month;
break;
}
case DateParts.Year: {
part.formatType = formatterOptions.year;
break;
}
case DateParts.Hour: {
part.formatType = formatterOptions.hour;
if (formatterOptions.hour12) {
part.hour12 = true;
}
break;
}
case DateParts.Minute: {
part.formatType = formatterOptions.minute;
break;
}
case DateParts.Second: {
part.formatType = formatterOptions.second;
break;
}
case DateParts.AmPm: {
part.formatType = formatterOptions.dayPeriod;
break;
}
}
}
return dateStruct;
}

private static fillDatePartsPositions(dateArray: any[]): void {
let currentPos = 0;

for (const part of dateArray) {
// Day|Month|Hour|Minute|Second|AmPm part positions
if (part.type === DateParts.Day || part.type === DateParts.Month ||
part.type === DateParts.Hour || part.type === DateParts.Minute || part.type === DateParts.Second ||
part.type === DateParts.AmPm
) {
// Offset 2 positions for number
part.position = [currentPos, currentPos + 2];
currentPos += 2;
} else if (part.type === DateParts.Year) {
// Year part positions
switch (part.formatType) {
case FormatDesc.Numeric: {
// Offset 4 positions for full year
part.position = [currentPos, currentPos + 4];
currentPos += 4;
break;
}
case FormatDesc.TwoDigits: {
// Offset 2 positions for short year
part.position = [currentPos, currentPos + 2];
currentPos += 2;
break;
}
}
} else if (part.type === DateTimeUtil.SEPARATOR) {
// Separator positions
part.position = [currentPos, currentPos + 1];
currentPos++;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { IgxInputGroupComponent } from '../input-group/input-group.component';
import { IgxInputGroupBase } from '../input-group/input-group.common';
import { DateTimeUtil } from '../date-common/util/date-time.util';
import { IgxDateTimeEditorDirective } from '../directives/date-time-editor/public_api';
import { isDate } from '../core/utils';
import { formatDate, isDate } from '../core/utils';
import { IgxIconComponent } from '../icon/icon.component';
import { IgxSuffixDirective } from '../directives/suffix/suffix.directive';
import { IgxButtonDirective } from '../directives/button/button.directive';
Expand Down Expand Up @@ -44,8 +44,8 @@ export class DateRangePickerFormatPipe implements PipeTransform {
if (!isDate(end)) {
end = DateTimeUtil.parseIsoDate(end);
}
const startDate = appliedFormat ? DateTimeUtil.formatDate(start, appliedFormat, locale || 'en') : start?.toLocaleDateString();
const endDate = appliedFormat ? DateTimeUtil.formatDate(end, appliedFormat, locale || 'en') : end?.toLocaleDateString();
const startDate = appliedFormat ? formatDate(start, appliedFormat, locale || 'en') : start?.toLocaleDateString();
const endDate = appliedFormat ? formatDate(end, appliedFormat, locale || 'en') : end?.toLocaleDateString();
let formatted;
if (start) {
formatted = `${startDate} - `;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from '@angular/forms';
import { IgxMaskDirective } from '../mask/mask.directive';
import { MaskParsingService } from '../mask/mask-parsing.service';
import { isDate, PlatformUtil } from '../../core/utils';
import { formatDate, isDate, PlatformUtil } from '../../core/utils';
import { IgxDateTimeEditorEventArgs, DatePartInfo, DatePart } from './date-time-editor.common';
import { noop } from 'rxjs';
import { DatePartDeltas } from './date-time-editor.common';
Expand Down Expand Up @@ -139,7 +139,7 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnCh
}

public get displayFormat(): string {
return this._displayFormat || this.inputFormat;
return this._displayFormat || this._inputFormat || DateTimeUtil.getLocaleDateFormat(this.locale, 'short');
}

/**
Expand Down Expand Up @@ -549,7 +549,7 @@ export class IgxDateTimeEditorDirective extends IgxMaskDirective implements OnCh
}
const format = this.displayFormat || this.inputFormat;
if (format) {
this.inputValue = DateTimeUtil.formatDate(this.dateValue, format.replace('tt', 'aa'), this.locale);
this.inputValue = formatDate(this.dateValue, format.replace('tt', 'aa'), this.locale);
} else {
this.inputValue = this.dateValue.toLocaleString();
}
Expand Down