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; } }
public 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);
}
}
Related
I wanted to create a readonly struct that represents time in 24-hour format, so it has a method that is supposed to return a string of time (for example: "08:45" if 8 and 45 were passed respectively or "03:40" if 25 hours and 160 minutes were passed)
The problem is in the method, how do I return a string with values from the object inserted into it? I imagined something like return "0{stringtimeobj.hours}:0{stringtimeobj.minutes} but I can't really figure out how to format a string so that it has values from an object in it. Please help out!
using System;
namespace TimeStruct
{
public readonly struct Time
{
private readonly int hours2;
private readonly int minutes2;
public Time(int minutes)
: this()
{
this.minutes2 = minutes;
}
public Time(int hours, int minutes)
{
this.hours2 = hours;
this.minutes2 = minutes;
}
public int Hours
{
get
{
if (this.hours2 < 24)
{
return this.minutes2;
}
else if (this.hours2 == 24)
{
return 0;
}
else
{
double overflowtohours = ((Math.Truncate((double)this.minutes2 / 60) + 1) * 60) - 60;
return Convert.ToInt32(this.hours2 - ((Math.Truncate((double)(Convert.ToInt32(overflowtohours / 60) + this.hours2) / 24) + 1) * 24) - 24);
}
}
}
public int Minutes
{
get
{
if (this.minutes2 < 60)
{
return this.minutes2;
}
else if (this.minutes2 == 60)
{
return 0;
}
else
{
double overflowtohours = ((Math.Truncate((double)this.minutes2 / 60) + 1) * 60) - 60;
return Convert.ToInt32(this.minutes2 - overflowtohours);
}
}
}
public string ToString(int hours3, int minutes3)
{
Time stringtimeobj = new Time(hours3, minutes3);
return /* string "00:00" with hour and minute values from object stringtimeobj inserted */
}
}
}
You just need this implementation of your struct:
public readonly struct Time
{
private readonly int _minutes;
public Time(int minutes) : this(0, minutes) { }
public Time(int hours, int minutes)
{
_minutes = (hours * 60 + minutes) % (24 * 60);
}
public int Hours => _minutes / 60;
public int Minutes => _minutes % 60;
public override string ToString() => $"{this.Hours:00}:{this.Minutes:00}";
}
When I run this code:
Console.WriteLine(new Time(8, 45).ToString());
Console.WriteLine(new Time(25, 160).ToString());
I get the following output:
08:45
03:40
You can implement the standard ToString method like this:
public override string ToString()
{
return $"{this.Hours:00}:{this.Minutes:00}";
}
or equivalent:
public override string ToString()
{
return String.Format("{0:00}:{1:00}", this.Hours, this.Minutes);
}
The override keyword is required because you override the default ToString method
It doesn't need parameters, because it reads the local properties (this.Minutes and this.Hours - you can omit the "this."). Plus the standard ToString doesn't take parameters
The first one uses an interpolated string, the second example uses String.Format
In both cases the :00 means "format as two digits, adding leading 0's as needed" (docs)
Bonus: the debugger will use this method to display the value of this type
You can use built-in DateTime.ToString() with custom format. In your case it would look like:
int hour = 2;
int minute = 1;
DateTime time = new DateTime(2000, 1, 1, hour, minute, 0);
Console.WriteLine(time.ToString("HH:mm")); // 02:01
Yep , you can use it like this way below
DateTime time = new DateTime(2000, 1, 1, stringtimeobj.Hours,stringtimeobj.Minutes, 0);
return time.ToString("HH:mm");
We need to do some checking. If "stringtimeobj.hours" is greater than 9, which is 10 to 24, then "{stringtimeobj.hours}" can be used.
Also, if "stringTimeobj.minutes" is greater than 9, that is, 10 to 60, then "{stringtimeobj.minutes}" can be used.
Something like that.
I have a requirement where I take an int (score in the example) below and return a decimal value by comparing it against various range conditions. If score is between 1 and 10 then the test is automatically failed and a decimal is not returned (in this case I return the string "FAILED". Higher than then 10 and the corresponding decimal value is returned based on what range score matches. This feels wrong doing it this way and was wondering if there is a better method. I considered using a dictionary with each range e.g. between 1 and 10 stored in a key and then querying this to return the value. However, I'm unsure how to do this. Can anyone suggest a better method?
Thank you
public decimal GetTestScore(int score, out string status)
{
decimal score = 0m;
string status = string.Empty;
if(score >= 1 && score <= 10)
status = FAILED;
else if(score >= 10 && score <= 20)
score = 1.0;
else if(score >= 20 && score <= 30)
score = 2.0;
else if(score >= 30 && score <= 40)
score = 3.0;
return score;
}
If you predict that your solution may have to expand to accommodate more score definitions, or have user configurable score definitions, you could use something like the following example:
public class ScoreResult
{
public int Low { get; set; }
public int High { get; set; }
public string Status { get; set; }
public double ReplacementScore { get; set; }
public ScoreResult(int low, int high, string status,
double replacementScore)
{
Low = low;
High = high;
Status = status;
ReplacementScore = replacementScore;
}
}
public class ScoreCalculator
{
private List<ScoreResult> _scores = new List<ScoreResult>();
public ScoreCalculator()
{
/*These are easy to change and could be
loaded from a database/service*/
_scores.Add(new ScoreResult(1, 10, "FAILED", 0));
_scores.Add(new ScoreResult(10, 20, string.Empty, 1));
_scores.Add(new ScoreResult(20, 30, string.Empty, 2));
_scores.Add(new ScoreResult(30, 40, string.Empty, 3));
}
public ScoreResult GetScoreResult(int score)
{
//Will return null if no match found
return _scores.FirstOrDefault
(s => score >= s.Low && score <= s.High);
}
}
Example usage:
var result = GetScoreResult(9);
Console.WriteLine(result.Status); //FAILED
Console.WriteLine(result.ReplacementScore); //0.0
Each score can be represented by an instance of ScoreResult, this makes it very easy to find a match using Linq and very easy to add/remove/modify your scores. You could even store the ScoreResult data in a database so that it is easily configurable.
I think this may work:
public decimal GetTestScore(int score, out string status)
{
status = string.Empty;
if (score < 10)
{
status = "FAILED";
return 0m;
}
return Math.Ceiling(score / 10m);
}
Well first return a nullable decimal and if validation failed, simply return null.
public decimal? GetTestScore(int score)
The other question is: why does it feel wrong? For simple application this will be enough.
Don't overengineer the problem.
A dictionary is not space efficient in your situation as you would need many redundant values for the ranges to map to one single score value.
Your solution is actually wrong as you are comparing to score <=20 and then for the next score value >=20 which is redundant (even if it behaves correctly)
Edit: You can of course simplify your check if the constraints are projectable
public decimal? GetTestScore(int score)
{
if (score >= 1 && score <= 10)
{
return null;
}
if (score < 1 || score > 40)
{
return 0;
}
return (score-1) / 10;
}
Edit again: Added boundary conditions
I have some environmental sensors and I want to detect sudden changes in temperature, and slow trends over time... however I'd like to do most of the math based on what's in memory with parameters that may look like this: (subject to change)
(note: Items in parens are computed in realtime as data is added)
5 minute (derivative, max, min, avg) + 36 datapoints for most current 3 hours
10 minute (derivative, max, min, avg) + 0 datapoints, calc is based on 5min sample
30 minute (derivative, max, min, avg) + 0 datapoints, calc is based on 5 min sample
Hourly (derivative, max, min, avg) + 24 datapoints for most current 1 day
Daily (derivative, max,min,avg) + 32 datapoints for most current month
Monthly (derivative, max,min,avg) + 12 datapoints for past year
Each datapoint is a two byte float. So each sensor will consume up to 124 Floats, plus the 24 calculated variables. I'd like to support as many sensors as the .NET embededd device will permit.
Since I'm using an embedded device for this project, my memory is constrained and so is my IO and CPU power.
How would you go about implementing this in .NET? So far, I've created a couple of structs and called it a "TrackableFloat" where the insertion of a value causes the old one to drop off the array and a recalculation is done.
The only thing that makes this more
complicated than it would be, is that
for any sensor does not report back
data, then that datapoint needs to be
excluded/ignored from all subsequent
realtime calulations.
When all is said and done, if any of the values: (derivative, max,min,avg) reach a pre defined setting, then a .NET event fires
I think someone out there will think this is an interesting problem, and would love to hear how they would approach implementing it.
Would you use a Class or a Struct?
How would you trigger the calculations? (Events most likely)
How would the alerts be triggered?
How would you store the data, in tiers?
Is there a library that already does something like this? (maybe that should be my first question )
How would you efficiently calculate the derivative?
Here is my first crack at this, and it doesn't completely hit the spec, but is very efficient. Would be interested in hearing your thoughts.
enum UnitToTrackEnum
{
Minute,
FiveMinute,
TenMinute,
FifteenMinute,
Hour,
Day,
Week,
Month,
unknown
}
class TrackableFloat
{
object Gate = new object();
UnitToTrackEnum trackingMode = UnitToTrackEnum.unknown;
int ValidFloats = 0;
float[] FloatsToTrack;
public TrackableFloat(int HistoryLength, UnitToTrackEnum unitToTrack)
{
if (unitToTrack == UnitToTrackEnum.unknown)
throw new InvalidOperationException("You must not have an unknown measure of time to track.");
FloatsToTrack = new float[HistoryLength];
foreach (var i in FloatsToTrack)
{
float[i] = float.MaxValue;
}
trackingMode = unitToTrack;
Min = float.MaxValue;
Max = float.MinValue;
Sum = 0;
}
public void Add(DateTime dt, float value)
{
int RoundedDTUnit = 0;
switch (trackingMode)
{
case UnitToTrackEnum.Minute:
{
RoundedDTUnit = dt.Minute;
break;
}
case UnitToTrackEnum.FiveMinute:
{
RoundedDTUnit = System.Math.Abs(dt.Minute / 5);
break;
}
case UnitToTrackEnum.TenMinute:
{
RoundedDTUnit = System.Math.Abs(dt.Minute / 10);
break;
}
case UnitToTrackEnum.FifteenMinute:
{
RoundedDTUnit = System.Math.Abs(dt.Minute / 15);
break;
}
case UnitToTrackEnum.Hour:
{
RoundedDTUnit = dt.Hour;
break;
}
case UnitToTrackEnum.Day:
{
RoundedDTUnit = dt.Day;
break;
}
case UnitToTrackEnum.Week:
{
//RoundedDTUnit = System.Math.Abs( );
break;
}
case UnitToTrackEnum.Month:
{
RoundedDTUnit = dt.Month;
break;
}
case UnitToTrackEnum.unknown:
{
throw new InvalidOperationException("You must not have an unknown measure of time to track.");
}
default:
break;
}
bool DoRefreshMaxMin = false;
if (FloatsToTrack.Length < RoundedDTUnit)
{
if (value == float.MaxValue || value == float.MinValue)
{
// If invalid data...
lock (Gate)
{
// Get rid of old data...
var OldValue = FloatsToTrack[RoundedDTUnit];
if (OldValue != float.MaxValue || OldValue != float.MinValue)
{
Sum -= OldValue;
ValidFloats--;
if (OldValue == Max || OldValue == Min)
DoRefreshMaxMin = true;
}
// Save new data
FloatsToTrack[RoundedDTUnit] = value;
}
}
else
{
lock (Gate)
{
// Get rid of old data...
var OldValue = FloatsToTrack[RoundedDTUnit];
if (OldValue != float.MaxValue || OldValue != float.MinValue)
{
Sum -= OldValue;
ValidFloats--;
}
// Save new data
FloatsToTrack[RoundedDTUnit] = value;
Sum += value;
ValidFloats++;
if (value < Min)
Min = value;
if (value > Max)
Max = value;
if (OldValue == Max || OldValue == Min)
DoRefreshMaxMin = true;
}
}
// Function is placed here to avoid a deadlock
if (DoRefreshMaxMin == true)
RefreshMaxMin();
}
else
{
throw new IndexOutOfRangeException("Index " + RoundedDTUnit + " is out of range for tracking mode: " + trackingMode.ToString());
}
}
public float Sum { get; set; }
public float Average
{
get
{
if (ValidFloats > 0)
return Sum / ValidFloats;
else
return float.MaxValue;
}
}
public float Min { get; set; }
public float Max { get; set; }
public float Derivative { get; set; }
public void RefreshCounters()
{
lock (Gate)
{
float sum = 0;
ValidFloats = 0;
Min = float.MaxValue;
Max = float.MinValue;
foreach (var i in FloatsToTrack)
{
if (i != float.MaxValue || i != float.MinValue)
{
if (Min == float.MaxValue)
{
Min = i;
Max = i;
}
sum += i;
ValidFloats++;
if (i < Min)
Min = i;
if (i > Max)
Max = i;
}
}
Sum = sum;
}
}
public void RefreshMaxMin()
{
if (ValidFloats > 0)
{
Min = float.MaxValue;
Max = float.MinValue;
lock (Gate)
{
foreach (var i in FloatsToTrack)
{
if (i != float.MaxValue || i != float.MinValue)
{
if (i < Min)
Min = i;
if (i > Max)
Max = i;
}
}
}
}
}
}
You should consider looking at a CEP library like Nesper.
I have trouble doing this. I'm creating a method that add working days on a specific date.
for example, I want to add 3 working days to sept 15, 2010 (Wednesday), the method would return sept 20 (Monday next week). it disregards saturday and sunday because its non-working day..
Something like this in C#:
DateTime AddWorkingDays(DateTime specificDate, int workingDaysToAdd)
{
return specificDate + (workingDaysToAdd - (all saturdays and sundays))
}
I don't consider special holidays on the computations, i just literally want to add days except saturday and sundays.. Thanks in advance! =)
If you don't need to consider holidays, I would suggest you do something like this:
public static DateTime AddWorkingDays(DateTime specificDate,
int workingDaysToAdd)
{
int completeWeeks = workingDaysToAdd / 5;
DateTime date = specificDate.AddDays(completeWeeks * 7);
workingDaysToAdd = workingDaysToAdd % 5;
for (int i = 0; i < workingDaysToAdd; i++)
{
date = date.AddDays(1);
while (!IsWeekDay(date))
{
date = date.AddDays(1);
}
}
return date;
}
private static bool IsWeekDay(DateTime date)
{
DayOfWeek day = date.DayOfWeek;
return day != DayOfWeek.Saturday && day != DayOfWeek.Sunday;
}
It's inefficient, but easy to understand. For an efficient version, you'd work out the number of complete weeks to add as before, but then have a mapping from any "current day of week" and "working days left to add" to "number of actual days to add". Then you could just work out the total number of days to add, and do it in one call.
EDIT: In terms of the level of inefficiency... it's really not very bad. It'll only perform manual "is this a weekend" checks for up to 4 days, which isn't too bad. In particular, despite igor's (current at the time of posting) claims, it's rather faster than his approach, flawed benchmarks notwithstanding ;)
Note that it may not handle negative inputs yet - I haven't checked.
One of the reasons behind the approach I'm using is that it doesn't rely on either me or the code reader knowing what the values in the DayOfWeek enum are. I don't care whether it's 0-6, 1-7, Monday-Sunday, Saturday-Friday... or even if there are completely bizarre values. I only compare for equality, which makes the code more "obviously correct".
A cool way (i think) is put that in a extension method, like:
public static class DateTimeExtensions
{
public static DateTime AddWorkingDays(this DateTime self, int days)
{
self = self.AddDays(days);
while (self.DayOfWeek == DayOfWeek.Saturday || self.DayOfWeek == DayOfWeek.Sunday)
{
self = self.AddDays(1);
}
return self;
}
}
so your final code will look like:
specificDate.AddWorkingDays(3);
Here's what you need :
Updated :
public static DateTime AddWeekdays(DateTime start, int days)
{
int remainder = days % 5;
int weekendDays = (days / 5) * 2;
DateTime end = start.AddDays(remainder);
if (start.DayOfWeek == DayOfWeek.Saturday && days > 0)
{
// fix for saturday.
end = end.AddDays(-1);
}
if (end.DayOfWeek == DayOfWeek.Saturday && days > 0)
{
// add two days for landing on saturday
end = end.AddDays(2);
}
else if (end.DayOfWeek < start.DayOfWeek)
{
// add two days for rounding the weekend
end = end.AddDays(2);
}
// add the remaining days
return end.AddDays(days + weekendDays - remainder);
}
int foundWorkingDays = 0;
while (foundWorkingDays < workingDaysToAdd)
{
specificDate= specificDate.AddDays(1);
if(specificDate.DayOfWeek != DayOfWeek.Sunday && specificDate.DayOfWeek != DayOfWeek.Saturday)
foundWorkingDays++;
}
return specificDate;
ADDED:
class Program
{
public static DateTime AddWorkingDays(DateTime specificDate,
int workingDaysToAdd)
{
int completeWeeks = workingDaysToAdd / 5;
DateTime date = specificDate.AddDays(completeWeeks * 7);
workingDaysToAdd = workingDaysToAdd % 5;
for (int i = 0; i < workingDaysToAdd; i++)
{
date = date.AddDays(1);
while (!IsWeekDay(date))
{
date = date.AddDays(1);
}
}
return date;
}
private static bool IsWeekDay(DateTime date)
{
DayOfWeek day = date.DayOfWeek;
return day != DayOfWeek.Saturday && day != DayOfWeek.Sunday;
}
public static DateTime MyAddWorkingDays(DateTime specificDate,
int workingDaysToAdd)
{
int foundWorkingDays = 0;
while (foundWorkingDays < workingDaysToAdd)
{
specificDate = specificDate.AddDays(1);
if (specificDate.DayOfWeek != DayOfWeek.Sunday && specificDate.DayOfWeek != DayOfWeek.Saturday)
foundWorkingDays++;
}
return specificDate;
}
static void Main(string[] args)
{
DateTime specificDate = DateTime.Now;
Stopwatch globalTimer = Stopwatch.StartNew();
Console.WriteLine(AddWorkingDays(specificDate, 300)); // 100000 :)
globalTimer.Stop();
Console.WriteLine(globalTimer.ElapsedMilliseconds);
globalTimer = Stopwatch.StartNew();
Console.WriteLine(MyAddWorkingDays(specificDate, 300)); // 100000 :)
globalTimer.Stop();
Console.WriteLine(globalTimer.ElapsedMilliseconds);
Console.ReadLine();
}
}
Is an old post but somebody could be interested in an extension that handles also negative days. (I've reworked #Jon answer)
public static DateTime AddWeekDays(this DateTime start, int days)
{
int direction = Math.Sign(days);
int completeWeeks = days / 5;
int remaining = days % 5;
DateTime end = start.AddDays(completeWeeks * 7);
for (int i = 0; i < remaining * direction; i++)
{
end = end.AddDays(direction * 1);
while (!IsWeekDay(end))
{
end = end.AddDays(direction * 1);
}
}
return end;
}
private static bool IsWeekDay(DateTime date)
{
DayOfWeek day = date.DayOfWeek;
return day != DayOfWeek.Saturday && day != DayOfWeek.Sunday;
}
This seems to me the cleanest way:
public static DateTime AddWorkingDays(DateTime date, int daysToAdd)
{
while (daysToAdd > 0)
{
date = date.AddDays(1);
if (date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday) daysToAdd -= 1;
}
return date;
}
For a random event generator I'm writing I need a simple algorithm to generate random ranges.
So, for example:
I may say I want 10 random intervals, between 1/1 and 1/7, with no overlap, in the states (1,2,3) where state 1 events add up to 1 day, state 2 events add up to 2 days and state 3 events add up to the rest.
Or in code:
struct Interval
{
public DateTime Date;
public long Duration;
public int State;
}
struct StateSummary
{
public int State;
public long TotalSeconds;
}
public Interval[] GetRandomIntervals(DateTime start, DateTime end, StateSummary[] sums, int totalEvents)
{
// insert your cool algorithm here
}
I'm working on this now, but in case someone beats me to a solution (or knows of an elegant pre-existing algorithm) I'm posting this on SO.
First use DateTime.Subtract to determine how many minutes/seconds/whatever between your min and max dates. Then use Math.Random to get a random number of minutes/seconds/whatever in that range. Then use the result of that to construct another TimeSpan instance and add that to your min DateTime.
Here's an implementation that compiles and works, although it's still somewhat rough. It requires that the input state array properly account for the entire time range of interest (end - start), but it would be trivial to add a bit of code that would make the final state fill up the time not accounted for in the first N-1 states. I also modified your structure definitions to use ints instead of longs for the durations, just to simplify things a bit.
For clarity (and laziness) I omitted all error checking. It works fine for the inputs like the ones you described, but it's by no means bulletproof.
public static Interval[] GetRandomIntervals( DateTime start, DateTime end,
StateSummary[] states, int totalIntervals )
{
Random r = new Random();
// stores the number of intervals to generate for each state
int[] intervalCounts = new int[states.Length];
int intervalsTemp = totalIntervals;
// assign at least one interval for each of the states
for( int i = 0; i < states.Length; i++ )
intervalCounts[i] = 1;
intervalsTemp -= states.Length;
// assign remaining intervals randomly to the various states
while( intervalsTemp > 0 )
{
int iState = r.Next( states.Length );
intervalCounts[iState] += 1;
intervalsTemp -= 1;
}
// make a scratch copy of the state array
StateSummary[] statesTemp = (StateSummary[])states.Clone();
List<Interval> result = new List<Interval>();
DateTime next = start;
while( result.Count < totalIntervals )
{
// figure out which state this interval will go in (this could
// be made more efficient, but it works just fine)
int iState = r.Next( states.Length );
if( intervalCounts[iState] < 1 )
continue;
intervalCounts[iState] -= 1;
// determine how long the interval should be
int length;
if( intervalCounts[iState] == 0 )
{
// last one for this state, use up all remaining time
length = statesTemp[iState].TotalSeconds;
}
else
{
// use up at least one second of the remaining time, but
// leave some time for the remaining intervals
int maxLength = statesTemp[iState].TotalSeconds -
intervalCounts[iState];
length = r.Next( 1, maxLength + 1 );
}
// keep track of how much time is left to assign for this state
statesTemp[iState].TotalSeconds -= length;
// add a new interval
Interval interval = new Interval();
interval.State = states[iState].State;
interval.Date = next;
interval.Duration = length;
result.Add( interval );
// update the start time for the next interval
next += new TimeSpan( 0, 0, length );
}
return result.ToArray();
}
Here is my current implementation that seems to work ok and accounts for all time. This would be so much cleaner if I didn't have to target .net 1.1
public class Interval
{
public Interval(int state)
{
this.State = state;
this.Duration = -1;
this.Date = DateTime.MinValue;
}
public DateTime Date;
public long Duration;
public int State;
}
class StateSummary
{
public StateSummary(StateEnum state, long totalSeconds)
{
State = (int)state;
TotalSeconds = totalSeconds;
}
public int State;
public long TotalSeconds;
}
Interval[] GetRandomIntervals(DateTime start, DateTime end, StateSummary[] sums, int totalEvents)
{
Random r = new Random();
ArrayList intervals = new ArrayList();
for (int i=0; i < sums.Length; i++)
{
intervals.Add(new Interval(sums[i].State));
}
for (int i=0; i < totalEvents - sums.Length; i++)
{
intervals.Add(new Interval(sums[r.Next(0,sums.Length)].State));
}
Hashtable eventCounts = new Hashtable();
foreach (Interval interval in intervals)
{
if (eventCounts[interval.State] == null)
{
eventCounts[interval.State] = 1;
}
else
{
eventCounts[interval.State] = ((int)eventCounts[interval.State]) + 1;
}
}
foreach(StateSummary sum in sums)
{
long avgDuration = sum.TotalSeconds / (int)eventCounts[sum.State];
foreach (Interval interval in intervals)
{
if (interval.State == sum.State)
{
long offset = ((long)(r.NextDouble() * avgDuration)) - (avgDuration / 2);
interval.Duration = avgDuration + offset;
}
}
}
// cap the durations.
Hashtable eventTotals = new Hashtable();
foreach (Interval interval in intervals)
{
if (eventTotals[interval.State] == null)
{
eventTotals[interval.State] = interval.Duration;
}
else
{
eventTotals[interval.State] = ((long)eventTotals[interval.State]) + interval.Duration;
}
}
foreach(StateSummary sum in sums)
{
long diff = sum.TotalSeconds - (long)eventTotals[sum.State];
if (diff != 0)
{
long diffPerInterval = diff / (int)eventCounts[sum.State];
long mod = diff % (int)eventCounts[sum.State];
bool first = true;
foreach (Interval interval in intervals)
{
if (interval.State == sum.State)
{
interval.Duration += diffPerInterval;
if (first)
{
interval.Duration += mod;
first = false;
}
}
}
}
}
Shuffle(intervals);
DateTime d = start;
foreach (Interval interval in intervals)
{
interval.Date = d;
d = d.AddSeconds(interval.Duration);
}
return (Interval[])intervals.ToArray(typeof(Interval));
}
public static ICollection Shuffle(ICollection c)
{
Random rng = new Random();
object[] a = new object[c.Count];
c.CopyTo(a, 0);
byte[] b = new byte[a.Length];
rng.NextBytes(b);
Array.Sort(b, a);
return new ArrayList(a);
}