1

Datetime conversion of a custom datetime field to a date value doesn't work correctly. This behavior isn't explained in Salesforce's documentation as far as I know.

  1. Set your org & user to Pacific time zone.
  2. Create a new Datetime field, ExpirationDate__c on Product2.
  3. On an existing or new product, populate the new field with today's date or another date and set the time to 7:00 pm.
  4. Run the following code in the dev console.
Product2 prod = [SELECT id, ExpirationDate__c FROM product2 WHERE ExpirationDate__c != null limit 1];
Date dd = prod.ExpirationDate__c.date();
System.debug(LoggingLevel.ERROR, prod.ExpirationDate__c);
System.debug(LoggingLevel.ERROR, dd);
  1. In the debug log, note the date difference after converting the datetime value to date value in the log.

Please disregard what's written below:

Can anyone explain why I'm getting a date in the past when trying to convert today's date in datetime format to date format?

Datetime d = system.today();
System.debug(LoggingLevel.ERROR, JSON.serialize(d));
Date d2 = d.date();
System.debug(LoggingLevel.ERROR, JSON.serialize(d2));
Date d3 = Date.newinstance(d2.year(), d2.month(), d2.day());
System.debug(LoggingLevel.ERROR, JSON.serialize(d3));
String dstr = JSON.serialize(d3).substring(1, 11);
System.debug(LoggingLevel.ERROR, dstr);
Date d4 = (Date)JSON.deserializeStrict(dstr, Date.class);
System.debug(LoggingLevel.ERROR, JSON.serialize(d4));
3

This is a combination of rounding and time zone coordinates. JSON.deserialize converts to GMT by default, and taking just the date out of a datetime "rounds down" to midnight. This means that, unless you're in GMT+00:00 or East, you end up rounding down towards the past. Consider this example:

Decimal value1 = 4.9;
Integer value2 = value1.intValue();
Decimal value3 = value2 - 0.1;
Integer value4 = value3.intValue();

value4 now has a value of 3. The -0.1 represents the time lost when you adjust a GMT to a local time when using a Western GMT (GMT-01:00 or West).

Today(), year(), month(), newInstance(), and day() all return the value in the local timezone, while now(), yearGMT(), monthGMT(), newInstanceGMT(), and dayGMT() all return the value in GMT. As such, it's easy to accidentally "round down" if you're not cognizant of the fact that you're converting back and forth.

I suggest you take some time to study the documentation to learn the differences between them, as dates and time zones are actually really complicated to get just right.

4
  • I was only using serialization/deserialization as a desperate attempt to convert datetime to date in the correct timezone. I have read the documentation on Date and Datetime, and it doesn't state anywhere as far as I know that converting datetime to date would result in timezone issues. – YXY Aug 7 '20 at 22:16
  • My goal was to convert a datetime value of an custom sobject to date value and assign that result to a date field on another sobject. Is there a straightforward way of doing this without coding your own timezone adjustments? – YXY Aug 7 '20 at 22:25
  • @YXY Simply assigning the DateTime value directly from System.now() to the date field should suffice, I would think. It's been a while since I've had to do this, but I recall that working in the past. – sfdcfox Aug 7 '20 at 22:52
  • My initial example was bad. I'm not disagreeing with your answer. I was unaware of the implicit type conversion and serialization/deserialization issues. Regardless, your answer did help me dig deeper so I upvoted it. I've updated my post to include a better example. – YXY Aug 10 '20 at 20:17
1

This is caused in the original example (now changed) by the use of implicit conversion of System.today(), a Date, to a Datetime. In this case the date is converted to a Datetime at midnight in GMT, so 2020/08/07 becomes 2020/08/07T00:00:00Z.

The implicit conversion is here:

Datetime d = system.today();

However, the system automatically converts date/times to the contextual user's time zone so if the user's time zone is "west" of GMT (so GMT-nn:00 rather than GMT+nn:00), let's say GMT-04:00, Veneszuela Time, then converting 2020/08/07T00:00:00Z to that timezone results in 2020/08/06T20:00:00 GMT-04:00.

Converting a "midnight in GMT" to the user's time zone has therefore resulted in a time on the day before and converting to a date at that point is too late.

To resolve this correctly you must:

  1. Use System.now().date() to ensure that you take the Date of an existing Datetime OR
  2. Use System.today() directly as a Date.
  3. ALWAYS avoid implicit conversion from Datetime to Date. It is a sure-fire way to introduce hidden bugs like this.

Note that only Datetime has a time zone; a Date does not.

3
  • I was just using system.today() and casting it to datetime as an example. My goal was to convert a datetime value to a date value. I'm encountering an issue where I would get the wrong date value if I try to convert a sobject datetime field value to a date value. It doesn't make sense that it won't work since the record creation and the datetime to date conversion is happening in my timezone. – YXY Aug 7 '20 at 22:32
  • My answer is accurate against your example since you used System.today(), a Date, to assign to a Datetime which causes a UTC/GMT date time to be created. If you change your example to use System.now() instead I will delete my answer. – Phil W Aug 8 '20 at 8:01
  • My initial example was bad. I'm not disagreeing with your answer. I was unaware of the implicit type conversion and serialization/deserialization issues. Regardless, your answer did help me dig deeper so I upvoted it. I've updated my post to include a better example. – YXY Aug 10 '20 at 20:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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