Merge pull request #11 from ing-bank/chore/splitDateFunctions

Split date functions
This commit is contained in:
Mikhail Bashkirov 2019-04-29 12:32:07 +02:00 committed by GitHub
commit 81033ef901
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 314 additions and 292 deletions

View file

@ -11,7 +11,7 @@ module.exports = config => {
// //
// npm run test -- --grep test/foo/bar.test.js // npm run test -- --grep test/foo/bar.test.js
// npm run test -- --grep test/bar/* // npm run test -- --grep test/bar/*
config.grep ? config.grep : 'packages/*/test/*.test.js', config.grep ? config.grep : 'packages/*/test/**/*.test.js',
], ],
// TODO: improve coverage // TODO: improve coverage

View file

@ -1,4 +1,6 @@
export { formatDate, getDateFormatBasedOnLocale, parseDate } from './src/formatDate.js'; export { formatDate } from './src/date/formatDate.js';
export { getDateFormatBasedOnLocale } from './src/date/getDateFormatBasedOnLocale.js';
export { parseDate } from './src/date/parseDate.js';
export { export {
formatNumber, formatNumber,
formatNumberToParts, formatNumberToParts,

View file

@ -0,0 +1,20 @@
import { splitDate } from './splitDate.js';
import { pad } from './pad.js';
/**
* To add a leading zero to a single number
*
* @param dateString
* @returns {*}
*/
export function addLeadingZero(dateString) {
const dateParts = splitDate(dateString);
const delimiter = dateParts ? dateParts[2] : '';
const uniformDateString = dateString.replace(/[.\-/\s]/g, delimiter);
const dateArray = uniformDateString.split && uniformDateString.split(delimiter);
if (!dateArray || dateArray.length !== 3) {
// prevent fail on invalid dates
return '';
}
return dateArray.map(pad).join('-');
}

View file

@ -0,0 +1,13 @@
import { trim } from './trim.js';
/**
* To clean date from added characters from IE
*
* @param dateAsString
* @returns {string|XML}
*/
export function clean(dateAsString) {
// list of separators is from wikipedia https://www.wikiwand.com/en/Date_format_by_country
// slash, point, dash or space
return trim(dateAsString.replace(/[^\d-. /]/g, ''));
}

View file

@ -0,0 +1,40 @@
import { getLocale } from './getLocale.js';
import { normalizeDate } from './normalizeDate.js';
/**
* Formats date based on locale and options
*
* @param date
* @param options
* @returns {*}
*/
export function formatDate(date, options) {
if (!(date instanceof Date)) {
return '0000-00-00';
}
const formatOptions = options || {};
// make sure months and days are always 2-digits
if (!options) {
formatOptions.year = 'numeric';
formatOptions.month = '2-digit';
formatOptions.day = '2-digit';
}
if (options && !(options && options.year)) {
formatOptions.year = 'numeric';
}
if (options && !(options && options.month)) {
formatOptions.month = '2-digit';
}
if (options && !(options && options.day)) {
formatOptions.day = '2-digit';
}
const computedLocale = getLocale(formatOptions && formatOptions.locale);
let formattedDate = '';
try {
formattedDate = new Intl.DateTimeFormat(computedLocale, formatOptions).format(date);
} catch (e) {
formattedDate = '';
}
return normalizeDate(formattedDate);
}

View file

@ -0,0 +1,33 @@
import { sanitizedDateTimeFormat } from './sanitizedDateTimeFormat.js';
import { splitDate } from './splitDate.js';
/**
* To compute the localized date format
*
* @returns {string}
*/
export function getDateFormatBasedOnLocale() {
function computePositions(dateParts) {
function getPartByIndex(index) {
return { 2012: 'year', 12: 'month', 20: 'day' }[dateParts[index]];
}
return [1, 3, 5].map(getPartByIndex);
}
// Arbitrary date with different values for year,month,day
const date = new Date();
date.setDate(20);
date.setMonth(11);
date.setFullYear(2012);
// Strange characters added by IE11 need to be taken into account here
const formattedDate = sanitizedDateTimeFormat(date);
// For Dutch locale, dateParts would match: [ 1:'20', 2:'-', 3:'12', 4:'-', 5:'2012' ]
const dateParts = splitDate(formattedDate);
const dateFormat = {};
dateFormat.positions = computePositions(dateParts);
return `${dateFormat.positions[0]}-${dateFormat.positions[1]}-${dateFormat.positions[2]}`;
}

View file

@ -0,0 +1,17 @@
import { localize } from '../localize.js';
/**
* Gets the locale to use
*
* @param {string} locale Locale to override browser locale
* @returns {string}
*/
export function getLocale(locale) {
if (locale) {
return locale;
}
if (localize && localize.locale) {
return localize.locale;
}
return 'en-GB';
}

View file

@ -0,0 +1,22 @@
/**
* To filter out some added characters in IE
*
* @param str
* @returns {string}
*/
export function normalizeDate(str) {
const dateString = [];
for (let i = 0, n = str.length; i < n; i += 1) {
// remove unicode 160
if (str.charCodeAt(i) === 160) {
dateString.push(' ');
// remove unicode 8206
} else if (str.charCodeAt(i) === 8206) {
dateString.push('');
} else {
dateString.push(str.charAt(i));
}
}
return dateString.join('');
}

View file

@ -0,0 +1,11 @@
/**
* To get the absolute value of a number.
*
* @param n
* @returns {string}
*/
export function pad(n) {
const v = Math.abs(n);
return String(v < 10 ? `0${v}` : v);
}

View file

@ -0,0 +1,58 @@
import { localize } from '../localize.js';
import { getDateFormatBasedOnLocale } from './getDateFormatBasedOnLocale.js';
import { addLeadingZero } from './addLeadingZero.js';
const memoize = (fn, parm) => {
const cache = {};
return () => {
const n = parm;
if (n in cache) {
return cache[n];
}
const result = fn(n);
cache[n] = result;
return result;
};
};
const memoizedGetDateFormatBasedOnLocale = memoize(getDateFormatBasedOnLocale, localize.locale);
/**
* To parse a date into the right format
*
* @param date
* @returns {Date}
*/
export function parseDate(date) {
const stringToParse = addLeadingZero(date);
let parsedString;
switch (memoizedGetDateFormatBasedOnLocale()) {
case 'day-month-year':
parsedString = `${stringToParse.slice(6, 10)}/${stringToParse.slice(
3,
5,
)}/${stringToParse.slice(0, 2)}`;
break;
case 'month-day-year':
parsedString = `${stringToParse.slice(6, 10)}/${stringToParse.slice(
0,
2,
)}/${stringToParse.slice(3, 5)}`;
break;
case 'year-month-day':
parsedString = `${stringToParse.slice(0, 4)}/${stringToParse.slice(
5,
7,
)}/${stringToParse.slice(8, 10)}`;
break;
default:
parsedString = '0000/00/00';
}
const parsedDate = new Date(parsedString);
// Check if parsedDate is not `Invalid Date`
// eslint-disable-next-line no-restricted-globals
if (!isNaN(parsedDate)) {
return parsedDate;
}
return undefined;
}

View file

@ -0,0 +1,13 @@
import { formatDate } from './formatDate.js';
import { clean } from './clean.js';
/**
* To sanitize a date from IE11 handling
*
* @param date
* @returns {string|XML}
*/
export function sanitizedDateTimeFormat(date) {
const fDate = formatDate(date);
return clean(fDate);
}

View file

@ -0,0 +1,9 @@
/**
* To split a date into days, months, years, etc
*
* @param date
* @returns {Array|{index: number, input: string}|*}
*/
export function splitDate(date) {
return date.match(/(\d{1,4})([^\d]+)(\d{1,4})([^\d]+)(\d{1,4})/);
}

View file

@ -0,0 +1,9 @@
/**
* To trim the date
*
* @param dateAsString
* @returns {string|XML}
*/
export function trim(dateAsString) {
return dateAsString.replace(/^[^\d]*/g, '').replace(/[^\d]*$/g, '');
}

View file

@ -1,236 +0,0 @@
import { localize } from './localize.js';
/**
* Gets the locale to use
*
* @param {string} locale Locale to override browser locale
* @returns {string}
*/
function getLocale(locale) {
if (locale) {
return locale;
}
if (localize && localize.locale) {
return localize.locale;
}
return 'en-GB';
}
/**
* To filter out some added characters in IE
*
* @param str
* @returns {string}
*/
function normalizeDate(str) {
const dateString = [];
for (let i = 0, n = str.length; i < n; i += 1) {
// remove unicode 160
if (str.charCodeAt(i) === 160) {
dateString.push(' ');
// remove unicode 8206
} else if (str.charCodeAt(i) === 8206) {
dateString.push('');
} else {
dateString.push(str.charAt(i));
}
}
return dateString.join('');
}
/**
* Formats date based on locale and options
*
* @param date
* @param options
* @returns {*}
*/
export function formatDate(date, options) {
if (!(date instanceof Date)) {
return '0000-00-00';
}
const formatOptions = options || {};
// make sure months and days are always 2-digits
if (!options) {
formatOptions.year = 'numeric';
formatOptions.month = '2-digit';
formatOptions.day = '2-digit';
}
if (options && !(options && options.year)) {
formatOptions.year = 'numeric';
}
if (options && !(options && options.month)) {
formatOptions.month = '2-digit';
}
if (options && !(options && options.day)) {
formatOptions.day = '2-digit';
}
const computedLocale = getLocale(formatOptions && formatOptions.locale);
let formattedDate = '';
try {
formattedDate = new Intl.DateTimeFormat(computedLocale, formatOptions).format(date);
} catch (e) {
formattedDate = '';
}
return normalizeDate(formattedDate);
}
/**
* To trim the date
*
* @param dateAsString
* @returns {string|XML}
*/
function trim(dateAsString) {
return dateAsString.replace(/^[^\d]*/g, '').replace(/[^\d]*$/g, '');
}
/**
* To clean date from added characters from IE
*
* @param dateAsString
* @returns {string|XML}
*/
function clean(dateAsString) {
// list of separators is from wikipedia https://www.wikiwand.com/en/Date_format_by_country
// slash, point, dash or space
return trim(dateAsString.replace(/[^\d-. /]/g, ''));
}
/**
* To get the absolute value of a number.
*
* @param n
* @returns {string}
*/
function pad(n) {
const v = Math.abs(n);
return String(v < 10 ? `0${v}` : v);
}
/**
* To sanitize a date from IE11 handling
*
* @param date
* @returns {string|XML}
*/
function sanitizedDateTimeFormat(date) {
const fDate = formatDate(date);
return clean(fDate);
}
/**
* To split a date into days, months, years, etc
*
* @param date
* @returns {Array|{index: number, input: string}|*}
*/
function splitDate(date) {
return date.match(/(\d{1,4})([^\d]+)(\d{1,4})([^\d]+)(\d{1,4})/);
}
/**
* To add a leading zero to a single number
*
* @param dateString
* @returns {*}
*/
function addLeadingZero(dateString) {
const dateParts = splitDate(dateString);
const delimiter = dateParts ? dateParts[2] : '';
const uniformDateString = dateString.replace(/[.\-/\s]/g, delimiter);
const dateArray = uniformDateString.split && uniformDateString.split(delimiter);
if (!dateArray || dateArray.length !== 3) {
// prevent fail on invalid dates
return '';
}
return dateArray.map(pad).join('-');
}
/**
* To compute the localized date format
*
* @returns {string}
*/
export function getDateFormatBasedOnLocale() {
function computePositions(dateParts) {
function getPartByIndex(index) {
return { 2012: 'year', 12: 'month', 20: 'day' }[dateParts[index]];
}
return [1, 3, 5].map(getPartByIndex);
}
// Arbitrary date with different values for year,month,day
const date = new Date();
date.setDate(20);
date.setMonth(11);
date.setFullYear(2012);
// Strange characters added by IE11 need to be taken into account here
const formattedDate = sanitizedDateTimeFormat(date);
// For Dutch locale, dateParts would match: [ 1:'20', 2:'-', 3:'12', 4:'-', 5:'2012' ]
const dateParts = splitDate(formattedDate);
const dateFormat = {};
dateFormat.positions = computePositions(dateParts);
return `${dateFormat.positions[0]}-${dateFormat.positions[1]}-${dateFormat.positions[2]}`;
}
const memoize = (fn, parm) => {
const cache = {};
return () => {
const n = parm;
if (n in cache) {
return cache[n];
}
const result = fn(n);
cache[n] = result;
return result;
};
};
const memoizedGetDateFormatBasedOnLocale = memoize(getDateFormatBasedOnLocale, localize.locale);
/**
* To parse a date into the right format
*
* @param date
* @returns {Date}
*/
export function parseDate(date) {
const stringToParse = addLeadingZero(date);
let parsedString;
switch (memoizedGetDateFormatBasedOnLocale()) {
case 'day-month-year':
parsedString = `${stringToParse.slice(6, 10)}/${stringToParse.slice(
3,
5,
)}/${stringToParse.slice(0, 2)}`;
break;
case 'month-day-year':
parsedString = `${stringToParse.slice(6, 10)}/${stringToParse.slice(
0,
2,
)}/${stringToParse.slice(3, 5)}`;
break;
case 'year-month-day':
parsedString = `${stringToParse.slice(0, 4)}/${stringToParse.slice(
5,
7,
)}/${stringToParse.slice(8, 10)}`;
break;
default:
parsedString = '0000/00/00';
}
const parsedDate = new Date(parsedString);
// Check if parsedDate is not `Invalid Date`
// eslint-disable-next-line no-restricted-globals
if (!isNaN(parsedDate)) {
return parsedDate;
}
return undefined;
}

View file

@ -10,7 +10,9 @@ import {
getGroupSeparator, getGroupSeparator,
getDecimalSeparator, getDecimalSeparator,
} from '../src/formatNumber.js'; } from '../src/formatNumber.js';
import { formatDate, parseDate, getDateFormatBasedOnLocale } from '../src/formatDate.js'; import { formatDate } from '../src/date/formatDate.js';
import { parseDate } from '../src/date/parseDate.js';
import { getDateFormatBasedOnLocale } from '../src/date/getDateFormatBasedOnLocale.js';
storiesOf('Localize System|Localize', module).add('lit component', () => { storiesOf('Localize System|Localize', module).add('lit component', () => {
class LitHtmlExample extends LocalizeMixin(LionLitElement) { class LitHtmlExample extends LocalizeMixin(LionLitElement) {

View file

@ -1,14 +1,15 @@
import { expect } from '@open-wc/testing'; import { expect } from '@open-wc/testing';
import { localize } from '../src/localize.js'; import { localize } from '../../src/localize.js';
import { localizeTearDown } from '../test-helpers.js'; import { localizeTearDown } from '../../test-helpers.js';
import { formatDate, parseDate, getDateFormatBasedOnLocale } from '../src/formatDate.js'; import { formatDate } from '../../src/date/formatDate.js';
import { parseDate } from '../../src/date/parseDate.js';
beforeEach(() => {
localizeTearDown();
});
describe('formatDate', () => { describe('formatDate', () => {
beforeEach(() => {
localizeTearDown();
});
it('displays the appropriate date based on locale', async () => { it('displays the appropriate date based on locale', async () => {
const testDate = new Date('2012/05/21'); const testDate = new Date('2012/05/21');
@ -112,49 +113,3 @@ describe('formatDate', () => {
expect(formatDate(date)).to.equal('0000-00-00'); expect(formatDate(date)).to.equal('0000-00-00');
}); });
}); });
describe('parseDate()', () => {
function equalsDate(value, date) {
return (
Object.prototype.toString.call(value) === '[object Date]' && // is Date Object
value.getDate() === date.getDate() && // day
value.getMonth() === date.getMonth() && // month
value.getFullYear() === date.getFullYear() // year
);
}
afterEach(() => {
// makes sure that between tests the localization is reset to default state
document.documentElement.lang = 'en-GB';
});
it('adds leading zeros', () => {
expect(equalsDate(parseDate('1-1-1979'), new Date('1979/01/01'))).to.equal(true);
expect(equalsDate(parseDate('1-11-1979'), new Date('1979/11/01'))).to.equal(true);
});
it('creates a date object', () => {
expect(parseDate('10/10/2000') instanceof Date).to.equal(true);
});
it('returns a date object', () => {
expect(equalsDate(parseDate('1-1-1979'), new Date('1979/01/01'))).to.equal(true);
expect(equalsDate(parseDate('31.12.1970'), new Date('1970/12/31'))).to.equal(true);
});
it('handles all kind of delimiters', () => {
expect(equalsDate(parseDate('12.12.1976'), new Date('1976/12/12'))).to.equal(true);
expect(equalsDate(parseDate('13.12.1976'), new Date('1976/12/13'))).to.equal(true);
expect(equalsDate(parseDate('14.12.1976'), new Date('1976/12/14'))).to.equal(true);
expect(equalsDate(parseDate('14.12-1976'), new Date('1976/12/14'))).to.equal(true);
expect(equalsDate(parseDate('14-12/1976'), new Date('1976/12/14'))).to.equal(true);
});
it('return undefined when no valid date provided', () => {
expect(parseDate('12.12.1976.,')).to.equal(undefined);
});
});
describe('getDateFormatBasedOnLocale()', () => {
it('returns the positions of day, month and year', async () => {
localize.locale = 'en-GB';
expect(getDateFormatBasedOnLocale()).to.equal('day-month-year');
localize.locale = 'en-US';
expect(getDateFormatBasedOnLocale()).to.equal('month-day-year');
});
});

View file

@ -0,0 +1,18 @@
import { expect } from '@open-wc/testing';
import { localize } from '../../src/localize.js';
import { localizeTearDown } from '../../test-helpers.js';
import { getDateFormatBasedOnLocale } from '../../src/date/getDateFormatBasedOnLocale.js';
describe('getDateFormatBasedOnLocale()', () => {
beforeEach(() => {
localizeTearDown();
});
it('returns the positions of day, month and year', async () => {
localize.locale = 'en-GB';
expect(getDateFormatBasedOnLocale()).to.equal('day-month-year');
localize.locale = 'en-US';
expect(getDateFormatBasedOnLocale()).to.equal('month-day-year');
});
});

View file

@ -0,0 +1,36 @@
import { expect } from '@open-wc/testing';
import { parseDate } from '../../src/date/parseDate.js';
function equalsDate(value, date) {
return (
Object.prototype.toString.call(value) === '[object Date]' && // is Date Object
value.getDate() === date.getDate() && // day
value.getMonth() === date.getMonth() && // month
value.getFullYear() === date.getFullYear() // year
);
}
describe('parseDate()', () => {
it('adds leading zeros', () => {
expect(equalsDate(parseDate('1-1-1979'), new Date('1979/01/01'))).to.equal(true);
expect(equalsDate(parseDate('1-11-1979'), new Date('1979/11/01'))).to.equal(true);
});
it('creates a date object', () => {
expect(parseDate('10/10/2000') instanceof Date).to.equal(true);
});
it('returns a date object', () => {
expect(equalsDate(parseDate('1-1-1979'), new Date('1979/01/01'))).to.equal(true);
expect(equalsDate(parseDate('31.12.1970'), new Date('1970/12/31'))).to.equal(true);
});
it('handles all kind of delimiters', () => {
expect(equalsDate(parseDate('12.12.1976'), new Date('1976/12/12'))).to.equal(true);
expect(equalsDate(parseDate('13.12.1976'), new Date('1976/12/13'))).to.equal(true);
expect(equalsDate(parseDate('14.12.1976'), new Date('1976/12/14'))).to.equal(true);
expect(equalsDate(parseDate('14.12-1976'), new Date('1976/12/14'))).to.equal(true);
expect(equalsDate(parseDate('14-12/1976'), new Date('1976/12/14'))).to.equal(true);
});
it('return undefined when no valid date provided', () => {
expect(parseDate('12.12.1976.,')).to.equal(undefined);
});
});