Funkcja JavaScript, aby dodać X miesięcy do daty


209

Szukam najłatwiejszego i najczystszego sposobu na dodanie X miesięcy do daty JavaScript.

Wolałbym nie radzić sobie z upływem roku ani pisać własnej funkcji .

Czy jest coś wbudowanego, co może to zrobić?


4
Spróbuj dodać metodę prototypowego obiektu daty, tak jak --------------- Date.prototype.addMonth = function (n) {return new Date (this.setMonth (this.getMonth ( ) + n)); };
Moises Hidalgo

1
@ kr37, odpowiedź Moisesa Hidalgo nie będzie działać poprawnie, jeśli docelowy miesiąc nie ma numeru dnia dzisiejszego. Odpowiedź bmpasini również to rozwiązuje
Alexandru Severin

Odpowiedzi tutaj nie są zbyt dobre, lepsze są na Dodawanie miesięcy do daty w JavaScript .
RobG

Odpowiedzi:


277

Poniższa funkcja dodaje miesiące do daty w JavaScript ( źródło ). Bierze pod uwagę okresy odnowienia roku i różne długości miesięcy:

function addMonths(date, months) {
    var d = date.getDate();
    date.setMonth(date.getMonth() + +months);
    if (date.getDate() != d) {
      date.setDate(0);
    }
    return date;
}

// Add 12 months to 29 Feb 2016 -> 28 Feb 2017
console.log(addMonths(new Date(2016,1,29),12).toString());

// Subtract 1 month from 1 Jan 2017 -> 1 Dec 2016
console.log(addMonths(new Date(2017,0,1),-1).toString());

// Subtract 2 months from 31 Jan 2017 -> 30 Nov 2016
console.log(addMonths(new Date(2017,0,31),-2).toString());

// Add 2 months to 31 Dec 2016 -> 28 Feb 2017
console.log(addMonths(new Date(2016,11,31),2).toString());

Powyższe rozwiązanie obejmuje przypadek przejścia od miesiąca z większą liczbą dni niż miesiąc docelowy. na przykład.

  • Dodaj dwanaście miesięcy do 29 lutego 2020 r. (Powinno być 28 lutego 2021 r.)
  • Dodaj miesiąc do 31 sierpnia 2020 r. (Powinien być 30 września 2020 r.)

Jeśli dzień miesiąca zmienia się podczas składania wniosku setMonth, wiemy, że przelaliśmy się na następny miesiąc z powodu różnicy długości miesiąca. W takim przypadku używamy, setDate(0)aby wrócić do ostatniego dnia poprzedniego miesiąca.

Uwaga: ta wersja tej odpowiedzi zastępuje wcześniejszą wersję (poniżej), która nie obsługiwała z wdziękiem różnych długości miesięcy.

var x = 12; //or whatever offset
var CurrentDate = new Date();
console.log("Current date:", CurrentDate);
CurrentDate.setMonth(CurrentDate.getMonth() + x);
console.log("Date after " + x + " months:", CurrentDate);

130
Ostrożnie - nie działa to w przypadku skrajnych przypadków, takich jak dodanie do 31. dnia większości miesięcy. Na przykład 31 października 2011 r. + 1 miesiąc przy użyciu tej metody to 01 grudnia 2011 r. Przy użyciu standardowego obiektu Data JavaScript.
tad

6
Dobry chwyt, Tad. Dziwi mnie, że tego nie widziałem. Zauważ, że T-SQL wybierz DATEADD (miesiąc, 1, „2011-10-31”) daje „2011-11-30”, co według mnie jest rozsądne. Mam 10 głosów z złą odpowiedzią. Chłodny. :-)
Czad

2
Zobacz także lata przestępne - dodanie 1 roku do 29 lutego w roku przestępnym da nieprzewidywalne wyniki w zależności od używanego języka (jako ogólna odpowiedź). To właśnie spowodowało, że w 2012 roku zlikwidowano platformę chmurową Microsoft Azure na kilka godzin
Ben Walding,

15
Jest tutajaddMonths funkcja , która uwzględnia koniec miesiąca (np. 2012-01-31 + 1 month = 2012-02-29Itd.).
RobG

3
wystarczy dodać, aby ustawić pierwszą datę każdego miesiąca: Today.setHours (0, 0, 0, 0); Today.setDate (1);
Alexey Strakh

56

Używam moment.js bibliotekę do manipulacji Date-Time . Przykładowy kod do dodania jeden miesiąc:

var startDate = new Date(...);
var endDateMoment = moment(startDate); // moment(...) can also be used to parse dates in string format
endDateMoment.add(1, 'months');

12
Dla własnego zdrowia psychicznego użyj moment.js.
Gaʀʀʏ

3
Absolutnie zgadzam się z @garry: użyj moment.js.
Michel

2
Zgadzam się, że jest to najczystszy sposób, ale pytanie brzmiało: „Czy jest coś wbudowanego, co może to zrobić?” Moment dodaje dziesiątki Kb do rozmiaru strony
Evgeny

To było dobre. Żadnych innych wtyczek, które znalazłem w sieci, które mogłyby działać znacznie lepiej niż to.
Eraniichan

26

Ta funkcja obsługuje przypadki krawędzi i jest szybka:

function addMonthsUTC (date, count) {
  if (date && count) {
    var m, d = (date = new Date(+date)).getUTCDate()

    date.setUTCMonth(date.getUTCMonth() + count, 1)
    m = date.getUTCMonth()
    date.setUTCDate(d)
    if (date.getUTCMonth() !== m) date.setUTCDate(0)
  }
  return date
}

test:

> d = new Date('2016-01-31T00:00:00Z');
Sat Jan 30 2016 18:00:00 GMT-0600 (CST)
> d = addMonthsUTC(d, 1);
Sun Feb 28 2016 18:00:00 GMT-0600 (CST)
> d = addMonthsUTC(d, 1);
Mon Mar 28 2016 18:00:00 GMT-0600 (CST)
> d.toISOString()
"2016-03-29T00:00:00.000Z"

Aktualizacja dla dat poza UTC: (autor: A.Hatchkins)

function addMonths (date, count) {
  if (date && count) {
    var m, d = (date = new Date(+date)).getDate()

    date.setMonth(date.getMonth() + count, 1)
    m = date.getMonth()
    date.setDate(d)
    if (date.getMonth() !== m) date.setDate(0)
  }
  return date
}

test:

> d = new Date(2016,0,31);
Sun Jan 31 2016 00:00:00 GMT-0600 (CST)
> d = addMonths(d, 1);
Mon Feb 29 2016 00:00:00 GMT-0600 (CST)
> d = addMonths(d, 1);
Tue Mar 29 2016 00:00:00 GMT-0600 (CST)
> d.toISOString()
"2016-03-29T06:00:00.000Z"

Jedyną rozsądną odpowiedzią tutaj. Ale należy zachować ostrożność w przypadku dat innych niż UTC. Może to przynieść nieoczekiwane rezultaty (np. 31 stycznia + 1 miesiąc = 1 mar dla UTC + 1
strefa

1
Innym problemem jest to, że zarówno modyfikuje pierwotną datę, jak i zwraca zmodyfikowaną wartość. Może warto dodać go do prototypu daty lub użyć zmiennej lokalnej dla zmienionej daty w funkcji?
Antony Hatchkins

Dzięki Antony, zmienna lokalna ma sens.
aMarCruz

1
Proponuję albo usunąć wiersz „data powrotu”, albo dodać lokalny var.
Antony Hatchkins

Zaktualizowano za pomocą zmiennej lokalnej i oddzielnych testów.
aMarCruz

12

Biorąc pod uwagę, że żadna z tych odpowiedzi nie uwzględni bieżącego roku, w którym zmienia się miesiąc, poniżej znajdziesz jedną, którą zrobiłem:

Metoda:

Date.prototype.addMonths = function (m) {
    var d = new Date(this);
    var years = Math.floor(m / 12);
    var months = m - (years * 12);
    if (years) d.setFullYear(d.getFullYear() + years);
    if (months) d.setMonth(d.getMonth() + months);
    return d;
}

Stosowanie:

return new Date().addMonths(2);

Ta odpowiedź nie uwzględnia dodawania miesięcy, jeśli w wynikowym miesiącu nie ma odpowiedniej daty (np. 31 stycznia + 1 miesiąc => 2 lub 3 marca). Wydaje się również, że istnieje problem z dodaniem więcej niż 12 miesięcy do daty: nie ma. Dodając, powiedzmy, 13 miesięcy, nie trzeba dodawać 1 roku i 1 miesiąca. Po prostu dodaj 13 miesięcy.
RobG

9

Zaczerpnięte z odpowiedzi @bmpsini i @Jazaret , ale bez rozszerzania prototypów: używanie prostych funkcji ( dlaczego rozszerzanie rodzimych obiektów jest złą praktyką? ):

function isLeapYear(year) { 
    return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)); 
}

function getDaysInMonth(year, month) {
    return [31, (isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
}

function addMonths(date, value) {
    var d = new Date(date),
        n = date.getDate();
    d.setDate(1);
    d.setMonth(d.getMonth() + value);
    d.setDate(Math.min(n, getDaysInMonth(d.getFullYear(), d.getMonth())));
    return d;
}

Użyj tego:

var nextMonth = addMonths(new Date(), 1);

4

Z powyższych odpowiedzi, jedyny, który obsługuje przypadki brzegowe (bmpasini z biblioteki datejs) ma problem:

var date = new Date("03/31/2015");
var newDate = date.addMonths(1);
console.log(newDate);
// VM223:4 Thu Apr 30 2015 00:00:00 GMT+0200 (CEST)

dobrze, ale:

newDate.toISOString()
//"2015-04-29T22:00:00.000Z"

gorzej:

var date = new Date("01/01/2015");
var newDate = date.addMonths(3);
console.log(newDate);
//VM208:4 Wed Apr 01 2015 00:00:00 GMT+0200 (CEST)
newDate.toISOString()
//"2015-03-31T22:00:00.000Z"

Wynika to z braku ustawienia czasu, a tym samym powrotu do 00:00:00, który następnie może przejść do poprzedniego dnia ze względu na strefę czasową lub zmiany oszczędzające czas lub cokolwiek ...

Oto moje proponowane rozwiązanie, które nie ma tego problemu, a także, jak sądzę, jest bardziej eleganckie, ponieważ nie opiera się na zakodowanych wartościach.

/**
* @param isoDate {string} in ISO 8601 format e.g. 2015-12-31
* @param numberMonths {number} e.g. 1, 2, 3...
* @returns {string} in ISO 8601 format e.g. 2015-12-31
*/
function addMonths (isoDate, numberMonths) {
    var dateObject = new Date(isoDate),
        day = dateObject.getDate(); // returns day of the month number

    // avoid date calculation errors
    dateObject.setHours(20);

    // add months and set date to last day of the correct month
    dateObject.setMonth(dateObject.getMonth() + numberMonths + 1, 0);

    // set day number to min of either the original one or last day of month
    dateObject.setDate(Math.min(day, dateObject.getDate()));

    return dateObject.toISOString().split('T')[0];
};

Jednostka pomyślnie przetestowana z:

function assertEqual(a,b) {
    return a === b;
}
console.log(
    assertEqual(addMonths('2015-01-01', 1), '2015-02-01'),
    assertEqual(addMonths('2015-01-01', 2), '2015-03-01'),
    assertEqual(addMonths('2015-01-01', 3), '2015-04-01'),
    assertEqual(addMonths('2015-01-01', 4), '2015-05-01'),
    assertEqual(addMonths('2015-01-15', 1), '2015-02-15'),
    assertEqual(addMonths('2015-01-31', 1), '2015-02-28'),
    assertEqual(addMonths('2016-01-31', 1), '2016-02-29'),
    assertEqual(addMonths('2015-01-01', 11), '2015-12-01'),
    assertEqual(addMonths('2015-01-01', 12), '2016-01-01'),
    assertEqual(addMonths('2015-01-01', 24), '2017-01-01'),
    assertEqual(addMonths('2015-02-28', 12), '2016-02-28'),
    assertEqual(addMonths('2015-03-01', 12), '2016-03-01'),
    assertEqual(addMonths('2016-02-29', 12), '2017-02-28')
);

3

Jak podkreślono większość odpowiedzi, moglibyśmy użyć metody setMonth () wraz z metodą getMonth () , aby dodać określoną liczbę miesięcy do określonej daty.

Przykład: (jak wspomniano w odpowiedzi @ChadD).

var x = 12; //or whatever offset 
var CurrentDate = new Date();
CurrentDate.setMonth(CurrentDate.getMonth() + x);

Ale powinniśmy ostrożnie korzystać z tego rozwiązania, ponieważ będziemy mieć problemy z przypadkowymi skrzynkami.

Do obsługi przypadków na krawędziach pomocna jest odpowiedź podana w poniższym linku.

https://stackoverflow.com/a/13633692/3668866


3

Proste rozwiązanie: 2678400000trwa 31 dni w milisekundach

var oneMonthFromNow = new Date((+new Date) + 2678400000);

Aktualizacja:

Użyj tych danych, aby zbudować naszą własną funkcję:

  • 2678400000 - 31 dzień
  • 2592000000 - 30 dni
  • 2505600000 - 29 dni
  • 2419200000 - 28 dni

8
Niektóre miesiące nie mają 31 dni
Alexandru Severin

Zgadzam się, ale możesz łatwo dostosować lub napisać funkcję
dr.dimitru 27.01.16

2
Zgaduję, że ludzie szukają właśnie tej funkcji, która w sposób automatyczny uwzględnia dni w miesiącach.
Alexander Bird

@AlexanderBird następnie użyj kuloodpornego narzędzia stworzonego przez kogoś innego, takiego jak moment.js
dr.dimitru

2
Ta odpowiedź nie uwzględnia czasu letniego, gdzie nie wszystkie dni trwają 24 godziny (lub 8,64e7 milisekund).
RobG

2
d = new Date();

alert(d.getMonth()+1);

Miesiące mają indeks oparty na 0, powinien ostrzec (4), który jest 5 (może);


Ta metoda może zwrócić wartość <0 i większą niż 12
Patrick

2

Aby dodać do zaakceptowanej odpowiedzi i komentarzy.

var x = 12; //or whatever offset
var CurrentDate = new Date();

//For the very rare cases like the end of a month
//eg. May 30th - 3 months will give you March instead of February
var date = CurrentDate.getDate();
CurrentDate.setDate(1);
CurrentDate.setMonth(CurrentDate.getMonth()+X);
CurrentDate.setDate(date);

2
Po ustawieniu Daty (31) na „1 lutego” otrzymujesz „3 marca”. Czy tego naprawdę chcesz?
Antony Hatchkins

2

Napisałem to alternatywne rozwiązanie, które działa dobrze dla mnie. Jest to przydatne, gdy chcesz obliczyć koniec umowy. Na przykład: początek = 15.01.2016, miesiące = 6, koniec = 2016-7-14 (tj. Ostatni dzień - 1):

<script>
function daysInMonth(year, month)
{
    return new Date(year, month + 1, 0).getDate();
}

function addMonths(date, months)
{
    var target_month = date.getMonth() + months;
    var year = date.getFullYear() + parseInt(target_month / 12);
    var month = target_month % 12;
    var day = date.getDate();
    var last_day = daysInMonth(year, month);
    if (day > last_day)
    {
        day = last_day;
    }
    var new_date = new Date(year, month, day);
    return new_date;
}

var endDate = addMonths(startDate, months);
</script>

Przykłady:

addMonths(new Date("2016-01-01"), 1); // 2016-01-31
addMonths(new Date("2016-01-01"), 2); // 2016-02-29 (2016 is a leap year)
addMonths(new Date("2016-01-01"), 13); // 2017-01-31
addMonths(new Date("2016-01-01"), 14); // 2017-02-28

0

Poniżej przedstawiono przykład obliczania przyszłej daty na podstawie danych wejściowych (data członkostwa) i dodanych miesięcy (miesiące członkostwa) za pomocą pól formularza.

Pole członkostwo w miesiącach ma domyślną wartość 0

Link wyzwalający (może być zdarzeniem onchange dołączonym do pola okresu członkostwa):

<a href="#" onclick="calculateMshipExp()"; return false;">Calculate Expiry Date</a>

function calculateMshipExp() {

var calcval = null;

var start_date = document.getElementById("membershipssignup_date").value;
var term = document.getElementById("membershipsmonths").value;  // Is text value

var set_start = start_date.split('/');  

var day = set_start[0];  
var month = (set_start[1] - 1);  // January is 0 so August (8th month) is 7
var year = set_start[2];
var datetime = new Date(year, month, day);
var newmonth = (month + parseInt(term));  // Must convert term to integer
var newdate = datetime.setMonth(newmonth);

newdate = new Date(newdate);
//alert(newdate);

day = newdate.getDate();
month = newdate.getMonth() + 1;
year = newdate.getFullYear();

// This is British date format. See below for US.
calcval = (((day <= 9) ? "0" + day : day) + "/" + ((month <= 9) ? "0" + month : month) + "/" + year);

// mm/dd/yyyy
calcval = (((month <= 9) ? "0" + month : month) + "/" + ((day <= 9) ? "0" + day : day) + "/" + year);

// Displays the new date in a <span id="memexp">[Date]</span> // Note: Must contain a value to replace eg. [Date]
document.getElementById("memexp").firstChild.data = calcval;

// Stores the new date in a <input type="hidden" id="membershipsexpiry_date" value="" name="membershipsexpiry_date"> for submission to database table
document.getElementById("membershipsexpiry_date").value = calcval;
}

0

Jak pokazują liczne złożone, brzydkie odpowiedzi, Daty i Czasy mogą być koszmarem dla programistów posługujących się dowolnym językiem. Moje podejście polega na konwertowaniu dat i wartości „delta t” na czas epoki (w ms), wykonaniu dowolnej arytmetyki, a następnie konwersji z powrotem na „czas ludzki”.

// Given a number of days, return a Date object
//   that many days in the future. 
function getFutureDate( days ) {

    // Convert 'days' to milliseconds
    var millies = 1000 * 60 * 60 * 24 * days;

    // Get the current date/time
    var todaysDate = new Date();

    // Get 'todaysDate' as Epoch Time, then add 'days' number of mSecs to it
    var futureMillies = todaysDate.getTime() + millies;

    // Use the Epoch time of the targeted future date to create
    //   a new Date object, and then return it.
    return new Date( futureMillies );
}

// Use case: get a Date that's 60 days from now.
var twoMonthsOut = getFutureDate( 60 );

Zostało to napisane dla nieco innego przypadku użycia, ale powinieneś być w stanie łatwo dostosować go do powiązanych zadań.

EDYCJA: Pełne źródło tutaj !


3
Bezużyteczna odpowiedź, ponieważ nie obsługuje miesięcy o różnej liczbie dni, oto jest pytanie.
Benubird

1
@Benubird - skoro tak uprzejmie o to poprosiłeś, przesłałem pełne źródło. Link w tym poście.
Dan Ahlquist,

Nie obejmuje to także czasu letniego, w którym nie wszystkie dni trwają 24 godziny (lub 8,64e7 milisekund).
RobG

Nie, nie ma. DST jest problemem dotyczącym lokalizacji.
Dan Ahlquist

0

Wszystko to wydaje się zbyt skomplikowane i myślę, że zaczyna się debata na temat tego, co dokładnie oznacza „miesiąc”. Czy to znaczy 30 dni? Czy to oznacza od 1 do 1? Od ostatniego dnia do ostatniego dnia?

Jeśli to drugie, to dodanie miesiąca do 27 lutego spowoduje przejście do 27 marca, ale dodanie miesiąca do 28 lutego spowoduje przejście do 31 marca (z wyjątkiem lat przestępnych, w których dochodzi do 28 marca). Odejmując miesiąc od 30 marca dostajesz ... 27 lutego? Kto wie...

Dla tych, którzy szukają prostego rozwiązania, wystarczy dodać milisekundy i gotowe.

function getDatePlusDays(dt, days) {
  return new Date(dt.getTime() + (days * 86400000));
}

lub

Date.prototype.addDays = function(days) {
  this = new Date(this.getTime() + (days * 86400000));
};

Wydaje mi się jasne, że „dodanie X miesięcy” oznacza wzrost miesiąca o X. To prawda, że ​​twoja odpowiedź zawiera mniej algorytmicznych kroków do kodowania, ale niestety uważam, że dokładność przebija prostotę. Gdy ludzie szukają odpowiedzi, chcą, aby „31 lipca 2016 r.” Zmieniło się w „31 sierpnia 2016 r.”. I to by tego nie zrobiło. Poza tym, jeśli naprawdę zależy Ci na prostocie kosztem dokładności, dlaczego nie zostać d.setMonth(d.getMonth()+1)? To nie jest nawet najprostszy pomysł.
Alexander Bird

Nie obejmuje to także czasu letniego, w którym nie wszystkie dni trwają 24 godziny (lub 8,64e7 milisekund).
RobG

-1
addDateMonate : function( pDatum, pAnzahlMonate )
{
    if ( pDatum === undefined )
    {
        return undefined;
    }

    if ( pAnzahlMonate === undefined )
    {
        return pDatum;
    }

    var vv = new Date();

    var jahr = pDatum.getFullYear();
    var monat = pDatum.getMonth() + 1;
    var tag = pDatum.getDate();

    var add_monate_total = Math.abs( Number( pAnzahlMonate ) );

    var add_jahre = Number( Math.floor( add_monate_total / 12.0 ) );
    var add_monate_rest = Number( add_monate_total - ( add_jahre * 12.0 ) );

    if ( Number( pAnzahlMonate ) > 0 )
    {
        jahr += add_jahre;
        monat += add_monate_rest;

        if ( monat > 12 )
        {
            jahr += 1;
            monat -= 12;
        }
    }
    else if ( Number( pAnzahlMonate ) < 0 )
    {
        jahr -= add_jahre;
        monat -= add_monate_rest;

        if ( monat <= 0 )
        {
            jahr = jahr - 1;
            monat = 12 + monat;
        }
    }

    if ( ( Number( monat ) === 2 ) && ( Number( tag ) === 29 ) )
    {
        if ( ( ( Number( jahr ) % 400 ) === 0 ) || ( ( Number( jahr ) % 100 ) > 0 ) && ( ( Number( jahr ) % 4 ) === 0 ) )
        {
            tag = 29;
        }
        else
        {
            tag = 28;
        }
    }

    return new Date( jahr, monat - 1, tag );
}


testAddMonate : function( pDatum , pAnzahlMonate )
{
    var datum_js = fkDatum.getDateAusTTMMJJJJ( pDatum );
    var ergebnis = fkDatum.addDateMonate( datum_js, pAnzahlMonate );

    app.log( "addDateMonate( \"" + pDatum + "\", " + pAnzahlMonate + " ) = \"" + fkDatum.getStringAusDate( ergebnis ) + "\"" );
},


test1 : function()
{
    app.testAddMonate( "15.06.2010",    10 );
    app.testAddMonate( "15.06.2010",   -10 );
    app.testAddMonate( "15.06.2010",    37 );
    app.testAddMonate( "15.06.2010",   -37 );
    app.testAddMonate( "15.06.2010",  1234 );
    app.testAddMonate( "15.06.2010", -1234 );
    app.testAddMonate( "15.06.2010",  5620 );
    app.testAddMonate( "15.06.2010", -5120 );

}

1
Uważam to za bardzo przydatne, gdy większość kodu jest w języku angielskim, a zmienne w języku Deutsch. ;)
Bernhard Hofmann

-1

Czasami przydatne jest utworzenie daty przez jednego operatora, jak w parametrach BIRT

Zrobiłem 1 miesiąc z powrotem z:

new Date(new Date().setMonth(new Date().getMonth()-1));   

-3
var a=new Date();
a.setDate(a.getDate()+5);

Zgodnie z powyższą metodą możesz dodać miesiąc do Datedziałania.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.