时间
This commit is contained in:
@@ -166,6 +166,14 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
|
|||||||
configurationBuilder.Properties<TimeOnly>()
|
configurationBuilder.Properties<TimeOnly>()
|
||||||
.HaveConversion<TimeOnlyTimeSpanConverter>()
|
.HaveConversion<TimeOnlyTimeSpanConverter>()
|
||||||
.HaveColumnType("time");
|
.HaveColumnType("time");
|
||||||
|
|
||||||
|
// MySQL DATETIME has no offset or DateTimeKind. All system timestamps
|
||||||
|
// are persisted as UTC, so restore that contract when materializing
|
||||||
|
// them. System.Text.Json will then emit the trailing "Z", allowing
|
||||||
|
// browsers to convert timestamps to the viewer's local time correctly.
|
||||||
|
configurationBuilder.Properties<DateTime>()
|
||||||
|
.HaveConversion<UtcDateTimeConverter>()
|
||||||
|
.HaveColumnType("datetime(6)");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder builder)
|
protected override void OnModelCreating(ModelBuilder builder)
|
||||||
@@ -1086,6 +1094,10 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
|
|||||||
});
|
});
|
||||||
builder.Entity<ExamSession>(entity =>
|
builder.Entity<ExamSession>(entity =>
|
||||||
{
|
{
|
||||||
|
// Exam slot times are China-local wall-clock times, not instants.
|
||||||
|
// Keep their existing API representation offset-free.
|
||||||
|
entity.Property(x => x.StartsAt).HasConversion<UnspecifiedDateTimeConverter>();
|
||||||
|
entity.Property(x => x.EndsAt).HasConversion<UnspecifiedDateTimeConverter>();
|
||||||
entity.Property(x => x.Notes).HasMaxLength(500);
|
entity.Property(x => x.Notes).HasMaxLength(500);
|
||||||
entity.HasIndex(x => new { x.ExamPlanId, x.StartsAt });
|
entity.HasIndex(x => new { x.ExamPlanId, x.StartsAt });
|
||||||
entity.HasIndex(x => x.TeachingTaskId);
|
entity.HasIndex(x => x.TeachingTaskId);
|
||||||
@@ -1112,6 +1124,8 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
|
|||||||
builder.Entity<ExamRoomAssignment>(entity =>
|
builder.Entity<ExamRoomAssignment>(entity =>
|
||||||
{
|
{
|
||||||
entity.ToTable("ExamRooms");
|
entity.ToTable("ExamRooms");
|
||||||
|
entity.Property(x => x.StartsAt).HasConversion<UnspecifiedDateTimeConverter>();
|
||||||
|
entity.Property(x => x.EndsAt).HasConversion<UnspecifiedDateTimeConverter>();
|
||||||
entity.HasIndex(x => new { x.ExamPlanId, x.StartsAt })
|
entity.HasIndex(x => new { x.ExamPlanId, x.StartsAt })
|
||||||
.HasDatabaseName("IX_ExamRooms_Plan_Time");
|
.HasDatabaseName("IX_ExamRooms_Plan_Time");
|
||||||
entity.HasIndex(x => new
|
entity.HasIndex(x => new
|
||||||
@@ -1200,6 +1214,9 @@ public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
|
|||||||
});
|
});
|
||||||
builder.Entity<MakeupExamSession>(entity =>
|
builder.Entity<MakeupExamSession>(entity =>
|
||||||
{
|
{
|
||||||
|
// Makeup-exam slot times follow the same wall-clock convention.
|
||||||
|
entity.Property(x => x.StartsAt).HasConversion<UnspecifiedDateTimeConverter>();
|
||||||
|
entity.Property(x => x.EndsAt).HasConversion<UnspecifiedDateTimeConverter>();
|
||||||
entity.Property(x => x.Notes).HasMaxLength(500);
|
entity.Property(x => x.Notes).HasMaxLength(500);
|
||||||
entity.HasIndex(x => new { x.MakeupExamPlanId, x.StartsAt });
|
entity.HasIndex(x => new { x.MakeupExamPlanId, x.StartsAt });
|
||||||
entity.HasIndex(x => x.TeachingTaskId);
|
entity.HasIndex(x => x.TeachingTaskId);
|
||||||
@@ -1646,3 +1663,15 @@ public sealed class TimeOnlyTimeSpanConverter()
|
|||||||
: ValueConverter<TimeOnly, TimeSpan>(
|
: ValueConverter<TimeOnly, TimeSpan>(
|
||||||
time => time.ToTimeSpan(),
|
time => time.ToTimeSpan(),
|
||||||
value => TimeOnly.FromTimeSpan(value));
|
value => TimeOnly.FromTimeSpan(value));
|
||||||
|
|
||||||
|
public sealed class UtcDateTimeConverter()
|
||||||
|
: ValueConverter<DateTime, DateTime>(
|
||||||
|
value => value.Kind == DateTimeKind.Local
|
||||||
|
? value.ToUniversalTime()
|
||||||
|
: DateTime.SpecifyKind(value, DateTimeKind.Utc),
|
||||||
|
value => DateTime.SpecifyKind(value, DateTimeKind.Utc));
|
||||||
|
|
||||||
|
public sealed class UnspecifiedDateTimeConverter()
|
||||||
|
: ValueConverter<DateTime, DateTime>(
|
||||||
|
value => DateTime.SpecifyKind(value, DateTimeKind.Unspecified),
|
||||||
|
value => DateTime.SpecifyKind(value, DateTimeKind.Unspecified));
|
||||||
|
|||||||
Reference in New Issue
Block a user