I'm selecting an IEnumerable of DateTime by using a list of int which represent a year.
Each of the resulting DateTimes is given a default month and day of 1 so e.g.
List<int> years = new List<int>() { 2018, 2017, 2016 };
var dateTimes = years.Select(x => new DateTime(int.Parse(x), 1, 1));
Gives me 2018/01/01, 2017/01/01, 2016/01/01 (ignoring the time component)
I want to get the same results but for each month of each year too, so actually 36 results for three given years:
2018/01/01
2018/02/01
...
2018/11/01
2018/12/01
2017/01/01
...
2016/12/01
(this is using non-US culture datetime where months are the middle value)
I was wondering if C# had a really nice shortcut like the range notation
var dateTimes = years.Select(x => new DateTime(int.Parse(x), 1..12, 1));
But that certainly doesn't work.
Any syntactic-shortcut way to achieve this without just looping i = 1 to 12 style?
The best I came up with was:
var dateTimes = new List<DateTime>();
foreach(var y in years)
{
for (int i = 1; i <= 12; i++) dateTimes.Add(new DateTime(int.Parse(y), i, 1));
}
Which does exactly what I want but though there was a more succinct way to write.
var dateTimes = years.SelectMany(y => Enumerable.Range(1, 12).Select(m => new DateTime(y, m, 1))); // .ToList() if you want
Enumerable.Range() available from .NET 3.5
Related
I previously asked this question here: List of Dates ordered in a certain way
I thought the proposed solution was fine until the year ticked over on my date list and encountered an issue.
My date list (in this string based format - this is how the data comes to me from the source API)
201711
201712
201801
201811
201812
201901
I want to present my data in a bar chart to show 3 months worth of year on year comparison in month order. This would mean I order the list as so
201711
201811
201712
201812
201801
201901
So I can then see year-on-year bars for November, December and Jan in that order.
I've tried the solution at the bottom of the question but it places the order like so (which is not what I want):
201801
201901
201711
201811
201712
201812
For clarity, next month it will need to move forward to be this date list:
the first month I want will always be 2 months before the current one
201712
201812
201801
201901
201802
201902
var rowsInOrder = dateList.OrderBy(n => DateTime.ParseExact(n, "yyyyMM", CultureInfo.CurrentCulture).Month).ThenBy(n=> DateTime.ParseExact(n, "yyyyMM", CultureInfo.CurrentCulture).Year);
You can use this Lookup approach that first determines the month-groups:
var monthLookup = dateList
.Select(s => new{String = s, Date = DateTime.ParseExact(s, "yyyyMM", CultureInfo.CurrentCulture)})
.OrderBy(x => x.Date) // not necessary in your sample data but i assume it's desired
.ToLookup(x=> x.Date.Month);
var rowsInOrder = monthLookup.SelectMany(x => x).Select(x => x.String);
I could manage to achieve your goal using GroupBy to group months,
var rowsInOrder = new List<string>();
foreach (var grouping in dates.GroupBy(s =>
DateTime.ParseExact(s, "yyyyMM", CultureInfo.CurrentCulture).Month))
{
rowsInOrder.AddRange(grouping.OrderBy(s => s));
};
You can also order months with same logic:
var rowsInOrder = new List<string>();
foreach (var grouping in dates
.OrderBy(s => DateTime.ParseExact(s, "yyyyMM", CultureInfo.CurrentCulture).Month).GroupBy(s =>
DateTime.ParseExact(s, "yyyyMM", CultureInfo.CurrentCulture).Month))
{
rowsInOrder.AddRange(grouping.OrderBy(s => s));
}
It seems to me that this is sufficient:
var rowsInOrder = dates.OrderBy(x => x).GroupBy(x => x.Substring(4)).SelectMany(x => x);
There's simply no need to muck around with parsing dates. It's a simple string sort and group this way.
So you are stuck with a sequence of objects, where every object has a Date property of type string in the format yyyyMM. and you want to extract some data from it.
Your date format is language independent. It doesn't matter whether your computer is a British one, or a Chinese one. The Date property will always be in format yyyyMM.
This makes it fairly easy to convert it into a DateTime format, which makes it easy to access the Year and Month.
const string dateTimeFormat = "yyyyMM";
CultureInfo provider = CultureInfo.InvariantCulture;
var dateList = ... // your original list of items with the string Date property
var itemsWithYearMonth = dateList.Select(item => new
{
DateTime = DateTime.ParseExact(item, dateTimeFormat, provider)
... // select other items you need for your bar chart
});
Now, given a StartYear/Month, a NrOfYears and a NrOfMonths you want to group dateTimeItems into groups of same month.
For example, starting at 2018-11, I want groups with four months, for three consecutive years (yeah, yeah, I know in your original request it was only 3 months, 2 years, but why limit yourself to this, let's make your code re-usable):
group 1: 2018-11, 2019-11, 2020-11, 2021-11
group 2: 2018-12, 2019-12, 2020-12, 2021-12
group 3: 2019-01, 2020-01, 2021-01, 2022-01
Bonus-points: we'll pass the year boundary!
So input:
var itemsWithYearMonth = ... // see above
int startYear = ...
int startMonth = ...
int nrOfMonths = ...
int nrOfYears = ...
We will make groups of items with same months. We don't want all Months of the year, we only want some months. If we want the 3 months starting at month 11, we need to keep the groups with Months 11, 12, 1.
var desiredMonths = Enumerable.Range(startMonth, nrOfMonths) // example: 11, 12, 13
.Select(monthNr => 1 + ((monthNr-1) % 12)); // 11, 12, 1
From your input, we don't want all months, we only want the year/month larger than the starting month.
DateTime startMonth = new DateTime(startYear, startMonth, 1);
The easiest way is to keep only the input source of items with date equal or larger than startMonth and take only the first NumberOfYears items of each group. This way you get the correct number of items if you pass the year boundary like I did in my example.
var result = itemsWithYearMonth
// keep only the items newer than startMonth
.Where(item => item.DateTime >= startMonth)
// group by same month:
.GroupBy(item => item.DateTime.Month,
(month, itemsWithThisMonth) => new
{
Month = month, // in my example: 11, 12, 1, ...
// in every group: take the first nrOfYears items:
Items = itemsWithThisMonth
// order by ascending year
.OrderBy(itemWithThisMonth => itemWithThisMonth.Year)
// no need to order by Month, all Months are equal in this group
.Take(nrOfYears)
.ToList(),
})
// keep only the desired months:
.Select(group => desiredMonth.Contains(group.Month));
So now you have groups:
group of month 11, with data of years 2018, 2019, 2020, 2021
group of month 12, with data of years 2018, 2019, 2020, 2021
group of month 01, with data of years 2019, 2020, 2021, 2022
I'm a newbie developer and I need to populate a dropdown list with dates so they can be selected by a website visitor to retrieve certain data.
The list is unusual because it must populate backward from last month up until a year ago. Thus, last month will be the first entry in the list and each previous month will follow. For example, if today is July 5, 2017, the dropdown list will look like this:
Jun 2017
May 2017
Apr 2017
Mar 2017
Feb 2017
Jan 2017
Dec 2016
and so on . . .
Each month the list gets longer, so I can’t code a fixed length to the list. I came up with the following code, but it won’t build:
[RequireLogin]
public ActionResult Index()
{
bool firstItem = true;
int x = -1;
int y = 1;
int daysFromStartDate = beginDate.Date.Subtract(currentDate).Days;
List<SelectListItem> items = new List<SelectListItem>();
while (daysFromStartDate >= 30)
{
DateTime previousMonth = DateTime.Now.Date.AddMonths(x);
string pMonth = previousMonth.ToString("MMM");
string pYear = previousMonth.ToString("YYYY");
Int32.TryParse(pMonth, out thisMonth);
Int32.TryParse(pYear, out thisYear);
if (firstItem)
firstItem = false;
SelectListItem item[y] = new SelectListItem() { Text = pMonth + " " + pYear, Value = pMonth, Selected = true }; //<- #1
else
SelectListItem item[y]= new SelectListItem() { Text = pMonth + " " + pYear, Value = pMonth, Selected = false }; //<- #2
items.Add(item[y]); //<- #3
x = x--;
y = y++;
}
ViewBag.MonthList = items;
return View();
}
I’m getting several build errors on the “item” variable and I don’t know how to fix them:
1 - Bad array declarator: To declare a managed array the rank specifier precedes the variable's identifier. To declare a fixed size buffer field, use the fixed keyword before the field type. Array size cannot be specified in a variable declaration (try initializing with a 'new' expression)
2 – A local variable named ‘item’ is already defined in this scope (I know I need to get rid of the dup, but that's just a symptom of my overall logic problem).
3 – Cannot apply indexing with [] to an expression of the type SelectListItem
Apparently, c# doesn't like me trying to use an index on my list items, as in "item[y]". I thought I was being clever, but this approach totally bombs.
I scoured this site as well as the Internet and read all of the examples I found, but couldn’t find a solution that matched my needs. Can anyone offer some guidance on how to fix this logic?
I believe I've thoroughly explained the problem, but if I'm lacking in precision or clarity, please accept my apology in advance. I'll be happy to respond to requests for more information.
*******QUESTION EDIT***********
This was a sticky one (for me anyway). However, Vlad Stryapko put me on the right track, and with a few tweeks, I got his solution (which I accepted) working. There's still another issue, but unrelated to my original post, so I'll post a separate question for that. Here's my final code:
My ViewModel
public SelectList MonthList { get; set; }
public SelectList ReverseMonthsLists()
{
var stringViewOfDates = GetDates().Select(_ => _.ToString("MMM yyyy")).ToList();
var list = new List<SelectListItem>();
list.Add(new SelectListItem { Selected = true, Text = stringViewOfDates[0], Value = stringViewOfDates[0] });
for (int i = 1; i < stringViewOfDates.Count(); i++)
{
list.Add(new SelectListItem { Selected = false, Text = stringViewOfDates[i], Value = stringViewOfDates[i] });
}
var selectList = new SelectList(list);
return selectList;
}
public static IEnumerable<DateTime> GetDates()
{
DateTime startDate = new DateTime(2017, 6, 1).Date;
var currentDate = DateTime.Now.Date;
int numberOfMonthsToShow = (currentDate.Year - startDate.Year) * 12 + currentDate.Month - startDate.Month;
if (numberOfMonthsToShow == 0)
numberOfMonthsToShow = 1;
var dates = new List<DateTime>(numberOfMonthsToShow);
for (int i = 0; i < numberOfMonthsToShow; i++)
{
dates.Add(currentDate);
currentDate = currentDate.AddMonths(-1);
}
return dates;
}
My Html View
#Html.DropDownList("selectList", Model.ReverseMonthsLists(), "Month Selector")
The code compiles now, but all I get is "System.Web.Mvc.SelectListItem" in my dropdown list. I'll pose that as a question in another thread, because I don't believe I'm allowed to ask a second question here.
given a generic function to get the list of dates :-
public static IEnumerable<string> ReverseMonthList(string format, DateTime start, int count)
{
return Enumerable.Range(0, count).Select(n => start.AddMonths(-(n + 1)).ToString(format));
}
then
var from = new DateTime(2016, 7, 5);
var now = new DateTime(2017, 7, 5);
var monthsSinceStart = ((now.Year - #from.Year) * 12) + now.Month - #from.Month;
var dates = ReverseMonthList("MMM yyyy", now, monthsSinceStart);
gives
Use LINQ and go backwards:
var ans = Enumerable.Range(1, 12).Select(p => start.AddMonths(-p).ToString("MMM yy")).ToList();
I haven't quite got what you need to so, here's an example of how you can build a list of dates starting from the current one and containing 12 months.
public static IEnumerable<DateTime> GetDates()
{
var currentDate = DateTime.Now;
var numberOfMonthsToShow = 12;
var dates = new List<DateTime>(numberOfMonthsToShow);
for (int i = 0; i < numberOfMonthsToShow; i++)
{
dates.Add(currentDate);
currentDate = currentDate.AddMonths(-1);
}
return dates;
}
If you want to start from the previous month, initialise currentDate as DateTime.Now.AddMonths(-1)
Next, you will have to build a string representation of those dates. For your format, use Y format selector.
var stringViewOfDates = GetDates().Select(_ => _.ToString("MMM yyyy"));
Now that you have your strings, you can add them to SelectList. I don't have ASP.NET MVC around right now, but it should be pretty trivial. Something like:
var list = new List<SelectListItem>();
list.Add(new SelectListItem { Selected = true, Text = stringViewOfDates[0], Value = stringViewOfDates[0]})
for (int i = 1; i < stringView.Count(); i++)
{
list.Add(new SelectListItem { Selected = false, Text = stringViewOfDates[i], Value = stringViewOfDates[i]);
}
var selectList = new SelectList(list);
You have a lot of code which seems redundant for a task of 'give me previous months'. Please try the one I've provided or explain why you need so convoluted code. I might be missing some logic of yours.
this is a console app
var startDate = DateTime.Now.Date;
var days = new List<Tuple<string, string>>();
int totalMonths = 12;
for (int i = 1; i < totalMonths; i++)
{
var tempDay = startDate.AddMonths(-i);
days.Add(new Tuple<string, string>($"{tempDay.ToString("MMM")} {tempDay.ToString("yyyy")}", tempDay.ToString("MMM")));
}
foreach (var d in days)
{
Console.WriteLine(d.Item1);
}
output
Jun 2017
May 2017
Apr 2017
Mar 2017
Feb 2017
Jan 2017
Dec 2016
Nov 2016
Oct 2016
Sep 2016
Aug 2016
for test purpose I have used tuple. Use the listitem class. for the item selection use either a separate loop to or use if condition in the generation method.
The list is unusual because it must populate backward from last month up until a year ago.
It's easy to do. First - generate dates. You have the start date (previous month) and a total number of months required (12). Then project each date into SelectListItem:
var today = DateTime.Today;
var startOfMonth = new DateTime(today.Year, today.Month, 1);
var items = from i in Enumerable.Range(1, 12)
let date = startOfMonth.AddMonths(-i)
select new SelectListItem {
Text = date.ToString("MMM yyyy"),
Value = date.ToString("MMM"),
Selected = i == 1
};
ViewBag.MonthList = items.ToList();
Notes:
You should calculate dates relative to the start of the current month. Otherwise, you can have a problem with February 29.
Use string format to produce the date in the format you want. Do not format parts of the date and concatenate them.
Year format string is lowercase yyyy
SelectListItem item[y] = ... is incorrect syntax. It's a mix of assignment and declaration. If you want to assign an item in the list, then you should refer to the item by index item[y] = .... But you should check before if the list contains enough items. Actually what you really need to do here is add an item to the list, which is achieved by items.Add(...).
Consider to learn LINQ - it allows you to write queries (in a declarative way) to easily filter, project, etc data
I was hoping to write down a countdown timer for a New Year's party tomorrow, but I seem to have encountered an issue with subtracting two DateTimes from eachother.
My app seems to work fine when dealing with dates in January 2015 (the first method), but it gives strange results when I cross the 2014 / 2015 year end boundary (the second one).
Any ideas?
private static readonly string MyDateFormat = "{0:" + string.Join("", (-1).ToString("x").Skip(1)) + "}";
public void disKoud()
{
var later = new DateTime(2015-01-01);
var now = new DateTime(2015-01-31);
var diff = (later - now);
Debug.WriteLine(MyDateFormat, diff); // 0000030
}
public void sonderNHoed()
{
var later = new DateTime(2015-01-01);
var now = new DateTime(2014-12-31);
var diff = (later - now);
Debug.WriteLine(MyDateFormat, diff); // 0000042 ??? WTF
}
You're not constructing the dates you think you are. These lines:
var later = new DateTime(2015-01-01);
var now = new DateTime(2014-12-31);
are equivalent to:
var later = new DateTime(2013);
var now = new DateTime(1972);
After all, (2015 - 1) - 1 is 2013, and (2014 - 12) - 31 is 1972. That's just simple arithmetic.
Those values are both within the first second of January 1st 0001 AD... because you've passed a number of ticks.
You meant:
var later = new DateTime(2015, 01, 01);
var now = new DateTime(2014, 12, 31);
... which is passing the year, month and day as separate values into the DateTime constructor.
One thing that can help here is to use your debugger. If you mouse over the values, you will clearly see that they are actually in the year 0001. That's surely not what you were expecting!
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Linq orderby, start with specific number, then return to lowest
I need to create a ComboBox that lists the months of the year, but need it to start with the
current month, and then the rest in month order, e.g.:
October
November
December
January
February
March
etc.....
The data source is a list of months in a database, which are numbered according to month number (i.e. January = 1, etc.) and then manipulated to give a datetime
How can I sort this list in C# so that I get the order I want?
TIA.
string[] months = CultureInfo.CurrentCulture.DateTimeFormat.MonthNames;
var ordered = months.Skip(DateTime.Today.Month - 1)
.Concat(months.Take(DateTime.Today.Month - 1))
.Where(s=>!String.IsNullOrEmpty(s))
.ToList();
Use DateTimeFormatInfo.GetMonthName methoed
List<string> list = new List<string>();
DateTimeFormatInfo dtFI = new DateTimeFormatInfo();
DateTime currentDate = DateTime.Now;
DateTime nextyearDate = currentDate.AddYears(1).AddDays(-1);
while (currentDate < nextyearDate)
{
list.Add(dtFI.GetMonthName(currentDate.Month));
currentDate = currentDate.AddMonths(1);
}
This will create a new list of months, starting from current month.
Another take on this with LINQ:
// month name source, use what you prefer
var monthNames = CultureInfo.CurrentCulture.DateTimeFormat.MonthNames;
var sorted = Enumerable.Range(1, 12).Zip(monthNames, Tuple.Create)
.OrderBy(t => (t.Item1 - DateTime.Today.Month + 12) % 12)
.Select(t => t.Item2)
.ToArray();
You can get the lists of months in C# with List<DateTime> monthNames = DateTimeFormatInfo.CurrentInfo.MonthNames.ToList();. It get a List<DateTime> with 13 elements (a null month name at the end, but you can always remove the last element monthNames.RemoveAt(monthNames.Count - 1);
http://msdn.microsoft.com/en-us/library/system.globalization.datetimeformatinfo.monthnames.aspx
To reorder this list you can get the current month number index using DateTime.Now.Month.ToString("00");, and restructure the list newMonthNames = monthNames.GetRange(index, 12 - index).AddRange(monthNames.GetRange(0, index);
There are many ways to do it, like others have shown.
I've seen this question, which looks for the Nth weekday in a specific month, but I want something that will look for the Nth [weekday list] in a given month. Is there an easy way to get that without looping through every day in the month?
For example, if I say I want the 3rd [Mon, Wed, Fri] in a month, and the month starts on a Wednesday, it should return the 1st Monday (1 = Wed, 2 = Fri, 3 = Mon). If I say I want the 2nd last [Mon, Tue, Wed, Thurs] and the last day of the month is a Sunday, it should return the previous Wednesday.
Meh I mean you might be able to come up with something ugly and incomprehensible that is formula-based and avoids looping, but it will be ugly and incomprehensible so bug-prone and a maintenance disaster, and not at all fun to try to come up with.
Instead, just come up with what is easy to write using loops, and then hide the loops by using LINQ:
static class DateTimeExtensions {
private static IEnumerable<DateTime> DaysOfMonth(int year, int month) {
return Enumerable.Range(1, DateTime.DaysInMonth(year, month))
.Select(day => new DateTime(year, month, day));
}
public static DateTime NthDayOfMonthFrom(
this HashSet<DayOfWeek> daysOfWeek,
int year,
int month,
int nth
) {
return
DateTimeExtensions.DaysOfMonth(year, month)
.Where(
date => daysOfWeek.Contains(date.DayOfWeek)
)
.Skip(nth - 1)
.First();
}
}
Usage:
var daysOfWeek = new HashSet<DayOfWeek> {
DayOfWeek.Monday, DayOfWeek.Wednesday, DayOfWeek.Friday
};
DateTime date = daysOfWeek.NthDayOfMonthFrom(2011, 6, 3));
Assert.Equal(date, new DateTime(2011, 6, 6));
Similarly, using Reverse, you can easily write NthLastDayOfWeekFrom. I'll leave it to you to come up with a better name.
I think I'm accepting Jason's answer because I like his solution, however here's what I had come up with before I saw his post. I thought I'd post it because I like the idea of not looping thorough every date, especially if N is a higher value and I'd like to get a date outside the current month.
Instead of looping through all days, I fast-forwarded (or rewind if I'm going backwards) to the closest week, then looped through the next couple of days to find the Nth instance the weekdays specified.
public static DateTime? GetNthWeekDayInMonth(DateTime month, int nth, List<DayOfWeek> weekdays)
{
var multiplier = (nth < 0 ? -1 : 1);
var day = new DateTime(month.Year, month.Month, 1);
// If we're looking backwards, start at end of month
if (multiplier == -1) day = day.AddMonths(1).AddDays(-1);
var dayCount = weekdays.Count;
if (dayCount == 0)
return null;
// Fast forward (or rewind) a few days to appropriate week
var weeks = ((nth - (1 * multiplier)) / dayCount);
day = day.AddDays(weeks * 7);
// Current Nth value
var currentNth = (1 * multiplier) + (weeks * dayCount);
// Loop through next week looking for Nth value
for (var x = 0; x <= 7; x++)
{
if (weekdays.Contains(day.DayOfWeek))
{
if (currentNth == nth)
{
// Verify the nth instance is still in the current month
if (day.Month == month.Month)
return day;
else
return null;
}
currentNth += multiplier;
}
day = day.AddDays(multiplier);
}
return null;
}