Files
Nonemm/src/Nonemm.Contests/OperatingTime.cs
T
erikbrockoandClaude Opus 5 6c2ee0f78f Act on the .udc session, off-time and band-change settings
Four settings were read by nothing.

MultipleSessions = 0000/30 runs the contest in half-hour sessions from
00:00 UTC. The session is part of the dupe key, so a station worked in
an earlier one may be worked again, and ResetMultsEverySession and
ResetSNEverySession start the multipliers and the serial numbers over.

DupeQSOMinutesAgo is the other way back to a station: after so many
minutes rather than in the next session. The log holds the time of the
last contact per dupe key instead of only the key, and the setting's
IgnoreBand and ThisMode decide the scope, because a file that uses it
turns DupeType off.

MinimumOffTime drives the time on and time off the score summary now
shows under the score, and beside them are the band changes made against
the allowance for a contest that limits them. N1MM's countdown for the
minutes you have to stay on a new band needs its info window and is not
here.

UdcFile.Flag now takes 1 and 0 as well as True and False, which is what
these settings are written as and what N1MM reads.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoGtneMQaz4M9w7Kk49AVD
2026-08-31 13:32:01 +00:00

49 lines
1.6 KiB
C#

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<Qso> 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<Qso> 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<Qso> qsos, TimeSpan minimum)
{
List<Qso> ordered = qsos.OrderBy(q => q.TimestampUtc).ToList();
return ordered.Count < 2
? TimeSpan.Zero
: ordered[^1].TimestampUtc - ordered[0].TimestampUtc - Off(ordered, minimum);
}
}