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.
Related
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.
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.
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.
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);
If I try:
DateTime.Now.Subtract(DateTime.UtcNow)
I would expect the result to be very close to zero. But it's not, instead it's the time zone difference, in my case, -4 hours. There is a .Kind -- the DateTime KNOWS the timezones are different. Why doesn't it track this for me? Is there a flavor of Subtract that DOES use Kind correctly?
(For reference, a good rundown of what each one outputs can be seen at: https://stackoverflow.com/a/3229429/237091)
Eh? The Kind property does not alter date math. It is only used by time zone methods.
You get exactly the result I would expect you to get. Not sure I understand why you were expecting zero.
There is a .Kind -- the DateTime KNOWS the timezones are different. Why doesn't it track this for me?
Because DateTime is fundamentally broken (and there's more...). IMO it should complain if you try to subtract a value of one kind from another. But no, it just uses the uninterpreted date/time in each value. Very few operations actually take any notice of the Kind, unfortunately. (If you use TimeZoneInfo, those operations do take notice of it.)
Kind was hacked into .NET 2.0; before then a DateTime value didn't even know what kind it was - if you used:
dt = dt.ToLocalTime().ToLocalTime().ToLocalTime();
it would apply the same offset change several times. The BCL team found a couple of spare bits in the binary representation, and used it for Kind.
Basically, I feel your pain. Personally I would prefer it if operations like this threw an exception - subtracting a UTC DateTime from a local DateTime or vice versa makes little sense, IMO.
As an entirely biased plug, you could use Noda Time which separates the ideas of Instant, LocalDate, LocalTime, LocalDateTime, OffsetDateTime and ZonedDateTime, and doesn't let you perform non-sensical arithmetic. Our aim is to provide a saner API than the BCL one. That doesn't necessarily mean we've succeeded, of course :)
Each DateTime object represents a local time (as opposed to a UTC time plus a time zone offset). Even if the Kind property equals UTC, it's just storing the local time at the zero time zone. If it were not a local time, there would be no reason for the UtcNow property.
DateTime does not even store the timezone. If Kind equals UTC, then at least you know it's timezone is zero, but if Kind is local or unspecified, there is no way of knowing the timezone (the Kind property equals Unspecified by default).
Therefore, the Subtract method cannot incorporate the timezone into its calculation because the timezone is unknown.