# Calculate the total cost between two dates based on a weighted time interval scheme

Consider we have to pay the usage of a machine with the following hourly cost:

• 00:00 - 01:00 --> $4 per hour • 01:00 - 05:00 -->$1 per hour
• 05:00 - 22:00 --> $2 per hour • 22:00 - 24:00 -->$3 per hour

If we use that machine from a date of x to another date of y with the given weighted time interval w,

var w = new List<Tuple<TimeSpan, double>>
{
new Tuple<TimeSpan, double>(new TimeSpan(1,0,0),1),// hourly cost is 1 from 01:00 to the next boundary
new Tuple<TimeSpan, double>(new TimeSpan(5,0,0),2),// hourly cost is 2 from 05:00 to the next boundary
new Tuple<TimeSpan, double>(new TimeSpan(22,0,0),3),// hourly cost is 3 from 22:00 to the next boundary
new Tuple<TimeSpan, double>(new TimeSpan(24,0,0),4)// hourly cost is 4 from 00:00 to the next boundary

};


the following is my attempt at calculating the total cost between two dates based on a weighted time interval scheme. Please kindly review it and make it much better.

class Program
{
// x cannot be later than y.
// the TimeSpan of w must be in ascending order

static double Total(DateTime x, DateTime y, List<Tuple<TimeSpan, double>> w)
{
double sum = 0;

int days = (int)(y - x).TotalDays;

if (days > 0)
sum += Sum(new TimeSpan(), new TimeSpan(24, 0, 0), w) * days;

sum += Sum(x.TimeOfDay, y.TimeOfDay, w);

return sum;
}

// x may be later than y.
// the TimeSpan of w must be in ascending order

static double Sum(TimeSpan x, TimeSpan y, List<Tuple<TimeSpan, double>> w)
{
double sum = 0;

if (x > y)
{
sum += Sum(x, new TimeSpan(24, 0, 0), w);
sum += Sum(new TimeSpan(0, 0, 0), y, w);
return sum;
}

w.Insert(0, new Tuple<TimeSpan, double>(new TimeSpan(0, 0, 0), w.Last().Item2));

for (int i = 0; i < w.Count - 1; i++)
{
if (x >= w[i + 1].Item1)
continue;
else
{
for (int j = i; j < w.Count - 1; j++)
{
if (y >= w[j + 1].Item1)
{
if (i == j)
sum += (w[j + 1].Item1 - x).TotalHours * w[j].Item2;
else
sum += (w[j + 1].Item1 - w[j].Item1).TotalHours * w[j].Item2;
continue;
}
else
{
if (i == j)
sum += (y - x).TotalHours * w[j].Item2;
else
sum += (y - w[j].Item1).TotalHours * w[j].Item2;
break;
}
}
break;
}
}

w.RemoveAt(0);
return sum;
}

static void Print(DateTime start, DateTime stop, List<Tuple<TimeSpan, double>> w)
{
Console.WriteLine($"x = {start:yyyy/MM/dd hh:mm:ss:fff tt}\ny = {stop:yyyy/MM/dd hh:mm:ss:fff tt}\ncost = {Total(start, stop, w)}\n"); } static void Main(string[] args) { var w = new List<Tuple<TimeSpan, double>> { new Tuple<TimeSpan, double>(new TimeSpan(1,0,0),1),// hourly cost is 1 from 01:00 to the next boundary new Tuple<TimeSpan, double>(new TimeSpan(5,0,0),2),// hourly cost is 2 from 05:00 to the next boundary new Tuple<TimeSpan, double>(new TimeSpan(22,0,0),3),// hourly cost is 3 from 22:00 to the next boundary new Tuple<TimeSpan, double>(new TimeSpan(24,0,0),4)// hourly cost is 4 from 00:00 to the next boundary }; Print(new DateTime(2016, 1, 1, 0, 30, 0, 1), new DateTime(2016, 1, 2, 0, 30, 0, 0), w); Print(new DateTime(2016, 1, 1, 0, 30, 0, 1), new DateTime(2016, 1, 3, 0, 30, 0, 0), w); Print(new DateTime(2016, 1, 1, 23, 0, 0, 0), new DateTime(2016, 1, 2, 22, 59, 59, 999), w); Print(new DateTime(2016, 1, 1, 23, 0, 0, 0), new DateTime(2016, 1, 3, 22, 59, 59, 999), w); Print(new DateTime(2016, 1, 1, 0, 0, 0, 0), new DateTime(2016, 1, 2, 0, 0, 0, 0), w); Print(new DateTime(2016, 1, 1, 5, 0, 0, 0), new DateTime(2016, 1, 1, 22, 0, 0, 0), w); Print(new DateTime(2016, 1, 1, 5, 0, 0, 0), new DateTime(2016, 1, 2, 22, 0, 0, 0), w); Print(new DateTime(2016, 1, 1, 0, 0, 0, 0), new DateTime(2016, 1, 1, 0, 0, 0, 0), w); Print(new DateTime(2016, 1, 1, 0, 0, 0, 0), new DateTime(2017, 1, 1, 0, 0, 0, 0), w); } }  • Your 'Question' is off-topic, we can review the rest. – Mast Aug 7 '16 at 10:03 • instead of your Print function calls use unit tests Aug 8 '16 at 12:02 ## 1 Answer Create a Class for the DateTime pair Make the constructor ensure the pair is in the correct order and the "x > y" type code goes away. And the constructor will check for null, strip milliseconds, etc. Client code can then presume clean data. Override CompareTo so sorting to proper order is trivial. And even more code goes away. This class should contain its own Sum() Create a class for the List of DateTime pairs This class will keep its elements in order. More code goes away. This class should contain its own Sum(). Variable Names! The sheer complexity of Sum demands meaningful variable names. Ambiguous Weighted Ranges Is exactly 1 hour charged at$1/hr or \$2/hr? Ditto for 5 and 22 hours. What about more than 24 hrs?

Don't say "just read the code." I'll just say the code is wrong, or the documentation is wrong. I can't tell which.

'rithmatic

Use decimal for money calculations. A quote of a quote from an SO question

The Decimal value type is appropriate for financial calculations requiring large numbers of significant integral and fractional digits and no round-off errors. The Decimal type does not eliminate the need for rounding. Rather, it minimizes errors due to rounding.

Display results consistently.

• For ambiguous Weighted Ranges: See the output at the end, one year cost is presented. Aug 7 '16 at 22:53