I am developing c#.net solution where i have to calculate Start Date based on provided End date and Duration (back days) without weekends.
i.e. End Date: 05/5/2011
Back days: 5
Start Date = (05/5/2011) - 5 days (Excludes weekends)
Start Date = 29/04/2011
thanks you,
Something like this is probably what I'd do:
DateTime CalcStartDate(DateTime endTime, int daysBack)
{
DateTime startTime = endTime.Date;
while (daysBack > 0)
{
startTime = startTime.AddDays(-1);
if (startTime.DayOfWeek != DayOfWeek.Saturday && startTime.DayOfWeek != DayOfWeek.Sunday)
{
--daysBack;
}
}
return startTime;
}
Or even better, Bala's suggestion of using a library. Date and time is messy, a hardened/tested library is usually a good choice.
Bala R has the answer correctly. Here is a link to an article on how to use the AddBusinessDays() method:
http://www.codeproject.com/KB/cs/AddBusinessDay.aspx
I don't know how far back you are going to go. If it is a lot of days back then looping through the days might be a little CPU intensive. Well, probably not with modern processors...
I decided to implement a solution without a loop.
My code is a little more difficult to read, but it should be more efficient performance-wise.
public static class DateTimeExtensions
{
public static DateTime SubtractBusinessDays(this DateTime fromDateTime, int days)
{
var subtractDays = days % 5;
var dayNumber = fromDateTime.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)fromDateTime.DayOfWeek;
var addDays = Math.Max(dayNumber - 5, 0);
var result = fromDateTime.AddDays(addDays - subtractDays - (days / 5 * 7));
if ((addDays + dayNumber) % 7 <= subtractDays)
result = result.AddDays(-2);
return result;
}
}
Related
This question already has answers here:
Timer callback raised every 24 hours - is DST handled correctly?
(3 answers)
Closed 5 years ago.
I'm not in front of my code right now, but don't really think I need it to ask this question. So I have a countdown timer that goes off every 18 hours and then resets. The timer checks the current DateTime.Now and adjusts the countdown timer as needed. I am having an issue trying to account for when daylight savings when it goes back an hour because; for example this past event on November 5th 2017 at 2am it goes back to 1am but when I do DateTime.IsDaylightSavingTime() it tells me that it's false even though Daylight Saving Time just went off. This makes my timer go back an extra hour because it thinks that Daylight Saving Time still hasn't happen for that one hour period. How would I get around this?
If you realy need to use local time for some reason, than you should count for DST changes in advance (prior scheduling next event).
TimeZoneInfo.GetAdjustmentRules() should help you to get the time and delta of next DST adjustment.
Following is the code for getting upcomming adjustment:
public static DateTime? GetNextAdjustmentDate(TimeZoneInfo timeZoneInfo)
{
var adjustments = timeZoneInfo.GetAdjustmentRules();
if (adjustments.Length == 0)
{
return null;
}
int year = DateTime.UtcNow.Year;
TimeZoneInfo.AdjustmentRule adjustment = null;
foreach (TimeZoneInfo.AdjustmentRule adjustment1 in adjustments)
{
// Determine if this adjustment rule covers year desired
if (adjustment1.DateStart.Year <= year && adjustment1.DateEnd.Year >= year)
adjustment = adjustment1;
}
if (adjustment == null)
return null;
//TimeZoneInfo.TransitionTime startTransition, endTransition;
DateTime dstStart = GetCurrentYearAdjustmentDate(adjustment.DaylightTransitionStart);
DateTime dstEnd = GetCurrentYearAdjustmentDate(adjustment.DaylightTransitionEnd);
if (dstStart >= DateTime.UtcNow.Date)
return dstStart;
if (dstEnd >= DateTime.UtcNow.Date)
return dstEnd;
return null;
}
private static DateTime GetCurrentYearAdjustmentDate(TimeZoneInfo.TransitionTime transitionTime)
{
int year = DateTime.UtcNow.Year;
if (transitionTime.IsFixedDateRule)
return new DateTime(year, transitionTime.Month, transitionTime.Day);
else
{
// For non-fixed date rules, get local calendar
System.Globalization.Calendar cal = CultureInfo.CurrentCulture.Calendar;
// Get first day of week for transition
// For example, the 3rd week starts no earlier than the 15th of the month
int startOfWeek = transitionTime.Week * 7 - 6;
// What day of the week does the month start on?
int firstDayOfWeek = (int)cal.GetDayOfWeek(new DateTime(year, transitionTime.Month, 1));
// Determine how much start date has to be adjusted
int transitionDay;
int changeDayOfWeek = (int)transitionTime.DayOfWeek;
if (firstDayOfWeek <= changeDayOfWeek)
transitionDay = startOfWeek + (changeDayOfWeek - firstDayOfWeek);
else
transitionDay = startOfWeek + (7 - firstDayOfWeek + changeDayOfWeek);
// Adjust for months with no fifth week
if (transitionDay > cal.GetDaysInMonth(year, transitionTime.Month))
transitionDay -= 7;
return new DateTime(year, transitionTime.Month, transitionDay);
}
}
You'd need to add some more code to retrieve and apply the adjustment delta.
Well - now, when you see all the hard work that would need to be done (and than maintained and made sure to be bug free), you might want to rething your problem to be able to use UTC.
I would use DateTime.UtcNow you won't have the issue with daylight savings
I am trying to extract dates for current week's days and I just can't find a sensible, smart way instead of a long case, switches and if statements.
Anybody knows a relatively easy way to extract those using .Net?
Thanks!
The DateTime.DayOfWeek is an enumeration that starts with Sunday being 0 and going forward. If you take today's day-of-week, it will also tell how many days ago Sunday was. Therefore going back that many days will give you the Sunday of this week, assuming week starts on Sunday. You can go forward from that for the seven days of the week.
var today = DateTime.Now;
var thisSunday = today.AddDays(-(int)today.DayOfWeek);
for (int i=0; i<7; i++)
Console.WriteLine(thisSunday.AddDays(i).ToString());
If the week starts from Monday, use
var thisMonday = today.AddDays(-(((int)today.DayOfWeek + 6) % 7));
You may use extension method to set the day that week start with (credit goes to #Compile This)
public static class DateTimeExtensions
{
public static DateTime StartOfWeek(this DateTime datetime, DayOfWeek startOfWeek)
{
int difference = datetime.DayOfWeek - startOfWeek;
if (difference >= 0)
return datetime.AddDays(-1 * difference).Date;
difference += 7;
return datetime.AddDays(-1 * difference).Date;
}
}
Then you can get date of the week using same loop as #Sami_Kuhmonen mentioned:
DateTime d = DateTime.Now.StartOfWeek(DayOfWeek.Saturday);
for (int i = 0; i < 7; i++)
Console.WriteLine(d.AddDays(i));
I need to take a month (defined as a start and end date) and return a set of date ranges for each week in that month. A week is defined as Sunday through Saturday. A good way to visualize it is if you double click on your Windows date in the start bar:
The month of October 2011 has 6 weeks: 10/1-10/1, 10/2-10/8, 10/9-10/15, 10/16-10/22, 10/23-10/29 and 10/30-10/31.
I can describe each week as a struct:
struct Range
{
public DateTime Start;
public DateTime End;
public Range(DateTime start, DateTime end)
{
Start = start;
End = end;
}
}
I need to write a function that takes a month and returns an array of ranges within it. Here's my first attempt, which appears to work and addresses the obvious edge cases:
public static IEnumerable<Range> GetRange(DateTime start, DateTime end)
{
DateTime curStart = start;
DateTime curPtr = start;
do
{
if (curPtr.DayOfWeek == DayOfWeek.Saturday)
{
yield return new Range(curStart, curPtr);
curStart = curPtr.AddDays(1);
}
curPtr = curPtr.AddDays(1);
} while (curPtr <= end);
if(curStart <= end)
yield return new Range(curStart, end);
}
I would like to know if there's a cleaner or more obvious approach to do the same. I'm not overly concerned about performance, but I'd like to improve code readability and make the algorithm a bit more concise. Perhaps there's a very creative solution involving a single LINQ expression or something. Thanks!
This is based on simply incrementing by 7, as suggested by Previti, ready for international use. If your C# is < 4.0, remove the default parameter = DayOfWeek.Sunday
public static IEnumerable<Range> GetRange(DateTime start, DateTime end, DayOfWeek startOfTheWeek = DayOfWeek.Sunday)
{
if (start > end)
{
throw new ArgumentException();
}
// We "round" the dates to the beginning of the day each
start = start.Date;
end = end.Date;
// The first week. It could be "shorter" than normal. We return it "manually" here
// The 6 + startOfWeek - start.DayOfWeek will give us the number of days that you
// have to add to complete the week. It's mod 7. It's based on the idea that
// the starting day of the week is a parameter.
DateTime curDay = new DateTime(Math.Min(start.AddDays((6 + (int)startOfTheWeek - (int)start.DayOfWeek) % 7).Ticks, end.Ticks), start.Kind);
yield return new Range(start, curDay);
curDay = curDay.AddDays(1);
while (curDay <= end)
{
// Each time we add 7 (SIX) days. This is because the difference between
// as considered by the problem, it's only 6 * 24 hours (because the week
// doesn't end at 23:59:59 of the last day, but at the beginning of that day)
DateTime nextDay = new DateTime(Math.Min(curDay.AddDays(6).Ticks, end.Ticks), start.Kind);
yield return new Range(curDay, nextDay);
// The start of the next week
curDay = nextDay.AddDays(1);
}
}
Some small notes: Math.Min isn't defined for DateTime, so I cheat a little by taking the Ticks of the DateTimes and comparing them. Then I rebuild the DateTime. I always use the DateTimeKind of the start date.
When you debug yield code, remember to "materialize" the result through the use of ToList or ToArray, otherwise the code won't be executed :-)
.NET gives me the current time in both local and UTC time zones (or any other) in the DateTime struct.
Given only hour/minute variables, find when the next occurrence of this time period (eg 6:30 PM/AM) will occur, with the ability to retrieve more future times at will.
It sounds easy but indeed friends this has been breaking my noodle for a good while.
Edit:
Example:
~-------|now|------??-----------|future known time|------------~
~-------2pm------??2-----------9am------------~
??2 = 19
If I understand correctly you want to know how much time has to pass in order to hit the next given hour:minute. You can use the TimeSpan struct for this.
//this is your target time from 1 to 12 h
var future = new TimeSpan(11, 30, 0);
//TimeOfDay gives you the time elapsed since midnight as a TimeSpan
var difference = future.Subtract(DateTime.Now.TimeOfDay);
//check for negative TimeSpan,
//it means the target time occurs on the next day, just add 24 hours
if (difference < TimeSpan.Zero)
difference = difference.Add(TimeSpan.FromDays(1));
Now you have a TimeSpan that represents what you need. You can use its properties to express it as you seem fit. For example:
difference.TotalHours; //(double) total time as a fractional hour
difference.Hours; //(int) just the hour component of the total time
As for retreiving more future times (am and pm), you can just add 12 more hours to difference to get the next occurence.
This was cooded in stackoverflow form, so it's probably there are some typos. Either way, you'll get the big picture.
public DateTime search(int hour, int min) {
if (hour >= 12)
return partialSearch(hour - 12, hour, min);
else
return partialSearch(hour, hour + 12, min);
}
public DateTime partialSearch(int morningHour, int afternoonHour, int min) {
DateTime now = DateTime.now;
if (now.hour == morningHour || now.hour == afternoonHour) {
if (now.minutes <= min) {
return now.AddMinutes(min - now.minutes);
}
now = now.AddHour(1);
}
now = now.AddMinutes(-now.Minutes); // set the minutes to 0
while(now.hour != morningHour || now.hour != afternoonHour) {
now = now.AddHour(1);
}
return now.addMinutes(min);
}
Not sure if I fully understood your requirements, but it seems you are looking for something like this?
public class TimeIterator
{
public DateTime CurrDateTime { get; set; }
public IEnumerable<DateTime> GetTimes(short count)
{
for (short i = 1; i <= count; i++)
yield return this.CurrDateTime.AddHours(i * 12);
}
public TimeIterator()
{
this.CurrDateTime = DateTime.Now;
}
}
This code can be easily adjusted to work with any time interval - not just 12 hour interval
I feel like this is math problem more than anything. My company has employees all over the country. Some parts of the company are on an "odd" pay cycle and some are on "even". I call the starting date of a given pay period a "payperiod". I need to do two things:
1) determine the payperiod in which a given date falls
//Something like this:
public static DateTime getPayPeriodStartDate(DateTime givenDate, string EvenOrOdd)
{ .. }
2) get a list of payperiods between two dates:
//Something like this:
public static List<DateTime> getPayPeriodsBetween(DateTime start, DateTime end, string EvenOrOdd)
{ .. }
I'm using a couple dates as fixed standards on which to base any future pay period dates. The fixed standard dates for even and odd are as follows:
Even - 01/04/09
Odd - 01/11/09
Each pay period starts on the sunday of the week and goes for two weeks. For instance, using the standard dates above, the first even pay period starts on 01/04/09 and ends on 01/17/09. The first odd pay period starts on 01/11/09 and ends on 01/24/09. As you can see, there is some overlap. We have thousands of employees so it's necessary to split them up a bit.
I have a solution that is based on week numbers but it's clunky and has to be "fixed" every new year. I'm wondering how you would handle this.
Not fully optimized or tested, but this is what I came up with:
const int DaysInPeriod = 14;
static IEnumerable<DateTime> GetPayPeriodsInRange(DateTime start, DateTime end, bool isOdd)
{
var epoch = isOdd ? new DateTime(2009, 11, 1) : new DateTime(2009, 4, 1);
var periodsTilStart = Math.Floor(((start - epoch).TotalDays) / DaysInPeriod);
var next = epoch.AddDays(periodsTilStart * DaysInPeriod);
if (next < start) next = next.AddDays(DaysInPeriod);
while (next <= end)
{
yield return next;
next = next.AddDays(DaysInPeriod);
}
yield break;
}
static DateTime GetPayPeriodStartDate(DateTime givenDate, bool isOdd)
{
var candidatePeriods = GetPayPeriodsInRange(givenDate.AddDays(-DaysInPeriod), givenDate.AddDays(DaysInPeriod), isOdd);
var period = from p in candidatePeriods where (p <= givenDate) && (givenDate < p.AddDays(DaysInPeriod)) select p;
return period.First();
}
I haven't tested for many test cases, but I think this fits the bill:
public static DateTime getPayPeriodStartDate(DateTime givenDate, string EvenOrOdd)
{
DateTime newYearsDay = new DateTime(DateTime.Today.Year, 1, 1);
DateTime firstEvenMonday = newYearsDay.AddDays((8 - (int)newYearsDay.DayOfWeek) % 7);
DateTime firstOddMonday = firstEvenMonday.AddDays(7);
TimeSpan span = givenDate - (EvenOrOdd.Equals("Even") ? firstEvenMonday : firstOddMonday);
int numberOfPayPeriodsPast = span.Days / 14;
return (EvenOrOdd.Equals("Even") ? firstEvenMonday : firstOddMonday).AddDays(14 * numberOfPayPeriodsPast);
}
public static List<DateTime> getPayPeriodsBetween(DateTime start, DateTime end, string EvenOrOdd)
{
DateTime currentPayPeriod = getPayPeriodStartDate(start, EvenOrOdd);
if (currentPayPeriod < start) currentPayPeriod = currentPayPeriod.AddDays(14);
List<DateTime> dtList = new List<DateTime>();
while (currentPayPeriod <= end)
{
dtList.Add(currentPayPeriod);
currentPayPeriod = currentPayPeriod.AddDays(14);
}
return dtList;
}
I am sure it can be improved.
I had a need to do something similar and was able to do it very easily using LINQ. Simply build up a List for even and odd and then query between dates from the odd/even as necessary. Also, I recommend you move to an emum for parameters like EvenOrOdd where you have fixed values.
I had a similar problem a few months ago, and I ended up writing a quick script to create entries in a database for each pay period so I never had to actually do the math. This way, The system works the same speed, and doesn't have to do any slow iterations every time a period is requested.
That being said, you can always take the starting date, and add two weeks (or however long your periods are) over and over until you reach the dates you specify in the function call. This is a bit ugly, and the longer it sits in production, the slower it gets (since the dates are getting further and further apart).
Both ways are trivial to implement, it's just a matter of what kind of resources you have at hand to tackle the issue.
So, for number 1: Start with either 1/4/2009 or 1/11/2009 (depending on even/odd pay week) and add 2 weeks until the givenDate is less than the date you're testing + 2 weeks. That's the start of the period.
For number 2: Same thing, start at the date and add 2 weeks until you're within the date range. While you're there, add each item to a list. As soon as you're past the last date, break out of your loop and return your shiny new list.
If you used my method and went with a database to house all this info, it turns into 2 simple queries:
1)SELECT * FROM payperiods WHERE startdate<=givenDate ORDER BY startdate LIMIT 1
2) SELECT * FROM payperiods WHERE startdate>=givenDate AND enddate<=givenDate ORDER BY startdate
It works perfectly. I have tested.
public static DateTime GetFirstDayOfWeek(DateTime dayInWeek)
{
CultureInfo _culture = (CultureInfo)CultureInfo.CurrentCulture.Clone();
CultureInfo _uiculture = (CultureInfo)CultureInfo.CurrentUICulture.Clone();
_culture.DateTimeFormat.FirstDayOfWeek = DayOfWeek.Monday;
_uiculture.DateTimeFormat.FirstDayOfWeek = DayOfWeek.Monday;
System.Threading.Thread.CurrentThread.CurrentCulture = _culture;
System.Threading.Thread.CurrentThread.CurrentUICulture = _uiculture;
// CultureInfo defaultCultureInfo = CultureInfo.CurrentCulture;
DayOfWeek firstDay = _culture.DateTimeFormat.FirstDayOfWeek;
DateTime firstDayInWeek = dayInWeek.Date;
// Logic Of getting pay period Monday(Odd monday)
int i = Convert.ToInt32(firstDay);
while (firstDayInWeek.DayOfWeek != firstDay)
if (i % 2 != 0)
{ firstDayInWeek = firstDayInWeek.AddDays(-1); }
else
{
firstDayInWeek = firstDayInWeek.AddDays(-2);
}
return firstDayInWeek;
}