From 5bfcc511e8d335e982def071751dec54adcbef0b Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Thu, 25 Jul 2013 20:25:24 -0700 Subject: [PATCH] AirDate now stored as a string --- NzbDrone.Api/Episodes/EpisodeResource.cs | 2 +- .../NzbgetProviderTests/DownloadNzbFixture.cs | 2 +- .../SabProviderTests/SabProviderFixture.cs | 2 +- ...ktProxyFixture.cs => TraktProxyFixture.cs} | 69 +++++++++++-------- NzbDrone.Core.Test/NzbDrone.Core.Test.csproj | 2 +- .../OrganizerTests/GetNewFilenameFixture.cs | 4 +- .../SeriesStatisticsFixture.cs | 4 +- .../Datastore/Migration/014_drop_air_date.cs | 15 ++++ .../Migration/015_add_air_date_as_string.cs | 15 ++++ .../Search/DailyEpisodeMatchSpecification.cs | 2 +- NzbDrone.Core/Fluent.cs | 69 ------------------- .../PartialSeasonSearchCriteria.cs | 2 +- .../IndexerSearch/NzbSearchService.cs | 3 +- NzbDrone.Core/MetadataSource/TraktProxy.cs | 14 +++- NzbDrone.Core/NzbDrone.Core.csproj | 2 + NzbDrone.Core/Organizer/FileNameBuilder.cs | 4 +- .../SeriesStats/SeriesStatisticsRepository.cs | 6 +- NzbDrone.Core/Tv/Episode.cs | 6 +- NzbDrone.Core/Tv/EpisodeRepository.cs | 4 +- NzbDrone.Core/Tv/RefreshSeriesService.cs | 4 +- 20 files changed, 108 insertions(+), 123 deletions(-) rename NzbDrone.Core.Test/MetadataSourceTests/{TracktProxyFixture.cs => TraktProxyFixture.cs} (73%) create mode 100644 NzbDrone.Core/Datastore/Migration/014_drop_air_date.cs create mode 100644 NzbDrone.Core/Datastore/Migration/015_add_air_date_as_string.cs diff --git a/NzbDrone.Api/Episodes/EpisodeResource.cs b/NzbDrone.Api/Episodes/EpisodeResource.cs index f81b5f075..03ae675c5 100644 --- a/NzbDrone.Api/Episodes/EpisodeResource.cs +++ b/NzbDrone.Api/Episodes/EpisodeResource.cs @@ -11,7 +11,7 @@ public class EpisodeResource : RestResource public Int32 SeasonNumber { get; set; } public Int32 EpisodeNumber { get; set; } public String Title { get; set; } - public DateTime? AirDate { get; set; } + public String AirDate { get; set; } public DateTime? AirDateUtc { get; set; } public String Overview { get; set; } public EpisodeFile EpisodeFile { get; set; } diff --git a/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetProviderTests/DownloadNzbFixture.cs b/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetProviderTests/DownloadNzbFixture.cs index d5a3cbb52..0b83050bf 100644 --- a/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetProviderTests/DownloadNzbFixture.cs +++ b/NzbDrone.Core.Test/Download/DownloadClientTests/NzbgetProviderTests/DownloadNzbFixture.cs @@ -37,7 +37,7 @@ public void Setup() _remoteEpisode.Episodes = Builder.CreateListOfSize(1) .All() - .With(e => e.AirDate = DateTime.Today) + .With(e => e.AirDate = DateTime.Today.ToString(Episode.AIR_DATE_FORMAT)) .Build() .ToList(); } diff --git a/NzbDrone.Core.Test/Download/DownloadClientTests/SabProviderTests/SabProviderFixture.cs b/NzbDrone.Core.Test/Download/DownloadClientTests/SabProviderTests/SabProviderFixture.cs index 3b1cd199c..0ec06fef8 100644 --- a/NzbDrone.Core.Test/Download/DownloadClientTests/SabProviderTests/SabProviderFixture.cs +++ b/NzbDrone.Core.Test/Download/DownloadClientTests/SabProviderTests/SabProviderFixture.cs @@ -42,7 +42,7 @@ public void Setup() _remoteEpisode.Episodes = Builder.CreateListOfSize(1) .All() - .With(e => e.AirDate = DateTime.Today) + .With(e => e.AirDate = DateTime.Today.ToString(Episode.AIR_DATE_FORMAT)) .Build() .ToList(); } diff --git a/NzbDrone.Core.Test/MetadataSourceTests/TracktProxyFixture.cs b/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs similarity index 73% rename from NzbDrone.Core.Test/MetadataSourceTests/TracktProxyFixture.cs rename to NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs index c98a5bdd6..7c7a3e8ce 100644 --- a/NzbDrone.Core.Test/MetadataSourceTests/TracktProxyFixture.cs +++ b/NzbDrone.Core.Test/MetadataSourceTests/TraktProxyFixture.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using FluentAssertions; using NUnit.Framework; @@ -27,7 +28,6 @@ public void successful_search(string title, string expected) result[0].Title.Should().Be(expected); } - [Test] public void no_search_result() { @@ -35,40 +35,16 @@ public void no_search_result() result.Should().BeEmpty(); } - [Test] - public void should_be_able_to_get_series_detail() + [TestCase(75978)] + [TestCase(79349)] + public void should_be_able_to_get_series_detail(int tvdbId) { - var details = Subject.GetSeriesInfo(75978); - + var details = Subject.GetSeriesInfo(tvdbId); ValidateSeries(details.Item1); - - var episodes = details.Item2; - - episodes.Should().NotBeEmpty(); - - episodes.GroupBy(e => e.SeasonNumber.ToString("000") + e.EpisodeNumber.ToString("000")) - .Max(e => e.Count()).Should().Be(1); - - episodes.Select(c => c.TvDbEpisodeId).Should().OnlyHaveUniqueItems(); - - episodes.Should().Contain(c => c.SeasonNumber > 0); - episodes.Should().Contain(c => !string.IsNullOrWhiteSpace(c.Overview)); - - foreach (var episode in episodes) - { - episode.AirDate.Should().HaveValue(); - episode.AirDate.Value.Kind.Should().Be(DateTimeKind.Utc); - episode.EpisodeNumber.Should().NotBe(0); - episode.Title.Should().NotBeBlank(); - episode.TvDbEpisodeId.Should().NotBe(0); - } - + ValidateEpisodes(details.Item2); } - - - private void ValidateSeries(Series series) { series.Should().NotBeNull(); @@ -85,5 +61,38 @@ private void ValidateSeries(Series series) series.TvRageId.Should().BeGreaterThan(0); series.TvdbId.Should().BeGreaterThan(0); } + + private void ValidateEpisodes(List episodes) + { + episodes.Should().NotBeEmpty(); + + episodes.GroupBy(e => e.SeasonNumber.ToString("000") + e.EpisodeNumber.ToString("000")) + .Max(e => e.Count()).Should().Be(1); + + episodes.Select(c => c.TvDbEpisodeId).Should().OnlyHaveUniqueItems(); + + episodes.Should().Contain(c => c.SeasonNumber > 0); + episodes.Should().Contain(c => !string.IsNullOrWhiteSpace(c.Overview)); + + foreach (var episode in episodes) + { + ValidateEpisode(episode); + } + } + + private void ValidateEpisode(Episode episode) + { + episode.Should().NotBeNull(); + episode.Title.Should().NotBeBlank(); + episode.EpisodeNumber.Should().NotBe(0); + episode.TvDbEpisodeId.Should().BeGreaterThan(0); + + episode.Should().NotBeNull(); + + if (episode.AirDateUtc.HasValue) + { + episode.AirDateUtc.Value.Kind.Should().Be(DateTimeKind.Utc); + } + } } } diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index df6b19c9d..3b697139b 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -157,7 +157,7 @@ - + diff --git a/NzbDrone.Core.Test/OrganizerTests/GetNewFilenameFixture.cs b/NzbDrone.Core.Test/OrganizerTests/GetNewFilenameFixture.cs index 56728b01b..8877b5b19 100644 --- a/NzbDrone.Core.Test/OrganizerTests/GetNewFilenameFixture.cs +++ b/NzbDrone.Core.Test/OrganizerTests/GetNewFilenameFixture.cs @@ -700,7 +700,7 @@ public void should_use_airDate_if_series_isDaily() var episodes = Builder .CreateListOfSize(1) .All() - .With(e => e.AirDate = new DateTime(2012, 12, 13)) + .With(e => e.AirDate = "2012-12-13") .With(e => e.Title = "Kristen Stewart") .Build(); @@ -729,7 +729,7 @@ public void should_use_airDate_if_series_isDaily_no_episode_title() var episodes = Builder .CreateListOfSize(1) .All() - .With(e => e.AirDate = new DateTime(2012, 12, 13)) + .With(e => e.AirDate = "2012-12-13") .With(e => e.Title = "Kristen Stewart") .Build(); diff --git a/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs b/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs index 18c2a4d82..2589fe5df 100644 --- a/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs +++ b/NzbDrone.Core.Test/SeriesStatsTests/SeriesStatisticsFixture.cs @@ -27,7 +27,7 @@ public void Setup() _episode = Builder.CreateNew() .With(e => e.Id = 0) .With(e => e.SeriesId = series.Id) - .With(e => e.AirDate = DateTime.Today.AddDays(5)) + .With(e => e.AirDateUtc = DateTime.Today.AddDays(5)) .Build(); Db.Insert(_episode); @@ -39,7 +39,7 @@ public void should_get_stats_for_series() var stats = Subject.SeriesStatistics(); stats.Should().HaveCount(1); - stats.First().NextAiring.Should().Be(_episode.AirDate); + stats.First().NextAiring.Should().Be(_episode.AirDateUtc); } } } diff --git a/NzbDrone.Core/Datastore/Migration/014_drop_air_date.cs b/NzbDrone.Core/Datastore/Migration/014_drop_air_date.cs new file mode 100644 index 000000000..229bb11cd --- /dev/null +++ b/NzbDrone.Core/Datastore/Migration/014_drop_air_date.cs @@ -0,0 +1,15 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Tags("")] + [Migration(14)] + public class drop_air_date : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + SQLiteAlter.DropColumns("Episodes", new []{ "AirDate" }); + } + } +} diff --git a/NzbDrone.Core/Datastore/Migration/015_add_air_date_as_string.cs b/NzbDrone.Core/Datastore/Migration/015_add_air_date_as_string.cs new file mode 100644 index 000000000..39f26f89c --- /dev/null +++ b/NzbDrone.Core/Datastore/Migration/015_add_air_date_as_string.cs @@ -0,0 +1,15 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Tags("")] + [Migration(15)] + public class add_air_date_as_string : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("Episodes").AddColumn("AirDate").AsString().Nullable(); + } + } +} diff --git a/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyEpisodeMatchSpecification.cs b/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyEpisodeMatchSpecification.cs index 98291cddd..e3e8a8f7b 100644 --- a/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyEpisodeMatchSpecification.cs +++ b/NzbDrone.Core/DecisionEngine/Specifications/Search/DailyEpisodeMatchSpecification.cs @@ -31,7 +31,7 @@ public bool IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase search var episode = _episodeService.GetEpisode(dailySearchSpec.SeriesId, dailySearchSpec.Airtime); - if (!remoteEpisode.ParsedEpisodeInfo.AirDate.HasValue || remoteEpisode.ParsedEpisodeInfo.AirDate.Value.Date != episode.AirDate.Value.Date) + if (!remoteEpisode.ParsedEpisodeInfo.AirDate.HasValue || remoteEpisode.ParsedEpisodeInfo.AirDate.Value.ToString(Episode.AIR_DATE_FORMAT) != episode.AirDate) { _logger.Trace("Episode AirDate does not match searched episode number, skipping."); return false; diff --git a/NzbDrone.Core/Fluent.cs b/NzbDrone.Core/Fluent.cs index fde05a8f5..8e256e592 100644 --- a/NzbDrone.Core/Fluent.cs +++ b/NzbDrone.Core/Fluent.cs @@ -7,11 +7,6 @@ namespace NzbDrone.Core { public static class Fluent { - private const Decimal ONE_KILOBYTE = 1024M; - private const Decimal ONE_MEGABYTE = ONE_KILOBYTE * 1024M; - private const Decimal ONE_GIGABYTE = ONE_MEGABYTE * 1024M; - private const Decimal ONE_TERABYTE = ONE_GIGABYTE * 1024M; - public static string WithDefault(this string actual, object defaultValue) { if (defaultValue == null) @@ -93,70 +88,6 @@ public static string Truncate(this string s, int maxLength) return s.Substring(0, i); } - public static string AddSpacesToEnum(this Enum enumValue) - { - var text = enumValue.ToString(); - - if (string.IsNullOrWhiteSpace(text)) - return ""; - var newText = new StringBuilder(text.Length * 2); - newText.Append(text[0]); - for (int i = 1; i < text.Length; i++) - { - if (char.IsUpper(text[i]) && text[i - 1] != ' ') - newText.Append(' '); - newText.Append(text[i]); - } - return newText.ToString(); - } - - public static string ToBestFileSize(this long bytes, int precision = 0) - { - var ulongBytes = (ulong)bytes; - return ulongBytes.ToBestFileSize(precision); - } - - public static string ToBestFileSize(this ulong bytes, int precision = 0) - { - if (bytes == 0) - return "0B"; - - decimal size = Convert.ToDecimal(bytes); - - string suffix; - - if (size > ONE_TERABYTE) - { - size /= ONE_TERABYTE; - suffix = "TB"; - } - - else if (size > ONE_GIGABYTE) - { - size /= ONE_GIGABYTE; - suffix = "GB"; - } - - else if (size > ONE_MEGABYTE) - { - size /= ONE_MEGABYTE; - suffix = "MB"; - } - - else if (size > ONE_KILOBYTE) - { - size /= ONE_KILOBYTE; - suffix = "KB"; - } - - else - { - suffix = " B"; - } - - return String.Format("{0:N" + precision + "} {1}", size, suffix); - } - public static int MinOrDefault(this IEnumerable ints) { if (ints == null) diff --git a/NzbDrone.Core/IndexerSearch/Definitions/PartialSeasonSearchCriteria.cs b/NzbDrone.Core/IndexerSearch/Definitions/PartialSeasonSearchCriteria.cs index 37ba8c7ed..c8c46e922 100644 --- a/NzbDrone.Core/IndexerSearch/Definitions/PartialSeasonSearchCriteria.cs +++ b/NzbDrone.Core/IndexerSearch/Definitions/PartialSeasonSearchCriteria.cs @@ -13,7 +13,7 @@ public PartialSeasonSearchCriteria(SeasonSearchCriteria seasonSearch, int prefix public override string ToString() { - return string.Format("[{0} : S{1:00}E{1:0}*]", SceneTitle, SeasonNumber, Prefix); + return string.Format("[{0} : S{1:00}E{2:0}*]", SceneTitle, SeasonNumber, Prefix); } } } \ No newline at end of file diff --git a/NzbDrone.Core/IndexerSearch/NzbSearchService.cs b/NzbDrone.Core/IndexerSearch/NzbSearchService.cs index 368c57916..b0c151933 100644 --- a/NzbDrone.Core/IndexerSearch/NzbSearchService.cs +++ b/NzbDrone.Core/IndexerSearch/NzbSearchService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Threading.Tasks; using NLog; using NzbDrone.Core.DataAugmentation.Scene; @@ -53,7 +54,7 @@ public List EpisodeSearch(int episodeId) if (series.SeriesType == SeriesTypes.Daily) { - return SearchDaily(episode.SeriesId, episode.AirDate.Value.Date); + return SearchDaily(episode.SeriesId, DateTime.ParseExact(episode.AirDate, Episode.AIR_DATE_FORMAT, CultureInfo.InvariantCulture)); } return SearchSingle(series, episode); diff --git a/NzbDrone.Core/MetadataSource/TraktProxy.cs b/NzbDrone.Core/MetadataSource/TraktProxy.cs index ead507936..342868633 100644 --- a/NzbDrone.Core/MetadataSource/TraktProxy.cs +++ b/NzbDrone.Core/MetadataSource/TraktProxy.cs @@ -69,7 +69,7 @@ private static Episode MapEpisode(Trakt.Episode traktEpisode) episode.EpisodeNumber = traktEpisode.number; episode.TvDbEpisodeId = traktEpisode.tvdb_id; episode.Title = traktEpisode.title; - episode.AirDate = FromEpoch(traktEpisode.first_aired); + episode.AirDate = FromIsoToString(traktEpisode.first_aired_iso); episode.AirDateUtc = FromIso(traktEpisode.first_aired_iso); return episode; @@ -95,7 +95,7 @@ private static SeriesStatusType GetSeriesStatus(string status) { if (ticks == 0) return null; - return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(ticks); + return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Unspecified).AddSeconds(ticks); } private static DateTime? FromIso(string iso) @@ -107,5 +107,15 @@ private static SeriesStatusType GetSeriesStatus(string status) return result.ToUniversalTime(); } + + private static string FromIsoToString(string iso) + { + DateTime result; + + if (!DateTime.TryParse(iso, out result)) + return null; + + return result.ToString(Episode.AIR_DATE_FORMAT); + } } } \ No newline at end of file diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index e3c37d666..e38e1e4bc 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -215,6 +215,8 @@ + + diff --git a/NzbDrone.Core/Organizer/FileNameBuilder.cs b/NzbDrone.Core/Organizer/FileNameBuilder.cs index 06573f5d2..fa762947e 100644 --- a/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -128,8 +128,8 @@ public string BuildFilename(IList episodes, Series series, EpisodeFile else { - if (episodes.First().AirDate.HasValue) - result += episodes.First().AirDate.Value.ToString("yyyy-MM-dd"); + if (!String.IsNullOrEmpty(episodes.First().AirDate)) + result += episodes.First().AirDate; else result += "Unknown"; diff --git a/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs b/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs index dcc973e0c..d92aa5b3c 100644 --- a/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs +++ b/NzbDrone.Core/SeriesStats/SeriesStatisticsRepository.cs @@ -54,10 +54,10 @@ private string GetSelectClause() { return @"SELECT SeriesId, - SUM(CASE WHEN Monitored = 1 AND Airdate <= @currentDate THEN 1 ELSE 0 END) AS EpisodeCount, - SUM(CASE WHEN Monitored = 1 AND Episodes.EpisodeFileId > 0 AND AirDate <= @currentDate THEN 1 ELSE 0 END) AS EpisodeFileCount, + SUM(CASE WHEN Monitored = 1 AND AirdateUtc <= @currentDate THEN 1 ELSE 0 END) AS EpisodeCount, + SUM(CASE WHEN Monitored = 1 AND Episodes.EpisodeFileId > 0 AND AirDateUtc <= @currentDate THEN 1 ELSE 0 END) AS EpisodeFileCount, MAX(Episodes.SeasonNumber) as SeasonCount, - MIN(CASE WHEN AirDate < @currentDate THEN NULL ELSE AirDate END) AS NextAiringString + MIN(CASE WHEN AirDateUtc < @currentDate THEN NULL ELSE AirDate END) AS NextAiringString FROM Episodes"; } diff --git a/NzbDrone.Core/Tv/Episode.cs b/NzbDrone.Core/Tv/Episode.cs index 2afaaba10..51775a754 100644 --- a/NzbDrone.Core/Tv/Episode.cs +++ b/NzbDrone.Core/Tv/Episode.cs @@ -9,13 +9,15 @@ namespace NzbDrone.Core.Tv { public class Episode : ModelBase { + public const string AIR_DATE_FORMAT = "yyyy-MM-dd"; + public int TvDbEpisodeId { get; set; } public int SeriesId { get; set; } public int EpisodeFileId { get; set; } public int SeasonNumber { get; set; } public int EpisodeNumber { get; set; } public string Title { get; set; } - public DateTime? AirDate { get; set; } + public string AirDate { get; set; } public DateTime? AirDateUtc { get; set; } public string Overview { get; set; } @@ -37,7 +39,7 @@ public Boolean HasFile public override string ToString() { - return string.Format("[0]{1}", TvDbEpisodeId, Title.NullSafe()); + return string.Format("[{0}]{1}", TvDbEpisodeId, Title.NullSafe()); } } } \ No newline at end of file diff --git a/NzbDrone.Core/Tv/EpisodeRepository.cs b/NzbDrone.Core/Tv/EpisodeRepository.cs index 006d26ef1..9529050ab 100644 --- a/NzbDrone.Core/Tv/EpisodeRepository.cs +++ b/NzbDrone.Core/Tv/EpisodeRepository.cs @@ -49,12 +49,12 @@ public Episode Find(int seriesId, int season, int episodeNumber) public Episode Get(int seriesId, DateTime date) { - return Query.Single(s => s.SeriesId == seriesId && s.AirDate.HasValue && s.AirDate.Value.Date == date.Date); + return Query.Single(s => s.SeriesId == seriesId && s.AirDate == date.ToString(Episode.AIR_DATE_FORMAT)); } public Episode Find(int seriesId, DateTime date) { - return Query.SingleOrDefault(s => s.SeriesId == seriesId && s.AirDate.HasValue && s.AirDate.Value.Date == date.Date); + return Query.SingleOrDefault(s => s.SeriesId == seriesId && s.AirDate == date.ToString(Episode.AIR_DATE_FORMAT)); } public List GetEpisodes(int seriesId) diff --git a/NzbDrone.Core/Tv/RefreshSeriesService.cs b/NzbDrone.Core/Tv/RefreshSeriesService.cs index 85650608a..7ad4c25e5 100644 --- a/NzbDrone.Core/Tv/RefreshSeriesService.cs +++ b/NzbDrone.Core/Tv/RefreshSeriesService.cs @@ -163,14 +163,14 @@ private void RefreshEpisodeInfo(Series series, IEnumerable remoteEpisod allEpisodes.AddRange(newList); allEpisodes.AddRange(updateList); - var groups = allEpisodes.Where(c=>c.AirDate.HasValue).GroupBy(e => new { e.SeriesId, e.AirDate }).Where(g => g.Count() > 1).ToList(); + var groups = allEpisodes.Where(c=>c.AirDateUtc.HasValue).GroupBy(e => new { e.SeriesId, e.AirDate }).Where(g => g.Count() > 1).ToList(); foreach (var group in groups) { int episodeCount = 0; foreach (var episode in group.OrderBy(e => e.SeasonNumber).ThenBy(e => e.EpisodeNumber)) { - episode.AirDate = episode.AirDate.Value.AddMinutes(series.Runtime * episodeCount); + episode.AirDateUtc = episode.AirDateUtc.Value.AddMinutes(series.Runtime * episodeCount); episodeCount++; } }