Finding the month between a range - c#

I have a datasource that returns dates and I have to find where the months falls within the month and day range buckets. The months and day range buckets are predefined so I put it in a Dictionary (not sure if that is even a good idea). I am using linq to find the min and Max dates and extracting the month from them. I need to find month from the dictionary where that month extracted falls within the range. For Example
Dictionary<int, int> MonthDayBuckets = new Dictionary<int, int>() { { 3,31 }, { 6,30 }, { 9,30 }, { 12,31 } };
var MinyDate = _dataSource.Min(x => x.values[0]);
var MaxDate = _dataSource.Max(x => x.values[0]);
var startMonth = Convert.ToDateTime(MinyDate).ToString("MM");
var endMonth = Convert.ToDateTime(MaxDate).ToString("MM");
Say startmonth return Jan so I want to be able to go to the dictionary and return only march (03.31) and if I get 10 for the Max (October) I am trying to return (12,31) December

If my understanding is correct, your MonthDayBuckets variable is meant to represent date ranges:
3/31 - 6/30
6/30 - 9/30
9/30 - 12/31
12/31 - 3/31
...and given a month, you're wanting to see what the end date is of the interval that the first of that month falls between? Like you gave the example of October returning 12/31.
This problem can be simplified since you'll get the same result saying "what's the next occurring date after this given date?" The next occurring date for 10/01 would be 12/31. So here's how you could rearrange your data:
var availableDates = new List<string> { "03/31", "06/30", "09/30", "12/31" };
Now you'll be able to find a match by finding the index of the first one that's greater than your given date. Note how I made the month/day combos lexicographical orderable.
var startMonth = Convert.ToDateTime(MinyDate).ToString("MM");
var startDate = startMonth + "/01";
var endMonth = Convert.ToDateTime(MaxDate).ToString("MM");
var endDate = endMonth + "/01";
// Wrap around to the first date if this falls after the end
var nextStartDate = availableDates.FirstOrDefault(d => d.CompareTo(startDate) >= 1) ?? availableDates[0];
var nextEndDate = availableDates.FirstOrDefault(d => d.CompareTo(endDate) >= 1) ?? availableDates[0];

You could use Linq for the purpose. For example,
var nearestKey = MonthDayBuckets.Keys.Where(x => x >= endMonth.Month).Min();
var nearestDate = new DateTime(DateTime.Now.Year,nearestKey,MonthDayBuckets[nearestKey]); // or whatever the year it needs to be represent
Though above query would get you the result, I would suggest you define a structure to store the Range itself, rather than using Dictionary
For example,
public class Range
{
public MonthDate StartRange{get;set;}
public MonthDate EndRange{get;set;}
public Range(MonthDate startRange,MonthDate endRange)
{
StartRange = startRange;
EndRange = endRange;
}
}
public class MonthDate
{
public MonthDate(int month,int date)
{
Month = month;
Date = date;
}
public int Month{get;set;}
public int Date{get;set;}
//Depending on if your Ranges are inclusive or not,you need to decide how to compare
public static bool operator >=(MonthDate source, MonthDate comparer)
{
return source.Month>= comparer.Month && source.Date>=comparer.Date;
}
public static bool operator <=(MonthDate source, MonthDate comparer)
{
return source.Month<= comparer.Month && source.Date<=comparer.Date;
}
}
Now you could define ranges as
var dateRanges = new Range[]
{
new Range(new MonthDate(12,31),new MonthDate(3,31)),
new Range(new MonthDate(3,31),new MonthDate(6,30)),
new Range(new MonthDate(6,30),new MonthDate(12,31)),
};
var result = dateRanges.First(x=>x.StartRange <= new MonthDate(endMonth.Month,endMonth.Day) && x.EndRange >= new MonthDate(endMonth.Month,endMonth.Day));

Related

How to display all Fridays Date between two dates

How to get a Friday date from the given start date and end date,
For Example:
25/03/2021 - starting date
14/08/2021 - endind date
I have a class
public static class DateUtils
{
public static List<DateTime> GetWeekdayInRange(this DateTime from, DateTime to, DayOfWeek day)
{
const int daysInWeek = 7;
var result = new List<DateTime>();
var daysToAdd = ((int)day - (int)from.DayOfWeek + daysInWeek) % daysInWeek;
do
{
from = from.AddDays(daysToAdd);
result.Add(from);
daysToAdd = daysInWeek;
}
while (from < to);
return result;
}
}
That is how i call it in main method:
var from = DateTime.Today; // 25/8/2019
var to = DateTime.Today.AddDays(23); // 23/9/2019
var allFriday = from.GetWeekdayInRange(to, DayOfWeek.Friday);
Console.WriteLine(allFriday);
Console.ReadKey();
Error i get:
System.Collections.Generic.List`1[System.DateTime]
I am new and still learning, how do I call in the main method so that my output be like all dates(fridays) between the range?
Link I followed
To Answer your question, instead of printing allFridays in one go, iterate over each element of list i.e allFridays, convert into string and then print
foreach(var friday in allFridays)
Console.WriteLine(friday);
Why you are getting System.Collections.Generic.List[System.DateTime] ?
Console.WriteLine(), for non primitive type by default calls
.ToString() function which prints type of it(if it is not overridden). In your case, you
need an individual date not a type of List, so you need to iterate
each DateTime from the list and print each date.
One Liner solution:
Console.WriteLine(string.Join(Environment.NewLine, allFridays));
Alternate solution:
public static List<DateTime> GetWeekdayInRange(this DateTime #from, DateTime to, DayOfWeek day)
{
//Create list of DateTime to store range of dates
var dates = new List<DateTime>();
//Iterate over each DateTime and store it in dates list
for (var dt = #from; dt <= to; dt = dt.AddDays(1))
dates.Add(dt);
//Filter date based on DayOfWeek
var filteredDates = dates.Where(x => x.DayOfWeek == day).ToList();
return filteredDates;
}
...
var #from = DateTime.Today; // 25/8/2019
var to = DateTime.Today.AddDays(23); // 23/9/2019
var allFriday = #from.GetWeekdayInRange(to, DayOfWeek.Friday);
Console.WriteLine(string.Join(Environment.NewLine, allFridays));
.NET FIDDLE
Since in your Usage section, you have successfully get the result via GetWeekdayInRange. You can print the dates with these methods:
Method 1:
allFriday.ForEach(x => Console.WriteLine(x.ToShortDateString()));
Method 2:
foreach (var friday in allFriday)
{
Console.WriteLine(friday.ToShortDateString());
}
Method 3:
for (var i = 0; i < allFriday.Count(); i++)
{
Console.WriteLine(allFriday[i].ToShortDateString());
}
Note: ToShortDateString() is one of the methods to display Date string. You can define your desired Date pattern with ToString().

Split field a string and compare each value with string linq c#

I'm trying to search in DataTable with three string columns[start_date, end_date, expected_visits] using LINQ, but the third column has many lines as shown
So I split the third one with \n but I want to loop over date to compare it with text of txtSearchYear and return the result as DataTable, When I tried to use ForEach as shown in my code, An error appeared with me says A local or parameter named 'e' cannot be declared in this scope because that name is used in an enclosing local scope to define a local here is my code ...
var tblResult =
tbl
.AsEnumerable()
.Where(x =>
x.Field<string>("start_date").Substring(0, 4).Equals(txtSearchYear.Text.Trim())
|| x.Field<string>("end_date").Substring(0, 4).Equals(txtSearchYear.Text.Trim())
|| x.Field<string>("expected_visits").Split('\n').ToList().ForEach((e) =>
{
e.Substring(0, 4).Equals(txtSearchYear.Text.Trim())
}));
if (tblResult.Any())
{
GetData(tblResult.CopyToDataTable());
}
else
{
DGVData.Rows.Clear();
}
I would directly compare the year and use the year (int) in lambda and trying to handle the dates as List or Array of Dates instead of comparing substrings, e.g.
DateTime now = DateTime.Now;
int year = -1;
if (!Int32.TryParse(txtSearchYear.Text.Trim(), out year) || year < 1900)
{
throw new InvalidProgramException($"Year {year} isn't valid for us.");
}
List<DateTime> dates = new List<DateTime>();
dates.Add(now);
dates.Add(now.Subtract(new TimeSpan(0, 10, 10)));
dates.Add(now.Subtract(new TimeSpan(0, 20, 20)));
/* ... */
var myList = dates.AsEnumerable().Where(x => x.Year == year).ToList();
Action<DateTime> action = new Action<DateTime>(MyDelegateAction);
myList.ForEach(e => MyDelegateAction(e));
}
private static void MyDelegateAction(DateTime date) { /* do somewhat */; }
kind regards, heinrich.

List to return values in intervals for a specific field

I am implementing Telerik Chart with a huge data. The labels on x-axis of the chart are overlapping. I have overcome this problem but it is not reliable for long run.
These are the fields List have:
FieldName DataType
Date DATETIME
DateString STRING
Unit DOUBLE
Price DOUBLE
X-Axis label value comes from DateString field
Solution I implemented
The MIN and MAX Date DateString field will always return.
For the rest, return those values where weekday is 'Monday'
Here is code-
// Get min and max Date
DateTime minDate = DateTime.Now;
DateTime maxDate = DateTime.Now;
if (dtGas.Rows.Count > 0)
{
minDate = Convert.ToDateTime(dtGas.Compute("MIN([Date])", ""));
maxDate = Convert.ToDateTime(dtGas.Compute("MAX([Date])", ""));
}
// Group by 'Date' and 'DateString' | 'SUM' of Unit and 'Price'
var qGas = from x in dtGas.AsEnumerable()
group x by new
{
Date = x.Field<DateTime>("Date"),
DateString = x.Field<string>("DateString")
} into egroup
let isOne = egroup.Key.Date.DayOfWeek.ToString() == "Monday"
select new
{
Date = egroup.Key.Date,
DateString = minDate == egroup.Key.Date ?
(
egroup.Key.DateString
) :
(
maxDate == egroup.Key.Date ?
(
egroup.Key.DateString
) :
(
(isOne) ?
(
egroup.Key.DateString
) :
(" ")
)
),
Unit = egroup.Sum(r => r.Field<double>("Unit")),
Price = egroup.Sum(r => r.Field<double>("Price")),
};
This solution helps to return not all values but some of them. Hence avoiding overlapping. But in future as data grows, even this solution will fail.
Solution I need to implement
An idea that I was thinking but don't know how to implement is-
The MIN and MAX Date DateString field will always return. [Same as what I am doing right now]
Return 8 values in intervals from MIN and MAX date regardless of total count of List Item.
2.a. But if List count is less than or equals to 8 then return all values.
So, for example, if in List I have 32 values. It should return me total 10 values in DateString field and rest will be empty string.
I'd suggest something like this:
public static IList<Stock> GetSome(this IList<Stock> input)
{
var result = new List<Stock>();
if (input.Count < 8)
{
return input;
}
else
{
var i = 0;
for (; i < input.Count; ++i)
{
if (i % 8 == 0)
{
result.Add(input[i]);
}
}
if (i % 8 != 0)
{
result.Add(input.Last());
}
}
return result;
}
If the Stocks are not in chronological order, I'd call .Sort() by date on them.
You'll probably like to add null and empty collection checking in your code.

How can I get a collection of months between two Dates?

Below is my code. I am only getting the difference between two dates, but I want the name of that month which comes between the from and to dates.
public static int GetMonthsBetween(DateTime from, DateTime to)
{
if (from > to) return GetMonthsBetween(to, from);
var monthDiff = Math.Abs((to.Year * 12 + (to.Month - 1)) - (from.Year * 12 + (from.Month - 1)));
if (from.AddMonths(monthDiff) > to || to.Day < from.Day)
{
return monthDiff - 1;
}
else
{
return monthDiff;
}
}
Based on your code you could substract the month difference from the "to" DateTime to get DateTime difference from your input.
public static List<DateTime> GetMonthsBetween(DateTime from, DateTime to)
{
if (from > to) return GetMonthsBetween(to, from);
var monthDiff = Math.Abs((to.Year * 12 + (to.Month - 1)) - (from.Year * 12 + (from.Month - 1)));
if (from.AddMonths(monthDiff) > to || to.Day < from.Day)
{
monthDiff -= 1;
}
List<DateTime> results = new List<DateTime>();
for (int i = monthDiff; i >= 1; i--)
{
results.Add(to.AddMonths(-i));
}
return results;
}
To get the name of the month just format the DateTime to "MMM".
var dts = GetMonthsBetween(DateTime.Today, DateTime.Today.AddMonths(5));
foreach (var dateTime in dts)
{
Console.WriteLine(dateTime.ToString("MMM"));
}
If you want the names of all months between two dates, use something like this:
var d1 = new DateTime(2015,6,1);
var d2 = new DateTime(2015,9,1);
var monthlist = new List<string>();
string format = d1.Year == d2.Year ? "MMMM" : "MMMM yyyy";
for (var d = d1; d <= d2; d = d.AddMonths(1))
{
monthlist.Add(d.ToString(format));
}
The full list is now in monthlist - you will want to return that from your method.
Assuming you're using Java and JodaTime there are several flaws in your code.
You cant use from > to to evaluate if a date is after an other. Use from.isAfter(to) instead.
JodaTime already supplies a method to calculate the amount of whole months between two given Dates Months.monthsBetween(start,end).
With the calculated month difference you can instantiate a new DateTime object that holds a date in your desired month and output its name via yourNewDateTimeObject.month().getAsText().
edit: Just found out you're using C# so ignore my text above this. Below here I will try to answer your question in C#.
Why dont you just subtract the from from the to date and obtain your difference?
The resulting TimeSpan can be used to determine the amount of whole months between your two given dates.
To obtain the resulting month name you could use yourDateTime.ToString("MMMM");

How can I make this LINQ query cleaner?

I have recently written a LINQ query to get a Dictionary containing the last 6 month's placement amounts.
It is returning a Dictionary of Month string - Decimal Amount pairs.
It seems kind of cludgey. Any of you LINQ masters out there able to help me refactor this to make a bit cleaner?
/// <summary>
/// Gets the last 6 months of Placement History totalled by Month
/// for all Agencies
/// </summary>
/// <returns></returns>
public Dictionary<string, decimal> getRecentPlacementHistory()
{
var placementHistoryByMonth = new Dictionary<string, decimal>();
using (DemoLinqDataContext db = new DemoLinqDataContext())
{
for (int i = 0; i < 6; i++)
{
Decimal monthTotal =
(from a in db.Accounts
where
(a.Date_Assigned.Value.Month == DateTime.Now.AddMonths(-i).Month &&
a.Date_Assigned.Value.Year == DateTime.Now.AddMonths(-i).Month)
select a.Amount_Assigned).Sum();
String currentMonth = DateTime.Now.AddMonths(-i).ToString("MMM");
placementHistoryByMonth.Add(currentMonth, monthTotal);
}
return placementHistoryByMonth;
}
}
First problem:
where (a.Date_Assigned.Value.Month == DateTime.Now.AddMonths(-i).Month &&
a.Date_Assigned.Value.Year == DateTime.Now.AddMonths(-i).Month)
Shouldn't the latter expression end with .Year rather than .Month? Surely you'll rarely get a year with a value of 1-12...
I would extract the idea of the "current month" as you're using it a lot. Note that you're also taking the current time multiple times, which could give odd results if it runs at midnight at the end of a month...
public Dictionary<string, decimal> getRecentPlacementHistory()
{
var placementHistoryByMonth = new Dictionary<string, decimal>();
using (DemoLinqDataContext db = new DemoLinqDataContext())
{
DateTime now = DateTime.Now;
for (int i = 0; i < 6; i++)
{
DateTime selectedDate = now.AddMonths(-i);
Decimal monthTotal =
(from a in db.Accounts
where (a.Date_Assigned.Value.Month == selectedDate.Month &&
a.Date_Assigned.Value.Year == selectedDate.Year)
select a.Amount_Assigned).Sum();
placementHistoryByMonth.Add(selectedDate.ToString("MMM"),
monthTotal);
}
return placementHistoryByMonth;
}
}
I realise it's probably the loop that you were trying to get rid of. You could try working out the upper and lower bounds of the dates for the whole lot, then grouping by the year/month of a.Date_Assigned within the relevant bounds. It won't be much prettier though, to be honest. Mind you, that would only be one query to the database, if you could pull it off.
Use Group By
DateTime now = DateTime.Now;
DateTime thisMonth = new DateTime(now.Year, now.Month, 1);
Dictionary<string, decimal> dict;
using (DemoLinqDataContext db = new DemoLinqDataContext())
{
var monthlyTotal = from a in db.Accounts
where a.Date_Assigned > thisMonth.AddMonths(-6)
group a by new {a.Date_Assigned.Year, a.Date_Assigned.Month} into g
select new {Month = new DateTime(g.Key.Year, g.Key.Month, 1),
Total = g.Sum(a=>a.Amount_Assigned)};
dict = monthlyTotal.OrderBy(p => p.Month).ToDictionary(n => n.Month.ToString("MMM"), n => n.Total);
}
No loop needed!
If you are not worried about missing months with no data,then I had a similar problem where I did the following : (translated to your variables)
DateTime startPeriod =
new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1).AddMonths(-6);
var query1 = from a in db.Accounts where a.Date_Assigned >= startPeriod
group a by new { a.Date_Assigned.Year ,a.Date_Assigned.Month } into result
select new
{
dt = new DateTime( result.Key.Year, result.Key.Month , 1),
MonthTotal = result.Sum(i => i.Amount_Assigned)
} ;
var dict = query1.OrderBy(p=> p.dt).ToDictionary(n => n.Dt.ToString("MMM") , n => n.MonthTotal );

Categories