0

Does anyone know of a C# library that will correctly apply an ISO 8601 duration string to a DateTime using "nominal" values for Y, M, etc.?

Using System.Xml.XmlConvert::ToTimeSpan() is faulty. It converts P1M to a Timespan of 30 days, which means it's using "accurate" values instead of "nominal" values, which isn't helpful in most of my use-cases:

See 2.1.7 nominal duration

I don't blame ToTimeSpan() for that, however, since TimeSpan is inadequate for storing nominal values. I need something I can apply to a DateTime and get a "nominally correct" outcome. Meaning "2020-01-01" with "P1Y" applied ends up as "2021-01-01".

jpt
  • 162
  • 1
  • 11
  • Does this answer your question? [Given a DateTime object, how do I get an ISO 8601 date in string format?](https://stackoverflow.com/questions/114983/given-a-datetime-object-how-do-i-get-an-iso-8601-date-in-string-format) – gunr2171 Mar 23 '20 at 14:55
  • @jpt Please can you make your question a little clearer. In particular, please can you give clear examples of the input you expect, and the output you expect. Most people are not sufficiently well-versed in the intricacies of ISO-8601 to know the meaning of `nominal` vs `accurate` and the linked definition `duration expressed amongst others in years, months, weeks or days` requires further reading to really comprehend. – RB. Mar 23 '20 at 15:09
  • Are you looking for [Noda Time](https://nodatime.org/1.3.x/api/NodaTime.Period.html) (specifically the "Period")? Full disclosure, I don't work with Noda Time, so I don't know if it de-/serializes the way you'd like, but if _that_ lib doesn't, I wouldn't know which would. – Corak Mar 23 '20 at 15:15
  • @RB - that's why I provided a link to the docs and DID provide an example of the input and output I expect. The problem space requires thinking to really comprehend, anyway, so anyone who can't be bothered to do so probably wouldn't be the best one to answer my question. – jpt Mar 23 '20 at 15:17
  • @gunr2171 - No that would not be an answer to my question. It's not quite relevant. ISO 8601 designate formats for Durations as well as Date/Times. I'm interesting in applying a Duration to a DateTime using C#. – jpt Mar 23 '20 at 15:18
  • @Corak - Looks like Noda Time's Period has some promise. I'll check it out. – jpt Mar 23 '20 at 15:19
  • How about https://stackoverflow.com/questions/2906022/timespan-to-iso8601-duration-format-string?lq=1 ? – gunr2171 Mar 23 '20 at 15:20
  • Btw.: a simple `dateTimeValue.AddMonths(1)` or `dateTimeValue.AddYears(1)` wouldn't be an option? – Corak Mar 23 '20 at 15:21
  • @gunr2171 - Thanks for trying, but you're going in the wrong direction. – jpt Mar 23 '20 at 15:22
  • Corak@ - Trying to avoid taking this calculation on as a responsibility. We use ISO 8601 durations as the spec for the calculation, but only just now realized the calculations performed by ToTimeSpan() are inadequate. We should have caught this earlier. I will need to parse the entire format and apply to a datetime to get this right. – jpt Mar 23 '20 at 15:24
  • Yes, a [TimeSpan](https://referencesource.microsoft.com/#mscorlib/system/timespan.cs) is basically just a number of ticks, so it can't represent a "variable" amount of time. As "P1M" in february is different from "P1M" in december. – Corak Mar 23 '20 at 15:26
  • @Corak - Looks like NodaTime's PeriodPattern may provide a roundtrip parsing mechanism that is generally compatible with ISO8601. I'll see what integrated it achieves for me. Thank you! – jpt Mar 23 '20 at 16:02

1 Answers1

0

Based on a tip from @Corak, I integrated NodaTime into my project and achieved what I was looking for with something similar to the following:

var sampleDuration = 'P3Y';
var parseResult = NodaTime.Text.PeriodPattern.NormalizingIso.Parse(sampleDuration);
if (parseResult.Success)
  {
    var expDateTime = NodaTime.LocalDateTime.FromDateTime(DateTime.UtcNow.Date).
                        Plus(parseResult.Value).ToDateTimeUnspecified();
  }

// If UTC time when run is 2020-01-01T00:00:00
// expDateTime should be 2023-01-01T00:00:00
jpt
  • 162
  • 1
  • 11