'Why can't I add months to OffsetDateTime?

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.



Solution 1:[1]

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.

Solution 2:[2]

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)));
}

Solution 3:[3]

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'"))

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Jeff
Solution 3 Moddasir