I am simply trying to utilize different BusinessHours and the BusinessHours class to determine if a given date is within x number of business days from another date. The following are true:
- The hour does not come into play
- Business hours are NOT set to 24 hours
- No every day is a business day (sun and mon are not for example)
- Holidays need to be taken into account
Reason for the question: This does not perform well on bulk so I am looking for a optimization help
What I am Doing Now
Initial Method
public boolean isLast_x_Days(Date st, Integer days){
//days to date is a map that store previous calculation for a given number of days
if(!days_to_date.containsKey(days)){
get_date_x_days_back(st,days);
}
return st >= days_to_date.get(days);
}
Get date that is X days back
public Date get_date_x_days_back(Date s, Integer days){
Integer cnt = days;
//If start date is in the future return
//End date is defined in the class
if(s > end_date || s == null || end_date == null)
return null;
Date calculated_last_day_within;
Date workingDate = end_date;
for(integer x=0;cnt > 0;x++){
Date tmp = workingDate.addDays(-x);
//Set the time to 13:30 so if a business day hours will not matter
DateTime tst = datetime.newInstance(tmp.year(),tmp.month(),tmp.day(),13,30,0);
if(businessHours.isWithin(business_hours.id,tst)){
cnt -= 1;
calculated_last_day_within = tst.date();
}
}
days_to_date.put(days,calculated_last_day_within);
return calculated_last_day_within;
}
It all works well in small batches of 200 or so, but when updated without a scope size CPU Limits get reached.
The reason limit get reached:
- In the trigger there are two different dates on a record and each use their own business hours. This requires two different instantiations of the class
- No real way to cache results as the dates on the records are completely different.
Trigger for context
trigger Populate_Last_x_Days on AM_Sample__c (before insert, before update) {
//Allows the test methods to insert records without setting values so we can test the batch functionality
if(Calculate_Last_x_Days_Tests.STOPTRIGGER) return;
TrainingDaysSettings__c tConfig = TrainingDaysSettings__c.getOrgDefaults();
BusinessHours_Utilities bu = New BusinessHours_Utilities(Date.today());
BusinessHours_Utilities buTrainingBefore = New BusinessHours_Utilities(tConfig.Business_Hours_Name__c);
BusinessHours_Utilities buTrainingAfter = New BusinessHours_Utilities(tConfig.Business_Hours_Name__c);
for(AM_Sample__c am : trigger.new){
try{
am.Last_20_Days__c = bu.isLast_x_Days(am.AM_Report_Date__c,20);
am.Last_365_Days__c = am.Last_20_Days__c ? am.Last_20_Days__c : bu.isLast_x_Days(am.AM_Report_Date__c, 365);
//Calculate the days since training using the Training Business Hours and Training Date
buTrainingBefore.setEndDate(am.WP_Training_Date__c);
buTrainingAfter.setEndDate(am.AM_Report_Date__c);
if(tConfig.Days_Within__c != null){
am.X60_Days_Before_Test__c = buTrainingBefore.isLast_x_Days(am.AM_Report_Date__c,tConfig.Days_Within__c.intValue());
am.X60_Days_After_Test__c = buTrainingAfter.isLast_x_Days(am.WP_Training_Date__c,(tConfig.Days_Within__c).intValue());
}else{
//Default to 60;
am.X60_Days_Before_Test__c = buTrainingBefore.isLast_x_Days(am.AM_Report_Date__c,60);
am.X60_Days_After_Test__c = buTrainingAfter.isLast_x_Days(am.WP_Training_Date__c,60);
}
am.Days_Since_Training__c = buTrainingBefore.businessDaysSince(am.WP_Training_Date__c,am.AM_Report_Date__c);
}catch(Exception e){
system.debug(logginglevel.error,'Unable to set the value: ' + e.getMessage() + ' - ' + e.getStackTraceString());
}
}
}
Q What optimization can be made so that I do not have to reduce the scope size on the batch to 200 to prevent CPU Limits?
BusinessHours.diff
and convert milliseconds to hours, and from there to days. You still have to traverse but you can at least start closer to the correct endpoint. I built out a pretty slick API with that assumption but I can't really share all 200 LOC. I can share some helpful snippets though if you are interested in that approach. – Adrian Larson♦ Nov 3 '15 at 16:24Datetime.newInstanceGMT
to avoid time zone issues, but it does not look like you can make your loop structure much more efficient. – Adrian Larson♦ Nov 3 '15 at 16:38