From 102576cd8c5baa9077e5b1b864b52c83c23c71ae Mon Sep 17 00:00:00 2001 From: Elvendir Date: Sat, 18 Mar 2023 17:53:41 +0100 Subject: [PATCH 01/19] adddede LastPlayed as filter option in beatmap carousel --- .../Select/Carousel/CarouselBeatmap.cs | 1 + osu.Game/Screens/Select/FilterCriteria.cs | 1 + osu.Game/Screens/Select/FilterQueryParser.cs | 53 ++++++++++++++++++- 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 7e48bc5cdd..5088dfdc02 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -48,6 +48,7 @@ namespace osu.Game.Screens.Select.Carousel match &= !criteria.CircleSize.HasFilter || criteria.CircleSize.IsInRange(BeatmapInfo.Difficulty.CircleSize); match &= !criteria.OverallDifficulty.HasFilter || criteria.OverallDifficulty.IsInRange(BeatmapInfo.Difficulty.OverallDifficulty); match &= !criteria.Length.HasFilter || criteria.Length.IsInRange(BeatmapInfo.Length); + match &= !criteria.LastPlayed.HasFilter || criteria.LastPlayed.IsInRange(BeatmapInfo.LastPlayed); match &= !criteria.BPM.HasFilter || criteria.BPM.IsInRange(BeatmapInfo.BPM); match &= !criteria.BeatDivisor.HasFilter || criteria.BeatDivisor.IsInRange(BeatmapInfo.BeatDivisor); diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 320bfb1b45..a2da98368d 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -31,6 +31,7 @@ namespace osu.Game.Screens.Select public OptionalRange BPM; public OptionalRange BeatDivisor; public OptionalRange OnlineStatus; + public OptionalRange LastPlayed; public OptionalTextFilter Creator; public OptionalTextFilter Artist; diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index c86554ddbc..6b6b2f04a9 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -61,6 +61,10 @@ namespace osu.Game.Screens.Select case "length": return tryUpdateLengthRange(criteria, op, value); + case "played": + case "lastplayed": + return tryUpdateLastPlayedRange(criteria, op, value); + case "divisor": return TryUpdateCriteriaRange(ref criteria.BeatDivisor, op, value, tryParseInt); @@ -109,7 +113,8 @@ namespace osu.Game.Screens.Select value.EndsWith("ms", StringComparison.Ordinal) ? 1 : value.EndsWith('s') ? 1000 : value.EndsWith('m') ? 60000 : - value.EndsWith('h') ? 3600000 : 1000; + value.EndsWith('h') ? 3600000 : + value.EndsWith('d') ? 86400000 : 1000; private static bool tryParseFloatWithPoint(string value, out float result) => float.TryParse(value, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out result); @@ -368,5 +373,51 @@ namespace osu.Game.Screens.Select return tryUpdateCriteriaRange(ref criteria.Length, op, totalLength, minScale / 2.0); } + + private static bool tryUpdateLastPlayedRange(FilterCriteria criteria, Operator op, string val) + { + List parts = new List(); + + GroupCollection? match = null; + + match ??= tryMatchRegex(val, @"^((?\d+):)?(?\d+):(?\d+)$"); + match ??= tryMatchRegex(val, @"^((?\d+(\.\d+)?)h)?((?\d+(\.\d+)?)m)?((?\d+(\.\d+)?)s)?$"); + match ??= tryMatchRegex(val, @"^(?\d+(\.\d+)?)$"); + + if (match == null) + return false; + + if (match["seconds"].Success) + parts.Add(match["seconds"].Value + "s"); + if (match["minutes"].Success) + parts.Add(match["minutes"].Value + "m"); + if (match["hours"].Success) + parts.Add(match["hours"].Value + "h"); + if (match["days"].Success) + parts.Add(match["days"].Value + "d"); + + + double totalLength = 0; + int minScale = 86400000; + + for (int i = 0; i < parts.Count; i++) + { + string part = parts[i]; + string partNoUnit = part.TrimEnd('m', 's', 'h', 'd') ; + if (!tryParseDoubleWithPoint(partNoUnit, out double length)) + return false; + + if (i != parts.Count - 1 && length >= 60) + return false; + if (i != 0 && partNoUnit.Contains('.')) + return false; + + int scale = getLengthScale(part); + totalLength += length * scale; + minScale = Math.Min(minScale, scale); + } + + return tryUpdateCriteriaRange(ref criteria.LastPlayed, op, totalLength, minScale / 2.0); + } } } From 216a88e18d71f74ddccea1b70aff77305ec88920 Mon Sep 17 00:00:00 2001 From: Elvendir Date: Sat, 18 Mar 2023 21:35:10 +0100 Subject: [PATCH 02/19] limited max Date comparison --- .../Screens/Select/Carousel/CarouselBeatmap.cs | 2 +- osu.Game/Screens/Select/FilterQueryParser.cs | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 5088dfdc02..069d4f36d6 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Select.Carousel match &= !criteria.CircleSize.HasFilter || criteria.CircleSize.IsInRange(BeatmapInfo.Difficulty.CircleSize); match &= !criteria.OverallDifficulty.HasFilter || criteria.OverallDifficulty.IsInRange(BeatmapInfo.Difficulty.OverallDifficulty); match &= !criteria.Length.HasFilter || criteria.Length.IsInRange(BeatmapInfo.Length); - match &= !criteria.LastPlayed.HasFilter || criteria.LastPlayed.IsInRange(BeatmapInfo.LastPlayed); + match &= !criteria.LastPlayed.HasFilter || criteria.LastPlayed.IsInRange(BeatmapInfo.LastPlayed ?? DateTimeOffset.MinValue); match &= !criteria.BPM.HasFilter || criteria.BPM.IsInRange(BeatmapInfo.BPM); match &= !criteria.BeatDivisor.HasFilter || criteria.BeatDivisor.IsInRange(BeatmapInfo.BeatDivisor); diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 6b6b2f04a9..38040c2f79 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -381,7 +381,7 @@ namespace osu.Game.Screens.Select GroupCollection? match = null; match ??= tryMatchRegex(val, @"^((?\d+):)?(?\d+):(?\d+)$"); - match ??= tryMatchRegex(val, @"^((?\d+(\.\d+)?)h)?((?\d+(\.\d+)?)m)?((?\d+(\.\d+)?)s)?$"); + match ??= tryMatchRegex(val, @"^((?\d+(\.\d+)?)d)?((?\d+(\.\d+)?)h)?((?\d+(\.\d+)?)m)?((?\d+(\.\d+)?)s)?$"); match ??= tryMatchRegex(val, @"^(?\d+(\.\d+)?)$"); if (match == null) @@ -403,7 +403,7 @@ namespace osu.Game.Screens.Select for (int i = 0; i < parts.Count; i++) { string part = parts[i]; - string partNoUnit = part.TrimEnd('m', 's', 'h', 'd') ; + string partNoUnit = part.TrimEnd('m', 's', 'h', 'd'); if (!tryParseDoubleWithPoint(partNoUnit, out double length)) return false; @@ -417,7 +417,16 @@ namespace osu.Game.Screens.Select minScale = Math.Min(minScale, scale); } - return tryUpdateCriteriaRange(ref criteria.LastPlayed, op, totalLength, minScale / 2.0); + totalLength += minScale / 2; + + // Limits the date to ~2000 years compared to now + // Might want to do it differently before 4000 A.C. + double limit = 86400000; + limit *= 365 * 2000; + totalLength = Math.Min(totalLength, limit); + + DateTimeOffset dateTimeOffset = DateTimeOffset.Now; + return tryUpdateCriteriaRange(ref criteria.LastPlayed, op, dateTimeOffset.AddMilliseconds(-totalLength)); } } } From 6dead81d211a760e185ff0248a080454e6729197 Mon Sep 17 00:00:00 2001 From: Elvendir Date: Sun, 19 Mar 2023 18:43:17 +0100 Subject: [PATCH 03/19] Generalized tryUpdateLastPlayedRange to tryUpdateDateRange and Added tests --- .../Filtering/FilterQueryParserTest.cs | 82 ++++++++++++++ osu.Game/Screens/Select/FilterQueryParser.cs | 107 ++++++++++++------ 2 files changed, 152 insertions(+), 37 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index da32edb8fb..9460228644 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -316,5 +316,87 @@ namespace osu.Game.Tests.NonVisual.Filtering return false; } } + + //Date criteria testing + + private static readonly object[] correct_date_query_examples = + { + new object[] { "600" }, + new object[] { "120:120" }, + new object[] { "48:0:0" }, + new object[] { "0.5s" }, + new object[] { "120m" }, + new object[] { "48h120s" }, + new object[] { "10y24M" }, + new object[] { "10y60d120s" }, + new object[] { "0.1y0.1M2d" }, + new object[] { "0.99y0.99M2d" } + }; + + [Test] + [TestCaseSource(nameof(correct_date_query_examples))] + public void TestValidDateQueries(string dateQuery) + { + string query = $"played={dateQuery} time"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter); + } + + private static readonly object[] incorrect_date_query_examples = + { + new object[] { "7m27" }, + new object[] { "7m7m7m" }, + new object[] { "5s6m" }, + new object[] { "7d7y" }, + new object[] { ":0" }, + new object[] { "0:3:" }, + new object[] { "\"three days\"" } + }; + + [Test] + [TestCaseSource(nameof(incorrect_date_query_examples))] + public void TestInvalidDateQueries(string dateQuery) + { + string query = $"played={dateQuery} time"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual(false, filterCriteria.LastPlayed.HasFilter); + } + + private static readonly object[] list_operators = + { + new object[] { "=", false, false, true, true }, + new object[] { ":", false, false, true, true }, + new object[] { "<", false, true, false, false }, + new object[] { "<=", false, true, true, false }, + new object[] { "<:", false, true, true, false }, + new object[] { ">", true, false, false, false }, + new object[] { ">=", true, false, false, true }, + new object[] { ">:", true, false, false, true } + }; + + [Test] + [TestCaseSource(nameof(list_operators))] + public void TestComparisonDateQueries(string ope, bool minIsNull, bool maxIsNull, bool isLowerInclusive, bool isUpperInclusive) + { + string query = $"played{ope}50"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual(isLowerInclusive, filterCriteria.LastPlayed.IsLowerInclusive); + Assert.AreEqual(isUpperInclusive, filterCriteria.LastPlayed.IsUpperInclusive); + Assert.AreEqual(maxIsNull, filterCriteria.LastPlayed.Max == null); + Assert.AreEqual(minIsNull, filterCriteria.LastPlayed.Min == null); + } + + [Test] + public void TestOutofrangeDateQuery() + { + const string query = "played=10000y"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter); + Assert.AreEqual(DateTimeOffset.MinValue.AddMilliseconds(1), filterCriteria.LastPlayed.Min); + } } } diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 38040c2f79..f66f1bcd1d 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -63,7 +63,7 @@ namespace osu.Game.Screens.Select case "played": case "lastplayed": - return tryUpdateLastPlayedRange(criteria, op, value); + return tryUpdateDateRange(ref criteria.LastPlayed, op, value); case "divisor": return TryUpdateCriteriaRange(ref criteria.BeatDivisor, op, value, tryParseInt); @@ -374,59 +374,92 @@ namespace osu.Game.Screens.Select return tryUpdateCriteriaRange(ref criteria.Length, op, totalLength, minScale / 2.0); } - private static bool tryUpdateLastPlayedRange(FilterCriteria criteria, Operator op, string val) + private static bool tryUpdateDateRange(ref FilterCriteria.OptionalRange dateRange, Operator op, string val) { - List parts = new List(); - GroupCollection? match = null; match ??= tryMatchRegex(val, @"^((?\d+):)?(?\d+):(?\d+)$"); - match ??= tryMatchRegex(val, @"^((?\d+(\.\d+)?)d)?((?\d+(\.\d+)?)h)?((?\d+(\.\d+)?)m)?((?\d+(\.\d+)?)s)?$"); + match ??= tryMatchRegex(val, @"^((?\d+(\.\d+)?)y)?((?\d+(\.\d+)?)M)?((?\d+(\.\d+)?)d)?((?\d+(\.\d+)?)h)?((?\d+(\.\d+)?)m)?((?\d+(\.\d+)?)s)?$"); match ??= tryMatchRegex(val, @"^(?\d+(\.\d+)?)$"); if (match == null) return false; - if (match["seconds"].Success) - parts.Add(match["seconds"].Value + "s"); - if (match["minutes"].Success) - parts.Add(match["minutes"].Value + "m"); - if (match["hours"].Success) - parts.Add(match["hours"].Value + "h"); - if (match["days"].Success) - parts.Add(match["days"].Value + "d"); + DateTimeOffset dateTimeOffset = DateTimeOffset.Now; - - double totalLength = 0; - int minScale = 86400000; - - for (int i = 0; i < parts.Count; i++) + try { - string part = parts[i]; - string partNoUnit = part.TrimEnd('m', 's', 'h', 'd'); - if (!tryParseDoubleWithPoint(partNoUnit, out double length)) - return false; + List keys = new List { "seconds", "minutes", "hours", "days", "months", "years" }; - if (i != parts.Count - 1 && length >= 60) - return false; - if (i != 0 && partNoUnit.Contains('.')) - return false; + foreach (string key in keys) + { + if (match[key].Success) + { + if (!tryParseDoubleWithPoint(match[key].Value, out double length)) + return false; - int scale = getLengthScale(part); - totalLength += length * scale; - minScale = Math.Min(minScale, scale); + switch (key) + { + case "seconds": + dateTimeOffset = dateTimeOffset.AddSeconds(-length); + break; + + case "minutes": + dateTimeOffset = dateTimeOffset.AddMinutes(-length); + break; + + case "hours": + dateTimeOffset = dateTimeOffset.AddHours(-length); + break; + + case "days": + dateTimeOffset = dateTimeOffset.AddDays(-length); + break; + + case "months": + dateTimeOffset = dateTimeOffset.AddMonths(-(int)Math.Round(length)); + break; + + case "years": + dateTimeOffset = dateTimeOffset.AddYears(-(int)Math.Round(length)); + break; + } + } + } + } + // If DateTime to compare is out-scope put it to Min + catch (Exception) + { + dateTimeOffset = DateTimeOffset.MinValue; + dateTimeOffset = dateTimeOffset.AddMilliseconds(1); } - totalLength += minScale / 2; + return tryUpdateCriteriaRange(ref dateRange, invert(op), dateTimeOffset); + } - // Limits the date to ~2000 years compared to now - // Might want to do it differently before 4000 A.C. - double limit = 86400000; - limit *= 365 * 2000; - totalLength = Math.Min(totalLength, limit); + // Function to reverse an Operator + private static Operator invert(Operator ope) + { + switch (ope) + { + default: + return Operator.Equal; - DateTimeOffset dateTimeOffset = DateTimeOffset.Now; - return tryUpdateCriteriaRange(ref criteria.LastPlayed, op, dateTimeOffset.AddMilliseconds(-totalLength)); + case Operator.Equal: + return Operator.Equal; + + case Operator.Greater: + return Operator.Less; + + case Operator.GreaterOrEqual: + return Operator.LessOrEqual; + + case Operator.Less: + return Operator.Greater; + + case Operator.LessOrEqual: + return Operator.GreaterOrEqual; + } } } } From 4b053b47852ea46ad104156d81a84e3bdaaf3b04 Mon Sep 17 00:00:00 2001 From: Elvendir Date: Sat, 1 Apr 2023 22:58:25 +0200 Subject: [PATCH 04/19] changed regex match to be inline with standard --- .../NonVisual/Filtering/FilterQueryParserTest.cs | 3 +-- osu.Game/Screens/Select/FilterQueryParser.cs | 9 +++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 9460228644..b0cc9146d2 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -322,8 +322,6 @@ namespace osu.Game.Tests.NonVisual.Filtering private static readonly object[] correct_date_query_examples = { new object[] { "600" }, - new object[] { "120:120" }, - new object[] { "48:0:0" }, new object[] { "0.5s" }, new object[] { "120m" }, new object[] { "48h120s" }, @@ -350,6 +348,7 @@ namespace osu.Game.Tests.NonVisual.Filtering new object[] { "5s6m" }, new object[] { "7d7y" }, new object[] { ":0" }, + new object[] { "0:3:6" }, new object[] { "0:3:" }, new object[] { "\"three days\"" } }; diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index f66f1bcd1d..f66b8fd377 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -378,9 +378,8 @@ namespace osu.Game.Screens.Select { GroupCollection? match = null; - match ??= tryMatchRegex(val, @"^((?\d+):)?(?\d+):(?\d+)$"); match ??= tryMatchRegex(val, @"^((?\d+(\.\d+)?)y)?((?\d+(\.\d+)?)M)?((?\d+(\.\d+)?)d)?((?\d+(\.\d+)?)h)?((?\d+(\.\d+)?)m)?((?\d+(\.\d+)?)s)?$"); - match ??= tryMatchRegex(val, @"^(?\d+(\.\d+)?)$"); + match ??= tryMatchRegex(val, @"^(?\d+(\.\d+)?)$"); if (match == null) return false; @@ -417,11 +416,13 @@ namespace osu.Game.Screens.Select break; case "months": - dateTimeOffset = dateTimeOffset.AddMonths(-(int)Math.Round(length)); + dateTimeOffset = dateTimeOffset.AddMonths(-(int)Math.Floor(length)); + dateTimeOffset = dateTimeOffset.AddDays(-30 * (length - Math.Floor(length))); break; case "years": - dateTimeOffset = dateTimeOffset.AddYears(-(int)Math.Round(length)); + dateTimeOffset = dateTimeOffset.AddYears(-(int)Math.Floor(length)); + dateTimeOffset = dateTimeOffset.AddDays(-365 * (length - Math.Floor(length))); break; } } From 52adb99fe5fe4f7f7ea756f0723354f17904203e Mon Sep 17 00:00:00 2001 From: Elvendir <39671719+Elvendir@users.noreply.github.com> Date: Tue, 4 Apr 2023 19:29:37 +0200 Subject: [PATCH 05/19] Update osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index b0cc9146d2..627b44dcd7 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -317,7 +317,6 @@ namespace osu.Game.Tests.NonVisual.Filtering } } - //Date criteria testing private static readonly object[] correct_date_query_examples = { From d6c6507578a0fd3a7a8a502c15febd91b0123069 Mon Sep 17 00:00:00 2001 From: Elvendir <39671719+Elvendir@users.noreply.github.com> Date: Tue, 4 Apr 2023 19:30:13 +0200 Subject: [PATCH 06/19] Update osu.Game/Screens/Select/FilterQueryParser.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Screens/Select/FilterQueryParser.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index f66b8fd377..9582694248 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -428,8 +428,7 @@ namespace osu.Game.Screens.Select } } } - // If DateTime to compare is out-scope put it to Min - catch (Exception) + catch (ArgumentOutOfRangeException) { dateTimeOffset = DateTimeOffset.MinValue; dateTimeOffset = dateTimeOffset.AddMilliseconds(1); From 0c1d6eb89465066285668b619bc73a54c9fa964c Mon Sep 17 00:00:00 2001 From: Elvendir Date: Wed, 5 Apr 2023 11:42:39 +0200 Subject: [PATCH 07/19] - rewrote upper and lower bound tests - removed equality in tests --- .../Filtering/FilterQueryParserTest.cs | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 627b44dcd7..c1678f14a6 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -317,7 +317,6 @@ namespace osu.Game.Tests.NonVisual.Filtering } } - private static readonly object[] correct_date_query_examples = { new object[] { "600" }, @@ -334,7 +333,7 @@ namespace osu.Game.Tests.NonVisual.Filtering [TestCaseSource(nameof(correct_date_query_examples))] public void TestValidDateQueries(string dateQuery) { - string query = $"played={dateQuery} time"; + string query = $"played<{dateQuery} time"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter); @@ -342,11 +341,11 @@ namespace osu.Game.Tests.NonVisual.Filtering private static readonly object[] incorrect_date_query_examples = { + new object[] { ".5s" }, new object[] { "7m27" }, new object[] { "7m7m7m" }, new object[] { "5s6m" }, new object[] { "7d7y" }, - new object[] { ":0" }, new object[] { "0:3:6" }, new object[] { "0:3:" }, new object[] { "\"three days\"" } @@ -356,41 +355,36 @@ namespace osu.Game.Tests.NonVisual.Filtering [TestCaseSource(nameof(incorrect_date_query_examples))] public void TestInvalidDateQueries(string dateQuery) { - string query = $"played={dateQuery} time"; + string query = $"played<{dateQuery} time"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual(false, filterCriteria.LastPlayed.HasFilter); } - private static readonly object[] list_operators = - { - new object[] { "=", false, false, true, true }, - new object[] { ":", false, false, true, true }, - new object[] { "<", false, true, false, false }, - new object[] { "<=", false, true, true, false }, - new object[] { "<:", false, true, true, false }, - new object[] { ">", true, false, false, false }, - new object[] { ">=", true, false, false, true }, - new object[] { ">:", true, false, false, true } - }; - [Test] - [TestCaseSource(nameof(list_operators))] - public void TestComparisonDateQueries(string ope, bool minIsNull, bool maxIsNull, bool isLowerInclusive, bool isUpperInclusive) + public void TestGreaterDateQuery() { - string query = $"played{ope}50"; + const string query = "played>50"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); - Assert.AreEqual(isLowerInclusive, filterCriteria.LastPlayed.IsLowerInclusive); - Assert.AreEqual(isUpperInclusive, filterCriteria.LastPlayed.IsUpperInclusive); - Assert.AreEqual(maxIsNull, filterCriteria.LastPlayed.Max == null); - Assert.AreEqual(minIsNull, filterCriteria.LastPlayed.Min == null); + Assert.AreEqual(false, filterCriteria.LastPlayed.Max == null); + Assert.AreEqual(true, filterCriteria.LastPlayed.Min == null); + } + + [Test] + public void TestLowerDateQuery() + { + const string query = "played<50"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual(true, filterCriteria.LastPlayed.Max == null); + Assert.AreEqual(false, filterCriteria.LastPlayed.Min == null); } [Test] public void TestOutofrangeDateQuery() { - const string query = "played=10000y"; + const string query = "played<10000y"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter); From df170517a85ea1949d20b6df1043143ac028ffec Mon Sep 17 00:00:00 2001 From: Elvendir Date: Wed, 5 Apr 2023 11:59:31 +0200 Subject: [PATCH 08/19] -renamed function inverse() to reverseInequalityOperator() for clarity -changed default case of reverseInequalityOperator() to out of range exception --- osu.Game/Screens/Select/FilterQueryParser.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 9582694248..bd4c29e457 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -434,16 +434,16 @@ namespace osu.Game.Screens.Select dateTimeOffset = dateTimeOffset.AddMilliseconds(1); } - return tryUpdateCriteriaRange(ref dateRange, invert(op), dateTimeOffset); + return tryUpdateCriteriaRange(ref dateRange, reverseInequalityOperator(op), dateTimeOffset); } // Function to reverse an Operator - private static Operator invert(Operator ope) + private static Operator reverseInequalityOperator(Operator ope) { switch (ope) { default: - return Operator.Equal; + throw new ArgumentOutOfRangeException(nameof(ope), $"Unsupported operator {ope}"); case Operator.Equal: return Operator.Equal; From c2f225f0253030ea03e710f1647da26f6b106377 Mon Sep 17 00:00:00 2001 From: Elvendir Date: Wed, 5 Apr 2023 21:25:58 +0200 Subject: [PATCH 09/19] Made Operator.Equal not parse for date filter and added corresponding test --- .../NonVisual/Filtering/FilterQueryParserTest.cs | 9 +++++++++ osu.Game/Screens/Select/FilterQueryParser.cs | 3 +++ 2 files changed, 12 insertions(+) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index c1678f14a6..450e6bdde7 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -381,6 +381,15 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.AreEqual(false, filterCriteria.LastPlayed.Min == null); } + [Test] + public void TestEqualDateQuery() + { + const string query = "played=50"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual(false, filterCriteria.LastPlayed.HasFilter); + } + [Test] public void TestOutofrangeDateQuery() { diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index bd4c29e457..70893c188d 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -376,6 +376,9 @@ namespace osu.Game.Screens.Select private static bool tryUpdateDateRange(ref FilterCriteria.OptionalRange dateRange, Operator op, string val) { + if (op == Operator.Equal) + return false; + GroupCollection? match = null; match ??= tryMatchRegex(val, @"^((?\d+(\.\d+)?)y)?((?\d+(\.\d+)?)M)?((?\d+(\.\d+)?)d)?((?\d+(\.\d+)?)h)?((?\d+(\.\d+)?)m)?((?\d+(\.\d+)?)s)?$"); From 928145cdeb3b523833ed6b5f3f85c830d91f4dc8 Mon Sep 17 00:00:00 2001 From: Elvendir Date: Wed, 5 Apr 2023 22:12:15 +0200 Subject: [PATCH 10/19] Enforce integer value before y and M Change impacted Tests --- .../NonVisual/Filtering/FilterQueryParserTest.cs | 8 +++++--- osu.Game/Screens/Select/FilterQueryParser.cs | 12 ++++++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 450e6bdde7..78b428e7c0 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -325,8 +325,8 @@ namespace osu.Game.Tests.NonVisual.Filtering new object[] { "48h120s" }, new object[] { "10y24M" }, new object[] { "10y60d120s" }, - new object[] { "0.1y0.1M2d" }, - new object[] { "0.99y0.99M2d" } + new object[] { "0y0M2d" }, + new object[] { "1y1M2d" } }; [Test] @@ -348,7 +348,9 @@ namespace osu.Game.Tests.NonVisual.Filtering new object[] { "7d7y" }, new object[] { "0:3:6" }, new object[] { "0:3:" }, - new object[] { "\"three days\"" } + new object[] { "\"three days\"" }, + new object[] { "0.1y0.1M2d" }, + new object[] { "0.99y0.99M2d" } }; [Test] diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 70893c188d..b49f0ba057 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -419,13 +419,17 @@ namespace osu.Game.Screens.Select break; case "months": - dateTimeOffset = dateTimeOffset.AddMonths(-(int)Math.Floor(length)); - dateTimeOffset = dateTimeOffset.AddDays(-30 * (length - Math.Floor(length))); + if (match[key].Value.Contains('.')) + return false; + + dateTimeOffset = dateTimeOffset.AddMonths(-(int)length); break; case "years": - dateTimeOffset = dateTimeOffset.AddYears(-(int)Math.Floor(length)); - dateTimeOffset = dateTimeOffset.AddDays(-365 * (length - Math.Floor(length))); + if (match[key].Value.Contains('.')) + return false; + + dateTimeOffset = dateTimeOffset.AddYears(-(int)length); break; } } From 8e156fdb5102af8cd71aaac32d3c36b3c018d382 Mon Sep 17 00:00:00 2001 From: Elvendir Date: Fri, 7 Apr 2023 00:29:46 +0200 Subject: [PATCH 11/19] Enforce integer through regex match instead --- osu.Game/Screens/Select/FilterQueryParser.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index b49f0ba057..348f663b8e 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -381,7 +381,7 @@ namespace osu.Game.Screens.Select GroupCollection? match = null; - match ??= tryMatchRegex(val, @"^((?\d+(\.\d+)?)y)?((?\d+(\.\d+)?)M)?((?\d+(\.\d+)?)d)?((?\d+(\.\d+)?)h)?((?\d+(\.\d+)?)m)?((?\d+(\.\d+)?)s)?$"); + match ??= tryMatchRegex(val, @"^((?\d+)y)?((?\d+)M)?((?\d+(\.\d+)?)d)?((?\d+(\.\d+)?)h)?((?\d+(\.\d+)?)m)?((?\d+(\.\d+)?)s)?$"); match ??= tryMatchRegex(val, @"^(?\d+(\.\d+)?)$"); if (match == null) @@ -419,16 +419,10 @@ namespace osu.Game.Screens.Select break; case "months": - if (match[key].Value.Contains('.')) - return false; - dateTimeOffset = dateTimeOffset.AddMonths(-(int)length); break; case "years": - if (match[key].Value.Contains('.')) - return false; - dateTimeOffset = dateTimeOffset.AddYears(-(int)length); break; } From f0f37df67fea4f20cc117012457ffd2c9b3bfec7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Feb 2024 15:27:17 +0100 Subject: [PATCH 12/19] Revert unnecessary change --- osu.Game/Screens/Select/FilterQueryParser.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 24580c9e96..5fb5859a3b 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -121,8 +121,7 @@ namespace osu.Game.Screens.Select value.EndsWith("ms", StringComparison.Ordinal) ? 1 : value.EndsWith('s') ? 1000 : value.EndsWith('m') ? 60000 : - value.EndsWith('h') ? 3600000 : - value.EndsWith('d') ? 86400000 : 1000; + value.EndsWith('h') ? 3600000 : 1000; private static bool tryParseFloatWithPoint(string value, out float result) => float.TryParse(value, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out result); From d7dfc8b88aa5081a6765898a94097050c8441c2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Feb 2024 15:55:57 +0100 Subject: [PATCH 13/19] Add failing test coverage for empty date filter not parsing --- osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 81a73fc99f..814b26a231 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -488,7 +488,8 @@ namespace osu.Game.Tests.NonVisual.Filtering new object[] { "0:3:" }, new object[] { "\"three days\"" }, new object[] { "0.1y0.1M2d" }, - new object[] { "0.99y0.99M2d" } + new object[] { "0.99y0.99M2d" }, + new object[] { string.Empty } }; [Test] From c24328dda347f8cd2196267790e225bb3f038d6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Feb 2024 15:56:32 +0100 Subject: [PATCH 14/19] Abandon date filter if no meaningful time bound found during parsing --- osu.Game/Screens/Select/FilterQueryParser.cs | 23 +++++++++++--------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 5fb5859a3b..3612a84ff9 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -394,7 +394,8 @@ namespace osu.Game.Screens.Select if (match == null) return false; - DateTimeOffset dateTimeOffset = DateTimeOffset.Now; + DateTimeOffset? dateTimeOffset = null; + DateTimeOffset now = DateTimeOffset.Now; try { @@ -410,27 +411,27 @@ namespace osu.Game.Screens.Select switch (key) { case "seconds": - dateTimeOffset = dateTimeOffset.AddSeconds(-length); + dateTimeOffset = (dateTimeOffset ?? now).AddSeconds(-length); break; case "minutes": - dateTimeOffset = dateTimeOffset.AddMinutes(-length); + dateTimeOffset = (dateTimeOffset ?? now).AddMinutes(-length); break; case "hours": - dateTimeOffset = dateTimeOffset.AddHours(-length); + dateTimeOffset = (dateTimeOffset ?? now).AddHours(-length); break; case "days": - dateTimeOffset = dateTimeOffset.AddDays(-length); + dateTimeOffset = (dateTimeOffset ?? now).AddDays(-length); break; case "months": - dateTimeOffset = dateTimeOffset.AddMonths(-(int)length); + dateTimeOffset = (dateTimeOffset ?? now).AddMonths(-(int)length); break; case "years": - dateTimeOffset = dateTimeOffset.AddYears(-(int)length); + dateTimeOffset = (dateTimeOffset ?? now).AddYears(-(int)length); break; } } @@ -438,11 +439,13 @@ namespace osu.Game.Screens.Select } catch (ArgumentOutOfRangeException) { - dateTimeOffset = DateTimeOffset.MinValue; - dateTimeOffset = dateTimeOffset.AddMilliseconds(1); + dateTimeOffset = DateTimeOffset.MinValue.AddMilliseconds(1); } - return tryUpdateCriteriaRange(ref dateRange, reverseInequalityOperator(op), dateTimeOffset); + if (!dateTimeOffset.HasValue) + return false; + + return tryUpdateCriteriaRange(ref dateRange, reverseInequalityOperator(op), dateTimeOffset.Value); } // Function to reverse an Operator From a8ae0a032f67900b8466263ac3ceb5f1b79b95d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Feb 2024 15:58:38 +0100 Subject: [PATCH 15/19] Simplify parsing --- osu.Game/Screens/Select/FilterQueryParser.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 3612a84ff9..17af0ee8ba 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -403,9 +403,12 @@ namespace osu.Game.Screens.Select foreach (string key in keys) { - if (match[key].Success) + if (!match.TryGetValue(key, out var group) || !group.Success) + continue; + + if (group.Success) { - if (!tryParseDoubleWithPoint(match[key].Value, out double length)) + if (!tryParseDoubleWithPoint(group.Value, out double length)) return false; switch (key) From f1d69abdc8d56502b23beccf62827518275cdc90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Feb 2024 15:59:07 +0100 Subject: [PATCH 16/19] Rename test --- osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 814b26a231..a5aac0a4ce 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -532,7 +532,7 @@ namespace osu.Game.Tests.NonVisual.Filtering } [Test] - public void TestOutofrangeDateQuery() + public void TestOutOfRangeDateQuery() { const string query = "played<10000y"; var filterCriteria = new FilterCriteria(); From 414066fd34f18db433fc4c3c2fc10f3624989924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Feb 2024 16:07:42 +0100 Subject: [PATCH 17/19] Inline problematic function (and rename things to make more sense) --- osu.Game/Screens/Select/FilterQueryParser.cs | 67 +++++++++++--------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 17af0ee8ba..278ca1ac5f 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -63,7 +63,7 @@ namespace osu.Game.Screens.Select case "played": case "lastplayed": - return tryUpdateDateRange(ref criteria.LastPlayed, op, value); + return tryUpdateDateAgoRange(ref criteria.LastPlayed, op, value); case "divisor": return TryUpdateCriteriaRange(ref criteria.BeatDivisor, op, value, tryParseInt); @@ -381,10 +381,42 @@ namespace osu.Game.Screens.Select return tryUpdateCriteriaRange(ref criteria.Length, op, totalLength, minScale / 2.0); } - private static bool tryUpdateDateRange(ref FilterCriteria.OptionalRange dateRange, Operator op, string val) + /// + /// This function is intended for parsing "days / months / years ago" type filters. + /// + private static bool tryUpdateDateAgoRange(ref FilterCriteria.OptionalRange dateRange, Operator op, string val) { - if (op == Operator.Equal) - return false; + switch (op) + { + case Operator.Equal: + // an equality filter is difficult to define for support here. + // if "3 months 2 days ago" means a single concrete time instant, such a filter is basically useless. + // if it means a range of 24 hours, then that is annoying to write and also comes with its own implications + // (does it mean "time instant 3 months 2 days ago, within 12 hours of tolerance either direction"? + // does it mean "the full calendar day, from midnight to midnight, 3 months 2 days ago"?) + // as such, for simplicity, just refuse to support this. + return false; + + // for the remaining operators, since the value provided to this function is an "ago" type value + // (as in, referring to some amount of time back), + // we'll want to flip the operator, such that `>5d` means "more than five days ago", as in "*before* five days ago", + // as intended by the user. + case Operator.Less: + op = Operator.Greater; + break; + + case Operator.LessOrEqual: + op = Operator.GreaterOrEqual; + break; + + case Operator.Greater: + op = Operator.Less; + break; + + case Operator.GreaterOrEqual: + op = Operator.LessOrEqual; + break; + } GroupCollection? match = null; @@ -448,32 +480,7 @@ namespace osu.Game.Screens.Select if (!dateTimeOffset.HasValue) return false; - return tryUpdateCriteriaRange(ref dateRange, reverseInequalityOperator(op), dateTimeOffset.Value); - } - - // Function to reverse an Operator - private static Operator reverseInequalityOperator(Operator ope) - { - switch (ope) - { - default: - throw new ArgumentOutOfRangeException(nameof(ope), $"Unsupported operator {ope}"); - - case Operator.Equal: - return Operator.Equal; - - case Operator.Greater: - return Operator.Less; - - case Operator.GreaterOrEqual: - return Operator.LessOrEqual; - - case Operator.Less: - return Operator.Greater; - - case Operator.LessOrEqual: - return Operator.GreaterOrEqual; - } + return tryUpdateCriteriaRange(ref dateRange, op, dateTimeOffset.Value); } } } From f7bea00564b7ac537b92a4eb9c4fe395bffb27d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Feb 2024 16:19:32 +0100 Subject: [PATCH 18/19] Improve test coverage --- .../Filtering/FilterQueryParserTest.cs | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index a5aac0a4ce..12d6060351 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -508,8 +508,11 @@ namespace osu.Game.Tests.NonVisual.Filtering const string query = "played>50"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); - Assert.AreEqual(false, filterCriteria.LastPlayed.Max == null); - Assert.AreEqual(true, filterCriteria.LastPlayed.Min == null); + Assert.That(filterCriteria.LastPlayed.Max, Is.Not.Null); + Assert.That(filterCriteria.LastPlayed.Min, Is.Null); + // the parser internally references `DateTimeOffset.Now`, so to not make things too annoying for tests, just assume some tolerance + // (irrelevant in proportion to the actual filter proscribed). + Assert.That(filterCriteria.LastPlayed.Max, Is.EqualTo(DateTimeOffset.Now.AddDays(-50)).Within(TimeSpan.FromSeconds(5))); } [Test] @@ -518,8 +521,25 @@ namespace osu.Game.Tests.NonVisual.Filtering const string query = "played<50"; var filterCriteria = new FilterCriteria(); FilterQueryParser.ApplyQueries(filterCriteria, query); - Assert.AreEqual(true, filterCriteria.LastPlayed.Max == null); - Assert.AreEqual(false, filterCriteria.LastPlayed.Min == null); + Assert.That(filterCriteria.LastPlayed.Max, Is.Null); + Assert.That(filterCriteria.LastPlayed.Min, Is.Not.Null); + // the parser internally references `DateTimeOffset.Now`, so to not make things too annoying for tests, just assume some tolerance + // (irrelevant in proportion to the actual filter proscribed). + Assert.That(filterCriteria.LastPlayed.Min, Is.EqualTo(DateTimeOffset.Now.AddDays(-50)).Within(TimeSpan.FromSeconds(5))); + } + + [Test] + public void TestBothSidesDateQuery() + { + const string query = "played>3M played<1y6M"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.That(filterCriteria.LastPlayed.Min, Is.Not.Null); + Assert.That(filterCriteria.LastPlayed.Max, Is.Not.Null); + // the parser internally references `DateTimeOffset.Now`, so to not make things too annoying for tests, just assume some tolerance + // (irrelevant in proportion to the actual filter proscribed). + Assert.That(filterCriteria.LastPlayed.Min, Is.EqualTo(DateTimeOffset.Now.AddYears(-1).AddMonths(-6)).Within(TimeSpan.FromSeconds(5))); + Assert.That(filterCriteria.LastPlayed.Max, Is.EqualTo(DateTimeOffset.Now.AddMonths(-3)).Within(TimeSpan.FromSeconds(5))); } [Test] From ae9a2661ace43a96a4fbf26072ed3efd0dc0ba54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 14 Feb 2024 16:20:47 +0100 Subject: [PATCH 19/19] Sprinkle some raw string prefixes --- osu.Game/Screens/Select/FilterQueryParser.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 278ca1ac5f..2c4077dacf 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -431,7 +431,7 @@ namespace osu.Game.Screens.Select try { - List keys = new List { "seconds", "minutes", "hours", "days", "months", "years" }; + List keys = new List { @"seconds", @"minutes", @"hours", @"days", @"months", @"years" }; foreach (string key in keys) { @@ -445,27 +445,27 @@ namespace osu.Game.Screens.Select switch (key) { - case "seconds": + case @"seconds": dateTimeOffset = (dateTimeOffset ?? now).AddSeconds(-length); break; - case "minutes": + case @"minutes": dateTimeOffset = (dateTimeOffset ?? now).AddMinutes(-length); break; - case "hours": + case @"hours": dateTimeOffset = (dateTimeOffset ?? now).AddHours(-length); break; - case "days": + case @"days": dateTimeOffset = (dateTimeOffset ?? now).AddDays(-length); break; - case "months": + case @"months": dateTimeOffset = (dateTimeOffset ?? now).AddMonths(-(int)length); break; - case "years": + case @"years": dateTimeOffset = (dateTimeOffset ?? now).AddYears(-(int)length); break; }