.Net - Time of the day - c#

I am working on an application that needs to set rules for periods of time. The company has different branches, each branch can set its own rules (i.e a branch starts work at 8.30 am, ends work at 17.30 pm, with 30 minutes pause for lunch; another branch start at 9.00, ends at 19.00 with 1 hour pause...)
So I need to define a class (let's call it WorkingDayDefinition for the moment) where start and end are not actually a DateTime, because they are not referred to any specific day in particular.
At the moment the only option I see in C# is using Timespan for setting a duration from the beginning of the day, so that 8.30 pm would be TimeSpan(8,30,0) to be added to the Day part of whichever day.
Is this a best practice in C#?
I searched for third parties libraries that could help me, but so far my best bet is this one:
http://www.codeproject.com/Articles/168662/Time-Period-Library-for-NET
that is not strictly what I need

You could use Noda Time. It provides a LocalTime (see here):
LocalTime is an immutable struct representing a time of day, with no reference to a particular calendar, time zone or date.
For 8.30 you would do something like:
LocalTime openingAt = new LocalTime(8, 30);

To me TimeSpam seems very suitable for what you want. It holds an interval of time, sometimes between two events, but in your case between the start of the day and the time you start/finish work. There is no reason I can think of not to use it just because the name might suggest this wasn't the original intention of the class. Plus it already integrates well with DateTimes for any time calculations you need to do later on down the road.

Related

Convert discrete non-UTC date/time parts to a UTC DateTimeOffset

When using Noda Time, is there a more direct way to go from a set of discrete non-UTC values (that is to say, separate year, month, day, hour, minute values) to a UTC DateTimeOffset variable? I'm doing the following in a loop and it seems a bit odd to create LocalDateTime then apply two different timezones in a row.
DateTimeOffset target =
new LocalDateTime(year, month, day, hour, minute)
.InZoneStrictly(dataTimeZone)
.WithZone(utcTimeZone)
.ToDateTimeOffset();
I realize I'm probably overthinking/micro-optimizing, but since I'm new to Noda Time I'm mostly asking in case there is a way with fewer steps or that is better in some way. (I'm aware InZoneStrictly can throw exceptions, this is actually related to a scheduling system, so I definitely don't want invalid or auto-adjusted results.)
Your implementation is fine, though you could use DateTimeZone.Utc instead of utcTimeZone.
Here's another implementation that will give the same results:
DateTimeOffset target =
new LocalDateTime(year, month, day, hour, minute)
.InZoneStrictly(dataTimeZone)
.ToInstant()
.ToDateTimeOffset();
You might consider whether you actually need DateTimeOffset or not. Unless you're calling to some other API that requires one, you could use an OffsetDateTime or just an Instant instead.
As for the part about InZoneStrictly, consider that a user might pass in a perfectly valid set of date and time values that just happen to be ambiguous within the given time zone because they represent a local time during a backward transition (either for DST or change in standard time). Do you really want to throw in such cases?
Also, consider that if you have logic that applies a daily recurrence for the same time on multiple days, eventually you could run into an invalid time during the gap of a forward transition (again either for DST or change in standard time).
In Noda Time 2.x, InZoneLeniently was updated to generally do the right thing in such scenarios (first occurrence of ambiguous values, skip ahead for invalid values). Scheduling was a primary use case in this decision.

Convert time data to local time in C#

I've read a few posts about similar subjects but nothing seems to answer this question. My database has the following information about a time
Day of the week (a number between 0-6)
Time (a number of milliseconds since midnight in the users local time)
UTC offset ( number of hours different to UTC )
DST Observed (boolean stating if DST is observed in that time zone)
This data represents opening hours. So there is a time for each day. I want to display that time in the users local time making the assumption that each day is in the future
int dayOffset = availability.Day - (int)now.DayOfWeek
if (dayOffset < 0)
dayOffset += 7;
I'm really struggling to get my head around time zones and handling when one time zone might be observing DST while another maybe DOES observe DST but hasn't yet.
My main issue at the moment is I think I need to create a DateTimeOffset object for the non-local time but I'm not sure how to do that as I don't know if DST is in effect or not.
I hope I'm making myself clear. It really is a mind-bending experience working with dates and time!
As indicated by other answers, the usual solution to handling DateTime across time zones would be to store UTC times.
However, considering that you are not referencing an absolute time at a specific date, but instead are referring to a time at an infinite number of days in a specific time zone; storing the time as an UTC time doesn't make sense anymore, since the UTC time (even if we discard the date) would be different depending on the date, due to DST.
The best way to store the time is fairly close to what you have done already.
Your problem is that the time zone information you are storing at the moment is ambiguous, as it does not refer to a specific time zone, but instead refers to properties of the time zone.
To solve this problem, simply store the time zone identifier instead of the UTC offset and DST boolean.
It is now possible for us to construct the DateTime object and convert it to any time zone by using the TimeZoneInfo class:
int dayOffset = availability.Day - (int)DateTime.Today.DayOfWeek;
if (dayOffset < 0)
{
dayOffset += 7;
}
var openingHourStart = DateTime
.SpecifyKind(DateTime.Today, DateTimeKind.Unspecified)
.AddDays(dayOffset)
.AddMilliseconds(availability.Time);
var sourceTimeZone = TimeZoneInfo.FindSystemTimeZoneById(availability.TimeZoneId);
var userTimeZone = TimeZoneInfo.Local;
var convertedOpeningHourStart = TimeZoneInfo.ConvertTime(openingHourStart,
sourceTimeZone,
userTimeZone);
Give a try to Quartz.NET.
It implements evaluation of CronExpressions, and even triggers to fire events at the given time. It can evaluate the next time an event will occur. This may help you out calculating the opening times.
Also, take a look at the cronmaker website: there you can understand the full potential of CronExpressions.
The CronExpressionDescriptor is a DotNET library for transforming CronExpressions into human readable strings.
Another library which I haven't tried yet is [HangFire].(https://www.hangfire.io/)
In this forum post you can find some discussion on how HangFire implements evaluation of RecurringJobs in local timezone with DST, which I believe is a solution for what you are looking for.
A comment to another answer made the problem a little bit more clear.
So, first and foremost, do store only UTC in your database. Really.
Now, since you are not interested in the actual dates, since you are storing working schedules that repeat weekly, the date only becomes relevant once you want to present your times - and when you put them in your database.
So let's first see how you get your times into your database correctly. I'm assuming a user will enter times in their own locale.
Make sure you first create a (localised) DateTime consisting of the current date and the given time (from the user), and transform that to a UTC DateTime (you can keep the current date, it doesn't matter):
var utcDateTime = DateTime.Now.Date
.AddHours(userHours)
.AddMinutes(userMinutes)
.ToUniversalTime();
Now when you are presenting these times to the user, simply go the other way:
var userDateTime = DateTime.Now.Date
.AddHours(utcDateTime.Hour)
.AddMinutes(utcDateTime.Minute)
.ToLocalTime();
And then you can use the userDateTime.Hour and .Minute for display purposes.
You should be leveraging DateTime.ToLocalTime() and TimeZoneInfo.ConvertTimeToUtc() in C# - see https://msdn.microsoft.com/en-us/library/system.datetime.tolocaltime(v=vs.110).aspx.
If you want to store only times that you're open from Monday to Sunday, fine. Have a simple data table to describe only the time for each day (0 = Sunday through 7 = Saturday -- this is .Net's DayOfWeek enumeration). Your lookup table might look like:
0 null
1 08:00:00
2 08:00:00
3 08:00:00
4 08:30:00
5 08:30:00
6 10:30:00
(Use whatever data type works for you--SQL Server 2008+ has a TIME data type, for example. Null can be used for Closed on that day--i.e., no open time.)
When it comes time to display YOUR time to any other user, use must create your UTC time on-the-fly at the moment you are displaying information to the local user.
Conyc provided one approach. My approach uses simple date/time strings. To use my approach, just store time values per day in your database. Then you can look up the open time for any given day. To express that time for another user in any locale, use this code to convert your time to UTC (you can substitute the "08:00:00 AM" string value with a string variable that you populated after looking up the open time in your database):
var StoreOpenTimeInUtc = TimeZoneInfo.ConvertTimeToUtc(Convert.ToDateTime("08:00:00 AM"));
To look up the open time in your database for a particular day in the future, you will need to concatenate the date to your time value, like this:
var StoreOpenTimeInUtc = TimeZoneInfo.ConvertTimeToUtc(Convert.ToDateTime("04/28/2018 08:00:00 AM"));
Once you have an accurate StoreOpenTimeInUtc variable, you can use that as the UTC value on someone else's machine who is anywhere else on planet earth. To convert that UTC value to their local time, use the .NET ToLocalTime() method:
var OpenTimeForLocalUser = StoreOpenTimeInUtc.ToLocalTime();
Note that this approach requires you to store only the open times as shown above. You don't have to worry about dates, local offsets from UTC, or anything else. Just leverage ConvertTimeToUtc() and ToLocalTime() as shown.

How do I accurately represent a Date Range in NodaTime?

Goals
I have a list of LocalDate items that represent sets of start dates and end dates.
I would like to be able to store date ranges, so that I can perform operations on them as a set of overlapping or distinct ranges, etc.
Questions
Does NodaTime provide some sort of DateRange construct that I've missed in the docs?
Am I thinking about this wrong? Is there a more natural / preferred way to accomplish this that NodaTime already allows for?
Am I setting myself up for trouble by attempting to think about a date range using a LocalDate for a start and an end date?
I'm completely new to NodaTime and assuming that this is a conceptual misunderstanding on my part.
Update: I noticed a similar question on the subject from 2009, but that seems to refer to another utilies class; I'm assuming that since then NodaTime may have evolved to accomodate this situation.
Noda Time provides an Interval type for a range of Instant values, but it doesn't provide range types for the other types. Part of the reason for this is the nuance of how ranges are used for different types.
If I give you a range of instants, it is always treated as a half open interval. The start value is included, but the end value is excluded. Humans do this naturally any time we provide a time value, such as when I say an event runs from 1:00 to 2:00, clearly I mean that the event is over at 2:00, so 2:00 is excluded.
But with whole calendar date ranges, typically the end dates are inclusive. To represent the entire month of January (as a range of LocalDate values), I would probably say Jan 1st through Jan 31st, and I am including the last day in its entirety.
We could probably add some additional range types to enforce these things, but we would need to think about how much value there is in having them in the API when you could probably just create them as needed. I'm not saying I'm for or against it either way, but that's probably something to be debated on the Noda Time user group.
To answer your specific questions:
No, there is no predefined range class for local dates.
The only other thing to consider is that calendar math is usually done via the Period class. For example, to determine how many days there are between two calendar dates:
LocalDate ld1 = new LocalDate(2012, 1, 1);
LocalDate ld2 = new LocalDate(2013, 12, 25);
Period period = Period.Between(ld1, ld2, PeriodUnits.Days);
long days = period.Days;
No, there's nothing wrong with creating a range class of local dates, there just might not be a whole lot of advantage. You may do just as well by having two properties, StartDate and EndDate, on your own classes. Just be careful about the inclusiveness of the end dates, vs the exclusiveness you'd see with an interval or time range.
And lastly, you said:
... so that I can perform operations on them as a set of overlapping or distinct ranges, etc.
You're probably looking for operations like intersection, union, calculating gaps, sorting, etc. These and more are defined by the Time Period Library, but Noda Time doesn't currently have anything like that. If one was to create it, it should probably be in a companion library ("NodaTime.Ranges", perhaps?). Likely it wouldn't be desired to pull it into the core, but you never know...
If you do end up using that Time Period Library, please make sure you recognize that it works with DateTime only, and is completely oblivious to DateTimeKind. So in order to be productive with it, you should probably make sure you are only working with UTC values, or "unspecified" calendar dates, and try not to ask it things like "how many hours are in a day" because it will get it wrong for days with daylight saving time transitions.

Correctly handling opening times with NodaTime

I'm currently writing a fairly simple app handling opening/closing times of businesses and running into serious difficulties trying to figure out how to properly store the info.
Most of our critical functionality is heavily dependent on getting times absolutely perfect, so obviously I want to get things done the best way possible to start off with!
Additionally, the data will be inputted by users, so if the underlying representation is slightly more complex (e.g. using TimeSpans to account for opening past midnight), this needs to be invisible to the user.
I need to store firstly, the business's opening hours, by day of week, with a timezone associated with them, e.g:
- M: 1000 - 2330
- T: 1000 - 0030
- W: 1900 - 0300
- Th: 2000 - 0300
- F: 2000 - 0800
- Sa: 1000 - 0500
- Su: 1000 - 2300
I'm currently thinking that the best way to store this is using a class like this:
public class OpeningHours
{
ZonedDateTime OpeningTime { get; set; }
Period durationOpen { get; set; }
// TODO: add a method to calculate ClosingTime as a ZonedDateTime
}
However, there's 2 main complications here:
I don't want to store the Year, Month, or Date part of the ZonedDateTime - I just care about the DayOfWeek.
Sure, I could just store each value as the first Monday/Tuesday etc after Jan 1 1970, but this seems hacky and pretty much plain wrong - as the author of NodaTime, very correctly, explains here when talking about the limitations of the BCL DateTime implementation. I also have a feeling this would probably end up with weird quirky bugs if later on we try and do any arithmetic with the dates.
The user is going to have to input the ClosingTime anyway. Client side I suppose I could do something simple like always assume the ClosingTime is the next day if it's before the OpeningTime, but again, it's not perfect, and also doesn't account for places that might be open for more than 24 hours (e.g. supermarkets)
Another thing I've considered is using a table with hours/days and letting people highlight the hours of the week to pick opening times, but you still run into the same problem with only wanting to store the DayOfWeek part of the OpeningTime.
Any suggestions would be appreciated, spending the last 6 hours reading about the hilariously silly ways we humans represent time has burnt me out a bit!
I would strongly consider using LocalTime instead of ZonedDateTime, for a couple of reasons:
You're not trying to represent a single instant in time; these are naturally recurring patterns (there's no associated date)
You're not trying to cope with the situation where the store is in different time zones for different opening hours; you probably want to associate a time zone with each store once, and then you can apply that time zone whenever you want
So I would have something like this (showing just the data members; how you sort out the behaviour is a separate matter):
public class StoreOpeningPeriod
{
IsoDayOfWeek openingDayOfWeek;
LocalTime openingTime;
LocalTime closingTime;
}
Note that this exactly follows your original data as you've shown it, which is always a good sign - you're neither adding nor losing information, and it's presumably in a convenient form.
If the closing time is earlier than the opening time, it's assumed that this crossed midnight - you might want to add a confirmation box for the user if this is relatively uncommon, but it's certainly easy to spot and handle in code.

Time only in c#

In C#
DateTime dateAndTime = DateTime.Now;
gives the current date and time. I need only the current time. If I use string, it is possible like:
string time=DateTime.Now.ToString("hh:mm:ss");
Is it possible to get only the time portion of DateTime without going through a string?
You can get the current time in a TimeSpan by accessing TimeOfDay, like this:
TimeSpan time = DateTime.Now.TimeOfDay;
Now time will represent the amount of time that passed since midnight.
There is no out-of-the-box type that holds just a time. You could use TimeSpan, and even the .NET framework does at parts (for example DateTime.TimeOfDay), however I think that TimeSpan is really serving a different purpose.
TimeSpan, to quote MSDN, simply "measures a time interval". That could easily be longer than the hours, minutes, seconds, etc. that make up a day (i.e. 24 hours in sum). And indeed the TimeSpan structure provides for that, having properties like Days, for example.
Thus, I think TimeSpan is not a very good fit to represent the time of day (which I assume you mean when saying "current time").
That brings us to another problem. What exactly is the "current time"? As I said, I assume that you mean the current time as in "the current time of day". But current time could also mean the current (elapsed) time since some particular point in time in the past.
Granted, all that can get pretty theoretic or even rhetoric and does not really help you.
I would just use DateTime. Where you actually care about the "time" value, just only use the time portion (like you have shown, you known about, with your ToString example). Although, depending on what you need the time for, you might resort to the DateTime.TimeOfDay property instead of simply formatting it as a string (unless of course that is what you need).
Finally, you could also resort to third party libraries like Node Time, that do provide types for time only (like LocalTime).
string time = string.Format("{0:hh-mm-ss-tt}", DateTime.Now);

Categories