7

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.

Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
Morgoth
  • 237
  • 2
  • 11
  • 1
    There are already answers here, but we basically want to discourage users from taking an `OffsetDateTime` which was originally related to a particular time zone, adding a period, and then assuming that the offset would still be the same in the original time zone. You can use `input.LocalDateTime.Minus(Period.FromMonths(1)).WithOffset(input.Offset)` if you're really sure it's what you want though. – Jon Skeet Apr 27 '19 at 08:46

3 Answers3

5

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.

Ortiga
  • 8,455
  • 5
  • 42
  • 71
  • Thanks, indeed OffsetDateTime doesn't have the timezone and might have lead me into potential issues. – Morgoth Apr 26 '19 at 10:57
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)));
}
Jeff
  • 7,504
  • 3
  • 25
  • 34
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'"))
Moddasir
  • 1,449
  • 13
  • 33