How would I get the number of weekday hours between two dates? (There's a lot of business days calculations, but doesn't seem to be much on weekday hours - not business/opening hours, just hours that aren't weekends).
This is my stab at it - is there a better way?
void Main()
{
// Works
DateTime start = new DateTime(2013,6,15,0,0,0); // Saturday
DateTime end = new DateTime(2013,6,17,10,0,0); // Monday
// Result = 10 (OK)
GetBusinessHours(start, end).Dump();
// Bugs
start = new DateTime(2013,6,14,0,0,0); // Friday
end = new DateTime(2013,6,15,0,0,0); // Saturday
// Result = 0 (Bug) - should be 24
GetBusinessHours(start, end).Dump();
}
public double GetBusinessHours(DateTime start, DateTime end)
{
double result = (end - start).TotalHours;
int weekendDays = Enumerable.Range(0, 1 + end.Subtract(start).Days).Select(offset => start.AddDays(offset)).Count(d => d.DayOfWeek == DayOfWeek.Sunday || d.DayOfWeek == DayOfWeek.Saturday);
double weekendDeltaHours = 0;
if (start.DayOfWeek == DayOfWeek.Saturday ||
start.DayOfWeek == DayOfWeek.Sunday)
{
weekendDays--;
weekendDeltaHours = start.Date.AddDays(1).Subtract(start).TotalHours;
}
else if (end.DayOfWeek == DayOfWeek.Saturday ||
end.DayOfWeek == DayOfWeek.Sunday)
{
weekendDeltaHours = (end - end.Date).TotalHours;
}
result = result - (weekendDays * 24) - weekendDeltaHours;
return result;
}
(Props to Ani for Enumerable.Range trick).
Not that this will be the most efficient method, it will work for what you require.
var end = DateTime.Now;
var start = end.AddDays(-100);
var weekend = new[] { DayOfWeek.Saturday, DayOfWeek.Sunday };
var count = Enumerable.Range(0, Convert.ToInt32(end.Subtract(start).TotalHours))
.Count(offset => !weekend.Contains(start.AddHours(offset).DayOfWeek));
It might be worth putting a check that the number of hours isn't too big for an int. However this would mean a date range of > 245,000 years!
Related
I want to fetch data by last week days like last Sunday, last Monday and so on 7 days. I wrote this query but I returns null.
var dateCriteria = DateTime.Now.Date.AddDays(-7);
var one = _context.Sale.Where(m => m.Date >= dateCriteria && m.Date.DayOfWeek.ToString() ==
"Sunday");
DayOfWeek is enum. So just use it without conversion:
var dateCriteria = DateTime.Now.Date.AddDays(-7);
var one = _context.Sale.Where(m => m.Date >= dateCriteria && m.Date.DayOfWeek ==
DayOfWeek.Sunday);
I am not sure if I understood your question correctly but here is what I would do to get the last Sunday's sales.
var one = _context.Sale.Where(m => m.Date == GetLast(DayOfWeek.Sunday));
private DateTime GetLast(DayOfWeek dayOfWeek) {
var currentDate = DateTime.Now.Date;
var currentDayOfWeek = (int)currentDate.DayOfWeek;
if (currentDayOfWeek <= (int)dayOfWeek) {
currentDayOfWeek = currentDayOfWeek + 7;
}
int daysToExtract = currentDayOfWeek - (int)dayOfWeek;
return currentDate.AddDays(-daysToExtract);
}
Just wondering if there is a more optimized and/or neater way (using LINQ for example) of writing what I have below to get a list of business week date ranges between two dates?
This is what I have currently ..
// Some storage
public class Bucket
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
// Other code removed for brevity ...
DateTime start = new DateTime(2015, 7, 1);
DateTime end = new DateTime(2015, 9, 1);
DayOfWeek firstDayOfWeek = DayOfWeek.Monday;
DayOfWeek lastDayOfWeek = DayOfWeek.Friday;
var buckets = new List<Bucket>();
var currentDate = start;
DateTime startOfBucket = currentDate;
DateTime endOfBucket = currentDate;
while (currentDate <= end)
{
var currentDayOfWeek = currentDate.DayOfWeek;
// Skip days outside the business week
if (currentDayOfWeek >= firstDayOfWeek && currentDayOfWeek <= lastDayOfWeek)
{
if (currentDayOfWeek == firstDayOfWeek)
{
// Start a new bucket
startOfBucket = currentDate;
}
if ((currentDayOfWeek == lastDayOfWeek) || (currentDate == end))
{
// End of bucket
endOfBucket = currentDate;
// Create bucket
buckets.Add(new Bucket()
{
StartDate = startOfBucket,
EndDate = endOfBucket
});
}
}
currentDate = currentDate.AddDays(1);
}
And this will give me the following date ranges ...
Start: 01/Jul/2015 End: 03/Jul/2015
Start: 06/Jul/2015 End: 10/Jul/2015
Start: 13/Jul/2015 End: 17/Jul/2015
Start: 20/Jul/2015 End: 24/Jul/2015
Start: 27/Jul/2015 End: 31/Jul/2015
Start: 03/Aug/2015 End: 07/Aug/2015
Start: 10/Aug/2015 End: 14/Aug/2015
Start: 17/Aug/2015 End: 21/Aug/2015
Start: 24/Aug/2015 End: 28/Aug/2015
Start: 31/Aug/2015 End: 01/Sep/2015
N.B. The first and last weeks are purposefully not full weeks (they abide to the date range given).
Edit
The solution provided here gives the number of days between the two dates but I am interested in getting the collection of date ranges.
Also, I don't need to account for any holidays.
Thanks,
It's quite handy using linq
var startDate = new DateTime(2015, 7, 1);
var endDate = new DateTime(2015, 9, 1);
var workDates = Enumerable.Range(0, (int)(endDate - startDate).TotalDays + 1)
.Select(i => startDate.AddDays(i))
.Where(date => (date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday))
.Select(i => i);
var display = workDates
.GroupAdjacentBy((x, y) => x.AddDays(1) == y)
.Select(g => string.Format("Start: {0:dd/MMM/yyyy} End: {1:dd/MMM/yyyy}", g.First(), g.Last()));
With the extension method GroupAdjacentBy<T>
public static class IEnumerableExtension
{
public static IEnumerable<IEnumerable<T>> GroupAdjacentBy<T>(
this IEnumerable<T> source, Func<T, T, bool> predicate)
{
using (var e = source.GetEnumerator())
{
if (e.MoveNext())
{
var list = new List<T> { e.Current };
var pred = e.Current;
while (e.MoveNext())
{
if (predicate(pred, e.Current))
{
list.Add(e.Current);
}
else
{
yield return list;
list = new List<T> { e.Current };
}
pred = e.Current;
}
yield return list;
}
}
}
}
Fiddle
This is based on Eric's accepted answer so please give him any upvote. I've just modified his solution to handle business weeks that could be 7 days long and also for one that could wrap a weekend.
var startDate = new DateTime(2015, 7, 1);
var endDate = new DateTime(2015, 9, 1);
DayOfWeek firstDayOfWeek = DayOfWeek.Monday;
DayOfWeek lastDayOfWeek = DayOfWeek.Friday;
var workDates = Enumerable.Range(0, (int)(endDate - startDate).TotalDays + 1)
.Select(i => startDate.AddDays(i))
.Where(date =>
// Normal work weeks where first day of week is before last (numerically) e.g. Monday -> Friday or Sunday -> Saturday
(firstDayOfWeek < lastDayOfWeek && date.DayOfWeek >= firstDayOfWeek && date.DayOfWeek <= lastDayOfWeek) ||
// Cater for business weeks whose start and end dates wrap over the weekend e.g. Thursday -> Tuesday
(lastDayOfWeek < firstDayOfWeek && (date.DayOfWeek >= firstDayOfWeek || date.DayOfWeek <= lastDayOfWeek)))
.Select(i => i);
var display = workDates
.GroupAdjacentBy((x, y) => x.AddDays(1) == y && !(x.DayOfWeek == lastDayOfWeek && y.DayOfWeek == firstDayOfWeek))
.Select(g => string.Format("Start: {0:dd/MMM/yyyy} End: {1:dd/MMM/yyyy}", g.First(), g.Last()));
I use this code from another question:
private bool NthDayOfMonth(DateTime date, DayOfWeek dow, int n){
int d = date.Day;
return date.DayOfWeek == dow && (d-1)/7 == (n-1);
}
It works fine. But it not checks a last day ( for me it's when n = 5). How to modify it?
Thanks.
The method below checks the given date is the last date of week of month.
private bool IsLastOfMonth(DateTime date)
{
var oneWeekAfter = date.AddDays(7);
return oneWeekAfter.Month != date.Month;
}
So there is new method, it just checks mondays
private bool IsLastMonday(DateTime date)
{
if (date.DayOfWeek != DayOfWeek.Monday)
return false; // it is not monday
// the next monday is...
var oneWeekAfter = date.AddDays(7);
// and is it in same month?, if it is, that means its not last monday
return oneWeekAfter.Month != date.Month;
}
Lets take March 30,
d = 30,
(date.DayOfWeek == DayOfWeek.Friday) == true,
(30-1)=29, 29/7 = 4
4 == (5-1)
So it works
To only check if DayOfWeek is last in mothth you can use
return date.AddDays(7).Month != date.Month;
Given a Date. How can I add a number of days to it while skipping weekends and other holidays coming between the range?
List <DateTime> holidays = new List<DateTime>()
{
new DateTime(2012, 01, 03),
new DateTime(2012, 01, 26)
};
dateTimeReview.Value = CalculateFutureDate(dateTimeStart.Value, 7,holidays);
static DateTime CalculateFutureDate(DateTime fromDate, int numberofWorkDays, ICollection<DateTime> holidays)
{
var futureDate = fromDate;
for (var i = 0; i < numberofWorkDays; i++ )
{
if (futureDate.DayOfWeek == DayOfWeek.Saturday
|| futureDate.DayOfWeek == DayOfWeek.Sunday
|| (holidays != null && holidays.Contains(futureDate)))
{
futureDate = futureDate.AddDays(1); // Increase FutureDate by one because of condition
futureDate = futureDate.AddDays(1); // Add a working day
}
}
return futureDate;
}
To skip holidays you will first need to create your own list of holidays. Holidays are different in every country and also subject to other factors.
Then you should add days one by one in a loop with a check if the added day is not a weekend day and does not occur in the list of holidays, until the given number of days has been added.
Unfortunately, there is no easier way to do this.
I tried the code above and didn't work. The returned date will somehow includes the holidays and weekends as well. I also want to check that the returned date to be on Workdays only.
So, below are my modified codes.
Basically it will calculate the number of workdays to be added and if the end date falls on holidays/weekends, shift the date to the next day.
Do take note that this is on an assumption that the start date is not on weekends/holidays.
static DateTime CalculateFutureDate(DateTime fromDate, int numberofWorkDays,
ICollection<DateTime> holidays)
{
var futureDate = fromDate;
for (var i = 0; i < numberofWorkDays; i++ )
{
if (futureDate.DayOfWeek == DayOfWeek.Saturday
|| futureDate.DayOfWeek == DayOfWeek.Sunday
|| (holidays != null && holidays.Contains(futureDate)))
{
futureDate = futureDate.AddDays(1);
numberofWorkDays++;
}
else
{
futureDate = futureDate.AddDays(1);
}
}
while(futureDate.DayOfWeek == DayOfWeek.Saturday
|| futureDate.DayOfWeek == DayOfWeek.Sunday
|| (holidays != null && holidays.Contains(futureDate)))
{
futureDate = futureDate.AddDays(1);
}
return futureDate;
}
I've built something similar to check for Office Hours:
public static DateTime AddBusinessHours(DateTime date, long hours)
{
int i = 0;
DateTime tmpDate = date;
do
{
tmpDate = tmpDate.AddHours(1);
if (!IsWeekend(tmpDate) && !IsHoliday(tmpDate) && IsOfficeHours(tmpDate))
i++;
}
while (i < hours);
return tmpDate;
}
public static bool IsWeekend(DateTime date)
{
return (date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday);
}
public static bool IsHoliday(DateTime date)
{
//All dates in the holiday calendar are without hours and minutes.
//With the normal date object, the Contains does not work.
DateTime tmp = new DateTime(date.Year, date.Month, date.Day);
HolidayCalendar calendar = HolidayCalendar.Instance;
return (calendar.Dates.Contains(tmp));
}
public static bool IsOfficeHours(DateTime date)
{
return (date.Hour >= 8 && date.Hour < 20); //Office Hours are between 8AM and 8PM
}
But as mentioned above, you need to run your own holiday calendar.
public static DateTime AddBusinessDays(DateTime pActualDate, int pNumberofWorkDays)
{
ICollection<DateTime> holidays = GetAllHolidays();
int i = default(int);
while (i < pNumberofWorkDays)
{
pActualDate = pActualDate.AddDays(1);
if (pActualDate.DayOfWeek == DayOfWeek.Saturday || pActualDate.DayOfWeek == DayOfWeek.Sunday
|| (holidays != null && holidays.Contains(pActualDate))) { }
else
{ i++; }
}
return pActualDate;
}
private static ICollection<DateTime> GetAllHolidays()
{
ICollection<DateTime> holidays = GetPublicHolidays().Select(s => s.Holidays).ToList();
HashSet<DateTime> finalHolidays = new HashSet<DateTime>();
//if sunday holiday then the following monday will be holiday
bool isMonday = GetCalendar().Any(s => s.Type == "KR" && s.IsMonday);
foreach (var hol in holidays)
{
if (hol.DayOfWeek == DayOfWeek.Sunday && isMonday)
{
//adding monday following day holiday to the list
finalHolidays.Add(hol.AddDays(1));
}
}
//exclude weekends from the holiday list
var excludeWeekends = holidays.Where(s => s.DayOfWeek == DayOfWeek.Sunday || s.DayOfWeek == DayOfWeek.Saturday);
//adding monday to the existing holiday collection
finalHolidays.UnionWith(holidays.Except(excludeWeekends));
return finalHolidays;
}
How do I get in C# the number of days in a month without Friday and Saturday?
Obligatory LINQ solution:
int days = Enumerable.Range( 1, DateTime.DaysInMonth( year, month ) )
.Select( day => new DateTime( year, month, day ) )
.Count( d => d.DayOfWeek != DayOfWeek.Saturday &&
d.DayOfWeek != DayOfWeek.Friday );
Here, quick and dirty:
class Program
{
static void Main(string[] args)
{
int month = DateTime.Today.Month;
int year = DateTime.Today.Year;
int daysInMonthMinusFridayAndSaturday = 0;
for (int i = 1; i <= DateTime.DaysInMonth(year,month); i++)
{
DateTime thisDay = new DateTime(year,month,i);
if(thisDay.DayOfWeek != DayOfWeek.Friday && thisDay.DayOfWeek != DayOfWeek.Saturday)
{
daysInMonthMinusFridayAndSaturday += 1;
}
}
Console.WriteLine(daysInMonthMinusFridayAndSaturday);
Console.ReadLine();
}
}
I would avoid loops or iterations and do it like this:
int GetWorkDays(int year, int month)
{
var firstDayOfMonth = new DateTime(year, month, 1).DayOfWeek;
var daysInMonth = DateTime.DaysInMonth(year, month);
// count whole weeks first
var wholeWeeks = daysInMonth / 7;
var extraDays = daysInMonth % 7;
// calculate the overlap of the "remainder days" with the weekend.
var lastDayOfMonth = (int) (firstDayOfMonth + extraDays - 1);
var overlapStart = Math.Max((int) firstDayOfMonth, (int) DayOfWeek.Friday);
var overlapEnd = Math.Min(lastDayOfMonth, (int) DayOfWeek.Saturday);
var weekendOverlap = Math.Max(0, overlapEnd - overlapStart + 1);
// substract weekend days
return daysInMonth - wholeWeeks * 2 - weekendOverlap;
}