X Tutup
Skip to content

Commit b716046

Browse files
piloopintbosch
authored andcommitted
feat(pipes): add date pipe
Closes #2877
1 parent 3143d18 commit b716046

File tree

8 files changed

+292
-12
lines changed

8 files changed

+292
-12
lines changed

modules/angular2/pipes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ export {ObservablePipe} from './src/change_detection/pipes/observable_pipe';
1212
export {JsonPipe} from './src/change_detection/pipes/json_pipe';
1313
export {IterableChanges} from './src/change_detection/pipes/iterable_changes';
1414
export {KeyValueChanges} from './src/change_detection/pipes/keyvalue_changes';
15+
export {DatePipe} from './src/change_detection/pipes/date_pipe';
1516
export {DecimalPipe, PercentPipe, CurrencyPipe} from './src/change_detection/pipes/number_pipe';
1617
export {LimitToPipe} from './src/change_detection/pipes/limit_to_pipe';

modules/angular2/src/change_detection/change_detection.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {UpperCaseFactory} from './pipes/uppercase_pipe';
1111
import {LowerCaseFactory} from './pipes/lowercase_pipe';
1212
import {JsonPipe} from './pipes/json_pipe';
1313
import {LimitToPipeFactory} from './pipes/limit_to_pipe';
14+
import {DatePipe} from './pipes/date_pipe';
1415
import {DecimalPipe, PercentPipe, CurrencyPipe} from './pipes/number_pipe';
1516
import {NullPipeFactory} from './pipes/null_pipe';
1617
import {ChangeDetection, ProtoChangeDetector, ChangeDetectorDefinition} from './interfaces';
@@ -101,6 +102,15 @@ export const percent: List<PipeFactory> =
101102
export const currency: List<PipeFactory> =
102103
CONST_EXPR([CONST_EXPR(new CurrencyPipe()), CONST_EXPR(new NullPipeFactory())]);
103104

105+
/**
106+
* Date/time formatter.
107+
*
108+
* @exportedAs angular2/pipes
109+
*/
110+
export const date: List<PipeFactory> =
111+
CONST_EXPR([CONST_EXPR(new DatePipe()), CONST_EXPR(new NullPipeFactory())]);
112+
113+
104114
export const defaultPipes = CONST_EXPR({
105115
"iterableDiff": iterableDiff,
106116
"keyValDiff": keyValDiff,
@@ -111,7 +121,8 @@ export const defaultPipes = CONST_EXPR({
111121
"limitTo": limitTo,
112122
"number": decimal,
113123
"percent": percent,
114-
"currency": currency
124+
"currency": currency,
125+
"date": date
115126
});
116127

117128
/**
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import {
2+
isDate,
3+
isNumber,
4+
isPresent,
5+
Date,
6+
DateWrapper,
7+
CONST,
8+
FunctionWrapper
9+
} from 'angular2/src/facade/lang';
10+
import {DateFormatter} from 'angular2/src/facade/intl';
11+
import {StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection';
12+
import {Pipe, BasePipe, PipeFactory} from './pipe';
13+
import {ChangeDetectorRef} from '../change_detector_ref';
14+
15+
// TODO: move to a global configable location along with other i18n components.
16+
var defaultLocale: string = 'en-US';
17+
18+
/**
19+
* Formats a date value to a string based on the requested format.
20+
*
21+
* # Usage
22+
*
23+
* expression | date[:format]
24+
*
25+
* where `expression` is a date object or a number (milliseconds since UTC epoch) and
26+
* `format` indicates which date/time components to include:
27+
*
28+
* | Component | Symbol | Short Form | Long Form | Numeric | 2-digit |
29+
* |-----------|:------:|--------------|-------------------|-----------|-----------|
30+
* | era | G | G (AD) | GGGG (Anno Domini)| - | - |
31+
* | year | y | - | - | y (2015) | yy (15) |
32+
* | month | M | MMM (Sep) | MMMM (September) | M (9) | MM (09) |
33+
* | day | d | - | - | d (3) | dd (03) |
34+
* | weekday | E | EEE (Sun) | EEEE (Sunday) | - | - |
35+
* | hour | j | - | - | j (13) | jj (13) |
36+
* | hour12 | h | - | - | h (1 PM) | hh (01 PM)|
37+
* | hour24 | H | - | - | H (13) | HH (13) |
38+
* | minute | m | - | - | m (5) | mm (05) |
39+
* | second | s | - | - | s (9) | ss (09) |
40+
* | timezone | z | - | z (Pacific Standard Time)| - | - |
41+
* | timezone | Z | Z (GMT-8:00) | - | - | - |
42+
*
43+
* In javascript, only the components specified will be respected (not the ordering,
44+
* punctuations, ...) and details of the the formatting will be dependent on the locale.
45+
* On the other hand in Dart version, you can also include quoted text as well as some extra
46+
* date/time components such as quarter. For more information see:
47+
* https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/intl/intl.DateFormat.
48+
*
49+
* `format` can also be one of the following predefined formats:
50+
*
51+
* - `'medium'`: equivalent to `'yMMMdjms'` (e.g. Sep 3, 2010, 12:05:08 PM for en-US)
52+
* - `'short'`: equivalent to `'yMdjm'` (e.g. 9/3/2010, 12:05 PM for en-US)
53+
* - `'fullDate'`: equivalent to `'yMMMMEEEEd'` (e.g. Friday, September 3, 2010 for en-US)
54+
* - `'longDate'`: equivalent to `'yMMMMd'` (e.g. September 3, 2010)
55+
* - `'mediumDate'`: equivalent to `'yMMMd'` (e.g. Sep 3, 2010 for en-US)
56+
* - `'shortDate'`: equivalent to `'yMd'` (e.g. 9/3/2010 for en-US)
57+
* - `'mediumTime'`: equivalent to `'jms'` (e.g. 12:05:08 PM for en-US)
58+
* - `'shortTime'`: equivalent to `'jm'` (e.g. 12:05 PM for en-US)
59+
*
60+
* Timezone of the formatted text will be the local system timezone of the end-users machine.
61+
*
62+
* # Examples
63+
*
64+
* Assuming `dateObj` is (year: 2015, month: 6, day: 15, hour: 21, minute: 43, second: 11)
65+
* in the _local_ time and locale is 'en-US':
66+
*
67+
* {{ dateObj | date }} // output is 'Jun 15, 2015'
68+
* {{ dateObj | date:'medium' }} // output is 'Jun 15, 2015, 9:43:11 PM'
69+
* {{ dateObj | date:'shortTime' }} // output is '9:43 PM'
70+
* {{ dateObj | date:'mmss' }} // output is '43:11'
71+
*
72+
* @exportedAs angular2/pipes
73+
*/
74+
@CONST()
75+
export class DatePipe extends BasePipe implements PipeFactory {
76+
static _ALIASES = {
77+
'medium': 'yMMMdjms',
78+
'short': 'yMdjm',
79+
'fullDate': 'yMMMMEEEEd',
80+
'longDate': 'yMMMMd',
81+
'mediumDate': 'yMMMd',
82+
'shortDate': 'yMd',
83+
'mediumTime': 'jms',
84+
'shortTime': 'jm'
85+
};
86+
87+
88+
transform(value, args: List<any>): string {
89+
var pattern: string = isPresent(args) && args.length > 0 ? args[0] : 'mediumDate';
90+
if (isNumber(value)) {
91+
value = DateWrapper.fromMillis(value);
92+
}
93+
if (StringMapWrapper.contains(DatePipe._ALIASES, pattern)) {
94+
pattern = <string>StringMapWrapper.get(DatePipe._ALIASES, pattern);
95+
}
96+
return DateFormatter.format(value, defaultLocale, pattern);
97+
}
98+
99+
supports(obj): boolean { return isDate(obj) || isNumber(obj); }
100+
101+
create(cdRef: ChangeDetectorRef): Pipe { return this }
102+
}

modules/angular2/src/facade/intl.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,21 @@ class NumberFormatter {
4040
return formatter.format(number);
4141
}
4242
}
43+
44+
class DateFormatter {
45+
static RegExp _multiPartRegExp = new RegExp(r'^([yMdE]+)([Hjms]+)$');
46+
47+
static String format(DateTime date, String locale, String pattern) {
48+
locale = _normalizeLocale(locale);
49+
var formatter = new DateFormat(null, locale);
50+
var matches = _multiPartRegExp.firstMatch(pattern);
51+
if (matches != null) {
52+
// Support for patterns which have known date and time components.
53+
formatter.addPattern(matches[1]);
54+
formatter.addPattern(matches[2], ', ');
55+
} else {
56+
formatter.addPattern(pattern);
57+
}
58+
return formatter.format(date);
59+
}
60+
}

modules/angular2/src/facade/intl.ts

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@ declare module Intl {
1414
format(value: number): string;
1515
}
1616

17-
var NumberFormat: {
18-
new (locale?: string, options?: NumberFormatOptions): NumberFormat;
19-
}
17+
var NumberFormat: { new (locale?: string, options?: NumberFormatOptions): NumberFormat; }
2018

2119
interface DateTimeFormatOptions {
2220
localeMatcher?: string;
@@ -37,9 +35,7 @@ declare module Intl {
3735
format(date?: Date | number): string;
3836
}
3937

40-
var DateTimeFormat: {
41-
new (locale?: string, options?: DateTimeFormatOptions): DateTimeFormat;
42-
}
38+
var DateTimeFormat: { new (locale?: string, options?: DateTimeFormatOptions): DateTimeFormat; }
4339
}
4440

4541
export enum NumberFormatStyle {
@@ -71,3 +67,78 @@ export class NumberFormatter {
7167
return new Intl.NumberFormat(locale, intlOptions).format(number);
7268
}
7369
}
70+
71+
function digitCondition(len: int): string {
72+
return len == 2 ? '2-digit' : 'numeric';
73+
}
74+
function nameCondition(len: int): string {
75+
return len < 4 ? 'short' : 'long';
76+
}
77+
function extractComponents(pattern: string): Intl.DateTimeFormatOptions {
78+
var ret: Intl.DateTimeFormatOptions = {};
79+
var i = 0, j;
80+
while (i < pattern.length) {
81+
j = i;
82+
while (j < pattern.length && pattern[j] == pattern[i]) j++;
83+
let len = j - i;
84+
switch (pattern[i]) {
85+
case 'G':
86+
ret.era = nameCondition(len);
87+
break;
88+
case 'y':
89+
ret.year = digitCondition(len);
90+
break;
91+
case 'M':
92+
if (len >= 3)
93+
ret.month = nameCondition(len);
94+
else
95+
ret.month = digitCondition(len);
96+
break;
97+
case 'd':
98+
ret.day = digitCondition(len);
99+
break;
100+
case 'E':
101+
ret.weekday = nameCondition(len);
102+
break;
103+
case 'j':
104+
ret.hour = digitCondition(len);
105+
break;
106+
case 'h':
107+
ret.hour = digitCondition(len);
108+
ret.hour12 = true;
109+
break;
110+
case 'H':
111+
ret.hour = digitCondition(len);
112+
ret.hour12 = false;
113+
break;
114+
case 'm':
115+
ret.minute = digitCondition(len);
116+
break;
117+
case 's':
118+
ret.second = digitCondition(len);
119+
break;
120+
case 'z':
121+
ret.timeZoneName = 'long';
122+
break;
123+
case 'Z':
124+
ret.timeZoneName = 'short';
125+
break;
126+
}
127+
i = j;
128+
}
129+
return ret;
130+
}
131+
132+
var dateFormatterCache: Map<string, Intl.DateTimeFormat> = new Map<string, Intl.DateTimeFormat>();
133+
134+
export class DateFormatter {
135+
static format(date: Date, locale: string, pattern: string): string {
136+
var key = locale + pattern;
137+
if (dateFormatterCache.has(key)) {
138+
return dateFormatterCache.get(key).format(date);
139+
}
140+
var formatter = new Intl.DateTimeFormat(locale, extractComponents(pattern));
141+
dateFormatterCache.set(key, formatter);
142+
return formatter.format(date);
143+
}
144+
}

modules/angular2/src/facade/lang.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ bool isStringMap(obj) => obj is Map;
3333
bool isArray(obj) => obj is List;
3434
bool isPromise(obj) => obj is Future;
3535
bool isNumber(obj) => obj is num;
36+
bool isDate(obj) => obj is DateTime;
3637

3738
String stringify(obj) => obj.toString();
3839

@@ -232,16 +233,20 @@ class Json {
232233
}
233234

234235
class DateWrapper {
236+
static DateTime create(int year, [int month = 1, int day = 1, int hour = 0,
237+
int minutes = 0, int seconds = 0, int milliseconds = 0]) {
238+
return new DateTime(year, month, day, hour, minutes, seconds, milliseconds);
239+
}
235240
static DateTime fromMillis(int ms) {
236-
return new DateTime.fromMillisecondsSinceEpoch(ms);
241+
return new DateTime.fromMillisecondsSinceEpoch(ms, isUtc: true);
237242
}
238243
static int toMillis(DateTime date) {
239244
return date.millisecondsSinceEpoch;
240245
}
241246
static DateTime now() {
242247
return new DateTime.now();
243248
}
244-
static toJson(DateTime date) {
249+
static String toJson(DateTime date) {
245250
return date.toUtc().toIso8601String();
246251
}
247252
}

modules/angular2/src/facade/lang.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ export function isNumber(obj): boolean {
9393
return typeof obj === 'number';
9494
}
9595

96+
export function isDate(obj): boolean {
97+
return obj instanceof Date && !isNaN(obj.valueOf());
98+
}
99+
96100
export function stringify(token): string {
97101
if (typeof token === 'string') {
98102
return token;
@@ -282,8 +286,12 @@ export class Json {
282286
}
283287

284288
export class DateWrapper {
285-
static fromMillis(ms): Date { return new Date(ms); }
286-
static toMillis(date: Date): number { return date.getTime(); }
289+
static create(year: int, month: int = 1, day: int = 1, hour: int = 0, minutes: int = 0,
290+
seconds: int = 0, milliseconds: int = 0): Date {
291+
return new Date(year, month - 1, day, hour, minutes, seconds, milliseconds);
292+
}
293+
static fromMillis(ms: int): Date { return new Date(ms); }
294+
static toMillis(date: Date): int { return date.getTime(); }
287295
static now(): Date { return new Date(); }
288-
static toJson(date): string { return date.toJSON(); }
296+
static toJson(date: Date): string { return date.toJSON(); }
289297
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib';
2+
3+
import {DatePipe} from 'angular2/src/change_detection/pipes/date_pipe';
4+
import {DateWrapper} from 'angular2/src/facade/lang';
5+
6+
export function main() {
7+
describe("DatePipe", () => {
8+
var date;
9+
var pipe;
10+
11+
beforeEach(() => {
12+
date = DateWrapper.create(2015, 6, 15, 21, 43, 11);
13+
pipe = new DatePipe();
14+
});
15+
16+
describe("supports", () => {
17+
it("should support date", () => { expect(pipe.supports(date)).toBe(true); });
18+
it("should support int", () => { expect(pipe.supports(123456789)).toBe(true); });
19+
20+
it("should not support other objects", () => {
21+
expect(pipe.supports(new Object())).toBe(false);
22+
expect(pipe.supports(null)).toBe(false);
23+
});
24+
});
25+
26+
describe("transform", () => {
27+
it('should format each component correctly', () => {
28+
expect(pipe.transform(date, ['y'])).toEqual('2015');
29+
expect(pipe.transform(date, ['yy'])).toEqual('15');
30+
expect(pipe.transform(date, ['M'])).toEqual('6');
31+
expect(pipe.transform(date, ['MM'])).toEqual('06');
32+
expect(pipe.transform(date, ['MMM'])).toEqual('Jun');
33+
expect(pipe.transform(date, ['MMMM'])).toEqual('June');
34+
expect(pipe.transform(date, ['d'])).toEqual('15');
35+
expect(pipe.transform(date, ['E'])).toEqual('Mon');
36+
expect(pipe.transform(date, ['EEEE'])).toEqual('Monday');
37+
expect(pipe.transform(date, ['H'])).toEqual('21');
38+
expect(pipe.transform(date, ['j'])).toEqual('9 PM');
39+
expect(pipe.transform(date, ['m'])).toEqual('43');
40+
expect(pipe.transform(date, ['s'])).toEqual('11');
41+
});
42+
43+
it('should format common multi component patterns', () => {
44+
expect(pipe.transform(date, ['yMEd'])).toEqual('Mon, 6/15/2015');
45+
expect(pipe.transform(date, ['MEd'])).toEqual('Mon, 6/15');
46+
expect(pipe.transform(date, ['MMMd'])).toEqual('Jun 15');
47+
expect(pipe.transform(date, ['yMMMMEEEEd'])).toEqual('Monday, June 15, 2015');
48+
expect(pipe.transform(date, ['jms'])).toEqual('9:43:11 PM');
49+
expect(pipe.transform(date, ['ms'])).toEqual('43:11');
50+
});
51+
52+
it('should format with pattern aliases', () => {
53+
expect(pipe.transform(date, ['medium'])).toEqual('Jun 15, 2015, 9:43:11 PM');
54+
expect(pipe.transform(date, ['short'])).toEqual('6/15/2015, 9:43 PM');
55+
expect(pipe.transform(date, ['fullDate'])).toEqual('Monday, June 15, 2015');
56+
expect(pipe.transform(date, ['longDate'])).toEqual('June 15, 2015');
57+
expect(pipe.transform(date, ['mediumDate'])).toEqual('Jun 15, 2015');
58+
expect(pipe.transform(date, ['shortDate'])).toEqual('6/15/2015');
59+
expect(pipe.transform(date, ['mediumTime'])).toEqual('9:43:11 PM');
60+
expect(pipe.transform(date, ['shortTime'])).toEqual('9:43 PM');
61+
});
62+
});
63+
});
64+
}

0 commit comments

Comments
 (0)
X Tutup