Home > Enterprise >  Functionally create array from anonther function
Functionally create array from anonther function

Time:04-15

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>

CodePudding user response:

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));

CodePudding user response:

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. :-)

  • Related