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. 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. Jun 6, 2020 at 5:27
  • Ok, can you add the cron expression you used to schedule the Jb? 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 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! 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. Jun 7, 2020 at 2:23
  • @IsuruBuddhikaHerath Yes, some people prefer separate batch and scheduled classes for whatever reason, and this is fine.
    – sfdcfox
    Jun 7, 2020 at 14:01

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.