So for example if I have the following code:
var nodaStart = new LocalDate(2012, 5, 1);
var nodaEnd = new LocalDate(2012,5,2);
var daysBetween = Period.Between(nodaStart, nodaEnd,PeriodUnits.Day);
Then daysBetween.Days == 1
However, the range I calculate needs to count that as 2 days. i.e. it needs to be inclusive of the start and end date.
The actual method can take and start and end date (that are no more than a year apart) and needs to calculate the number of days. If there are more than 31 days then the remainder are returned as a number of whole weeks.
I have that logic working fine but because the count is exclusive I'm one day out.
I guess I can just do startDate.addDays(-1) before I create nodaStart from it but I wonder if there's a more elegant / pretty way to have noda return the Period.
Thanks
UPDATE:
I've had a read of the source code for the Period class and the + operator is overloaded so I can add
daysBetween += Period.FromDays(1);
(Sorry it's taken me so long to answer this - I hadn't seen it before.)
Any of:
Adding a day to the end before calculating (this is the most logical approach, IMO - as Roger says, you want the start of the next day, effectively)
Subtracting a day from the start before calculating
Adding 1 to the number of days you get out of the end
should be fine. I don't think Noda Time will change to make this any simpler. Between is a sort of "fuzzy around units" version of a subtraction operator - and you won't find many subtraction operators where 2 - 1 is 2.
For "fuzzy" brained humans, we may consider a period of days to be inclusive of start and end date if it identifies a single day, week, month, etc (cf. whole multiple of), so you could code it:
var start = new NodaTime.LocalDateTime(s.Year, s.Month, s.Day, s.Hour, s.Minute);
var end = new NodaTime.LocalDateTime(e.Year, e.Month, e.Day, e.Hour, e.Minute);
NodaTime.Period periodInclusive = NodaTime.Period.Between(start, end.PlusDays(1), NodaTime.PeriodUnits.AllDateUnits);
NodaTime.Period period = NodaTime.Period.Between(start, end, NodaTime.PeriodUnits.AllDateUnits);
bool isInclusivePeriod = periodInclusive.Days + periodInclusive.Weeks + periodInclusive.Months + periodInclusive.Years <
period.Days + period.Weeks + period.Months + period.Years;
period = isInclusivePeriod ? periodInclusive : period;
// do stuff with period here....
Related
This question already has answers here:
C# How to calculate working days excluding specific dates (Holidays)
(4 answers)
Closed 1 year ago.
The community reviewed whether to reopen this question 1 year ago and left it closed:
Original close reason(s) were not resolved
I'm creating an API and I need to subtract a certain number of periods and make sure I only quantify business days (excluding weekends and holidays) but I'm having trouble.
I've found some useful solutions to only capture weekdays when I subtract from dateTime.Now() but I need to exclude US federal holidays as well.
I'd probably just scroll it with a loop, repeatedly adding or subbing a period and testing upon each add/sub if the current day is banned, sub a whole day:
DateTime ScrollSkipping(DateTime start, TimeSpan period, int numPeriods, HashSet<DateTime> hashsetOfPublicHolidays){
var d = start; //eg new DateTime(2021, 12, 26, 0, 30, 0);
var t = period; //eg -1 hour to go backwards
var skip = TimeSpan.FromDays(period<TimeSpan.Zero?-1:1);
var p = numPeriods; // eg 25 times
for(int x = 0; x < p;){
if(d.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday || hashsetOfPublicHolidays.Contains(d.Date))
d += skip;
else{
d += t;
x++;
}
}
}
If you supplied a TimeSpan.FromHours(-1); as the period and 25 as the number of periods, and don't "use up" a period when you're skipping whole days for some reason, then resume decrementing it when you're back on a normal date then you'll skip completely over the weekends/holidays.
To go backwards supply a negative TimeSpan as the period, forwards positive
Obviously you need to know the 'US Federal holidays' There are some paid APIS that provide those data or as US holidays ar very simple, use a list built from this: https://www.law.cornell.edu/uscode/text/5/6103
I have a requirement to calculate the duration between two times that may span midnight.
The use case is to allow the user to set up shift plans e.g. “09:00 to 17:00” or “22:00 to 06:00” and calculate the contracted time
For “09:00 to 17:00” I can use:
LocalTime inAt = new LocalTime(9, 0);
LocalTime outAt = new LocalTime(17, 0);
var period = Period.Between(inAt, outAt);
Which results in 8 hours, the answer I am looking for.
For “22:00 to 06:00” the period returns 16 hours (regardless of the order of the parameters).
LocalTime inAt = new LocalTime(22, 0);
LocalTime outAt = new LocalTime(6, 0);
var period = Period.Between(inAt, outAt);
var period2 = Period.Between(outAt, inAt);
I am guessing that this is related to daylight saving time, unless you know the dates you cannot be sure that the answer will always the 8 hours. If the clocks go forward it would be 7, backwards would be 9.
How can I ensure that no matter what LocalTime values are used the period would disregard any daylight savings? Should I use LocalDateTime with an arbitrary date such as 2021-01-01?
Also, am I correct in using Period or should I be using Duration?
Update
This seems to work however I am still wondering if there is an eaiser way of doing it?
LocalTime inAt = new LocalTime(22, 0);
LocalTime outAt = new LocalTime(6, 0);
var period = Period.Between(inAt, outAt, PeriodUnits.Ticks);
LocalTime? midnightAdjustedTime = null;
if (period.Ticks < 0)
{
midnightAdjustedTime = LocalTime.Midnight + period;
}
var duration = Duration.FromTicks(midnightAdjustedTime?.TickOfDay ?? period.Ticks);
This has nothing to do with daylight savings - it can't do, given that everything is in terms of LocalTime. It's about negative periods.
For “22:00 to 06:00” the period returns 16 hours (regardless of the order of the parameters)
No, it doesn't. One returns 16 hours, the other returns -16 hours:
using NodaTime;
var start = new LocalTime(22, 0);
var end = new LocalTime(6, 0);
var startToEnd = Period.Between(start, end);
var endToStart = Period.Between(end, start);
Console.WriteLine(startToEnd);
Console.WriteLine(endToStart);
Output:
PT-16H
PT16H
It's not clear to me how you came to the conclusion that both returned the same value, but the fact that they don't return the same value is crucial to fixing the problem. The simple approach is just to add 1 day (24 hours) if the period is negative. The code you've got is almost right, but it can be done much simpler - and without using the Ticks unit at all:
// Note: Period.ToDuration() isn't "generally safe" due to variable lengths
// of months etc, but is okay here.
var duration = startToEnd.ToDuration();
if (duration < Duration.Zero)
{
duration += Duration.FromDays(1);
}
Console.WriteLine(duration);
Say I have an year, 2017.
I then have a date range, 01/07/2017 - 01-07-2018 OR 01/07/2017 - 01-01-2017 OR 01/01/2016 - 01/01/2018 ( <- this should return 365 days)
I now need to calculate how many total days are there in the given range for the given year.
Note that dates are stored as dd/mm/yyyy with an always 00:00:00 time.
What would the best logic be considering all possible cases of ranges?
You can compute the start and end dates for a year easily:
var start2017 = new DateTime(2017,1,1);
var end2017 = new DateTime(2017,12,31);
And then you can compute the overlap between this new range and your other range1:
var startOverlap = start2017 < startOtherRange ? startOtherRange : start2017;
var endOverlap = end2017 > endOtherRange ? endOtherRange : end2017;
var totalDays = (endOverlap - startOverlap).TotalDays + 1;
The above is correct if ranges are meant to include both their start and end dates. If you want, say, an exclusive endpoint then we'd adjust the end of out 2017 computed range one day further forwards and would no longer require the +1 adjustment at the end)
(And I presume you can derive from there how to turn it into a function if required that takes year, startRange, endRange parameters and does the above with some appropriate renaming)
1I had some vague recollection of DateTime.Min(value1, value2) and similarly for Max but it's definitely not in the BCL that I can see. Those would replace the conditional operators on the following lines. Once C# has "extension everything" these functions could be written as static extensions to DateTime.
This question already has answers here:
how to calculate number of weeks given 2 dates?
(7 answers)
Closed 9 years ago.
Lets say, I have two date Order date - 1/1/2014 and Delivery date - 6/2/2014. Now if I want to calculate how much work week its taken (Order date-delivery date), how can I do it in c#.
If you want the number of worked days in a date range, you can use this:
var from = DateTime.Today.AddDays(-10);
var to = DateTime.Today;
var daysOfWeek = new DayOfWeek[] { DayOfWeek.Monday, DayOfWeek.Tuesday
, DayOfWeek.Wednesday, DayOfWeek.Friday
, DayOfWeek.Thursday };
var days = Enumerable.Range(0, 1 + to.Subtract(from).Days)
.Select((n, i) => from.AddDays(i).DayOfWeek)
.Where(n => daysOfWeek.Contains(n.DayOfWeek));
If you want the number of weeks during a date range, use this:
(int)((to - from).TotalDays/7)
(int)((DeliveryDate-OrderDate).TotalDays/7)
I am presuming by "how much workweek" you mean "how many workdays". This is not so straightforward as it depends on the culture and you need to take holidays into account.
If you rely on Mon through Fri being the work days you could use a solution similar to what was discussed in c# DateTime to Add/Subtract Working Days, counting each day from Order Date to Delivery Date for which the conditions hold.
That Q&A still leaves you with the issue of how to determine the holidays of a certain region (be warned - in Switzerland each part of the country has different holidays!).
Update: From Nagaraj's suggested link I gather that you might also refer to "weeks" as chunks (that is "how many workweeks it has taken"). If so, in turn, you will need to define how many days of a week must be taken to take the week into account...
I'm using strings and convert that to dates, because I'm not sure where you get your dates and in what form. Adjust your code accordingly.
string orderDate = #"1/1/2014";
string deliveryDate = #"6/2/2014";
// This will give you a total number of days that passed between the two dates.
double daysPassed = Convert.ToDateTime(deliveryDate).
Subtract(Convert.ToDateTime(orderDate)).TotalDays;
// Use this if you want actual weeks. This will give you a double approximate. Change to it to an integer round it off (truncate it).
double weeksPassed = daysPassed / 7;
// Use this if you want to get an approximate number of work days in those weeks (based on 5 days a week schedule).
double workDaysPassed = weeksPassed * 5;
I guess you are not interested in working days but weeks. You can use GetWeekOfYear:
http://msdn.microsoft.com/en-us/library/system.globalization.calendar.getweekofyear%28v=vs.110%29.aspx
EDIT
To respond to the comment, here some code example:
int start = System.Globalization.CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(new DateTime(2014, 1, 14), System.Globalization.CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
int end = System.Globalization.CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(new DateTime(2014, 2, 3), System.Globalization.CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
int weeks = end - start;
That should give you the weeks needed.
I am looking to use this plugin: http://keith-wood.name/countdown.html
but I need to use the TimeZone feature of it. So I was looking at the sample code
$('#sydneyCountdown').countdown({until: liftoffTime, timezone: +10});
so +10 is the TimeOffSet number. Now I need to make it so I can do a jquery get request and grab the TimeOffSet from the server(which gets the users time from the db and does TimeOffSet).
However it seems that C# TimeOffSet returns something like this "+02:00"(not this is just a random zone not the same as the one in the jquery example).
So it seems like all the C# TimeOffSet follow that format +/-xx:xx
So I don't understand why the jquery plugin is only 2 digts while the other one is 4 digits.
Can I know off safley the last 2 digits in the C# tomatch the plugin format?
Edit - would this work?
// working on how to get offsetTime will be posted soon.
string time = "-08:30";
string[] split = new string[] {":"};
string[] splited = time.Split(split, StringSplitOptions.RemoveEmptyEntries);
int hours = Convert.ToInt32(splited[0]);
int mins = Convert.ToInt32(splited[1]);
int totalMins = (hours * 60) + mins;
So just convert the hours to mins and then add the mins to it?
Edit - With offSetTime
var info = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");
TimeSpan span = info.BaseUtcOffset;
string time = Convert.ToString(span);
string[] split = new string[] {":"};
string[] splited = time.Split(split, StringSplitOptions.RemoveEmptyEntries);
int hours = Convert.ToInt32(splited[0]);
int mins = Convert.ToInt32(splited[1]);
int totalMins = (hours * 60) + mins;
Problem though OffSet only gives me the number not the +/- sign. So I don't know how to get it.
Edit -
Never mind I it makes sense that they don't add the plus sign since I was just testing one that would have a "-" sign and it is shown.
The plugin says:
Cater for time zones with the timezone
setting, which is set to the target
time's offset from GMT, in either
hours or minutes.
So, I think based on the magnitude of the timezone value, it treats it either as hours or minutes. You should convert the <sign><hh>:<mm> format to into number of minutes, to account for timezones that are not hour-aligned. Something like:
var tz = "-08:30";
var tz_tokens = /([+\-])0?(\d+):(\d+)/.exec(tz);
var tz_minutes = (tz_tokens[1] + 1) * (tz_tokens[2] * 60 + tz_tokens[3]);
// ..., timezone: tz_minutes, ...