Get the day of the month (not using .NET DateTime)
I'm working on a date-time system for a game. In the interest of reusability, I decided not to use the DateTime included in .NET. It reflects Earth time, whereas I'd like to be able to have arbitrary values for things like hoursPerDay, and have, say, 10 months in a year, with any number of days each. For now I don't care about things like leap years, or time zones.
I'm having trouble figuring out how to get the current day of the month (MM-DD-YYYY). The code in question is the DayOfMonth property's get method.
public sealed class Time
{
int _ticks = 0;
int _ticksPerSecond = 30;
int _secondsPerMinute = 60;
int _minutesPerHour = 60;
int _hoursPerDay = 24;
readonly List<string> _days;
readonly Dictionary<string, int> _months;
// I haven't decided on ctor parameters yet, but we'd define base units
public Time()
{
// What we call the days of the week.
_days = new List<string> { "Monday", "Tuesday", "Wednesday" };
// What we call the months of the year, and the number of days each.
_months = new Dictionary<string, int>
{
{"January", 31},
{"February", 28},
{"March", 31}
};
}
public void Advance(int ticks)
{
_ticks += ticks;
}
// Number of ticks elapsed since epoch start
public int TotalTicks { get { return _ticks; } }
// Number of ticks elapsed during the current second
public int CurrentTicks { get { return _ticks % _ticksPerSecond; } }
public int TotalSeconds { get { return _ticks / _ticksPerSecond; } }
pub开发者_运维百科lic int CurrentSeconds { get { return TotalSeconds % _secondsPerMinute; } }
public int TotalMinutes { get { return TotalSeconds / _secondsPerMinute; } }
public int CurrentMinutes { get { return TotalMinutes % _minutesPerHour; } }
public int TotalHours { get { return TotalMinutes / _minutesPerHour; } }
public int CurrentHours { get { return TotalHours % _hoursPerDay; } }
public List<string> Days { get { return _days; } }
public int TotalDays { get { return TotalHours / _hoursPerDay; } }
public int CurrentDay { get { return TotalDays % _days.Count; } }
public string DayOfWeek { get { return _days[CurrentDay]; } }
public int DayOfMonth
{
get
{
var d = 0;
while (d < TotalDays)
{
foreach (var month in Months)
{
var daysInMonth = month.Value;
while (daysInMonth > 0 && d < TotalDays)
{
d++;
daysInMonth--;
}
}
}
return d;
}
}
public Dictionary<string, int> Months { get { return _months; } }
public int TotalMonths { get { return TotalDays / _months.Values.Sum(); } }
public int CurrentMonth { get { return TotalMonths % Months.Count; } }
…
}
A. The code above doesn't work. It always returns a fixed value.
B. The only way I can think of to accomplish this is to iterate through each month, adding up days until we reach each month's day count, until we hit the total days elapsed. That doesn't seem very efficient to me, especially with the redundant checking if (d < TotalDays)
above.
C. It seems like the more time that elapses, the longer it will take to find the DayOfMonth (and perhaps other) value(s).
I guess I'm looking for a paradigm shift, because I think I might be painting myself in a corner.
Update
For anyone who is interested in the (somewhat) working algorithms, I got this fixed by changing the DayOfMonth property to the following.
public int DayOfMonth
{
get
{
var d = TotalDays;
var found = false;
while(!found)
{
foreach (var month in _months)
{
if (d > month.Value) d -= month.Value;
else
{
found = true;
break;
}
}
}
return d;
}
}
The TotalMonths property needed a similar fix.
public int TotalMonths
{
get
{
var d = TotalDays;
var found = false;
var m = 0;
while(!found)
{
foreach (var month in _months)
{
if (d > month.Value)
{
d -= month.Value;
m++;
}
else
{
found = true;
break;
}
}
}
return m;
}
}
which enables MonthOfYear:
public string MonthOfYear { get { return _months.Keys.ToList()[CurrentMonth]; } }
So far things seem to be in order. I suspect there's a more elegant solution, perhaps involving Yield (which I don't yet understand), so if anyone comes up with something interesting I'd love to hear about it.
If you don't have leap years, it's relatively easy:
- You can work out how many ticks are in a year, and use the
%
operator to work out the tick within the year for a given value - Divide by the number of ticks in a day to get the day within the year
- You could then have a statically-constructed array for the "start day-of-year of each month", do a binary search to find the right month, and then subtract the start day-of-year to find the day within the month. Alternatively, just iterate over the months and subtract the number of days in each month until you find your value is less than the number of days within the month you're considering.
One option: use Noda Time and implement your own CalendarSystem
... which probably isn't too hard, except for understanding the system to start with :) (Bear in mind that Noda Time isn't production-quality yet, but you could have fun with it...)
I've not used it myself in the past, but the System.Globalization.Calendar class may help you.
It looks as though you can create your own "calendars" for use with DateTime etc. Their example implementations include things like "ChineseLunisolarCalendar, so perhaps instead of doing all the hard work yourself, you can create some sort of "calendar specification" so you can still use the built-in types?
this is how the DateTime
does it when you look at the decompiled assembly:
private int GetDatePart(int part)
{
long internalTicks = this.InternalTicks;
int num1 = (int)internalTicks / 864000000000L;
int num2 = num1 / 146097;
num1 = num1 - (num2 * 146097);
int num3 = num1 / 36524;
if (num3 == 4)
{
num3 = 3;
}
num1 = num1 - (num3 * 36524);
int num4 = num1 / 1461;
num1 = num1 - (num4 * 1461);
int num5 = num1 / 365;
if (num5 == 4)
{
num5 = 3;
}
if (part == 0)
{
return ((((num2 * 400) + (num3 * 100)) + (num4 * 4)) + num5) + 1;
}
num1 = num1 - (num5 * 365);
if (part == 1)
{
return num1 + 1;
}
bool flag = num5 == 3 && ((num4 == 24 && num3 == 3) || !(num4 == 24));
int[] daysToMonth366 = DateTime.DaysToMonth366;
int num6 = num1 >> 6;
while (num1 >= daysToMonth366[num6])
{
num6++;
}
if (part == 2)
{
return num6;
}
return (num1 - daysToMonth366[(num6 - 1)]) + 1;
}
internal long InternalTicks
{
get
{
return this.dateData & 4611686018427387903L;
}
}
public int Day
{
get
{
return this.GetDatePart(3);
}
}
精彩评论