diff --git a/.gitignore b/.gitignore
index da592f8d..07f7564f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
node_modules
.DS_Store
dist
+
+\.idea/
diff --git a/example.js b/example.js
index 4bd9d03d..ed0d5e80 100644
--- a/example.js
+++ b/example.js
@@ -13,6 +13,13 @@ let interval;
initial: `terkelg`,
format: v => `@${v}`
},
+ {
+ type: 'date',
+ name: 'birthday',
+ message: `What's your birth day?`,
+ mask: '"Year:" YYYY, "Month:" MM, "Day:" DD \\\\\\\\||// \\Hour: HH, \\Minute: mm, "Seconds:" ss',
+ validate: date => date > Date.now() ? `Your birth day can't be in the future` : true
+ },
{
type: 'number',
name: 'age',
diff --git a/lib/dateparts/datepart.js b/lib/dateparts/datepart.js
new file mode 100644
index 00000000..62b893bc
--- /dev/null
+++ b/lib/dateparts/datepart.js
@@ -0,0 +1,35 @@
+'use strict';
+
+class DatePart {
+ constructor({token, date, parts, locales}) {
+ this.token = token;
+ this.date = date || new Date();
+ this.parts = parts || [this];
+ this.locales = locales || {};
+ }
+
+ up() {}
+
+ down() {}
+
+ next() {
+ const currentIdx = this.parts.indexOf(this);
+ return this.parts.find((part, idx) => idx > currentIdx && part instanceof DatePart);
+ }
+
+ setTo(val) {}
+
+ prev() {
+ let parts = [].concat(this.parts).reverse();
+ const currentIdx = parts.indexOf(this);
+ return parts.find((part, idx) => idx > currentIdx && part instanceof DatePart);
+ }
+
+ toString() {
+ return String(this.date);
+ }
+}
+
+module.exports = DatePart;
+
+
diff --git a/lib/dateparts/day.js b/lib/dateparts/day.js
new file mode 100644
index 00000000..5db84fe1
--- /dev/null
+++ b/lib/dateparts/day.js
@@ -0,0 +1,42 @@
+'use strict';
+
+const DatePart = require('./datepart');
+
+const pos = n => {
+ n = n % 10;
+ return n === 1 ? 'st'
+ : n === 2 ? 'nd'
+ : n === 3 ? 'rd'
+ : 'th';
+}
+
+class Day extends DatePart {
+ constructor(opts={}) {
+ super(opts);
+ }
+
+ up() {
+ this.date.setDate(this.date.getDate() + 1);
+ }
+
+ down() {
+ this.date.setDate(this.date.getDate() - 1);
+ }
+
+ setTo(val) {
+ this.date.setDate(parseInt(val.substr(-2)));
+ }
+
+ toString() {
+ let date = this.date.getDate();
+ let day = this.date.getDay();
+ return this.token === 'DD' ? String(date).padStart(2, '0')
+ : this.token === 'Do' ? date + pos(date)
+ : this.token === 'd' ? day + 1
+ : this.token === 'ddd' ? this.locales.weekdaysShort[day]
+ : this.token === 'dddd' ? this.locales.weekdays[day]
+ : date;
+ }
+}
+
+module.exports = Day;
diff --git a/lib/dateparts/hours.js b/lib/dateparts/hours.js
new file mode 100644
index 00000000..171b3d2c
--- /dev/null
+++ b/lib/dateparts/hours.js
@@ -0,0 +1,30 @@
+'use strict';
+
+const DatePart = require('./datepart');
+
+class Hours extends DatePart {
+ constructor(opts={}) {
+ super(opts);
+ }
+
+ up() {
+ this.date.setHours(this.date.getHours() + 1);
+ }
+
+ down() {
+ this.date.setHours(this.date.getHours() - 1);
+ }
+
+ setTo(val) {
+ this.date.setHours(parseInt(val.substr(-2)));
+ }
+
+ toString() {
+ let hours = this.date.getHours();
+ if (/h/.test(this.token))
+ hours = (hours % 12) || 12;
+ return this.token.length > 1 ? String(hours).padStart(2, '0') : hours;
+ }
+}
+
+module.exports = Hours;
diff --git a/lib/dateparts/index.js b/lib/dateparts/index.js
new file mode 100644
index 00000000..dc0cc953
--- /dev/null
+++ b/lib/dateparts/index.js
@@ -0,0 +1,13 @@
+'use strict';
+
+module.exports = {
+ DatePart: require('./datepart'),
+ Meridiem: require('./meridiem'),
+ Day: require('./day'),
+ Hours: require('./hours'),
+ Milliseconds: require('./milliseconds'),
+ Minutes: require('./minutes'),
+ Month: require('./month'),
+ Seconds: require('./seconds'),
+ Year: require('./year'),
+}
diff --git a/lib/dateparts/meridiem.js b/lib/dateparts/meridiem.js
new file mode 100644
index 00000000..8488677b
--- /dev/null
+++ b/lib/dateparts/meridiem.js
@@ -0,0 +1,24 @@
+'use strict';
+
+const DatePart = require('./datepart');
+
+class Meridiem extends DatePart {
+ constructor(opts={}) {
+ super(opts);
+ }
+
+ up() {
+ this.date.setHours((this.date.getHours() + 12) % 24);
+ }
+
+ down() {
+ this.up();
+ }
+
+ toString() {
+ let meridiem = this.date.getHours() > 12 ? 'pm' : 'am';
+ return /\A/.test(this.token) ? meridiem.toUpperCase() : meridiem;
+ }
+}
+
+module.exports = Meridiem;
diff --git a/lib/dateparts/milliseconds.js b/lib/dateparts/milliseconds.js
new file mode 100644
index 00000000..89842702
--- /dev/null
+++ b/lib/dateparts/milliseconds.js
@@ -0,0 +1,28 @@
+'use strict';
+
+const DatePart = require('./datepart');
+
+class Milliseconds extends DatePart {
+ constructor(opts={}) {
+ super(opts);
+ }
+
+ up() {
+ this.date.setMilliseconds(this.date.getMilliseconds() + 1);
+ }
+
+ down() {
+ this.date.setMilliseconds(this.date.getMilliseconds() - 1);
+ }
+
+ setTo(val) {
+ this.date.setMilliseconds(parseInt(val.substr(-(this.token.length))));
+ }
+
+ toString() {
+ return String(this.date.getMilliseconds()).padStart(4, '0')
+ .substr(0, this.token.length);
+ }
+}
+
+module.exports = Milliseconds;
diff --git a/lib/dateparts/minutes.js b/lib/dateparts/minutes.js
new file mode 100644
index 00000000..aa1d8f7e
--- /dev/null
+++ b/lib/dateparts/minutes.js
@@ -0,0 +1,28 @@
+'use strict';
+
+const DatePart = require('./datepart');
+
+class Minutes extends DatePart {
+ constructor(opts={}) {
+ super(opts);
+ }
+
+ up() {
+ this.date.setMinutes(this.date.getMinutes() + 1);
+ }
+
+ down() {
+ this.date.setMinutes(this.date.getMinutes() - 1);
+ }
+
+ setTo(val) {
+ this.date.setMinutes(parseInt(val.substr(-2)));
+ }
+
+ toString() {
+ let m = this.date.getMinutes();
+ return this.token.length > 1 ? String(m).padStart(2, '0') : m;
+ }
+}
+
+module.exports = Minutes;
diff --git a/lib/dateparts/month.js b/lib/dateparts/month.js
new file mode 100644
index 00000000..f6564559
--- /dev/null
+++ b/lib/dateparts/month.js
@@ -0,0 +1,33 @@
+'use strict';
+
+const DatePart = require('./datepart');
+
+class Month extends DatePart {
+ constructor(opts={}) {
+ super(opts);
+ }
+
+ up() {
+ this.date.setMonth(this.date.getMonth() + 1);
+ }
+
+ down() {
+ this.date.setMonth(this.date.getMonth() - 1);
+ }
+
+ setTo(val) {
+ val = parseInt(val.substr(-2)) - 1;
+ this.date.setMonth(val < 0 ? 0 : val);
+ }
+
+ toString() {
+ let month = this.date.getMonth();
+ let tl = this.token.length;
+ return tl === 2 ? String(month + 1).padStart(2, '0')
+ : tl === 3 ? this.locales.monthsShort[month]
+ : tl === 4 ? this.locales.months[month]
+ : String(month + 1);
+ }
+}
+
+module.exports = Month;
diff --git a/lib/dateparts/seconds.js b/lib/dateparts/seconds.js
new file mode 100644
index 00000000..0c1a1a4f
--- /dev/null
+++ b/lib/dateparts/seconds.js
@@ -0,0 +1,28 @@
+'use strict';
+
+const DatePart = require('./datepart');
+
+class Seconds extends DatePart {
+ constructor(opts={}) {
+ super(opts);
+ }
+
+ up() {
+ this.date.setSeconds(this.date.getSeconds() + 1);
+ }
+
+ down() {
+ this.date.setSeconds(this.date.getSeconds() - 1);
+ }
+
+ setTo(val) {
+ this.date.setSeconds(parseInt(val.substr(-2)));
+ }
+
+ toString() {
+ let s = this.date.getSeconds();
+ return this.token.length > 1 ? String(s).padStart(2, '0') : s;
+ }
+}
+
+module.exports = Seconds;
diff --git a/lib/dateparts/year.js b/lib/dateparts/year.js
new file mode 100644
index 00000000..f068e430
--- /dev/null
+++ b/lib/dateparts/year.js
@@ -0,0 +1,28 @@
+'use strict';
+
+const DatePart = require('./datepart');
+
+class Year extends DatePart {
+ constructor(opts={}) {
+ super(opts);
+ }
+
+ up() {
+ this.date.setFullYear(this.date.getFullYear() + 1);
+ }
+
+ down() {
+ this.date.setFullYear(this.date.getFullYear() - 1);
+ }
+
+ setTo(val) {
+ this.date.setFullYear(val.substr(-4));
+ }
+
+ toString() {
+ let year = String(this.date.getFullYear()).padStart(4, '0');
+ return this.token.length === 2 ? year.substr(-2) : year;
+ }
+}
+
+module.exports = Year;
diff --git a/lib/elements/confirm.js b/lib/elements/confirm.js
index 6c776bdb..e5ce738f 100644
--- a/lib/elements/confirm.js
+++ b/lib/elements/confirm.js
@@ -66,7 +66,7 @@ class ConfirmPrompt extends Prompt {
render() {
if (this.closed) return;
- if (this.first) this.out.write(cursor.hide);
+ if (this.firstRender) this.out.write(cursor.hide);
super.render();
this.out.write(
diff --git a/lib/elements/date.js b/lib/elements/date.js
new file mode 100644
index 00000000..ad29bc35
--- /dev/null
+++ b/lib/elements/date.js
@@ -0,0 +1,212 @@
+'use strict';
+
+const color = require('kleur');
+const Prompt = require('./prompt');
+const { style, clear, figures, strip } = require('../util');
+const { erase, cursor } = require('sisteransi');
+const { DatePart, Meridiem, Day, Hours, Milliseconds, Minutes, Month, Seconds, Year } = require('../dateparts');
+
+const regex = /\\(.)|"((?:\\["\\]|[^"])+)"|(D[Do]?|d{3,4}|d)|(M{1,4})|(YY(?:YY)?)|([aA])|([Hh]{1,2})|(m{1,2})|(s{1,2})|(S{1,4})|./g;
+const regexGroups = {
+ 1: ({token}) => token.replace(/\\(.)/g, '$1'),
+ 2: (opts) => new Day(opts), // Day // TODO
+ 3: (opts) => new Month(opts), // Month
+ 4: (opts) => new Year(opts), // Year
+ 5: (opts) => new Meridiem(opts), // AM/PM // TODO (special)
+ 6: (opts) => new Hours(opts), // Hours
+ 7: (opts) => new Minutes(opts), // Minutes
+ 8: (opts) => new Seconds(opts), // Seconds
+ 9: (opts) => new Milliseconds(opts), // Fractional seconds
+}
+
+const dfltLocales = {
+ months: 'January,February,March,April,May,June,July,August,September,October,November,December'.split(','),
+ monthsShort: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','),
+ weekdays: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','),
+ weekdaysShort: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(',')
+}
+
+/**
+ * DatePrompt Base Element
+ * @param {Object} opts Options
+ * @param {String} opts.message Message
+ * @param {Number} [opts.initial] Index of default value
+ * @param {Stream} [opts.stdin] The Readable stream to listen to
+ * @param {Stream} [opts.stdout] The Writable stream to write readline data to
+ * @param {String} [opts.mask] The format mask
+ */
+class DatePrompt extends Prompt {
+ constructor(opts={}) {
+ super(opts);
+ this.msg = opts.message;
+ this.cursor = 0;
+ this.typed = '';
+ this.locales = Object.assign(dfltLocales, opts.locales);
+ this._date = opts.initial || new Date();
+ this.errorMsg = opts.error || 'Please Enter A Valid Value';
+ this.validator = opts.validate || (() => true);
+ this.mask = opts.mask || 'YYYY-MM-DD HH:mm:ss';
+ this.clear = clear('');
+ this.render();
+ }
+
+ get value() {
+ return this.date
+ }
+
+ get date() {
+ return this._date;
+ }
+
+ set date(date) {
+ if (date) this._date.setTime(date.getTime());
+ }
+
+ set mask(mask) {
+ let result;
+ this.parts = [];
+ while(result = regex.exec(mask)) {
+ let match = result.shift();
+ let idx = result.findIndex(gr => gr != null);
+ this.parts.push(idx in regexGroups
+ ? regexGroups[idx]({ token: result[idx] || match, date: this.date, parts: this.parts, locales: this.locales })
+ : result[idx] || match);
+ }
+
+ let parts = this.parts.reduce((arr, i) => {
+ if (typeof i === 'string' && typeof arr[arr.length - 1] === 'string')
+ arr[arr.length - 1] += i;
+ else arr.push(i);
+ return arr;
+ }, []);
+
+ this.parts.splice(0);
+ this.parts.push(...parts);
+ this.reset();
+ }
+
+ moveCursor(n) {
+ this.typed = '';
+ this.cursor = n;
+ this.fire();
+ }
+
+ reset() {
+ this.moveCursor(this.parts.findIndex(p => p instanceof DatePart));
+ this.fire();
+ this.render();
+ }
+
+ abort() {
+ this.done = this.aborted = true;
+ this.error = false;
+ this.fire();
+ this.render();
+ this.out.write('\n');
+ this.close();
+ }
+
+ async validate() {
+ let valid = await this.validator(this.value);
+ if (typeof valid === 'string') {
+ this.errorMsg = valid;
+ valid = false;
+ }
+ this.error = !valid;
+ }
+
+ async submit() {
+ await this.validate();
+ if (this.error) {
+ this.color = 'red';
+ this.fire();
+ this.render();
+ return;
+ }
+ this.done = true;
+ this.aborted = false;
+ this.fire();
+ this.render();
+ this.out.write('\n');
+ this.close();
+ }
+
+ up() {
+ this.typed = '';
+ this.parts[this.cursor].up();
+ this.render();
+ }
+
+ down() {
+ this.typed = '';
+ this.parts[this.cursor].down();
+ this.render();
+ }
+
+ left() {
+ let prev = this.parts[this.cursor].prev();
+ if (prev == null) return this.bell();
+ this.moveCursor(this.parts.indexOf(prev));
+ this.render();
+ }
+
+ right() {
+ let next = this.parts[this.cursor].next();
+ if (next == null) return this.bell();
+ this.moveCursor(this.parts.indexOf(next));
+ this.render();
+ }
+
+ next() {
+ let next = this.parts[this.cursor].next();
+ this.moveCursor(next
+ ? this.parts.indexOf(next)
+ : this.parts.findIndex((part) => part instanceof DatePart));
+ this.render();
+ }
+
+ _(c) {
+ if (/\d/.test(c)) {
+ this.typed += c;
+ this.parts[this.cursor].setTo(this.typed);
+ this.render();
+ }
+ }
+
+ render() {
+ if (this.closed) return;
+ if (this.firstRender) this.out.write(cursor.hide);
+ else this.out.write(erase.lines(1));
+ super.render();
+ let clear = erase.line + (this.lines ? erase.down(this.lines) : '') + cursor.to(0);
+ this.lines = 0;
+
+ let error = '';
+ if (this.error) {
+ let lines = this.errorMsg.split('\n');
+ error = lines.reduce((a, l, i) => a + `\n${i ? ` ` : figures.pointerSmall} ${color.red().italic(l)}`, ``);
+ this.lines = lines.length;
+ }
+
+ // Print prompt
+ let prompt = [
+ style.symbol(this.done, this.aborted),
+ color.bold(this.msg),
+ style.delimiter(false),
+ this.parts.reduce((arr, p, idx) =>
+ arr.concat(idx === this.cursor
+ ? color.cyan().underline(p.toString())
+ : p), []).join(''),
+ ].join(' ');
+
+ let position = '';
+ if (this.lines) {
+ position += cursor.up(this.lines);
+ position += cursor.left+cursor.to(strip(prompt).length);
+ }
+
+ this.out.write(clear+prompt+error+position);
+ }
+}
+
+module.exports = DatePrompt;
diff --git a/lib/elements/index.js b/lib/elements/index.js
index 2091caeb..68d8fc65 100644
--- a/lib/elements/index.js
+++ b/lib/elements/index.js
@@ -4,6 +4,7 @@ module.exports = {
TextPrompt: require('./text'),
SelectPrompt: require('./select'),
TogglePrompt: require('./toggle'),
+ DatePrompt: require('./date'),
NumberPrompt: require('./number'),
MultiselectPrompt: require('./multiselect'),
AutocompletePrompt: require('./autocomplete'),
diff --git a/lib/elements/multiselect.js b/lib/elements/multiselect.js
index 4d62a597..f0fa9ec3 100644
--- a/lib/elements/multiselect.js
+++ b/lib/elements/multiselect.js
@@ -120,7 +120,7 @@ class MultiselectPrompt extends Prompt {
render() {
if (this.closed) return;
- if (this.first) this.out.write(cursor.hide);
+ if (this.firstRender) this.out.write(cursor.hide);
super.render();
// print prompt
diff --git a/lib/elements/number.js b/lib/elements/number.js
index 339bdb97..f0a50cf5 100644
--- a/lib/elements/number.js
+++ b/lib/elements/number.js
@@ -4,7 +4,6 @@ const { cursor, erase } = require('sisteransi');
const { style, clear, figures, strip } = require('../util');
const isNumber = /[0-9]/;
-const isValidChar = /\.|-/;
const isDef = any => any !== undefined;
const round = (number, precision) => {
let factor = Math.pow(10, precision);
@@ -108,7 +107,7 @@ class NumberPrompt extends Prompt {
return;
}
let x = this.value;
- this.value = x !== `` ? x : this.initial
+ this.value = x !== `` ? x : this.initial;
this.done = true;
this.aborted = false;
this.error = false;
@@ -178,7 +177,7 @@ class NumberPrompt extends Prompt {
let error = ``;
if (this.error) {
let lines = this.errorMsg.split(`\n`);
- error += lines.reduce((a, l, i) => a += `\n${i ? ` ` : figures.pointerSmall} ${color.red().italic(l)}`, ``);
+ error += lines.reduce((a, l, i) => a + `\n${i ? ` ` : figures.pointerSmall} ${color.red().italic(l)}`, ``);
this.lines = lines.length;
}
diff --git a/lib/elements/toggle.js b/lib/elements/toggle.js
index 124660ae..b542c096 100644
--- a/lib/elements/toggle.js
+++ b/lib/elements/toggle.js
@@ -94,7 +94,7 @@ class TogglePrompt extends Prompt {
render() {
if (this.closed) return;
- if (this.first) this.out.write(cursor.hide);
+ if (this.firstRender) this.out.write(cursor.hide);
super.render();
this.out.write(
diff --git a/lib/prompts.js b/lib/prompts.js
index dc60ce77..35b33cee 100644
--- a/lib/prompts.js
+++ b/lib/prompts.js
@@ -75,6 +75,24 @@ $.invisible = args => {
*/
$.number = args => toPrompt('NumberPrompt', args);
+/**
+ * Date prompt
+ * @param {string} args.message Prompt message to display
+ * @param {number} args.initial Default number value
+ * @param {function} [args.onState] On state change callback
+ * @param {number} [args.max] Max value
+ * @param {number} [args.min] Min value
+ * @param {string} [args.style="default"] Render style ('default', 'password', 'invisible')
+ * @param {Boolean} [opts.float=false] Parse input as floats
+ * @param {Number} [opts.round=2] Round floats to x decimals
+ * @param {Number} [opts.increment=1] Number to increment by when using arrow-keys
+ * @param {function} [args.validate] Function to validate user input
+ * @param {Stream} [args.stdin] The Readable stream to listen to
+ * @param {Stream} [args.stdout] The Writable stream to write readline data to
+ * @returns {Promise} Promise with user input
+ */
+$.date = args => toPrompt('DatePrompt', args);
+
/**
* Classic yes/no prompt
* @param {string} args.message Prompt message to display
diff --git a/readme.md b/readme.md
index 04f39855..d1869e6d 100755
--- a/readme.md
+++ b/readme.md
@@ -692,8 +692,8 @@ You can overwrite how choices are being filtered by passing your own suggest fun
| suggest | `function` | Filter function. Defaults to sort by `title` property. `suggest` should always return a promise. Filters using `title` by default |
| limit | `number` | Max number of results to show. Defaults to `10` |
| style | `string` | Render style (`default`, `password`, `invisible`, `emoji`). Defaults to `'default'` |
-| initial | Default initial value |
-| fallback | Fallback message when no match is found. Defaults to `initial` value if provided |
+| initial | | Default initial value |
+| fallback | | Fallback message when no match is found. Defaults to `initial` value if provided |
| onRender | `function` | On render callback. Keyword `this` refers to the current prompt |
| onState | `function` | On state change callback. Function signature is an `object` with two propetires: `value` and `aborted` |
@@ -704,6 +704,200 @@ const suggestByTitle = (input, choices) =>
```
+### date(message, [initial], [warn])
+> Interactive date prompt.
+
+Use left/right/tab to navigate. Use up/down to change date.
+
+#### Example
+
+
+```js
+{
+ type: 'date',
+ name: 'value',
+ message: 'Pick a date',
+ initial: new Date(1997, 09, 12),
+ validate: date => date > Date.now() ? 'Not in the future' : true
+}
+```
+
+#### Options
+| Param | Type | Description |
+| ----- | :--: | ----------- |
+| message | `string` | Prompt message to display |
+| initial | `date` | Default date |
+| locales | `object` | Use to define custom locales. See below for an example. |
+| mask | `string` | The format mask of the date. See below for more information.
Default: `YYYY-MM-DD HH:mm:ss` |
+| validate | `function` | Receive user input. Should return `true` if the value is valid, and an error message `String` otherwise. If `false` is returned, a default error message is shown |
+| onRender | `function` | On render callback. Keyword `this` refers to the current prompt |
+| onState | `function` | On state change callback. Function signature is an `object` with two propetires: `value` and `aborted` |
+
+Default locales:
+
+```javascript
+{
+ months: [
+ 'January', 'February', 'March', 'April',
+ 'May', 'June', 'July', 'August',
+ 'September', 'October', 'November', 'December'
+ ],
+ monthsShort: [
+ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
+ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
+ ],
+ weekdays: [
+ 'Sunday', 'Monday', 'Tuesday', 'Wednesday',
+ 'Thursday', 'Friday', 'Saturday'
+ ],
+ weekdaysShort: [
+ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'
+ ]
+}
+```
+
+#### Formatting Tokens
+
+
Token |
+ Output |
+
---|---|
Month | +|
M | +1 2 ... 11 12 | +
MM | +01 02 ... 11 12 | +
MMM | +Jan Feb ... Nov Dec | +
MMMM | +January February ... November December | +
Day of Month | +|
D | +1 2 ... 30 31 | +
Do | +1st 2nd ... 30th 31st | +
DD | +01 02 ... 30 31 | +
Day of Week | +|
d | +0 1 ... 5 6 | +
ddd | +Sun Mon ... Fri Sat | +
dddd | +Sunday Monday ... Friday Saturday | +
Year | +|
YY | +70 71 ... 29 30 | +
YYYY | +1970 1971 ... 2029 2030 | +
AM/PM | +|
A | +AM PM | +
a | +am pm | +
Hour | +|
H | +0 1 ... 22 23 | +
HH | +00 01 ... 22 23 | +
h | +1 2 ... 11 12 | +
hh | +01 02 ... 11 12 | +
Minute | +|
m | +0 1 ... 58 59 | +
mm | +00 01 ... 58 59 | +
Second | +|
s | +0 1 ... 58 59 | +
ss | +00 01 ... 58 59 | +
Fractional Second | +|
S | +0 1 ... 8 9 | +
SS | +0 1 ... 98 99 | +
SSS | +0 1 ... 998 999 | +
SSSS | +0 1 ... 9998 9999 | +