7

I need to find this month, previous month and the next month of a specific date.

For example, date was set to 31 of every month, what I expect to get the date is 2018-02-28, 2018-03-31 and 2018-04-30. For those dates which has no 31, than it becomes the day before.

And finally generate 2 period, 2018-02-28 to 2018-03-29, 2018-03-30 to 2018-04-31. I don't know how to handle feb and the month which less than 31.

var d = new Date();
var tyear = d.getFullYear(); //2018
var tmonth = d.getMonth();  //2  
new Date(2018, tmonth-1, 31);//output 2018-03-02 not what I wanted
Wayne Fung
  • 69
  • 1
  • 5

6 Answers6

4

A simple algorithm is to add months to the original date, and if the new date is wrong, set it to the last day of the previous month. Keeping the original date values unmodified helps, e.g.

/* @param {Date} start - date to start
** @param {number} count - number of months to generate dates for
** @returns {Array} monthly Dates from start for count months
*/
function getMonthlyDates(start, count) {
  var result = [];
  var temp;
  var year = start.getFullYear();
  var month = start.getMonth();
  var startDay = start.getDate();
  for (var i=0; i<count; i++) {
    temp = new Date(year, month + i, startDay);
    if (temp.getDate() != startDay) temp.setDate(0);
    result.push(temp);
  }
  return result;
}

// Start on 31 Jan in leap year
getMonthlyDates(new Date(2016,0,31), 4).forEach(d => console.log(d.toString()));
// Start on 31 Jan not in leap year
getMonthlyDates(new Date(2018,0,31), 4).forEach(d => console.log(d.toString()));

// Start on 30 Jan
getMonthlyDates(new Date(2018,0,30), 4).forEach(d => console.log(d.toString()));
// Start on 5 Jan
getMonthlyDates(new Date(2018,0,5), 4).forEach(d => console.log(d.toString()));
RobG
  • 142,382
  • 31
  • 172
  • 209
1

I think you're going to need an array with 12 numbers in it. Each number is the amount of days in each month and the numbers in the array go in order (first number is 31 because January has 31 days, second is 28 or 29 for Feb), etc. Then you'll get the month number from your input date and look in the array at the number corresponding to the month number +/- 1.

You'll then need to construct a date for the previous month and the next month based on the number of days in the current month.

See comments inline:

let daysInMonths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

document.getElementById("date").addEventListener("input", function(){
  console.clear();
  
  // Create new Date based on value in date picker
  var selectedDate = new Date(this.value + 'T00:00');

  var year = selectedDate.getYear();

  // Determine if it is a leap year (Feb has 29 days) and update array if so.
  if (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)) {
    daysInMonths[1] = 29;
  }

  var selectedDateMonth = selectedDate.getMonth();
     
  // Get previous month number (if current month is January, get December)
  let prevMonth = selectedDateMonth > 0 ? selectedDateMonth - 1 : 11;
  
  let prevMonthDate = null;
  
  // If selected date is last day of month...
  if(selectedDate.getDate() === daysInMonths[selectedDateMonth]){
    // Create new date that takes the selected date and subtracts the correct amount of
    // days from it based on a lookup in the array.
    var newDate1 = new Date(selectedDate.getTime());
    prevMonthDate = 
     new Date(newDate1.setDate(selectedDate.getDate() - daysInMonths[selectedDateMonth]));
  } else {
    // Create a new date that is last month and one day earlier
    var newDate2 = new Date(selectedDate.getTime());
    prevMonthDate = 
      new Date(new Date(newDate2.setDate(selectedDate.getDate() - 1))
        .setMonth(selectedDate.getMonth() - 1));
  }
  
  // Get next month (if current month is December, get January
  let nextMonth = selectedDateMonth < 11 ? selectedDateMonth + 1 : 0;  
  
  let nextMonthDate = null;
  
  // Same idea for next month, but add instead of subtract.
  
  // If selected date is last day of month...
  if(selectedDate.getDate() === daysInMonths[selectedDateMonth]){
    var newDate3 = new Date(selectedDate.getTime());
    nextMonthDate = 
     new Date(newDate3.setDate(selectedDate.getDate() + daysInMonths[selectedDateMonth + 1]));
  } else {
    var newDate4 = new Date(selectedDate.getTime());
    nextMonthDate = new Date(new Date(newDate4.setDate(selectedDate.getDate() + 1)).setMonth(selectedDate.getMonth() + 1));
  }  

  console.log("Last month date: " + prevMonthDate.toLocaleDateString());
  console.log("Next month date: " + nextMonthDate.toLocaleDateString());  
});
<p>Pick a date: <input type="date" id="date"></p>
Scott Marcus
  • 64,069
  • 6
  • 49
  • 71
0

Use this approach:

Javascript Date Object – Adding and Subtracting Months

From the Author

There is a slight problem with the Javascript Date() Object when trying to advance to the next month or go back to the previous month.

For example, if your date is set to October 31, 2018 and you add one month, you'd probably expect the new date to be November 30, 2018 because November 31st doesn't exist. This, however, isn't the case.

Javascript automatically advances your Date object to December 1st. This functionality is very useful in most situations(i.e. adding days to a date, determining the number of days in a month or if it's a leap year), but not for adding/subtracting months. I've put together some functions below that extend the Date() object: nextMonth() and prevMonth().

function prevMonth() {
  var thisMonth = this.getMonth();
  this.setMonth(thisMonth - 1);
  if (this.getMonth() != thisMonth - 1 && (this.getMonth() != 11 || (thisMonth == 11 && this.getDate() == 1)))
    this.setDate(0);
}

function nextMonth() {
  var thisMonth = this.getMonth();
  this.setMonth(thisMonth + 1);
  if (this.getMonth() != thisMonth + 1 && this.getMonth() != 0)
    this.setDate(0);
}

Date.prototype.nextMonth = nextMonth;
Date.prototype.prevMonth = prevMonth;

var today = new Date(2018, 2, 31); //<----- March 31st, 2018

var prevMonth = new Date(today.getTime());
prevMonth.prevMonth();
console.log("Previous month:", prevMonth);

console.log("This month:", today)

var nextMonth = new Date(today.getTime());
nextMonth.nextMonth();
console.log("Next month:", nextMonth);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Community
  • 1
  • 1
Ele
  • 33,468
  • 7
  • 37
  • 75
  • OP says *For those dates which has no 31, than it becomes the day before.* Your answer would return same day of previous and next months when it's not the last day of the month. – Scott Marcus Mar 13 '18 at 21:20
  • Simply quoting someone else's work isn't an answer. These methods mutate the original date, you haven't shown how to create a sequence of dates like 30-Jan, 28-Feb, 30-Mar, 30-Apr, etc. If you simply keep incrementing by one month, you'll get 28-Mar, 28-Apr, etc. – RobG Mar 14 '18 at 00:35
0

Dates and time zones are a real pain in JS, so challenge accepted.

I broke it down in two steps:
- Count the days of prev and next month
- Compare with selected day and pick the lowest number

Testcases included

function createUTCDate(year, month, day) {
  return new Date(Date.UTC(year, month, day));
}

function splitDate(date) {
  return {
    year: date.getUTCFullYear(),
    month: date.getUTCMonth(),
    day: date.getUTCDate()
  };
}

function numberOfDaysInMonth(year, month) {
  return new Date(year, month + 1, 0).getDate();
}

function dateNextMonth(dateObj) {
  const daysNextMonth = numberOfDaysInMonth(dateObj.year, dateObj.month + 1);
  const day = Math.min(daysNextMonth, dateObj.day);
  return createUTCDate(dateObj.year, dateObj.month + 1, day);
}

function datePreviousMonth(dateObj) {
  const daysPrevMonth = numberOfDaysInMonth(dateObj.year, dateObj.month - 1);
  const day = Math.min(daysPrevMonth, dateObj.day);
  return createUTCDate(dateObj.year, dateObj.month - 1, day);
}

const log = console.log;

function print(dateString) {
  const date = new Date(dateString);
  const dateObj = splitDate(date);
  log("Previous: ", datePreviousMonth(dateObj).toISOString());
  log("Selected: ", date.toISOString());
  log("Next: ", dateNextMonth(dateObj).toISOString());
  log("--------------");
}

const testCases = [
  "2018-03-01 UTC",
  "2018-03-31 UTC",
  "2018-01-01 UTC",
  "2018-12-31 UTC"
];

testCases.forEach(print);

Please note that the hack with new Date(xxx + " UTC") is not according to spec and is just there for testing purposes. Results may vary per browser. You should choose an input format and construct your dates accordingly.

Jonathan
  • 8,771
  • 4
  • 41
  • 78
  • The string "2018-03-01 UTC" is not a valid ISO 8601 date string as dates do not have a timezone. As a consequence, parsing is implementation dependent (and results in an invalid date in at least one current browser). Why didn't you use just "2018-03-01"? Also note that it will be parsed as UTC by the built-in parser, so you need to use all UTC methods or the host timezone offset will introduce difficult to find bugs in seemingly rare and random cases. – RobG Mar 14 '18 at 05:21
0

I handle it in a foolish way by concatenating string

let daysInMonths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
let months = ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"];

var target = nexttarget = lasttarget = "29"; //target day

if (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)) {
    daysInMonths[1] = 29;
}

function findLastDay(target, month){
    if(target > daysInMonths[month]){
        target = daysInMonths[month];
    }
    return target;
}

then

var d = new Date();
var year = d.getFullYear();
var month = d.getMonth();

target = findLastDay(target, month);

var this_month = year+"-"+months[month]+"-"+target;
console.log(this_month);//2018-03-29

// next month
if(month == 11){
   nextmonth = 0;
   nextyear = year + 1;
}else{
   nextmonth = month+1;
   nextyear = year;
}

nexttarget = findLastDay(nexttarget, nextmonth);

var next_month = nextyear+"-"+months[nextmonth]+"-"+nexttarget;
console.log(next_month);//2018-04-29

//last month
if(month == 0){
   lastmonth = 11;
   lastyear = year - 1;
}else{
   lastmonth = month - 1;
   lastyear = year;
}

lasttarget = findLastDay(lasttarget, lastmonth);

var last_month = lastyear+"-"+months[lastmonth]+"-"+lasttarget;
console.log(last_month);//2018-02-28
Wayne Fung
  • 69
  • 1
  • 5
-1

Date handling is tricky at the best of times. Don't do this yourself. Use Moment.js.

var target = 31;
var today = moment().date(target).calendar();
//  today == '03/31/2018'

var nextMonth =  moment().date(target).add(1, 'month').calendar();
// nextMonth == '04/30/2018'

var lastMonth = moment().date(target).subtract(1, 'month').calendar()
// lastMonth == '02/28/2018'
Ian McLaird
  • 5,507
  • 2
  • 22
  • 31
  • 1
    That is nice, but should really be a comment because OP didn't ask for moment or any library other than jQuery. – Scott Marcus Mar 13 '18 at 21:48
  • @ScottMarcus I'm dying to see the first jQuery answer :D – Jonathan Mar 13 '18 at 23:22
  • @Jonathan You can take my vanilla JS answer and convert it to jQuery quite easily, because this answer is not really suited to jQuery. Most of the code would stay exactly the same. – Scott Marcus Mar 13 '18 at 23:42