diff --git a/osu.Game/Extensions/TimeDisplayExtensions.cs b/osu.Game/Extensions/TimeDisplayExtensions.cs index dc05482a05..ee6f458398 100644 --- a/osu.Game/Extensions/TimeDisplayExtensions.cs +++ b/osu.Game/Extensions/TimeDisplayExtensions.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; +using Humanizer; using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Extensions { @@ -47,5 +49,54 @@ namespace osu.Game.Extensions return new LocalisableFormattableString(timeSpan, @"mm\:ss"); } + + /// + /// Formats a provided date to a short relative string version for compact display. + /// + /// The time to be displayed. + /// A timespan denoting the time length beneath which "now" should be displayed. + /// A short relative string representing the input time. + public static string ToShortRelativeTime(this DateTimeOffset time, TimeSpan lowerCutoff) + { + var now = DateTime.Now; + var difference = now - time; + + // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. + // this is intended to be a best-effort, more legible approximation of that. + // compare: + // * https://github.com/ppy/osu-web/blob/a8f5a68fb435cb19a4faa4c7c4bce08c4f096933/resources/assets/lib/scoreboard-time.tsx + // * https://momentjs.com/docs/#/customization/ (reference for the customisation format) + + // TODO: support localisation (probably via `CommonStrings.CountHours()` etc.) + // requires pluralisable string support framework-side + + if (difference < lowerCutoff) + return CommonStrings.TimeNow.ToString(); + + if (difference.TotalMinutes < 1) + return "sec".ToQuantity((int)difference.TotalSeconds); + if (difference.TotalHours < 1) + return "min".ToQuantity((int)difference.TotalMinutes); + if (difference.TotalDays < 1) + return "hr".ToQuantity((int)difference.TotalHours); + + // this is where this gets more complicated because of how the calendar works. + // since there's no `TotalMonths` / `TotalYears`, we have to iteratively add months/years + // and test against cutoff dates to determine how many months/years to show. + + if (time > now.AddMonths(-1)) + return difference.TotalDays < 2 ? "1dy" : $"{(int)difference.TotalDays}dys"; + + for (int months = 1; months <= 11; ++months) + { + if (time > now.AddMonths(-(months + 1))) + return months == 1 ? "1mo" : $"{months}mos"; + } + + int years = 1; + while (time <= now.AddYears(-(years + 1))) + years += 1; + return years == 1 ? "1yr" : $"{years}yrs"; + } } } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index a9f03e5c9f..ddd9d9a2b2 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -17,6 +17,7 @@ using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Game.Database; +using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -395,7 +396,7 @@ namespace osu.Game.Online.Leaderboards Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold, italics: true); } - protected override string Format() => ScoreboardTimeUtils.FormatRelativeTime(Date, TimeSpan.FromSeconds(30)); + protected override string Format() => Date.ToShortRelativeTime(TimeSpan.FromSeconds(30)); } public class LeaderboardScoreStatistic diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs index 1927e66edb..5018fb8c70 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreboardTime.cs @@ -2,8 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Game.Extensions; using osu.Game.Graphics; -using osu.Game.Utils; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -15,6 +15,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } protected override string Format() - => ScoreboardTimeUtils.FormatRelativeTime(Date, TimeSpan.FromHours(1)); + => Date.ToShortRelativeTime(TimeSpan.FromHours(1)); } } diff --git a/osu.Game/Utils/ScoreboardTimeUtils.cs b/osu.Game/Utils/ScoreboardTimeUtils.cs index 4e079ff39f..ec222de018 100644 --- a/osu.Game/Utils/ScoreboardTimeUtils.cs +++ b/osu.Game/Utils/ScoreboardTimeUtils.cs @@ -10,58 +10,6 @@ namespace osu.Game.Utils { public static class ScoreboardTimeUtils { - /// - /// Formats a provided date to a short relative string version for compact display. - /// - /// The time to be displayed. - /// A timespan denoting the time length beneath which "now" should be displayed. - /// A short relative string representing the input time. - public static string FormatRelativeTime(DateTimeOffset time, TimeSpan lowerCutoff) - { - // web uses momentjs's custom locales to format the date for the purposes of the scoreboard. - // this is intended to be a best-effort, more legible approximation of that. - // compare: - // * https://github.com/ppy/osu-web/blob/a8f5a68fb435cb19a4faa4c7c4bce08c4f096933/resources/assets/lib/scoreboard-time.tsx - // * https://momentjs.com/docs/#/customization/ (reference for the customisation format) - - // TODO: support localisation (probably via `CommonStrings.CountHours()` etc.) - // requires pluralisable string support framework-side - - var now = DateTime.Now; - var span = now - time; - - if (span < lowerCutoff) - return CommonStrings.TimeNow.ToString(); - - if (span.TotalMinutes < 1) - return formatQuantity("sec", (int)span.TotalSeconds); - if (span.TotalHours < 1) - return formatQuantity("min", (int)span.TotalMinutes); - if (span.TotalDays < 1) - return formatQuantity("hr", (int)span.TotalHours); - - // this is where this gets more complicated because of how the calendar works. - // since there's no `TotalMonths` / `TotalYears`, we have to iteratively add months/years - // and test against cutoff dates to determine how many months/years to show. - - if (time > now.AddMonths(-1)) - return formatQuantity("dy", (int)span.TotalDays); - - for (int months = 1; months <= 11; ++months) - { - if (time > now.AddMonths(-(months + 1))) - return formatQuantity("mo", months); - } - - int years = 1; - // Add upper bound to prevent a crash - while (years < 20 && time <= now.AddYears(-(years + 1))) - years += 1; - if (years < 20) - return formatQuantity("yr", years); - - return "never"; - } private static string formatQuantity(string template, int quantity) {