C# lists - sorting date problem - c#

I have a C# list collection that I'm trying to sort. The strings that I'm trying to sort are dates "10/19/2009","10/20/2009"...etc. The sort method on my list will sort the dates but the problem is when a day has one digit, like "10/2/2009". When this happens the order is off. It will go "10/19/2009","10/20/2009","11/10/2009","11/2/2009","11/21/2009"..etc. This is ordering them wrong because it sees the two as greater than the 1 in 10. How can I correct this?
thanks

The problem is they're strings, but you want to sort them by dates. Use a comparison function that converts them to dates before comparing. Something like this:
List<string> strings = new List<string>();
// TODO: fill the list
strings.Sort((x, y) => DateTime.Parse(x).CompareTo(DateTime.Parse(y)));

Assuming all your strings will parse:
MyList.OrderBy(d => DateTime.Parse(d));
Otherwise, you might need to use ParseExact() or something a little more complicated.

write a compare method to convert "10/2/2009" to a date then compare

I wanted to see how well I could outperform Chris's solution with my own IComparer. The difference was negligible. To sort the same list of one million dates, my solution took 63.2 seconds, and Chris's took 66.2 seconds.
/// <summary>
/// Date strings must be in the format [M]M/[D]D/YYYY
/// </summary>
class DateStringComparer : IComparer<string>
{
private static char[] slash = { '/' };
public int Compare(string Date1, string Date2)
{
// get date component strings
string[] strings1 = Date1.Split(slash);
string[] strings2 = Date2.Split(slash);
// get date component numbers
int[] values1 = { Convert.ToInt32(strings1[0]),
Convert.ToInt32(strings1[1]),
Convert.ToInt32(strings1[2]) };
int[] values2 = { Convert.ToInt32(strings2[0]),
Convert.ToInt32(strings2[1]),
Convert.ToInt32(strings2[2]) };
// compare year, month, day
if (values1[2] == values2[2])
if (values1[0] == values2[0])
return values1[1].CompareTo(values2[1]);
else
return values1[0].CompareTo(values2[0]);
else
return values1[2].CompareTo(values2[2]);
}
}
As for sorting the dates as pre-existing DateTime instances, that took 252 milliseconds.

You need to either use a sort specific for dates, or use something like Natural Sort.

Parse the strings to DateTime objects and use DateTime.Compare.
Chris beat me to it!

If you care about performance and if that is possible for you, you would preferably sort your dates before you generate the strings. You would then use the date objects directly for the sort.
You would then save time manipulating strings back and forth.

Related

c# DateTime.Parse precision Issue

Quick question about precision with DateTime.Parse in c#?
I have multiple input files that I am consolidating into one list of lines, which I will then order by DateTime.
This is fine, except the Parse function seems to handle the date incorrectly when the milliseconds is only 2 digits, in the case below it treats 09:57:44.84 as 09:57:44.840 instead of 09:57:44.084
List<DateTime> lstUnOrdered = new List<DateTime>();
lstUnOrdered.Add(DateTime.Parse("04/09/2020 09:57:44.573", CultureInfo.InvariantCulture));
lstUnOrdered.Add(DateTime.Parse("04/09/2020 09:57:44.84", CultureInfo.InvariantCulture));
var Ordered = lstUnOrdered.OrderBy(x => x.TimeOfDay);
foreach (var item in Ordered)
{
Console.WriteLine(item.ToString("dd/MM/yyyy HH:mm:ss.fff"));
}
When run, you get the following Output
09/04/2020 09:57:44.573
09/04/2020 09:57:44.840
I Expected this Output
09/04/2020 09:57:44.084
09/04/2020 09:57:44.573
Any suggestions on where I may be going wrong?
Thanks
EDIT:
Based on the comments below, just a few updates:
"Fundamentally: whatever is generating these values is broken, and should be fixed. If that's impossible for you, you should patch the data before parsing it, by inserting extra 0s where necessary. "
-- This is correct, I have no control over the data. But I do know that the order in the input file is :
09/04/2020 09:57:44.84
09/04/2020 09:57:44.573
This tells me the date should be 084 and not 840, I'm not disputing Parse is incorrect, just looking for alternatives to parse these dates with more precision, rather then having to write another method to sanitize the date string first.
I can of course split the string on the . and add 1 or 2 zeroes if needed, was hoping .Net had an inbuilt way for doing this with DateTime.Parse or alternative.
Thanks
Just an Update, This will fix the issue with the bad input string in case anyone is wondering:
static void Main(string[] args)
{
List<DateTime> lstUnOrdered = new List<DateTime>();
lstUnOrdered.Add(DateTime.Parse(MakeCorrectDate("04/09/2020 09:57:44.573"),
CultureInfo.InvariantCulture));
lstUnOrdered.Add(DateTime.Parse(MakeCorrectDate("04/09/2020 09:57:44.84"), CultureInfo.InvariantCulture));
var Ordered = lstUnOrdered.OrderBy(x => x.TimeOfDay);
foreach (var item in Ordered)
{
Console.WriteLine(item.ToString("dd/MM/yyyy HH:mm:ss.fff"));
}
}
private static string MakeCorrectDate(string strDate)
{
string[] milli = strDate.Split('.');
return milli[0] + "." + milli[1].PadLeft(3, '0');
}

Is there way to get the max value from YYYYMM combination?

Eg: I have 2020M01 ,2019M12,2020M03 in datarow[].
How can i fetch max value i.e 2020M03
Your date format looks like it can be string sorted:
var dates = new System.Collections.Generic.List<string> { "2020M01", "2019M02", "2020M03" };
var max = dates.OrderByDescending(x => x).FirstOrDefault();
Or, as #panagiotis points out, Max() would also work:
var max = dates.Max();
This isn't an unusual format. Such formats ensure that date literals can be sorted alphabetically. YYYY-MM-DD and YYYYMMDD can be sorted alphabetically too.
This means that you can find the minimum or maximum value in a list simply by using the Min() or Max() LINQ functions, eg :
var months=new[]{ "2020M01" ,"2019M12","2020M03"};
var latestMonth=months.Max();
var earliestMonth=months.Min();
You say you have an array of DataRow, so I assume your dates must be in some column of the row
I'd thus say you need something like:
myDataRowArray.Max(ro => ro["nameOfYourDateColumn"].ToString());
If you mean that all your dates are in different columns of the same datarow, it's a bit more tricky. If there is nothing else in the row other than these dates, then it can be done with the row's ItemArray, an array of object representing each value of row's cells:
myDataRow.ItemArray.Max(elem => elem.ToString());
If there are some columns of the datarow that are your dates and some that are not, you're going to need to pull them out. Here I extract 3 different columns and put their values to a string array, then find the max:
new[] {
myRow["date1Column"].ToString(),
myRow["date2Column"].ToString(),
myRow["date3Column"].ToString()
}.Max();

sort a list of strings based by comparing the datetime portion of the string

I would like to sort a list of strings. the following example of pre-sorted strings:
"2017-03-21 17:14:36.111 I like Red"
"2017-03-21 17:14:35.333 I like Yellow"
"2017-03-21 17:14:36.111 I like Green"
I would like to sort the list using the datetime substring only.
private void MergeAndSort(string[] lines1, string[] lines2)
{
var entries = new List<string>();
entries.AddRange(lines1);
entries.AddRange(lines2);
entries.Sort();
foreach (string entry in entries)
{
CombinedrichTextBox.AppendText(entry);
}
}
Assuming your date/time string length stays constant you can do this.
string[] entries =
{
"2017-03-21 17:14:36.380 I like Red",
"2017-03-21 17:14:35.380 I like Yellow",
"2017-03-21 17:14:36.380 I like Green"
};
var data = new List<Tuple<DateTime, string>>();
foreach (var entry in entries)
{
data.Add(
new Tuple<DateTime, string>(DateTime.Parse(entry.Substring(0, 23)),
entry.Substring(23)));
}
var sorted = data.OrderBy(e => e.Item1);
entries = sorted.Select(e => $"{e.Item1} {e.Item2}").ToArray();
The cool thing about standard DateTime strings is that you don't need to convert them in order to sort them. You can just sort the strings.
So then your problem is in figuring out where the date/time string is. You can do as Ahsan suggests and use the position in the string. You can look for the first letter in the string. Which depends on what the actual strings and not just the examples look like.
Can I do this in just one C# statement? You bet!
var sorted = lines1
.concat(item2)
.Sort((a,b) => string.Compare(a.Substring(0,23), b.Substring(0,23)))
.ToList();
This example is a little sloppy because it assumes that there will always be a standard date at the front of each string, and that's often a dangerous assumption. If you also assume that the DateTime strings are all unique, and the millisecond resolution would suggest that they are, then you don't need to do the Substring and you can just used Sort().

Contains doen't check in the date range

I have a date range come like this,
string ActualReleaseDates ="7/8/2016, 7/9/2016, 7/11/2016,7/3/2016,7/10/2016,7/17/2016,7/24/2016,7/31/2016";
string NewsReleasedDate ="07/11/2016";
I want to check NewsReleaseDate is inside the ActualReleaseDates
But in the following code it return as a false.
if (ActualReleaseDates.Split(',').Contains(NewsReleasedDate.TrimStart(new Char[] { '0' })))
{
//some code here
}
The immediate problem is that after splitting your ActualReleaseDates string, there isn't an entry of "7/11/2016"... instead, there's an entry of " 7/11/2016"... note the space.
But more fundamentally, just trimming the start of NewsReleasedDate won't help if the value is something like "07/08/2016"... what you should be doing is handling these values as dates, rather than as strings:
Split ActualReleaseDates by comma, then parse each value (after trimming whitespace) in an appropriate format (which I suspect is M/d/yyyy) so that you get a List<DateTime>.
Parse NewsReleasedDate in the appropriate format, which I suspect is MM/dd/yyyy, so you get a DateTime.
See whether the parsed value from the second step occurs in the list from the first step.
(I'd personally recommend using Noda Time and parsing to LocalDate values, but I'm biased...)
Fundamentally, you're trying to see whether one date occurs in a list of dates... so make sure you get your data into its most appropriate representation as early as possible. Ideally, avoid using strings for this at all... we don't know where your data has come from, but if it started off in another representation and was converted into text, see if you can avoid that conversion.
The white space problem. You can use trim() and ' 7/11/2016' will be '7/11/2016'
var ActualReleaseDates = "7/8/2016, 7/9/2016, 7/11/2016,7/3/2016,7/10/2016,7/17/2016,7/24/2016,7/31/2016";
var NewsReleasedDate = "07/11/2016";
var splitActualReleaseDates = ActualReleaseDates.Split(',').Select(x => x.Trim());
if (splitActualReleaseDates.Contains(NewsReleasedDate.TrimStart(new Char[] { '0' })))
{
}
You can use linq to convert your strings into DateTime objects and compare them instead of strings
string ActualReleaseDates ="7/8/2016,7/9/2016,7/11/2016,7/3/2016,7/10/2016,7/17/2016,7/24/2016,7/31/2016";
string NewsReleasedDate ="07/11/2016";
var releaseDates = ActualReleaseDates.Split(',').Select(x => DateTime.Parse(x));
var newsReleased = DateTime.Parse(NewsReleaseDate);
if (releaseDates.Contains(newsReleased))
{
//some code here
}
please note that DateTime is parsed respectively to the current Culture. You can use DateTime.ParseExact if you want to specify exact date format.
You can Prase to DateTime before doing the query like this:
(I think this is the most accurate and guaranteed way to compare dates)
Func<string, DateTime> stringToDate = s => DateTime.ParseExact(s.Trim(), "M/d/yyyy",
CultureInfo.InvariantCulture);
DateTime newReleaseDateTime = stringToDate(NewsReleasedDate);
bool result = ActualReleaseDates.Split(',').Select(x => stringToDate(x))
.Contains(newReleaseDateTime);
It returns false because of the date 07/11/2016 stored in NewsReleasedDate is stored as string with a '0' at the begining. And in the ActualReleaseDates string you have white spaces between the ',' and numbers.
Try to rewrite theese strings like this :
ActualReleaseDates ="7/8/2016,7/9/2016,7/11/2016,7/3/2016,7/10/2016,7/17/2016,7/24/2016,7/31/2016"; // white spaces removed.
and the variable like this :
NewsReleasedDate ="7/11/2016"; // 0 removed
This is my code example :
string ActualReleaseDates = "7/8/2016,7/9/2016,7/11/2016,7/3/2016,7/10/2016,7/17/2016,7/24/2016,7/31/2016";
string NewsReleasedDate = "7/11/2016";
string[] dates = ActualReleaseDates.Split(',');
Console.WriteLine(dates.Contains(NewsReleasedDate));
This is not the best way to compare dates, you can use Date class which is usefull to do this kind of comparations.

Have a string of "DateTimeTicks_Guid", use only CompareTo to generate find elements with a date range

I have a set of strings, the strings are in format: Ticks then an underscore than a Guid.
So for example: Ticks_Guid would be the string. The ticks are actually DateTime.MaxTicks - the ticks of some date (aside: I do this to get the string to naturally show up in descending order).
Using only CompareTo is there a way to get the strings that are within a certain date range?
Since you say you have the strings in naturally descending order, I assume you have a fixed-width section for the ticks component of the string (otherwise the descending order bit won't work). The easiest comparison therefore would be to create two similarly formatted string; one for the tick value of the earlier date combined with an empty guid (all 0s) and another for the latter date combined with a guid that is all 0xFF, and then find the strings that sort between those in raw code-point order.
I'd just compare if the ticks are in between your dates to check in an if-statement:
ArrayList a = new ArrayList();
string b = "345678_lalala";
string c = "429817_lalelu";
a.Add(b);
a.Add(c);
//date to check
string begin = "298918";
string end = "419812";
ArrayList outputList = new ArrayList();
foreach (string str in a)
{
if (str.CompareTo(begin) == 1 && str.CompareTo(end) == -1)
{
outputList.Add(str);
}
}

Categories

Resources