Skip to content

Commit e8a7444

Browse files
Merge pull request #18 from telerik/format-parts
Format parts
2 parents f8232b9 + ee60f52 commit e8a7444

File tree

15 files changed

+877
-253
lines changed

15 files changed

+877
-253
lines changed

docs/date-formatting/api.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,23 @@ Defines the locale id.
159159
#### Returns `String`
160160

161161
The formatted date.
162+
163+
#### splitDateFormat
164+
165+
Returns information about the individual segments of a format. The returned objects contain the segment type (for example, year or month), the pattern, and the information about the [dateFormatNames](https://github.com/telerik/kendo-intl/blob/master/docs/cldr/api.md#dateformatnames) that are used for the pattern.
166+
167+
## Parameters
168+
169+
#### format `String|Object`
170+
171+
Specifies a string representing a predefined or custom date format, or a configuration object.
172+
173+
#### locale `String`
174+
175+
Defines the locale ID.
176+
177+
## Return Value
178+
179+
#### Returns `Array`
180+
181+
An array with information about the format segments.

docs/date-formatting/index.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ The following specifiers can be used in the custom formats.
121121

122122
formatDate(new Date(2011, 0, 1), "yy"); // 11
123123

124-
formatDate(new Date(111, 0, 1), "yyyy"); // 0111
124+
formatDate(new Date(111, 0, 1), "yyyy"); // 0111
125125

126126
* **The `"Q"` specifier**—Renders a quarter of the year.
127127

@@ -321,6 +321,24 @@ The supported types of options are:
321321

322322
formatDate(new Date(2000, 10, 6), { year: "numeric", month: "long" }); // November 2000
323323

324+
## Splitting Date Formats
325+
326+
To utilize or manipulate individual parts of the format, you might need more detailed information about the format itself in some cases. You can use the `splitDateFormat` method to get information about each individual segment of the format.
327+
328+
The following example demonstrates how to implement the usage of abbreviated names in a predefined format.
329+
330+
import { formatDate, splitDateFormat } from '@telerik/kendo-intl';
331+
332+
const date = new Date(2000, 0, 1);
333+
334+
splitDateFormat({ date: 'full' }).map(part => {
335+
if (part.type !== 'literal') {
336+
return formatDate(date, { pattern: part.pattern.slice(0, 3) });
337+
}
338+
339+
return part.pattern;
340+
}).join(''); //Sat, Jan 1, 2000
341+
324342
## Suggested Links
325343

326344
* [Date Formatting API]({% slug dateformatapi_internalization %})

examples/basic.html

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<script>var exports = {}; </script>
5+
<script src="../dist/cdn/js/kendo-intl.js"></script>
6+
7+
</head>
8+
<body>
9+
<input />
10+
<script>
11+
var intl = exports.KendoIntl;
12+
13+
var KEY_CODES = {
14+
BACKSPACE: 8,
15+
ARROWS: {
16+
UP: 38,
17+
DOWN: 40,
18+
LEFT: 37,
19+
RIGHT: 39
20+
}
21+
};
22+
23+
var DateSetters = {
24+
month: function(date, value) {
25+
if (1 <= value && value <= 12) {
26+
date.setMonth(value);
27+
return true;
28+
}
29+
},
30+
day: function(date, value) {
31+
var newDate = new Date(date);
32+
newDate.setDate(value);
33+
if (newDate.getMonth() === date.getMonth()) {
34+
date.setDate(value);
35+
return true;
36+
}
37+
},
38+
year: function(date, value) {
39+
date.setFullYear(value);
40+
return true;
41+
}
42+
}
43+
44+
var DateModifiers = {
45+
month: function(date, offset) {
46+
date.setMonth(date.getMonth() + offset);
47+
},
48+
day: function(date, offset) {
49+
date.setDate(date.getDate() + offset);
50+
},
51+
year: function(date, offset) {
52+
date.setFullYear(date.getFullYear() + offset);
53+
}
54+
};
55+
56+
var Input = function(element, format, date) {
57+
this.element = element;
58+
this.parts = intl.splitDateFormat(format);
59+
this.date = date;
60+
this.element.addEventListener("keydown", this.onKeyDown.bind(this));
61+
this.element.addEventListener("keypress", this.onKeyPress.bind(this));
62+
this.init();
63+
};
64+
65+
Input.prototype = {
66+
init: function() {
67+
var parts = this.parts;
68+
var inputValue = '';
69+
for (var idx = 0; idx < parts.length; idx++) {
70+
var part = parts[idx];
71+
var value;
72+
if (part.type === 'literal') {
73+
value = part.pattern;
74+
} else {
75+
value = intl.formatDate(this.date, { pattern: part.pattern });
76+
if (part.names) {
77+
part.names = intl.dateFormatNames('en', part.names);
78+
}
79+
}
80+
part.value = value;
81+
inputValue += value;
82+
}
83+
84+
this.elementValue = this.element.value = inputValue;
85+
},
86+
87+
setValue: function() {
88+
var parts = this.parts;
89+
var inputValue = '';
90+
for (var idx = 0; idx < parts.length; idx++) {
91+
var part = parts[idx];
92+
inputValue += part.name || part.value;
93+
}
94+
95+
this.elementValue = this.element.value = inputValue;
96+
},
97+
98+
adjacentPart: function(idx, dir) {
99+
var current = this.parts[idx];
100+
var next = this.parts[idx + dir];
101+
102+
if (next && next.type === 'literal') {
103+
next = this.parts[idx + 2 * dir];
104+
if (dir > 0 && next) {
105+
next.position = current.position + (current.name || current.value).length + this.parts[idx + dir].value.length;
106+
}
107+
}
108+
109+
return next;
110+
},
111+
112+
onKeyDown: function(e) {
113+
var position = this.element.selectionStart;
114+
var partIdx = this.getPartIndex(position);
115+
var parts = this.parts;
116+
var part = parts[partIdx];
117+
var keyCode = e.keyCode;
118+
var backspace = keyCode == KEY_CODES.BACKSPACE;
119+
var up = keyCode == KEY_CODES.ARROWS.UP;
120+
var down = keyCode == KEY_CODES.ARROWS.DOWN;
121+
var left = keyCode == KEY_CODES.ARROWS.LEFT;
122+
var right = keyCode == KEY_CODES.ARROWS.RIGHT;
123+
var handle = this._metaKey = backspace || up || down || left || right;
124+
125+
if (backspace) {
126+
if(!part.name) {
127+
part.name = part.type;
128+
this.setValue();
129+
}
130+
delete part.currentValue;
131+
} else if (up || down) {
132+
delete part.name;
133+
delete part.currentValue;
134+
var offset = up ? 1 : -1;
135+
DateModifiers[part.type](this.date, offset);
136+
part.value = intl.formatDate(this.date, { pattern: part.pattern });
137+
this.setValue();
138+
} else if (left) {
139+
part = this.adjacentPart(partIdx, -1);
140+
} else if (right) {
141+
part = this.adjacentPart(partIdx, 1);
142+
}
143+
144+
if (handle) {
145+
e.preventDefault();
146+
if (part) {
147+
this.element.setSelectionRange(part.position, part.position + (part.name || part.value).length);
148+
}
149+
}
150+
},
151+
152+
onKeyPress: function(e) {
153+
var position = this.element.selectionStart;
154+
var partIdx = this.getPartIndex(position);
155+
var parts = this.parts;
156+
var part = parts[partIdx];
157+
if (!this._metaKey) {
158+
e.preventDefault();
159+
var key = e.key.toLowerCase();
160+
var value = (part.currentValue || '') + key;
161+
if (part.names) {
162+
if (!this.trySetName(value, part) && part.currentValue) {
163+
this.trySetName(key, part);
164+
}
165+
} else {
166+
if (!this.trySetNumber(value, part)) {
167+
this.trySetNumber(key, part);
168+
}
169+
}
170+
}
171+
},
172+
173+
trySetNumber: function(value, part) {
174+
var number = parseInt(value, 10);
175+
if (!isNaN(number) && DateSetters[part.type](this.date, number)) {
176+
delete part.name;
177+
part.currentValue = value;
178+
part.value = value;
179+
this.setValue();
180+
this.element.setSelectionRange(part.position, part.position + value.length);
181+
return true;
182+
}
183+
},
184+
185+
trySetName: function(value, part) {
186+
for (let idx = 0; idx < part.names.length; idx++) {
187+
if (part.names[idx].toLowerCase().startsWith(value)) {
188+
this.date.setMonth(idx);
189+
part.currentValue = value;
190+
part.value = intl.formatDate(this.date, { pattern: part.pattern });
191+
delete part.name;
192+
this.setValue();
193+
this.element.setSelectionRange(part.position, part.position + value.length);
194+
return true;
195+
}
196+
}
197+
198+
},
199+
200+
getPartIndex: function(position) {
201+
var parts = this.parts;
202+
var length = 0;
203+
var idx = -1;
204+
var part;
205+
206+
do {
207+
idx++;
208+
part = parts[idx];
209+
part.position = length;
210+
length += (part.name || part.value).length;
211+
} while (length < position);
212+
213+
if (part.type === "literal") {
214+
idx += parts[idx + 1] ? 1 : -1;
215+
if (parts[idx]) {
216+
parts[idx].position = length;
217+
}
218+
}
219+
220+
return idx;
221+
}
222+
};
223+
224+
var input = new Input(document.getElementsByTagName("input")[0], "MMMM/d/y", new Date());
225+
</script>
226+
</body>
227+
</html>

src/cldr.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export interface DateFormatNameOptions {
2020
/**
2121
* Specifies whether the standalone names should be returned.
2222
*/
23-
standAlone?: string;
23+
standAlone?: boolean;
2424
}
2525

2626
/**

src/dates.d.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { DateFormatNameOptions } from './cldr';
2+
13
/**
24
* Settings for the formatDate and parseDate functions.
35
*/
@@ -7,6 +9,11 @@ export interface DateFormatOptions {
79
*/
810
skeleton?: string;
911

12+
/**
13+
* Defines the exact pattern to be used to format the date.
14+
*/
15+
pattern?: string;
16+
1017
/**
1118
* Specifies which of the locale `dateFormats` should be used to format the value.
1219
*/
@@ -93,12 +100,28 @@ export function formatDate(value: Date, format: string|DateFormatOptions, locale
93100
*/
94101
export function parseDate(value: string, format?: string | DateFormatOptions | string[] | DateFormatOptions[], locale?: string): Date;
95102

103+
export interface DateFormatPart {
104+
/**
105+
* Specifies the type of the format part.
106+
*/
107+
type?: 'era' | 'year' | 'quarter' | 'month' | 'day' | 'weekday' | 'hour' | 'minute' | 'second' | 'dayperiod' | 'zone' | 'literal';
108+
109+
/**
110+
* Specifies the pattern of the format part.
111+
*/
112+
pattern?: string;
113+
114+
/**
115+
* Specifies the format names options.
116+
*/
117+
names?: DateFormatNameOptions;
118+
}
119+
96120
/**
97-
* Returns the full format based on the Date object and the specified format. If no format is provided, the default short date format is used.
121+
* Splits the date format into objects containing information about each part of the pattern.
98122
*
99-
* @param value The date to format.
100123
* @param format The format string or options.
101124
* @param locale The optional locale id. If not specified, the `"en"` locale id is used.
102-
* @returns The full date format.
125+
* @returns The date format parts.
103126
*/
104-
export function dateFormatString(value: Date, format: string|DateFormatOptions, locale?: string): string;
127+
export function splitDateFormat(format: string|DateFormatOptions, locale?: string): DateFormatPart[];

src/dates.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
export { default as formatDate } from './dates/format-date';
22
export { default as parseDate } from './dates/parse-date';
3-
export { default as dateFormatString } from './dates/date-format-string';
3+
export { default as splitDateFormat } from './dates/split-date-format';

src/dates/constants.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const MONTH = 'month';
2+
const HOUR = 'hour';
3+
const ZONE = 'zone';
4+
const WEEKDAY = 'weekday';
5+
const QUARTER = 'quarter';
6+
7+
const DATE_FIELD_MAP = {
8+
'G': 'era',
9+
'y': 'year',
10+
'q': QUARTER,
11+
'Q': QUARTER,
12+
'M': MONTH,
13+
'L': MONTH,
14+
'd': 'day',
15+
'E': WEEKDAY,
16+
'c': WEEKDAY,
17+
'e': WEEKDAY,
18+
'h': HOUR,
19+
'H': HOUR,
20+
'm': 'minute',
21+
's': 'second',
22+
'a': 'dayperiod',
23+
'x': ZONE,
24+
'X': ZONE,
25+
'z': ZONE,
26+
'Z': ZONE
27+
};
28+
29+
const dateFormatRegExp = /d{1,2}|E{1,6}|e{1,6}|c{3,6}|c{1}|M{1,5}|L{1,5}|y{1,4}|H{1,2}|h{1,2}|m{1,2}|a{1,5}|s{1,2}|S{1,3}|z{1,4}|Z{1,5}|x{1,5}|X{1,5}|G{1,5}|q{1,5}|Q{1,5}|"[^"]*"|'[^']*'/g;
30+
31+
export { dateFormatRegExp, DATE_FIELD_MAP };

src/dates/date-format-string.js

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)