What do you do when you want to calculate a formula-like field but a regular formula won't work?
Salesforce CRM's formulas handle dates very well. If you want to enter a date value and have formula fields display, for instance, mydate__c + 21 days, that's simple. Just use mydate__c + 21
.
Side note: If you try going the long way around and use DATE( YEAR( mydate__c ), MONTH( mydate__c ), DAY( mydate__c ) + 21 )
and mydate__c = 09/17/2010, Salesforce returns #Error! because there's no date 09/38/2010. Similarly, adding three months to a date like 1/31/2010 will also give an error. More about this in a future post.
DateTime fields are like Date fields, but they include... wait for it... a time component (and can be created in the running user's local time zone or in GMT).
Here's a use-case for a DateTime formula field:
A photography studio schedules photo shoots, and different packages include different durations. Similarly, we could use a hair salon which offers different services, each with a different duration, a dentist... you get the idea.
Requirements:
- Enter a DateTime for an appointment start time (
starttime__c
) - Enter a duration (though in a production system, I'd include a value on the
Product2
sObject, we'll just enter a value here) (minutes__c
) - Display a read-only DateTime field with the end time (
endtime__c
) - The end time must be read-only to all users, like any formula field
Here's what won't work:
- A formula field won't work because there are no MINUTE(), HOUR(), SECOND() formula functions
- Workflow won't work because it depends on formulas to fill new values for date/datetime fields
That leaves Apex. First, the configuration:
- Create DateTime field
starttime__c
- Create DateTime field
endtime__c
- Set
endtime__c
field-level security to Read-Only for all profiles - Create Number (18,0) field
minutes__c
- Create a trigger on the sobject
Here's the trigger:
trigger timeTrigger on TestObject__c (before insert, before update) { for (TestObject__c t : Trigger.New){ if(t.StartTime__c != null && t.minutes__c != null){ datetime myDateT = t.StartTime__c; double d = t.minutes__c; Integer shootmins = d.intValue(); if(mydateT != null && shootmins != null) t.EndTime__c = myDateT.addminutes(integer.valueof(shootmins)); } } }
Regular readers will note that I do usually split triggers into a trigger and a class, but I've not done so here purely for the sake of brevity.
Here's the test code:
public without sharing class shootTimesTriggerTest { private static testMethod void ShootCalculateEndTime_PositiveTestCases() { TestObject__c to; TestObject__c l; test.starttest(); l = new TestObject__c (name = 'test'); datetime myDateTime = datetime.newInstance(2008, 12, 1, 12, 30, 2); l.StartTime__c = myDateTime; l.minutes__c = 90; insert l; to = [SELECT id, EndTime__c FROM TestObject__c WHERE id = :l.id]; datetime newDateTime = datetime.newInstance(2008, 12, 1, 14, 0, 2); system.assertequals(to.EndTime__c, newDateTime); l.minutes__c = 45; update l; to = [SELECT id, EndTime__c FROM TestObject__c WHERE id = :l.id]; newDateTime = datetime.newInstance(2008, 12, 1, 13, 15, 2); system.assertequals(to.EndTime__c, newDateTime); test.stoptest(); } private static testMethod void OppCalculateEndTime_NegativeTestCases() { test.starttest(); TestObject__c l = new TestObject__c (name = 'test'); l.minutes__c = null; insert l; system.assertequals(l.EndTime__c, null); test.stoptest(); } }
A few points about how this works:
- Triggers run in System mode, so they don't respect field-level security. Thus, we can set a field to read-only for all profiles, and the EndTime__c field will still be updated.
- The test code runs in System mode as well, avoiding any potential problems if the field were set to invisible to a profile and we used System.RunAs() to test for various profiles.
- Although I'm not a fan of using SOQL queries this often, I used these in the interest of saving time. Keep in mind that if you had quite a few queries in your regular code, adding these two might put you over the limit, so use queries sparingly!
- This is the only way I know of to add minutes to a DateTime.
Did I miss anything? Please let me know in the comments.
Michael Lee says
I managed to get this to work with a formula field — though it was a little problematic and filled with a little guess work when I added the duration.
Manual_Time__c
+ ( (Duration__c
) / (1439))I’m aware that the fraction seems a little off — 1440 is the number of minutes in one day — but experimentation led me to change the result just a little bit, and this gave me what I wanted (since all of my durations were under one day)
I imagine that the Apex code is a bit more bullet proof than the way that I used a formula field.
David Schach says
That’s a great point. I hadn’t considered adding fractions of days to a DateTime, but it does work. Good call.
Michael Barrow says
I recently had the exact same need and had contacted SFDC support, and they told me to use the fraction addition approach as well. It works like a charm for adding or subtracting hours to adjust Date/Time formula fields for display purposes to adjust for timezone differences.
Regards,
Michael