Skip to content

Commit

Permalink
Merge pull request #2 from LordZardeck/fix/dates-with-time
Browse files Browse the repository at this point in the history
Fix issue with dates not being tracked from start of day
  • Loading branch information
LordZardeck authored Jan 19, 2024
2 parents deb2179 + d78aa9f commit ccebd83
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 36 deletions.
6 changes: 6 additions & 0 deletions build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// @ts-ignore
await Bun.build({
entrypoints: ['./src/index.ts'],
outdir: './dist',
minify: true,
})
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"scripts": {
"lint": "bunx eslint ./src/*.ts --fix",
"watch": "bunx tsc -p tsconfig.json --watch",
"build": "bunx rimraf dist && bunx tsc -p tsconfig.json",
"build": "bunx rimraf dist && bun build.ts && bunx tsc --emitDeclarationOnly",
"test": "bun test",
"coverage": "bun test --coverage"
},
Expand Down
7 changes: 4 additions & 3 deletions src/AbstractLoanSchedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
endOfMonth,
isSameYear,
setDate,
startOfDay,
startOfMonth,
} from 'date-fns'

Expand All @@ -33,7 +34,7 @@ export function getInterestByPeriod({ rate, to, from, amount }: InterestParamete
}

export function getPaymentDate(issueDate: Date, scheduleMonth: number, paymentDay: number): Date {
const paymentDate = addMonths(startOfMonth(issueDate), scheduleMonth)
const paymentDate = addMonths(startOfMonth(startOfDay(issueDate)), scheduleMonth)
const paymentEndOfMonth = endOfMonth(paymentDate)

return setDate(
Expand Down Expand Up @@ -126,8 +127,8 @@ export function calculateSchedule<P extends Payment = Payment>(
const minPaymentAmount = Decimal.min(firstPayment.paymentAmount, lastPayment.paymentAmount).toFixed(fixedDecimal)
const maxPaymentAmount = Decimal.max(firstPayment.paymentAmount, lastPayment.paymentAmount).toFixed(fixedDecimal)

const dateStart = setDate(initialPayment.paymentDate, 1)
const dateEnd = setDate(lastPayment.paymentDate, 1)
const dateStart = setDate(startOfDay(initialPayment.paymentDate), 1)
const dateEnd = setDate(startOfDay(lastPayment.paymentDate), 1)

const term = differenceInMonths(dateEnd, dateStart)

Expand Down
6 changes: 3 additions & 3 deletions src/AnnuityLoanSchedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
getPaymentDateOnWorkingDay,
} from './AbstractLoanSchedule'
import { Payment, PaymentType, Schedule, ScheduleConfig, ScheduleOptions } from './types'
import { isAfter, isSameDay } from 'date-fns'
import { isAfter, isSameDay, startOfDay } from 'date-fns'
import ProdCal from 'prod-cal'

export type AnnuityPayment = Payment & {
Expand All @@ -34,7 +34,7 @@ export function generateAnnuityPayments(parameters: ScheduleConfig, options?: Sc

const payments: Array<AnnuityPayment> = [
{
...createInitialPayment(amount, issueDate, rate),
...createInitialPayment(amount, startOfDay(issueDate), rate),
annuityPaymentAmount: 0,
},
]
Expand All @@ -43,7 +43,7 @@ export function generateAnnuityPayments(parameters: ScheduleConfig, options?: Sc
.map(Number.call, Number)
.map((termMonth) => ({
paymentDate: getPaymentDateOnWorkingDay(
termMonth === 0 ? new Date(issueDate) : getPaymentDate(issueDate, termMonth, paymentOnDay),
termMonth === 0 ? startOfDay(issueDate) : getPaymentDate(issueDate, termMonth, paymentOnDay),
isHoliday,
),
paymentType: PaymentType.ER_TYPE_REGULAR,
Expand Down
4 changes: 2 additions & 2 deletions src/BubbleLoanSchedule.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Decimal from 'decimal.js'
import { calculateInterestByPeriod, calculateSchedule, createInitialPayment } from './AbstractLoanSchedule'
import { ScheduleOptions, ScheduleConfig, Payment } from './types'
import { addMonths, setDate } from 'date-fns'
import { addMonths, setDate, startOfDay } from 'date-fns'

export function generateBubblePayments(parameters: ScheduleConfig, options?: ScheduleOptions) {
const fixedDecimal = options?.decimalDigit ?? 2
Expand Down Expand Up @@ -38,7 +38,7 @@ export function generateBubblePayments(parameters: ScheduleConfig, options?: Sch
},
]
},
[createInitialPayment(amount, issueDate, rate)] as Payment[],
[createInitialPayment(amount, startOfDay(issueDate), rate)] as Payment[],
)
}

Expand Down
4 changes: 2 additions & 2 deletions src/DifferentiatedLoanSchedule.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Decimal from 'decimal.js'
import { calculateInterestByPeriod, calculateSchedule, createInitialPayment } from './AbstractLoanSchedule'
import { ScheduleOptions, ScheduleConfig, Payment } from './types'
import { addMonths, setDate } from 'date-fns'
import { addMonths, setDate, startOfDay } from 'date-fns'

export function generateDifferentiatedPayments(parameters: ScheduleConfig, options?: ScheduleOptions) {
const fixedDecimal = options?.decimalDigit ?? 2
Expand Down Expand Up @@ -39,7 +39,7 @@ export function generateDifferentiatedPayments(parameters: ScheduleConfig, optio
},
]
},
[createInitialPayment(amount, issueDate, rate)] as Payment[],
[createInitialPayment(amount, startOfDay(issueDate), rate)] as Payment[],
)
}

Expand Down
129 changes: 104 additions & 25 deletions test/AbstractLoanSchedule.spec.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,119 @@
import { describe, expect, mock, test } from 'bun:test'
import { createHolidayChecker, getPaymentDate, getPaymentDateOnWorkingDay, printSchedule } from '../src'
import { describe, expect, mock, it } from 'bun:test'
import {
calculateSchedule,
createHolidayChecker,
generateAnnuityPayments,
getPaymentDate,
getPaymentDateOnWorkingDay,
printSchedule,
ScheduleConfig,
} from '../src'
import ProdCal from 'prod-cal'

describe('AbstractLoan should', () => {
test('print Schedule', () => {
const printFunction = mock(() => false)

printSchedule(
{
overAllInterest: 0,
amount: 0,
fullAmount: 0,
term: 0,
minPaymentAmount: 0,
maxPaymentAmount: 0,
payments: [],
efficientRate: 0,
},
printFunction,
)
expect(printFunction).toHaveBeenCalled()
})

test('add month and closest day', () => {
describe('AbstractLoan', () => {
it('should add month and closest day', () => {
expect(getPaymentDate(new Date(2013, 4, 1), 33, 31).getTime()).toEqual(new Date(2016, 1, 29).getTime())
})

test('return payment date as next day after holiday', () => {
it('should return payment date as next day after holiday', () => {
expect(
getPaymentDateOnWorkingDay(new Date(2015, 4, 1), createHolidayChecker(new ProdCal('ru'))).getTime(),
).toEqual(new Date(2015, 4, 5).getTime())
})

test('return payment date as closet day before holiday if holiday lasts to the end of the month', () => {
it('should return payment date as closet day before holiday if holiday lasts to the end of the month', () => {
expect(
getPaymentDateOnWorkingDay(new Date(2015, 4, 31), createHolidayChecker(new ProdCal('ru'))).getTime(),
).toEqual(new Date(2015, 4, 29).getTime())
})

describe('printSchedule', () => {
it('should print Schedule', () => {
const printFunction = mock(() => false)

printSchedule(
{
overAllInterest: 0,
amount: 0,
fullAmount: 0,
term: 0,
minPaymentAmount: 0,
maxPaymentAmount: 0,
payments: [],
efficientRate: 0,
},
printFunction,
)
expect(printFunction).toHaveBeenCalled()
})
})

describe('calculateSchedule', () => {
it('should require at least 2 payment records', () => {
expect(() =>
calculateSchedule(
{
amount: 500000,
rate: 11.5,
term: 12,
paymentOnDay: 25,
issueDate: new Date(2018, 9, 25),
},
[],
),
).toThrow()
})

describe('should return the same term length specified', () => {
it('when no payment amount is specified', () => {
const config: ScheduleConfig = {
amount: 26000,
rate: 18,
term: 60,
paymentOnDay: 25,
issueDate: new Date(2018, 9, 25),
}
const schedule = calculateSchedule(config, generateAnnuityPayments(config))
expect(schedule.term).toEqual(60)
})
it('when no payment amount is specified and date has time', () => {
const config: ScheduleConfig = {
amount: 26000,
rate: 18,
term: 60,
paymentOnDay: 25,
issueDate: new Date(2018, 9, 25, 6, 0, 0),
}
const schedule = calculateSchedule(config, generateAnnuityPayments(config))
expect(schedule.term).toEqual(60)
})

it('when payment amount is specified', () => {
const config: ScheduleConfig = {
amount: 26000,
rate: 18,
term: 60,
paymentOnDay: 22,
paymentAmount: 660.23,
issueDate: new Date(2024, 0, 22),
earlyRepayment: [],
}
const schedule = calculateSchedule(config, generateAnnuityPayments(config))
expect(schedule.term).toEqual(60)
})
it('when payment amount is specified and date has time', () => {
const config: ScheduleConfig = {
amount: 26000,
rate: 18,
term: 60,
paymentOnDay: 22,
paymentAmount: 660.23,
issueDate: new Date(2024, 0, 22, 6, 0, 0),
earlyRepayment: [],
}
const schedule = calculateSchedule(config, generateAnnuityPayments(config))
expect(schedule.term).toEqual(60)
})
})
})
})

0 comments on commit ccebd83

Please sign in to comment.