Time Notes

3 min read

Parse timestamps, measure elapsed time, and validate duration strings in Katalog templates. Primarily used in status.fields to surface human-readable time values and in when: conditions to drive time-based rotation and expiry logic.

All timestamp notes accept RFC3339, RFC3339Nano, 2006-01-02T15:04:05Z, and YYYY-MM-DD formats. Safe zero values are returned for unparseable input.

Reference

NoteDescription
timeAgoReturn a human-readable elapsed-time string from a timestamp.
timeSinceReturn the number of seconds elapsed since a timestamp as an integer.
timeUntilReturn a Go duration string representing the time remaining until a timestamp.
isExpiredReturn true when a timestamp plus a duration is in the past.
timeFormatReformat a timestamp string using Go’s time format layout.
durationSecondsParse an extended duration string (Go units plus d/w/mo/y) and return the total number of seconds as an integer.
durationAddAdd two extended duration strings (Go units plus d/w/mo/y) and return the result as a canonical Go duration string.
durationValidReturn true when the string is a valid extended duration (Go units plus d/w/mo/y).
weekdayReturn true when the current UTC day is Monday through Friday.
weekendReturn true when the current UTC day is Saturday or Sunday.
timeInWindowReturn true when the current UTC time falls within the window [after, before).
timeNotInWindowReturn true when the current UTC time is outside the window [after, before).
nextCronReturn the next scheduled fire time for a standard 5-field cron expression as an RFC3339 string.

Examples

# timeAgo
status:
  fields:
    - path: lastSyncAgo
      value: "{{ timeAgo .children.cronjob.status.lastScheduleTime }}"
      # → "5s ago" / "12m ago" / "3h ago" / "2d ago"

    - path: createdAgo
      value: "{{ timeAgo .metadata.creationTimestamp }}"

# timeSince
# Gate rotation on time elapsed (30 days = 2592000 seconds)
when:
  - field: "{{ timeSince (index .metadata.annotations \"myorg.io/last-rotated\") }}"
    operator: gte
    value: "2592000"

# timeUntil
operatorBox:
  reconciler:
    requeue:
      after: "{{ timeUntil .status.certExpiry }}"
      when:
        - field: status.certExpiry
          operator: exists

status:
  fields:
    - path: timeUntilExpiry
      value: "{{ timeUntil .status.certExpiry }}"
      # → "719h43m12s"  (about 30 days)
      # → "0s"          (already expired)

# isExpired
# Recreate the secret when the rotation annotation says it is due
onCreate:
  secrets:
    - name: "{{ .metadata.name }}-token"
      once: true
      rotateAfter: 30d

# Gate a resource on whether a timestamp annotation is past its TTL
when:
  - field: "{{ isExpired (index .metadata.annotations \"myorg.io/generated-at\") \"30d\" }}"
    equals: "true"

# timeFormat
status:
  fields:
    - path: createdDate
      value: "{{ timeFormat .metadata.creationTimestamp \"Jan 2, 2006\" }}"
      # → "Apr 13, 2026"

    - path: expiresAt
      value: "{{ timeFormat .status.expirationTime \"2006-01-02\" }}"

# durationSeconds
status:
  fields:
    - path: resyncIntervalSeconds
      value: "{{ durationSeconds .spec.resyncInterval }}"
      # "5m" → 300  |  "1h30m" → 5400  |  "7d" → 604800

# durationAdd
# Compute the total window from base + buffer
status:
  fields:
    - path: totalWindow
      value: "{{ durationAdd .spec.baseWindow .spec.buffer }}"
      # "5m" + "30s" → "5m30s"

# durationValid
spec:
  crds:
    myApp:
      validate:
        - message: "spec.rotationPeriod must be a valid duration (e.g. 30d, 90m, 1y)"
          deny:
            - field: "{{ durationValid .spec.rotationPeriod }}"
              equals: "false"

# weekday
status:
  fields:
    - path: phase
      value: "Active"
      when:
        - field: "{{ weekday }}"
          equals: "true"
    - path: phase
      value: "Suspended"

# weekend
status:
  fields:
    - path: phase
      value: "Weekend"
      when:
        - field: "{{ weekend }}"
          equals: "true"

# timeInWindow
status:
  fields:
    - path: phase
      value: "Active"
      when:
        - field: "{{ timeInWindow \"09:00\" \"18:00\" }}"
          equals: "true"
    - path: phase
      value: "Suspended"

# Combine with weekday for a full business-hours check:
# {{ and (timeInWindow "09:00" "18:00") weekday }}

# timeNotInWindow
status:
  fields:
    - path: maintenanceActive
      value: "true"
      when:
        - field: "{{ timeNotInWindow \"02:00\" \"04:00\" }}"
          equals: "false"

# nextCron
status:
  fields:
    - path: nextMaintenance
      value: "{{ nextCron \"0 2 * * 0\" }}"
      # → "2026-07-19T02:00:00Z"  (next Sunday 02:00 UTC)

    - path: nextDeploy
      value: "{{ nextCron \"0 9 * * 1\" }}"
      # → next Monday 09:00 UTC