< Summary - Jellyfin

Information
Class: Jellyfin.Server.Migrations.Routines.FixDates
Assembly: jellyfin
File(s): /srv/git/jellyfin/Jellyfin.Server/Migrations/Routines/FixDates.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 92
Coverable lines: 92
Total lines: 171
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 24
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 1/23/2026 - 12:11:06 AM Line coverage: 0% (0/10) Branch coverage: 0% (0/10) Total lines: 1714/19/2026 - 12:14:27 AM Line coverage: 0% (0/92) Branch coverage: 0% (0/24) Total lines: 171 1/23/2026 - 12:11:06 AM Line coverage: 0% (0/10) Branch coverage: 0% (0/10) Total lines: 1714/19/2026 - 12:14:27 AM Line coverage: 0% (0/92) Branch coverage: 0% (0/24) Total lines: 171

Coverage delta

Coverage delta 1 -1

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
PerformAsync()0%620%
FixBaseItemsAsync()0%2040%
FixChaptersAsync()0%2040%
FixBaseItemImageInfos()0%2040%
ToUniversalTime(...)0%110100%

File(s)

/srv/git/jellyfin/Jellyfin.Server/Migrations/Routines/FixDates.cs

#LineLine coverage
 1using System;
 2using System.Diagnostics;
 3using System.Linq;
 4using System.Threading;
 5using System.Threading.Tasks;
 6using Jellyfin.Database.Implementations;
 7using Jellyfin.Server.ServerSetupApp;
 8using Microsoft.EntityFrameworkCore;
 9using Microsoft.Extensions.Logging;
 10
 11namespace Jellyfin.Server.Migrations.Routines;
 12
 13/// <summary>
 14/// Migration to fix dates saved in the database to always be UTC.
 15/// </summary>
 16[JellyfinMigration("2025-06-20T18:00:00", nameof(FixDates))]
 17public class FixDates : IAsyncMigrationRoutine
 18{
 19    private const int PageSize = 5000;
 20
 21    private readonly ILogger _logger;
 22    private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
 23
 24    /// <summary>
 25    /// Initializes a new instance of the <see cref="FixDates"/> class.
 26    /// </summary>
 27    /// <param name="logger">The logger.</param>
 28    /// <param name="startupLogger">The startup logger for Startup UI integration.</param>
 29    /// <param name="dbProvider">Instance of the <see cref="IDbContextFactory{JellyfinDbContext}"/> interface.</param>
 30    public FixDates(
 31        ILogger<FixDates> logger,
 32        IStartupLogger<FixDates> startupLogger,
 33        IDbContextFactory<JellyfinDbContext> dbProvider)
 34    {
 035        _logger = startupLogger.With(logger);
 036        _dbProvider = dbProvider;
 037    }
 38
 39    /// <inheritdoc />
 40    public async Task PerformAsync(CancellationToken cancellationToken)
 41    {
 042        if (!TimeZoneInfo.Local.Equals(TimeZoneInfo.Utc))
 43        {
 044            var context = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
 045            await using (context.ConfigureAwait(false))
 46            {
 047                var sw = Stopwatch.StartNew();
 48
 049                await FixBaseItemsAsync(context, sw, cancellationToken).ConfigureAwait(false);
 050                sw.Reset();
 051                await FixChaptersAsync(context, sw, cancellationToken).ConfigureAwait(false);
 052                sw.Reset();
 053                await FixBaseItemImageInfos(context, sw, cancellationToken).ConfigureAwait(false);
 054            }
 055        }
 056    }
 57
 58    private async Task FixBaseItemsAsync(JellyfinDbContext context, Stopwatch sw, CancellationToken cancellationToken)
 59    {
 060        int itemCount = 0;
 61
 062        var baseQuery = context.BaseItems.OrderBy(e => e.Id);
 063        var records = baseQuery.Count();
 064        _logger.LogInformation("Fixing dates for {Count} BaseItems.", records);
 65
 066        sw.Start();
 067        await foreach (var result in context.BaseItems.OrderBy(e => e.Id)
 068                        .WithPartitionProgress(
 069                            (partition) =>
 070                                _logger.LogInformation(
 071                                    "Processing BaseItems batch {BatchNumber} ({ProcessedSoFar}/{TotalRecords}) - Time: 
 072                                    partition + 1,
 073                                    Math.Min((partition + 1) * PageSize, records),
 074                                    records,
 075                                    sw.Elapsed))
 076                        .PartitionEagerAsync(PageSize, cancellationToken)
 077                        .WithCancellation(cancellationToken)
 078                        .ConfigureAwait(false))
 79        {
 080            result.DateCreated = ToUniversalTime(result.DateCreated);
 081            result.DateLastMediaAdded = ToUniversalTime(result.DateLastMediaAdded);
 082            result.DateLastRefreshed = ToUniversalTime(result.DateLastRefreshed);
 083            result.DateLastSaved = ToUniversalTime(result.DateLastSaved);
 084            result.DateModified = ToUniversalTime(result.DateModified);
 085            itemCount++;
 86        }
 87
 088        var saveCount = await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
 089        _logger.LogInformation("BaseItems: Processed {ItemCount} items, saved {SaveCount} changes in {ElapsedTime}", ite
 090    }
 91
 92    private async Task FixChaptersAsync(JellyfinDbContext context, Stopwatch sw, CancellationToken cancellationToken)
 93    {
 094        int itemCount = 0;
 95
 096        var baseQuery = context.Chapters;
 097        var records = baseQuery.Count();
 098        _logger.LogInformation("Fixing dates for {Count} Chapters.", records);
 99
 0100        sw.Start();
 0101        await foreach (var result in context.Chapters.OrderBy(e => e.ItemId)
 0102                        .WithPartitionProgress(
 0103                            (partition) =>
 0104                                _logger.LogInformation(
 0105                                    "Processing Chapter batch {BatchNumber} ({ProcessedSoFar}/{TotalRecords}) - Time: {E
 0106                                    partition + 1,
 0107                                    Math.Min((partition + 1) * PageSize, records),
 0108                                    records,
 0109                                    sw.Elapsed))
 0110                        .PartitionEagerAsync(PageSize, cancellationToken)
 0111                        .WithCancellation(cancellationToken)
 0112                        .ConfigureAwait(false))
 113        {
 0114            result.ImageDateModified = ToUniversalTime(result.ImageDateModified, true);
 0115            itemCount++;
 116        }
 117
 0118        var saveCount = await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
 0119        _logger.LogInformation("Chapters: Processed {ItemCount} items, saved {SaveCount} changes in {ElapsedTime}", item
 0120    }
 121
 122    private async Task FixBaseItemImageInfos(JellyfinDbContext context, Stopwatch sw, CancellationToken cancellationToke
 123    {
 0124        int itemCount = 0;
 125
 0126        var baseQuery = context.BaseItemImageInfos;
 0127        var records = baseQuery.Count();
 0128        _logger.LogInformation("Fixing dates for {Count} BaseItemImageInfos.", records);
 129
 0130        sw.Start();
 0131        await foreach (var result in context.BaseItemImageInfos.OrderBy(e => e.Id)
 0132                        .WithPartitionProgress(
 0133                            (partition) =>
 0134                                _logger.LogInformation(
 0135                                    "Processing BaseItemImageInfos batch {BatchNumber} ({ProcessedSoFar}/{TotalRecords})
 0136                                    partition + 1,
 0137                                    Math.Min((partition + 1) * PageSize, records),
 0138                                    records,
 0139                                    sw.Elapsed))
 0140                        .PartitionEagerAsync(PageSize, cancellationToken)
 0141                        .WithCancellation(cancellationToken)
 0142                        .ConfigureAwait(false))
 143        {
 0144            result.DateModified = ToUniversalTime(result.DateModified);
 0145            itemCount++;
 146        }
 147
 0148        var saveCount = await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
 0149        _logger.LogInformation("BaseItemImageInfos: Processed {ItemCount} items, saved {SaveCount} changes in {ElapsedTi
 0150    }
 151
 152    private DateTime? ToUniversalTime(DateTime? dateTime, bool isUTC = false)
 153    {
 0154        if (dateTime is null)
 155        {
 0156            return null;
 157        }
 158
 0159        if (dateTime.Value.Year == 1 && dateTime.Value.Month == 1 && dateTime.Value.Day == 1)
 160        {
 0161            return null;
 162        }
 163
 0164        if (dateTime.Value.Kind == DateTimeKind.Utc || isUTC)
 165        {
 0166            return dateTime.Value;
 167        }
 168
 0169        return dateTime.Value.ToUniversalTime();
 170    }
 171}