Gets number of full months between dates, can this be improved? - javascript

The line let dateInPlay = zzGetLastDateOfTheMonth(new Date(year, month + 1, 1)) is duplicated and I could not think of how to avoid that.
Thanks in advance for any improvements!
/**
* Gets number of full months between dates
*
* #author me
* #version 1.0
* #since 2022/10/24
* #param {Date} start date
* #param {Date} end date
* #return {number} number of months between dates
*/
function zzFullMonthsBetween(_startDate, _endDate) {
let firstDate = zzGetFirstDateOfTheMonth(_startDate)
let lastDate = zzGetLastDateOfTheMonth(_endDate)
if (lastDate > firstDate) {
let numberOfMonths = 1
if (firstDate.getFullYear() == lastDate.getFullYear() && firstDate.getMonth() == lastDate.getMonth())
return numberOfMonths
let year = firstDate.getFullYear()
let month = firstDate.getMonth()
if (month == 12)
{
year++
month = 1
}
let dateInPlay = zzGetLastDateOfTheMonth(new Date(year, month + 1, 1))
numberOfMonths++
do {
if (lastDate.getTime() == dateInPlay.getTime())
return numberOfMonths
else {
numberOfMonths++
let year = dateInPlay.getFullYear()
let month = dateInPlay.getMonth()
if (month == 12)
{
year++
month = 1
}
dateInPlay = zzGetLastDateOfTheMonth(new Date(year, month + 1, 1))
}
}
while (lastDate !== dateInPlay);
}
return undefined
}
// function TESTzzFullMonthsBetween()
// {
// let today = new Date()
// let dates = [new Date('1/1/2022'), new Date('10/24/2022'), new Date('11/27/2022'), new Date('12/5/2022'), new Date('1/2/2023'), new Date('2/1/2023'), new Date('2/2/2024'), ]
// for (let z = 0; z < dates.length; z++)
// Logger.log(`${dates[z]}: ${zzFullMonthsBetween(today, dates[z])}`)
// }
function zzGetFirstDateOfTheMonth(_date)
{
return new Date(_date.getFullYear(), _date.getMonth(), 1)
}
function zzGetLastDateOfTheMonth(_date)
{
let year = _date.getFullYear()
let month = _date.getMonth()
if (month == 12)
{
year++
month = 1
}
return zzAddOnDate(new Date(year, month + 1, 1), -1)
}
function zzAddOnDate(_date, _daysToAdd)
{
var newDate = new Date(_date);
newDate.setDate(newDate.getDate() + _daysToAdd);
return newDate;
}

Related

Split month into first and second half based on current date and return weekdays (JS)

perhaps someone can give me some good ideas of how to accomplish this.
I would like to get the weekdays for either the first two weeks or last two weeks of a month based on the current date.
So if we are using the following to get the date today (2022-07-06)
const current = new Date();
const date = `${current.getFullYear()}-${current.getMonth()+1}-${current.getDate()}`;
The results I would be looking for are
const firstHalfWeekdates = ['2022-07-04', '2022-07-05', '2022-07-06', '2022-07-07', '2022-07-08', '2022-07-11', '2022-07-12', '2022-07-13', '2022-07-14', '2022-07-15']
and if the date fell on 2022-07-18 it would return
const secondHalfWeekdates = ['2022-07-18', '2022-07-19', '2022-07-20', '2022-07-21', '2022-07-22', '2022-07-25', '2022-07-26', '2022-07-27', '2022-07-28', '2022-07-29']
Also happy to use a library
Maybe this can give you a start. It returns the weekdays, divided by week.
I was trying to do everything you asked, but I ran into some problems, for example you want to divide the month into 4 weeks, 2 in the first half and 2 in the second half, but for example this month now July/2022, has a week that has only one weekday (July 1º), but in your expected results you ignored this week, whats the logic to ignore weeks? It has to be a complete week with 5 weekdays?
What about last month Jun/2022, there were not 4 complete weeks, there were only 3 complete weeks, the other 2 has 3 and 4 days respectively, which week would you ignore in this case?
function isWeekDay(day) {
return day != 0 && day != 6;
}
function formatDateYYYYMMDD(date) {
let dateString = date.toLocaleDateString('en-GB');
let year = dateString.substring(6, 10);
let month = dateString.substring(3, 5);
let day = dateString.substring(0, 2);
return `${year}-${month}-${day}`;
}
function getWeekdaysOfTheCurrentMonthDividedByWeek() {
let currentDate = new Date();
let month = currentDate.getMonth();
let weekdays = [];
let tempDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1);
let week = [];
while (tempDate.getMonth() == month) {
if (isWeekDay(tempDate.getDay())) {
week.push(formatDateYYYYMMDD(tempDate));
} else if (week.length > 0) {
weekdays.push(week);
week = [];
}
tempDate.setDate(tempDate.getDate() + 1);
}
return weekdays;
}
console.log(getWeekdaysOfTheCurrentMonthDividedByWeek());
You could split the month up into calendar weeks. (e.g. for July 2022 it would be: July 1-2, 3-9, 10-16, ect…)
Then, depending on the day, take either the first or second half of the weeks.
Iterate over the filtered weeks, counting the weekdays.
I choose to include the third week in the first half of the month if there were 5 weeks, but you could change that by changing Math.ceil to Math.floor
/**
* Get the last item in an array, or undefined if the array is empty.
* #template T
* #param {[T]} array
* #returns {T|undefined}
*/
const lastItem = array => array[array.length - 1];
const getWeekdays = current => {
/** #type {[[Date]]} */
const weeks = [];
// Get the weeks
/**
* Get the calendar week of the given date.
* #param {Date} firstDay The first day of the week.
* #returns {[Date]}
*/
const getWeek = firstDay => {
/** #type {[Date]} */
let days = [];
let dateToTest = new Date(firstDay);
// Continue until the end of the week or month, whichever comes first.
while (
dateToTest.getDay() <= 6 &&
dateToTest.getMonth() == firstDay.getMonth()
) {
days.push(new Date(dateToTest));
dateToTest.setDate(dateToTest.getDate() + 1);
}
return days;
};
// The first day of the month
const firstDay = new Date(current.getFullYear(), current.getMonth());
let dateToTest = new Date(firstDay);
do {
weeks.push(getWeek(dateToTest));
dateToTest = new Date(lastItem(lastItem(weeks)));
dateToTest.setDate(dateToTest.getDate() + 1);
} while (dateToTest.getMonth() == firstDay.getMonth());
// Filter to half of the month
// Get the week of the given date
let currentWeek = 0;
weekLoop: for (let i = 0; i < weeks.length; i++) {
const week = weeks[i];
for (const day of week) {
if (day == current) {
currentWeek = i;
break weekLoop;
}
}
}
/** #type {[[Date]]} */
let weeksInHalf = [];
const numOfWeeksInFirstHalf = Math.ceil(weeks.length / 2),
numOfWeeksInSecondHalf = weeks.length - numOfWeeksInFirstHalf;
for (
let i = 0;
i <
(currentWeek < numOfWeeksInFirstHalf
? numOfWeeksInFirstHalf
: numOfWeeksInSecondHalf);
i++
) {
weeksInHalf.push(weeks[i]);
}
// Filter out weekends
// Format dates
return weeksInHalf
.flat()
.filter(day => day.getDay() > 0 && day.getDay() < 6)
.map(
day => `${day.getFullYear()}-${day.getMonth() + 1}-${day.getDate()}`
);
};
// Tests
for (let i = 0; i < 12; i++) {
const weekdays = getWeekdays(new Date(2022, i));
weekdays.forEach(dateString => {
const [year, month, day] = dateString.split("-");
const date = new Date(year, month - 1, day);
if (date.getDay() == 0 || date.getDay() == 6)
throw new Error("Invalid day: (day)");
else console.log(dateString)
});
}

JavaScript weekday list get

I need to create a JavaScript function for the below requirements. I need to get every week day date list. If you know nodeJs package tell me that. Thank you for your attention.
Example -
2022-01-03
2022-01-04
2022-01-05
2022-01-06
2022-01-07
2022-01-10
2022-01-11
2022-01-12
2022-01-13
2022-01-14
..........
..........
until year-end
like this pattern (only Monday to Friday)
function getWeekDaysForYear(year) {
const isLeap = year % 4 === 0;
const numberOfDays = isLeap ? 366 : 365;
let currentDate = new Date(Date.UTC(year, 0, 0, 0, 0, 0, 0));
const weekDays = [];
for(let i = 1; i <= numberOfDays; i++) {
currentDate = new Date(currentDate.getTime() + 24 * 3600 * 1000);
if (currentDate.getDay() === 0 || currentDate.getDay() === 6) {
continue;
}
weekDays.push(currentDate.toISOString().split('T')[0]);
}
return weekDays;
}
console.log(getWeekDaysForYear(2022));
This is a simple function which returns all the weekdays for the specified year in an array.
JavaScript's Date object overflows, so you can use:
for (i=1;i<366;i++) {
if (i%7==1 || i%7==2) continue;
const d = new Date(2022, 0, i);
document.write(d.toDateString(),'<br>');
}
You will need to watch for leap years, and recalculate which days are the weekend every year.
Something like this
const endDate = new Date(2022,1,2);
const date = new Date(); //today
while (endDate > date) {
const weekDay = date.getDay();
if (weekDay != 6 && weekDay != 0) {
let year = new Intl.DateTimeFormat('en', { year: 'numeric' }).format(date);
let month = new Intl.DateTimeFormat('en', { month: '2-digit' }).format(date);
let day = new Intl.DateTimeFormat('en', { day: '2-digit' }).format(date);
console.log(`${year}-${month}-${day}`);
}
date.setDate(date.getDate() + 1);
}
Since we're play code golf…
function getWeekDays(year) {
// Start on 1 Jan of given year
let d = new Date(Date.UTC(year, 0));
let result = [];
do {
// Only push dates that aren't Sat (6) or Sun (0)
d.getDay() % 6 ? result.push(d.toLocaleDateString('en-CA')) : null;
// Increment date
d.setDate(d.getDate() + 1);
// Until get to 1 Jan again
} while (d.getMonth() + d.getDate() > 1)
return result;
}
console.log(getWeekDays(new Date().getFullYear()))
function getWeekDaysForDateRange(start, end) {
const [startYear, startMonth, startDate] = start.split("-");
const [endYear, endMonth, endDate] = end.split("-");
let beginDate = new Date(Date.UTC(startYear, startMonth - 1, startDate - 1, 0, 0, 0, 0));
let closeDate = new Date(Date.UTC(endYear, endMonth - 1, endDate, 0, 0, 0, 0));
const weekDays = [];
while(beginDate.getTime() !== closeDate.getTime()) {
beginDate = new Date(beginDate.getTime() + 24 * 3600 * 1000);
if (beginDate.getDay() === 0 || beginDate.getDay() === 6) {
continue;
}
weekDays.push(beginDate.toISOString().split('T')[0]);
}
return weekDays;
}
console.log(getWeekDaysForDateRange('2022-01-01', '2022-01-10'));
Something like this would work for date range as you want!

How to get starting and ending days of each month in a date interval in js

i'm looking for a function which takes as parameters a starting and an ending date that returns an array of each first/ending days for each months in this interval.
Expected :
myFunction(new Date('2021-02-06'), new Date('2021-04-24'))
Expected output :
[
{begin: '2021-02-06', end: '2021-02-28' },
{begin: '2021-03-01', end: '2021-03-31' },
{begin: '2021-04-01', end: '2021-04-24' },
]
You can use next code:
myFunction(new Date('2021-02-06'), new Date('2021-04-24'));
function myFunction(startDateInput, endDateInput) {
var monthDifference = monthDiff(startDateInput, endDateInput)
var dates = [];
for (var i = 0; i <= monthDifference; i++) {
var month = startDateInput.getMonth();
var year = startDateInput.getFullYear();
// this gets first day in month
var startDate = new Date(year, month + i, 1)
// this gets last day in month
var endDate = new Date(year, month + i + 1, 0)
console.log('startDate', startDate);
console.log('endDate', endDate);
dates.push(
{
begin : startDate,
end : endDate
}
)
}
// this is to overwrite first and last date in array, it can be done better
dates[0].begin = startDateInput;
dates[monthDifference].end = endDateInput;
console.log('dates', dates)
}
function monthDiff(d1, d2) {
var months;
months = (d2.getFullYear() - d1.getFullYear()) * 12;
months -= d1.getMonth();
months += d2.getMonth();
return months <= 0 ? 0 : months;
}
Of course you can improve this code further. I made it quickly.
const formatDateString = (date) => {
const leadingZero = (d) => (100 + d + '').substr(1);
const y = date.getFullYear();
const m = leadingZero(date.getMonth() + 1);
const d = leadingZero(date.getDate());
return `${y}-${m}-${d}`;
};
const myFunction = (startIn, endIn) => {
const monthDifference = monthsDist(startIn, endIn);
const res = [];
const startYear = startIn.getFullYear();
const startMonth = startIn.getMonth();
const startDate = startIn.getDate();
for (let deltaM = 0; deltaM <= monthDifference; deltaM++) {
const monthStart = new Date(
startYear,
startMonth + deltaM,
deltaM ? 1 : startDate
);
const monthEnd =
deltaM < monthDifference ?
new Date(startYear, startMonth + deltaM + 1, 0) :
endIn;
res.push({
begin: formatDateString(monthStart),
end: formatDateString(monthEnd),
});
}
return res;
};
const monthsDist = (start, end) => {
const diffM =
(end.getFullYear() - start.getFullYear()) * 12 +
end.getMonth() -
start.getMonth();
return diffM > 0 ? diffM : 0;
};
console.log(myFunction(new Date('2021-02-06'), new Date('2021-04-24')));
console.log(myFunction(new Date('2020-02-06'), new Date('2021-04-24')));
console.log(myFunction(new Date('2021-04-06'), new Date('2021-04-24')));

Use asterisk * instead of year in object with dates?

I have this code to calculate days between dates and skip holidays.
var gon = {};
gon["holiday"] = "2015-08-28,2015-09-25,2016-08-31,2016-08-07,2015-08-13,2016-08-29,2016-01-07,2015-10-31".split(",");
// 2 helper functions - moment.js is 35K minified so overkill in my opinion
function pad(num) { return ("0" + num).slice(-2); }
function formatDate(date) { var d = new Date(date), dArr = [d.getFullYear(), pad(d.getMonth() + 1), pad(d.getDate())];return dArr.join('-');}
function calculateDays(first,last) {
var aDay = 24 * 60 * 60 * 1000,
daysDiff = parseInt((last.getTime()-first.getTime())/aDay,10);
if (daysDiff>0) {
for (var i = first.getTime(), lst = last.getTime(); i <= lst; i += aDay) {
var d = new Date(i);
console.log(d.getDay());
if (d.getDay() == 6 || d.getDay() == 0 // weekend
|| gon.holiday.indexOf(formatDate(d)) != -1) {
daysDiff--;
}
}
}
return daysDiff;
}
How can I use asterisk * instead of year to cover all years. I don't want to do like this
gon["holiday"] = "2018-08-28,2018-09-25,2019-08-28,2019-09-25,2020-08-28,2020-09-25,2021-08-28,2021-09-25".split(",");
Can I do something like this
gon["holiday"] = "*-08-28,*-09-25".split(",");
This code could do the work for you:
gon["holiday"]= [...Array(10)].map((_,i) => (2015+i) + "-08-28");
Results:
(10) ["2015-08-28", "2016-08-28", "2017-08-28", "2018-08-28", "2019-08-28", "2020-08-28", "2021-08-28", "2022-08-28", "2023-08-28", "2024-08-28"]
You can use findIndex and provide it a function that would only match the day and month instead of the year, like so:
var gon = {};
gon["holiday"] = "*-08-28,*-09-25".split(",");
function pad(num) { return ("0" + num).slice(-2); }
function formatDate(date) { var d = new Date(date), dArr = [d.getFullYear(), pad(d.getMonth() + 1), pad(d.getDate())];return dArr.join('-');}
function calculateDays(first,last) {
var aDay = 24 * 60 * 60 * 1000,
daysDiff = parseInt((last.getTime()-first.getTime())/aDay,10);
if (daysDiff>0) {
for (var i = first.getTime(), lst = last.getTime(); i <= lst; i += aDay) {
var d = new Date(i);
console.log(d.getDay());
if (d.getDay() == 6 || d.getDay() == 0 // weekend
|| gon.holiday.findIndex((h)=>formatDate(d).replace(/[^-]+-/, '') == h.replace(/[^-]+-/, '')) != -1) {
daysDiff--;
}
}
}
return daysDiff;
}
I'm using a regular expression to remove the year, it removes the first occurrence of a dash and any characters before it.
You could have an array for holidays with fixed anniversaries in MM-DD format:
var fixedHols = ['08-28','09-25'];
another for those that move more or less randomly, such as easter, ramadan, diwali:
var movingHols = ['YYYY-MM-DD',...]
and another for rules–based holidays that is generated for the given year like the first Monday in May or the Tuesday following the first Monday in November or whatever, then test dates against them, e.g.
function isHoliday(date) {
let z = n => (n<10?'0':'')+n;
let fixedHols = ['08-28','09-25'];
let ymd = formatDate(date);
let md = ymd.slice(-5);
let movingHols = [ /* dates as YYY-MM-DD */ ];
let rulesHols = [ /* generate YYY-MM-DD for date.getFullYear() */ ];
// If date is in any array return true, otherwise return false
return [fixedHols, movingHols, rulesHols].some((hols, i) => hols.includes(i? ymd : md));
}
function formatDate(d) {
var z = n => (n<10?'0':'')+n;
return d.getFullYear()+'-'+z(d.getMonth()+1)+'-'+z(d.getDate());
}
[new Date(2018,7,27), // 27 Aug
new Date(2018,7,28), // 28 Aug
new Date(2018,7,29), // 29 Aug
new Date(2021,7,28), // 28 Aug
new Date(2018,8,25), // 25 Sep
new Date(2018,8,26)] // 26 Sep
.forEach(d =>
console.log(`Is ${formatDate(d)} a holiday? ${isHoliday(d)?'Yes':'No'}`)
);

How to return the next available date

I'm building a project with express and I have a scheduling calendar. I want to give to my users next available day. Format YYYY-MM-DD.
Rules:
The next available day is usually tomorrow unless:
- After 4pm the next available day is two days from now (i.e. Monday afternoon they can book Wednesday);
- Friday after 4pm the next available day is Monday;
- For Saturday it's Monday;
- For Sunday it's Tuesday;
I also have an array of public holidays, which are also unavailable. If the next day is a public holiday, the app should return the day after.
When there is a public holiday my app goes into a loop and it runs the whole loop. I don't know how to fix this. I thought it would skip the loop when it runs the second time.
const publicHolidays = ['2018-09-28', '2018-12-25']
const availableDay = (nextDay) => {
const d = new Date();
const utc = d.getTime() + (d.getTimezoneOffset() * 60000);
const nd = new Date(utc + (3600000 * 8));
if (nextDay === undefined) {
nextDay = 1;
}
if (nd.getDay() === 5 && nd.getHours() > 15) {
nextDay = 3;
} else if ([0, 6].includes(nd.getDay()) || nd.getHours() > 15) {
nextDay = 2;
}
const day = new Date();
const tomorrow = new Date(day);
tomorrow.setDate(tomorrow.getDate() + nextDay);
const yy = tomorrow.getFullYear();
let mm = tomorrow.getMonth() + 1;
if (mm < 10) {
mm = `0${mm}`;
}
let dd = tomorrow.getDate();
if (dd < 10) {
dd = `0${dd}`;
}
const available = `${yy}-${mm}-${dd}`;
if (publicHolidays.includes(available)) {
const nextDay = 7;
for (let i = 2; i < nextDay; i += 1) {
availableDay(i);
}
} else {
console.log('returning available', available);
return(available);
}
}
availableDay()
I think this logic will work - I've created a function to do the "date string - yyyy-mm-dd" thing because it's used in two places now
I also check for weekends by tomorrow.getDay() % 6 === 0 - you can of course use [0, 6].includes(tomorrow.getDay()) if you prefer
const publicHolidays = ['2018-09-28', '2018-12-25']
const availableDay = () => {
let nextDay = 1; // since we are not recursive any more
const d = new Date();
const utc = d.getTime() + (d.getTimezoneOffset() * 60000);
const nd = new Date(utc + (3600000 * 8));
if (nd.getDay() === 5 && nd.getHours() > 15) {
nextDay = 3;
} else if ([0, 6].includes(nd.getDay()) || nd.getHours() > 15) {
nextDay = 2;
}
const day = new Date();
const tomorrow = new Date(day);
tomorrow.setDate(tomorrow.getDate() + nextDay);
// changes start here
const dateString = d => `${.getFullYear()}-${('0' + (d.getMonth() + 1)).toString(-2)}-${('0' + d.getDate()).toString(-2)}`;
let available = dateString(tomorrow);
while (publicHolidays.includes(available) || (tomorrow.getDay() === 0)) {
tomorrow.setDate(tomorrow.getDate() + 1);
available = dateString(tomorrow);
}
console.log('returning available', available);
return(available);
}
availableDay()
There's probably more you can do to streamline the code - but this should fix the problem at least
I think you should always + 1 to nextDay. so if today is public holiday, try get the next day. the cycle repeat until it is not public holiday.
if (publicHolidays.includes(available)) {
availableDay(nextDay +1 );
} else {
console.log('returning available', available);
return(available);
}
Here is a more generic solution that might be applicable for people searching for something similar:
/**
* #summary Finds the next available date between a range, excluding a list of unavailable dates
* #param {Date} startDate The beginning of the date range.
* #param {Date} endDate The beginning of the date range.
* #param {Array of Date} excludeDates Dates that are not available.
*/
export const findNextAvailableDate = (startDate, endDate, excludeDates) => {
const excludeDatesStrArr = excludeDates.map(date => {
// Make sure dates are in a consistent string format so we can check for equality
excludeDate.setUTCHours(0, 0, 0, 0)
return excludeDate.toISOString()
})
let possibleDate = startDate
possibleDate.setUTCHours(0, 0, 0, 0)
let possibleDateStr = possibleDate.toISOString()
while (possibleDateStr !== endDate) {
if (!excludeDatesStrArr.includes(possibleDateStr)) {
// Date is not in exclude array, return available date
return possibleDate
} else {
// Date is included in exclude array, iterate to the next day
const newDate = possibleDate.setDate(possibleDate.getDate() + 1)
possibleDate = new Date(newDate)
possibleDate.setUTCHours(0, 0, 0, 0)
possibleDateStr = possibleDate.toISOString()
}
}
// Did not find next available date
return false
}

Categories

Resources