diff --git a/aio/content/guide/deprecations.md b/aio/content/guide/deprecations.md
index c62407cf5173bd..043e2e26b67c17 100644
--- a/aio/content/guide/deprecations.md
+++ b/aio/content/guide/deprecations.md
@@ -85,6 +85,7 @@ v14 - v17
| `@angular/service-worker` | [`SwUpdate#available`](api/service-worker/SwUpdate#available) | v16 |
| template syntax | [`/deep/`, `>>>`, and `::ng-deep`](#deep-component-style-selector) | unspecified |
| template syntax | [`bind-`, `on-`, `bindon-`, and `ref-`](#bind-syntax) | v15 |
+| `@angular/common` | [`DatePipe` - `DATE_PIPE_DEFAULT_TIMEZONE`](api/common/DATE_PIPE_DEFAULT_TIMEZONE) | v17 |
For information about Angular CDK and Angular Material deprecations, see the [changelog](https://github.com/angular/components/blob/main/CHANGELOG.md).
@@ -107,6 +108,7 @@ In the [API reference section](api) of this site, deprecated APIs are indicated
|:--- |:--- |:--- |:--- |
| [`CurrencyPipe` - `DEFAULT_CURRENCY_CODE`](api/common/CurrencyPipe#currency-code-deprecation) | `{provide: DEFAULT_CURRENCY_CODE, useValue: 'USD'}` | v9 | From v11 the default code will be extracted from the locale data given by `LOCALE_ID`, rather than `USD`. |
| [`NgComponentOutlet.ngComponentOutletNgModuleFactory`](api/common/NgComponentOutlet) | `NgComponentOutlet.ngComponentOutletNgModule` | v14 | Use the `ngComponentOutletNgModule` input instead. This input doesn't require resolving NgModule factory. |
+| [`DatePipe` - `DATE_PIPE_DEFAULT_TIMEZONE`](api/common/DATE_PIPE_DEFAULT_TIMEZONE) |`{ provide: DATE_PIPE_DEFAULT_OPTIONS, useValue: { timezone: '-1200' }` | v15 | Use the `DATE_PIPE_DEFAULT_OPTIONS` injection token, which can configure multiple settings at once instead. |
diff --git a/goldens/public-api/common/index.md b/goldens/public-api/common/index.md
index 106db1554959dd..368263a16a3943 100644
--- a/goldens/public-api/common/index.md
+++ b/goldens/public-api/common/index.md
@@ -76,11 +76,14 @@ export class CurrencyPipe implements PipeTransform {
}
// @public
+export const DATE_PIPE_DEFAULT_OPTIONS: InjectionToken;
+
+// @public @deprecated
export const DATE_PIPE_DEFAULT_TIMEZONE: InjectionToken;
// @public
export class DatePipe implements PipeTransform {
- constructor(locale: string, defaultTimezone?: string | null | undefined);
+ constructor(locale: string, defaultTimezone?: string | null | undefined, defaultOptions?: DatePipeConfig | null | undefined);
// (undocumented)
transform(value: Date | string | number, format?: string, timezone?: string, locale?: string): string | null;
// (undocumented)
@@ -88,11 +91,19 @@ export class DatePipe implements PipeTransform {
// (undocumented)
transform(value: Date | string | number | null | undefined, format?: string, timezone?: string, locale?: string): string | null;
// (undocumented)
- static ɵfac: i0.ɵɵFactoryDeclaration;
+ static ɵfac: i0.ɵɵFactoryDeclaration;
// (undocumented)
static ɵpipe: i0.ɵɵPipeDeclaration;
}
+// @public
+export interface DatePipeConfig {
+ // (undocumented)
+ dateFormat: string;
+ // (undocumented)
+ timezone: string;
+}
+
// @public
export class DecimalPipe implements PipeTransform {
constructor(_locale: string);
diff --git a/goldens/size-tracking/integration-payloads.json b/goldens/size-tracking/integration-payloads.json
index dba601e85a20ef..18b8ab61afc8ee 100644
--- a/goldens/size-tracking/integration-payloads.json
+++ b/goldens/size-tracking/integration-payloads.json
@@ -26,7 +26,7 @@
"cli-hello-world-ivy-i18n": {
"uncompressed": {
"runtime": 926,
- "main": 124269,
+ "main": 124779,
"polyfills": 35252
}
},
diff --git a/packages/common/src/common.ts b/packages/common/src/common.ts
index 2bd0c424f8d865..1195385b42261e 100644
--- a/packages/common/src/common.ts
+++ b/packages/common/src/common.ts
@@ -22,7 +22,7 @@ export {parseCookieValue as ɵparseCookieValue} from './cookie';
export {CommonModule} from './common_module';
export {NgClass, NgFor, NgForOf, NgForOfContext, NgIf, NgIfContext, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet, NgComponentOutlet} from './directives/index';
export {DOCUMENT} from './dom_tokens';
-export {AsyncPipe, DatePipe, DATE_PIPE_DEFAULT_TIMEZONE, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe, TitleCasePipe, KeyValuePipe, KeyValue} from './pipes/index';
+export {AsyncPipe, DatePipe, DatePipeConfig, DATE_PIPE_DEFAULT_TIMEZONE, DATE_PIPE_DEFAULT_OPTIONS, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe, TitleCasePipe, KeyValuePipe, KeyValue} from './pipes/index';
export {PLATFORM_BROWSER_ID as ɵPLATFORM_BROWSER_ID, PLATFORM_SERVER_ID as ɵPLATFORM_SERVER_ID, PLATFORM_WORKER_APP_ID as ɵPLATFORM_WORKER_APP_ID, PLATFORM_WORKER_UI_ID as ɵPLATFORM_WORKER_UI_ID, isPlatformBrowser, isPlatformServer, isPlatformWorkerApp, isPlatformWorkerUi} from './platform_id';
export {VERSION} from './version';
export {ViewportScroller, NullViewportScroller as ɵNullViewportScroller} from './viewport_scroller';
diff --git a/packages/common/src/pipes/date_pipe.ts b/packages/common/src/pipes/date_pipe.ts
index aacb939eee39a4..e400f391b79a06 100644
--- a/packages/common/src/pipes/date_pipe.ts
+++ b/packages/common/src/pipes/date_pipe.ts
@@ -10,14 +10,52 @@ import {Inject, InjectionToken, LOCALE_ID, Optional, Pipe, PipeTransform} from '
import {formatDate} from '../i18n/format_date';
+import {DatePipeConfig, DEFAULT_DATE_FORMAT} from './date_pipe_config';
import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
/**
* Optionally-provided default timezone to use for all instances of `DatePipe` (such as `'+0430'`).
* If the value isn't provided, the `DatePipe` will use the end-user's local system timezone.
+ *
+ * @deprecated use DATE_PIPE_DEFAULT_OPTIONS token to configure DatePipe
*/
export const DATE_PIPE_DEFAULT_TIMEZONE = new InjectionToken('DATE_PIPE_DEFAULT_TIMEZONE');
+/**
+ * DI token that allows to provide default configuration for the `DatePipe` instances in an
+ * application. The value is an object which can include the following fields:
+* - `dateFormat`: configures the default date format. If not provided, the `DatePipe`
+ * will use the 'mediumDate' as a value.
+ * - `timezone`: configures the default timezone. If not provided, the `DatePipe` will
+ * use the end-user's local system timezone.
+ *
+ * @see `DatePipeConfig`
+ *
+ * @usageNotes
+ *
+ * Various date pipe default values can be overwritten by providing this token with
+ * the value that has this interface.
+ *
+ * For example:
+ *
+* Override the default date format by providing a value using the token:
+ * ```typescript
+ * providers: [
+ * {provide: DATE_PIPE_DEFAULT_OPTIONS, useValue: {dateFormat: 'shortDate'}}
+ * ]
+ * ```
+ *
+ * Override the default timezone by providing a value using the token:
+ * ```typescript
+ * providers: [
+ * {provide: DATE_PIPE_DEFAULT_OPTIONS, useValue: {timezone: '-1200'}}
+ * ]
+ * ```
+
+ */
+export const DATE_PIPE_DEFAULT_OPTIONS =
+ new InjectionToken('DATE_PIPE_DEFAULT_OPTIONS');
+
// clang-format off
/**
* @ngModule CommonModule
@@ -185,19 +223,27 @@ export const DATE_PIPE_DEFAULT_TIMEZONE = new InjectionToken('DATE_PIPE_
export class DatePipe implements PipeTransform {
constructor(
@Inject(LOCALE_ID) private locale: string,
- @Inject(DATE_PIPE_DEFAULT_TIMEZONE) @Optional() private defaultTimezone?: string|null) {}
+ @Inject(DATE_PIPE_DEFAULT_TIMEZONE) @Optional() private defaultTimezone?: string|null,
+ @Inject(DATE_PIPE_DEFAULT_OPTIONS) @Optional() private defaultOptions?: DatePipeConfig|null,
+ ) {}
/**
* @param value The date expression: a `Date` object, a number
* (milliseconds since UTC epoch), or an ISO string (https://www.w3.org/TR/NOTE-datetime).
* @param format The date/time components to include, using predefined options or a
- * custom format string.
+ * custom format string. When not provided, the `DatePipe` looks for the value using the
+ * `DATE_PIPE_DEFAULT_OPTIONS` injection token (and reads the `dateFormat` property).
+ * If the token is not configured, the `mediumDate` is used as a value.
* @param timezone A timezone offset (such as `'+0430'`), or a standard UTC/GMT, or continental US
- * timezone abbreviation. When not supplied, either the value of the `DATE_PIPE_DEFAULT_TIMEZONE`
- * injection token is used or the end-user's local system timezone.
+ * timezone abbreviation. When not provided, the `DatePipe` looks for the value using the
+ * `DATE_PIPE_DEFAULT_OPTIONS` injection token (and reads the `timezone` property). If the token
+ * is not configured, the end-user's local system timezone is used as a value.
* @param locale A locale code for the locale format rules to use.
* When not supplied, uses the value of `LOCALE_ID`, which is `en-US` by default.
* See [Setting your app locale](guide/i18n-common-locale-id).
+ *
+ * @see `DATE_PIPE_DEFAULT_OPTIONS`
+ *
* @returns A date string in the desired format.
*/
transform(value: Date|string|number, format?: string, timezone?: string, locale?: string): string
@@ -207,13 +253,15 @@ export class DatePipe implements PipeTransform {
value: Date|string|number|null|undefined, format?: string, timezone?: string,
locale?: string): string|null;
transform(
- value: Date|string|number|null|undefined, format = 'mediumDate', timezone?: string,
+ value: Date|string|number|null|undefined, format?: string, timezone?: string,
locale?: string): string|null {
if (value == null || value === '' || value !== value) return null;
try {
- return formatDate(
- value, format, locale || this.locale, timezone ?? this.defaultTimezone ?? undefined);
+ const _format = format ?? this.defaultOptions?.dateFormat ?? DEFAULT_DATE_FORMAT;
+ const _timezone =
+ timezone ?? this.defaultOptions?.timezone ?? this.defaultTimezone ?? undefined;
+ return formatDate(value, _format, locale || this.locale, _timezone);
} catch (error) {
throw invalidPipeArgumentError(DatePipe, (error as Error).message);
}
diff --git a/packages/common/src/pipes/date_pipe_config.ts b/packages/common/src/pipes/date_pipe_config.ts
new file mode 100644
index 00000000000000..90216b9c73e1b7
--- /dev/null
+++ b/packages/common/src/pipes/date_pipe_config.ts
@@ -0,0 +1,26 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+/**
+ * An interface that describes the date pipe configuration, which can be provided using the
+ * `DATE_PIPE_DEFAULT_OPTIONS` token.
+ *
+ * @see `DATE_PIPE_DEFAULT_OPTIONS`
+ *
+ * @publicApi
+ */
+export interface DatePipeConfig {
+ dateFormat: string;
+ timezone: string;
+}
+
+/**
+ * The default date format of Angular date pipe, which corresponds to the following format:
+ * `'MMM d,y'` (e.g. `Jun 15, 2015`)
+ */
+export const DEFAULT_DATE_FORMAT = 'mediumDate';
diff --git a/packages/common/src/pipes/index.ts b/packages/common/src/pipes/index.ts
index 54e6dedd6ccbfd..cd1b27307852f4 100644
--- a/packages/common/src/pipes/index.ts
+++ b/packages/common/src/pipes/index.ts
@@ -13,7 +13,8 @@
*/
import {AsyncPipe} from './async_pipe';
import {LowerCasePipe, TitleCasePipe, UpperCasePipe} from './case_conversion_pipes';
-import {DATE_PIPE_DEFAULT_TIMEZONE, DatePipe} from './date_pipe';
+import {DATE_PIPE_DEFAULT_OPTIONS, DATE_PIPE_DEFAULT_TIMEZONE, DatePipe} from './date_pipe';
+import {DatePipeConfig} from './date_pipe_config';
import {I18nPluralPipe} from './i18n_plural_pipe';
import {I18nSelectPipe} from './i18n_select_pipe';
import {JsonPipe} from './json_pipe';
@@ -24,8 +25,10 @@ import {SlicePipe} from './slice_pipe';
export {
AsyncPipe,
CurrencyPipe,
+ DATE_PIPE_DEFAULT_OPTIONS,
DATE_PIPE_DEFAULT_TIMEZONE,
DatePipe,
+ DatePipeConfig,
DecimalPipe,
I18nPluralPipe,
I18nSelectPipe,
diff --git a/packages/common/test/pipes/date_pipe_spec.ts b/packages/common/test/pipes/date_pipe_spec.ts
index a047416580d86c..be3217dc181d05 100644
--- a/packages/common/test/pipes/date_pipe_spec.ts
+++ b/packages/common/test/pipes/date_pipe_spec.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {DatePipe} from '@angular/common';
+import {DATE_PIPE_DEFAULT_OPTIONS, DatePipe} from '@angular/common';
import localeEn from '@angular/common/locales/en';
import localeEnExtra from '@angular/common/locales/extra/en';
import {Component, ɵregisterLocaleData, ɵunregisterLocaleData} from '@angular/core';
@@ -72,9 +72,55 @@ import {TestBed} from '@angular/core/testing';
});
describe('transform', () => {
- it('should use "mediumDate" as the default format',
+ it('should use "mediumDate" as the default format if no format is provided',
() => expect(pipe.transform('2017-01-11T10:14:39+0000')).toEqual('Jan 11, 2017'));
+ it('should give precedence to the passed in format',
+ () => expect(pipe.transform('2017-01-11T10:14:39+0000', 'shortDate')).toEqual('1/11/17'));
+
+ it('should use format provided in component as default format when no format is passed in',
+ () => {
+ @Component({
+ selector: 'test-component',
+ imports: [DatePipe],
+ template: '{{ value | date }}',
+ standalone: true,
+ providers: [{provide: DATE_PIPE_DEFAULT_OPTIONS, useValue: {dateFormat: 'shortDate'}}]
+ })
+ class TestComponent {
+ value = '2017-01-11T10:14:39+0000';
+ }
+
+ const fixture = TestBed.createComponent(TestComponent);
+ fixture.detectChanges();
+
+ const content = fixture.nativeElement.textContent;
+ expect(content).toBe('1/11/17');
+ });
+
+ it('should use format provided in module as default format when no format is passed in',
+ () => {
+ @Component({
+ selector: 'test-component',
+ imports: [DatePipe],
+ template: '{{ value | date }}',
+ standalone: true,
+ })
+ class TestComponent {
+ value = '2017-01-11T10:14:39+0000';
+ }
+
+ TestBed.configureTestingModule({
+ imports: [TestComponent],
+ providers: [{provide: DATE_PIPE_DEFAULT_OPTIONS, useValue: {dateFormat: 'shortDate'}}]
+ });
+ const fixture = TestBed.createComponent(TestComponent);
+ fixture.detectChanges();
+
+ const content = fixture.nativeElement.textContent;
+ expect(content).toBe('1/11/17');
+ });
+
it('should return first week if some dates fall in previous year but belong to next year according to ISO 8601 format',
() => {
expect(pipe.transform('2019-12-28T00:00:00', 'w')).toEqual('52');
@@ -111,6 +157,49 @@ import {TestBed} from '@angular/core/testing';
expect(pipe.transform('2017-01-11T00:00:00', 'mediumDate', '+0100'))
.toEqual('Jan 11, 2017');
});
+
+ it('should use timezone provided in component as default timezone when no format is passed in',
+ () => {
+ @Component({
+ selector: 'test-component',
+ imports: [DatePipe],
+ template: '{{ value | date }}',
+ standalone: true,
+ providers: [{provide: DATE_PIPE_DEFAULT_OPTIONS, useValue: {timezone: '-1200'}}]
+ })
+ class TestComponent {
+ value = '2017-01-11T00:00:00';
+ }
+
+ const fixture = TestBed.createComponent(TestComponent);
+ fixture.detectChanges();
+
+ const content = fixture.nativeElement.textContent;
+ expect(content).toBe('Jan 10, 2017');
+ });
+
+ it('should use timezone provided in module as default timezone when no format is passed in',
+ () => {
+ @Component({
+ selector: 'test-component',
+ imports: [DatePipe],
+ template: '{{ value | date }}',
+ standalone: true,
+ })
+ class TestComponent {
+ value = '2017-01-11T00:00:00';
+ }
+
+ TestBed.configureTestingModule({
+ imports: [TestComponent],
+ providers: [{provide: DATE_PIPE_DEFAULT_OPTIONS, useValue: {timezone: '-1200'}}]
+ });
+ const fixture = TestBed.createComponent(TestComponent);
+ fixture.detectChanges();
+
+ const content = fixture.nativeElement.textContent;
+ expect(content).toBe('Jan 10, 2017');
+ });
});
it('should be available as a standalone pipe', () => {