namespace Aeshnidae.Trials; /// /// The trophy week. An ISO week key ("2026-W38") whose boundary is the award moment /// (Settings.AwardDay / AwardHour, server-local) rather than Monday midnight: every /// server-local time is shifted forward by the distance from the award moment to the /// following Monday 00:00 before the ISO week is read, so a clear at Sunday 18:00 /// belongs to the week that has just begun and the award at Sunday 17:00 closes a /// week that ends exactly then. With Sunday 17:00 the shift is seven hours. /// public static class Weeks { /// From the award moment to the next Monday 00:00. public static TimeSpan Shift(Settings s) { var day = (int)s.AwardDayOfWeek; // Sunday 0 .. Saturday 6 var hour = Math.Clamp(s.AwardHour, 0, 23); // Days from the award day to Monday, going forward: Sunday -> 1, Monday -> 7, Tuesday -> 6 ... var daysToMonday = ((int)DayOfWeek.Monday - day + 7) % 7; if (daysToMonday == 0) daysToMonday = 7; return TimeSpan.FromDays(daysToMonday) - TimeSpan.FromHours(hour); } /// The week a server-local time falls in. public static string KeyOf(DateTime localTime, Settings s) { var shifted = localTime + Shift(s); return $"{ISOWeek.GetYear(shifted)}-W{ISOWeek.GetWeekOfYear(shifted):00}"; } /// The week that ended at the most recent award moment, from a server-local time. public static string PreviousKey(DateTime localTime, Settings s) => KeyOf(localTime.AddDays(-7), s); /// The next award moment after a server-local time. public static DateTime NextAward(DateTime localTime, Settings s) { var hour = Math.Clamp(s.AwardHour, 0, 23); var candidate = localTime.Date.AddHours(hour); while (candidate.DayOfWeek != s.AwardDayOfWeek || candidate <= localTime) candidate = candidate.AddDays(1); return candidate; } /// The award moment that began the current week (the last one that passed). public static DateTime CurrentWeekStart(DateTime localTime, Settings s) => NextAward(localTime, s).AddDays(-7); /// The Monday the shifted ISO week of a key starts on, for the human "week of" line. public static string Describe(string key, Settings s) { try { var parts = key.Split("-W"); var monday = ISOWeek.ToDateTime(int.Parse(parts[0]), int.Parse(parts[1]), DayOfWeek.Monday); var start = monday - Shift(s); return start.ToString("d MMM yyyy", CultureInfo.InvariantCulture); } catch { return key; } } }