Why can't I add months to OffsetDateTime? - c#

I'm using NodaTime to manage dates and time zones in a .Net Core WebApi. One of the type in the library is OffsetDateTime, which is very similar to DateTimeOffset from the .Net framework. I use it everywhere to manipulate dates in an explicit and transparent way since dates are sometimes into system time zone and user time zone.
I need to add a month to a certain date at a certain point, but I can not add a month to a OffsetDateTime object, all I can do is add up until hours or a type called Duration which is calendar-independent. If it was the type Instant I'd understand since Instant represents a point in time in a really abstract way, but not OffsetDateTime. OffsetDateTime even has a "Calendar" property which really shows it's bound to a calendar system which should allow you to do arithmetics like what I want to do, without having to go through type conversions etc.
On top of that, DateTimeOffset (from the .net framework) allows you to add months, but I want to be consistent and use the same types everywhere.
Long story short, I can not do :
public OffsetDateTime GetPreviousMonth(OffsetDateTime input)
{
return input.AddMonths(-1)
}
I can only do:
offsetDateTime.PlusHours(15)
offsetDateTime.PlusMinutes(3000)
offsetDateTime.Minus(Duration.FromMinutes(60))
offsetDateTime.Minus(Duration.FromHours(1))
Any idea how I can solve this without going through type conversions? Maybe I overlooked something in the documentation, but I don't think so.

An OffsetDateTime represents a local date time with an offset from UTC, and an Instant.
It is not, however, bound to a TimeZone.
For this reason, you can add a "fixed" amount from it like seconds, minutes and hours because those are not TimeZone dependents.
You cannot subtract a month from it, as it can't know if there was a daylight transition during the past month.
I know you asked for a solution without type conversion, but in reality you can't. To handle this correctly, you must convert it to a ZonedDateTime with the correct time zone. Any solution without specifying the TimeZone you may eventually hit a case where the result is wrong.

You can use OffsetDateTime.With, which lets you provide a LocalDate adjuster. You can operate (Plus, Minus, ...) on a LocalDate with a Period which lets you specify time spans in terms of months:
public OffsetDateTime GetPreviousMonth(OffsetDateTime input)
{
return input.With((LocalDate ld) => ld.Minus(Period.FromMonths(1)));
}

You can use OffsetDateTime directly, but you should use some method like now().
Then you can add or minus D, M, Y, H, M, S and so on
OffsetDateTime.now().minusDays(5).
format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"))
OR
OffsetDateTime.now(ZoneOffset.UTC).plusMonths(month).
format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"))

Related

How to calculate time zone UTC offset for specified date and time with NodaTime (given daylight)?

I have Iana time zone Id and need to know the time zone offset for some DateTime.
As I understand, the following function returns the offset for DateTime.Now (or no daylight offset?).
TimeSpan GetTimeZoneOffset(string timeZoneId) =>
new DateTimeZoneCache(TzdbDateTimeZoneSource.Default)
.GetZoneOrNull(timeZoneId)
.GetUtcOffset(SystemClock.Instance.GetCurrentInstant())
.ToTimeSpan();
But I need
TimeSpan GetTimeZoneOffset(string timeZoneId, DateTime utcInstant)
How to implement it?
Fundamentally, you need DateTimeZone.GetUtcOffset, which accepts an Instant.
If the DateTime value always has a Kind of Utc, you can use Instant.FromDateTimeUtc. If it might have a different Kind, you'll need to work out more detailed requirements.
Next you need an IDateTimeZoneProvider to map the time zone ID into a DateTimeZone. That might be DateTimeZoneProviders.Tzdb, or it might be one you've injected somewhere for testability.
Once you've got the Instant and the DateTimeZone, you can call GetUtcOffset to get an Offset. You can convert that back to a TimeSpan, but I'd actually encourage you to avoid using DateTime and TimeSpan as far as possible in your app - if you can use the Noda Time types everywhere within your codebase and only convert between those and the BCL types at boundaries (e.g. database access) you'll find you need to do a lot less of this work.
But if you really need a TimeSpan, the method would look like this:
TimeSpan GetTimeZoneOffset(string timeZoneId, DateTime dateTimeUtc)
{
Instant instant = Instant.FromDateTimeUtc(dateTimeUtc);
DateTimeZone zone = DateTimeZoneProviders.Tzdb[timeZoneId];
Offset offset = zone.GetUtcOffset(instant);
return offset.ToTimeSpan();
}
Note that the IDateTimeZoneProvider[string] indexer will throw an exception if the time zone isn't found in that provider. If you want to handle this a different way, use IDateTimeZoneProvider.GetZoneOrNull() and check whether the result is null or not.
TimeSpan GetTimeZoneOffset(string timeZoneId, DateTime date) =>
new DateTimeZoneCache(TzdbDateTimeZoneSource.Default)
.GetZoneOrNull(timeZoneId)
.GetUtcOffset(Instant.FromDateTimeUtc(utcDay))
.ToTimeSpan();

Best practice for adding/subtracting from universal or local DateTime

I'm trying to add a wrapper around DateTime to include the time zone information. Here's what I have so far:
public struct DateTimeWithZone {
private readonly DateTime _utcDateTime;
private readonly TimeZoneInfo _timeZone;
public DateTimeWithZone(DateTime dateTime, TimeZoneInfo timeZone) {
_utcDateTime = TimeZoneInfo.ConvertTimeToUtc(DateTime.SpecifyKind(dateTime, DateTimeKind.Unspecified), timeZone);
_timeZone = timeZone;
}
public DateTime UniversalTime { get { return _utcDateTime; } }
public TimeZoneInfo TimeZone { get { return _timeZone; } }
public DateTime LocalTime { get { return TimeZoneInfo.ConvertTimeFromUtc(_utcDateTime, _timeZone); } }
public DateTimeWithZone AddDays(int numDays) {
return new DateTimeWithZone(TimeZoneInfo.ConvertTimeFromUtc(UniversalTime.AddDays(numDays), _timeZone), _timeZone);
}
public DateTimeWithZone AddDaysToLocal(int numDays) {
return new DateTimeWithZone(LocalTime.AddDays(numDays), _timeZone);
}
}
This has been adapted from an answer #Jon Skeet provided in an earlier question.
I am struggling with with adding/subtracting time due to problems with daylight saving time. According to the following it is best practice to add/subtract the universal time:
https://msdn.microsoft.com/en-us/library/ms973825.aspx#datetime_topic3b
The problem I have is that if I say:
var timeZone = TimeZoneInfo.FindSystemTimeZoneById("Romance Standard Time");
var date = new DateTimeWithZone(new DateTime(2003, 10, 26, 00, 00, 00), timeZone);
date.AddDays(1).LocalTime.ToString();
This will return 26/10/2003 23:00:00. As you can see the local time has lost an hour (due to daylight saving time ending) so if I was to display this, it would say it's the same day as the day it's just added a day to. However if i was to say:
date.AddDaysToLocal(1).LocalTime.ToString();
I would get back 27/10/2003 00:00:00 and the time is preserved. This looks correct to me but it goes against the best practice to add to the universal time.
I'd appreciate it if someone could help clarify what's the correct way to do this. Please note that I have looked at Noda Time and it's currently going to take too much work to convert to it, also I'd like a better understanding of the problem.
Both ways are correct (or incorrect) depending upon what you need to do.
I like to think of these as different types of computations:
Chronological computation.
Calendrical computation.
A chronological computation involves time arithmetic in units that are regular with respect to physical time. For example the addition of seconds, nanoseconds, hours or days.
A calendrical computation involves time arithmetic in units that humans find convenient, but which don't always have the same length of physical time. For example the addition of months or years (each of which have a varying number of days).
A calendrical computation is convenient when you want to add a coarse unit that does not necessarily have a fixed number of seconds in it, and yet you still want to preserve the finer field units in the date, such as days, hours, minutes and seconds.
In your local time computation, you add a day, and presuming a calendrical computation is what you intended, you preserve the local time of day, despite the fact that 1 day is not always 24 hours in the local calendar. Be aware that arithmetic in local time has the potential to result in a local time that has two mappings to UTC, or even zero mappings to UTC. So your code should be constructed such that you know this can never happen, or be able to detect when it does and react in whatever way is correct for your application (e.g. disambiguate an ambiguous mapping).
In your UTC time computation (a chronological computation), you always add 86400 seconds, and the local calendar can react however it may due to UTC offset changes (daylight saving related or otherwise). UTC offset changes can be as large as 24h, and so adding a chronological day may not even bump the local calendar day of the month by one. Chronological computations always have a result which has a unique UTC <-> local mapping (assuming the input has a unique mapping).
Both computations are useful. Both are commonly needed. Know which you need, and know how to use the API to compute whichever you need.
Just to add to Howard's great answer, understand that the "best practice" you refer to is about incrementing by an elapsed time. Indeed, if you wanted to add 24 hours, you'd do that in UTC and you'd find you'd end up on 23:00 due to there being an extra hour in that day.
I typically consider adding a day to be a calendrical computation (using Howard's terminology), and thus it doesn't matter how many hours there are on that day or not - you increment the day in local time.
You do then have to verify that the result is a valid time on that day, as it very well may have landed you on an invalid value, in the "gap" of a forward transition. You'll have to decide how to adjust. Likewise, when you convert to UTC, you should test for ambiguous time and adjust accordingly.
Understand that by not doing any adjusting on your own, you're relying on the default behavior of the TimeZoneInfo methods, which adjust backward during an ambiguous time (even though the usually desired behavior is to adjust forward), and that ConvertTimeFromUtc will throw an exception during an invalid time.
This is the reason why ZonedDateTime in Noda Time has the concept of "resolvers" to allow you to control this behavior more specifically. Your code is missing any similar concept.
I'll also add that while you say you've looked at Noda Time and it's too much work to convert to it - I'd encourage you to look again. One doesn't necessarily need to retrofit their entire application to use it. You can, but you can also just introduce it where it's needed. For example, you might want to use it internally in this DateTimeWithZone class, in order to force you down the right path.
One more thing - When you use SpecifyKind in your input, you're basically saying to ignore whatever the input kind is. Since you're designing general purpose code for reuse, you're inviting the potential for bugs. For example, I might pass in DateTime.UtcNow, and you're going to assume it's the timezone-based time. Noda Time avoids this problem by having separate types instead of a "kind". If you're going to continue to use DateTime, then you should evaluate the kind to apply an appropriate action. Just ignoring it is going to get you into trouble for sure.

What's the best way of handling begin and end times for an event in c# where the events are global

We're making an app which is going to list events across the globe. The events span multiple days.
We want to store the begin and end of the event.
Should I just use 2 DateTime properties and store it all as UTC?
Should we just store the 2 points in time as ticks?
Should we be using a TimeSpan?
Would NodaTime be good here?
Personally I would store everything as UTC time. Since .NET naturally supports converting dates to and from UTC, you don't have to worry about future compatibility. Most databases support storing dates in UTC format. I would also use TimeSpan for code readability when calculating time periods,but storing the datetime as ticks may give you a small performance benefit since you can do comparisons without creating DateTime objects. Depends on how many comparisons you do.
Two datetimes as UTC at rest. Then it will make sense to do operations when you have a start and no end since the event is underway at present. WCF will even automatically make them local times across the wire for you if you like (be sure to set the DateTime.Kind).
DateTimes are ticks internally, so storing them as ticks accomplishes little. They'll be ticks at rest in your DB.
Tuple TimeSpan <long, long, long>;
TimeSpan myTimeSpan = new TimeSpan<start, end, difference>;
You use dot notation to access items like;
long start = myTimeSpan.item1;
The Nth item would be accessed by; myTimeSpan.itemN;
From here if you need UTC time you can easily get it by doing something like;
DateTime startingUTC = new DateTime(myTimeSpan.item1, UTC);
This constructer; public DateTime(long ticks, DateTimeKind kind) will allow you to easily produce DateTime objects in UTC or local time when you need to display the data to the user. Otherwise, it's much easier and more efficient to work with ticks.

DateTime vs DateTimeOffset

What is the difference between a DateTime and a DateTimeOffset and when should one be used?
Currently, we have a standard way of dealing with .NET DateTimes in a TimeZone-aware way: Whenever we produce a DateTime we do it in UTC (e.g. using DateTime.UtcNow), and whenever we display one, we convert back from UTC to the user's local time.
That works fine, but I've been reading about DateTimeOffset and how it captures the local and UTC time in the object itself.
DateTimeOffset is a representation of instantaneous time (also known as absolute time). By that, I mean a moment in time that is universal for everyone (not accounting for leap seconds, or the relativistic effects of time dilation). Another way to represent instantaneous time is with a DateTime where .Kind is DateTimeKind.Utc.
This is distinct from calendar time (also known as civil time), which is a position on someone's calendar, and there are many different calendars all over the globe. We call these calendars time zones. Calendar time is represented by a DateTime where .Kind is DateTimeKind.Unspecified, or DateTimeKind.Local. And .Local is only meaningful in scenarios where you have an implied understanding of where the computer that is using the result is positioned. (For example, a user's workstation)
So then, why DateTimeOffset instead of a UTC DateTime? It's all about perspective. Let's use an analogy - we'll pretend to be photographers.
Imagine you are standing on a calendar timeline, pointing a camera at a person on the instantaneous timeline laid out in front of you. You line up your camera according to the rules of your timezone - which change periodically due to daylight saving time, or due to other changes to the legal definition of your time zone. (You don't have a steady hand, so your camera is shaky.)
The person standing in the photo would see the angle at which your camera came from. If others were taking pictures, they could be from different angles. This is what the Offset part of the DateTimeOffset represents.
So if you label your camera "Eastern Time", sometimes you are pointing from -5, and sometimes you are pointing from -4. There are cameras all over the world, all labeled different things, and all pointing at the same instantaneous timeline from different angles. Some of them are right next to (or on top of) each other, so just knowing the offset isn't enough to determine which timezone the time is related to.
And what about UTC? Well, it's the one camera out there that is guaranteed to have a steady hand. It's on a tripod, firmly anchored into the ground. It's not going anywhere. We call its angle of perspective the zero offset.
So - what does this analogy tell us? It provides some intuitive guidelines-
If you are representing time relative to some place in particular, represent it in calendar time with a DateTime. Just be sure you don't ever confuse one calendar with another. Unspecified should be your assumption. Local is only useful coming from DateTime.Now. For example, I might get DateTime.Now and save it in a database - but when I retrieve it, I have to assume that it is Unspecified. I can't rely that my local calendar is the same calendar that it was originally taken from.
If you must always be certain of the moment, make sure you are representing instantaneous time. Use DateTimeOffset to enforce it, or use UTC DateTime by convention.
If you need to track a moment of instantaneous time, but you want to also know "What time did the user think it was on their local calendar?" - then you must use a DateTimeOffset. This is very important for timekeeping systems, for example - both for technical and legal concerns.
If you ever need to modify a previously recorded DateTimeOffset - you don't have enough information in the offset alone to ensure that the new offset is still relevant for the user. You must also store a timezone identifier (think - I need the name of that camera so I can take a new picture even if the position has changed).
It should also be pointed out that Noda Time has a representation called ZonedDateTime for this, while the .Net base class library does not have anything similar. You would need to store both a DateTimeOffset and a TimeZoneInfo.Id value.
Occasionally, you will want to represent a calendar time that is local to "whomever is looking at it". For example, when defining what today means. Today is always midnight to midnight, but these represent a near-infinite number of overlapping ranges on the instantaneous timeline. (In practice we have a finite number of timezones, but you can express offsets down to the tick) So in these situations, make sure you understand how to either limit the "who's asking?" question down to a single time zone, or deal with translating them back to instantaneous time as appropriate.
Here are a few other little bits about DateTimeOffset that back up this analogy, and some tips for keeping it straight:
If you compare two DateTimeOffset values, they are first normalized to zero offset before comparing. In other words, 2012-01-01T00:00:00+00:00 and 2012-01-01T02:00:00+02:00 refer to the same instantaneous moment, and are therefore equivalent.
If you are doing any unit testing and need to be certain of the offset, test both the DateTimeOffset value, and the .Offset property separately.
There is a one-way implicit conversion built in to the .Net framework that lets you pass a DateTime into any DateTimeOffset parameter or variable. When doing so, the .Kind matters. If you pass a UTC kind, it will carry in with a zero offset, but if you pass either .Local or .Unspecified, it will assume to be local. The framework is basically saying, "Well, you asked me to convert calendar time to instantaneous time, but I have no idea where this came from, so I'm just going to use the local calendar." This is a huge gotcha if you load up an unspecified DateTime on a computer with a different timezone. (IMHO - that should throw an exception - but it doesn't.)
Shameless Plug:
Many people have shared with me that they find this analogy extremely valuable, so I included it in my Pluralsight course, Date and Time Fundamentals. You'll find a step-by-step walkthrough of the camera analogy in the second module, "Context Matters", in the clip titled "Calendar Time vs. Instantaneous Time".
From Microsoft:
These uses for DateTimeOffset values are much more common than those for DateTime values. As a result, DateTimeOffset should be considered the default date and time type for application development.
source: "Choosing Between DateTime, DateTimeOffset, TimeSpan, and TimeZoneInfo", MSDN
We use DateTimeOffset for nearly everything as our application deals with particular points in time (e.g. when a record was created/updated). As a side note, we use DATETIMEOFFSET in SQL Server 2008 as well.
I see DateTime as being useful when you want to deal with dates only, times only, or deal with either in a generic sense. For example, if you have an alarm that you want to go off every day at 7 am, you could store that in a DateTime utilizing a DateTimeKind of Unspecified because you want it to go off at 7am regardless of DST. But if you want to represent the history of alarm occurrences, you would use DateTimeOffset.
Use caution when using a mix of DateTimeOffset and DateTime especially when assigning and comparing between the types. Also, only compare DateTime instances that are the same DateTimeKind because DateTime ignores timezone offset when comparing.
DateTime is capable of storing only two distinct times, the local time and UTC. The Kind property indicates which.
DateTimeOffset expands on this by being able to store local times from anywhere in the world. It also stores the offset between that local time and UTC. Note how DateTime cannot do this unless you'd add an extra member to your class to store that UTC offset. Or only ever work with UTC. Which in itself is a fine idea btw.
This piece of code from Microsoft explains everything:
// Find difference between Date.Now and Date.UtcNow
date1 = DateTime.Now;
date2 = DateTime.UtcNow;
difference = date1 - date2;
Console.WriteLine("{0} - {1} = {2}", date1, date2, difference);
// Find difference between Now and UtcNow using DateTimeOffset
dateOffset1 = DateTimeOffset.Now;
dateOffset2 = DateTimeOffset.UtcNow;
difference = dateOffset1 - dateOffset2;
Console.WriteLine("{0} - {1} = {2}",
dateOffset1, dateOffset2, difference);
// If run in the Pacific Standard time zone on 4/2/2007, the example
// displays the following output to the console:
// 4/2/2007 7:23:57 PM - 4/3/2007 2:23:57 AM = -07:00:00
// 4/2/2007 7:23:57 PM -07:00 - 4/3/2007 2:23:57 AM +00:00 = 00:00:00
The most important distinction is that DateTime does not store time zone information, while DateTimeOffset does.
Although DateTime distinguishes between UTC and Local, there is absolutely no explicit time zone offset associated with it. If you do any kind of serialization or conversion, the server's time zone is going to be used. Even if you manually create a local time by adding minutes to offset a UTC time, you can still get bit in the serialization step, because (due to lack of any explicit offset in DateTime) it will use the server's time zone offset.
For example, if you serialize a DateTime value with Kind=Local using Json.Net and an ISO date format, you'll get a string like 2015-08-05T07:00:00-04. Notice that last part (-04) had nothing to do with your DateTime or any offset you used to calculate it... it's just purely the server's time zone offset.
Meanwhile, DateTimeOffset explicitly includes the offset. It may not include the name of the time zone, but at least it includes the offset, and if you serialize it, you're going to get the explicitly included offset in your value instead of whatever the server's local time happens to be.
There's a few places where DateTimeOffset makes sense. One is when you're dealing with recurring events and daylight savings time. Let's say I want to set an alarm to go off at 9am every day. If I use the "store as UTC, display as local time" rule, then the alarm will be going off at a different time when daylight savings time is in effect.
There are probably others, but the above example is actually one that I've run into in the past (this was before the addition of DateTimeOffset to the BCL - my solution at the time was to explicitly store the time in the local timezone, and save the timezone information along side it: basically what DateTimeOffset does internally).
TLDR if you don't want to read all these great answers :-)
Explicit:
Using DateTimeOffset because the timezone is forced to UTC+0.
Implicit:
Using DateTime where you hope everyone sticks to the unwritten rule of the timezone always being UTC+0.
(Side note for devs: explicit is always better than implicit!)
(Side side note for Java devs, C# DateTimeOffset == Java OffsetDateTime, read this: https://www.baeldung.com/java-zoneddatetime-offsetdatetime)
DateTime.Now
Fri 03 Dec 21 18:40:11
DateTimeOffset.Now
Fri 03 Dec 21 18:40:11 +02:00
So, DateTimeOffset stores information about how the time relates to UTC, basically the time zone.
A major difference is that DateTimeOffset can be used in conjunction with TimeZoneInfo to convert to local times in timezones other than the current one.
This is useful on a server application (e.g. ASP.NET) that is accessed by users in different timezones.
The only negative side of DateTimeOffset I see is that Microsoft "forgot" (by design) to support it in their XmlSerializer class. But it has since been added to the XmlConvert utility class.
XmlConvert.ToDateTimeOffset
XmlConvert.ToString
I say go ahead and use DateTimeOffset and TimeZoneInfo because of all the benefits, just beware when creating entities which will or may be serialized to or from XML (all business objects then).

Is this a correct use of DateTime? Will it work for all timezones?

So,
I'm storing a DateTime object as a private member on an object. This private member is set on object creation like so:
this.mCreateDate = DateTime.UtcNow.ToUniversalTime();
Now, later on in the application, I want to see how long the object has been alive for. This could be many weeks (this is a long running web app).
I'm getting the object lifetime like so:
DateTime now = DateTime.UtcNow.ToUniversalTime();
TimeSpan objectLifetime = now.Subtract(Foo.CreateDate);
// Output formatted time span
Does this all look correct?
First, calling ToUniversalTime() on DateTime.UtcNow is redundant, since .net 2.0, no conversion happens if the "Kind" of the source DateTime object is "Utc".
Other than that, this looks fine.
This seems correct, and you're following the golden rule of DateTime: Use UTC, display in local!
Your approach seems correct to me although I think it is more appropriate to use the DateTimeOffset structure, which is new since .NET 3.5.
I quote Anthony Moore:
Use DateTimeOffset whenever you are referring to an exact point in time. For example, use it to calculate "now", transaction times, file change times, logging event times, etc. If the time zone is not known, use it with UTC. These uses are much more common than the scenarios where DateTime is preferred, so this should be considered the default.
Use DateTime for any cases where the absolute point in time does not apply: e.g. store opening times that apply across time zones.
Use DateTime for interop scenarios where the information is a Date and Time without information about the time zone, e.g. OLE Automation, databases, existing .NET APIs that use DateTime, etc.
Use DateTime with a 00:00:00 time component to represent whole dates, e.g. Date of irth.
Use TimeSpan to represent times of day without a date.
So you could simply write:
this.mCreateDate = DateTimeOffset.Now;
//...
TimeSpan lifeTime = DateTimeOffset.Now - Foo.CreateDate;

Categories

Resources