converting data string to time using Linq - c#

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.

Related

Humanizer for DateTime

I have this code:
Configurator.DateTimeHumanizeStrategy = new PrecisionDateTimeHumanizeStrategy(.75);
var dateTime1 = DateTime.UtcNow.AddYears(2).AddMonths(-5);
var text1 = dateTime1.Humanize();
In the text1 variable I get "one year from now". But this is not very accurate. Is there any way to get "one year and seven months from now"?
Update 1:
Solution #Daniel Hoffman has some problems, for example if my date is in the past:
//UtcNow is 11.07.2021
var dateTime6 = new DateTime(2021, 4, 24);
TimeSpan dateTimeSpan6 = dateTime6 - DateTime.UtcNow;
var text6 = dateTime6.Humanize();
string textSpan6 = dateTimeSpan6.Humanize(maxUnit: TimeUnit.Year, precision: 2);
then I get "2 months, 11 weeks" which contains basically the same information twice but in different units.
Update 2:
I have fixed the problem with dates in the past, by using Duration() method:
var timeSpan = date - DateTime.UtcNow;
return timeSpan.Duration().Humanize(maxUnit: TimeUnit.Year, precision: 2, minUnit: TimeUnit.Day);
[Edit]: Using TimeSpan will allow you to specify the precision of your period, but you will lose the ability to have "yesterday" or
"tomorrow", and it omits the " ago" or " from now", all of which are
localized.
A partial workaround would be to use the TimeSpan.Humanize
method for TimeSpans less than 366 days and DateTime.Humanize
otherwise. And if it's only going to be used in one language, the user
can append the appropriate text depending on if the timespan is
negative.
You can use the precision parameter with a TimeSpan:
TimeSpan periodFromNow = DateTime.UtcNow.AddYears(2).AddMonths(-5) - DateTime.UtcNow;
Then:
string myPeriodFromNow = periodFromNow.Humanize(maxUnit: TimeUnit.Year, precision: 2);
Other examples:
TimeSpan.FromDays(486).Humanize(maxUnit: TimeUnit.Year, precision: 7) => "1 year, 3 months, 29 days" // One day further is 1 year, 4 month
TimeSpan.FromDays(517).Humanize(maxUnit: TimeUnit.Year, precision: 7) => "1 year, 4 months, 30 days" // This month has 30 days and one day further is 1 year, 5 months
See also: https://github.com/Humanizr/Humanizer#humanize-timespan
It seems like its not currently possible in Humanizer to do what you want.
Check out this method PrecisionHumanize() on line 102, if the amount of days exceeds 365 then only years will be returned. And in general it seems like only one type of length of time can be returned, there is no years and months or minutes and seconds, just the largest one.
But check out another library called NodaTime it might be able to do what you want.
Here is a link to a different question similar to yours.

How to extract maximum date from a list of random dates, grouped by week, month, quarter or year?

Given a list of random dates, is there an elegant C# way (LINQ or otherwise) to extract the last date in the list, grouped by week, month, quarter or year?
I hope that's clear but if not, using this example set of dates:
Mon 01/01/2018
Tue 02/01/2018
Thu 04/01/2018
Tue 09/01/2018
Thu 11/01/2018
Fri 12/01/2018
Mon 22/01/2018
Tue 23/01/2018
Wed 24/01/2018
Wed 31/01/2018
Thu 01/02/2018
Tue 27/02/2018
Extracting maximum by week would yield
Thu 04/01/2018 = the last date in the first week in the sample
Fri 12/01/2018 = the last date in the second week in the sample
Wed 24/01/2018 = etc.
Thu 01/02/2018
Tue 27/02/2018
And extracting maximum by month would yield
Wed 31/01/2018 = the last date in January in the sample
Tue 27/02/2018 = the last date in February in the sample
I need to be able to do this extraction by week number, calendar month, calendar quarter and calendar year.
I'm not even sure how to begin this. So at present I have no code to share.
Any ideas will be very welcome.
CLARIFICATION: the weekday names are included here for the benefit of us humans. My actual date is proper DateTime types. So the code doesn't need to parse Strings to DateTimes.
Additionally, I can easily write something like this without LINQ. But what I am hoping for is an elegant solution using LINQ or some other clever trick.
This is pretty straightforward using LINQ. Here the example for grouping by month:
var grouped = dates.OrderBy(x => x.Ticks).GroupBy(x => x.Month)
.Select(x => new {Month = x.Key, Max = x.Max()});
This gives for your example:
{ Month = 1, Max = 1/31/2018 12:00:00 AM }
{ Month = 2, Max = 2/27/2018 12:00:00 AM }
To do this for the week instead, use this lambda for the GroupBy:
x => CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(
x, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday
)
Output:
{ Week = 1, Max = 1/4/2018 12:00:00 AM }
{ Week = 2, Max = 1/12/2018 12:00:00 AM }
{ Week = 4, Max = 1/24/2018 12:00:00 AM }
{ Week = 5, Max = 2/1/2018 12:00:00 AM }
{ Week = 9, Max = 2/27/2018 12:00:00 AM }
For the quarter:
x => (x.Month + 2)/3
Output:
{ Quarter = 1, Max = 2/27/2018 12:00:00 AM }
For the year:
x => x.Year
Output
{ Year = 2018, Max = 2/27/2018 12:00:00 AM }
I settled on this in C# which can be adapted for any other grouping required
public static Dictionary<Tuple<int, int>, DateTime> MaxByYearMonth(this List<DateTime> sourceDateTimes)
{
IEnumerable<DateTime> ordered = sourceDateTimes.OrderBy(x => x.Ticks);
IEnumerable<IGrouping<Tuple<int, int>, DateTime>> grouped = ordered.GroupBy(x => new Tuple<int, int>(x.Year, x.Month));
Dictionary<Tuple<int, int>, DateTime> maxed = grouped.ToDictionary(x => x.Key, x => x.Max());
return maxed;
}

Last 3 Months in linq

I have this line in a stored procedure like this :
DATENAME(MONTH, tblReg.StartDate) as [Month],
Now I want to convert this line in linq
var b = sd.tblReg;
foreach (var c in b)
{
res += "'" + c.StartDate + "',";
}
res = res.Substring(0, res.Length - 1);
res += "]";
and want to get last 3 months.. i.e. current month is Aug so with Aug i want to get last 3 months same if current month is Jan then Dec Nov Oct.. in res like this
['May' ,'June','July','Aug']
You could do something like this to find previous 3 months using Linq. DateTimeFormat.GetMonthName will help you to get month name.
int month = ..; // given a month
var result = Enumerable
.Range(-2,18) // Compute +/- 3 months for original 12 months.
.TakeWhile(x=>x <=month) // Take months until the current month
.Reverse() // Reverse the order as we need backword months.
.Take(4) // Take top 4 months (including current month)
.Select(x=>CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(x<=0?x+12: x==12? 12 : (x+12)%12))
Check this Demo

Create a List of hours

I'm trying to create list of hours my code like
var hours = Enumerable.Range(00, 24).Select(i => i.ToString("D2")));
it generates something like this in 24 hour format
but actually i need same thing not on 24 hours but 12 like after with AM PM postfix
what is the best way to generate hour list with 00.00 AM/PM formatted
Thanks
You can create a DateTime instance then use ToString format of hh.mm tt.
var hours = Enumerable.Range(00, 24)
.Select(i => new DateTime(2000,1,1,i,0,0).ToString("hh.mm tt"));
You need to execute the query using something like ToArray() to get the result since the behavior of linq is deferred execution.
var hours = Enumerable.Range(00, 24).Select(i => (DateTime.MinValue.AddHours(i)).ToString("hh.mm tt"));
You're dealing with time, so using appropriate type, DateTime, seems better than using int:
var hours = from i in Enumerable.Range(0, 24)
let h = new DateTime(2014, 1, 1, i, 0, 0)
select h.ToString("t", CultureInfo.InvariantCulture);
var startDate = DateTime.Today;
IEnumerable<DateTime> listOfHours = Enumerable.Range(0, 24).Select(h => startDate.AddHours(h));
If you need more flexibility and have from/to for specific times that overlap midnight (and increase step to let's say every 2h) you can use something like this:
private static IEnumerable<string> GetListOfHours(DateTime start, DateTime end, int step)
{
while (start <= end)
{
yield return start.ToString("hh.mm tt");
start = start.AddHours(step);
}
}
So this:
var start = DateTime.Now.Date.AddHours(6); // 6 am
var end = DateTime.Now.AddDays(1).Date.AddHours(2); // 2 am
var step = 1;
var list = GetListOfHours(start, end, step);
Will produce this:
06.00 AM
07.00 AM
08.00 AM
09.00 AM
10.00 AM
11.00 AM
12.00 PM
01.00 PM
02.00 PM
03.00 PM
04.00 PM
05.00 PM
06.00 PM
07.00 PM
08.00 PM
09.00 PM
10.00 PM
11.00 PM
12.00 AM
01.00 AM
02.00 AM
https://dotnetfiddle.net/8LYxA6

Parse out monthly items from a collection of DateTime objects

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);

Categories