src/period/parser.js

Summary

Maintainability
D
2 days
Test Coverage
import {
    getMonthNamesForLocale,
    formatAsISODate,
    getLastDateOfMonth,
    getFirstDateOfQuarter,
    getLastDateOfQuarter,
    computeWeekBasedPeriod,
} from './helpers'
import { toLocaleDayFormat } from './formatters'

const periodTypeRegex = {
    Daily: /^([0-9]{4})([0-9]{2})([0-9]{2})$/, // YYYYMMDD
    Weekly: /^([0-9]{4})()W([0-9]{1,2})$/, // YYYY"W"[1-53]
    WeeklyWednesday: /^([0-9]{4})(Wed)W([0-9]{1,2})$/, // YYYY"WedW"[1-53]
    WeeklyThursday: /^([0-9]{4})(Thu)W([0-9]{1,2})$/, // YYYY"ThuW"[1-53]
    WeeklySaturday: /^([0-9]{4})(Sat)W([0-9]{1,2})$/, // YYYY"SatW"[1-53]
    WeeklySunday: /^([0-9]{4})(Sun)W([0-9]{1,2})$/, // YYYY"SunW"[1-53]
    BiWeekly: /^([0-9]{4})BiW([0-9]{1,2})$/, // YYYY"BiW"[1-27]
    Monthly: /^([0-9]{4})([0-9]{2})$/, // YYYYMM
    BiMonthly: /^([0-9]{4})([0-9]{2})B$/, // YYYY0[1-6]"B"
    Quarterly: /^([0-9]{4})Q([1234])$/, // YYYY"Q"[1-4]
    SixMonthly: /^([0-9]{4})S([12])$/, // YYYY"S"[1/2]
    SixMonthlyApril: /^([0-9]{4})AprilS([12])$/, // YYYY"AprilS"[1/2]
    SixMonthlyNov: /^([0-9]{4})NovS([12])$/, // YYYY"NovS"[1/2]
    Yearly: /^([0-9]{4})$/, // YYYY
    FinancialApril: /^([0-9]{4})April$/, // YYYY"April"
    FinancialJuly: /^([0-9]{4})July$/, // YYYY"July"
    FinancialOct: /^([0-9]{4})Oct$/, // YYYY"Oct"
    FinancialNov: /^([0-9]{4})Nov$/, // YYYY"Nov"
}

/* eslint-disable complexity */
const weeklyMatcherParser = (match, locale = 'en') => {
    const year = parseInt(match[1], 10)
    const weekType = match[2]
    const week = parseInt(match[3], 10)

    if (week < 1 || week > 53) {
        throw new Error('Invalid week number')
    }

    let weekTypeDiff = 0
    switch (weekType) {
        case 'Wed':
            weekTypeDiff = 2
            break
        case 'Thu':
            weekTypeDiff = 3
            break
        case 'Sat':
            weekTypeDiff = -2
            break
        case 'Sun':
            weekTypeDiff = -1
            break
        default:
            break
    }

    const p = computeWeekBasedPeriod({
        year,
        week,
        locale,
        weekTypeDiff,
    })

    const name =
        p.startMonthName === p.endMonthName
            ? `${p.year} W${p.week} ${p.startMonthName} ${p.startDayNum} - ${p.endDayNum}`
            : `${p.year} W${p.week} ${p.startMonthName} ${p.startDayNum} - ${p.endMonthName} ${p.endDayNum}`

    return {
        id: `${p.year}${weekType}W${p.week}`,
        name,
        startDate: p.startDate,
        endDate: p.endDate,
    }
}

const isValidDailyPeriod = (month, year, day) =>
    month > 11 || month < 0 || day > 31 || day < 1 || year < 1000 || year > 5000
/* eslint-enable */

const regexMatchToPeriod = {
    Daily: (match, locale = 'en') => {
        const year = parseInt(match[1], 10)
        const month = parseInt(match[2], 10) - 1
        const day = parseInt(match[3], 10)
        if (isValidDailyPeriod(month, year, day)) {
            throw new Error('Invalid Daily period')
        }
        const date = new Date(match[1], match[2] - 1, match[3])
        return {
            id: `${date.getFullYear()}${`0${date.getMonth() + 1}`.substr(
                -2
            )}${`0${date.getDate()}`.substr(-2)}`,
            name: toLocaleDayFormat(date, locale),
            startDate: formatAsISODate(date),
            endDate: formatAsISODate(date),
        }
    },
    Weekly: weeklyMatcherParser,
    WeeklyWednesday: weeklyMatcherParser,
    WeeklyThursday: weeklyMatcherParser,
    WeeklySaturday: weeklyMatcherParser,
    WeeklySunday: weeklyMatcherParser,
    BiWeekly: (match, locale = 'en') => {
        const year = parseInt(match[1], 10)
        let biWeek = parseInt(match[2], 10)

        if (biWeek < 1 || biWeek > 27) {
            throw new Error('Invalid BiWeek number')
        }

        const week = biWeek * 2 - 1
        const p = computeWeekBasedPeriod({
            year,
            week,
            locale,
            periodLength: 13,
        })
        biWeek = (p.week + 1) / 2

        const name =
            p.startMonthName === p.endMonthName
                ? `${p.year} BiWeek ${biWeek} ${p.startMonthName} ${p.startDayNum} - ${p.endDayNum}`
                : `${p.year} BiWeek ${biWeek} ${p.startMonthName} ${p.startDayNum} - ${p.endMonthName} ${p.endDayNum}`

        return {
            id: `${p.year}BiW${biWeek}`,
            name,
            startDate: p.startDate,
            endDate: p.endDate,
        }
    },
    Monthly: (match, locale = 'en') => {
        const id = match[0]
        const year = parseInt(match[1], 10)
        const month = parseInt(match[2], 10) - 1
        if (month > 11 || month < 0) {
            throw new Error('Invalid month number')
        }
        const monthNum = `0${month + 1}`.substr(-2)
        const monthNames = getMonthNamesForLocale(locale)
        const lastDay = getLastDateOfMonth(year, month)
        return {
            id,
            name: `${monthNames[month]} ${year}`,
            startDate: `${year}-${monthNum}-01`,
            endDate: formatAsISODate(lastDay),
        }
    },
    BiMonthly: (match, locale = 'en') => {
        const id = match[0]
        const year = parseInt(match[1], 10)
        const biMonth = parseInt(match[2], 10)
        if (biMonth < 1 || biMonth > 6) {
            throw new Error('Invalid BiMonth number')
        }
        const startMonth = (biMonth - 1) * 2
        const startMonthNum = `0${startMonth + 1}`.substr(-2)
        const endMonth = startMonth + 1
        const monthNames = getMonthNamesForLocale(locale)
        return {
            id,
            name: `${monthNames[startMonth]} - ${monthNames[endMonth]} ${year}`,
            startDate: `${year}-${startMonthNum}-01`,
            endDate: formatAsISODate(getLastDateOfMonth(year, endMonth)),
        }
    },
    Quarterly: (match, locale = 'en') => {
        const id = match[0]
        const year = parseInt(match[1], 10)
        const quarter = parseInt(match[2], 10)
        const startMonth = (quarter - 1) * 3
        const endMonth = quarter * 3 - 1
        const monthNames = getMonthNamesForLocale(locale)
        return {
            id,
            name: `${monthNames[startMonth]} - ${monthNames[endMonth]} ${year}`,
            startDate: formatAsISODate(getFirstDateOfQuarter(year, quarter)),
            endDate: formatAsISODate(getLastDateOfQuarter(year, quarter)),
        }
    },
    SixMonthly: (match, locale = 'en') => {
        const id = match[0]
        const year = match[1]
        const s = parseInt(match[2], 10) - 1
        const startMonth = 6 * s
        const endMonth = 6 * s + 6
        const endMonthNum = `0${endMonth}`.substr(-2)
        const monthNames = getMonthNamesForLocale(locale)
        return {
            id,
            name: `${monthNames[startMonth]} - ${
                monthNames[endMonth - 1]
            } ${year}`,
            startDate: `${year}-0${startMonth + 1}-01`,
            endDate: `${year}-${endMonthNum}-${s === 0 ? '30' : '31'}`,
        }
    },
    SixMonthlyApril: (match, locale = 'en') => {
        const id = match[0]
        const year = parseInt(match[1], 10)
        const s = parseInt(match[2], 10) - 1
        const startMonth = s === 0 ? 4 : 10
        const startMonthNum = `0${startMonth}`.substr(-2)
        const endMonth = s === 0 ? 9 : 3
        const endMonthNum = `0${endMonth}`.substr(-2)
        const monthNames = getMonthNamesForLocale(locale)
        return {
            id,
            name:
                s === 0
                    ? `${monthNames[startMonth - 1]} - ${
                          monthNames[endMonth - 1]
                      } ${year}`
                    : `${monthNames[startMonth - 1]} ${year} - ${
                          monthNames[endMonth - 1]
                      } ${year + 1}`,
            startDate: `${year}-${startMonthNum}-01`,
            endDate: `${year + s}-${endMonthNum}-${s === 0 ? '30' : '31'}`,
        }
    },
    /* eslint-disable complexity */
    SixMonthlyNov: (match, locale = 'en') => {
        const id = match[0]
        const year = parseInt(match[1], 10)
        const s = parseInt(match[2], 10) - 1
        const startMonth = s === 0 ? 11 : 5
        const startMonthNum = `0${startMonth}`.substr(-2)
        const endMonth = s === 0 ? 4 : 10
        const endMonthNum = `0${endMonth}`.substr(-2)
        const monthNames = getMonthNamesForLocale(locale)
        const startYear = s === 0 ? year : year + 1
        const endYear = year + 1
        return {
            id,
            name:
                s === 0
                    ? `${monthNames[startMonth - 1]} ${year} - ${
                          monthNames[endMonth - 1]
                      } ${endYear}`
                    : `${monthNames[startMonth - 1]} - ${
                          monthNames[endMonth - 1]
                      } ${endYear}`,
            startDate: `${startYear}-${startMonthNum}-01`,
            endDate: `${endYear}-${endMonthNum}-${s === 0 ? '30' : '31'}`,
        }
    },
    /* eslint-enable */
    Yearly: (match) => ({
        id: match[0],
        name: match[1],
        startDate: `${match[1]}-01-01`,
        endDate: `${match[1]}-12-31`,
    }),
    FinancialApril: (match, locale = 'en') => {
        const year = parseInt(match[1], 10)
        const monthNames = getMonthNamesForLocale(locale)
        return {
            id: match[0],
            name: `${monthNames[3]} ${year} - ${monthNames[2]} ${year + 1}`,
            startDate: `${year}-04-01`,
            endDate: `${year + 1}-03-31`,
        }
    },
    FinancialJuly: (match, locale = 'en') => {
        const year = parseInt(match[1], 10)
        const monthNames = getMonthNamesForLocale(locale)
        return {
            id: match[0],
            name: `${monthNames[6]} ${year} - ${monthNames[5]} ${year + 1}`,
            startDate: `${year}-07-01`,
            endDate: `${year + 1}-06-30`,
        }
    },
    FinancialOct: (match, locale = 'en') => {
        const year = parseInt(match[1], 10)
        const monthNames = getMonthNamesForLocale(locale)
        return {
            id: match[0],
            name: `${monthNames[9]} ${year} - ${monthNames[8]} ${year + 1}`,
            startDate: `${year}-10-01`,
            endDate: `${year + 1}-09-30`,
        }
    },
    FinancialNov: (match, locale = 'en') => {
        const year = parseInt(match[1], 10)
        const monthNames = getMonthNamesForLocale(locale)
        return {
            id: match[0],
            name: `${monthNames[10]} ${year} - ${monthNames[9]} ${year + 1}`,
            startDate: `${year}-11-01`,
            endDate: `${year + 1}-10-31`,
        }
    },
}

export function getPeriodFromPeriodId(periodId, locale = 'en') {
    const period = Object.keys(periodTypeRegex)
        .filter(
            (periodType) =>
                periodTypeRegex[periodType].test(periodId) &&
                regexMatchToPeriod.hasOwnProperty(periodType)
        )
        .map((periodType) => {
            const matchedPeriod = regexMatchToPeriod[periodType](
                periodId.match(periodTypeRegex[periodType]),
                locale
            )
            matchedPeriod.type = periodType
            return matchedPeriod
        })[0]

    if (!period) {
        throw new Error('Invalid period format')
    }

    return period
}

export default getPeriodFromPeriodId