8

I have an object that has properties currently as DateTime.

The object is marked as valid within a time frame. The default being 00:00:00 to 23:59:59

The user enters the value in the UI and the property is set via:

new DateTime(DateTime.Now.Year, 
             DateTime.Now.Month, 
             DateTime.Now.Day, 
             model.Hours, 
             model.Minutes, 
             model.Seconds)

This is then converted to UTC when it hits the database.

Today's date is 29th August 2013. If a colleague in India runs this program it will store the data in the database as 28th August 2013 18:30:00 as they are 5.5 hours ahead of UTC so 29th August 2013 00:00:00 becomes yesterday.

When the logic tries to determine if the object is valid the logic is:

if (DateTime.UtcNow.TimeOfDay > model.MyPropertyFromDB.TimeOfDay)

We are trying to determine if the current time is within a range of 00:00:00 and 23:59:59

This fails as 14:00 (current time) is not greater than 18:30

What would be the best approach to compare just times?

Would storing the values as DateTimeOffSet help, is using ToLocal() ok?

Other considerations are that a user in India is using the app which is hosted in the UK so it needs to be timezone aware.

Thanks

GregC
  • 7,737
  • 2
  • 53
  • 67
Jon
  • 38,814
  • 81
  • 233
  • 382
  • 6
    did you consider saving date as utc in the db? – Konstantin Aug 29 '13 at 13:19
  • [Does this sort it out for you ?][1] [1]: http://stackoverflow.com/questions/7577389/how-to-elegantly-deal-with-timezones – SteveB Aug 29 '13 at 13:21
  • Maybe this can help you http://stackoverflow.com/a/14268167/1587864 – margabit Aug 29 '13 at 13:23
  • 1
    @kostyan I think OP said he did "This then then converted to UTC when it hits the database." – oleksii Aug 29 '13 at 13:25
  • Why not just compare the full dates `if (DateTime.UtcNow > model.MyPropertyFromDB)`? – Dan Bechard Aug 29 '13 at 13:36
  • because we want to only compare time – Jon Aug 29 '13 at 13:37
  • I don't get it.. what's wrong with the code you posted? Please restate your problem / question more clearly. – Dan Bechard Aug 29 '13 at 13:38
  • Is _“[the date] is then converted to UTC when it hits the database”_ and _“If a colleague in India runs this program it will store the data in the database as 28th August 2012 18:30:00”_ not a contradiction? If you are using UTC it does not matter if the date is entered in the UK or in India. UTC time is the same around the world. Also, the time frame you check for has an implicit time zone (UTC I presume). Using UTC across the board should work without any problems so it seems there is some local time zone issue not clearly presented by your question. – Martin Liversage Aug 29 '13 at 13:50
  • the issue is i'm not comparing whole dates, just times, so 00:00:00 becomes 18:30 and then current time is 14:00 and what I want is is current time greater than 00:00:00 and less than 23:59:59 but as it converts to 18:30 it will fail – Jon Aug 29 '13 at 13:53
  • 4
    Aside from anything else, you shouldn't be calling `DateTime.Now` three times - if that trips over a boundary (day or hour) you'll get very odd results. You should call it *once*, and then use the result three times. (Ditto if you use `UtcNow`, which would almost certainly be a better idea.) – Jon Skeet Aug 29 '13 at 14:48

3 Answers3

2

Like others, I'm still unclear on exactly what you are wanting. But clearly, you shouldn't do this:

new DateTime(DateTime.Now.Year, 
             DateTime.Now.Month, 
             DateTime.Now.Day, 
             model.Hours, 
             model.Minutes, 
             model.Seconds)

That would be much better as:

DateTime.Today.Add(new TimeSpan(model.Hours, model.Minutes, model.Seconds))

But why are you doing this to begin with? Either of these would give you back the local date. I assume this is going to run on a server, so do you really want the time zone of the server to influence this result? Probably not. Please read: The Case Against DateTime.Now.

If you wanted the UTC date, you could do this:

DateTime.UtcNow.Date.Add(new TimeSpan(model.Hours, model.Minutes, model.Seconds))

That would at least be universally the same regardless of your server's time zone. But still, I don't think this is what you are after.

What's not clear is why is the user only entering the time while you are assigning the current date. If the date is relevant, then shouldn't the user enter it and it would be part of your model?

If the date is not relevant, then why are you storing it? You can use a TimeSpan type for the time value internally. You didn't say what your database is, but let's just guess that it is SQL Server, in which case you could use the time type on the field in the table.

I suppose it's possible that the date is relevant, but you want to control it, while the user takes control of providing the time. If that's the case, then you must know the time zone of the user (or the time zone of whatever the context is if it's not the user). Assuming you had a Windows time zone identifier (see the timezone tag wiki), then you could do something like this:

var tz = TimeZoneInfo.FindSystemTimeZoneById(theTimeZoneId);
var local = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tz);
var dt = local.Date.Add(new TimeSpan(model.Hours, model.Minutes, model.Seconds));

If you don't have the time zone information, then this wouldn't be possible to solve.

As general advice, you might want to try using Noda Time instead of the built-in stuff. It's much better at helping you figure out this sort of thing. From the main page:

Noda Time is an alternative date and time API for .NET. It helps you to think about your data more clearly, and express operations on that data more precisely.

That appears to be directly the problem you are having here. If you want to clarify some of the questions I asked, I'd be happy to edit my answer and show you exactly how to do this with Noda Time.

Why your question is confusing

We are trying to determine if the current time is within a range of 00:00:00 and 23:59:59

All times are within that range. Well, maybe a value like 23:59:59.1 would be outside of it, but you aren't collecting fractional seconds in your model, so that's irrelevant. But why would you need to validate that? Maybe you are just trying to avoid numbers that aren't valid times at all? Like 99:99:99?

This fails as 14:00 (current time) is not greater than 18:30

Wait - you didn't say anything about comparing one time greater than another. 14:00 and 18:30 are both still in the range you specified.

What would be the best approach to compare just times?

Hard to answer. Are they both UTC times? Is one UTC and one is local? Are they both local? Do you know the time zone of the local times? Are you prepared to deal with ambiguous or invalid local times do to daylight saving time transitions?

Would storing the values as DateTimeOffSet help?

Perhaps, but you haven't given me enough information. It would help only if the date portion is relevant and the you get the correct offsets.

is using ToLocal() ok?

I would argue that no, it's not ok. Local in this context will give you the time zone of the server, which you probably don't want to introduce into your business logic.

Community
  • 1
  • 1
Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
0

So if I understand this correctly you have a time saved in UTC in the database and you are trying to determine whether it falls within a particular time frame? I'm not sure if you want the time frame in local time or UTC so here are both:

DateTime dbTime = model.MyPropertyFromDB;

TimeSpan minTime = new TimeSpan(0, 0, 0);
TimeSpan maxTime = new TimeSpan(23, 59, 59);

if (dbTime.TimeOfDay > minTime && dbTime.TimeOfDay < maxTime)
{
    //Within time range (UTC)
}

if (dbTime.ToLocalTime().TimeOfDay > minTime && dbTime.ToLocalTime().TimeOfDay < maxTime)
{
    //Within time range (local)
}

Edit: If you want to compare Now to a start and end time from an object in database:

TimeSpan now = DateTime.UtcNow.TimeOfDay;
TimeSpan startDate = model.startDate.TimeOfDay;
TimeSpan endDate = model.endDate.TimeOfDay;

if (now > startDate && now < endDate)
{
    //Within time range (UTC)
}
Dan Bechard
  • 5,104
  • 3
  • 34
  • 51
  • Yes but the db values are the min/max values – Jon Aug 29 '13 at 14:02
  • Is current time of day within range of db values – Jon Aug 29 '13 at 14:07
  • yup but model.startDate.TimeOfDay; is 18:30 as the UTC conversion to 00:00:00 to 18:30:00 and now is 14:00:00 so its not greater than and fails – Jon Aug 29 '13 at 14:34
  • You said the values are stored as UTC in the database. In that case, model.startDate.TimeOfDay is 00:00:00 UTC. There is no conversion to 18:30:00.. – Dan Bechard Sep 04 '13 at 13:39
  • If you still haven't found the answer you are looking for please edit the original post or, even better, create a new question stating: What EXACTLY is the problem you are trying to solve? How EXACTLY are you trying to solve it? What EXACTLY is the data you are working with? – Dan Bechard Sep 04 '13 at 13:42
0

I would say that the methodology being used here is fundamentally flawed and that you need to take a different approach.

new DateTime(DateTime.Now.Year,   // Server date
             DateTime.Now.Month, 
             DateTime.Now.Day, 
             model.Hours,         // Local time
             model.Minutes, 
             model.Seconds)

I can't see a way of 'normalising' the input in this way, unless you have a way of reliably knowing exactly which timezone a user is in. Simply, there's no easy way to turn a date built in this way into UTC.

My first question to you is, how is the model being passed from client to server? If you're using javascript/ajax then the solution should be fairly straightforward to solve by constructing the datetime object on the client (which will include their timezone data) and then rely on the browser to convert it to UTC for transit.

If you are using Razor\MVC then you can achieve a similar thing with forms encoding, except that you will need to call ToUTC on the server as the browser won't automatically fix the date for you for this media format.

Both methods require that you build a full datetime object on the client and then submit it, rather than trying to build it from seconds, minutes, hours on the server. You don't need to expose all this to the client of course, as long as the datetime is fully formed at the point of submission.

Once you've got a nice UTC datetime, you can extract just the time if you don't need the rest of it.

Hope this helps.

Pete

beyond-code
  • 1,423
  • 1
  • 12
  • 20