Misc tweaks

Add date validation for lgpe go park encounters (deferred, now finally remembered to implement?)
This commit is contained in:
Kurt 2025-04-21 00:57:23 -05:00
parent 154c370901
commit 87d2f10c7f
29 changed files with 103 additions and 43 deletions

View File

@ -27,9 +27,9 @@ public static PersonalColor GetColor(IEncounterTemplate enc)
public static ReadOnlySpan<Ball> GetPreferredByColor(IEncounterTemplate enc) => GetPreferredByColor(enc, GetColor(enc)); public static ReadOnlySpan<Ball> GetPreferredByColor(IEncounterTemplate enc) => GetPreferredByColor(enc, GetColor(enc));
public static ReadOnlySpan<Ball> GetPreferredByColor<T>(T enc, PersonalColor color) where T : IVersion public static ReadOnlySpan<Ball> GetPreferredByColor<T>(T enc, PersonalColor color) where T : IContext
{ {
if (enc.Version is GameVersion.PLA) if (enc.Context is EntityContext.Gen8a)
return GetPreferredByColorLA(color); return GetPreferredByColorLA(color);
return GetPreferredByColor(color); return GetPreferredByColor(color);
} }

View File

@ -1,7 +1,7 @@
namespace PKHeX.Core; namespace PKHeX.Core;
/// <summary> /// <summary>
/// <see cref="GameVersion.BATREV"/> Game Language IDs /// <see cref="SAV4BR"/> Game Language IDs
/// </summary> /// </summary>
public enum LanguageBR : byte public enum LanguageBR : byte
{ {

View File

@ -24,6 +24,7 @@ public static class EncounterServerDate
WA8 wa8 => Result(wa8.IsWithinDistributionWindow(obtained)), WA8 wa8 => Result(wa8.IsWithinDistributionWindow(obtained)),
WB8 wb8 => Result(wb8.IsWithinDistributionWindow(obtained)), WB8 wb8 => Result(wb8.IsWithinDistributionWindow(obtained)),
WC9 wc9 => Result(wc9.IsWithinDistributionWindow(obtained)), WC9 wc9 => Result(wc9.IsWithinDistributionWindow(obtained)),
EncounterSlot7GO g7 => Result(g7.IsWithinDistributionWindow(obtained)),
EncounterSlot8GO g8 => Result(g8.IsWithinDistributionWindow(obtained)), EncounterSlot8GO g8 => Result(g8.IsWithinDistributionWindow(obtained)),
_ => throw new ArgumentOutOfRangeException(nameof(enc)), _ => throw new ArgumentOutOfRangeException(nameof(enc)),
}; };

View File

@ -7,8 +7,9 @@ namespace PKHeX.Core;
/// <inheritdoc cref="PogoSlotExtensions" /> /// <inheritdoc cref="PogoSlotExtensions" />
/// </summary> /// </summary>
public sealed record EncounterSlot7GO(int StartDate, int EndDate, ushort Species, byte Form, byte LevelMin, byte LevelMax, Shiny Shiny, Gender Gender, PogoType Type) public sealed record EncounterSlot7GO(int StartDate, int EndDate, ushort Species, byte Form, byte LevelMin, byte LevelMax, Shiny Shiny, Gender Gender, PogoType Type)
: IEncounterable, IEncounterMatch, IPogoSlot, IEncounterConvertible<PB7> : IEncounterable, IEncounterMatch, IPogoSlot, IEncounterConvertible<PB7>, IEncounterServerDate
{ {
public bool IsDateRestricted => true;
public byte Generation => 7; public byte Generation => 7;
public EntityContext Context => EntityContext.Gen7b; public EntityContext Context => EntityContext.Gen7b;
public Ball FixedBall => Ball.None; // GO Park can override the ball; obey capture rules for LGP/E public Ball FixedBall => Ball.None; // GO Park can override the ball; obey capture rules for LGP/E
@ -137,6 +138,18 @@ public bool IsMatchExact(PKM pk, EvoCriteria evo)
return true; return true;
} }
public bool IsWithinDistributionWindow(PKM pk)
{
var date = new DateOnly(pk.MetYear + 2000, pk.MetMonth, pk.MetDay);
return IsWithinDistributionWindow(date);
}
public bool IsWithinDistributionWindow(DateOnly date)
{
var stamp = PogoDateRangeExtensions.GetTimeStamp(date.Year, date.Month, date.Day);
return this.IsWithinStartEnd(stamp);
}
public EncounterMatchRating GetMatchRating(PKM pk) public EncounterMatchRating GetMatchRating(PKM pk)
{ {
var stamp = PogoDateRangeExtensions.GetTimeStamp(pk.MetYear + 2000, pk.MetMonth, pk.MetDay); var stamp = PogoDateRangeExtensions.GetTimeStamp(pk.MetYear + 2000, pk.MetMonth, pk.MetDay);

View File

@ -3,13 +3,8 @@ namespace PKHeX.Core;
/// <summary> /// <summary>
/// Represents all details that an entity may be encountered with. /// Represents all details that an entity may be encountered with.
/// </summary> /// </summary>
public interface IEncounterTemplate : ISpeciesForm, IVersion, IGeneration, IShiny, ILevelRange, ILocation, IFixedAbilityNumber, IFixedBall, IShinyPotential public interface IEncounterTemplate : ISpeciesForm, IVersion, IGeneration, IShiny, ILevelRange, ILocation, IFixedAbilityNumber, IFixedBall, IShinyPotential, IContext
{ {
/// <summary>
/// Original Context
/// </summary>
EntityContext Context { get; }
/// <summary> /// <summary>
/// Indicates if the encounter originated as an egg. /// Indicates if the encounter originated as an egg.
/// </summary> /// </summary>

View File

@ -79,18 +79,18 @@ public override void Verify(LegalityAnalysis data)
VerifyFullness(data, pk); VerifyFullness(data, pk);
var enc = data.EncounterMatch; var enc = data.EncounterMatch;
if (enc is IEncounterServerDate { IsDateRestricted: true } serverGift) if (enc is IEncounterServerDate { IsDateRestricted: true } encounterDate)
{ {
var date = new DateOnly(pk.MetYear + 2000, pk.MetMonth, pk.MetDay); var actualDay = new DateOnly(pk.MetYear + 2000, pk.MetMonth, pk.MetDay);
// HOME Gifts for Sinnoh/Hisui starters were forced JPN until May 20, 2022 (UTC). // HOME Gifts for Sinnoh/Hisui starters were forced JPN until May 20, 2022 (UTC).
if (enc is WB8 { IsDateLockJapanese: true } or WA8 { IsDateLockJapanese: true }) if (enc is WB8 { IsDateLockJapanese: true } or WA8 { IsDateLockJapanese: true })
{ {
if (date < new DateOnly(2022, 5, 20) && pk.Language != (int)LanguageID.Japanese) if (actualDay < new DateOnly(2022, 5, 20) && pk.Language != (int)LanguageID.Japanese)
data.AddLine(GetInvalid(LDateOutsideDistributionWindow)); data.AddLine(GetInvalid(LDateOutsideDistributionWindow));
} }
var result = serverGift.IsWithinDistributionWindow(date); var result = encounterDate.IsWithinDistributionWindow(actualDay);
if (result == EncounterServerDateCheck.Invalid) if (result == EncounterServerDateCheck.Invalid)
data.AddLine(GetInvalid(LDateOutsideDistributionWindow)); data.AddLine(GetInvalid(LDateOutsideDistributionWindow));
} }

View File

@ -106,11 +106,11 @@ public static bool IsRibbonValidMasterRank(PKM pk, IEncounterTemplate enc, Evolu
private static bool IsRibbonValidMasterRankSWSH(PKM pk, IEncounterTemplate enc) private static bool IsRibbonValidMasterRankSWSH(PKM pk, IEncounterTemplate enc)
{ {
// Transfers from prior games, as well as from GO, require the battle-ready symbol in order to participate in Ranked. // Transfers from prior games, as well as from GO, require the battle-ready symbol in order to participate in Ranked.
if ((enc.Generation < 8 || enc.Version == GameVersion.GO) && pk is IBattleVersion { BattleVersion: 0 }) if ((enc.Generation < 8 || enc.Context is EntityContext.Gen7b) && pk is IBattleVersion { BattleVersion: 0 })
return false; return false;
// GO transfers: Capture date is global time, and not console changeable. // GO transfers and server gifts: Capture date is global time, and not console changeable.
bool hasRealDate = enc.Version == GameVersion.GO || enc is IEncounterServerDate { IsDateRestricted: true }; bool hasRealDate = enc is IEncounterServerDate { IsDateRestricted: true };
if (hasRealDate) if (hasRealDate)
{ {
// Ranked is still ongoing, but the use of Mythicals was restricted to Series 13 only. // Ranked is still ongoing, but the use of Mythicals was restricted to Series 13 only.

View File

@ -122,7 +122,7 @@ public override string CardTitle
get get
{ {
// Check to see if date is valid // Check to see if date is valid
if (!DateUtil.IsDateValid(Year, Month, Day)) if (!DateUtil.IsValidDate(Year, Month, Day))
return null; return null;
return new DateOnly(Year, Month, Day); return new DateOnly(Year, Month, Day);

View File

@ -83,7 +83,7 @@ private uint Day
get get
{ {
// Check to see if date is valid // Check to see if date is valid
if (!DateUtil.IsDateValid(Year, Month, Day)) if (!DateUtil.IsValidDate(Year, Month, Day))
return null; return null;
return new DateOnly((int)Year, (int)Month, (int)Day); return new DateOnly((int)Year, (int)Month, (int)Day);

View File

@ -80,7 +80,7 @@ private uint Day
get get
{ {
// Check to see if date is valid // Check to see if date is valid
if (!DateUtil.IsDateValid(Year, Month, Day)) if (!DateUtil.IsValidDate(Year, Month, Day))
return null; return null;
return new DateOnly((int)Year, (int)Month, (int)Day); return new DateOnly((int)Year, (int)Month, (int)Day);

View File

@ -80,7 +80,7 @@ private uint Day
get get
{ {
// Check to see if date is valid // Check to see if date is valid
if (!DateUtil.IsDateValid(Year, Month, Day)) if (!DateUtil.IsValidDate(Year, Month, Day))
return null; return null;
return new DateOnly((int)Year, (int)Month, (int)Day); return new DateOnly((int)Year, (int)Month, (int)Day);

View File

@ -0,0 +1,9 @@
namespace PKHeX.Core;
public interface IContext
{
/// <summary>
/// The Context the data originated in.
/// </summary>
EntityContext Context { get; }
}

View File

@ -342,7 +342,7 @@ public override string OriginalTrainerName
{ {
get get
{ {
if (!DateUtil.IsDateValid(2000 + ReceivedYear, ReceivedMonth, ReceivedDay)) if (!DateUtil.IsValidDate(2000 + ReceivedYear, ReceivedMonth, ReceivedDay))
return null; return null;
return new DateOnly(ReceivedYear + 2000, ReceivedMonth, ReceivedDay); return new DateOnly(ReceivedYear + 2000, ReceivedMonth, ReceivedDay);
} }
@ -370,7 +370,7 @@ public override string OriginalTrainerName
{ {
get get
{ {
if (!DateUtil.IsTimeValid(ReceivedHour, ReceivedMinute, ReceivedSecond)) if (!DateUtil.IsValidTime(ReceivedHour, ReceivedMinute, ReceivedSecond))
return null; return null;
return new TimeOnly(ReceivedHour, ReceivedMinute, ReceivedSecond); return new TimeOnly(ReceivedHour, ReceivedMinute, ReceivedSecond);
} }

View File

@ -164,7 +164,7 @@ private byte[] Write()
get get
{ {
// Check to see if date is valid // Check to see if date is valid
if (!DateUtil.IsDateValid(2000 + MetYear, MetMonth, MetDay)) if (!DateUtil.IsValidDate(2000 + MetYear, MetMonth, MetDay))
return null; return null;
return new DateOnly(2000 + MetYear, MetMonth, MetDay); return new DateOnly(2000 + MetYear, MetMonth, MetDay);
} }
@ -207,7 +207,7 @@ private byte[] Write()
get get
{ {
// Check to see if date is valid // Check to see if date is valid
if (!DateUtil.IsDateValid(2000 + EggYear, EggMonth, EggDay)) if (!DateUtil.IsValidDate(2000 + EggYear, EggMonth, EggDay))
return null; return null;
return new DateOnly(2000 + EggYear, EggMonth, EggDay); return new DateOnly(2000 + EggYear, EggMonth, EggDay);
} }

View File

@ -4,6 +4,9 @@
namespace PKHeX.Core; namespace PKHeX.Core;
/// <summary>
/// Logic for detecting a <see cref="PKM"/> entity from a byte array or length.
/// </summary>
public static class EntityDetection public static class EntityDetection
{ {
/// <summary> /// <summary>

View File

@ -3,6 +3,9 @@
namespace PKHeX.Core; namespace PKHeX.Core;
/// <summary>
/// Logic for interacting with Entity file extensions.
/// </summary>
public static class EntityFileExtension public static class EntityFileExtension
{ {
// All side-game formats that don't follow the usual pk* format // All side-game formats that don't follow the usual pk* format

View File

@ -4,6 +4,9 @@
namespace PKHeX.Core; namespace PKHeX.Core;
/// <summary>
/// Logic for creating file names for <see cref="PKM"/> data.
/// </summary>
public static class EntityFileNamer public static class EntityFileNamer
{ {
/// <summary> /// <summary>

View File

@ -5,6 +5,9 @@
namespace PKHeX.Core; namespace PKHeX.Core;
/// <summary>
/// Utility class for detecting the type format of a Pokémon entity.
/// </summary>
public static class EntityFormat public static class EntityFormat
{ {
/// <summary> /// <summary>
@ -210,7 +213,7 @@ private static EntityFormatDetected IsFormatReally8b(PK8 pk)
public enum EntityFormatDetected public enum EntityFormatDetected
{ {
None = -1, None,
FormatPK1, FormatPK1,
FormatPK2, FormatSK2, FormatPK2, FormatSK2,

View File

@ -2,6 +2,9 @@
namespace PKHeX.Core; namespace PKHeX.Core;
/// <summary>
/// Logic for creating a PID, mostly for originating in Generations 3-5.
/// </summary>
public static class EntityPID public static class EntityPID
{ {
/// <summary> /// <summary>
@ -25,7 +28,7 @@ public static uint GetRandomPID(Random rnd, ushort species, byte gender, GameVer
// Below logic handles Gen3-5. // Below logic handles Gen3-5.
// No need to get form specific entry, as Gen3-5 do not have that feature. // No need to get form specific entry, as Gen3-5 do not have that feature.
var gt = PersonalTable.B2W2[species].Gender; var gt = PersonalTable.B2W2[species].Gender;
bool g34 = origin <= GameVersion.CXD; bool g34 = origin.IsGen3();
uint abilBitVal = g34 ? oldPID & 0x0000_0001 : oldPID & 0x0001_0000; uint abilBitVal = g34 ? oldPID & 0x0000_0001 : oldPID & 0x0001_0000;
bool g3unown = origin is GameVersion.FR or GameVersion.LG && species == (int)Species.Unown; bool g3unown = origin is GameVersion.FR or GameVersion.LG && species == (int)Species.Unown;

View File

@ -36,6 +36,8 @@ public static class RecentTrainerCache
public static void SetRecentTrainer(ITrainerInfo trainer) public static void SetRecentTrainer(ITrainerInfo trainer)
{ {
Trainer = trainer; Trainer = trainer;
// Update Gen6/7 trainer reference if applicable, otherwise retain whatever was there.
if (trainer is IRegionOriginReadOnly g67) if (trainer is IRegionOriginReadOnly g67)
Trainer67 = g67; Trainer67 = g67;
} }

View File

@ -3,7 +3,7 @@ namespace PKHeX.Core;
/// <summary> /// <summary>
/// Simple record containing trainer data /// Simple record containing trainer data
/// </summary> /// </summary>
public sealed record SimpleTrainerInfo : ITrainerInfo, IRegionOriginReadOnly public sealed record SimpleTrainerInfo : ITrainerInfo, IRegionOriginReadOnly, ITrainerID
{ {
public string OT { get; init; } = TrainerName.ProgramINT; public string OT { get; init; } = TrainerName.ProgramINT;
public ushort TID16 { get; init; } = 12345; public ushort TID16 { get; init; } = 12345;

View File

@ -165,7 +165,8 @@ private static bool IsChecksumValid(Span<byte> sav, int offset)
public override int Language public override int Language
{ {
get => (int)(BRLanguage == LanguageBR.JapaneseOrEnglish && Japanese ? LanguageID.Japanese : BRLanguage.ToLanguageID()); get => (int)(BRLanguage == LanguageBR.JapaneseOrEnglish && Japanese ? LanguageID.Japanese : BRLanguage.ToLanguageID());
set { set
{
Japanese = value == (int)LanguageID.Japanese; Japanese = value == (int)LanguageID.Japanese;
BRLanguage = ((LanguageID)value).ToLanguageBR(); BRLanguage = ((LanguageID)value).ToLanguageBR();
} }

View File

@ -137,7 +137,7 @@ public void SetTeam(IReadOnlyList<PK6> team, int t)
{ {
get get
{ {
if (!DateUtil.IsDateValid(MatchYear, MatchMonth, MatchDay)) if (!DateUtil.IsValidDate(MatchYear, MatchMonth, MatchDay))
return null; return null;
return new DateTime(MatchYear, MatchMonth, MatchDay, MatchHour, MatchMinute, MatchSecond); return new DateTime(MatchYear, MatchMonth, MatchDay, MatchHour, MatchMinute, MatchSecond);
} }
@ -163,7 +163,7 @@ public void SetTeam(IReadOnlyList<PK6> team, int t)
{ {
get get
{ {
if (!DateUtil.IsDateValid(UploadYear, UploadMonth, UploadDay)) if (!DateUtil.IsValidDate(UploadYear, UploadMonth, UploadDay))
return null; return null;
return new DateTime(UploadYear, UploadMonth, UploadDay, UploadHour, UploadMinute, UploadSecond); return new DateTime(UploadYear, UploadMonth, UploadDay, UploadHour, UploadMinute, UploadSecond);
} }

View File

@ -95,7 +95,7 @@ public void SetPlayerNames(IReadOnlyList<string> value)
{ {
get get
{ {
if (!DateUtil.IsDateValid(MatchYear, MatchMonth, MatchDay)) if (!DateUtil.IsValidDate(MatchYear, MatchMonth, MatchDay))
return null; return null;
return new DateTime(MatchYear, MatchMonth, MatchDay, MatchHour, MatchMinute, MatchSecond); return new DateTime(MatchYear, MatchMonth, MatchDay, MatchHour, MatchMinute, MatchSecond);
} }

View File

@ -44,7 +44,7 @@ public abstract class PlayTimeLastSaved<TSave, TEpoch>(TSave sav, Memory<byte> r
public DateTime? LastSavedDate public DateTime? LastSavedDate
{ {
get => !DateUtil.IsDateValid(LastSaved.Year, LastSaved.Month, LastSaved.Day) get => !DateUtil.IsValidDate(LastSaved.Year, LastSaved.Month, LastSaved.Day)
? null ? null
: LastSaved.Timestamp; : LastSaved.Timestamp;
set set

View File

@ -73,7 +73,7 @@ public void SetFestaPhraseUnlocked(int index, bool value)
public DateTime? FestaDate public DateTime? FestaDate
{ {
get => FestaYear >= 0 && FestaMonth > 0 && FestaDay > 0 && FestaHour >= 0 && FestaMinute >= 0 && FestaSecond >= 0 && DateUtil.IsDateValid(FestaYear, FestaMonth, FestaDay) get => FestaYear >= 0 && FestaMonth > 0 && FestaDay > 0 && FestaHour >= 0 && FestaMinute >= 0 && FestaSecond >= 0 && DateUtil.IsValidDate(FestaYear, FestaMonth, FestaDay)
? new DateTime(FestaYear, FestaMonth, FestaDay, FestaHour, FestaMinute, FestaSecond) ? new DateTime(FestaYear, FestaMonth, FestaDay, FestaHour, FestaMinute, FestaSecond)
: null; : null;
set set

View File

@ -11,7 +11,7 @@ public static class DateUtil
/// <param name="month">The month of the date of which to check the validity.</param> /// <param name="month">The month of the date of which to check the validity.</param>
/// <param name="day">The day of the date of which to check the validity.</param> /// <param name="day">The day of the date of which to check the validity.</param>
/// <returns>A boolean indicating if the date is valid.</returns> /// <returns>A boolean indicating if the date is valid.</returns>
public static bool IsDateValid(int year, int month, int day) public static bool IsValidDate(int year, int month, int day)
{ {
if (year is <= 0 or > 9999) if (year is <= 0 or > 9999)
return false; return false;
@ -30,14 +30,20 @@ public static bool IsDateValid(int year, int month, int day)
/// <param name="month">The month of the date of which to check the validity.</param> /// <param name="month">The month of the date of which to check the validity.</param>
/// <param name="day">The day of the date of which to check the validity.</param> /// <param name="day">The day of the date of which to check the validity.</param>
/// <returns>A boolean indicating if the date is valid.</returns> /// <returns>A boolean indicating if the date is valid.</returns>
public static bool IsDateValid(uint year, uint month, uint day) public static bool IsValidDate(uint year, uint month, uint day)
{ {
return year < int.MaxValue && month < int.MaxValue && day < int.MaxValue && IsDateValid((int)year, (int)month, (int)day); return year < int.MaxValue && month < int.MaxValue && day < int.MaxValue && IsValidDate((int)year, (int)month, (int)day);
} }
private static readonly DateTime Epoch2000 = new(2000, 1, 1); private static readonly DateTime Epoch2000 = new(2000, 1, 1);
private const int SecondsPerDay = 60*60*24; // 86400 private const int SecondsPerDay = 60*60*24; // 86400
/// <summary>
/// Combines the date and time components into a seconds-elapsed value, relative to the epoch 2000.
/// </summary>
/// <param name="date">Date component</param>
/// <param name="time">Time component</param>
/// <returns>Seconds elapsed since epoch 2000</returns>
public static int GetSecondsFrom2000(DateTime date, DateTime time) public static int GetSecondsFrom2000(DateTime date, DateTime time)
{ {
int seconds = (int)(date - Epoch2000).TotalSeconds; int seconds = (int)(date - Epoch2000).TotalSeconds;
@ -46,12 +52,24 @@ public static int GetSecondsFrom2000(DateTime date, DateTime time)
return seconds; return seconds;
} }
/// <summary>
/// Converts a seconds-elapsed value to the Date and Time components, relative to the epoch 2000.
/// </summary>
/// <param name="seconds">Seconds elapsed since epoch 2000</param>
/// <param name="date">Date component</param>
/// <param name="time">Time component</param>
public static void GetDateTime2000(uint seconds, out DateTime date, out DateTime time) public static void GetDateTime2000(uint seconds, out DateTime date, out DateTime time)
{ {
date = Epoch2000.AddSeconds(seconds); date = Epoch2000.AddSeconds(seconds);
time = Epoch2000.AddSeconds(seconds % SecondsPerDay); time = Epoch2000.AddSeconds(seconds % SecondsPerDay);
} }
/// <summary>
/// Converts a seconds-elapsed value to a string.
/// </summary>
/// <param name="value">Seconds elapsed</param>
/// <param name="secondsBias">If provided, treat as epoch 2000 + secondsBias</param>
/// <returns>String representation of the date/time value</returns>
public static string ConvertDateValueToString(int value, int secondsBias = -1) public static string ConvertDateValueToString(int value, int secondsBias = -1)
{ {
var sb = new System.Text.StringBuilder(); var sb = new System.Text.StringBuilder();
@ -81,7 +99,14 @@ public static DateOnly GetRandomDateWithin(DateOnly start, DateOnly end, Random
/// <inheritdoc cref="GetRandomDateWithin(DateOnly,DateOnly,Random)"/> /// <inheritdoc cref="GetRandomDateWithin(DateOnly,DateOnly,Random)"/>
public static DateOnly GetRandomDateWithin(DateOnly start, DateOnly end) => GetRandomDateWithin(start, end, Util.Rand); public static DateOnly GetRandomDateWithin(DateOnly start, DateOnly end) => GetRandomDateWithin(start, end, Util.Rand);
public static bool IsTimeValid(byte receivedHour, byte receivedMinute, byte receivedSecond) /// <summary>
/// Checks if the given time components represent a valid time.
/// </summary>
/// <param name="receivedHour"></param>
/// <param name="receivedMinute"></param>
/// <param name="receivedSecond"></param>
/// <returns></returns>
public static bool IsValidTime(byte receivedHour, byte receivedMinute, byte receivedSecond)
{ {
return receivedHour < 24u && receivedMinute < 60u && receivedSecond < 60u; return receivedHour < 24u && receivedMinute < 60u && receivedSecond < 60u;
} }

View File

@ -179,7 +179,6 @@ public static string ToTitleCase(ReadOnlySpan<char> span)
/// <inheritdoc cref="ToTitleCase(ReadOnlySpan{char})"/> /// <inheritdoc cref="ToTitleCase(ReadOnlySpan{char})"/>
public static void ToTitleCase(ReadOnlySpan<char> span, Span<char> result) public static void ToTitleCase(ReadOnlySpan<char> span, Span<char> result)
{ {
// Add each word to the string builder. Continue from the first index that isn't a space.
// Add the first character as uppercase, then add each successive character as lowercase. // Add the first character as uppercase, then add each successive character as lowercase.
bool first = true; bool first = true;
for (var i = 0; i < span.Length; i++) for (var i = 0; i < span.Length; i++)

View File

@ -11,7 +11,7 @@ public class DateUtilTests
[InlineData(2001, 1, 31)] [InlineData(2001, 1, 31)]
public void RecognizesCorrectDates(int year, int month, int day) public void RecognizesCorrectDates(int year, int month, int day)
{ {
Assert.True(DateUtil.IsDateValid(year, month, day), $"Failed to recognize {year}/{month}/{day}"); Assert.True(DateUtil.IsValidDate(year, month, day), $"Failed to recognize {year}/{month}/{day}");
} }
[Theory] [Theory]
@ -29,13 +29,13 @@ public void RecognizesCorrectDates(int year, int month, int day)
[InlineData(2016, 12, 31)] [InlineData(2016, 12, 31)]
public void RecognizesValidMonthBoundaries(int year, int month, int day) public void RecognizesValidMonthBoundaries(int year, int month, int day)
{ {
Assert.True(DateUtil.IsDateValid(year, month, day), $"Incorrect month boundary for {year}/{month}/{day}"); Assert.True(DateUtil.IsValidDate(year, month, day), $"Incorrect month boundary for {year}/{month}/{day}");
} }
[Fact] [Fact]
public void RecognizeCorrectLeapYear() public void RecognizeCorrectLeapYear()
{ {
Assert.True(DateUtil.IsDateValid(2004, 2, 29)); Assert.True(DateUtil.IsValidDate(2004, 2, 29));
} }
[Theory] [Theory]
@ -51,7 +51,7 @@ public void RecognizeCorrectLeapYear()
[InlineData(uint.MaxValue, uint.MaxValue, uint.MaxValue, false, "Failed with uint.MaxValue, negative")] [InlineData(uint.MaxValue, uint.MaxValue, uint.MaxValue, false, "Failed with uint.MaxValue, negative")]
public void CheckDate(uint year, uint month, uint day, bool cmp, string because) public void CheckDate(uint year, uint month, uint day, bool cmp, string because)
{ {
var result = DateUtil.IsDateValid(year, month, day); var result = DateUtil.IsValidDate(year, month, day);
result.Should().Be(cmp, because); result.Should().Be(cmp, because);
} }