mirror of
https://github.com/ppy/osu.git
synced 2024-11-15 09:07:25 +08:00
Merge pull request #30145 from WitherFlower/ranked-date-filtering
Add ranked date and submitted date filtering to song select
This commit is contained in:
commit
dafe8d6448
@ -627,6 +627,87 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
||||
Assert.AreEqual(DateTimeOffset.MinValue.AddMilliseconds(1), filterCriteria.LastPlayed.Min);
|
||||
}
|
||||
|
||||
private static DateTimeOffset dateTimeOffsetFromDateOnly(int year, int month, int day) =>
|
||||
new DateTimeOffset(year, month, day, 0, 0, 0, TimeSpan.Zero);
|
||||
|
||||
private static readonly object[] ranked_date_valid_test_cases =
|
||||
{
|
||||
new object[] { "ranked<2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Max },
|
||||
new object[] { "ranked<2012.03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Max },
|
||||
new object[] { "ranked<2012/03/05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Max },
|
||||
new object[] { "ranked<2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Max },
|
||||
|
||||
new object[] { "ranked<=2012", dateTimeOffsetFromDateOnly(2013, 1, 1), (FilterCriteria x) => x.DateRanked.Max },
|
||||
new object[] { "ranked<=2012.03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Max },
|
||||
new object[] { "ranked<=2012/03/05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Max },
|
||||
new object[] { "ranked<=2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Max },
|
||||
|
||||
new object[] { "ranked>2012", dateTimeOffsetFromDateOnly(2013, 1, 1), (FilterCriteria x) => x.DateRanked.Min },
|
||||
new object[] { "ranked>2012.03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Min },
|
||||
new object[] { "ranked>2012/03/05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Min },
|
||||
new object[] { "ranked>2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Min },
|
||||
|
||||
new object[] { "ranked>=2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Min },
|
||||
new object[] { "ranked>=2012.03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Min },
|
||||
new object[] { "ranked>=2012/03/05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Min },
|
||||
new object[] { "ranked>=2012-3-5", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Min },
|
||||
|
||||
new object[] { "ranked=2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Min },
|
||||
new object[] { "ranked=2012", dateTimeOffsetFromDateOnly(2012, 1, 1), (FilterCriteria x) => x.DateRanked.Min },
|
||||
new object[] { "ranked=2012-03", dateTimeOffsetFromDateOnly(2012, 3, 1), (FilterCriteria x) => x.DateRanked.Min },
|
||||
new object[] { "ranked=2012-03", dateTimeOffsetFromDateOnly(2012, 4, 1), (FilterCriteria x) => x.DateRanked.Max },
|
||||
new object[] { "ranked=2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 5), (FilterCriteria x) => x.DateRanked.Min },
|
||||
new object[] { "ranked=2012-03-05", dateTimeOffsetFromDateOnly(2012, 3, 6), (FilterCriteria x) => x.DateRanked.Max },
|
||||
};
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(ranked_date_valid_test_cases))]
|
||||
public void TestValidRankedDateQueries(string query, DateTimeOffset expected, Func<FilterCriteria, DateTimeOffset?> f)
|
||||
{
|
||||
var filterCriteria = new FilterCriteria();
|
||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||
Assert.AreEqual(true, filterCriteria.DateRanked.HasFilter);
|
||||
Assert.AreEqual(expected, f(filterCriteria));
|
||||
}
|
||||
|
||||
private static readonly object[] ranked_date_invalid_test_cases =
|
||||
{
|
||||
new object[] { "ranked<0" },
|
||||
new object[] { "ranked=99999" },
|
||||
new object[] { "ranked>=2012-03-05-04" },
|
||||
};
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(ranked_date_invalid_test_cases))]
|
||||
public void TestInvalidRankedDateQueries(string query)
|
||||
{
|
||||
var filterCriteria = new FilterCriteria();
|
||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||
Assert.AreEqual(false, filterCriteria.DateRanked.HasFilter);
|
||||
}
|
||||
|
||||
private static readonly object[] submitted_date_test_cases =
|
||||
{
|
||||
new object[] { "submitted<2012", true },
|
||||
new object[] { "submitted<2012.03", true },
|
||||
new object[] { "submitted<2012/03/05", true },
|
||||
new object[] { "submitted<2012-3-5", true },
|
||||
|
||||
new object[] { "submitted<0", false },
|
||||
new object[] { "submitted=99999", false },
|
||||
new object[] { "submitted>=2012-03-05-04", false },
|
||||
new object[] { "submitted>=2012/03.05-04", false },
|
||||
};
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(submitted_date_test_cases))]
|
||||
public void TestInvalidRankedDateQueries(string query, bool expected)
|
||||
{
|
||||
var filterCriteria = new FilterCriteria();
|
||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||
Assert.AreEqual(expected, filterCriteria.DateSubmitted.HasFilter);
|
||||
}
|
||||
|
||||
private static readonly object[] played_query_tests =
|
||||
{
|
||||
new object[] { "0", DateTimeOffset.MinValue, true },
|
||||
|
@ -66,6 +66,8 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
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 ?? DateTimeOffset.MinValue);
|
||||
match &= !criteria.DateRanked.HasFilter || (BeatmapInfo.BeatmapSet?.DateRanked != null && criteria.DateRanked.IsInRange(BeatmapInfo.BeatmapSet.DateRanked.Value));
|
||||
match &= !criteria.DateSubmitted.HasFilter || (BeatmapInfo.BeatmapSet?.DateSubmitted != null && criteria.DateSubmitted.IsInRange(BeatmapInfo.BeatmapSet.DateSubmitted.Value));
|
||||
match &= !criteria.BPM.HasFilter || criteria.BPM.IsInRange(BeatmapInfo.BPM);
|
||||
|
||||
match &= !criteria.BeatDivisor.HasFilter || criteria.BeatDivisor.IsInRange(BeatmapInfo.BeatDivisor);
|
||||
|
@ -37,6 +37,8 @@ namespace osu.Game.Screens.Select
|
||||
public OptionalRange<int> BeatDivisor;
|
||||
public OptionalSet<BeatmapOnlineStatus> OnlineStatus = new OptionalSet<BeatmapOnlineStatus>();
|
||||
public OptionalRange<DateTimeOffset> LastPlayed;
|
||||
public OptionalRange<DateTimeOffset> DateRanked;
|
||||
public OptionalRange<DateTimeOffset> DateSubmitted;
|
||||
public OptionalTextFilter Creator;
|
||||
public OptionalTextFilter Artist;
|
||||
public OptionalTextFilter Title;
|
||||
|
@ -65,6 +65,12 @@ namespace osu.Game.Screens.Select
|
||||
case "lastplayed":
|
||||
return tryUpdateDateAgoRange(ref criteria.LastPlayed, op, value);
|
||||
|
||||
case "ranked":
|
||||
return tryUpdateRankedDateRange(ref criteria.DateRanked, op, value);
|
||||
|
||||
case "submitted":
|
||||
return tryUpdateRankedDateRange(ref criteria.DateSubmitted, op, value);
|
||||
|
||||
case "played":
|
||||
if (!tryParseBool(value, out bool played))
|
||||
return false;
|
||||
@ -592,5 +598,163 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
return tryUpdateCriteriaRange(ref dateRange, op, dateTimeOffset.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper function for building a UTC date from only the year, month and day.
|
||||
/// UTC is used to keep consistent search results with osu!web.
|
||||
/// </summary>
|
||||
private static DateTimeOffset dateTimeOffsetFromDateOnly(int year, int month, int day) =>
|
||||
new DateTimeOffset(year, month, day, 0, 0, 0, TimeSpan.Zero);
|
||||
|
||||
/// <summary>
|
||||
/// Parses a string containing a ranked or submitted date filter.
|
||||
/// Returns a boolean depending on whether parsing was successful or not.
|
||||
/// Accepted dates are in the formats `yyyy`, `yyyy-mm` and `yyyy-mm-dd`.
|
||||
/// Leading zeros are accepted. Numbers can be separated by `-`, `/`, or `.`
|
||||
/// </summary>
|
||||
/// <param name="dateRange">The <see cref="FilterCriteria.OptionalRange{DateTimeOffset}"/> to store the parsed data into, if successful.</param>
|
||||
/// <param name="op">The operator of the filtering query</param>
|
||||
/// <param name="val">The string value to attempt parsing for.</param>
|
||||
private static bool tryUpdateRankedDateRange(ref FilterCriteria.OptionalRange<DateTimeOffset> dateRange, Operator op, string val)
|
||||
{
|
||||
GroupCollection? match = tryMatchRegex(val, @"^(?<year>\d+)([-/.](?<month>\d+)([-/.](?<day>\d+))?)?$");
|
||||
|
||||
if (match == null)
|
||||
return false;
|
||||
|
||||
int? year = null;
|
||||
int? month = null;
|
||||
int? day = null;
|
||||
|
||||
List<string> keys = new List<string> { @"year", @"month", @"day" };
|
||||
|
||||
foreach (string key in keys)
|
||||
{
|
||||
if (!match.TryGetValue(key, out var group) || !group.Success)
|
||||
continue;
|
||||
|
||||
if (group.Success)
|
||||
{
|
||||
if (!tryParseDoubleWithPoint(group.Value, out double value))
|
||||
return false;
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case @"year":
|
||||
year = (int)value;
|
||||
break;
|
||||
|
||||
case @"month":
|
||||
month = (int)value;
|
||||
break;
|
||||
|
||||
case @"day":
|
||||
day = (int)value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (year == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
DateTimeOffset dateTimeOffset;
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case Operator.Less:
|
||||
month ??= 1;
|
||||
day ??= 1;
|
||||
|
||||
dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value);
|
||||
return tryUpdateCriteriaRange(ref dateRange, op, dateTimeOffset);
|
||||
|
||||
case Operator.LessOrEqual:
|
||||
if (month == null)
|
||||
{
|
||||
month = 1;
|
||||
day = 1;
|
||||
dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddYears(1);
|
||||
return tryUpdateCriteriaRange(ref dateRange, Operator.Less, dateTimeOffset);
|
||||
}
|
||||
|
||||
if (day == null)
|
||||
{
|
||||
day = 1;
|
||||
dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddMonths(1);
|
||||
return tryUpdateCriteriaRange(ref dateRange, Operator.Less, dateTimeOffset);
|
||||
}
|
||||
|
||||
dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddDays(1);
|
||||
return tryUpdateCriteriaRange(ref dateRange, Operator.Less, dateTimeOffset);
|
||||
|
||||
case Operator.GreaterOrEqual:
|
||||
month ??= 1;
|
||||
day ??= 1;
|
||||
|
||||
dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value);
|
||||
return tryUpdateCriteriaRange(ref dateRange, op, dateTimeOffset);
|
||||
|
||||
case Operator.Greater:
|
||||
if (month == null)
|
||||
{
|
||||
month = 1;
|
||||
day = 1;
|
||||
dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddYears(1);
|
||||
return tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, dateTimeOffset);
|
||||
}
|
||||
|
||||
if (day == null)
|
||||
{
|
||||
day = 1;
|
||||
dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddMonths(1);
|
||||
return tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, dateTimeOffset);
|
||||
}
|
||||
|
||||
dateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddDays(1);
|
||||
return tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, dateTimeOffset);
|
||||
|
||||
case Operator.Equal:
|
||||
|
||||
DateTimeOffset minDateTimeOffset;
|
||||
DateTimeOffset maxDateTimeOffset;
|
||||
|
||||
if (month == null)
|
||||
{
|
||||
month = 1;
|
||||
day = 1;
|
||||
minDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value);
|
||||
maxDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddYears(1);
|
||||
return tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, minDateTimeOffset)
|
||||
&& tryUpdateCriteriaRange(ref dateRange, Operator.Less, maxDateTimeOffset);
|
||||
}
|
||||
|
||||
if (day == null)
|
||||
{
|
||||
day = 1;
|
||||
minDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value);
|
||||
maxDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddMonths(1);
|
||||
return tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, minDateTimeOffset)
|
||||
&& tryUpdateCriteriaRange(ref dateRange, Operator.Less, maxDateTimeOffset);
|
||||
}
|
||||
|
||||
minDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value);
|
||||
maxDateTimeOffset = dateTimeOffsetFromDateOnly(year.Value, month.Value, day.Value).AddDays(1);
|
||||
return tryUpdateCriteriaRange(ref dateRange, Operator.GreaterOrEqual, minDateTimeOffset)
|
||||
&& tryUpdateCriteriaRange(ref dateRange, Operator.Less, maxDateTimeOffset);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (ArgumentOutOfRangeException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user