'Functionally create array from anonther function
I try to make an object of arrays contains first and last date of weeks on one specific month. For example:
{
W1: [ '2021-01-01', '2021-01-07' ],
W2: [ '2021-01-08', '2021-01-14' ],
W3: [ '2021-01-15', '2021-01-21' ],
W4: [ '2021-01-22', '2021-01-28' ],
W5: [ '2021-01-29', '2021-01-31' ]
}
Is there any more functional approach for this code?
// const moment = require('moment'); // require
const getMaxDate = (year, month) => moment(`${year}-${month}`, "YYYY-MM").daysInMonth();
const getDate = (x, y, z) => moment().year(x).month(y).date(z).format("YYYY-MM-DD");
const dateOnMonth = (month, year) => {
const max = getMaxDate(year, month)
let obj = {}
let counter = 0
let last = 0
for (let i = 0; i <= max; i++) {
if (i != 0 && i % 7 == 0) {
counter++
const y = getDate(year, month - 1, i - 6)
const z = getDate(year, month - 1, i)
obj[`W${counter}`] = [y, z]
last = i + 1
} else if (counter == 4) {
const j = getDate(year, month - 1, last)
const k = getDate(year, month - 1, max)
obj[`W${counter+1}`] = [j, k]
}
}
return obj
}
console.log(dateOnMonth(1, 2021))
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.2/moment.min.js"></script>
Solution 1:[1]
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))
// ...
Solution 2:[2]
Try this,
You are wasting too many iterations in for loop, while my code will take about max 5 iterations in for loop.
Change the month and year according to your need.
var month = 4; // means May, this index starts from 0 as January
var year = 1986
function get_dates_by_week_of_a_month_year(month,year){
var d = new Date(year, month+1,0);
var num_days = d.getDate();
obj={};
week=1;
for(i=0;i<num_days;i+=7){
start = i+1;
end = (i+7) > num_days ? num_days : (i+7);
d1 = new Date(year, month, start).toLocaleDateString('en-CA');
d2 = new Date(year, month, end).toLocaleDateString('en-CA');
obj['W'+week++] = [d1,d2];
}
return obj;
}
console.log(get_dates_by_week_of_a_month_year(month,year));
Solution 3:[3]
It makes sense to use functions for code that will be used multiple times, however it doesn't make sense for trivial functions. I wouldn't use a library for this as the algorithm is very simple and plain JS has pretty much everything required.
You can also simplify the algorithm somewhat, e.g.
/* Return weeks of month
* First week starts on 1st of month and goes for 7 days
* Last week has less than 7 days in any month of more than 28 days
*
* @param {string|number} year - calendar year, e.g. 2020
* @param {string|number} month - calendar month, 1 == Jan, etc.
* @returns {Object} weeks as {weekNumber:[startDate, endDate]}
* where dates are in YYYY-MM-DD format
*/
function getMonthWeeks(year, month) {
// Create a date from the supplied values
let date = new Date(year, month - 1);
// Other variables
let weeks = {};
let week = 0;
// Loop over weeks until exceed month end
while (date.getMonth() < month) {
// Get week number string
let weekNum = `W${++week}`;
// Initialise week array with start date
weeks[weekNum] = [toYMD(date)];
// Increment to end of week
date.setDate(date.getDate() + 6);
// If gone past end of month, go back to last day
if (date.getMonth() == month) {
date.setDate(0);
}
// Add last day to week array
weeks[weekNum].push(toYMD(date));
// Increment to start of next week
date.setDate(date.getDate() + 1);
}
return weeks;
}
// Helper to format dates
function toYMD(date) {
return date.toLocaleDateString('en-CA');
}
// April 2022
console.log(getMonthWeeks(2022,4));
// May 2022
console.log(getMonthWeeks(2022,5));
Given the simple algorithm, you know what the first four weeks will be so really only need to insert the year and month into the first 4 weeks and calculate the end of the 5th (if there is one).
You might use a library for adding days and formatting dates, but I really don't see the need. Using:
date.getMonth() == month
to check if the month has overflowed might be a little obscure, but can be re–written so the logic is clearer with only one extra line.
Functions shouldn't be used where they are unnecessarily inefficient, e.g.
const getMaxDate = (year, month) => moment(`${year}-${month}`, "YYYY-MM").daysInMonth();
Creates a string, parses it to a date, then calls another function to get the number of days in the month which happens to coincide with the date of the last day in the month. Compare to:
const getMaxDate = (year, month) => moment([year, month-1]).endOf('month').getDate();
and
const getMaxDate = (year, month) => new Date(year, month, 0).getDate();
All assume month is calendar month number.
Also:
const getDate = (x, y, z) => moment().year(x).month(y).date(z).format("YYYY-MM-DD");
is very inefficient (and a bit confusing as y is month), consider:
const getDate = (year, month, day) => moment([year, month-1, day]).format("YYYY-MM-DD");
Lastly, given the first 4 weeks have exactly the same dates for every month, you can use simple arithmetic and string formatting for the dates and just use one date to calculate the last day, e.g.
// month is calendar month
function getWeeksInMonth(year, month) {
let z = n => ('0'+n).slice(-2);
let endDay = new Date(year, month, 0).getDate();
let weeks = {};
let week = 0;
let day = 1;
// Do first 4 weeks
while (day < endDay) {
weeks['W'+ ++week] = [`${year}-${z(month)}-${z(day)}`,
`${year}-${z(month)}-${z(day+6)}`];
day += 7;
}
// Add 5th week if there is one
if (endDay > 28) {
weeks['W5'] = [`${year}-${z(month)}-29`,
`${year}-${z(month)}-${endDay}`];
}
return weeks
}
console.log(getWeeksInMonth(2022,2)); // February
console.log(getWeeksInMonth(2022,4)); // April
I think that's very much more efficient than a whole lot of unnecessary date manipulation and formatting. :-)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | |
| Solution 3 |
