DateTime.addMonths skips a month (from feb to mar)

Not an answer, but a clue, it seems it has to do something related to timezone and summer time. Same code in my BST org gives a different response.

Date startDate = Date.parse('30/05/2019');
Date endDate = startDate.addYears(1);

Integer durationInMonths = StartDate.MonthsBetween(EndDate);

for (Integer i = 0; i <= durationInMonths; i++) {
    DateTime m = DateTime.newInstance( StartDate.Year(), StartDate.Month(), 1);

    m = m.addMonths(i);

    System.debug(i);
    System.debug(m.Day() + ' / ' + m.Month() + ' / ' + String.valueOf(m.year()));
}

Debug :

17:17:14.14 (16267512)|USER_DEBUG|[11]|DEBUG|0
17:17:14.14 (16351252)|USER_DEBUG|[12]|DEBUG|1 / 5 / 2019
17:17:14.14 (16404681)|USER_DEBUG|[11]|DEBUG|1
17:17:14.14 (16434289)|USER_DEBUG|[12]|DEBUG|31 / 5 / 2019
17:17:14.14 (16453217)|USER_DEBUG|[11]|DEBUG|2
17:17:14.14 (16480510)|USER_DEBUG|[12]|DEBUG|1 / 7 / 2019
17:17:14.14 (16498216)|USER_DEBUG|[11]|DEBUG|3
17:17:14.14 (16524565)|USER_DEBUG|[12]|DEBUG|31 / 7 / 2019
17:17:14.14 (16542405)|USER_DEBUG|[11]|DEBUG|4
17:17:14.14 (16568781)|USER_DEBUG|[12]|DEBUG|31 / 8 / 2019
17:17:14.14 (16592710)|USER_DEBUG|[11]|DEBUG|5
17:17:14.14 (16619134)|USER_DEBUG|[12]|DEBUG|1 / 10 / 2019
17:17:14.14 (16636368)|USER_DEBUG|[11]|DEBUG|6
17:17:14.14 (16663050)|USER_DEBUG|[12]|DEBUG|30 / 10 / 2019
17:17:14.14 (16680525)|USER_DEBUG|[11]|DEBUG|7
17:17:14.14 (16706633)|USER_DEBUG|[12]|DEBUG|30 / 11 / 2019
17:17:14.14 (16723573)|USER_DEBUG|[11]|DEBUG|8
17:17:14.14 (16750065)|USER_DEBUG|[12]|DEBUG|30 / 12 / 2019
17:17:14.14 (16767156)|USER_DEBUG|[11]|DEBUG|9
17:17:14.14 (16793676)|USER_DEBUG|[12]|DEBUG|30 / 1 / 2020
17:17:14.14 (16810795)|USER_DEBUG|[11]|DEBUG|10
17:17:14.14 (16837147)|USER_DEBUG|[12]|DEBUG|29 / 2 / 2020
17:17:14.14 (16854164)|USER_DEBUG|[11]|DEBUG|11
17:17:14.14 (16880438)|USER_DEBUG|[12]|DEBUG|31 / 3 / 2020
17:17:14.14 (16897409)|USER_DEBUG|[11]|DEBUG|12
17:17:14.14 (16923463)|USER_DEBUG|[12]|DEBUG|1 / 5 / 2020

EDIT:

When you do

Date startDate = Date.parse('30/05/2019');

System.debug(startDate); //Prints 2019-05-30 00:00:00

Which is SummerTime/DaylightSaving 1 hour ahead. So Actual DateTime is 2019-05-29 23:00:00

Am not sure if there can be more than 1 hour in DST, so on safe side I have added 2 hours as offset and it gives me correct response.

Datetime startDate = Datetime.newInstance(2019,05,30 , 2, 0, 0);


for(Integer i =0 ;i < 12;i++){
    startDate = startDate.addMonths(1);
    System.debug(startDate);
}

Debug :

17:47:17.14 (15747813)|USER_DEBUG|[6]|DEBUG|2019-06-30 01:00:00
17:47:17.14 (15781585)|USER_DEBUG|[6]|DEBUG|2019-07-30 01:00:00
17:47:17.14 (15793019)|USER_DEBUG|[6]|DEBUG|2019-08-30 01:00:00
17:47:17.14 (15804331)|USER_DEBUG|[6]|DEBUG|2019-09-30 01:00:00
17:47:17.14 (15817668)|USER_DEBUG|[6]|DEBUG|2019-10-30 01:00:00
17:47:17.14 (15827719)|USER_DEBUG|[6]|DEBUG|2019-11-30 01:00:00
17:47:17.14 (15837722)|USER_DEBUG|[6]|DEBUG|2019-12-30 01:00:00
17:47:17.14 (15848084)|USER_DEBUG|[6]|DEBUG|2020-01-30 01:00:00
17:47:17.14 (15862108)|USER_DEBUG|[6]|DEBUG|2020-02-29 01:00:00
17:47:17.14 (15874963)|USER_DEBUG|[6]|DEBUG|2020-03-29 01:00:00
17:47:17.14 (15884811)|USER_DEBUG|[6]|DEBUG|2020-04-29 01:00:00
17:47:17.14 (15894518)|USER_DEBUG|[6]|DEBUG|2020-05-29 01:00:00

Easy solution that I want to post here - don't use addMonths. It looks like it just adds 30 days to the date, which isn't a valid solution in the slightest and produces problems like above. My mistake was assuming that addMonths wouldn't modify the day part of the date, which it totally will!

Instead, just modify the month given to the constructor.

Date startDate = Date.parse('5/30/2019');
Date endDate = startDate.addYears(1);

Integer durationInMonths = StartDate.MonthsBetween(EndDate);

for (Integer i = 0; i <= durationInMonths; i++) {
    DateTime m = DateTime.newInstance( StartDate.Year(), StartDate.Month() + i, 1);

    System.debug(i);
    System.debug(m.Day() + ' / ' + m.Month() + ' / ' + String.valueOf(m.year()));                
}

This will output a date with the first day and the given month.

enter image description here


Other solution (@Sebastian Kessel) - just use a Date instead of DateTime:

Date startDate = Date.parse('5/30/2019');
Date endDate = startDate.addYears(1);

Integer durationInMonths = StartDate.MonthsBetween(EndDate);

for (Integer i = 0; i <= durationInMonths; i++) {
    Date m = Date.newInstance( StartDate.Year(), StartDate.Month(), 1);

    m = m.addMonths(i);               
}

Tags:

Date

Apex