diff --git a/src/fiscal-periods/date-param.test.ts b/src/fiscal-periods/date-param.test.ts index 127d613..2cd7f59 100644 --- a/src/fiscal-periods/date-param.test.ts +++ b/src/fiscal-periods/date-param.test.ts @@ -1,4 +1,5 @@ import { DateParam } from '~/fiscal-periods/date-param' +import { ParseError } from '~/fiscal-periods/error' test('toString', () => { expect(new DateParam('latest').toString()).toEqual('latest') @@ -16,3 +17,14 @@ test('isLatest', () => { expect(new DateParam('latest').isLatest()).toBeTruthy() expect(new DateParam(new Date('2020-09-06')).isLatest()).toBeFalsy() }) + +test('parse', () => { + expect(DateParam.parse('2020-09-06')).toEqual( + new DateParam(new Date(2020, 9, 6)) + ) + expect(DateParam.parse('latest')).toEqual(new DateParam('latest')) + expect(DateParam.parse('Latest')).toEqual(new DateParam('latest')) + expect(() => DateParam.parse('foo')).toThrow(ParseError) + expect(() => DateParam.parse('2020-9-6')).toThrow(ParseError) + expect(() => DateParam.parse('2020/09/06')).toThrow(ParseError) +}) diff --git a/src/fiscal-periods/date-param.ts b/src/fiscal-periods/date-param.ts index 55138c8..ac5f530 100644 --- a/src/fiscal-periods/date-param.ts +++ b/src/fiscal-periods/date-param.ts @@ -1,3 +1,5 @@ +import { ParseError } from '~/fiscal-periods/error' + export class DateParam { constructor(public date: Date | 'latest') {} @@ -20,4 +22,21 @@ export class DateParam { public isLatest(): boolean { return this.date === 'latest' } + + static parse(str: string): DateParam { + str = str.toLowerCase() + if (str == 'latest') { + return new DateParam(str) + } + + const matches = str.match(/^(\d{4})-(\d{2})-(\d{2})$/) + if (matches == undefined) { + throw new ParseError(`Invalid date format: ${str}`) + } + + const year = parseInt(matches[1], 10) + const month = parseInt(matches[2], 10) + const day = parseInt(matches[3], 10) + return new DateParam(new Date(year, month, day)) + } } diff --git a/src/fiscal-periods/error.ts b/src/fiscal-periods/error.ts index 61e93f3..0166913 100644 --- a/src/fiscal-periods/error.ts +++ b/src/fiscal-periods/error.ts @@ -24,3 +24,12 @@ export class InvalidLYLQError implements Error { this.message = message } } + +export class ParseError implements Error { + public name = 'ParseError' + public message: string + + constructor(message = '') { + this.message = message + } +} diff --git a/src/fiscal-periods/period-parser.test.ts b/src/fiscal-periods/period-parser.test.ts new file mode 100644 index 0000000..fbda466 --- /dev/null +++ b/src/fiscal-periods/period-parser.test.ts @@ -0,0 +1,17 @@ +import { DateParam } from '~/fiscal-periods/date-param' +import { ParseError } from '~/fiscal-periods/error' +import { PeriodParser } from '~/fiscal-periods/period-parser' +import { YearQuarterParam } from '~/fiscal-periods/year-quarter-param' + +test('parse', () => { + expect(PeriodParser.parse('2020Q3')).toEqual(new YearQuarterParam(2020, 3)) + expect(PeriodParser.parse('LYLQ')).toEqual(new YearQuarterParam('LY', 'LQ')) + expect(PeriodParser.parse('2020-09-06')).toEqual( + new DateParam(new Date(2020, 9, 6)) + ) + expect(() => PeriodParser.parse('foo')).toThrow(ParseError) + expect(() => PeriodParser.parse('2020LQ')).toThrow(ParseError) + expect(() => PeriodParser.parse('LYQ3')).toThrow(ParseError) + expect(() => PeriodParser.parse('2020/09/06')).toThrow(ParseError) + expect(() => PeriodParser.parse('latest')).toThrow(ParseError) +}) diff --git a/src/fiscal-periods/period-parser.ts b/src/fiscal-periods/period-parser.ts new file mode 100644 index 0000000..b1a9e7f --- /dev/null +++ b/src/fiscal-periods/period-parser.ts @@ -0,0 +1,30 @@ +import { DateParam } from '~/fiscal-periods/date-param' +import { ParseError } from '~/fiscal-periods/error' +import { YearQuarterParam } from '~/fiscal-periods/year-quarter-param' + +export class PeriodParser { + constructor() { + // noop + } + + static parse(str: string): DateParam | YearQuarterParam { + str = str.toUpperCase() + + try { + return YearQuarterParam.parse(str) + } catch { + // noop + } + + try { + const date = DateParam.parse(str) + if (!date.isLatest()) { + return date + } + } catch { + // noop + } + + throw new ParseError(`Invalid period format: ${str}`) + } +} diff --git a/src/fiscal-periods/year-quarter-param.test.ts b/src/fiscal-periods/year-quarter-param.test.ts index b44a25d..d6e5ea2 100644 --- a/src/fiscal-periods/year-quarter-param.test.ts +++ b/src/fiscal-periods/year-quarter-param.test.ts @@ -1,7 +1,8 @@ import { InvalidLYLQError, InvalidYearError, - InvalidQuarterError + InvalidQuarterError, + ParseError } from '~/fiscal-periods/error' import { YearQuarter } from '~/fiscal-periods/year-quarter' import { YearQuarterParam } from '~/fiscal-periods/year-quarter-param' @@ -50,3 +51,22 @@ test('fromYearQuarter', () => { new YearQuarterParam(2018, 1) ) }) + +test('parse', () => { + expect(YearQuarterParam.parse('2020Q3')).toEqual( + new YearQuarterParam(2020, 3) + ) + expect(YearQuarterParam.parse('2020q3')).toEqual( + new YearQuarterParam(2020, 3) + ) + expect(YearQuarterParam.parse('LYLQ')).toEqual( + new YearQuarterParam('LY', 'LQ') + ) + expect(YearQuarterParam.parse('lylq')).toEqual( + new YearQuarterParam('LY', 'LQ') + ) + expect(() => YearQuarterParam.parse('20Q3')).toThrow(ParseError) + expect(() => YearQuarterParam.parse('LYQ3')).toThrow(InvalidLYLQError) + expect(() => YearQuarterParam.parse('2020LQ')).toThrow(InvalidLYLQError) + expect(() => YearQuarterParam.parse('foo')).toThrow(ParseError) +}) diff --git a/src/fiscal-periods/year-quarter-param.ts b/src/fiscal-periods/year-quarter-param.ts index ceba3d1..d319380 100644 --- a/src/fiscal-periods/year-quarter-param.ts +++ b/src/fiscal-periods/year-quarter-param.ts @@ -1,7 +1,8 @@ import { InvalidLYLQError, InvalidYearError, - InvalidQuarterError + InvalidQuarterError, + ParseError } from '~/fiscal-periods/error' import { YearQuarter } from '~/fiscal-periods/year-quarter' @@ -44,6 +45,20 @@ export class YearQuarterParam { return this.quarter === 'LQ' } + // XXX: LYLQの同時指定や-1などの相対値指定をどうするか確認する + static parse(str: string): YearQuarterParam { + str = str.toUpperCase() + const matches = str.match(/^(\d{4}|LY)(Q\d|LQ)$/) + if (matches == undefined) { + throw new ParseError(`Invalid year-quarter format: ${str}`) + } + + const year = matches[1] === 'LY' ? 'LY' : parseInt(matches[1], 10) + const quarter = + matches[2] === 'LQ' ? 'LQ' : parseInt(matches[2].substring(1), 10) + return new YearQuarterParam(year, quarter) + } + static fromYearQuarter(period: YearQuarter): YearQuarterParam { return new YearQuarterParam(period.year, period.quarter) }