using System.Globalization;
using System.Text;
namespace Volcanic.AgenticCore.Extensions.Scheduler;
/// How jobs and times are shown (the schedule tool, /jobs, zone-change replies). Invariant culture, 24 h.
public static class JobFormat
{
public static string Icon(JobKind kind) => kind switch { JobKind.Reminder => "⏰", JobKind.Prompt => "💬", _ => "🔍" };
/// 27 Sep 09:00 in .
public static string DayTime(DateTimeOffset at, TimeZoneInfo zone) =>
TimeZoneInfo.ConvertTime(at, zone).ToString("d MMM HH:mm", CultureInfo.InvariantCulture);
/// 14:10 when on the same local day as , else .
public static string When(DateTimeOffset at, TimeZoneInfo zone, DateTimeOffset now)
{
var local = TimeZoneInfo.ConvertTime(at, zone);
return local.Date == TimeZoneInfo.ConvertTime(now, zone).Date
? local.ToString("HH:mm", CultureInfo.InvariantCulture)
: DayTime(at, zone);
}
/// Sat 2026-09-26 14:03 (the model-facing form: weekday and full date).
public static string Stamp(DateTimeOffset at, TimeZoneInfo zone) =>
TimeZoneInfo.ConvertTime(at, zone).ToString("ddd yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture);
public static string ZoneName(TimeZoneInfo zone) => ZoneNames.Name(zone);
/// exit 1, exit 1, timed out, timed out, or did not start.
public static string FailureReason(JobResult result) => result switch
{
{ ExitCode: { } code, TimedOut: true } => $"exit {code.ToString(CultureInfo.InvariantCulture)}, timed out",
{ ExitCode: { } code } => $"exit {code.ToString(CultureInfo.InvariantCulture)}",
{ TimedOut: true } => "timed out",
_ => "did not start",
};
/// `k7m2q` ⏰ Call the dentist · once 27 Sep 09:00; cron jobs show the expression and the next run; a failed last run is appended.
public static string Line(JobView view, TimeZoneInfo zone, DateTimeOffset now)
{
var job = view.Job;
var sb = new StringBuilder().Append('`').Append(job.Id).Append("` ").Append(Icon(job.Kind)).Append(' ').Append(job.Name);
if (job.Schedule.At is { } at) sb.Append(" · once ").Append(DayTime(at, zone));
else
{
sb.Append(" · `").Append(job.Schedule.Cron).Append('`');
if (view.NextRunAt is { } next) sb.Append(" · next ").Append(When(next, zone, now));
}
if (job.LastResult is { Ok: false } failed) sb.Append(" · last: failed (").Append(FailureReason(failed)).Append(')');
return sb.ToString();
}
}