Better way to get Interval bounds when only time is given - c#

I have to find out the limits of interval i.e upper-bound and lower-bound of an interval based on interval type when datetime is given.
Example: say given time = 12:05 (then, this lies in the interval range 12:00 - 1:00 if interval type is hourly; 12:00 - 12:30 if interval type is half-an hour based;
12:00 - 12:15 if interval type is quarterly. likewise interval type can be anything.
Currently i am loading all different set of interval ranges in a dictionary object on an application load and then i fetch interval range from this dictionary for the given time.
Sorry, I know this problem statement looks simple but couldn't think of other approaches as of now. It would be helpful if someone can help me here. Thanks in advance.

You can calculate the range start by dividing the total minutes by your interval and then subtracting the remainder from the total minutes. After that, you can easily get the end of the range.
First, you need to get the time part from your DateTime object as TimeSpan by using DateTime.TimeOfDay. Then use TimeSpan.TotalMinutes.
Here's a good start:
public class TimeRange
{
public TimeRange(TimeSpan from, TimeSpan to)
{
From = from;
To = to;
}
public TimeSpan From { get; set; }
public TimeSpan To { get; set; }
}
public TimeRange GetRange(DateTime d, int minutesInterval)
{
TimeSpan time = d.TimeOfDay;
var from = time.TotalMinutes - (time.TotalMinutes % minutesInterval);
var to = from + minutesInterval;
return new TimeRange(TimeSpan.FromMinutes(from), TimeSpan.FromMinutes(to));
}
For clarity, I created a simple class called TimeRange to represent the start and end of the interval range. You can, however, feel free to handle this in a different way.
Usage:
DateTime d = DateTime.Now;
TimeRange range = GetRange(d, 60);
//TimeRange range = GetRange(d, 15);
Console.WriteLine("From: {0}\r\nTo: {1}", range.From, range.To);
Try it online.

Related

Rounding time in Nodatime to nearest interval

We need to floor a time to the nearest arbitrary interval (represented by e.g. a Timespan or a Duration).
Assume for an example that we need to floor it to the nearest ten minutes.
e.g. 13:02 becomes 13:00 and 14:12 becomes 14:10
Without using Nodatime you could do something like this:
// Floor
long ticks = date.Ticks / span.Ticks;
return new DateTime( ticks * span.Ticks );
Which will use the ticks of a timespan to floor a datetime to a specific time.
It seems NodaTime exposes some complexity we hadn't considered before. You can write a function like this:
public static Instant FloorBy(this Instant time, Duration duration)
=> time.Minus(Duration.FromTicks(time.ToUnixTimeTicks() % duration.BclCompatibleTicks));
But that implementation doesn't seem correct.
"Floor to nearest ten minutes" seems to be dependent on timezone/offset of the time.
While might be 13:02 in UTC, in Nepal which has an offset of +05:45, the time would be 18:47.
This means that in UTC, flooring to the nearest ten minutes, would mean subtracting two minutes, while in Nepal, it would mean subtracting seven minutes.
I feel like I should be able to round a ZonedDateTime or an OffsetDateTime by an arbitrary timespan somehow. I can get close by writing a function like this
public static OffsetDateTime FloorToNearestTenMinutes(this OffsetDateTime time)
{
return time
.Minus(Duration.FromMinutes(time.Minute % 10))
.Minus(Duration.FromSeconds(time.Second));
}
but that doesn't allow me to specify an arbitrary duration, as the OffsetDateTime has no concept of ticks.
How do I round an Instant/ZonedDateTime/OffsetDateTime correctly, with an arbitrary interval, taking into account time zones?
For OffsetDateTime, I'd advise you to write a Func<LocalTime, LocalTime> which is effectively an "adjuster" in Noda Time terminology. You can then just use the With method:
// This could be a static field somewhere - or a method, so you can use
// a method group conversion.
Func<LocalTime, LocalTime> adjuster =>
new LocalTime(time.Hour, time.Minute - time.Minute % 10, 0);
// The With method applies the adjuster to just the time portion,
// keeping the date and offset the same.
OffsetDateTime rounded = originalOffsetDateTime.With(adjuster);
Note that this only works because your rounding will never change the date. If you need a version that can change date as well (e.g. rounding 23:58 to 00:00 of the next day) then you'd need to get the new LocalDateTime and construct a new OffsetDateTime with that LocalDateTime and the original offset. We don't have a convenience method for that, but it's just a matter of calling the constructor.
ZonedDateTime is fundamentally trickier due to the reasons you've given. Right now, Nepal doesn't observe DST - but it might do so in the future. Rounding near the DST boundary could take you into an ambiguous or even skipped time, potentially. That's why we don't provide a similar With method for ZonedDateTime. (In your case it isn't likely, although it's historically possibly... with date adjusters you could easily end up in this situation.)
What you could do is:
Call ZonedDateTime.ToOffsetDateTime
Round the OffsetDateTime as above
Call OffsetDateTime.InZone(zone) to get back to a ZonedDateTime
You could then check that the offset of the resulting ZonedDateTime is the same as the original, if you wanted to detect weird cases - but you'd then need to decide what to actually do about them. The behaviour is fairly reasonable though - if you start with a ZonedDateTime with a time portion of (say) 01:47, you'll end up with a ZonedDateTime in the same time zone from 7 minutes earlier. It's possible that wouldn't be 01:40, if a transition occurred within the last 7 minutes... but I suspect you don't actually need to worry about it.
I ended up taking some stuff from Jon Skeets answer and rolling my own Rounder that takes in an arbitrary Duration to round with. (Which was one of the key things I needed, which is also why I'm not accepting that answer).
Per Jons suggestion I convert the Instant to an OffsetDateTime and apply the rounder, which takes in an arbitrary duration. Example and implementation is below:
// Example of usage
public void Example()
{
Instant instant = SystemClock.Instance.GetCurrentInstant();
OffsetDateTime offsetDateTime = instant.WithOffset(Offset.Zero);
var transformedOffsetDateTime = offsetDateTime.With(t => RoundToDuration(t, Duration.FromMinutes(15)));
var transformedInstant = transformedOffsetDateTime.ToInstant();
}
// Rounding function, note that it at most truncates to midnight at the day.
public static LocalTime RoundToDuration(LocalTime timeToTransform, Duration durationToRoundBy)
{
var ticksInDuration = durationToRoundBy.BclCompatibleTicks;
var ticksInDay = timeToTransform.TickOfDay;
var ticksAfterRounding = ticksInDay % ticksInDuration;
var period = Period.FromTicks(ticksAfterRounding);
var transformedTime = timeToTransform.Minus(period);
return transformedTime;
}
For anyone interested here is my implementation, which correctly accounts for the occasions we cross a day, and always rounds up (rather than floors):
public static class RoundingExtensions
{
private static readonly Duration OneDay = Duration.FromDays(1);
public static LocalTime RoundUpToDuration(this LocalTime localDateTime, Duration duration)
{
if (duration <= Duration.Zero) return localDateTime;
var ticksInDuration = duration.BclCompatibleTicks;
var ticksInDay = localDateTime.TickOfDay;
var ticksAfterRounding = ticksInDay % ticksInDuration;
if (ticksAfterRounding == 0) return localDateTime;
// Create period to add ticks to get to next rounding.
var period = Period.FromTicks(ticksInDuration - ticksAfterRounding);
return localDateTime.Plus(period);
}
public static OffsetDateTime RoundUpToDuration(this OffsetDateTime offsetDateTime, Duration duration)
{
if (duration <= Duration.Zero) return offsetDateTime;
var result = offsetDateTime.With(t => RoundUpToDuration(t, duration));
if (OffsetDateTime.Comparer.Instant.Compare(offsetDateTime, result) > 0) result = result.Plus(OneDay);
return result;
}
public static ZonedDateTime RoundUpToDuration(this ZonedDateTime zonedDateTime, Duration duration)
{
if (duration <= Duration.Zero) return zonedDateTime;
var odt = zonedDateTime.ToOffsetDateTime().RoundUpToDuration(duration);
return odt.InZone(zonedDateTime.Zone);
}
}

Calculating time with hours and minutes only

I am attempting to create a timesheet calculator which takes calculates the time an employee works and I am close, with one problem.
As I perform the calculation, I only want hours and minutes to display. I am able to get that done, but that causes an issue. If the employee punches out before a full minute is elapsed, that minute is not included in the calculation.
For example, if an emp punches in at 12:00:30 and punches out at 5:00:29, that last minute is not counted in the calculation, so the time shows as 4:59 instead of 5:00.
How do I get the calculation to be based on the hours and minutes and exclude seconds completely?
This is the code I have:
private void btnPunchOut_Click(object sender, EventArgs e)
{
DateTime stopTime = DateTime.Now;
lblPunchOutTime.Text = stopTime.ToShortTimeString();
TimeSpan timeWorked = new TimeSpan();
timeWorked = stopTime - startTime;
lblTimeWorked.Text = timeWorked.ToString(#"hh\:mm");
}
Use TimeSpan.TotalSeconds perhaps...And then add 30 seconds or more, before you convert it to hours by dividing by 3600.
As in
lblTimeWorked.Text = ((timeWorked.TotalSeconds+30)/3600).ToString("0.00") + " hours";
Use Timespan.TotalHours if you want the hours.
But if you want to be accurate, you should create a separate class dedicated to calculating the hours worked by a staff member. Then you can encapsulate lots of business rules in the dedicated class. Staff have entitlements and overtime, expenses or penalty rates - so this can get complex if done properly.
If you want a calculation that really ignores the seconds, the clearest way to accomplish that is to get rid of the seconds on both the start time and the end time. It might not seem accurate because it allows a difference of one second to become a difference of one minute. But that could still be a valid business rule, that you want to subtract according the the minutes that appeared on the clock rather than the actual elapsed seconds.
In other words,
1:00:01 is adjusted to 1:00:00.
1:00:59 is adjusted to 1:00:00.
1:01:00 is "adjusted" to 1:01:00.
1:01:01 is adjusted to 1:01:00.
You can accomplish that with an extension like this:
public static class TimespanExtensions
{
public static TimeSpan TrimToMinutes(this TimeSpan input)
{
return TimeSpan.FromMinutes(Math.Truncate(input.TotalMinutes));
}
}
(I'm sure there's a more efficient way of truncating the seconds, but at least this is clear.)
Now instead of having to figure out how to calculate the difference while rounding seconds or adding seconds, you just trim the seconds before calculating the difference. Here's a unit test:
[TestMethod]
public void NumberOfMinutesIgnoresSeconds()
{
var startTime = TimeSpan.FromSeconds(59).TrimToMinutes();
var endTime = TimeSpan.FromSeconds(60).TrimToMinutes();
Assert.AreEqual(1, (endTime - startTime).TotalMinutes);
}
One Timespan represents 59 seconds, and the next one is 60, or the first second of the next minute. But if you trim the seconds and then calculate the difference you get exactly one minute.
In the context of your code,
DateTime stopTime = DateTime.Now;
lblPunchOutTime.Text = stopTime.ToShortTimeString();
var timeWorked = stopTime.TrimToMinutes() - startTime.TrimToMinutes();
lblTimeWorked.Text = timeWorked.ToString(#"hh\:mm");

Timer Max interval

Using System.Windows.Form.Timer the interval is an int, which gives a maximum interval limit of around 25 days. I know I could create some arbitrary algorithm to start another timer once the limit is reached, but that's just daft.
MISLEADING-IGNORE-->So if I want to set it to around 29 days (2619609112.7228003) milliseconds?<--MISLEADING-IGNORE
EDIT:
The real question here is how can I set System.Windows.Form.Timer to a value higher than maxInt?
The purpose is that I need to set an interval from whenever to the first day of the next month, so it could be 28,29,30 or 31 days, and when that interval expires, calculate the interval to the next first day of the month.
(Basically a Crystal Report is to be run on the 1st day of the month and printed (around 500 pages), because of the length of the reports it is to be run out of hours so it doesn't tie up the printer.)
e.g. run it today (today is 1/12/15), 1/1/16 is next 'first day of the month' so set the interval to the milliseconds between now and then.
1/1/16 comes around so the timer ticks, then calculate and set the interval for 1/2/2016 (the next first day of the month).
#SeeSharp - I did see that question, but I am working on a legacy app and am unsure of the implications of changing the timer, but if I can't get this timer to work I may look at the threading one, thanks.
EDIT2: Thanks for all of your suggestions, I've opted for a 3rd party plugin called FluentScheduler
Set the timer interval to one day (say) and use it to count the number of days up to 29.
Edit
Set the timer to half a day (say) and use it to check that the date is the first of the month.
How about a Month timer - This will fire close to midnight when the month changes. May be that suits your requirement better ?
If we have to consider day-light saving too, then perhaps the timer should fire at 2:00 AM on the 1st day of month so I'll make it configurable.
Here is a code to explain my idea -
public class MonthTimer : IDisposable
{
public event EventHandler<MonthChangedEventArgs> MonthChanged;
DateTime mLastTimerDate;
Timer mTimer;
public MonthTimer(TimeSpan timeOfFirstDay)
: this(DateTime.Now, timeOfFirstDay)
{
}
public MonthTimer(DateTime currentDate, TimeSpan timeOfFirstDay)
{
mLastTimerDate = currentDate.Date;
var milliSecondsInDay = new TimeSpan(1, 0, 0, 0).TotalMilliseconds;
Contract.Assert(timeOfFirstDay.TotalMilliseconds <= milliSecondsInDay); // time within 1st day of month
DateTime currentDateLastSecond = currentDate.Date.AddDays(1).AddTicks(-1); // one tick before midnight
TimeSpan timeSpanInCurrentDate = currentDateLastSecond.Subtract(currentDate); // remaining time till today ends
// I want the timer to check every day at specifed time (as in timeOfFirstDay) if the month has changed
// therefore at first I would like timer's timeout to be until the same time, following day
var milliSecondsTillTomorrow = (timeSpanInCurrentDate + timeOfFirstDay).TotalMilliseconds;
// since out milliseconds will never exceed - . Its okay to convert them to int32
mTimer = new Timer(TimerTick, null, Convert.ToInt32(milliSecondsTillTomorrow), Convert.ToInt32(milliSecondsInDay));
}
private void TimerTick(object state)
{
if(DateTime.Now.Month != mLastTimerDate.Month)
{
if (MonthChanged != null)
MonthChanged(this, new MonthChangedEventArgs(mLastTimerDate, DateTime.Now.Date));
}
mLastTimerDate = DateTime.Now.Date;
}
public void Dispose()
{
mTimer.Dispose();
}
}
public class MonthChangedEventArgs : EventArgs
{
public MonthChangedEventArgs(DateTime previousMonth, DateTime currentMonth)
{
CurrentMonth = currentMonth;
PreviousMonth = previousMonth;
}
public DateTime CurrentMonth
{
get;
private set;
}
public DateTime PreviousMonth
{
get;
private set;
}
}
client code
// Check the new month around 2 AM on 1st day
mMonthTimer = new MonthTimer(new TimeSpan(2, 0, 0));
mMonthTimer.MonthChanged += mMonthTimer_MonthChanged;
One thing I'm not using System.Threading.Timer, therefor the even handler will be called on a separate thread & not UI thread as incase of System.Windows.Forms.Timer if this is an issue in yr case do let me know.
Also do write me a comment if it serves yr purpose or any if any issues
Try Microsoft's Reactive Framework (NuGet "Rx-Main").
You can write this:
Observable
.Timer(DateTimeOffset.Now.AddDays(29.0))
.Subscribe(x =>
{
/* 29 Days Later */
});

C# Is there a way to make a list of time range? Configurable

Is there a way to make a list of time range?
For example:
A list containing:
12:00 to 1:00 pm
1:00 to 2:00 pm
etc...
Where the dividing section is configuration.
I think you have to use datetime and divide it to a certain number(in this case one hour)
Could someone please point me to the right direction or provide me an example?
Thanks in advance!
There's no built-in type that defines a time-range but it would be pretty easy to create one by combining a DateTime and a TimeSpan. For example:
struct TimeRange
{
private readonly DateTime start;
private readonly TimeSpan duration;
public TimeRange ( DateTime start, TimeSpan duration )
{
this.start = start;
this.duration = duration;
}
}
You could then build a List<TimeRange> using a specific DateTime as the starting point and adding the required TimeSpan for each element. For example, here's a very basic implementation of TimeRange including a method called Split which returns an IEnumerable<TimeRange> based on the current TimeRange and the required duration of the sub-ranges.
struct TimeRange
{
private readonly DateTime start;
private readonly TimeSpan duration;
public TimeRange ( DateTime start, TimeSpan duration )
{
this.start = start;
this.duration = duration;
}
public DateTime From { get { return start; } }
public DateTime To { get { return start + duration; } }
public TimeSpan Duration { get { return duration; } }
public IEnumerable<TimeRange> Split (TimeSpan subDuration)
{
for (DateTime subRangeStart = From; subRangeStart < this.To; subRangeStart += subDuration)
{
yield return new TimeRange(subRangeStart, subDuration);
}
}
public override string ToString()
{
return String.Format ("{0} -> {1}", From, To);
}
}
You can then do something like this:
TimeRange mainRange = new TimeRange(DateTime.Now, new TimeSpan(12, 0, 0));
List<TimeRange> rangeList = mainRange.Split(new TimeSpan(1, 0, 0)).ToList();
This will give a list of 12 time ranges of 1-hour duration starting from the current time.
** Update **
Note that the above implementation is VERY basic. The Split method, for example, will happily produce a lits of ranges where the end of the last sub-range is beyond the end of the parent range if the sub duration is not an integral division of the parent range. It would be hard to add checks for this kind of thing, though. The real question is what you want to happen in those kind of scenarios.
It would also be very easy to create a static TimeRange.CreateList method that builds a List<TimeRange> without the need for an explicit parent range.

Calculating how many minutes there are between two times

I have a datagridview in my application which holds start and finish times. I want to calculate the number of minutes between these two times. So far I have got:
var varFinish = tsTable.Rows[intCellRow]["Finish Time"];
TimeSpan varTime = (DateTime)varFinish - (DateTime)varValue;
int intMinutes = TimeSpan.FromMinutes(varTime);
But the last line won't compile because it says I am using invalid arguments for the Timespan constructor. I've researched quite a bit about how to calculate the number of minutes between two times, but I'm hitting a bit of a brick wall. Can someone please advise me on the best way to achieve my objective.
EDIT/
Now my code is as follows:
var varFinish = tsTable.Rows[intCellRow]["Finish Time"];
TimeSpan varTime = (DateTime)varFinish - (DateTime)varValue;
int intMinutes = (int)varTime.TotalMinutes;
But I am getting an invalid cast on the second line. Both varFinish and varValue are times e.g. 10:00 and 8:00 say. So not sure why they won't cast to type DateTime?
Try this
DateTime startTime = varValue
DateTime endTime = varTime
TimeSpan span = endTime.Subtract ( startTime );
Console.WriteLine( "Time Difference (minutes): " + span.TotalMinutes );
Edit:
If are you trying 'span.Minutes', this will return only the minutes of timespan [0~59], to return sum of all minutes from this interval, just use 'span.TotalMinutes'.
double minutes = varTime.TotalMinutes;
int minutesRounded = (int)Math.Round(varTime.TotalMinutes);
TimeSpan.TotalMinutes: The total number of minutes represented by this instance.
In your quesion code you are using TimeSpan.FromMinutes incorrectly. Please see the MSDN Documentation for TimeSpan.FromMinutes, which gives the following method signature:
public static TimeSpan FromMinutes(double value)
hence, the following code won't compile
var intMinutes = TimeSpan.FromMinutes(varTime); // won't compile
Instead, you can use the TimeSpan.TotalMinutes property to perform this arithmetic. For instance:
TimeSpan varTime = (DateTime)varFinish - (DateTime)varValue;
double fractionalMinutes = varTime.TotalMinutes;
int wholeMinutes = (int)fractionalMinutes;
You just need to query the TotalMinutes property like this varTime.TotalMinutes
If the difference between endTime and startTime is greater than or equal to 60 Minutes , the statement:endTime.Subtract(startTime).Minutes; will always return (minutesDifference % 60). Obviously which is not desired when we are only talking about minutes (not hours here).
Here are some of the ways if you want to get total number of minutes(in different typecasts):
// Default value that is returned is of type *double*
double double_minutes = endTime.Subtract(startTime).TotalMinutes;
int integer_minutes = (int)endTime.Subtract(startTime).TotalMinutes;
long long_minutes = (long)endTime.Subtract(startTime).TotalMinutes;
string string_minutes = (string)endTime.Subtract(startTime).TotalMinutes;

Categories

Resources