Noda Time 2.0 contains a number of breaking changes. If you have a project which uses Noda Time 1.x and are considering upgrading to 2.0, please read the following migration guide carefully. In particular, there are some changes which are changes to execution-time behaviour, and won't show up as compile-time errors.
Some parameters have been renamed for consistency. This should not affect code that uses positional argument passing; code which uses named arguments will need to specify the new parameter name where there are changes. This does not affect binary compatibility.
A few members in 1.x were already marked as obsolete, and they have now been removed. Code using these members will no longer compile. Two of these were simple typos in the name - fixing code using these is simply a matter of using the correct name instead:
DateTimeZoneProviders.Default has been removed. It wasn't the default in any Noda
Time code, and it's clearer to use the
DateTimeZoneProviders.Tzdb member, which the
member was equivalent to anyway.
Support for the resource-based time zone database format was removed in Noda Time 2.0. In terms
of the public API, this just meant removing three
TzdbDateTimeZoneSource constructors, and
removing some documented edge cases where the legacy resource format didn't include as much
information as the more recent "nzd" format. If you were previously using the resource format,
just move to the "nzd" format, using the static factory members of
Instant(long) constructor is now private; use
As the resolution of 2.0 is nanoseconds, a constructor taking a number of ticks since the
Unix epoch is confusing. The static method is self-describing, and this allows the constructor
to be rewritten for use within Noda Time itself.
LocalTime.LocalDateTime property has been removed. It was rarely a good idea to
arbitrarily pick the Unix epoch as the date, and usually indicates a broken design. If you
still need this behaviour, you can easily construct a
LocalDate for the Unix epoch and use
the addition operator instead.
CalendarSystem.GetMaxMonth has been renamed to
GetMonthsInYear, to match the equivalent
YearOfCentury have both been removed. We considered it unlikely that they
were being used, and the subtle differences between the Gregorian and ISO calendar systems were
almost certainly not helpful. Users who wish to compute the century and year of century in a
particular form can do so reasonably easily in their own code. With this change in place, the
distinction between the ISO calendar system and Gregorian-4 is only maintained for simplicity,
compatibility and consistency; the two calendars behave identically.
Duration.FromStandardWeeks has been removed on the grounds that it was quite odd; it's unusual
to want a duration of a standard week - you can always just multiply by 7 and call
Standard has been removed from the members of
NodaConstants and also from
(so that's now
Duration.FromDays). If it was annoying for the Noda Time developers, it was probably annoying
for users too... the meaning is exactly the same, and the documentation still talks about "standard" days/weeks,
but having it in the names was a bit obnoxious, particularly in code which used a lot of constants.
Factory methods in
CalendarSystem which either didn't take any parameters (
GetPersianCalendar) or which
no longer support those parameters (
GetJulianCalendar) have been converted into properties.
So for example, the equivalent of
CalendarSystem.GetJulianCalendar(4) is now just
Methods and properties on
Instant to do with the Unix epoch have been renamed to be consistent with
methods introduced in
DateTimeOffset in .NET 4.6:
Ticksproperty is now
Static properties on the pattern classes have been renamed to remove the
Pattern suffix. For example,
LocalDateTimePattern.ExtendedIsoPattern is now just
IsoDayOfWeek properties in
ZonedDateTime are now just called
DayOfWeek. The previous numeric
have been removed, but in all cases if you were actually calling them, you can just cast the
int and always get the same result, as all calendar systems use ISO days of the week.
LocalTime constructors accepting tick values (tick-of-second and tick-of-millisecond)
have been converted to static factory methods (
LocalDateTime constructors accepting tick values have been removed. To construct a
value with a value more fine-grained than milliseconds, either construct separate
values and add them together, or construct a
LocalDateTime to the nearest second (or millisecond) and use
PlusNanoseconds to construct the final one.
Properties related to week-years (e.g.
WeekOfWeekYear) have been removed, in favour of a more
flexible system. See the week-years guide for more information. In most cases,
the fix is to use
IClock.Now property has been replaced by
IClock.GetCurrentInstant(). This is both
clearer in meaning, and a more appropriate member type. The two calls are absolutely equivalent
Days properties of
int rather than
long properties. Any property for those units outside the range of
could never be added to a date anyway, as it would immediately go out of range. This change just
makes that clearer, and embraces the new "
int for dates,
long for times" approach which
applies throughout Noda Time 2.0. The indexer for
PeriodBuilder is still of type
it will throw an
ArgumentOutOfRangeException for values outside the range of
setting date-based units.
Normalization of a period which has time units which add up to a "days" range outside the range
int will similarly fail.
In Noda Time 1.x,
Offset was implemented as a number-of-milliseconds.
Sub-second time zone offsets aren't used in practice (modulo a very small
number of historical cases), and in any case aren't supported by the TZDB or
BCL source data that we are able to use, so we supported more precision than
In Noda Time 2.0,
Offset is implemented as a number-of-seconds. This should
be mostly transparent, though
Offset.FromMilliseconds() will now effectively
truncate to the whole number of seconds. (Similarly,
now truncate to the whole number of seconds rather than a whole number of
milliseconds.) The range of
Offset is also reduced from +/- 23:59:59.999 to
+/- 18:00:00. The range reduction should have no practical consequence for real
situations, but test code which tried to use offsets between 18 and 24 hours
ahead of or behind UTC will need to be adjusted.
As a consequence of this change, offset formatting and parsing patterns no
longer support the
F custom patterns, nor the
f (full) standard
pattern. Attempting to use these will generate an error, and attempting to
OffsetDateTime) values containing fractional second
offsets will fail (though as mentioned above, these values do not tend to exist
The Coptic and Julian calendars no longer support variants based on "minimum number of days in the first week of the week year" - it was felt this was really only important for the Gregorian calendar. This affects the calls used to fetch Coptic/Julian calendars, the ID in formatted text, and also serialized values.
As noted above (when talking about removed members), the ISO calendar is now equivalent to the Gregorian 4 calendar.
Binary serialization: there is no compatibility between 1.x and 2.0. If binary data has been persisted, it will need to be converted to the 2.0 format. We don't currently provide any tools to simplify this, but if it turns out to be a significant problem, please post on the mailing list and we'll see what we can do.
The default values of some structs have changed, from returning the Unix epoch to returning January 1st 1AD (at midnight where applicable):
ZonedDateTime(in UTC, as in 1.x)
OffsetDateTime(with an offset of 0, as in 1.x)
We recommend that you avoid relying on the default values at all - partly for the sake of clarity.
In version 1.x, the
y format specifier meant "absolute year" (which may be negative) and the
specifier meant "year of era". Unfortunatly, this is not compatible with the BCL, where
y really means year
of era. Under most calendars this is irrelevant in the BCL, as most only support a single era - but in Noda Time,
the default calendar system (Gregorian) supports dates before 1CE. (Even so, most users will never create or use
dates before 1CE.)
In version 2.0,
y means "year of era", and
u means "absolute year". This use of
u is in line with
Unicode TR-35 although
there are other aspects of format specifiers which aren't - 2.0 is not embracing TR-35 universally, preferring BCL-compatibility,
but the BCL doesn't have any "absolute year" specifier.
For most users this change will be invisible, as it is anticipated that most users only use values where
absolute year and year of era are the same value. However, anyone using
Y in their format patterns will
see an exception thrown, and anyone using
y but formatting a value where the year is not the same as
the year of era will see different results.
One unfortunate side-effect of this is that the normal BCL handling - using the patterns specified by the BCL -
gives ambiguous values. For example,
new LocalDate(-50, 1, 1).ToString() will return something like "01 January 0051"
instead of the previous "01 January -0050". Users whose applications are likely to encounter
dates before 1CE should consider using custom format patterns with the
u specifier instead of
Noda Time's ISO-8601 pattern handling will provide the same text values as before, as the patterns have been
updated to use
u. This includes the patterns used in
|Value||Noda Time 1.x||Noda Time 2+||BCL|
|Year of era||
-now for the positive and negative signs, instead of asking the
NumberFormatInfofrom the culture. (These are the characters used by all standard cultures, so this will only change behavior when using a custom culture.)
The numeric standard patterns for
Offset have been removed, with no direct equivalent.
These were not known to be useful, felt "alien" in various ways, and cause issues within the
implementation. If you need these features - possibly in a specialized way - please contact the
mailing list and we may be able to suggest alternative implementations to meet your specific
Patterns no longer allow ASCII letters (a-z, A-Z) to act as literals when they're not escaped or quoted. Quoting make the intention more explicit, and avoids unintended use of a literal when a specifier was expected (e.g. a date pattern of "yyyy-mm-dd"). One exception here is 'T', which is allowed for date/time patterns only - so a date/time pattern of "yyyy-MM-ddTHH:mm:ss" is still acceptable for ISO-8601, for example. If this change breaks your code, simply escape or quote the literals within the pattern.
When specifying a BCL
DateTimeFormatInfo values can be used;
any other non-null reference will now throw an exception. When a
DateTimeFormatInfo is provided,
the invariant culture is used for resource lookups and text comparisons. The previous behavior was to
use the current culture for anything other than a
CultureInfo value. To obtain the equivalent behavior, simply
provider as CultureInfo to end up with a null argument for non-CultureInfo values, which will still
use the current culture.
In 2.0, the
LenientResolver, which is used by
was changed to more closely match real-world usage.
For ambiguous values, the lenient resolver used to return the later of the two possible instants. It now returns the earlier of the two possible instants. For example, if 01:00 is ambiguous, it used to return 1:00 standard time and it now will return 01:00 daylight time.
For skipped values, the lenient resolver used to return the instant corresponding to the first possible local time following the "gap". It now returns the instant that would have occurred if the gap had not existed. This corresponds to a local time that is shifted forward by the duration of the gap. For example, if values from 02:00 to 02:59 were skipped, a value of 02:30 used to return 03:00 and it will now return 03:30.
If you require the behavior of the 1.x implementation, you can create a custom resolver that combines the
ReturnStartOfIntervalAfter resolvers. For example:
var resolver = Resolvers.CreateMappingResolver( Resolvers.ReturnLater, Resolvers.ReturnStartOfIntervalAfter);
You can use this resolver as an argument to
LocalDateTime.InZone instead of calling
DateTimeZone.ResolveLocal instead of calling
We would strongly encourage you to carefully evaluate whether you truly need the old behavior or not before making these compatibility changes, as we have found that the new behavior aligns more closely with most real-world scenarios.