SObject Datetime Precision and Testing
This week I hit one of those little oddities in Apex that I don’t think I’ve seen mentioned before. Most likely this won’t impact you in your day-to-day usage of the platform. I would not have even noticed apart from my failing tests. So what did I encounter?
We like to think that when we save an sObject to the database then read it back we get the same data that was saved. And in general we do. But it appears that with Datetimes this is not necessarily the case. Broadly speaking we get the same value back. Certainly at the level of precision that most people work at. But actually we lose precision when we save.
Before I go further I only noticed because my test was actually writing to the database and reading the record back. This is not best practice. I was only doing this because I had not implemented any form of database wrapper to allow me to mock out the database from my tests.
I was testing a method that updates an SObject setting a Datetime field to the current date and time. My test structure was something like this
@IsTest
private static void runTest() {
// Given
MyObject__c obj = new MyObject__c();
insert obj;
Id objId = obj.Id;
Datetime beforeTest = System.now();
// When
myMethod(objId);
// Then
Datetime afterTest = System.now();
MyObject__c afterObject = [Select DateField__c from MyObject__c where Id=:objId];
System.assert(afterObject.DateField__c >= beforeTest && afterObject.DateField__c <= beforeTest);
}
To my surprise this failed. Doing some debugging the date field was being set and a System.Debug printed out all thee datetimes as being the same. Not too surprising. I would only expect them to vary by a few milliseconds. So using the getTime() method to get the number of milliseconds since the start of the Unix epoch I again debugged.
beforeTest and afterTest were as expected. However the date field was before beforeTest! How is this possible. Well I ran the test a few times before I noticed the pattern. The date field value always ended in 000. So we’d have something like (numbers shortened for brevity):
beforeTest: 198818123
dateField: 198818000
afterTest: 198818456
So saving the DateTime was losing the hundreds of millisecond precision of datetime. In this case storing a value into an SObject and reading it back will result in a different value!
Anyway this is pretty easy to fix in a test by similarly losing the precision from our before and after times as below
@IsTest
private static void runTest() {
// Given
MyObject__c obj = new MyObject__c();
insert obj;
Id objId = obj.Id;
Long beforeTest = (System.now().getTime()/1000)*1000;
// When
myMethod(objId);
// Then
Long afterTest = (System.now().getTime()/1000)*1000;
MyObject__c afterObject = [Select DateField__c from MyObject__c where Id=:objId];
Long testValue = afterObject.DateField__c.getTime();
System.assert(testValue >= beforeTest && testValue <= beforeTest);
}