Let's assume that there are two dates: 2022-01-10
and 2022-08-20
.
User can type a day of a month when an action needs to be executed repeatedly once per month between these dates.
So if user types "25" the action should be executed 7 times, because the end date is out of range.
How to get such result using luxon in JavaScript? I need to have number of matching dates OR (that would be even better) a list of these dates.
Another example:
Input:
Day of a month: 5
Start date: 2022-07-08
End date: 2022-09-05
Expected output:
2022-08-05
2022-09-05
CodePudding user response:
A little hacky but this should get you on a decent path...
const startDate = luxon.DateTime.utc(2022, 1, 5, 0, 0, 0, 0)
const endDate = luxon.DateTime.utc(2022, 6, 5, 0, 0, 0, 0)
const diff = endDate.diff(startDate, ["months"])
// we get a diff of `5` here, let's generate the date map next
console.log(diff.toObject())
const possibleDates = []
const dayOfMonthInput = 2
const targetDateByInput = luxon.DateTime.utc(2022, 1, dayOfMonthInput, 0, 0, 0, 0)
for (
let i = 0,maybeDate=targetDateByInput;
i <= diff.months;
i , maybeDate = targetDateByInput.plus({ months: i })) {
// is maybeDate valid after START date?
if (maybeDate.startOf('day') > startDate.startOf('day')) {
// is maybeDate valid BEFORE end date?
if (maybeDate.startOf('day') < endDate.startOf('day')) {
// add to collection
possibleDates.push(maybeDate)
}
}
}
// output possible dates
console.log(possibleDates)
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/global/luxon.min.js"></script>
CodePudding user response:
One way is to just iterate over the intervening months. The only tricky bit is getting the next nth day of a month.
The following returns an array of Dates. If the nth date doesn't exist in a month, it's skipped.
A Luxon implementation should be a few less code as the parse and format functions wouldn't be necessary.
// Parse YYYY-MM-DD as local
function parseISODateLocal(date) {
let [y, m, d] = date.split(/\D/);
return new Date(y, m-1, d);
}
// Format date as YYYY-MM-DD
function formatYMD(d = new Date()) {
let z = n => (n < 10? '0' : '') n;
return `${d.getFullYear()}-${z(d.getMonth() 1)}-${z(d.getDate())}`;
}
// Get the next instance of the nth day of a month after date
function getNextNth(date, nth) {
// Get candiate nth date
let x = new Date(date.getFullYear(), date.getMonth(), nth);
// If it's before supplied date, add a month
if (x <=date) {
x.setMonth(x.getMonth() 1, nth);
// If rolled over to next month, set to nth
if (x.getDate() < nth) {
x.setDate(nth);
}
}
return x;
}
/* Return nth dates between start and end
* If nth date doesn't exist in a month, it's skipped
*
* @param {string} start - date in YYYY-MM-DD format
* @param {string} end - date in YYYY-MM-DD format
* @returns {Date[]} nth days between start and end (possibly none)
*/
function getNthDates(start, end, nth) {
let s = parseISODateLocal(start);
let e = parseISODateLocal(end);
let nthDates = [];
while (s < e) {
s = getNextNth(s, nth);
if (s < e) nthDates.push(new Date(s));
}
return nthDates;
}
// E.g.
let start = '2020-01-01';
let end = '2021-01-01';
let nth = 31;
console.log(getNthDates(start, end, nth).map(d => formatYMD(d)));
The getNextNth function relies on the fact that 31 day months are never more than one month away from the previous 31 day month, so the loop to find the next nth doesn't need recursion. If the current month doesn't have an nth day, the next will (supposing nth is in the range 1 to 31, which should be validated, as should the input date strings).