校历优化

This commit is contained in:
biss committed 2026-09-29 09:33:25 +08:00
1 parent 0ef0d4d399
commit 4ef4164b00
6 files changed
+196 -39

No files matched your search

@@ -119,21 +119,23 @@ public sealed class AcademicCalendarController(AppDbContext db, HolidayCnClient
catch (InvalidOperationException exception) { return ValidationProblem(exception.Message); }
var existing = await db.AcademicCalendarEvents.Where(item => item.AcademicTermId == request.AcademicTermId && item.Source == AcademicCalendarEventSource.HolidayCn && item.SourceYear == request.Year).ToListAsync(cancellationToken);
var ranges = HolidayCnRanges.Group(source.Days);
db.AcademicCalendarEvents.RemoveRange(existing);
db.AcademicCalendarEvents.AddRange(source.Days.Select(day => new AcademicCalendarEvent
db.AcademicCalendarEvents.AddRange(ranges.Select(range => new AcademicCalendarEvent
{
AcademicTermId = request.AcademicTermId,
Title = day.Name,
Title = range.Name,
Kind = AcademicCalendarEventKind.PublicHoliday,
StartsOn = day.Date,
ReminderDays = day.IsOffDay ? 3 : 0,
Description = day.IsOffDay ? "法定节假日或调休休息日" : "调休上班日",
StartsOn = range.StartsOn,
EndsOn = range.StartsOn == range.EndsOn ? null : range.EndsOn,
ReminderDays = range.IsOffDay ? 3 : 0,
Description = range.IsOffDay ? "法定节假日或调休休息日" : "调休上班日",
Source = AcademicCalendarEventSource.HolidayCn,
SourceYear = request.Year,
IsOffDay = day.IsOffDay
IsOffDay = range.IsOffDay
}));
await db.SaveChangesAsync(cancellationToken);
return Ok(new { Year = request.Year, SyncedCount = source.Days.Count, ReplacedCount = existing.Count, source.Papers });
return Ok(new { Year = request.Year, SyncedCount = ranges.Count, SourceDayCount = source.Days.Count, ReplacedCount = existing.Count, source.Papers });
}
private async Task<ActionResult?> ValidateAsync(AcademicCalendarEventRequest request, CancellationToken cancellationToken)
@@ -21,3 +21,42 @@ public sealed class HolidayCnClient(HttpClient httpClient)
public sealed record HolidayCnYear(int Year, IReadOnlyList<string> Papers, IReadOnlyList<HolidayCnDay> Days);
public sealed record HolidayCnDay(string Name, DateOnly Date, bool IsOffDay);
/// <summary>
/// 将数据源中按日列出的同一连续节假日归并为一个校历日程。
/// 调休上班日与休息日即使同名也必须分别保留,避免错误地把工作日包含在假期范围内。
/// </summary>
public static class HolidayCnRanges
{
public static IReadOnlyList<HolidayCnRange> Group(IEnumerable<HolidayCnDay> days) => days
.OrderBy(day => day.Date)
.GroupBy(day => new { day.Name, day.IsOffDay })
.SelectMany(group => GroupConsecutive(group.Key.Name, group.Key.IsOffDay, group))
.OrderBy(range => range.StartsOn)
.ToList();
private static IEnumerable<HolidayCnRange> GroupConsecutive(
string name,
bool isOffDay,
IEnumerable<HolidayCnDay> days)
{
HolidayCnDay? first = null;
HolidayCnDay? previous = null;
foreach (var day in days.OrderBy(item => item.Date))
{
if (first is not null && previous is not null && day.Date > previous.Date.AddDays(1))
{
yield return new HolidayCnRange(name, first.Date, previous.Date, isOffDay);
first = null;
}
first ??= day;
previous = day;
}
if (first is not null && previous is not null)
yield return new HolidayCnRange(name, first.Date, previous.Date, isOffDay);
}
}
public sealed record HolidayCnRange(string Name, DateOnly StartsOn, DateOnly EndsOn, bool IsOffDay);