diff --git a/src/endOfQuarter.ts b/src/endOfQuarter.ts new file mode 100644 index 0000000..f059613 --- /dev/null +++ b/src/endOfQuarter.ts @@ -0,0 +1,21 @@ +import { tzLocal } from './LocalTimezone' +import { Timezone } from './Timezone' +import { daysInMonth } from './daysInMonth' +import { quarterOfYear } from './quarterOfYear' + +/** + * Find the last moment of the quarter. + * + * @category Anchors + * + * @param date A date + * @param tz An optional timezone. Defaults to the local timezone. + * @returns The last moment of the quarter. + */ +export function endOfQuarter(date: Date, tz: Timezone = tzLocal): Date { + const quarter = quarterOfYear(date, tz) + const monthIndex = 3 * (quarter - 1) + 2 + const year = tz.year(date) + const day = daysInMonth(year, monthIndex) + return tz.makeDate(year, monthIndex, day, 23, 59, 59, 999) +} diff --git a/src/findEndOfMonthIndex.ts b/src/findEndOfMonthIndex.ts index 5f94cfd..beb9ca1 100644 --- a/src/findEndOfMonthIndex.ts +++ b/src/findEndOfMonthIndex.ts @@ -1,4 +1,4 @@ -import { isEndOfMonth } from './isEndOfMonth' +import { isLastDayOfMonth } from './isLastDayOfMonth' import { tzLocal } from './LocalTimezone' import { Timezone } from './Timezone' @@ -13,5 +13,5 @@ export function findEndOfMonthIndex( dates: Date[], tz: Timezone = tzLocal ): number { - return dates.findIndex(date => isEndOfMonth(date, tz)) + return dates.findIndex(date => isLastDayOfMonth(date, tz)) } diff --git a/src/findLastEndOfMonthIndex.ts b/src/findLastEndOfMonthIndex.ts index 3d541b6..44e2118 100644 --- a/src/findLastEndOfMonthIndex.ts +++ b/src/findLastEndOfMonthIndex.ts @@ -1,4 +1,4 @@ -import { isEndOfMonth } from './isEndOfMonth' +import { isLastDayOfMonth } from './isLastDayOfMonth' import { tzLocal } from './LocalTimezone' import { Timezone } from './Timezone' import { findLastIndex } from './utils' @@ -14,5 +14,5 @@ export function findLastEndOfMonthIndex( dates: Date[], tz: Timezone = tzLocal ): number { - return findLastIndex(dates, date => isEndOfMonth(date, tz)) + return findLastIndex(dates, date => isLastDayOfMonth(date, tz)) } diff --git a/src/index.ts b/src/index.ts index 31ee749..fb79c66 100644 --- a/src/index.ts +++ b/src/index.ts @@ -37,6 +37,7 @@ export { endOfDay } from './endOfDay' export { endOfHour } from './endOfHour' export { endOfMinute } from './endOfMinute' export { endOfMonth } from './endOfMonth' +export { endOfQuarter } from './endOfQuarter' export { endOfSecond } from './endOfSecond' export { endOfToday } from './endOfToday' export { endOfWeek } from './endOfWeek' @@ -58,12 +59,14 @@ export { isDateEqual } from './isDateEqual' export { isDateNotEqual } from './isDateNotEqual' export { isDateOnOrAfter } from './isDateOnOrAfter' export { isDateOnOrBefore } from './isDateOnOrBefore' -export { isEndOfMonth } from './isEndOfMonth' +export { isLastDayOfMonth } from './isLastDayOfMonth' +export { isLastDayOfQuarter } from './isLastDayOfQuarter' export { isLeapYear } from './isLeapYear' export { isoWeekDate } from './isoWeekDate' export { isoWeekOfYear } from './isoWeekOfYear' export { isValidDate } from './isValidDate' export { lastDayOfMonth } from './lastDayOfMonth' +export { lastDayOfQuarter } from './lastDayOfQuarter' export { lastDayOfWeek } from './lastDayOfWeek' export { lastDayOfWeekday } from './lastDayOfWeekday' export { lastDayOfYear } from './lastDayOfYear' @@ -89,6 +92,7 @@ export { startOfHour } from './startOfHour' export { startOfISOWeek } from './startOfISOWeek' export { startOfMinute } from './startOfMinute' export { startOfMonth } from './startOfMonth' +export { startOfQuarter } from './startOfQuarter' export { startOfSecond } from './startOfSecond' export { startOfWeek } from './startOfWeek' export { startOfWeekday } from './startOfWeekday' diff --git a/src/isEndOfMonth.ts b/src/isLastDayOfMonth.ts similarity index 61% rename from src/isEndOfMonth.ts rename to src/isLastDayOfMonth.ts index 4eb59e8..126688e 100644 --- a/src/isEndOfMonth.ts +++ b/src/isLastDayOfMonth.ts @@ -11,11 +11,6 @@ import { Timezone } from './Timezone' * @param tz An optional timezone. Defaults to the local timezone. * @returns True if the date is the last day of the month. */ -export function isEndOfMonth(date: Date, tz: Timezone = tzLocal): boolean { - const { year, monthIndex, day } = tz.dateParts(date, { - year: true, - monthIndex: true, - day: true - }) - return day === daysInMonth(year, monthIndex) +export function isLastDayOfMonth(date: Date, tz: Timezone = tzLocal): boolean { + return tz.day(date) === daysInMonth(tz.year(date), tz.monthIndex(date)) } diff --git a/src/isLastDayOfQuarter.ts b/src/isLastDayOfQuarter.ts new file mode 100644 index 0000000..c88bc99 --- /dev/null +++ b/src/isLastDayOfQuarter.ts @@ -0,0 +1,21 @@ +import { isDateEqual } from './isDateEqual' +import { lastDayOfQuarter } from './lastDayOfQuarter' +import { tzLocal } from './LocalTimezone' +import { Timezone } from './Timezone' + +/** + * Check if the date is the last day of the quarter. + * + * @category Calendars + * + * @param date The date to check. + * @param tz An optional timezone. Defaults to the local timezone. + * @returns True if the date is the last day of the quarter. + */ +export function isLastDayOfQuarter( + date: Date, + tz: Timezone = tzLocal +): boolean { + const end = lastDayOfQuarter(date, tz) + return isDateEqual(date, end) +} diff --git a/src/lastDayOfQuarter.ts b/src/lastDayOfQuarter.ts new file mode 100644 index 0000000..199900b --- /dev/null +++ b/src/lastDayOfQuarter.ts @@ -0,0 +1,21 @@ +import { tzLocal } from './LocalTimezone' +import { Timezone } from './Timezone' +import { daysInMonth } from './daysInMonth' +import { quarterOfYear } from './quarterOfYear' + +/** + * Find the last day of the quarter. + * + * @category Anchors + * + * @param date The start date. + * @param tz An optional timezone. Defaults to the local timezone. + * @returns A date which is the last day of the quarter. + */ +export function lastDayOfQuarter(date: Date, tz: Timezone = tzLocal): Date { + const quarter = quarterOfYear(date, tz) + const monthIndex = 3 * (quarter - 1) + 2 + const year = tz.year(date) + const day = daysInMonth(year, monthIndex) + return tz.makeDate(year, monthIndex, day) +} diff --git a/src/startOfMonth.ts b/src/startOfMonth.ts index 3f36df1..d3f4907 100644 --- a/src/startOfMonth.ts +++ b/src/startOfMonth.ts @@ -2,7 +2,7 @@ import { tzLocal } from './LocalTimezone' import { Timezone } from './Timezone' /** - * Find the start of the months for a given date. + * Find the start of the month for a given date. * * @category Anchors * diff --git a/src/startOfQuarter.ts b/src/startOfQuarter.ts new file mode 100644 index 0000000..bc42abb --- /dev/null +++ b/src/startOfQuarter.ts @@ -0,0 +1,18 @@ +import { tzLocal } from './LocalTimezone' +import { Timezone } from './Timezone' +import { quarterOfYear } from './quarterOfYear' + +/** + * Find the start of the quarter for a given date. + * + * @category Anchors + * + * @param date The date. + * @param tz An optional timezone. Defaults to the local timezone. + * @returns The start of the quarter. + */ +export function startOfQuarter(date: Date, tz: Timezone = tzLocal): Date { + const quarter = quarterOfYear(date, tz) + const monthIndex = 3 * (quarter - 1) + return tz.makeDate(tz.year(date), monthIndex, 1) +} diff --git a/test/endOfQuarter.test.ts b/test/endOfQuarter.test.ts new file mode 100644 index 0000000..47acc6d --- /dev/null +++ b/test/endOfQuarter.test.ts @@ -0,0 +1,31 @@ +import { + IANATimezone, + dataToTimezoneOffset, + endOfQuarter, + tzLocal, + tzUtc +} from '../src' +import chicagoTzData from '@jetblack/tzdata/dist/latest/America/Chicago.json' +import tokyoTzData from '@jetblack/tzdata/dist/latest/Asia/Tokyo.json' + +describe('endOfQuarter', () => { + const tzChicago = new IANATimezone( + 'America/Chicago', + chicagoTzData.map(dataToTimezoneOffset) + ) + const tzTokyo = new IANATimezone( + 'Asia/Tokyo', + tokyoTzData.map(dataToTimezoneOffset) + ) + + for (const tz of [tzUtc, tzLocal, tzChicago, tzTokyo]) { + describe(tz.name, () => { + it('should find the last moment of the quarter', () => { + const date = tz.makeDate(2000, 0, 1) + const actual = endOfQuarter(date, tz) + const expected = tz.makeDate(2000, 2, 31, 23, 59, 59, 999) + expect(tz.toISOString(actual)).toBe(tz.toISOString(expected)) + }) + }) + } +}) diff --git a/test/isEndOfMonth.test.ts b/test/isLastDayOfMonth.test.ts similarity index 75% rename from test/isEndOfMonth.test.ts rename to test/isLastDayOfMonth.test.ts index 377f298..419d875 100644 --- a/test/isEndOfMonth.test.ts +++ b/test/isLastDayOfMonth.test.ts @@ -1,14 +1,14 @@ import { IANATimezone, dataToTimezoneOffset, - isEndOfMonth, + isLastDayOfMonth, tzLocal, tzUtc } from '../src' import chicagoTzData from '@jetblack/tzdata/dist/latest/America/Chicago.json' import tokyoTzData from '@jetblack/tzdata/dist/latest/Asia/Tokyo.json' -describe('isEndOfMonth', () => { +describe('isLastDayOfMonth', () => { const tzChicago = new IANATimezone( 'America/Chicago', chicagoTzData.map(dataToTimezoneOffset) @@ -22,27 +22,27 @@ describe('isEndOfMonth', () => { describe(tz.name, () => { it('should know 30 January is not the end of the month', () => { const date = tz.makeDate(2008, 0, 30) - expect(isEndOfMonth(date, tz)).toBeFalsy() + expect(isLastDayOfMonth(date, tz)).toBeFalsy() }) - it('should know 31 January is not the end of the month', () => { + it('should know 31 January is the end of the month', () => { const date = tz.makeDate(2008, 0, 31) - expect(isEndOfMonth(date, tz)).toBeTruthy() + expect(isLastDayOfMonth(date, tz)).toBeTruthy() }) it('should know 28 February 2008 is not the end of the month because 2008 is a leap year', () => { const date = tz.makeDate(2008, 1, 28) - expect(isEndOfMonth(date, tz)).toBeFalsy() + expect(isLastDayOfMonth(date, tz)).toBeFalsy() }) it('should know 29 February 2008 is the end of the month because 2008 is a leap year', () => { const date = tz.makeDate(2008, 1, 29) - expect(isEndOfMonth(date, tz)).toBeTruthy() + expect(isLastDayOfMonth(date, tz)).toBeTruthy() }) it('should know 28 February 2009 is the end of the month because 2008 is not a leap year', () => { const date = tz.makeDate(2009, 1, 28) - expect(isEndOfMonth(date, tz)).toBeTruthy() + expect(isLastDayOfMonth(date, tz)).toBeTruthy() }) }) } diff --git a/test/isLastDayOfQuarter.test.ts b/test/isLastDayOfQuarter.test.ts new file mode 100644 index 0000000..c966881 --- /dev/null +++ b/test/isLastDayOfQuarter.test.ts @@ -0,0 +1,34 @@ +import { + IANATimezone, + dataToTimezoneOffset, + isLastDayOfQuarter, + tzLocal, + tzUtc +} from '../src' +import chicagoTzData from '@jetblack/tzdata/dist/latest/America/Chicago.json' +import tokyoTzData from '@jetblack/tzdata/dist/latest/Asia/Tokyo.json' + +describe('isLastDayOfQuarter', () => { + const tzChicago = new IANATimezone( + 'America/Chicago', + chicagoTzData.map(dataToTimezoneOffset) + ) + const tzTokyo = new IANATimezone( + 'Asia/Tokyo', + tokyoTzData.map(dataToTimezoneOffset) + ) + + for (const tz of [tzUtc, tzLocal, tzChicago, tzTokyo]) { + describe(tz.name, () => { + it('should know 30 January is not the end of the quarter', () => { + const date = tz.makeDate(2008, 0, 30) + expect(isLastDayOfQuarter(date, tz)).toBeFalsy() + }) + + it('should know 31 March is the end of the month', () => { + const date = tz.makeDate(2008, 2, 31) + expect(isLastDayOfQuarter(date, tz)).toBeTruthy() + }) + }) + } +}) diff --git a/test/lastDayOfQuarter.test.ts b/test/lastDayOfQuarter.test.ts new file mode 100644 index 0000000..09a5c35 --- /dev/null +++ b/test/lastDayOfQuarter.test.ts @@ -0,0 +1,30 @@ +import { + IANATimezone, + dataToTimezoneOffset, + lastDayOfQuarter, + tzLocal, + tzUtc +} from '../src' +import chicagoTzData from '@jetblack/tzdata/dist/latest/America/Chicago.json' +import tokyoTzData from '@jetblack/tzdata/dist/latest/Asia/Tokyo.json' + +describe('lastDayOfQuarter', () => { + const tzChicago = new IANATimezone( + 'America/Chicago', + chicagoTzData.map(dataToTimezoneOffset) + ) + const tzTokyo = new IANATimezone( + 'Asia/Tokyo', + tokyoTzData.map(dataToTimezoneOffset) + ) + + for (const tz of [tzUtc, tzLocal, tzChicago, tzTokyo]) { + describe(tz.name, () => { + it('should find the last day of the quarter', () => { + const actual = lastDayOfQuarter(tz.makeDate(2000, 0, 1), tz) + const expected = tz.makeDate(2000, 2, 31) + expect(tz.toISOString(actual)).toBe(tz.toISOString(expected)) + }) + }) + } +}) diff --git a/test/startOfQuarter.test.ts b/test/startOfQuarter.test.ts new file mode 100644 index 0000000..4b49695 --- /dev/null +++ b/test/startOfQuarter.test.ts @@ -0,0 +1,38 @@ +import { + IANATimezone, + dataToTimezoneOffset, + startOfQuarter, + tzLocal, + tzUtc +} from '../src' +import chicagoTzData from '@jetblack/tzdata/dist/latest/America/Chicago.json' +import tokyoTzData from '@jetblack/tzdata/dist/latest/Asia/Tokyo.json' + +describe('startOfQuarter', () => { + const tzChicago = new IANATimezone( + 'America/Chicago', + chicagoTzData.map(dataToTimezoneOffset) + ) + const tzTokyo = new IANATimezone( + 'Asia/Tokyo', + tokyoTzData.map(dataToTimezoneOffset) + ) + + for (const tz of [tzUtc, tzLocal, tzChicago, tzTokyo]) { + describe(tz.name, () => { + it('should return the start of the quarters', () => { + const date = tz.makeDate(2014, 8, 2, 2, 11, 55, 664) + const actual = startOfQuarter(date, tz) + const expected = tz.makeDate(2014, 6, 1) + expect(tz.toISOString(actual)).toBe(tz.toISOString(expected)) + }) + + it('should not change date', () => { + const date = tz.makeDate(2014, 6, 1) + const actual = startOfQuarter(date, tz) + const expected = tz.makeDate(2014, 6, 1) + expect(tz.toISOString(actual)).toBe(tz.toISOString(expected)) + }) + }) + } +})