Parse out monthly items from a collection of DateTime objects - c#

I previously asked this question to take a oollection of datetime objects and group them by dayOfweek and time
So just to recap: I take a collection of DateTime
List<DateTime> collectionOfDateTime = GetDateColletion();
and then grouping by dayofWeek and time of day by doing this
var byDayOfWeek = collectionOfDateTime.GroupBy(dt => dt.DayOfWeek + "-" + dt.Hour + "-" + dt.Minute);
So at this point, I have these grouped by week (consistent time) working perfectly.
I now have a new requirement to group by Month instead of by week. When i say "month", its not the same day of the month but something like "the first tuesday of each Month"
I am trying to figure out what "key" to use in a group by to group all items that fit that monthly logic (the first tuesday of the month, the second friday of each month, etc)
As an example lets say i had these dates to start out with;
var date1 = new DateTime(2013, 1, 4).AddHours(8); // This is the first Friday in Jan
var date2 = new DateTime(2013, 2, 1).AddHours(8); // This is the first Friday in Feb
var date3 = new DateTime(2013, 1, 5).AddHours(3); // This is the first Sat in Jan
var date4 = new DateTime(2013, 2, 2).AddHours(3); // This is the first Sat in Feb
var date5 = new DateTime(2013, 2, 2).AddHours(6);  // This is the first Sat in Feb - different time
If these were the dates that went into the original array, i need a groupby to end up with 3 groups.
The first group would have date1 & date2 in it
The second group would have date3 and date4 in it.
date5 would be on its own as it doesn't match any of the other groups given the different time
Can anyone suggest anyway to group by that criteria?

I think it's easier than it looks:
var byDayOfMonth = from d in dates
let h = (d.Day / 7) + 1
group d by new { d.DayOfWeek, h } into g
select g;
Local variable h = (d.Day / 7) + 1 sets which DayOfWeek within that month it actually is.
I run it for test and received 2 groups, exactly the same as in your example. Keys for that groups are:
{ DayOfWeek = Friday, h = 1 }
{ DayOfWeek = Saturday, h = 1 }
What means, there are groups for 'First Friday of month' and 'First Saturday of month'.
You can easily extend grouping key by d.Hour and/or d.Minute if you like:
var byDayOfMonth = from d in dates
let h = (d.Day / 7) + 1
group d by new { d.DayOfWeek, h, d.Hour, d.Minute } into g
select g;
Results (keys only):
{ DayOfWeek = Friday, h = 1, Hour = 8, Minute = 0 }
{ DayOfWeek = Saturday, h = 1, Hour = 3, Minute = 0 }
{ DayOfWeek = Saturday, h = 1, Hour = 6, Minute = 0 }

There is probably an easier way to do this but this is what's come to me:
I gather from your question that you need to group everything from "the first Tuesday of February until the first Monday of March" etc. such that you get these "month" spans that are a variable number of days - depending on the month in which they start. If so then you really need to break this down into ranges using the day of the year so:
Group by the First Wednesday of the Month 2013
Group 0 (0-1)
All DayOfYear between 0 and 1 2013
Group 1 (2-36)
The first Wednesday of the month: January is DayOfYear 2.
The first Wednesday of the month: February is DayOfYear 37.
etc.
So the first range is a function f such that f(32) = 1 (DayOfYear is 32) because it falls in the range 2 to 37. This f is an indexed collection of ranges, finding the item in the collection that a given DayOfYear falls into, and returning that item's index as the group number.
You can dynamically build this table by getting your min and max dates from GetDateCollection to determine the overall range. Because the logic surrounding dates is a pretty complex topic in of itself I'd fall back on a library like NodaTime (specifically the arithmetic documentation), start with the min date, advance day by day until I found the first qualifying day (i.e., "first Monday of the month") and create a range 0 to that day - 1 as group 0 and push that onto an indexed collection (ArrayList likely). Then loop from that date using LocalDate.PlusWeeks(1) until the month changes, constructing a new range and pushing that range onto the same indexed collection.
Each time you cross into a new year you'll have to add 365 (or 366 if the previous year is a leap year) to your DayOfYear as you build your indexed collection since DayOfYear resets each year.
Now you've got a collection of ranges that acts as a table that groups days into the desired units based on their DayOfYear.
Write a function that traverses the table comparing the DayOfYear (+ [365|366] * x where x is the # of years the date you are comparing is from your min year) of a given date against the items in the collection until you locate the range that day falls within, and return that index of that item as the group number. (Alternatively each range could be a Func<DateTime,bool> that returns true if the provided DateTime falls in that range.)
An alternative data structure to the collection of ranges would be an array of ushort with length equal to all the days from min to max dates in your date range, and the value for each day their assigned group number (calculated with ranges, as above). This will perform faster for grouping, though the performance may not be noticeable if you're working with a smaller dataset (only a few hundred dates).

To group by using Linq maybe this code will help you:
List<DateTime> collectionOfDateTime = GetDateColletion();
collectionOfDateTime.GroupBy(s => Convert.ToInt16(s.DayOfWeek) & s.Hour & s.Minute);

Related

converting data string to time using Linq

How can I convert the following into times, knowing that the values are the number of minutes.
350-659, 1640-2119, 2880-3479;
The output id like is
M 5:50am - 10:59am
T 3:20am - 10:59am
W 12:00am - 9:59am
etc....
Ranges -
Mon= 0-1439
Tue = 1440-2879
Wed = 2880 - 4319
Thurs = 4321 - 5759
Fri = 5760 - 7199
Sat = 7200 - 8639
Sun = 8640 - 10079
What I have so far is
var days = new[] { 24, 48, 72, 96, 120, 144, 168 };
var numbers = Enumerable.Range(1,7);
var hours = days.ToDictionary(x => (double)x/24, i => (int)I*60);
which outputs
Key Value
1 1440
2 2880
3 4320
4 5760
5 7200
6 8640
7 10080
I kinda don't get the question at all, but taking everything you've said at face value:
var times = "350-659, 1640-2119, 2880-3479;"
.Split(',') //split to string pairs like "350-659"
.Select(s => s.Split('-').Select(x => int.Parse(x)).ToArray()) //split stringpairs to two strings like "350" and "659", then parse to ints and store as an array
.Select(sa => new { //turn array ints into dates
F = new DateTime(0).AddMinutes(sa[0]), //date 0 i.e. jan 1 0001 was a monday. add minutes to it to get a time and day
T = new DateTime(0).AddMinutes(sa[1] + 1) //add 1 to the end minute otherwise 659 is 10:59pm and you want 11:00am
}
)
.Select(t =>
$"{($"{t.F:ddd}"[0])} {t.F:hh':'mmtt} - {t.T:hh':'mmtt}" //format the date to a day name and pull the first character, plus format the dates to hh:mmtt format (eg 09:00AM)
);
Console.Write(string.Join("\r\n", times));
If you actually want to work with these things in a sensible way I recommend you stop sooner than the final Select, which stringifies them, and work with the anonymous type t that contains a pair of datetimes
The only thing about this output that doesn't match the spec, is that the AM/PM are uppercase. If that bothers you, consider:
$"{t.F:ddd}"[0] + ($" {t.F:hh':'mmtt} - {t.T:hh':'mmtt}").ToLower()
You could get the current monday of the week and add the minutes. I don't think you can do this in Linq directly.
//your timestamp
int minutes = 2345;
//get the day of week (sunday = 0)
int weekday = (int)DateTime.Now.DayOfWeek - 1;
if (DateTime.Now.DayOfWeek == DayOfWeek.Sunday)
weekday = 6;
//get the first day of this week
DateTime firstDayOfWeek = DateTime.Now.AddDays(-1 * weekday);
//add the number of minutes
DateTime date = firstDayOfWeek.Date.AddMinutes(minutes);
An interval of time (as opposed to an absolute point in time) is expressed as a TimeSpan. In this case, you'd have one TimeSpan that represents the offset (from the beginning of the week) until the starting time, then another TimeSpan that represents the offset to the end time.
Here's how to convert your string into a series of TimeSpans.
var input = #"540-1019;1980-2459;3420-3899;4860-5339;6300-6779";
var times = input
.Split(';')
.Select(item => item.Split('-'))
.Select(pair => new
{
StartTime = new TimeSpan(hours: 0, minutes: int.Parse(pair[0]), seconds: 0),
EndTime = new TimeSpan(hours: 0, minutes: int.Parse(pair[1]), seconds: 0)
})
.ToList();
foreach (var time in times)
{
Console.WriteLine
(
#"Day: {0} Start: {1:h\:mm} End: {2:h\:mm}",
time.StartTime.Days,
time.StartTime,
time.EndTime
);
}
Output:
Day: 0 Start: 9:00 End: 16:59
Day: 1 Start: 9:00 End: 16:59
Day: 2 Start: 9:00 End: 16:59
Day: 3 Start: 9:00 End: 16:59
Day: 4 Start: 9:00 End: 16:59
You can of course choose to format the TimeSpan in any way you want using the appropriate format string.

How to determine if now(utc) is within the range of the given days of the week and times of the day in ISO 8601 format

I run into this question of how to determine if DateTime.UtcNow (e.g. 2018-01-01T20:00:00Z) falls within the given range of days and times that are in another timezone. There are no specific dates given, just the days of the week, and the time of the day. The given time is in ISO 8601 standard format.
To simplify this question, it can be how to check if a UTC time is within business hours in China.
For example, the given day and time range is given by someone form China in time zone +08:00, it can be: FromDayOfWeek = "Friday", FromTimeOfDay = "17:00:00+8:00", ToDayOfWeek = "Monday", ToTimeOfWeek = "08:00:00+8:00". I need to determine if "now" in China is sometime between the given range (Friday 17:00:00+8:00 - Monday 08:00:00+8:00).
I'm stuck at how to convert the DateTime and get the day of the week in that local time, since 2018-01-01T20:00:00Z is Monday in UK, but at the same time, since China is +08:00, it is already Tuesday in China.
My approach:
// parse the time to get the zone first (+08:00)
TimeSpan ts = TimeSpan.Parse("-08:00");
// Create a custom time zone since the time zone id is not given, and cannot be searched by SearchTimeZoneById
TimeZoneInfo tzi = TimeZoneInfo.CreateCustomTimeZone(zoneId, ts, displayName, standardName);
DateTime localDateTime = TimeZoneInfo.ConvertTime(Date.UtcNow, tzi);
String localDay = localDateTime.DayOfWeek;
// Determine if localDay is between FromDayOfWeek and ToDayOfWeek
// cast the days to integers from 1 (Monday) to 7 (Sunday)
// create an array of days in integar days = [5, 6, 7, 1]
// if days.contains(localDays), check the times
...
Can anyone suggest some better solutions? I am not sure if mine works, and there are holes in how to deal with Day Light Saving time, since the zone will change, and how to check the time range. I am new to C#, any suggestions of libraries I can use is great!
Instead of converting both start and end times from UTC, just convert the other time into UTC
TimeZoneInfo chinaTimeZone = TimeZoneInfo.CreateCustomTimeZone(zoneID, TimeSpan.Parse("-08:00"), displayName, standardName);
DateTime FromTime = new DateTime(2018, 0, 19, 13, 0, 0); // year, month, day, hour, minute, second : Friday 1pm
DateTime ToTime = new DateTime(2018, 0, 21, 1, 0, 0); // year, month, day, hour, minute, second : Monday 1am
DateTime nowinUTC = DateTime.UtcNow;
DateTime nowInChina = TimeZoneInfo.ConvertTimeFromUtc(nowinUTC, chinaTimeZone);
if(FromTime< nowInChina && ToTime> nowInChina)
{
// Time is within the from and two times
}
Per the comments on #Moffen's answer, you only want to check if Now is within a specific DayOfWeek range:
public void CheckAll(List<SomeClass> spans)
{
var chinaTZ = TimeZoneInfo.CreateCustomTimeZone(zoneID, TimeSpan.Parse("-08:00"), displayName, standardName);
var nowInChina = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, chinaTZ);
foreach ( var span in spans )
{
if (InRange(nowInChina, span.startDay, span.endDay))
// Do something on success
// Check for valid times here
;
else
// Do something on Failure
;
}
}
public bool InRange(DateTime dateToCheck, DayOfWeek startDay, DayOfWeek endDay)
{
// Initialise as one day prior because first action in loop is to increment current
var current = (int)startDay - 1;
do
{
// Move to next day, wrap back to Sunday if went past Saturday
current = (current + 1) % 7;
if (dateToCheck.DayOfWeek == (DayOfWeek)current)
return true;
} while (current != (int)endDay);
return false;
}

7-digit Julian Date to Normal Calendar Date

How do I convert a 7-digit Julian Date (2016092) to a regular Calendar date (MM-DD-YYYY)?
I was thinking of taking the last three digits and converting it to a regular date then appending the first four digits as the year but I'd have to consider leap years.
Expected output: 04-01-2016
My current (SQL) code which solves the problem is
DECLARE #dt char(7)
SET #dt = 2016092
SELECT DATEADD(dd, CAST(RIGHT(#dt, 3) AS int) - 1, CAST(LEFT(#dt, 4) AS datetime))
How can I implement it on C#?
You don't have any kind of Julian Day (Date) format which is
https://en.wikipedia.org/wiki/Julian_day
But a kind of custom format which can be reconstructed from the sql provided:
year * 1000 + days from 1st Jan + 1
So 2016092 means year 2016 and 092 - 1 = 91st day from the 1st of Jan (1st of Apr)
Implementation:
int source = 2016092;
DateTime result = new DateTime(source / 1000, 1, 1).AddDays(source % 1000 - 1);
Console.WriteLine($"{result:MM-dd-yyyy}");
Outcome:
04-01-2016

Find next available day in an Array of string

I've been trying to figure out how to take next available day based on Present day i.e., if today is Friday, then search in Array for the next nearest day like if Array values are 1[Monday], 2[Tuesday], 4[Thursday], 6[Saturday] then my next day should be Saturday.
Here is what i tried
//Here i'll get days like 0, 2, 3, 4, 6 pattern, and i'm spliting them based on comma to get single-single day value in array of string
string[] GetDays = DayInWeek.Split(','); [Here day patterns will change everytime, based on user selection]
//Here i'm looping to take each day and get Enum Text based on Enum Value
foreach (string FirstDay in GetDays)
{
//Here i'm converting the string value into int and passing to DayOfWeek Enum to get respective day
DayOfWeek DayChoosen = ((DayOfWeek)(Convert.ToInt32(FirstDay)));
//Here i have my actual day for example Friday
DayOfWeek StartDay = "Friday";
//Here i need condition to find next available day in the foreach i.e., after Friday next value should be Saturday, or Sunday, Monday & so on until Friday==Friday
if (StartDay == DayChoosen)
{
//End foreach
}
}
As i told based on present Day i should find next available day i.e, if Friday i should search for Saturday, if Saturday is not there then Sunday, Monday and so on till Friday=Friday
You don't need all these manipulations with foreach.
You can do the following:
private int nextDay(string dayInWeek, int startDay)
{
int[] getDays = dayInWeek.Split(',').Select(int.Parse).ToArray();
return getDays.Where(d => d > startDay).Any()
? getDays.Where(d => d > startDay).Min()
: getDays.Min();
}
This algorithm checks if there are any days of a week, which are presented in your array, and come after the startDay. Else, it outputs the first available day in a week.
For example, for a string "0, 2, 3, 4, 6":
for startDay 0 - output 2, as it is the minimal integer which is more than 0
for startDay 1 - outputs 2
for startDay 3 - output 4
for startDay 6 it finds no items, which are more than 6, and outputs minimum 0
For string "5" (Friday only):
for startDay 5, 6 - finds no items which are more than 5, output minimum (5)
for startDay 0-4 - outputs 5, as the minimum number which is greater than 0-4
Try this;
//Here i'll get days like 0, 2, 3, 4, 6 pattern, and i'm splitting them based on comma to get single-single day value in array of string
string DayInWeek = "0, 2, 3, 4, 6";
string[] GetDays = DayInWeek.Split(','); //[Here day patterns will change everytime, based on user selection]
DayOfWeek nextAvailableDay;
//Here i'm looping to take each day and get Enum Text based on Enum Value
foreach (string FirstDay in GetDays)
{
//Here i'm converting the string value into int and passing to DayOfWeek Enum to get respective day
DayOfWeek DayChoosen = ((DayOfWeek)(Convert.ToInt32(FirstDay)));
//Here i have my actual day for example Friday
DayOfWeek StartDay = DayOfWeek.Friday;
//Here i need condition to find next available day in the foreach i.e., after Friday next value should be Saturday, or Sunday, Monday & so on until Friday==Friday
if (StartDay.Equals(DayChoosen))
break;
if (StartDay < DayChoosen)
{
nextAvailableDay = DayChoosen;
break;
}
continue;
}
You should play on int list. I will provide you the pseudo code.
Algorithm:
sort the available list
get all greater numbers (greater than your current one) from the list
select the minimum now, and break
if you don't have any greater number, select all the minimum numbers (less than your current one)
select maximum from this list now, and break
if you don't have any greater or less numbers, than select the same number and break
now convert the number into week day
It is the possible algorithm I came up to solve this problem. You can convert it into code. May be its not the best one but I am sure it will work fine.
check this:
Console.WriteLine("Kindly provide input");
string DayInWeek = Console.ReadLine();
string[] GetDays = DayInWeek.Split(',');
Array.Sort(GetDays);
DateTime dt = DateTime.Today;
int i = (int)dt.DayOfWeek;
Console.WriteLine("Today is " + (DayOfWeek)i);
bool flag = false;
foreach (string FirstDay in GetDays)
{
if (Convert.ToInt32(FirstDay) > i)
{
Console.WriteLine((DayOfWeek)Convert.ToInt32(FirstDay) + " is the next day");
flag = true;
break;
}
}
if (!flag)
{
Console.WriteLine((DayOfWeek)Convert.ToInt32(GetDays[0]) + " is the next day");
}
Console.ReadKey();

Get datetime from given day

user just enter the day of week. For instance user enter friday. I need to find the exact date of given day and format will be like dd.MM.yyyy.
But I don't know how I do it.
Example:
label1 - Friday (entered by user)
label2 - 08.06.2012 (found by system)
label1 is just a string (just Friday). It's coming from webservice as a string variable. I need to find the date and compare with today, If it's not equal or small than today I give date of upcoming Friday, else I give the date of the Friday the week after.
"If it's not equal or small than today I give exact date, else I give next week date. "
Assuming that means that you return always the next date in future with the given day of week, the only exception is when today is the given day of week.
public static DateTime getNextWeekDaysDate(String englWeekDate)
{
var desired = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), englWeekDate);
var current = DateTime.Today.DayOfWeek;
int c = (int)current;
int d = (int)desired;
int n = (7 - c + d);
return DateTime.Today.AddDays((n >= 7) ? n % 7 : n);
}
Let's test:
DateTime Monday = getNextWeekDaysDate("Monday"); // 2012-06-11
DateTime Tuesday = getNextWeekDaysDate("Tuesday"); // 2012-06-05 <-- !!! today
DateTime Wednesday= getNextWeekDaysDate("Wednesday"); // 2012-06-06
DateTime Thursday = getNextWeekDaysDate("Thursday"); // 2012-06-07
DateTime Friday = getNextWeekDaysDate("Friday"); // 2012-06-08
Create enum of days (i.e. monday - 0, tuesday - 1, etc);
Get DateTime.Now DayOfWeek and cast in some way it to your enum value.
Calculate difference between Now.DayOfWeek and user's day of the week;
Use DateTime.AddDays(difference).DayofTheWeek;
get current time with DateTime.now
Current day is DateTime.Now.DayOfWeek
Then get the day of week your user entered
Then your result is DateTime.now.AddDays( NowDayOfWeek - UserDayOfWeek).

Categories