I have a set of records(around 90,000 records) to be updated at 1AM every day. But I cannot update at least 10,000 records in a bulk as salesforce reaches governor limits. Then I wrote a schedulable class as follows.

global class ScheduledCustomObjectUpdater implements Schedulable {
    global void execute(SchedulableContext SC) {
        List<Custom_Object__c> customObjects = [SELECT Name
                                                WHERE SystemModStamp < :Date.today()
                                                LIMIT 5000];
        finish(SC, customObjects);

    private void updateCustomObjects(List<Custom_Object__c> customObjects) {
        List<Custom_Object__c> customObjectsToUpdate = new List<Custom_Object__c>();
        for(Custom_Object__c customObject : customObjects) {
            customObject.Name = 'some text here';
        update customObjectsToUpdate;

    private void finish(SchedulableContext SC, List<Custom_Object__c> customObjects) {
        CronTrigger job = [SELECT CronJobDetail.Name FROM CronTrigger WHERE Id = :SC.getTriggerId()];

        if(customObjects.size() == 5000) {
            Datetime next = DateTime.now().addMinutes(1);

            String hour = String.valueOf(next.hour());
            String min  = String.valueOf(next.minute()); 
            String ss   = String.valueOf(next.second());

            //parse to cron expression
            String nextFireTime = ss + ' ' + min + ' ' + hour + ' * * ? *';

            // schedule a new job in next minute
            System.schedule(job.CronJobDetail.Name, nextFireTime, new ScheduledCustomObjectUpdater());
        } else {
            // all done. schedule a job for tomorrow
            System.schedule(job.CronJobDetail.Name, '0 0 1 * * ? *', new ScheduledCustomObjectUpdater());

Then I scheduled this class as a job on my sandbox at some time during the day(yesterday) using Dev Console Annonymous Window. it updated all the records and sheduled a new job at 1AM following day(today).

But when I check the records today, they have been updated at 10:00AM. My expectation was to update the records at 1AM.

enter image description here

Hoping any help to figure out what's going on there.

  • 1
    In which timezone the salesforce user is? Also, using Batch along with schedule will be more effective. Commented Jun 6, 2020 at 5:05
  • My timezone is AEST (i.e. GMT + 10). Batch alone with schedule? I'll try to find some resources regarding this. Commented Jun 6, 2020 at 5:27
  • Ok, can you add the cron expression you used to schedule the Jb? Commented Jun 6, 2020 at 5:35
  • 1
    Just checked the Apex Jobs. According to the logs, it has started running at 1AM correctly. But it has scheduled a series of Jobs and only completed at 10.30AM. all the records have been updated during 10.00AM to 10.30AM Commented Jun 6, 2020 at 5:52
  • 1
    Figured out what was happening. When the job runs at 1AM, it queries 5000 records where SystemModStamp < Date.today(); The hidden fact is SystemModStamp is in GMT time, Date.today() is in local time. Therefore, even if a record is updated at 1AM in local time, its SystemModStamp is set in GMT time (i.e Localtime -10). Hence, this process keeps running until it can query records. Only the records that are updated after 10AM have SystemModStamp with current day. @rahulgawale, Thanks for your help! Commented Jun 7, 2020 at 2:33

1 Answer 1


System.now() returns a datetime that's in GMT, which explains the time offset. To get a proper CRON string, you could use the format method:

DateTime inAMinute = DateTime.now().addMinutes(1);
String cronString = inAMinute.format('0 m h d M ? YYYY');

However, since you really want this to run a 1AM every day, you would want this job to not abort itself repeatedly. It'd be more convenient to just fire off a batch job later.

That would look like this:

public class ScheduledCustomObjectUpdater implements Schedulable, Database.Batchable<Custom_Object__c> {
  public void execute(ScheduableContext context) {
    Database.executeBatch(this, 2000);
  public Database.QueryLocator start(Database.BatchableContext context) {
    return Database.getQueryLocator([
      select ...fields... from Custom_Object__c where ...
  public void execute(Database.BatchableContext context, Custom_Object__c[] scope) {
    ...update scope records...
  public void finish(Database.BatchableContext context) {
    ...finish logic here...
  • 1
    I could implement the requirement exactly the same way thanks to the Rahul Gawale's comment on my question. Only difference is I used two separate classes; a Scheduled class and a Batchable class. Hope that is fine. Commented Jun 7, 2020 at 2:23
  • @IsuruBuddhikaHerath Yes, some people prefer separate batch and scheduled classes for whatever reason, and this is fine.
    – sfdcfox
    Commented Jun 7, 2020 at 14:01

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .