using System; using System.Linq; using StardewValley; namespace StardewModdingAPI.Utilities { /// Represents a Stardew Valley date. public class SDate { /********* ** Properties *********/ /// The internal season names in order. private readonly string[] Seasons = { "spring", "summer", "fall", "winter" }; /// The number of days in a season. private readonly int DaysInSeason = 28; /********* ** Accessors *********/ /// The day of month. public int Day { get; } /// The season name. public string Season { get; } /// The year. public int Year { get; } /********* ** Public methods *********/ /// Construct an instance. /// The day of month. /// The season name. /// One of the arguments has an invalid value (like day 35). public SDate(int day, string season) : this(day, season, Game1.year) { } /// Construct an instance. /// The day of month. /// The season name. /// The year. /// One of the arguments has an invalid value (like day 35). public SDate(int day, string season, int year) { // validate if (season == null) throw new ArgumentNullException(nameof(season)); if (!this.Seasons.Contains(season)) throw new ArgumentException($"Unknown season '{season}', must be one of [{string.Join(", ", this.Seasons)}]."); if (day < 1 || day > this.DaysInSeason) throw new ArgumentException($"Invalid day '{day}', must be a value from 1 to {this.DaysInSeason}."); if (year < 1) throw new ArgumentException($"Invalid year '{year}', must be at least 1."); // initialise this.Day = day; this.Season = season; this.Year = year; } /// Get a new date with the given number of days added. /// The number of days to add. /// Returns the resulting date. /// The offset would result in an invalid date (like year 0). public SDate AddDays(int offset) { // simple case int day = this.Day + offset; string season = this.Season; int year = this.Year; // handle season transition if (day > this.DaysInSeason || day < 1) { // get current season index int curSeasonIndex = Array.IndexOf(this.Seasons, this.Season); if (curSeasonIndex == -1) throw new InvalidOperationException($"The current season '{this.Season}' wasn't recognised."); // get season offset int seasonOffset = day / this.DaysInSeason; if (day < 1) seasonOffset -= 1; // get new date day = this.GetWrappedIndex(day, this.DaysInSeason); season = this.Seasons[this.GetWrappedIndex(curSeasonIndex + seasonOffset, this.Seasons.Length)]; year += seasonOffset / this.Seasons.Length; } // validate if(year < 1) throw new ArithmeticException($"Adding {offset} days to {this} would result in invalid date {day:00} {season} {year}."); // return new date return new SDate(day, season, year); } /// Get a string representation of the date. This is mainly intended for debugging or console messages. public override string ToString() { return $"{this.Day:00} {this.Season} Y{this.Year}"; } /// Get the current in-game date. public static SDate Now() { return new SDate(Game1.dayOfMonth, Game1.currentSeason, Game1.year); } /********* ** Operator methods *********/ /// /// Equality operator. Tests the date being equal to each other /// /// The first date being compared /// The second date being compared /// The equality of the dates public static bool operator ==(SDate s1, SDate s2) { if (s1.Day == s2.Day && s1.Year == s2.Year && s1.Season == s2.Season) return true; else return false; } /// /// Inequality operator. Tests the date being not equal to each other /// /// The first date being compared /// The second date being compared /// The inequality of the dates public static bool operator !=(SDate s1, SDate s2) { if (s1.Day == s2.Day && s1.Year == s2.Year && s1.Season == s2.Season) return false; else return true; } /// /// Less than operator. Tests the date being less than to each other /// /// The first date being compared /// The second date being compared /// If the dates are less than public static bool operator >(SDate s1, SDate s2) { if (s1.Year > s2.Year) return true; else if (s1.Year == s2.Year) { if (s1.Season == "winter" && s2.Season != "winter") return true; else if (s1.Season == s2.Season && s1.Day > s2.Day) return true; if (s1.Season == "fall" && (s2.Season == "summer" || s2.Season == "spring")) return true; if (s1.Season == "summer" && s2.Season == "spring") return true; } return false; } /// /// Less or equal than operator. Tests the date being less than or equal to each other /// /// The first date being compared /// The second date being compared /// If the dates are less than or equal than public static bool operator >=(SDate s1, SDate s2) { if (s1.Year > s2.Year) return true; else if (s1.Year == s2.Year) { if (s1.Season == "winter" && s2.Season != "winter") return true; else if (s1.Season == s2.Season && s1.Day >= s2.Day) return true; if (s1.Season == "fall" && (s2.Season == "summer" || s2.Season == "spring")) return true; if (s1.Season == "summer" && s2.Season == "spring") return true; } return false; } /// /// Greater or equal than operator. Tests the date being greater than or equal to each other /// /// The first date being compared /// The second date being compared /// If the dates are greater than or equal than public static bool operator <=(SDate s1, SDate s2) { if (s1.Year < s2.Year) return true; else if (s1.Year == s2.Year) { if (s1.Season == s2.Season && s1.Day <= s2.Day) return true; else if (s1.Season == "spring" && s2.Season != "spring") return true; if (s1.Season == "summer" && (s2.Season == "fall" || s2.Season == "winter")) return true; if (s1.Season == "fall" && s2.Season == "winter") return true; } return false; } /// /// Greater than operator. Tests the date being greater than to each other /// /// The first date being compared /// The second date being compared /// If the dates are greater than public static bool operator <(SDate s1, SDate s2) { if (s1.Year < s2.Year) return true; else if (s1.Year == s2.Year) { if (s1.Season == s2.Season && s1.Day < s2.Day) return true; else if (s1.Season == "spring" && s2.Season != "spring") return true; if (s1.Season == "summer" && (s2.Season == "fall" || s2.Season == "winter")) return true; if (s1.Season == "fall" && s2.Season == "winter") return true; } return false; } /// /// Overrides the equals function. /// /// Object being compared. /// The equalaity of the object. public override bool Equals(object obj) { return base.Equals(obj); } /// /// This returns the hashcode of the object /// /// The hashcode of the object. public override int GetHashCode() { return base.GetHashCode(); } /********* ** Private methods *********/ /// Get the real index in an array which should be treated as a two-way loop. /// The index in the looped array. /// The number of elements in the array. private int GetWrappedIndex(int index, int length) { int wrapped = index % length; if (wrapped < 0) wrapped += length; return wrapped; } } }