Files
household-bot/packages/domain/src/billing-period.ts

103 lines
2.3 KiB
TypeScript

import type { Instant } from './time'
import { DOMAIN_ERROR_CODE, DomainError } from './errors'
const BILLING_PERIOD_PATTERN = /^(\d{4})-(\d{2})$/
function isIntegerInRange(value: number, min: number, max: number): boolean {
return Number.isInteger(value) && value >= min && value <= max
}
export class BillingPeriod {
readonly year: number
readonly month: number
private constructor(year: number, month: number) {
this.year = year
this.month = month
}
static from(year: number, month: number): BillingPeriod {
if (!isIntegerInRange(year, 1970, 9999)) {
throw new DomainError(
DOMAIN_ERROR_CODE.INVALID_BILLING_PERIOD,
`Invalid billing year: ${year}`
)
}
if (!isIntegerInRange(month, 1, 12)) {
throw new DomainError(
DOMAIN_ERROR_CODE.INVALID_BILLING_PERIOD,
`Invalid billing month: ${month}`
)
}
return new BillingPeriod(year, month)
}
static fromString(value: string): BillingPeriod {
const match = BILLING_PERIOD_PATTERN.exec(value)
if (!match) {
throw new DomainError(
DOMAIN_ERROR_CODE.INVALID_BILLING_PERIOD,
`Billing period must match YYYY-MM: ${value}`
)
}
const [, yearString, monthString] = match
return BillingPeriod.from(Number(yearString), Number(monthString))
}
static fromInstant(instant: Instant): BillingPeriod {
const zoned = instant.toZonedDateTimeISO('UTC')
return BillingPeriod.from(zoned.year, zoned.month)
}
next(): BillingPeriod {
if (this.month === 12) {
return BillingPeriod.from(this.year + 1, 1)
}
return BillingPeriod.from(this.year, this.month + 1)
}
previous(): BillingPeriod {
if (this.month === 1) {
return BillingPeriod.from(this.year - 1, 12)
}
return BillingPeriod.from(this.year, this.month - 1)
}
compare(other: BillingPeriod): -1 | 0 | 1 {
if (this.year < other.year) {
return -1
}
if (this.year > other.year) {
return 1
}
if (this.month < other.month) {
return -1
}
if (this.month > other.month) {
return 1
}
return 0
}
equals(other: BillingPeriod): boolean {
return this.year === other.year && this.month === other.month
}
toString(): string {
return `${this.year.toString().padStart(4, '0')}-${this.month.toString().padStart(2, '0')}`
}
}