Finding Times within a Certain Span - c#

Here is my scenario: When I click an item from datagridview I want to check to see if the job such as fixing a computer is not going to happen at the same time as another event on my schedule. If one event is at 1pm on 6/14/20 I don't want to allow the user to be able to select another job that overlaps with that one. I actually want a minimum of an hour between events. Where do I start with this?
I have a datagridview that gets data from a Mysql table called jobs. It displays all of the details of the job (date, time, type of event, etc.). When I click on an item from the table it stores the data in string variables and opens another window to confirm the user's selection. For example JobType will get stored in a string called type, date will get stored in a string date, time will get stored in a string time. I want the check to occur before the confirmation window pops up.
This is a windows forms app in C#
Here is my code that i tried:
//convert date time that is selected from datagridview
DateTime dtselected = DateTime.Parse(date + " " + time);
//convert date time of each job
DateTime dtstored = DateTime.Parse(job.JobDateTime);
//subtract values
TimeSpan value1 = dtselected.Subtract(dtstored);
TimeSpan value2 = dtstored.Subtract(dtselected);
//set minimum difference between the two times to be 1 hour and 5 minutes
TimeSpan difference = new TimeSpan(0, 1, 5, 0, 0);
//check if two times overlap
if(value1 <= difference || value2 <= difference)
{
MessageBox.Show("Job times too close");
return;
}

Related

How to store a specific date-time through a console.readline statement?

I have tried to write multiple programs relating to date/time (including Random.Next()), but I have a persisting problem. Every time I use a Console.ReadLine() statement (or some other statement that would cause the code to pause and resume) it refreshes the variable set by date/time as well as every variable caused by or modified by date/time in any way. Is there a way to store the time the program is run instead of the current time? (I'm using an online editor, dotnetfiddle.net, if that changes anything)
Here's a few examples:
using System;
namespace timeTest
{
public class Program
{
public static void Main()
{
Random random = new Random();//Random is based off of time, which updates
Console.WriteLine("Randomizer: " + random.Next(1,11));//Prints random number
long time = DateTime.Now.Ticks;
Console.WriteLine("Time in ticks: "+time);//Prints time in ticks
int hr = DateTime.Now.Hour;
int min = DateTime.Now.Minute;
int sec = DateTime.Now.Second;
Console.WriteLine("Military time: "+hr+":"+min+":"+sec);//Prints time on the clock
int sec2 = sec*2;//Stores value in a different variable times two
Console.WriteLine("2x the current second: "+sec2);//Prints twice the second
while (true) {Console.ReadLine();}//Every time Console.ReadLine is called, it changes again
}
}
}
DateTime.Now is a calculated property. It returns the current time at the exact moment it is called. When you run under the debugger, it always gets the value it has at the moment the value is evaluated by the debugger. Your program won't notice that, but it is something you need to keep in mind.
However, you have a conceptual error here:
int hr = DateTime.Now.Hour;
int min = DateTime.Now.Minute;
int sec = DateTime.Now.Second;
Because DateTime.Now is evaluated each time it is called, this returns a new instance of the time with every call. This may result in the minute not matching the hour displayed (when the hour just changes between the first and the second line, your displayed time will be an hour off).
Evaluate the value once:
DateTime now = DateTime.Now;
int hr = now.Hour;
int min = now.Minute;
int sec = now.Second;
You'll note that in this case, the value of now won't change, even if you stop in the debugger.
Note that there are also standard-formatting options that don't require you to separate the values yourself, just do
Console.WriteLine($"{now:T}"); // standard (localized) time format

Time Trackbar or Very Large Values using Trackbar

I am writing a vehicle tracking application using GMAP.NET
I want to display simultaneous movement of multiple vehicle markers based on date/time.
For this, I would like to use a Trackbar such that when the User slides the trackbar, the corresponding movement of multiple markers is shown.
I did the same application on the browser and it worked very well. I converted the datetime attribute of the marker tag to a UNIX Timestamp and then set the trackbar (NOUI Slider) values to the the UNIX Timestamp range.
But, Now I find that I cannot do this at all with the Windows Desktop C# Trackbar.
The problems are:
The trackbar is unable to take very large timestamp values. Eg. 5 years data.
Even after reducing the very large numbers, the trackbar still cannot take such high values and keeps lagging or crashing.
Eg. I need to show data for 5 years. In this case, my trackbar would have minimum value - 1262304000 (i.e. 01-01-2010) to 1425203100 (i.e. 01-03-2015 | 09:45).
But such large values just don't work with my trackbar.
I have even taken reduction eg. setting 1262304000 as 1 and then 162899100 (i.e. 1425203100 - 1262304000) becomes my maximum value.
But even this is very intensive and the trackbar does not work.
Please suggest if there is a time-trackbar which can show very a large datetimerange or how I can use a regular trackbar with very large values.
The code as follows -
A. The function to convert datetime to a UNIX Timestamp
public Int32 UnixTimeStampUTC(DateTime datetime)
{
Int32 unixTimeStamp;
DateTime zuluTime = datetime.ToUniversalTime();
DateTime unixEpoch = new DateTime(1970, 1, 1).ToUniversalTime();
unixTimeStamp = (Int32)(zuluTime.Subtract(unixEpoch)).TotalSeconds;
return unixTimeStamp;
}
B. Taking the DateTime from a DataTable by Concatenating Date & Time Columns and Adding a third Column to hold the Unix TimeStamp of the DateTime Column
for (int z = 0; z < dt.Rows.Count; z++)
{
if (reviseddt.Rows[z]["date"].ToString() != "")
{
dte = DateTime.Parse(reviseddt.Rows[z]["date"].ToString());
dteticks = UnixTimeStampUTC(dte);
}
else
{
dteticks = 0;
}
if (reviseddt.Rows[z]["time"].ToString() != "")
{
tm = DateTime.Parse(reviseddt.Rows[z]["time"].ToString());
tmticks = UnixTimeStampUTC(tm);
}
else
{
tmticks = 0;
}
Int32 fullDt = dteticks + tmticks;
reviseddt.Rows[z]["datetime"] = fullDt.ToString();
reviseddt.Rows[z]["datetimestring"] = reviseddt.Rows[z]["date"].ToString() + reviseddt.Rows[z]["time"].ToString();
C. Initalising the Trackbar by Getting the min and max values from the datetime column in the datatable and then taking the range as the difference between the min and max values. Hence, Trackbar range = 1 to (Max-Min)
string strminlevel = toplotDataTable.Compute("min(datetime)", String.Empty).ToString();
string strmaxLevel = toplotDataTable.Compute("max(datetime)", String.Empty).ToString();
Int32 minLevel = Convert.ToInt32(strminlevel);
Int32 maxLevel = Convert.ToInt32(strmaxLevel);
Int32 rangeint = maxLevel - minLevel;
trackBarMoveTarget.Maximum = rangeint;
trackBarMoveTarget.Minimum = 1;
trackBarMoveTarget.LargeChange = 100;
trackBarMoveTarget.SmallChange = 1;
There are two problems with using TrackBar at this kind of scale.
First you need to be able to move the track bar left and right, but if the SmallChange and LargeChange properties are too small then you may be making a lot of progress, but it simply doesn't show on the track bar. Try this for a start:
trackBarMoveTarget.Maximum = rangeint;
trackBarMoveTarget.Minimum = 1;
trackBarMoveTarget.LargeChange = rangeint / 50;
trackBarMoveTarget.SmallChange = rangeint / 200;
The other problem is that the TrackBar control tries to render helpful little tick marks along its length. By default the TickFrequency is 1 and the Maximum value is 10. When You set Maximum to 162899100 without changing TickFrequency then the poor TrackBar control has to try and render 162899100 tick marks and that takes a long time. Try this:
trackBarMoveTarget.TickFrequency = rangeint / 100;
Be realistic - the trackbar only has a resolution down to 1 pixel - most displays are, at the most 1920 pixels wide. Therefore the resolution only has to be 1920 - let's round it up to 2000.
Now you have a 5 year period = 5 * 365 + 1 (for the odd leap year) = 1826. This is almost equal to our original 2000 odd. So let's make it 1826.
So set the Minimum value to 0 and the Maximum to 1826.
When you get the track event you take the first date 01-01-2010 and add the value of the trackbar to it:
var firstDate = new DateTime(2010, 1, 1).Date;
var trackDate = firstDate + TimeSpan.FromDays(trackBar.Value);
easy and quick.

How to handle daylight saving when using the Outlook C# GetFreeBusy() API?

I have an Outlook VSTO addin and I am doing search for resource calendar availability using the GetFreeBusy() API Calll which, given a date, will search over the next 28 days in 30 minute increments (by default) to determine which slots are free and which are busy. It works fine except I am struggling to figure out how to cope with the situation where a daylight savings time exists within that 28 day interval.
Here is my code:
using Microsoft.Office.Interop.Outlook;
string freeBusy = exchangeUser.GetFreeBusy(startDate, 30, true);
this gives me back a string like this that returns free / busy availability in 30 minute increments for 28 days.
0000000000000000202222222222000000000000000000000000000000000222222
this string is always 1344 characters long (48 slots per day * 28 days)
where each character represents a 30 minute slot and shows 0 if the time is free. I have the following parsing code that I took from this Microsoft article that returns an array of free time slots:
private IEnumerable<DateTime> ParseFreeBusy(string freeBusyString, DateTime startingDate)
{
var timeSlots = new List<DateTime>();
for (int i = 0; i < freeBusyString.Length; i++)
{
double slot = i * 30;
DateTime timeSlot = startingDate.Date.AddMinutes(slot);
bool isFree = freeBusy.Substring(i, 1) == "0";
if (isFree)
{
timeSlots.Add(timeSlot);
}
}
return timeSlots;
}
If I plug in October 25th as the start date when I look at the results every thing lines up perfectly up until November 2nd at 2AM (given daylight savings)
The root issue is that my naive code is simply increments and keeps adding 30 minutes for each entry since I am simply looping through each slot and doing this:
startingDate.Date.AddMinutes(slot);
I did a test and booked a calendar slot from 1AM - 2AM on November 2nd and this is what i get from GetFreeBusy() starting on that day
002222000...
so using the default loop above (remember, every character is a 30 min slot and 0 = free), this would translate to the following slot logic:
12L00 AM - free (0)
12:30 AM - free (0)
1L00 AM - booked (2)
1:30 AM - booked (2)
THESE NEXT TWO "booked" below is really representing the 2nd 1AM - 2AM since we roll the clocks back an hour
2:00 AM - booked (2)
2:30 AM - booked (2)
3:00 AM - free (0)
which is wrong as my code would show 2AM - 3AM booked when the "real" 2-3A AM is free. If my parsing was correct and handled this rollback, I would end up with this correct answer of:
12L00 AM - free (0)
12:30 AM - free (0)
1L00 AM - booked (2)
1:30 AM - booked (2)
IGNORE the second 1AM to 2AM as its already taken care of
2:00 AM - free (0)
2:30 AM - free (0)
3:00 AM - free (0)
What is interesting is that regardless of daylight savings, the resulting string is always 1344 characters long (I would have expected it to be shorter or longer on those months with daylight savings implications).
Does anyone have any experience with using outlook GetFreeBusy() and understand for how to deal with this situation when you hit a daily savings time slot?
I have been playing around with a few ideas like:
var tzInfo = TimeZoneInfo.Local;
if (tzInfo.IsAmbiguousTime(timeSlot))
{
//this would be a time to do something
}
or something like
DaylightTime daylightTime = tz.GetDaylightChanges(minStartTime.Year);
if (daylightTime.End == proposedTimeSlot)
{
daylightSavingsOffset = daylightSavingsOffset + ((daylightTime.Delta.Hours * 60) / meetingDuration);
}
but I am not completely sure what do with this once i detect the "special slots" and I can't find any documentation or recommendations around this situation.
Any suggestions?
What is interesting is that regardless of daylight savings, the resulting string is always 1344 characters long (I would have expected it to be shorter or longer on those months with daylight savings implications).
It's completely logical, Let's start with GetFreeBusy, It happens because the result is based on duration and specific intervals not Date and time stamps, and as we know Date and Time is relative to our location based on time zone but elapsed time and duration is not,let's assume we have meeting in 10 hours from now, we maybe are in different time Zones but after 10 hours (relative to our location) we both should meet each other, but our local times may vary significantly, the system works this way because it should be able to operate across different time zones at the same time, so it uses UniversalTime at the heart and converts it back to local time for generating the result.
Now let's check the code, when we use startingDate.Date.AddMinutes(slot); we are not considering DateTimeSaving as we are operating on our local time and the addition is relative to it, by using UniversalTime we can create a unified base point for our time additions and intervals, after that by converting it back to local time we can apply date time saving to it,
so I believe this code should work as expected:
private static IEnumerable<DateTime> ParseFreeBusy(string freeBusyString, DateTime startingDate)
{
var timeSlots = new HashSet<DateTime>();
var utc = startingDate.ToUniversalTime();
var timeZone = TimeZone.CurrentTimeZone; //can change to particular time zone, currently set to local timezone of the system
for (int i = 0; i < freeBusyString.Length; i++)
{
double slot = i * 30;
DateTime timeSlot = utc.AddMinutes(slot);
bool isFree = freeBusyString.Substring(i, 1) == "0";
if (isFree)
{
var localTimeSlot = timeZone.ToLocalTime(timeSlot);
timeSlots.Add(localTimeSlot);
}
}
return timeSlots;
}
NOTE:: beside using UTC for time, I changed the List to HashSet because if you have free slot on those specific times you would get duplicate entries, by using HashSet this problem won't occur.
here is a method I used for testing it:
private static void TestFreeSlots()
{
var saving = TimeZone.CurrentTimeZone.GetDaylightChanges(DateTime.Now.Year);
var datetime = new DateTime(saving.End.Year, saving.End.Month, saving.End.Day - 1);
//you may need to change the string to see effective result
var result = ParseFreeBusy("0000000000000000000000000000000000000000000000002222000", datetime);
}
and finally here is a little sample to demonstrate the method used here
private static void TestTimeZone()
{
var saving = TimeZone.CurrentTimeZone.GetDaylightChanges(DateTime.Now.Year);
var datetime = new DateTime(saving.End.Year, saving.End.Month, saving.End.Day - 1);
var utc = datetime.ToUniversalTime();
var timeZone = TimeZone.CurrentTimeZone;
for (var i = 0; i < 120; i++)
{
var next = timeZone.ToLocalTime(utc);
Console.WriteLine(next);
utc = utc.AddMinutes(30);
}
}
and your results should be similar to this:
This first function finds time slots that Outlook will return as duplicates due to DST. It can probably stand some refactoring but it's effective for now: (EDIT: I modified the function so it doesn't remove time slots as you go into DST).
public static Collection<DateTime> GetDuplicateSlots(
TimeZoneInfo timeZone, DateTime start, int intervalLength, int numOfIntervals)
{
Collection<DateTime> duplicates = new Collection<DateTime>();
bool dstAtStart = timeZone.IsDaylightSavingTime(start);
for (int interval = 0; interval < numOfIntervals; interval++)
{
DateTime current = start.Date.AddMinutes(interval * intervalLength);
if (dstAtStart && !timeZone.IsDaylightSavingTime(current))
{
duplicates.Add(current);
duplicates.Add(current.AddMinutes(intervalLength));
return duplicates;
}
}
return duplicates; // no duplicates
}
Then we just need to adjust for the duplicates when we go through the string of free/busy time slots:
public static void DisplayFreeBusy(
string freeBusyString, DateTime start, int intervalLength)
{
TimeZoneInfo cst = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
Collection<DateTime> duplicateSlots =
GetDuplicateSlots(cst, start, intervalLength, freeBusyString.Length);
int duplicatesConsumed = 0;
for (int slot = 0; slot < freeBusyString.Length; slot++)
{
int actualSlot = slot - duplicatesConsumed;
DateTime slotTime = start.Date.AddMinutes(actualSlot * intervalLength);
if (duplicatesConsumed != duplicateSlots.Count &&
duplicateSlots.Contains(slotTime))
{
duplicatesConsumed++;
}
else
{
Console.WriteLine("{0} -- {1}", slotTime, freeBusyString[slot]);
}
}
}
Note that the actualSlot variable corresponds to the time slots, while the slot variable still corresponds with a character in the free/busy string. When a duplicate is found, it is "consumed" and that character in the string is skipped. Once the duplicates have been consumed, the function will continue normally from that point.
I live in Arizona and we don't have DST so I had to force a different time zone. You can obviously substitute your local time zone instead of CST.
I tested this with a shorter input string but I added the extra '2' characters for the daylight savings slots. It handled the excess slots and prints out the proper number of slots.

How to populate dropdownlist with hours in c#

I have to 2 dropdownlists and I want to populate them with their values as hours from 1 to 24. Then I want to calculate the difference between the two selected values. If i choose in the first dropdown let's say the hour value as 12 AND second value as 16 then the difference will be 4.
How can I achieve that in c#?
I'm developing a asp.net web application coded in C#.
Below is the code of how I populate my dropdownlist:
DateTime Date = DateTime.Today;
DateTime Time = DateTime.Now;
ListItem item1 = new ListItem(Time.ToShortTimeString(),
Time.ToShortTimeString());
for (int i = 0; i <= 48; i++)
{
ListItem item2 = new ListItem(Date.ToShortTimeString(),
Date.ToShortTimeString());
droplist.Items.Add(item2);
if (Date.CompareTo(Time) < 0 && Date.AddMinutes(30).CompareTo(Time) > 0)
droplist.Items.Add(item1);
Date = Date.AddMinutes(60);
}
How can I assign values to my dropdown, values being the hours?
Instead of adding the date/time string to the drop down, you may add the DateTime value itself. The dropdown displays its members by calling ToString(). The disadvantage here: you are not able to use another time format like ToShortTimeString().
Another way is to use the DateTime.TryParse method to convert back before calculating.

C#: Edge Cases when removing a subset of a day from a TimeSpan on a daily basis

I'm trying to determine a TimeSpan (eg., time between logout and login), but with the caveat that a part of each day shouldn't be counted (eg., don't count the time between 7PM and 7AM on a given day). Removing that time from full days is easy enough, but what's the best way to check for parts of a day (eg., user logs out at 6PM and logs back in at 8PM the same day) and other edge cases?
ETA: I've currently got a set-up that looks like this:
public class HasProductiveHours
{
//These fields should represent an hour of the day (in 24-hour format)
// in which production should start or stop.
public int startProductiveHour;
public int endProductiveHour;
//Resource type enum
public ResourceType resourceType
//How often this resource is collected, in seconds
public float harvestTime
public HasProductiveHours (int startProductiveHour, int endProductiveHour, ResourceType resourceType, int harvestTime)
{
// Other creation-time things
this.startProductiveHour = startProductiveHour;
this.endProductiveHour = endProductiveHour;
this.resourceType = resourceType;
this.harvestTime = harvestTime;
TimeSimulator.Instance.SimulateElapsedTime (this);
}
}
Instances of this class and its children are simulating production of some resource every hour or so (depending on the resource). There's a system in place to simulate elapsed time since the user's last logout.
DateTime lastLogoutTime;
public void Logout ()
{
// Other logout things
lastLogoutTime = DateTime.Now;
}
public void SimulateElapsedTime (HasProductiveHours toSimulate)
{
DateTime currentTime = DateTime.Now;
TimeSpan timeSinceLogout = currentTime - lastLogout;
int unproductiveHours = Math.Abs (toSimulate.startProductionHour - toSimulate.endProductionHour) * timeSinceLogout.Days;
timeSinceLogout.Subtract (new TimeSpan (unproductiveHours, 0, 0);
double secondsElapsed = timeSinceLogout.TotalSeconds;
int harvestsElapsed = (int)(secondsElapsed / toSimulate.harvestTime);
PlayerData.Instance.AddResource (toSimulate.resourceType, harvestsElapsed);
}
A few steps should easily fix this:
Check the logout time - see if it is between 7am and 7pm (The hour method gives you the number of hours since midnight. You want to see if it is >=7 and <=19). If it is not, move the time forward to 7am. Should be easy enough to do - If the time is <7am, change the time to 7am. If the Time is greater than 7pm, change the time to 7am the next day
Check the login time - see if it is between 7am and 7pm. If it is not, move the time back to 7pm. Should be easy enough to do - If the hour >7pm, set it to 7pm, if the hour is <7am, change the time to 7pm on the previous day
Check to make sure logout time < login time. If not, the entire time logged out was during the none counting hours, so your time span should be set to 0. If your logout time < login, just subtract them to get your timespan
This for the situation described in the comment:
If the day numbers are the now the same these are the steps I would take:
Create a new datetime with the logout outs date, and a time of 1900. Subtract the new date from the logout date - this will give you the timespan until the end of day
Move the logout date to 7am the next day. If the logout and the login are now on the same day, just subtract the logout from the login, this is the timespan for that day. If they are not the same date, repeat step 1
Add the timespans generated in step 1 and step 2 together. This gives your total time
For example: I logout Jan 1, at 16:00. I login Jan 2, at 10:00
The time I was logout on Jan 1 during tracking hours is 3hours (Jan 1, 19:00 - Jan 1, 16:00)
I move the logout time to Jan 2, at 07:00.
The day of logout is 2, and the day of login is 2, now I can subtract them
The time I was logout in Jan 2 during tracking hours is 3hours (Jan 2, 10:00 - Jan 2, 07:00)
I now add them together and get 6hours of logout time during tracking hours.
#thorkia has some good suggestions.
Alternatively you can calculate the time difference and then sum up (from another table) the total non-production/non-time that was covered and subtract that from the total.
So
8am-6am = 2 hours total
6am to 7am = 1 hour non-time
result = 1 hour total real-time

Categories