Take the 2-minute tour ×
Salesforce Stack Exchange is a question and answer site for Salesforce administrators, implementation experts, developers and anybody in-between. It's 100% free, no registration required.

I am generating a set of date-based keys by looping over a start date and adding successive months to the initial Datetime object.

I seem to have run into a bug in the format() method of the Datetime object that is proving hard to work around. It appears that the combination of addMonths() and format() causes the month of December to appear as month number 11 rather than 12. This is show in the example below:

Integer repeat = 4;

//build the key,
for (Integer i = 0; i < repeat; i++ ) {
    String key = startDateTime.addMonths(i).format('yyyy-MM','America/Los_Angeles');

    System.debug(LoggingLevel.DEBUG, 'KEY: ' + key);
    //for a startDateTime of 10/01/2014 and a repeat of 4 months, the output will be:
    //KEY: 2014-10
    //KEY: 2014-11
    //KEY: 2014-11
    //KEY: 2015-01

}

As you can see, November is repeated and December is missed.

If anyone knows why this occurring - perhaps my format string is incorrect? Or if anyone knows a way around this issue, it would be very helpful.

share|improve this question
    
I tried reproducing this, but it appears to be working correctly for me. I changed it to system.now().addMonths... and set repeat = 24 (just to get a bigger set) and all months appear correct. I'm running this in a Summer '14 sandbox. –  jongpie Jul 30 at 18:43
    
Hmm, me too. I'll go and see if there are any other conditions that might be causing it. One clue - if I just spit out the string representing the date by appending the Datetime to a string and outputting, the month is correct. It's just the format method (and other ways of getting the month - eg Datetime.month()) that is wrong. –  CaspNZ Jul 30 at 18:59
1  
What is the local time zone of the Salesforce user executing the code? Are they on Pacific time? –  Daniel Ballinger Jul 30 at 21:21
    
Yes they are. However, I don't think that is the issue, as the only boundary condition is the month, not the day of the Datetime. Actually, I wonder if it could be the Datetime initially being say 10/1 and successive additions don't quite push it over the next month boundary (I've tried with and without the timezone modifier). I still think it's a bug, but your comments have been useful - successive additions could be the key. –  CaspNZ Jul 30 at 21:26
    
I tried the same test with a user where the TimeZoneSidKey was 'Pacific/Auckland' and I couldn't reproduce it. Is there a Daylight savings transition in that Date range in Pacific time? I've seen issues with addDays() in relation to DST changes. See Fix addDays() on the DateTime type –  Daniel Ballinger Jul 30 at 21:43

2 Answers 2

up vote 3 down vote accepted

I suspect you are dealing with a Timezone Daylight Savings Time transition in the (GMT-07:00) Pacific Daylight Time (America/Tijuana) Timezone (Pacific) that occurs in that date range.

On Sunday the 2nd of November they will transition from PDT to PST. Source So they will go from UTC-7h to UTC-8h.

The DateTime.month() and format() for DateTime will be falling back a month because the addMonths() method won't be adjusting for changes in daylight savings. Since you are using a DateTime on midnight, the shift of an hour is sufficient to move you off by one month.

Here is some revised anonymous Apex to execute with a used where the TimeZoneSidKey is America/Tijuana.

DateTime startDateTime = DateTime.newInstance(2014, 11, 1);

Integer repeat = 3;

//build the key,
for (Integer i = 0; i < repeat; i++ ) {
    DateTime incrementedDateTime = startDateTime.addMonths(i);

    String key = incrementedDateTime.format('yyyy-MM','America/Los_Angeles');

    System.debug(LoggingLevel.DEBUG, i + ' KEY: ' + key + 
        '\n Month: ' + incrementedDateTime.month() +
        '\n Month GMT: ' + incrementedDateTime.monthGmt() +
        '\n GMT: ' + incrementedDateTime.dateGmt() + 
        '\n incrementedDateTime:' + incrementedDateTime);

}

This will output:

0 KEY: 2014-11 Month: 11 Month GMT: 11
GMT: 2014-11-01 00:00:00 incrementedDateTime:2014-11-01 07:00:00

1 KEY: 2014-11 Month: 11 Month GMT: 12
GMT: 2014-12-01 00:00:00 incrementedDateTime:2014-12-01 07:00:00

2 KEY: 2014-12 Month: 12 Month GMT: 1
GMT: 2015-01-01 00:00:00 incrementedDateTime:2015-01-01 07:00:00

Note how the output from incrementedDateTime is showing the hour as "07:00:00". The addMonths() method hasn't shifted the hours to "08:00:00" with the DST change.

I've seen similar issues with addDays() in relation to DST changes. See Fix addDays() on the DateTime type.

share|improve this answer
    
Wow, nice work there. That was a tricky one. Thanks again. –  CaspNZ Jul 30 at 22:56

Edit

I just noticed something. Because strings are immutable, try changing your code as follows to see if it makes a difference:

Integer repeat = 4;
String key;

//build the key,
for (Integer i = 0; i < repeat; i++ ) {
    key = startDateTime.addMonths(i).format('yyyy-MM','America/Los_Angeles');

    System.debug(LoggingLevel.DEBUG, 'KEY: ' + key);
    //for a startDateTime of 10/01/2014 and a repeat of 4 months, the output will be:
    //KEY: 2014-10
    //KEY: 2014-11
    //KEY: 2014-11
    //KEY: 2015-01

}

End Edit

From the APEX Developer's Guide:

format() Converts the date to the local time zone and returns the converted date as a formatted string using the locale of the context user. If the time zone cannot be determined, GMT is used.

format(String, String) Converts the date to the specified time zone and returns the converted date as a string using the supplied Java simple date format. If the supplied time zone is not in the correct format, GMT is used.

When comparing the two definitions, it seems implicit that at a minimum, a year, month and day is expected; that only the time is optional. Perhaps adding a default day to your StartDateTime, then removing it later when your results are displayed will resolve your issue.

Note:

  • I've not tested this and have only looked at the reference material to see if anything popped out.
  • I didn't have time to look at the Java simple data formats to see if year and month only was a supported date format as you're using it. It seem that finding those things in the docs is incredibly difficult every time I try. Hopefully Oracle will do something to make them more user friendly some day. Even when you do, SF doesn't always support what it says it does.
share|improve this answer
    
The Datetime I'm using contains a full date with year, month and day specified. The output format specifier only contains year and month, but shouldn't need a day parameter. I'll try the Immutability change that you suggested. –  CaspNZ Jul 30 at 19:37

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.