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 opened a case with Salesforce (#08078994), but their Support Engineer has claimed repeatedly that, "I have reconfirmed and there is no issue with Date Time methods. The issue which you are notifying to us is related to coding done by you."

Am I really doing something wrong here? I'm trying to show that when I use JSON.createParser() to build a DateTime value, it's garbled. The formatGmt() method shows the correct time, but hourGmt() returns a different value (the local hour instead of GMT).

The code I wrote for this StackExchange answer to my question about parsing an ISO 8601 timestamp was affected by the bug.

String iso8601 = '2012-07-31T11:22:33.444Z';
JSONParser parser = JSON.createParser( '{"t":"' + iso8601 + '"}');
parser.nextToken();
parser.nextValue();
DateTime dt1 = parser.getDateTimeValue();

// dt2 should be identical to dt1
DateTime dt2 = DateTime.newInstance( dt1.getTime());
String timeformat = 'M/d/yyyy h:mm a';

System.Debug( iso8601 + ' parsed as ' + dt1);

System.Assert( dt1.format() == dt2.format()); // localtime is the same
System.Assert( dt1.getTime() == dt2.getTime()); // same number of milliseconds since epoch
System.Assert( dt1.formatGmt( timeformat) == dt2.formatGmt( timeformat));
System.Assert( dt1.hourGmt() == dt2.hourGmt()); // assertion fails when it should not

EDIT

This is still broken in Winter '12, I have not had an opportunity to test it in Spring '13 yet. Here's a simplified example, that eliminates the dual DateTime objects and uses a single call to JSON.deserialize:

String iso8601 = '2012-07-31T11:22:33.000Z';
DateTime dt1 = (DateTime) JSON.deserialize( '"' + iso8601 + '"', DateTime.class);
String timeformat = 'M/d/yyyy h:mm a';
System.Debug( iso8601 + ' parsed as ' + dt1);
System.assertEquals( '7/31/2012 11:22 AM', dt1.formatGmt( timeformat), 'not parsed correctly');
System.assertEquals( 11, dt1.hourGmt(), 'hourGmt should be 11');

And the output:

USER_DEBUG|[7]|DEBUG|2012-07-31T11:22:33.000Z parsed as 2012-07-31 11:22:33
EXCEPTION_THROWN|[10]|System.AssertException: Assertion Failed: hourGmt should be 11: Expected: 11, Actual: 4

I think the key here is that formatGmt() prints an hour of 11, but hourGmt() returns 4, the same as hour().

And oddly, I can kind of "reset" the DateTime object using this construct:

dt1 = DateTime.newInstance( dt1.getTime());

If I add that before the assert in my test code, it passes.

EDIT

Still broken in Winter '13.

share|improve this question
1  
Can you include the debug output? –  metadaddy Oct 2 '12 at 18:52
    
The answer to this question might be helpful: salesforce.stackexchange.com/q/1667/72. –  Matt K Oct 2 '12 at 19:13
3  
I vote that it's a bug. If getTime matches then all the GMT methods should certainly produce the same results. –  ca_peterson Oct 2 '12 at 19:17
    
I think @ca_peterson has said it well. If formatGmt() and getTime() (which returns milliseconds since 1/1/1970 GMT) return the same values, then why doesn't hourGmt()? –  tomlogic Oct 3 '12 at 2:49
add comment

2 Answers 2

up vote 8 down vote accepted

Update for subsequent question edit

Here is what I think is happening:

  1. The DateTime dt1 returned by JSONParser.getDateTimeValue() internally stores values based on the users time zone offset. In my case, and I suspect yours, this is currently GMT - 7. This is why calls to dt1.dateGmt() include an offset.

  2. The time component that is stored internally in dt1 has been adjusted to the users time zone as well. So 11 AM becomes 4 AM due to the -7 hour offset.

  3. Calls to dt1.hourGmt() only take into account the time portion of the DateTime, and hence return 4 rather than 11. The other missing 7 hours are stored in the date portion of the DateTime (which can be seen in calls to dt1.dateGMT()).

An interesting experiment would be to alter the active users time zone to see if the result changes as expected.

The more I read over this the more I concur that this is a Salesforce bug in the hourGMT() method.


The DateTime dt1 returned by JSONParser.getDateTimeValue() has internally been adjusted to the active users time zone. This offset is tracked against the date component and the time component has been adjusted accordingly. For my test user on GMT-7 this only left 4 hours in the time component.

As Mike notes in his answer, DateTime.newInstance(Long) returns a DateTime in the GMT time zone.

Constructs a DateTime and initializes it to represent the specified number of milliseconds since January 1, 1970, 00:00:00 GMT. The returned date is in the GMT time zone.

Internally the dt2 date isn't tracking the users time zone offset, so the time component has the full 11 hours.


I tried modifying your example to explicitly create dt2 using the GMT values.

String iso8601 = '2012-07-31T11:22:33.000Z'; // NB: Removed milliseconds as they weren't available in newInstanceGmt
JSONParser parser = JSON.createParser( '{"t":"' + iso8601 + '"}');
parser.nextToken();
parser.nextValue();
DateTime dt1 = parser.getDateTimeValue();

// dt2 should be identical to dt1
//DateTime dt2 = DateTime.newInstance( dt1.getTime());
//DateTime dt2= DateTime.newInstanceGmt(dt1.dateGMT(), dt1.timeGmt());
DateTime dt2= DateTime.newInstanceGmt(2012, 07, 31, 11, 22, 33);

String timeformat = 'M/d/yyyy h:mm a';

//2012-07-31T11:22:33.000Z parsed as 2012-07-31 11:22:33    
System.Debug( iso8601 + ' parsed as ' + dt1);

// The date values differ by 7 hours when tested with a user on PDT
System.Debug(dt1.dateGMT() + ', ' + dt1.timeGmt()); //2012-07-31 07:00:00, 04:22:33.000Z
System.Debug(dt2.dateGMT() + ', ' + dt2.timeGmt()); //2012-07-31 00:00:00, 11:22:33.000Z

System.assertEquals( dt1.format(), dt2.format(), 'localtime is the same');
System.assertEquals( dt1.getTime(), dt2.getTime(), 'same number of milliseconds since epoch');
System.assertEquals( dt1.formatGmt( timeformat), dt2.formatGmt( timeformat));
System.assertEquals( dt1.hourGmt(), dt2.hourGmt(), 'assertion fails when it should not');

The final assertion fails with the message:

System.AssertException: Assertion Failed: assertion fails when it should not: Expected: 4, Actual: 11

Note the output of the latter two debug lines:

14:44:27.257 (257267000)|USER_DEBUG|[15]|DEBUG|2012-07-31 07:00:00, 04:22:33.000Z 14:44:27.257 (257435000)|USER_DEBUG|[16]|DEBUG|2012-07-31 00:00:00, 11:22:33.000Z

There is a difference in the internal representation of the DateTime. dt1 has a 7 hour offset in the GMT date and only 4 hours in the time component.

In contrast, dt2 has 0 hours in the GMT date and 11 hours in the time component.

The Salesforce user that executed the code had:

Time Zone (GMT-07:00) Pacific Daylight Time (America/Los_Angeles)

share|improve this answer
    
To be clear, are you arguing this is a bug, or is "working as designed"? –  ca_peterson Oct 2 '12 at 22:40
    
I would argue it is working as designed and we just need to be cognizant that some methods internally convert the data to local timezones. It looks like that to be sure one should always use the GMT versions except for data presentation. –  Mike Chale Oct 2 '12 at 23:47
    
I'd say it is working as designed and that comparing hourGmt() between a DateTime that is working to GMT internally and another that is working to a time zone offset internally won't produce the same result. If there is a bug anywhere it is in hourGmt(), as it is only looking at the time component and not the offset on the date. –  Daniel Ballinger Oct 3 '12 at 0:24
    
Very curious, and somewhat confusing that dateGmt() includes hours -- that's the GMT time for "midnight 2012-07-31 localtime" and doesn't seem right. Please see my updated question with a (hopefully) simpler and clearer example of the problem. I think that JSONParser.getDateTimeValue() isn't setting the timezone of the DateTime object correctly. –  tomlogic Oct 3 '12 at 3:09
    
Made another update to my question, and still feeling like this is a bug that Salesforce should fix. –  tomlogic Jan 30 '13 at 18:03
add comment

I think the problem is how you create dt2. From the documentation for newInstance:

Constructs a DateTime from the specified date and time in the local time zone. The returned date is in the GMT time zone.

I suspect that even though getTime() returns the value in GMT that newInstance() is first converting it to local time. If you know the value to be parsed is in GMT you could try newInstanceGmt().

share|improve this answer
    
The version of newInstance() in this case, is the one that takes a single Long: "Constructs a DateTime and initializes it to represent the specified number of milliseconds since January 1, 1970, 00:00:00 GMT. The returned date is in the GMT time zone." –  tomlogic Oct 3 '12 at 3:10
add comment

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.