Post

Icon Cron Seconds Support Implementation

Implementation of seconds support in cron expressions with six-field format, making every N seconds the default scheduling option

Cron Seconds Support Implementation

Cron Seconds Support Implementation Summary

Overview

Added support for seconds in cron expressions, making the default option “Every N seconds” with a default of 5 seconds.

Changes Made

1. CronDialog.kt - UI Form Updates

File: /composeApp/src/commonMain/kotlin/krill/zone/krillapp/executor/cron/CronDialog.kt

Added Variables:

  • everySeconds - default “5”
  • dailySecond - default “0”
  • weeklySecond - default “0”
  • monthlySecond - default “0”
  • Changed default mode from "every_minutes" to "every_seconds"

UI Components:

  • Added “Every N seconds” radio button option above the minutes option
  • Added seconds input field for daily, weekly, and monthly modes
  • Updated all mode radio buttons to include seconds configuration

Expression Building:

Updated buildExpression() to generate 6-field cron expressions:

  • every_seconds: "*/N * * * * *" (default: "*/5 * * * * *")
  • every_minutes: "0 */N * * * *" (prepends 0 for seconds)
  • every_hours: "0 M */H * * *"
  • daily: "S M H * * *"
  • weekly: "S M H * * DOW"
  • monthly: "S M H D * *"
  • custom: User-defined expression

Expression Parsing:

Enhanced LaunchedEffect to handle both formats:

  • 6-field format: SEC MIN HOUR DOM MON DOW (primary)
  • 5-field format: MIN HOUR DOM MON DOW (legacy, assumes second=0)

2. CronLogic.kt - Core Logic Updates

File: /krill-sdk/src/commonMain/kotlin/krill/zone/krillapp/trigger/cron/CronLogic.kt

CronSpec Data Class:

Added seconds: Set<Int> field as the first parameter

parseExpression():

  • Now accepts both 5-field and 6-field formats
  • 6-field: SEC MIN HOUR DOM MON DOW
  • 5-field: MIN HOUR DOM MON DOW (assumes seconds = 0)

nextExecution():

  • Changed from searching by minute boundaries to second boundaries
  • Added second field matching in the execution logic
  • Loop increments by 1 second instead of 1 minute
  • Checks seconds field first, then minutes, hours, etc.

Before:

1
2
3
4
fun nextExecution(cronSpec: CronSpec, now: Instant): Instant? {
    var candidate = now.plus(1, DateTimeUnit.MINUTE)  // Start at next minute
    // ...loop by minutes
}

After:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun nextExecution(cronSpec: CronSpec, now: Instant): Instant? {
    var candidate = now.plus(1, DateTimeUnit.SECOND)  // Start at next second
    
    while (attempts < maxAttempts) {
        val dt = candidate.toLocalDateTime(TimeZone.currentSystemDefault())
        
        // Check seconds first
        if (cronSpec.seconds.isNotEmpty() && dt.second !in cronSpec.seconds) {
            candidate = candidate.plus(1, DateTimeUnit.SECOND)
            attempts++
            continue
        }
        
        // Then check minutes, hours, etc.
        // ...
    }
}

3. ServerCronProcessor.kt - Execution Updates

File: /krill-sdk/src/commonMain/kotlin/krill/zone/krillapp/executor/cron/ServerCronProcessor.kt

Continuous Loop:

Changed the execution loop to work with second-level precision:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private suspend fun executeCronJob(node: Node): Boolean {
    while (true) {
        val cronSpec = CronLogic.parseExpression(expression)
        val nextTime = CronLogic.nextExecution(cronSpec, Clock.System.now())
        
        if (nextTime == null) {
            logger.e { "Failed to calculate next execution for ${node.name()}" }
            return false
        }
        
        val waitMillis = nextTime.toEpochMilliseconds() - Clock.System.now().toEpochMilliseconds()
        if (waitMillis > 0) {
            delay(waitMillis)
        }
        
        // Execute children at scheduled time
        nodeManager.executeChildren(node)
    }
}

Backward Compatibility

The implementation maintains full backward compatibility with existing 5-field cron expressions:

  • 5-field expressions are automatically converted to 6-field with seconds=0
  • Existing cron nodes continue to work without modification
  • UI gracefully handles both formats when loading existing nodes

Example conversions:

1
2
3
4
5
6
7
8
9
10
11
// 5-field input
"*/5 * * * *"  // Every 5 minutes

// Parsed as 6-field
"0 */5 * * * *"  // Every 5 minutes at 0 seconds

// 6-field input
"*/30 * * * * *"  // Every 30 seconds

// Parsed as-is
"*/30 * * * * *"

UI Changes

New Default Screen

When creating a new cron node, users now see:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Schedule

● Every N seconds
  Seconds: [5]

○ Every N minutes
  Minutes: [5]

○ Every N hours
  Hours: [1]  Minute: [0]

○ Daily at time
  Hour: [0]  Minute: [0]  Second: [0]

○ Weekly on days
  [Days selector]
  Hour: [0]  Minute: [0]  Second: [0]

○ Monthly on day
  Day: [1]  Hour: [0]  Minute: [0]  Second: [0]

○ Custom cron expression
  [Text field]

Generated cron: */5 * * * * *

Field Validation

All numeric inputs are validated:

  • Seconds: 0-59
  • Minutes: 0-59
  • Hours: 0-23
  • Day of month: 1-31

Testing

Test Cases Verified:

  1. Every N seconds:
    • */5 * * * * * - Every 5 seconds ✓
    • */30 * * * * * - Every 30 seconds ✓
    • 0,15,30,45 * * * * * - At 0, 15, 30, 45 seconds ✓
  2. Daily with seconds:
    • 30 15 9 * * * - 9:15:30 AM daily ✓
    • 0 0 0 * * * - Midnight exactly ✓
  3. Weekly with seconds:
    • 0 0 9 * * 1 - 9:00:00 AM Mondays ✓
    • 30 30 14 * * 5 - 2:30:30 PM Fridays ✓
  4. Backward compatibility:
    • */5 * * * *0 */5 * * * *
    • 0 9 * * *0 0 9 * * *

Performance Considerations

Before (minute precision):

  • Loop increments: 1 minute
  • Max iterations: ~525,600 per year search
  • Typical delay: 1-60 seconds

After (second precision):

  • Loop increments: 1 second
  • Max iterations: ~31,536,000 per year search
  • Typical delay: 0-60 seconds
  • Safeguard: maxAttempts = 366 * 24 * 60 * 60 (1 year of seconds)

The second-level precision increases computational overhead slightly, but the maxAttempts safeguard prevents infinite loops.

Migration Notes

For Users:

  • Existing cron nodes work unchanged
  • New cron nodes default to “Every 5 seconds”
  • Can still use minute/hour/daily/weekly/monthly modes
  • Custom expressions support both 5-field and 6-field formats

For Developers:

  • CronLogic.parseExpression() handles both formats automatically
  • UI components gracefully handle missing seconds field
  • All processors work with both formats

Future Enhancements

Potential improvements:

  • Add preset buttons (every 10s, 30s, 1m, 5m, etc.)
  • Visual timeline showing next 5 executions
  • Validation warnings for unrealistic schedules
  • Expression tester with live preview

Conclusion

The seconds support implementation provides users with fine-grained scheduling control while maintaining full backward compatibility. The default of “Every 5 seconds” makes it easy to create responsive automation workflows for time-sensitive tasks like sensor polling or status monitoring.

This post is licensed under CC BY 4.0 by the author.