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!
Related
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
I have a list of Peak time intervals like
7:00:00 to 7:59:59
8:00:00 to 8:59:59
9:00:00 to 9:59:59
10:00:00 to 10:59:59
16:00:00 to 16:59:59
17:00:00 to 17:59:59
18:00:00 to 18:59:59
19:00:00 to 19:59:59
Now I want to get the list of off peak time intervals or off peak time atleast which does not fall in the above mentioned time intervals.
I am trying to do this with linq query but unable to do so, my entire code is in c#. Can anyone of you help me to get this done?
Thanks in advance...
Since you haven't mentioned how you are representing the Date Range, am assuming that you have a class in place, which has a Begin and End property as follows.
public class DateRange
{
public DateTime Begin{get;set;}
public DateTime End{get;set;}
}
And then your PeakTime Intervals are represented as
var today = DateTime.Now;
var excludeList = new List<DateRange>()
{
new DateRange
{
Begin = new DateTime(today.Year,today.Month,today.Day,3,0,0),
End = new DateTime(today.Year,today.Month,today.Day,3,59,59),
},
new DateRange
{
Begin = new DateTime(today.Year,today.Month,today.Day,7,0,0),
End = new DateTime(today.Year,today.Month,today.Day,7,59,59),
},
};
You can now generate the Time Range for the day using
var result = Enumerable.Range(0,24).Select(x=>
new DateRange{
Begin = new DateTime(today.Year,today.Month,today.Day,x,0,0),
End = new DateTime(today.Year,today.Month,today.Day,x,59,59)
}).Where(x=> !excludeList.Select(c=>c.Begin).ToList().Contains(x.Begin)
&& !excludeList.Select(c=>c.End).ToList().Contains(x.End));
You want to sort your intervals, and rewrite any overlapping intervals to no longer overlap. Your sample data set suggests this is already done.
I wouldn't recommend using Linq for this, since you have to measure between all the items. I think it's more readable with a simple for loop:
Given a simple class Interval(TimeSpan Start, TimeSpan End), and the list of intervals you provided as List<Interval> intervals:
TimeSpan gapBetweenIntervals = TimeSpan.FromSeconds(1); // since all your sample intervals have 1 second gaps
List<Interval> complement = new List<Interval>(intervals.Count + 1);
complement.Add(new Interval(new TimeSpan(), intervals[0].Start - gapBetweenIntervals));
for (int i = 0; i < intervals.Count - 1; ++i)
{
complement.Add(new Interval(intervals[i].End + gapBetweenIntervals, intervals[i + 1].Start - gapBetweenIntervals));
}
complement.Add(new Interval(intervals[intervals.Count - 1].End + gapBetweenIntervals, new TimeSpan(24, 0, 0) - gapBetweenIntervals));
complement.RemoveAll(i => i.Start > i.End);
This gives the list:
00:00:00 to 06:59:59
12:00:00 to 15:59:59
20:00:00 to 23:59:59
Just for fun, I did rewrite this with Linq, though I'm not sure I'd recommend using this:
var lowerBounds = new[] { new TimeSpan() }.Concat(intervals.Select(i => i.End + gapBetweenIntervals));
var upperBounds = intervals.Select(i => i.Start - gapBetweenIntervals).Concat(new[] { new TimeSpan(24, 0, 0) - gapBetweenIntervals });
lowerBounds.Zip(upperBounds, (l, u) => new Interval(l, u)).Where(i => i.Start < i.End);
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 have a requirement which I'm getting a little confused about. I started using NodaTime which I think is the best way to go.
I have two users, User1 and User2 both in two different timezones. They are available to meet between 2pm and 5pm for example, in their local timezones. If User2 has an offset of +2 hours from User1, then the overlap is just 1 hour. What I want to get the number of hours overlap (the actual time for User1 and User2 would be a bonus.)
All I have got so far is:
var user1TimeZone = DateTimeZoneProviders.Tzdb.GetZoneOrNull(user1timezone);
var user2TimeZone = DateTimeZoneProviders.Tzdb.GetZoneOrNull(user2timeZone);
Any thoughts on how I should even start tackling this problem?
Thanks,
Firstly, be aware that it could change each day: don't treat a time zone as a fixed offset.
Secondly, be aware that the local time specified (for each of start/end) may not even happen, or may happen twice. Work out how you want to handle ambiguous and skipped times.
For any particular day, I would just convert they users' start/end times to Instant (via ZonedDateTime) and then you can find the overlap. This does assume that any overlap happens on the same day, however... that isn't the case in reality. I'm having a meeting soon where one of the attendees is in New Zealand - it's March 14th here, but March 15th there. Accounting for that is rather trickier...
Here's code for the relatively simple case though:
using NodaTime;
using System;
class Test
{
static void Main()
{
// My availability: 4pm-7pm in London
var jon = new Availability(
DateTimeZoneProviders.Tzdb["Europe/London"],
new LocalTime(16, 0, 0),
new LocalTime(19, 0, 0));
// My friend Richard's availability: 12pm-4pm in New York
var richard = new Availability(
DateTimeZoneProviders.Tzdb["America/New_York"],
new LocalTime(12, 0, 0),
new LocalTime(16, 0, 0));
// Let's look through all of March 2017...
var startDate = new LocalDate(2017, 3, 1);
var endDate = new LocalDate(2017, 4, 1);
for (LocalDate date = startDate; date < endDate; date = date.PlusDays(1))
{
var overlap = GetAvailableOverlap(date, jon, richard);
Console.WriteLine($"{date:yyyy-MM-dd}: {overlap:HH:mm}");
}
}
static Duration GetAvailableOverlap(
LocalDate date,
Availability avail1,
Availability avail2)
{
// TODO: Check that the rules of InZoneLeniently are what you want.
// Be careful, as you could end up with an end before a start...
var start1 = (date + avail1.Start).InZoneLeniently(avail1.Zone);
var end1 = (date + avail1.End).InZoneLeniently(avail1.Zone);
var start2 = (date + avail2.Start).InZoneLeniently(avail2.Zone);
var end2 = (date + avail2.End).InZoneLeniently(avail2.Zone);
var latestStart = Instant.Max(start1.ToInstant(), start2.ToInstant());
var earliestEnd = Instant.Min(end1.ToInstant(), end2.ToInstant());
// Never return a negative duration... return zero of there's no overlap.
// Noda Time should have Duration.Max really...
var overlap = earliestEnd - latestStart;
return overlap < Duration.Zero ? Duration.Zero : overlap;
}
}
public sealed class Availability
{
public DateTimeZone Zone { get; }
public LocalTime Start { get; }
public LocalTime End { get; }
public Availability(DateTimeZone zone, LocalTime start, LocalTime end)
{
Zone = zone;
Start = start;
End = end;
}
}
If you have a server where you do that, you have to send UTC and then compare it. When you get the time on the client side you have to convert it into local. It means, that when first user wants to arrange a meeting, he sends his time into UTC to server, then when second user gets this time, he will convert it into his local time.
// First user sends UTC.
DateTime firstUserTime = DateTime.UtcNow;
// Second user gets time in his time zone.
DateTime secondUserTime = firstUserTime.ToLocalTime();
I am wondering if i can get the date of every alternate friday starting with 13th of April, 2012 to give it as a parameter to a stored procedure using c#, asp.net?
It should also be most recently passed date. Thank you!
Just set a DateTime with the date you want to start at, and then keep adding 14 days:
So to get every other Friday after 4/13 until the end of the year:
DateTime dt = new DateTime(2012, 04, 13);
while (dt.Year == 2012)
{
Console.WriteLine(dt.ToString());
dt = dt.AddDays(14);
}
More info after comment:
If you want the most recent alternate Friday since 2012/04/13, you can compute the number of days between now and 2012/04/13, take the remainder of that divided by 14, and subtract that many days from today's date:
DateTime baseDate = new DateTime(2012, 04, 13);
DateTime today = DateTime.Today;
int days = (int)(today - baseDate).TotalDays;
int rem = days % 14;
DateTime mostRecentAlternateFriday = today.AddDays(-rem);
You can easily make a generator method that would give you the set of fridays:
public IEnumerable<DateTime> GetAlternatingFridaysStartingFrom(DateTime startDate)
{
DateTime tempDate = new DateTime(startDate.year, startDate.Month, startDate.Day);
if(tempDate.DayOfWeek != DayOfWeek.Friday)
{
// Math may be off, do some testing
tempDate = tempDate.AddDays((7 - ((int)DayOfWeek.Friday - (int)tempDate.DayOfWeek) % 7);
}
while(true)
{
yield return tempDate;
tempDate = tempDate.AddDays(14);
}
}
Then, simply use some LINQ to determine how much you want:
var numberOfFridays = GetAlternatingFridaysStartingFrom(DateTime.Today).Take(10);
Why do you need a stored proc?
If you have a date that is Friday, why not just use AddDays(14) in a loop?
If you want to find the nearest Friday from a start date, just use this:
while(date.DayOfWeek != DayOfWeek.Friday)
{
date.AddDays(1);
}
Then use the 14 day loop to get every other Friday.
You can create simple method that will enumerate them like so:
public static IEnumerable<DateTime> GetAlternatingWeekDay(DateTime startingDate)
{
for (int i = 1; ; i++)
{
yield return startingDate.AddDays(14*i);
}
}
Which you can call like this:
DateTime startingDate = DateTime.Parse("2012-04-13");
foreach (var date in GetAlternatingWeekDay(startingDate).Take(10))
{
Console.WriteLine(date.ToString("R"));
}
Alternately, if you need to know the date for a given number of weeks out, you could use code like this:
DateTime date = DateTime.Parse("2012-04-13").AddDays(7 * numberOfWeeks);