functional, immutable, reusable
Here's a functional approach where a date module can create dates, access properties of the constructed dates, and manipulate the dates in immutable ways. weeks is a more sophisticated operation that is built using the simple date operations -
// date.js
const date = (y, m, d) => new Date(y, m - 1, d)
const day = (t) => t.getDate()
const month = (t) => t.getMonth() + 1
const year = (t) => t.getFullYear()
const addDays = (t, n) => date(year(t), month(t), day(t) + n)
const daysInMonth = (t) => day(date(year(t), month(t) + 1, 0))
const weeks = (t, days = daysInMonth(t)) =>
days <= 7
? [[t, addDays(t, days - 1)]]
: [[t, addDays(t, 6)], ...weeks(addDays(t, 7), days - 7)]
console.log(weeks(date(2022, 1, 1)))
[
[ "2022-01-01T00:00:00.000Z", "2022-01-07T00:00:00.000Z" ],
[ "2022-01-08T00:00:00.000Z", "2022-01-14T00:00:00.000Z" ],
[ "2022-01-15T00:00:00.000Z", "2022-01-21T00:00:00.000Z" ],
[ "2022-01-22T00:00:00.000Z", "2022-01-28T00:00:00.000Z" ],
[ "2022-01-29T00:00:00.000Z", "2022-01-31T00:00:00.000Z" ]
]
If you want to format the output as a string, that should be it's own date operation that operates on the underlying data type. Here you can see it would be easy to add leading zeroes or target specific locales -
// date.js (continued)
const toString = (t) => `${year(t)}-${month(t)}-${day(t)}`
console.log(weeks(date(2022, 3, 1)).map(w => [toString(w[0]), toString(w[1])]))
[
[ "2022-3-1", "2022-3-7" ],
[ "2022-3-8", "2022-3-14" ],
[ "2022-3-15", "2022-3-21" ],
[ "2022-3-22", "2022-3-28" ],
[ "2022-3-29", "2022-3-31" ]
]
thinking in modules
It's important to adapt your thinking to a modular-based approach. This is the new JavaScript way moving forward where import allows for partial import of modules and enables compilers to remove huge swathes of unused "dead" code; a process called tree-shaking.
Similar to classes, modules allow their authors to effectively black box data and provide sanctioned ways for interacting with the data through publicly exported module methods. Unlike modules however, classes cannot be separated from their instance methods. If you import the class to use it for one method, all the other methods will come with it.
The underlying representation of date doesn't have to be Date. An array [year, month, date], object {year, month, date} or any other representation can be used. Users of the module are not supposed to make any assumptions about the underlying data type and only use the module methods provided. This allows the module author to make underlying changes and optimizations freely as long as public methods still behave properly.
// date.js
// date : (int, int, int) -> t
const date = (y, m, d) => new Date(y, m - 1, d)
// day : t -> int
const day = (t) => t.getDate()
// month : t -> int
const month = (t) => t.getMonth() + 1
// year : t -> int
const year = (t) => t.getFullYear()
// addDays : (t, int) -> t
const addDays = (t, n) => date(year(t), month(t), day(t) + n)
// daysInMonth : t -> int
const daysInMonth = (t) => day(date(year(t), month(t) + 1, 0))
// weeks : t -> array (t, t)
const weeks = (t, days = daysInMonth(t)) =>
days <= 7
? [[t, addDays(t, days - 1)]]
: [[t, addDays(t, 6)], ...weeks(addDays(t, 7), days - 7)]
// toString : t -> string
const format = (t) => `${year(t)}-${month(t)}-${day(t)}`
export { date, day, month, year, addDays, daysInMonth, weeks, toString }
// main.js
import { date, weeks } from "./date.js"
const feb22 = date(2022, 2, 1)
console.log(weeks(feb22))
// ...