Skip to content

Commit bdb5371

Browse files
matthiasweissthePunderWoman
authored andcommitted
feat(common): add injection token for default DatePipe configuration (#47157)
This commit introduces a new `DATE_PIPE_DEFAULT_OPTIONS` token, which can be used to configure default DatePipe options, such as date format and timezone. DEPRECATED: The `DATE_PIPE_DEFAULT_TIMEZONE` token is now deprecated in favor of the `DATE_PIPE_DEFAULT_OPTIONS` token, which accepts an object as a value and the timezone can be defined as a field (called `timezone`) on that object. PR Close #47157
1 parent a792bf1 commit bdb5371

File tree

8 files changed

+192
-14
lines changed

8 files changed

+192
-14
lines changed

aio/content/guide/deprecations.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ v15 - v18
8888
| `@angular/service-worker` | [`SwUpdate#available`](api/service-worker/SwUpdate#available) | <!-- v13 --> v16 |
8989
| template syntax | [`/deep/`, `>>>`, and `::ng-deep`](#deep-component-style-selector) | <!-- v7 --> unspecified |
9090
| template syntax | [`bind-`, `on-`, `bindon-`, and `ref-`](#bind-syntax) | <!-- v13 --> v15 |
91+
| `@angular/common` | [`DatePipe` - `DATE_PIPE_DEFAULT_TIMEZONE`](api/common/DATE_PIPE_DEFAULT_TIMEZONE) | <!-- v15 --> v17 |
9192

9293
For information about Angular CDK and Angular Material deprecations, see the [changelog](https://github.com/angular/components/blob/main/CHANGELOG.md).
9394

@@ -110,6 +111,7 @@ In the [API reference section](api) of this site, deprecated APIs are indicated
110111
|:--- |:--- |:--- |:--- |
111112
| [`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`. |
112113
| [`NgComponentOutlet.ngComponentOutletNgModuleFactory`](api/common/NgComponentOutlet) | `NgComponentOutlet.ngComponentOutletNgModule` | v14 | Use the `ngComponentOutletNgModule` input instead. This input doesn't require resolving NgModule factory. |
114+
| [`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. |
113115

114116
<a id="common-http"></a>
115117

goldens/public-api/common/index.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,23 +76,34 @@ export class CurrencyPipe implements PipeTransform {
7676
}
7777

7878
// @public
79+
export const DATE_PIPE_DEFAULT_OPTIONS: InjectionToken<DatePipeConfig>;
80+
81+
// @public @deprecated
7982
export const DATE_PIPE_DEFAULT_TIMEZONE: InjectionToken<string>;
8083

8184
// @public
8285
export class DatePipe implements PipeTransform {
83-
constructor(locale: string, defaultTimezone?: string | null | undefined);
86+
constructor(locale: string, defaultTimezone?: string | null | undefined, defaultOptions?: DatePipeConfig | null | undefined);
8487
// (undocumented)
8588
transform(value: Date | string | number, format?: string, timezone?: string, locale?: string): string | null;
8689
// (undocumented)
8790
transform(value: null | undefined, format?: string, timezone?: string, locale?: string): null;
8891
// (undocumented)
8992
transform(value: Date | string | number | null | undefined, format?: string, timezone?: string, locale?: string): string | null;
9093
// (undocumented)
91-
static ɵfac: i0.ɵɵFactoryDeclaration<DatePipe, [null, { optional: true; }]>;
94+
static ɵfac: i0.ɵɵFactoryDeclaration<DatePipe, [null, { optional: true; }, { optional: true; }]>;
9295
// (undocumented)
9396
static ɵpipe: i0.ɵɵPipeDeclaration<DatePipe, "date", true>;
9497
}
9598

99+
// @public
100+
export interface DatePipeConfig {
101+
// (undocumented)
102+
dateFormat: string;
103+
// (undocumented)
104+
timezone: string;
105+
}
106+
96107
// @public
97108
export class DecimalPipe implements PipeTransform {
98109
constructor(_locale: string);

goldens/size-tracking/integration-payloads.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"cli-hello-world-ivy-i18n": {
2727
"uncompressed": {
2828
"runtime": 926,
29-
"main": 124269,
29+
"main": 124195,
3030
"polyfills": 35252
3131
}
3232
},

packages/common/src/common.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export {parseCookieValue as ɵparseCookieValue} from './cookie';
2222
export {CommonModule} from './common_module';
2323
export {NgClass, NgFor, NgForOf, NgForOfContext, NgIf, NgIfContext, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet, NgComponentOutlet} from './directives/index';
2424
export {DOCUMENT} from './dom_tokens';
25-
export {AsyncPipe, DatePipe, DATE_PIPE_DEFAULT_TIMEZONE, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe, TitleCasePipe, KeyValuePipe, KeyValue} from './pipes/index';
25+
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';
2626
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';
2727
export {VERSION} from './version';
2828
export {ViewportScroller, NullViewportScroller as ɵNullViewportScroller} from './viewport_scroller';

packages/common/src/pipes/date_pipe.ts

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,51 @@ import {Inject, InjectionToken, LOCALE_ID, Optional, Pipe, PipeTransform} from '
1010

1111
import {formatDate} from '../i18n/format_date';
1212

13+
import {DatePipeConfig, DEFAULT_DATE_FORMAT} from './date_pipe_config';
1314
import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
1415

1516
/**
1617
* Optionally-provided default timezone to use for all instances of `DatePipe` (such as `'+0430'`).
1718
* If the value isn't provided, the `DatePipe` will use the end-user's local system timezone.
19+
*
20+
* @deprecated use DATE_PIPE_DEFAULT_OPTIONS token to configure DatePipe
1821
*/
1922
export const DATE_PIPE_DEFAULT_TIMEZONE = new InjectionToken<string>('DATE_PIPE_DEFAULT_TIMEZONE');
2023

24+
/**
25+
* DI token that allows to provide default configuration for the `DatePipe` instances in an
26+
* application. The value is an object which can include the following fields:
27+
* - `dateFormat`: configures the default date format. If not provided, the `DatePipe`
28+
* will use the 'mediumDate' as a value.
29+
* - `timezone`: configures the default timezone. If not provided, the `DatePipe` will
30+
* use the end-user's local system timezone.
31+
*
32+
* @see `DatePipeConfig`
33+
*
34+
* @usageNotes
35+
*
36+
* Various date pipe default values can be overwritten by providing this token with
37+
* the value that has this interface.
38+
*
39+
* For example:
40+
*
41+
* Override the default date format by providing a value using the token:
42+
* ```typescript
43+
* providers: [
44+
* {provide: DATE_PIPE_DEFAULT_OPTIONS, useValue: {dateFormat: 'shortDate'}}
45+
* ]
46+
* ```
47+
*
48+
* Override the default timezone by providing a value using the token:
49+
* ```typescript
50+
* providers: [
51+
* {provide: DATE_PIPE_DEFAULT_OPTIONS, useValue: {timezone: '-1200'}}
52+
* ]
53+
* ```
54+
*/
55+
export const DATE_PIPE_DEFAULT_OPTIONS =
56+
new InjectionToken<DatePipeConfig>('DATE_PIPE_DEFAULT_OPTIONS');
57+
2158
// clang-format off
2259
/**
2360
* @ngModule CommonModule
@@ -185,19 +222,27 @@ export const DATE_PIPE_DEFAULT_TIMEZONE = new InjectionToken<string>('DATE_PIPE_
185222
export class DatePipe implements PipeTransform {
186223
constructor(
187224
@Inject(LOCALE_ID) private locale: string,
188-
@Inject(DATE_PIPE_DEFAULT_TIMEZONE) @Optional() private defaultTimezone?: string|null) {}
225+
@Inject(DATE_PIPE_DEFAULT_TIMEZONE) @Optional() private defaultTimezone?: string|null,
226+
@Inject(DATE_PIPE_DEFAULT_OPTIONS) @Optional() private defaultOptions?: DatePipeConfig|null,
227+
) {}
189228

190229
/**
191230
* @param value The date expression: a `Date` object, a number
192231
* (milliseconds since UTC epoch), or an ISO string (https://www.w3.org/TR/NOTE-datetime).
193232
* @param format The date/time components to include, using predefined options or a
194-
* custom format string.
233+
* custom format string. When not provided, the `DatePipe` looks for the value using the
234+
* `DATE_PIPE_DEFAULT_OPTIONS` injection token (and reads the `dateFormat` property).
235+
* If the token is not configured, the `mediumDate` is used as a value.
195236
* @param timezone A timezone offset (such as `'+0430'`), or a standard UTC/GMT, or continental US
196-
* timezone abbreviation. When not supplied, either the value of the `DATE_PIPE_DEFAULT_TIMEZONE`
197-
* injection token is used or the end-user's local system timezone.
237+
* timezone abbreviation. When not provided, the `DatePipe` looks for the value using the
238+
* `DATE_PIPE_DEFAULT_OPTIONS` injection token (and reads the `timezone` property). If the token
239+
* is not configured, the end-user's local system timezone is used as a value.
198240
* @param locale A locale code for the locale format rules to use.
199241
* When not supplied, uses the value of `LOCALE_ID`, which is `en-US` by default.
200242
* See [Setting your app locale](guide/i18n-common-locale-id).
243+
*
244+
* @see `DATE_PIPE_DEFAULT_OPTIONS`
245+
*
201246
* @returns A date string in the desired format.
202247
*/
203248
transform(value: Date|string|number, format?: string, timezone?: string, locale?: string): string
@@ -207,13 +252,15 @@ export class DatePipe implements PipeTransform {
207252
value: Date|string|number|null|undefined, format?: string, timezone?: string,
208253
locale?: string): string|null;
209254
transform(
210-
value: Date|string|number|null|undefined, format = 'mediumDate', timezone?: string,
255+
value: Date|string|number|null|undefined, format?: string, timezone?: string,
211256
locale?: string): string|null {
212257
if (value == null || value === '' || value !== value) return null;
213258

214259
try {
215-
return formatDate(
216-
value, format, locale || this.locale, timezone ?? this.defaultTimezone ?? undefined);
260+
const _format = format ?? this.defaultOptions?.dateFormat ?? DEFAULT_DATE_FORMAT;
261+
const _timezone =
262+
timezone ?? this.defaultOptions?.timezone ?? this.defaultTimezone ?? undefined;
263+
return formatDate(value, _format, locale || this.locale, _timezone);
217264
} catch (error) {
218265
throw invalidPipeArgumentError(DatePipe, (error as Error).message);
219266
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
/**
10+
* An interface that describes the date pipe configuration, which can be provided using the
11+
* `DATE_PIPE_DEFAULT_OPTIONS` token.
12+
*
13+
* @see `DATE_PIPE_DEFAULT_OPTIONS`
14+
*
15+
* @publicApi
16+
*/
17+
export interface DatePipeConfig {
18+
dateFormat: string;
19+
timezone: string;
20+
}
21+
22+
/**
23+
* The default date format of Angular date pipe, which corresponds to the following format:
24+
* `'MMM d,y'` (e.g. `Jun 15, 2015`)
25+
*/
26+
export const DEFAULT_DATE_FORMAT = 'mediumDate';

packages/common/src/pipes/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
*/
1414
import {AsyncPipe} from './async_pipe';
1515
import {LowerCasePipe, TitleCasePipe, UpperCasePipe} from './case_conversion_pipes';
16-
import {DATE_PIPE_DEFAULT_TIMEZONE, DatePipe} from './date_pipe';
16+
import {DATE_PIPE_DEFAULT_OPTIONS, DATE_PIPE_DEFAULT_TIMEZONE, DatePipe} from './date_pipe';
17+
import {DatePipeConfig} from './date_pipe_config';
1718
import {I18nPluralPipe} from './i18n_plural_pipe';
1819
import {I18nSelectPipe} from './i18n_select_pipe';
1920
import {JsonPipe} from './json_pipe';
@@ -24,8 +25,10 @@ import {SlicePipe} from './slice_pipe';
2425
export {
2526
AsyncPipe,
2627
CurrencyPipe,
28+
DATE_PIPE_DEFAULT_OPTIONS,
2729
DATE_PIPE_DEFAULT_TIMEZONE,
2830
DatePipe,
31+
DatePipeConfig,
2932
DecimalPipe,
3033
I18nPluralPipe,
3134
I18nSelectPipe,

packages/common/test/pipes/date_pipe_spec.ts

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {DatePipe} from '@angular/common';
9+
import {DATE_PIPE_DEFAULT_OPTIONS, DatePipe} from '@angular/common';
1010
import localeEn from '@angular/common/locales/en';
1111
import localeEnExtra from '@angular/common/locales/extra/en';
1212
import {Component, ɵregisterLocaleData, ɵunregisterLocaleData} from '@angular/core';
@@ -72,9 +72,55 @@ import {TestBed} from '@angular/core/testing';
7272
});
7373

7474
describe('transform', () => {
75-
it('should use "mediumDate" as the default format',
75+
it('should use "mediumDate" as the default format if no format is provided',
7676
() => expect(pipe.transform('2017-01-11T10:14:39+0000')).toEqual('Jan 11, 2017'));
7777

78+
it('should give precedence to the passed in format',
79+
() => expect(pipe.transform('2017-01-11T10:14:39+0000', 'shortDate')).toEqual('1/11/17'));
80+
81+
it('should use format provided in component as default format when no format is passed in',
82+
() => {
83+
@Component({
84+
selector: 'test-component',
85+
imports: [DatePipe],
86+
template: '{{ value | date }}',
87+
standalone: true,
88+
providers: [{provide: DATE_PIPE_DEFAULT_OPTIONS, useValue: {dateFormat: 'shortDate'}}]
89+
})
90+
class TestComponent {
91+
value = '2017-01-11T10:14:39+0000';
92+
}
93+
94+
const fixture = TestBed.createComponent(TestComponent);
95+
fixture.detectChanges();
96+
97+
const content = fixture.nativeElement.textContent;
98+
expect(content).toBe('1/11/17');
99+
});
100+
101+
it('should use format provided in module as default format when no format is passed in',
102+
() => {
103+
@Component({
104+
selector: 'test-component',
105+
imports: [DatePipe],
106+
template: '{{ value | date }}',
107+
standalone: true,
108+
})
109+
class TestComponent {
110+
value = '2017-01-11T10:14:39+0000';
111+
}
112+
113+
TestBed.configureTestingModule({
114+
imports: [TestComponent],
115+
providers: [{provide: DATE_PIPE_DEFAULT_OPTIONS, useValue: {dateFormat: 'shortDate'}}]
116+
});
117+
const fixture = TestBed.createComponent(TestComponent);
118+
fixture.detectChanges();
119+
120+
const content = fixture.nativeElement.textContent;
121+
expect(content).toBe('1/11/17');
122+
});
123+
78124
it('should return first week if some dates fall in previous year but belong to next year according to ISO 8601 format',
79125
() => {
80126
expect(pipe.transform('2019-12-28T00:00:00', 'w')).toEqual('52');
@@ -111,6 +157,49 @@ import {TestBed} from '@angular/core/testing';
111157
expect(pipe.transform('2017-01-11T00:00:00', 'mediumDate', '+0100'))
112158
.toEqual('Jan 11, 2017');
113159
});
160+
161+
it('should use timezone provided in component as default timezone when no format is passed in',
162+
() => {
163+
@Component({
164+
selector: 'test-component',
165+
imports: [DatePipe],
166+
template: '{{ value | date }}',
167+
standalone: true,
168+
providers: [{provide: DATE_PIPE_DEFAULT_OPTIONS, useValue: {timezone: '-1200'}}]
169+
})
170+
class TestComponent {
171+
value = '2017-01-11T00:00:00';
172+
}
173+
174+
const fixture = TestBed.createComponent(TestComponent);
175+
fixture.detectChanges();
176+
177+
const content = fixture.nativeElement.textContent;
178+
expect(content).toBe('Jan 10, 2017');
179+
});
180+
181+
it('should use timezone provided in module as default timezone when no format is passed in',
182+
() => {
183+
@Component({
184+
selector: 'test-component',
185+
imports: [DatePipe],
186+
template: '{{ value | date }}',
187+
standalone: true,
188+
})
189+
class TestComponent {
190+
value = '2017-01-11T00:00:00';
191+
}
192+
193+
TestBed.configureTestingModule({
194+
imports: [TestComponent],
195+
providers: [{provide: DATE_PIPE_DEFAULT_OPTIONS, useValue: {timezone: '-1200'}}]
196+
});
197+
const fixture = TestBed.createComponent(TestComponent);
198+
fixture.detectChanges();
199+
200+
const content = fixture.nativeElement.textContent;
201+
expect(content).toBe('Jan 10, 2017');
202+
});
114203
});
115204

116205
it('should be available as a standalone pipe', () => {

0 commit comments

Comments
 (0)