Documentation
¶
Overview ¶
Package engine is the billing time engine (piece ⑨): civil dates and billing-style recurrence. It is pure: no I/O, no clock, no time zones except when converting a Date to an instant.
Index ¶
- Variables
- func DaysIn(y int, m time.Month) int
- type Date
- func (d Date) AddDays(n int) Date
- func (d Date) After(other Date) bool
- func (d Date) At(hour, min int, loc *time.Location) time.Time
- func (d Date) Before(other Date) bool
- func (d Date) Compare(other Date) int
- func (d Date) Day() int
- func (d Date) DaysUntil(other Date) int
- func (d Date) IsZero() bool
- func (d Date) MarshalText() ([]byte, error)
- func (d Date) Month() time.Month
- func (d *Date) Scan(src any) error
- func (d Date) String() string
- func (d *Date) UnmarshalText(b []byte) error
- func (d Date) Value() (driver.Value, error)
- func (d Date) Weekday() time.Weekday
- func (d Date) Year() int
- type DayRule
- type Rule
- type Schedule
- func (s Schedule) Between(from, to Date) []Date
- func (s Schedule) ChangeFrom(at, newAnchor Date, interval int, unit Unit) (Schedule, error)
- func (s Schedule) ChangeRule(at Date, r Rule) (Schedule, error)
- func (s Schedule) Current(d Date) Rule
- func (s Schedule) First() Date
- func (s Schedule) NextOnOrAfter(d Date) (Date, bool)
- func (s Schedule) NominalOf(d Date) (Date, bool)
- func (s Schedule) Upcoming(d Date, n int) []Date
- func (s Schedule) Validate() error
- type Shift
- type Unit
- type Version
Constants ¶
This section is empty.
Variables ¶
var ErrInvalidDate = errors.New("engine: invalid date")
ErrInvalidDate is returned when a year/month/day triple does not exist.
var ErrInvalidRule = errors.New("engine: invalid rule")
ErrInvalidRule is returned for rules that cannot produce charges.
var ErrInvalidSchedule = errors.New("engine: invalid schedule")
ErrInvalidSchedule is returned for schedules that cannot be evaluated.
Functions ¶
Types ¶
type Date ¶
type Date struct {
// contains filtered or unexported fields
}
Date is a civil calendar date (year, month, day) with no time and no time zone. A charge happens on a Date; a reminder happens at an instant.
The zero value is not a valid date; use IsZero to detect it.
func MustDate ¶
MustDate is like NewDate but panics on invalid input. Use it for constants and tests.
func WeekdayOfMonth ¶
WeekdayOfMonth returns the n-th weekday wd of a month (n = 1 to 4), or the last one when n is -1.
func (Date) At ¶
At returns the instant at hour:min on this date in loc. Wall-clock times that do not exist because of DST are normalized by the time package.
func (Date) Compare ¶
Compare returns -1, 0 or +1 depending on whether d is before, equal to or after other.
func (Date) DaysUntil ¶
DaysUntil returns the number of days from d to other (negative if other is before d).
func (Date) MarshalText ¶
MarshalText implements encoding.TextMarshaler (used by JSON).
func (*Date) UnmarshalText ¶
UnmarshalText implements encoding.TextUnmarshaler.
type DayRule ¶
type DayRule string
DayRule says which day of the month a monthly or yearly rule charges on. It is read from the anchor, so a rule has a single source of truth.
const ( // DayOfMonth charges on the anchor's day number, or on the last day of // shorter months (the default). DayOfMonth DayRule = "" // NthWeekday charges on the anchor's weekday in the anchor's week of the // month: anchored on the third Wednesday, it is always the third // Wednesday. Only the 1st to 4th exist in every month. NthWeekday DayRule = "weekday" // LastWeekday charges on the last of the anchor's weekday in the month // ("the last Friday"). The anchor must be in the last 7 days of its month. LastWeekday DayRule = "last-weekday" )
type Rule ¶
type Rule struct {
Interval int `json:"interval"`
Unit Unit `json:"unit"`
Anchor Date `json:"anchor"`
DayRule DayRule `json:"dayRule,omitempty"`
Shift Shift `json:"weekendShift,omitempty"`
}
Rule is a billing recurrence: a charge every Interval Units, starting at Anchor.
Billing semantics (as Stripe, Apple and Google bill):
- every occurrence is computed from the anchor, never from the previous occurrence, so there is no drift;
- when the anchor day does not exist in a month, the charge falls on the last day of that month (31 Jan → 28/29 Feb → 31 Mar).
Monthly and yearly rules can instead follow a weekday of the month (the third Wednesday, the last Friday; see DayRule). Any rule can move charges that fall on a weekend to the next or previous business day (Shift). The shift moves the due date only: the next charge is still computed from the anchor, so a shifted charge never moves the ones after it.
func (Rule) NextOnOrAfter ¶
NextOnOrAfter returns the first charge date on or after d and its index.
func (Rule) Nominal ¶
Nominal returns the n-th date the rule is anchored on, before any weekend shift (n = 0 is the anchor). Every nominal date is computed from the anchor, never from the previous charge.
func (Rule) Occurrence ¶
Occurrence returns the n-th charge date of the rule (n = 0 is the first charge): the nominal date moved off the weekend when the rule says so. It panics if n < 0 or if the rule is invalid; call Validate first.
func (Rule) Reanchor ¶
Reanchor returns the same kind of rule anchored on a new date, for "from now on" changes. A weekday rule keeps following a weekday of the month: the new anchor's (the last one when it is in the last week and the rule was "last", or when it is a fifth weekday).
type Schedule ¶
type Schedule struct {
Versions []Version `json:"versions"`
// Until, if set, is the last date on which a charge may happen
// (inclusive). Used for end dates and cancellation at period end.
Until Date `json:"until,omitzero"`
// MaxCharges, if > 0, limits the total number of charges (contracts with
// a fixed number of payments).
MaxCharges int `json:"maxCharges,omitempty"`
}
Schedule is the full billing timeline of a subscription: one or more rule versions plus optional limits. Past charges keep the rule they were created under; "this and following" edits append a new version.
func NewSchedule ¶
NewSchedule returns a schedule with a single rule, in effect from its first charge (or its anchor, when a weekend shift moves the first charge later).
func (Schedule) ChangeFrom ¶
ChangeFrom implements "this and following" edits. The charge that was due on `at` (and every later one) is replaced by a new rule anchored on newAnchor. Charges before min(at, newAnchor) keep their previous rule.
func (Schedule) ChangeRule ¶
ChangeRule is ChangeFrom with a whole rule (day rule and weekend shift included): the charge due on `at` and every later one follow r. Charges before min(at, r's first charge) keep their previous rule.
func (Schedule) NextOnOrAfter ¶
NextOnOrAfter returns the first charge date on or after d, if any.
func (Schedule) NominalOf ¶
NominalOf returns the date the charge due on d is anchored on, before any weekend shift. It reports false when no charge of the schedule is due on d.
type Shift ¶
type Shift string
Shift moves a charge that falls on a weekend to a business day. Only Saturdays and Sundays count: there are no holiday calendars.