Comparing dates in C# - c#

I am a beginning programmer in C# and asp.net. I am busy trying to create a Hotel application. Where somebody is able to book for example an 8 day vacation. But now I need to add a formula that calculates the price. The method I am writing is getting the price per night of the room from the database. And the days that the person is staying is entered in the view and passed down to the controller.So I want to calculate the price inside the controller. But now I have a problem cause the price of staying in the hotel is higher in the high-season than in the low-season. So the prices differs per day. But now I do not really now how to compare the dates so I am able to give an accurate total price.
I have looked over some threads on stack overflow and they often advice to use Timespan to compare dates. But I was wondering is Timespan the best solution for me? Cause for my project the price should flow and not be fixed. For example it should not be like 28 May - 10 July is €120 euro per night but more like 28 May €109, 29 May €112, 30 May €113 - 9 July €127, 10 July 130.
If I would succeed in creating a different price per day then the last thing should not be that hard I hope. The price of each date should be added to each other so I will have the total price.
So my questions are:
Is the best way to compare dates Timespan?
Is there an easy way to calculate this? I would not like fixed dates.
Are there any good tutorials for this?

I would just compare each Date object between the start and end dates to see if it falls within a defined range to determine the rate, and sum them as I go.
This is probably overkill for you, but I would encapsulate the different 'seasons' and their rates in a class, and add a method to the class that will determine if a date falls within that 'season'. This will simplify the other methods.
Then I would create a method that, given a single date, will return the rate for that date.
Finally, I would calculate the total price by calling the GetRate() method for each day between the client's start date (inclusive) and end date (exclusive).
Here's a sample of how I would do it. First, the class to hold a 'season'
public class Season
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public int Rate { get; set; }
public bool ContainsDate(DateTime date)
{
// Assumption: Year is ignored - seasons are considered
// to start and end on the same date each year
//
// Rules: (remember a season may start in Dec and end in Jan,
// so you cant just check if the date is greater than
// the start or less than the end!)
//
// 1. If the start and end month are the same,
// return true if the month is equal to start (or end) month
// AND the day is between start and end days.
// 2. If the date is in the same month as the start month,
// return true if the day is greater than or equal to start day.
// 3. If the date is in the same month as the end month,
// return true if the day is less than or equal to end day.
// 4. If the StartMonth is less than the EndMonth,
// return true if the month is between them.
// 5. Otherwise, return true if month is NOT between them.
if (StartDate.Month == EndDate.Month)
return date.Month == StartDate.Month &&
date.Day >= StartDate.Day &&
date.Day <= EndDate.Day;
if (date.Month == StartDate.Month)
return date.Day >= StartDate.Day;
if (date.Month == EndDate.Month)
return date.Day <= EndDate.Day;
if (StartDate.Month <= EndDate.Month)
return date.Month > StartDate.Month && date.Month < EndDate.Month;
return date.Month < EndDate.Month || date.Month > StartDate.Month;
}
}
Next, a method that will calculate the rate for a specific date:
public static int GetRate(DateTime date)
{
// Normally these 'seasons' and rates would not be hard coded here
const int offSeasonRate = 125;
var winterSeason = new Season
{
StartDate = DateTime.Parse("November 15"),
EndDate = DateTime.Parse("January 12"),
Rate = 150
};
var springSeason = new Season
{
StartDate = DateTime.Parse("May 20"),
EndDate = DateTime.Parse("June 15"),
Rate = 140
};
var summerSeason = new Season
{
StartDate = DateTime.Parse("July 10"),
EndDate = DateTime.Parse("August 31"),
Rate = 170
};
// Create a list of all the seasons
var seasons = new List<Season> {winterSeason, springSeason, summerSeason};
// Loop through all the seasons and see if this date is in one of them
foreach (var season in seasons)
{
if (season.ContainsDate(date))
{
// Note: depending on your implementation, Rate could be a multiplier
// in which case you would return (offSeasonRate * season.Rate);
return season.Rate;
}
}
// If we get this far, the date was not in a 'season'
return offSeasonRate;
}
Finally, here is the method that gets the total price for a date range:
var startDate = DateTime.Today;
var endDate = startDate.AddDays(2);
var price = 0;
// Sum the rates for each day between
// start date (inclusive) and end date (exclusive).
for (var curDate = startDate; curDate < endDate; curDate = curDate.AddDays(1))
{
price += GetRate(curDate);
}
Console.WriteLine("The total cost from {0} to {1} is: €{2}",
startDate, endDate, price);

Related

Find consecutive 12 month rolling periods based on a condition

I'm trying to build a calculator to identify the earliest finish date with 5 consecutive 12 month rolling periods which satisfy a specific condition (absence less than 180 days in my example)
The code starts with a fixed start date and adds 12 months to calculate the absence period in any given window. If the absence period is greater than 180 days, it should adjust the start date so the absence is less than 180 days and continue till 5 such consecutive periods are found
I can think of the brute force solution but would love to know any more elegant solutions.
public void CalculateAbsence()
{
var startDate = new LocalDate(2018, 5, 5);
var qualifyinYears = 0;
while (qualifyinYears < 5)
{
var totalAbsence = 0;
var endDate = startDate.PlusYears(1);
//Retrieve All applicable absences in this qualifying Period
foreach (var abs in Immigration.ApplicableAbsences(startDate, endDate))
{
totalAbsence += Period.DaysBetween((abs.Start < startDate ? startDate : abs.Start), (abs.End > endDate ? endDate : abs.End));
}
Console.WriteLine($"Year {startDate}-{endDate}. Absence: {totalAbsence}");
qualifyinYears++;
startDate = startDate.PlusYears(1);
}
}

Define a Custom Weekend Starting and Ending Day & Time and finding its occurrences over date range

I am having a tough time in calculating the number of custom defined weekends and later finding them over a specified date range, I have to apply different rates based on the days (weekends, season days & normal days).
For example, a trip is starting at 16/08/2017 09:00:00 and ending at 29/08/2017 09:00:00 where as my weekend start at Fri 15:00:00 till Mon 09:00:00 every week. I need to calculate how many weekends occur in the given trip start and end time and how many regular days, as both are charged at different rates. Any help is appreciated.
Please note that all the date and times will be dynamic and can be changed, so looking for a generic solution
If your range is small enough you can try:
public class DateTimeRange
{
private DateTime Start { get; set; }
private DateTime End { get; set; }
public DateTimeRange(DateTime start, DateTime end)
{
Start = start;
End = end;
}
public int DayOffsCount()
{
var current = Start;
var dayOffsCount = 0;
while (current < End)
{
if (IsDayOff(current))
{
dayOffsCount++;
}
current = current.AddDays(1);
}
return dayOffsCount;
}
}
public bool IsDayOff(DateTime dt)
{
if (dt.DayOfWeek == DayOfWeek.Saturday || dt.DayOfWeek == DayOfWeek.Sunday)
return true;
return IsHoliday(dt);
}

Get DateTime of the next nth day of the month

If given a date and a variable n, how can I calculate the DateTime for which the day of the month will be the nth Date?
For example, Today is the 17th of June.
I would like a function that when provided 15 would return the DateTime for July 15.
A few more examples:
Today is Feb. 26: Function would return March 30 when provided 30.
Today is Dec. 28. Function would return Jan 4 when provided 4.
Today is Feb. 28. Function would return March 29 when provided 29, unless it was a leap year, in which case it would return Feb 29.
Why not just do?
private DateTime GetNextDate(DateTime dt, int DesiredDay)
{
if (DesiredDay >= 1 && DesiredDay <= 31)
{
do
{
dt = dt.AddDays(1);
} while (dt.Day != DesiredDay);
return dt.Date;
}
else
{
throw new ArgumentOutOfRangeException();
}
}
After many, many edits, corrections and re-writes, here is my final answer:
The method that follows returns a a DateTime representing the next time the day of number day comes up in the calendar. It does so using an iterative approach, and is written in the form of an extension method for DateTime objects, and thus isn't bound to today's date but will work with any date.
The code executes the following steps to get the desired result:
Ensure that the day number provided is valid (greater than zero and smaller than 32).
Enter into a while loop that keeps going forever (until we break).
Check if cDate's month works (the day must not have passed, and the month must have enough days in it).
If so, return.
If not, increase the month by one, set the day to one, set includeToday to true so that the first day of the new month is included, and execute the loop again.
The code:
static DateTime GetNextDate3(this DateTime cDate, int day, bool includeToday = false)
{
// Make sure provided day is valid
if (day > 0 && day <= 31)
{
while (true)
{
// See if day has passed in current month or is not contained in it at all
if ((includeToday && day > cDate.Day || (includeToday && day >= cDate.Day)) && day <= DateTime.DaysInMonth(cDate.Year, cDate.Month))
{
// If so, break and return
break;
}
// Advance month by one and set day to one
// FIXED BUG HERE (note the order of the two calls)
cDate = cDate.AddDays(1 - cDate.Day).AddMonths(1);
// Set includeToday to true so that the first of every month is taken into account
includeToday = true;
}
// Return if the cDate's month contains day and it hasn't passed
return new DateTime(cDate.Year, cDate.Month, day);
}
// Day provided wasn't a valid one
throw new ArgumentOutOfRangeException("day", "Day isn't valid");
}
The spec is a little bit unclear about to do when today is the dayOfMonth. I assumed it was it to return the same. Otherwise it would just be to change to <= today.Day
public DateTime FindNextDate(int dayOfMonth, DateTime today)
{
var nextMonth = new DateTime(today.Year, today.Month, 1).AddMonths(1);
if(dayOfMonth < today.Day){
nextMonth = nextMonth.AddMonths(1);
}
while(nextMonth.AddDays(-1).Day < dayOfMonth){
nextMonth = nextMonth.AddMonths(1);
}
var month = nextMonth.AddMonths(-1);
return new DateTime(month.Year, month.Month, dayOfMonth);
}
Stumbled upon this thread today while trying to figure out this same problem.
From my testing, the following seems to work well and the loop only needs two goes (I think? Maybe 3 max(?)) to get to the answer:
public static DateTime GetNearestSpecificDay(DateTime start, int dayNum)
{
if (dayNum >= 1 && dayNum <= 31)
{
DateTime result = start;
while (result.Day != dayNum)
result = dayNum > result.Day ? result.AddDays(dayNum - result.Day) : new DateTime(result.Month == 12 ? result.Year + 1 : result.Year, (result.Month % 12) + 1, 1);
return result;
}
else
return DateTime.Today;
}
Edit: As requested, here's a less compact version that walks through the logic step by step. I've also updated the original code to account for a required year change when we reach December.
public static DateTime GetNearestSpecificDay(DateTime start, int dayNum)
{
// Check if target day is valid in the Gregorian calendar
if (dayNum >= 1 && dayNum <= 31)
{
// Declare a variable which will hold our result & temporary results
DateTime result = start;
// While the current result's day is not the desired day
while (result.Day != dayNum)
{
// If the desired day is greater than the current day
if (dayNum > result.Day)
{
// Add the difference to try and skip to the target day (if we can, if the current month does not have enough days, we will be pushed into the next month and repeat)
result = result.AddDays(dayNum - result.Day);
}
// Else, get the first day of the next month, then try the first step again (which should get us where we want to be)
else
{
// If the desired day is less than the current result day, it means our result day must be in the next month (it obviously can't be in the current)
// Get the next month by adding 1 to the current month mod 12 (so when we hit december, we go to january instead of trying to use a not real 13th month)
// If result.Month is November, 11%12 = 11; 11 + 1 = 12, which rolls us into December
// If result.Month is December, 12%12 = 0; 0 + 1 = 1, which rolls us into January
var month = (result.Month % 12) + 1;
// Get current/next year.
// Since we are adding 1 to the current month, we can assume if the previous month was 12 that we must be entering into January of next year
// Another way to do this would be to check if the new month is 1. It accomplishes the same thing but I chose 12 since it doesn't require an extra variable in the original code.
// Below can be translated as "If last result month is 12, use current year + 1, else, use current year"
var year = result.Month == 12 ? result.Year + 1 : result.Year;
// Set result to the start of the next month in the current/next year
result = new DateTime(year, month, 1);
}
}
// Return result
return result;
}
else
// If our desired day is invalid, just return Today. This can be an exception or something as well, just using Today fit my use case better.
return DateTime.Today;
}
Fun little puzzle. I generated 100 DateTimes which represent the starting day of each month, then checked each month to see if it had the date we want. It's lazy so we stop when we find a good one.
public DateTime FindNextDate(int dayOfMonth, DateTime today)
{
DateTime yesterday = today.AddDays(-1);
DateTime currentMonthStart = new DateTime(today.Year, today.Month, 1);
var query = Enumerable.Range(0, 100)
.Select(i => currentMonthStart.AddMonths(i))
.Select(monthStart => MakeDateOrDefault(
monthStart.Year, monthStart.Month, dayOfMonth,
yesterday)
.Where(date => today <= date)
.Take(1);
List<DateTime> results = query.ToList();
if (!results.Any())
{
throw new ArgumentOutOfRangeException(nameof(dayOfMonth))
}
return results.Single();
}
public DateTime MakeDateOrDefault(
int year, int month, int dayOfMonth,
DateTime defaultDate)
{
try
{
return new DateTime(year, month, dayOfMonth);
}
catch
{
return defaultDate;
}
}

How to check if DateTime is within a specific range

I'm developing a video rental application using C# winforms, and came across a problem I can't seem to write up or find the solution to.
The program needs to check the current date and number of days passed and also the range between them.
If the current Date is less than or equal to the date specified, it will not calculate the penalty cost.
Otherwise if the Date today has already passed the date specified, it will calculate the penalty cost multiplied by the number of days that has passed between them.
Here's the sample code I have playing with the idea:
DateTime db = DateTime.Parse(dateBeforeString);
DateTime dt = DateTime.Now;
var dateDiff = (dt - db);
double totalDays = dateDiff.TotalDays;
int totalPenalty = initialPenaltyInt*(int)Convert.ToInt64(totalDays);
int totalCost = totalPenalty + rentalCostInt;
if(DateTime.Now != db)
{
//do stuff here to:
//check if current day is less than the one on the database
//set total penalty to zero
}
else if(DateTime.Now > db)
{
//otherwise calculate the total penalty cost multipled by the number of days passed since a specific date
}
Simplistic, but might help you progress, hopefully:
public class Penalties
{
// What about this choice of "int" (vs. decimal)?
public virtual int ComputeOverdueDaysPenalty(int penaltyPerOverdueDay, DateTime dueDate)
{
// Work only with year, month, day, to drop time info and ignore time zone
dueDate = new DateTime(dueDate.Year, dueDate.Month, dueDate.Day);
var now = DateTime.Now;
now = new DateTime(now.Year, now.Month, now.Day);
return now > dueDate ? (int)now.Subtract(dueDate).TotalDays * penaltyPerOverdueDay : 0;
}
}
class Program
{
static void Main(string[] args)
{
var penalties = new Penalties();
var now = DateTime.Now;
// due = today
// should print 0
Console.WriteLine(penalties.ComputeOverdueDaysPenalty(1234, new DateTime(now.Year, now.Month, now.Day)));
// due = today plus 1
var dueDate = now.AddDays(1);
// should print 0 again
Console.WriteLine(penalties.ComputeOverdueDaysPenalty(1234, dueDate));
// due = today minus 1
dueDate = dueDate.Subtract(new TimeSpan(48, 0, 0));
// should print 1234
Console.WriteLine(penalties.ComputeOverdueDaysPenalty(1234, dueDate));
// due = today minus 2
dueDate = dueDate.Subtract(new TimeSpan(24, 0, 0));
// should print 2468
Console.WriteLine(penalties.ComputeOverdueDaysPenalty(1234, dueDate));
dueDate = DateTime.Parse("2016-10-02");
// should print 12340, as of 10/12/2016
Console.WriteLine(penalties.ComputeOverdueDaysPenalty(1234, dueDate));
Console.ReadKey();
}
}
Just a remark:
I find it a bit odd you've settled for using the int type in that context, btw.
If your "penalty" units are in fact some currency, the recommended data type for that is decimal, in most use cases.
'Hope this helps.

linq compare column data between two dates

I need to compare the scores of a user between last week and two weeks ago.
i have a table like so
Scores
user score subject date
2 10 math 21/10/2012
2 5 science 23/10/2012
2 5 math 16/10/2012
2 9 science 15/12/2012
I need to produce a query that shows last week's score and whether it is an increase or decrease from 2 weeks ago's score
user score subject date increase/decrease
2 10 math 21/10/2012 +5
2 10 science 23/10/2012 -4
The date column doesn't need to be included in the query
I already have the code to get week range from last week to two weeks ago. I'm having trouble comparing the two dates however.
DateTime date = DateTime.Now;
DateTime startOneWeekAgo = date.AddDays(-7).Date.AddDays(-(int)date.DayOfWeek),
endOneWeekAgo = startOneWeekAgo.AddDays(7);
DateTime startTwoWeeksAgo = startOneWeekAgo.AddDays(-7),
endTwoWeeksAgo = endOneWeekAgo.AddDays(-7);
from s in Scores where s.scoredate >= startOneWeekAgo && s.scoredate <
endOneWeekAgo
this results in. This is what I have so far. Help would be appreciated.
user score subject
2 10 math
2 5 science
DateTime beginningOfWeek = DateTime.Now.BeginningOfWeek();
DateTime twoWeeksAgo = beginningOfWeek.AddDays(-14);
DateTime endOfLastWeek = beginningOfWeek.AddMilliseconds(-1);
var query = from s in scores
where s.Date >= twoWeeksAgo && s.Date <= endOfLastWeek
orderby s.Date
group s by new { s.User, s.Subject } into g
select new
{
User = g.Key.User,
Subject = g.Key.Subject,
Date = g.Last().Date,
Diff = g.Last().Score - g.First().Score
};
Thus you always selecting only two last scores (two weeks ago and one week ago), you will have only two records in each group.
If you will have only one week's results in database, then difference will be zero, because last and first entry in group will be the same.
Also you can use some DateTime extension for obtaining beginning of week:
public static class DateExtensions
{
public static DateTime BeginningOfWeek(this DateTime date,
DayOfWeek firstDayOfWeek = DayOfWeek.Monday)
{
if (date.DayOfWeek == firstDayOfWeek)
return date.Date;
DateTime result = date.AddDays(-1);
while (result.DayOfWeek != firstDayOfWeek)
result = result.AddDays(-1);
return result.Date;
}
}
And one more thing - try to avoid declaring several variables at once.

Categories

Resources