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);
}
}
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);
}
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;
}
}
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.
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.