using Nonemm.Core; namespace Nonemm.Contests; /// Time on and time off, which the sponsors of a contest with a rest rule ask /// the entrant to declare. A gap between two contacts counts as time off when /// it is at least the contest's `MinimumOffTime`; anything shorter is time /// spent operating. This is how N1MM's off-time window reads a log. public static class OperatingTime { /// The gaps that count as time off, in the order they happened. public static IReadOnlyList<(DateTime From, DateTime To)> Breaks( IEnumerable qsos, TimeSpan minimum) { List<(DateTime, DateTime)> found = []; DateTime? last = null; foreach (Qso qso in qsos.OrderBy(q => q.TimestampUtc)) { if (last is { } before && qso.TimestampUtc - before >= minimum) { found.Add((before, qso.TimestampUtc)); } last = qso.TimestampUtc; } return found; } public static TimeSpan Off(IEnumerable qsos, TimeSpan minimum) { TimeSpan total = TimeSpan.Zero; foreach ((DateTime from, DateTime to) in Breaks(qsos, minimum)) { total += to - from; } return total; } /// From the first contact to the last, less the time off. A log of one /// contact or none is no time at all. public static TimeSpan On(IEnumerable qsos, TimeSpan minimum) { List ordered = qsos.OrderBy(q => q.TimestampUtc).ToList(); return ordered.Count < 2 ? TimeSpan.Zero : ordered[^1].TimestampUtc - ordered[0].TimestampUtc - Off(ordered, minimum); } }