Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support q/Q for quarters #57

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const u_SEC = 2 ** 6;
export const u_DSEC = 2 ** 7; // decisecond
export const u_CSEC = 2 ** 8; // centisecond
export const u_MSEC = 2 ** 9; // millisecond
export const u_QUARTER = 2 ** 10;

// Excel date boundaries
export const MIN_S_DATE = 0;
Expand Down
3 changes: 3 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ function prepareFormatterData (pattern, shouldThrow = false) {
* When the formatter encounters `*` it normally emits nothing instead of the
* `*` and the next character (like Excel TEXT function does). Setting this
* to a character will make the formatter emit that followed by the next one.
* @param {boolean} [options.q=false]
* Enables using the "q" for quarters. This does not work in spreadsheets so
* it is off by default.
* @returns {string} A formatted value
*/
export function format (pattern, value, options = {}) {
Expand Down
2 changes: 2 additions & 0 deletions lib/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export const defaultOptions = {
dateSpanLarge: true,
// Simulate the Lotus 1-2-3 leap year bug
leap1900: true,
// Allow the use of "q" to mean quarters
q: false,
// Emit regular vs. non-breaking spaces
nbsp: false,
// Robust/throw mode
Expand Down
33 changes: 16 additions & 17 deletions lib/parseFormatSection.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable padded-blocks */
import { resolveLocale } from './locale.js';
import {
u_YEAR, u_MONTH, u_DAY, u_HOUR, u_MIN, u_SEC, u_DSEC, u_CSEC, u_MSEC,
u_YEAR, u_QUARTER, u_MONTH, u_DAY, u_HOUR, u_MIN, u_SEC, u_DSEC, u_CSEC, u_MSEC,
EPOCH_1900, EPOCH_1317,
TOKEN_AMPM, TOKEN_BREAK, TOKEN_CALENDAR, TOKEN_CHAR, TOKEN_COLOR, TOKEN_COMMA, TOKEN_CONDITION,
TOKEN_DATETIME, TOKEN_DBNUM, TOKEN_DIGIT, TOKEN_DURATION, TOKEN_ERROR, TOKEN_ESCAPED, TOKEN_EXP,
Expand Down Expand Up @@ -236,34 +236,33 @@ export function parseFormatSection (inputTokens) {
const bit = { type: '', size: 0, date: 1 };
const value = token.value.toLowerCase(); // deal with in tokenizer?
const startsWith = value[0];
if (value === 'y' || value === 'yy') {
if (startsWith === 'y' || startsWith === 'e') {
bit.size = u_YEAR;
bit.type = 'year-short';
}
else if (startsWith === 'y' || startsWith === 'e') {
bit.size = u_YEAR;
bit.type = 'year';
}
else if (value === 'b' || value === 'bb') {
bit.size = u_YEAR;
bit.type = 'b-year-short';
bit.type = value === 'y' || value === 'yy'
? 'year-short'
: 'year';
}
else if (startsWith === 'b') {
bit.size = u_YEAR;
bit.type = 'b-year';
bit.type = value === 'b' || value === 'bb'
? 'b-year-short'
: 'b-year';
}
else if (startsWith === 'q') {
bit.size = u_QUARTER;
bit.type = value === 'q' ? 'quarter-short' : 'quarter';
bit.value = token.value;
}
else if (value === 'd' || value === 'dd') {
bit.size = u_DAY;
bit.type = 'day';
bit.pad = /dd/.test(value);
}
else if (value === 'ddd' || value === 'aaa') {
bit.size = u_DAY;
bit.type = 'weekday-short';
}
else if (startsWith === 'd' || startsWith === 'a') {
bit.size = u_DAY;
bit.type = 'weekday';
bit.type = value === 'ddd' || value === 'aaa'
? 'weekday-short'
: 'weekday';
}
else if (startsWith === 'h') {
bit.size = u_HOUR;
Expand Down
8 changes: 8 additions & 0 deletions lib/runPart.js
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,14 @@ export function runPart (value, part, opts, l10n_) {
const y = year % 100;
ret.push(y < 10 ? '0' : '', y);
}
else if (tokenType === 'quarter') {
const q = String(1 + ~~((month - 1) / 3)).padStart(2, '0');
ret.push(opts.q ? q : tok.value);
}
else if (tokenType === 'quarter-short') {
const q = String(1 + ~~((month - 1) / 3));
ret.push(opts.q ? q : tok.value);
}
else if (tokenType === 'month') {
ret.push((tok.pad && month < 10 ? '0' : ''), month);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/tokenize.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const tokenHandlers = [
[ TOKEN_DIGIT, /^[1-9]/, 0 ],
[ TOKEN_CALENDAR, /^(?:B[12])/i, 0 ],
[ TOKEN_ERROR, /^B$/, 0 ], // pattern must not end in a "B"
[ TOKEN_DATETIME, /^(?:[hH]+|[mM]+|[sS]+|[yY]+|[bB]+|[dD]+|[gG]+|[aA]{3,}|e+)/, 0 ],
[ TOKEN_DATETIME, /^(?:[hH]+|[mM]+|[sS]+|[yY]+|[bB]+|[dD]+|[gG]+|[qQ]+|[aA]{3,}|e+)/, 0 ],
[ TOKEN_DURATION, /^(?:\[(h+|m+|s+)\])/i, 1 ],
[ TOKEN_CONDITION, /^\[(<[=>]?|>=?|=)\s*(-?[.\d]+)\]/, [ 1, 2 ] ],
[ TOKEN_DBNUM, /^\[(DBNum[0-4]?\d)\]/i, 1 ],
Expand Down
32 changes: 32 additions & 0 deletions test/date-parsing-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,38 @@ test('Date specifiers:', t => {
t.end();
});

test('Date specifiers: optional quarters', t => {
// default operation
t.format('yyyy\\Qq', date, '1909Qq');
t.format('yyyy\\Qqq', date, '1909Qqq');
t.format('yyyy\\Qqqq', date, '1909Qqqq');
t.format('yyyy\\Qqqqq', date, '1909Qqqqq');
t.format('yyyy\\Qqqqqq', date, '1909Qqqqqq');
// opting into quarters
t.format('yyyy\\Qq', date, '1909Q1', { q: true });
t.format('yyyy\\Qqq', date, '1909Q01', { q: true });
t.format('yyyy\\Qqqq', date, '1909Q01', { q: true });
t.format('yyyy\\Qqqqq', date, '1909Q01', { q: true });
t.format('yyyy\\Qqqqqq', date, '1909Q01', { q: true });
t.format('yyyy"Q"q', date, '1909Q1', { q: true });
t.format('yyyy-"Q"Q', date, '1909-Q1', { q: true });
t.format('yyyy-"Q"Q', date, '1909-Q1', { q: true });
// correct quarters
t.format('yyyy"Q"q', 31413, '1986Q1', { q: true });
t.format('yyyy"Q"q', 31444, '1986Q1', { q: true });
t.format('yyyy"Q"q', 31472, '1986Q1', { q: true });
t.format('yyyy"Q"q', 31503, '1986Q2', { q: true });
t.format('yyyy"Q"q', 31533, '1986Q2', { q: true });
t.format('yyyy"Q"q', 31564, '1986Q2', { q: true });
t.format('yyyy"Q"q', 31594, '1986Q3', { q: true });
t.format('yyyy"Q"q', 31625, '1986Q3', { q: true });
t.format('yyyy"Q"q', 31656, '1986Q3', { q: true });
t.format('yyyy"Q"q', 31686, '1986Q4', { q: true });
t.format('yyyy"Q"q', 31717, '1986Q4', { q: true });
t.format('yyyy"Q"q', 31747, '1986Q4', { q: true });
t.end();
});

test('Date specifiers: month vs. minute', t => {
t.format('h', date, '3');
t.format('m', date, '1');
Expand Down