Skip to content

Commit

Permalink
feat!: Functionality, tests, and documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Sleavely committed Aug 23, 2022
1 parent 86ad9b5 commit 37380a9
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 0 deletions.
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# next-monthly

Determine the next monthly occurance, falling back on the last day of the month.

[ ![npm version](https://img.shields.io/npm/v/next-monthly.svg?style=flat) ](https://npmjs.org/package/next-monthly "View package")
[ ![CI status](https://github.com/Sleavely/next-monthly/actions/workflows/node.js.yml/badge.svg) ](https://github.com/Sleavely/next-monthly/actions/workflows/node.js.yml "View workflow")

## Installation

```sh
npm i next-monthly
```

## Usage

```js
const nextMonthly = require('next-monthly')

// Jan 31, Feb 28 or 29, etc.
const lastDayOfThisMonth = nextMonthly()


// If you want a specific date, like for charging users who signed up on the 30th (but sometimes on Feb 28, etc.)
const nextBillingDate = nextMonthly({
from: new Date('1990-01-07T01:29:03.999Z'),

// Defaults to Date.now(). Only used here for demo purpose:
now: new Date('2022-08-23T12:29:08.551Z'),
})
// Returns date: 2022-09-07T01:29:03.999Z
```

## Options

| **Property** | **Description** |
|---|---|
| `from` | The basis for the monthly occurrance. Defaults to `0000-01-31T00:00:00.000Z` for a classic last-day-of-month experience. |
| `now` | The point in time from which to look for the next occurance. Defaults to `Date.now()`. |

## See also

* [rrule](https://www.npmjs.com/package/rrule) - Library for working with recurrence rules for calendar dates according to RFC 5545.
43 changes: 43 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
module.exports = exports = ({
from = new Date('0000-01-31T00:00:00.000Z'),
now = Date.now(),
} = {}) => {
const _from = from instanceof Date ? from : new Date(from)
const _now = now instanceof Date ? now : new Date(now)

// When the period of recurrances hasnt even begun yet
if (_now < _from) return _from

// TODO: literally everything below here

const lastDayOfNowMonth = new Date(Date.UTC(_now.getUTCFullYear(), _now.getUTCMonth() + 1, 0))
const targetInNowMonth = Math.min(_from.getUTCDate(), lastDayOfNowMonth.getUTCDate())

const targetMonthUTC = _now.getUTCMonth() + (
_now.getUTCDate() > targetInNowMonth
? 1
: 0
)

const nextDate = new Date(Date.UTC(
_now.getUTCFullYear(),
targetMonthUTC,
Math.min(_from.getUTCDate(), new Date(Date.UTC(_now.getUTCFullYear(), targetMonthUTC + 1, 0)).getUTCDate()),
_from.getUTCHours(),
_from.getUTCMinutes(),
_from.getUTCSeconds(),
_from.getUTCMilliseconds(),
))

console.log({
_from,
_now,
nowMonth: _now.getUTCMonth(),
lastDayOfNowMonth,
targetInNowMonth,
targetMonthUTC,
nextDate,
})

return nextDate
}
57 changes: 57 additions & 0 deletions src/index.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const nextMonthly = require('./index')

it('defaults to last day of this month', () => {
const now = new Date()
const expected = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 0)).toJSON()

const output = nextMonthly().toJSON()

expect(output).toBe(expected)
})

it('accepts a "now" override', () => {
const output = nextMonthly({
now: '2003-11-13T00:00:00.000Z',
})
expect(output.toJSON()).toBe('2003-11-30T00:00:00.000Z')
})

it('returns date within the same month as now', () => {
const output = nextMonthly({
from: '2022-07-26T00:00:00.000Z',
now: '2022-08-03T00:00:00.000Z',
})
expect(output.toJSON()).toBe('2022-08-26T00:00:00.000Z')
})

it('returns date in next month if it has already passed', () => {
const output = nextMonthly({
from: '2022-07-26T00:00:00.000Z',
now: '2022-08-31T00:00:00.000Z',
})
expect(output.toJSON()).toBe('2022-09-26T00:00:00.000Z')
})

it('returns the from-date when it is in the future', () => {
const output = nextMonthly({
from: '2022-08-23T01:00:00.000Z',
now: '1999-08-23T00:00:00.000Z',
})
expect(output.toJSON()).toBe('2022-08-23T01:00:00.000Z')
})

it('returns 29th on leap year when target is 31st', () => {
const output = nextMonthly({
from: '1990-07-31T00:00:00.000Z',
now: '2000-02-15T00:00:00.000Z',
})
expect(output.toJSON()).toBe('2000-02-29T00:00:00.000Z')
})

it('behaves like README', () => {
const nextBillingDate = nextMonthly({
from: new Date('1990-01-07T01:29:03.999Z'),
now: new Date('2022-08-23T12:29:08.551Z'),
})
expect(nextBillingDate.toJSON()).toBe('2022-09-07T01:29:03.999Z')
})

0 comments on commit 37380a9

Please sign in to comment.