<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://msmvps.com/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Jon Skeet: Coding Blog : C#, Design</title><link>http://msmvps.com/blogs/jon_skeet/archive/tags/C_2300_/Design/default.aspx</link><description>Tags: C#, Design</description><dc:language>en</dc:language><generator>CommunityServer 2008.5 SP2 (Build: 40407.4157)</generator><item><title>The perils of conditional mutability</title><link>http://msmvps.com/blogs/jon_skeet/archive/2012/05/06/the-perils-of-conditional-mutability.aspx</link><pubDate>Sun, 06 May 2012 13:58:45 GMT</pubDate><guid isPermaLink="false">d67277c4-116b-43f1-b688-e9ef184ea916:1809562</guid><dc:creator>skeet</dc:creator><slash:comments>6</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/rsscomments.aspx?PostID=1809562</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/commentapi.aspx?PostID=1809562</wfw:comment><comments>http://msmvps.com/blogs/jon_skeet/archive/2012/05/06/the-perils-of-conditional-mutability.aspx#comments</comments><description>&lt;p&gt;This morning I was wrestling with trying to make some Noda Time unit tests faster. For some reason, the &lt;a href="http://teamcity.codebetter.com"&gt;continuous integration host we&amp;#39;re using&lt;/a&gt; is &lt;em&gt;really&lt;/em&gt; slow at loading resources under .NET 4. The unit tests which run in 10 seconds on my home laptop take over three &lt;em&gt;hours&lt;/em&gt; on the continuous integration system. Taking stack traces at regular intervals showed the problem was with the NodaFormatInfo constructor, which reads some resources.&lt;/p&gt;  &lt;p&gt;I may look into streamlining the resource access later, but before we get to that point, I wanted to try to reduce the number of times we call that constructor in the first place. NodaFormatInfo is meant to be cached, so I wouldn&amp;#39;t have expected thousands of instances to be created - but it&amp;#39;s only cached when the System.Globalization.CultureInfo it&amp;#39;s based on is read-only. This is where the problems start...&lt;/p&gt;  &lt;p&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/system.globalization.cultureinfo.aspx"&gt;CultureInfo&lt;/a&gt; is &lt;em&gt;conditionally mutable &lt;/em&gt;(not an official term, just one I&amp;#39;ve coined for the purposes of this post). You can ask whether or not it&amp;#39;s read-only with the &lt;a href="http://msdn.microsoft.com/en-us/library/system.globalization.cultureinfo.isreadonly.aspx"&gt;IsReadOnly property&lt;/a&gt;, and obviously if it&amp;#39;s read-only you can&amp;#39;t change it. Additionally, CultureInfo is composed of other conditionally mutable objects - &lt;a href="http://msdn.microsoft.com/en-us/library/system.globalization.datetimeformatinfo.aspx"&gt;DateTimeFormatInfo&lt;/a&gt;, &lt;a href="http://msdn.microsoft.com/en-us/library/system.globalization.numberformatinfo.aspx"&gt;NumberFormatInfo&lt;/a&gt; etc. There&amp;#39;s a static &lt;a href="http://msdn.microsoft.com/en-us/library/system.globalization.cultureinfo.readonly.aspx"&gt;ReadOnly method&lt;/a&gt; on CultureInfo to create a read-only wrapper around a mutable CultureInfo. It&amp;#39;s not clearly documented whether that&amp;#39;s &lt;em&gt;expected&lt;/em&gt; to take a deep copy (so that callers can really rely on it not changing) or whether it&amp;#39;s expected to reflect any further changes made to the culture info it&amp;#39;s based on. To go in the other direction, you can call Clone on a CultureInfo to create a mutable copy of any existing culture.&lt;/p&gt;  &lt;p&gt;Further complications are introduced by the properties on the composite objects - we have properties such as &lt;a href="http://msdn.microsoft.com/en-us/library/system.globalization.datetimeformatinfo.monthnames.aspx"&gt;DateTimeFormatInfo.MonthNames&lt;/a&gt; which returns a string array. Remember, arrays are &lt;em&gt;always&lt;/em&gt; mutable. So it&amp;#39;s really important to know whether the array reference returned from the property refers to a &lt;em&gt;copy&lt;/em&gt; of the underlying data, or whether it refers to the array that&amp;#39;s used internally by the type. Obviously for read-only DateTimeFormatInfo objects, I&amp;#39;d expect a copy to be returned - but for a mutable DateTimeFormatInfo, it would potentially make sense to return the underlying array reference. Unfortunately, the documentation doesn&amp;#39;t make this clear - but in practice, it always returns a copy. If you need to change the month names, you need to clone the array, mutate the clone, and then set the MonthNames property.&lt;/p&gt;  &lt;p&gt;&lt;em&gt;All of this makes CultureInfo hard to work with&lt;/em&gt;. The caching decision earlier on only really works if a &amp;quot;read-only&amp;quot; culture genuinely won&amp;#39;t change behind the scenes. The type system gives you no help to catch subtle bugs at compile-time. Making any of this robust but efficient (in terms of taking minimal copies) is tricky to say the least.&lt;/p&gt;  &lt;p&gt;Not only does it make it hard to work with from a client&amp;#39;s point of view, but apparently it&amp;#39;s hard to implement correctly too...&lt;/p&gt;  &lt;h2&gt;First bug: Mono&amp;#39;s invariant culture isn&amp;#39;t terribly invariant...&lt;/h2&gt;  &lt;p&gt;(Broken in 2.10.8; apparently fixed later.)&lt;/p&gt;  &lt;p&gt;I discovered this while getting Noda Time&amp;#39;s unit tests to pass on Mono. Unfortunately there are some I&amp;#39;ve had to effectively disable at the moment, due to deficiencies in Mono (some of which are being fixed, of course).&lt;/p&gt;  &lt;p&gt;Here&amp;#39;s a program which builds a clone of the invariant culture, changes the &lt;em&gt;clone&amp;#39;s&lt;/em&gt; genitive month names, and then prints out the first &lt;em&gt;non-genitive&lt;/em&gt; name from the plain invariant culture:&lt;/p&gt;  &lt;div class="code"&gt;&lt;span class="Namespace"&gt;using&lt;/span&gt; System;     &lt;br /&gt;&lt;span class="Namespace"&gt;using&lt;/span&gt; System.Globalization;     &lt;br /&gt;    &lt;br /&gt;&lt;span class="ReferenceType"&gt;class&lt;/span&gt; Test     &lt;br /&gt;{     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &lt;span class="Modifier"&gt;static&lt;/span&gt;&amp;#160;&lt;span class="ValueType"&gt;void&lt;/span&gt; Main()     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; CultureInfo clone = (CultureInfo) CultureInfo.InvariantCulture.Clone();     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="InlineComment"&gt;// Note: not even deliberately changing MonthNames for this culture!&lt;/span&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; clone.DateTimeFormat.MonthGenitiveNames[0] = &lt;span class="String"&gt;&amp;quot;Changed&amp;quot;&lt;/span&gt;;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="InlineComment"&gt;// Prints Changed&lt;/span&gt;     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine(CultureInfo.InvariantCulture.DateTimeFormat.MonthNames[0]);     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }     &lt;br /&gt;} &lt;/div&gt;  &lt;p&gt;I believe this bug is &lt;em&gt;really&lt;/em&gt; due to the lack of support for genitive month names in Mono at the moment - the MonthGenitiveNames property always just returns a reference to the month names for the invariant culture - without taking a copy first. (It always returns the invariant culture&amp;#39;s month names, even if you&amp;#39;re using a different culture entirely.) The code above shows an &amp;quot;innocent&amp;quot; attempt to change a mutable clone - but in reality we could have used &lt;em&gt;any&lt;/em&gt; culture (including an immutable one) to make the change.&lt;/p&gt;  &lt;p&gt;Note that in the .NET implementation, the change would only have been to a copy of the underlying data, so even the &lt;em&gt;clone&lt;/em&gt; wouldn&amp;#39;t have reflected change anywhere.&lt;/p&gt;  &lt;h2&gt;Second bug: ReadOnly losing changes&lt;/h2&gt;  &lt;p&gt;The second bug is the one I found this morning. It looks like it&amp;#39;s fixed in .NET 4, but it&amp;#39;s present in .NET 3.5, which is where it bit me this morning. When you try to make a read-only wrapper around a mutated culture, some of the properties are preserved... but some aren&amp;#39;t:&lt;/p&gt;  &lt;div class="code"&gt;&lt;span class="Namespace"&gt;using&lt;/span&gt; System;    &lt;br /&gt;&lt;span class="Namespace"&gt;using&lt;/span&gt; System.Globalization;    &lt;br /&gt;    &lt;br /&gt;&lt;span class="ReferenceType"&gt;class&lt;/span&gt; Test    &lt;br /&gt;{    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; &lt;span class="Modifier"&gt;static&lt;/span&gt;&amp;#160;&lt;span class="ValueType"&gt;void&lt;/span&gt; Main()    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; {    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; CultureInfo clone = (CultureInfo) CultureInfo.InvariantCulture.Clone();    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; clone.DateTimeFormat.AMDesignator = &lt;span class="String"&gt;&amp;quot;ChangedAm&amp;quot;&lt;/span&gt;;    &lt;br /&gt;    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="InlineComment"&gt;// The array is recreated on each call to MonthNames, so changing the&lt;/span&gt;    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="InlineComment"&gt;// value within the array itself doesn&amp;#39;t work :(&lt;/span&gt;    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;span class="ReferenceType"&gt;string&lt;/span&gt;[] months = (&lt;span class="ReferenceType"&gt;string&lt;/span&gt;[]) clone.DateTimeFormat.MonthNames;    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; months[0] = &lt;span class="String"&gt;&amp;quot;ChangedMonth&amp;quot;&lt;/span&gt;;    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; clone.DateTimeFormat.MonthNames = months;    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; CultureInfo readOnlyCopy = CultureInfo.ReadOnly(clone);    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine(clone.DateTimeFormat.AMDesignator); &lt;span class="InlineComment"&gt;// ChangedAm&lt;/span&gt;    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine(clone.DateTimeFormat.MonthNames[0]); &lt;span class="InlineComment"&gt;// ChangedMonth&lt;/span&gt;    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine(readOnlyCopy.DateTimeFormat.AMDesignator); &lt;span class="InlineComment"&gt;// ChangedAm&lt;/span&gt;    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Console.WriteLine(readOnlyCopy.DateTimeFormat.MonthNames[0]); &lt;span class="InlineComment"&gt;// January (!)&lt;/span&gt;    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; }    &lt;br /&gt;} &lt;/div&gt;  &lt;p&gt;I don&amp;#39;t know what&amp;#39;s going on here. In the end I just left the test code using the mutable clone, having added a comment explaining &lt;em&gt;why&lt;/em&gt; it wasn&amp;#39;t created a read-only wrapper.&lt;/p&gt;  &lt;p&gt;I&amp;#39;ve experimented with a few different options here, including setting the MonthNames property on the clone &lt;em&gt;after&lt;/em&gt; creating the wrapper. No joy - I simply can&amp;#39;t make the new month names stick in the read-only copy. &amp;lt;sigh&amp;gt;&lt;/p&gt;  &lt;h2&gt;Conclusion&lt;/h2&gt;  &lt;p&gt;I&amp;#39;ve been frustrated by the approach we&amp;#39;ve taken to cultures in Noda Time for a while. I haven&amp;#39;t worked out exactly what we should do about it yet, so it&amp;#39;s likely to stay somewhat annoying for v1, but I may well revisit it significantly for v2. Unfortunately, there&amp;#39;s nothing I can do about CultureInfo itself.&lt;/p&gt;  &lt;p&gt;What I would have &lt;em&gt;preferred&lt;/em&gt; in all of this is the builder pattern: make CultureInfo, DateTimeFormatInfo etc all immutable, but give each of them mutable builder types, with the ability to create a mutable builder based on an existing immutable instance, and obviously to create a new immutable instance from a builder. That would make all kinds of things simpler - including caching.&lt;/p&gt;  &lt;p&gt;For the moment though, I hope we can all learn lessons from this - or have old lessons reinforced, at least:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Making a single type behave in different ways based on different &amp;quot;modes&amp;quot; makes it hard to use correctly. (Yes, this is the same first conclusion as with DateTime in the previous post. Funny, that.)&lt;/li&gt;    &lt;li&gt;Immutability has to be deep to be meaningful: it&amp;#39;s not much use having a supposedly read-only object which composes a StringBuilder...&lt;/li&gt;    &lt;li&gt;Arrays should be &lt;a href="http://blogs.msdn.com/b/ericlippert/archive/2008/09/22/arrays-considered-somewhat-harmful.aspx"&gt;considered somewhat harmful&lt;/a&gt;. If you&amp;#39;re going to return an array from a method, make sure you document whether this is a &lt;em&gt;copy&lt;/em&gt; of the underlying data, or a &amp;quot;live&amp;quot; reference. (The latter should be very rare, particularly for a public API.) The exception here is if you return an empty array - that&amp;#39;s effectively immutable, so you can always return it with no problems.&lt;/li&gt;    &lt;li&gt;The builder pattern rocks - use it!&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;In my next post I&amp;#39;ll try to get back to the TimeZoneInfo oddities I mentioned last time.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://msmvps.com/aggbug.aspx?PostID=1809562" width="1" height="1"&gt;</description><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Noda+Time/default.aspx">Noda Time</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Design/default.aspx">Design</category></item><item><title>Subtleties in API design - member placement</title><link>http://msmvps.com/blogs/jon_skeet/archive/2012/02/29/subtleties-in-api-design-member-placement.aspx</link><pubDate>Wed, 29 Feb 2012 17:27:25 GMT</pubDate><guid isPermaLink="false">d67277c4-116b-43f1-b688-e9ef184ea916:1806600</guid><dc:creator>skeet</dc:creator><slash:comments>38</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/rsscomments.aspx?PostID=1806600</wfw:commentRss><wfw:comment xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://msmvps.com/blogs/jon_skeet/commentapi.aspx?PostID=1806600</wfw:comment><comments>http://msmvps.com/blogs/jon_skeet/archive/2012/02/29/subtleties-in-api-design-member-placement.aspx#comments</comments><description>&lt;p&gt;&lt;a href="http://noda-time.googlecode.com"&gt;Noda Time&lt;/a&gt; is nearing v1.0, which means I&amp;#39;m spending more time writing documentation than code. It also means reviewing the APIs we&amp;#39;ve got with a critical eye - whether that&amp;#39;s removing extraneous members, adding useful ones, or moving things around. (In particular, writing documentation often suggests where a change would make calling code read more naturally.)&lt;/p&gt;  &lt;p&gt;This post is about one particular section of the API, and the choices available. Although I &lt;em&gt;do&lt;/em&gt; go into some detail around the specific calls involved, that&amp;#39;s just for context... the underlying choices are ones which could be faced when designing any API. I&amp;#39;ve rarely spent as much time thinking about API decisions as I have with Noda Time, so hopefully this will prove interesting to you even if you really don&amp;#39;t care about Noda Time itself as a project.&lt;/p&gt;  &lt;h1&gt;Introduction: time zones, local date/times and zoned date/times - oh my!&lt;/h1&gt;  &lt;p&gt;(Okay, so that&amp;#39;s not quite as snappy as &lt;a href="http://youtu.be/peh61wd-53w?t=42s"&gt;the Judy Garland version&lt;/a&gt;, but hey...)&lt;/p&gt;  &lt;p&gt;The area of API we&amp;#39;re going to focus on is time zones, and converting between &amp;quot;local&amp;quot; date/time values and &amp;quot;zoned&amp;quot; ones. The three types involved are:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;strong&gt;LocalDateTime&lt;/strong&gt;: a &amp;quot;local&amp;quot; date and time, with no specific time zone. So, something like &amp;quot;7:30 in the evening on February 27th 2012&amp;quot;. This means different instants in time in different time zones, of course: if you&amp;#39;re arranging a meeting, it&amp;#39;s good enough when the attendees are in the same time zone, but not good enough if you&amp;#39;re meeting with someone on the other side of the world. (A LocalDateTime also has an associated calendar system, but I&amp;#39;m not going to talk about that much in this post.) &lt;/li&gt;    &lt;li&gt;&lt;strong&gt;DateTimeZone&lt;/strong&gt;: a time zone. At its core, this maps &lt;em&gt;any&lt;/em&gt; &amp;quot;instant&amp;quot; in time to an &lt;em&gt;offset&lt;/em&gt; - the difference between UTC and local time in that time zone. The offset changes over time, typically (but not always) due to daylight saving changes. &lt;/li&gt;    &lt;li&gt;&lt;strong&gt;ZonedDateTime&lt;/strong&gt;: a date and time in a particular time zone, with an offset from UTC to avoid ambiguity in some cases (and for efficiency). This identifies a specific instant in time (simply by subtracting the offset from the local date/time). Conceptually this is equivalent to just maintaining the &amp;quot;instant&amp;quot; value, the time zone, and the calendar system - but it&amp;#39;s generally cleaner to think of it as a &amp;quot;local&amp;quot; value with an offset from UTC. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;If those brief descriptions don&amp;#39;t make sense for you at the moment (this sort of thing is quite hard to describe concisely and precisely) you may want to see whether the Noda Time &lt;a href="http://noda-time.googlecode.com/hg/docs/userguide/concepts.html"&gt;user guide &amp;quot;concepts&amp;quot; page&lt;/a&gt; helps.&lt;/p&gt;  &lt;h3&gt;The API task: mapping from { LocalDateTime, DateTimeZone } to ZonedDateTime&lt;/h3&gt;  &lt;p&gt;It&amp;#39;s easy to get from a ZonedDateTime to a LocalDateTime - you can just use the LocalDateTime property. The difficult bit is the other way round. We obviously want to be able to create a ZonedDateTime from the combination of a LocalDateTime and a DateTimeZone, but the question is where to put this functionality. Three options suggest themselves:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;A static method (or constructor) in ZonedDateTime which takes both the time zone and the local date/time as arguments &lt;/li&gt;    &lt;li&gt;An instance method on LocalDateTime which just takes the time zone as an argument &lt;/li&gt;    &lt;li&gt;An instance method on DateTimeZone which just takes the local date/time as an argument &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;It gets more complicated though - we&amp;#39;re not really talking about &lt;em&gt;one&lt;/em&gt; operation here, but potentially several. Although the mapping from instant to offset is unambiguous in DateTimeZone, the mapping from LocalDateTime to offset is &lt;em&gt;not &lt;/em&gt;straightforward. There can be 0, 1 or 2 possible results. For example, in the America/Los_Angeles time zone the clocks go forward from 2am to 3am on Sunday March 11th 2012, and go back from 2am to 1am on Sunday 4th November 2012. That means:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;The mapping from local date/time to offset at 7.30pm on February 27th 2012 is unambiguous: it&amp;#39;s definitely -8 hours (L.A. is 8 hours &lt;em&gt;behind&lt;/em&gt; UTC). &lt;/li&gt;    &lt;li&gt;The mapping at 2.30am on March 11th 2012 is &lt;em&gt;impossible&lt;/em&gt;: at 2am the clocks were put forward to 3am, so 2.30am simply never occurs. &lt;/li&gt;    &lt;li&gt;The mapping at 2.30am on November 4th 2012 is &lt;em&gt;ambiguous&lt;/em&gt;: it happens once &lt;em&gt;before&lt;/em&gt; the clocks go back at 3am, and once &lt;em&gt;afterwards&lt;/em&gt;. The offset is either -7 or -8 hours, depending on which occurrence you mean. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;When mapping a local time to &amp;quot;global&amp;quot; time, this is something you should really think about. Most APIs obscure the issue, but one of the purposes of Noda Time is to &lt;em&gt;force&lt;/em&gt; developers to think about issues which they should be aware of. This one is particularly insidious in that it&amp;#39;s the kind of problem which is &lt;em&gt;much&lt;/em&gt; more likely to arise when you&amp;#39;re asleep than during daylight hours - so it&amp;#39;s unlikely to be found during manual testing. (Ditto the day of week - most time zones have daylight saving transitions early on a Sunday morning.)&lt;/p&gt;  &lt;p&gt;So, Noda Time exposes four ways of mapping a LocalDateTime and DateTimeZone to a ZonedDateTime:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Exact: if there&amp;#39;s a single mapping, return it. Otherwise, throw an exception. &lt;/li&gt;    &lt;li&gt;Earlier: if there&amp;#39;s a single mapping, return it. If there&amp;#39;s more than one, return the earlier one. If the time is skipped, throw an exception. &lt;/li&gt;    &lt;li&gt;Later: if there&amp;#39;s a single mapping, return it. If there&amp;#39;s more than one, return the later one. If the time is skipped, throw an exception. &lt;/li&gt;    &lt;li&gt;All information: find out all the information relevant to mapping the given local value - how many matches there are, what they would be, what the time zone information is for each mapping, etc. The caller can then do what they want. &lt;/li&gt; &lt;/ul&gt;  &lt;h1&gt;Options available&lt;/h1&gt;  &lt;p&gt;The question is &lt;em&gt;how&lt;/em&gt; we expose these operations. Let&amp;#39;s look at some options, then discuss the pros and cons.&lt;/p&gt;  &lt;h3&gt;Option 1: methods on LocalDateTime&lt;/h3&gt;  &lt;p&gt;A lot of Noda Time is designed to be &amp;quot;fluent&amp;quot; so it makes a certain amount of sense to be able to take a LocalDateTime, perform some arithmetic on it, then convert it to a ZonedDateTime, then (say) format it. So we could have something like:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;var zoned = local.InZone(zone); // implicitly exact &lt;/li&gt;    &lt;li&gt;var zoned = local.InZoneOrEarlier(zone); &lt;/li&gt;    &lt;li&gt;var zoned = local.InZoneOrLater(zone); &lt;/li&gt;    &lt;li&gt;var mapping = local.MapToZone(zone); &lt;/li&gt; &lt;/ul&gt;  &lt;h3&gt;Option 2: methods on DateTimeZone&lt;/h3&gt;  &lt;p&gt;All the calculations involved are really about the time zone - the local date/time value is just a simple value as far as most of this is concerned. So we can put the methods on DateTimeZone instead:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;var zoned = zone.AtExactly(local); &lt;/li&gt;    &lt;li&gt;var zoned = zone.AtEarlier(local); &lt;/li&gt;    &lt;li&gt;var zoned = zone.AtLater(local); &lt;/li&gt;    &lt;li&gt;var mapping = zone.MapLocalDateTime(local); &lt;/li&gt; &lt;/ul&gt;  &lt;h3&gt;Option 3: methods (or constructors) on ZonedDateTime&lt;/h3&gt;  &lt;p&gt;Maybe we consider the two inputs to be fairly equal, but the result type is more important:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;var zoned = ZonedDateTime.FromLocal(zone, local); &lt;/li&gt;    &lt;li&gt;var zoned = ZonedDateTime.FromLocalOrEarlier(zone, local); &lt;/li&gt;    &lt;li&gt;var zoned = ZonedDateTime.FromLocalOrLater(zone, local); &lt;/li&gt;    &lt;li&gt;var mapping = ZoneLocalMapping.FromLocal(local) &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;(I&amp;#39;m not terribly happy about the names here; there could be better ones of course.)&lt;/p&gt;  &lt;h3&gt;Variation a: any of the above options, but with an enum for ambiguity resolution&lt;/h3&gt;  &lt;p&gt;We don&amp;#39;t really need four methods on any of these APIs; the first three only differ by how they handle ambiguity (the situation where a particular local date/time occurs twice). We could use an enum to represent that choice instead:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;var zoned = local.InZone(zone, ZoneAmbiguityResolver.Error); &lt;/li&gt;    &lt;li&gt;var zoned = local.InZone(zone, ZoneAmbiguityResolver.Earlier); &lt;/li&gt;    &lt;li&gt;var zoned = local.InZone(zone, ZoneAmbiguityResolver.Later); &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;(Or a &amp;quot;smart enum&amp;quot; with behaviour, if we wanted. A normal class type with methods etc, but only a restricted set of valid values.)&lt;/p&gt;  &lt;h3&gt;Variation b: always go via the mapping result&lt;/h3&gt;  &lt;p&gt;Given that we already have the idea of getting the full mapping results, we can reduce the API to just &lt;em&gt;one&lt;/em&gt; method to return the mapping information, and then put extra methods on that:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;var zoned = local.MapInZone(zone).SingleMatch; &lt;/li&gt;    &lt;li&gt;var zoned = local.MapInZone(zone).SingleOrEarlier; &lt;/li&gt;    &lt;li&gt;var zoned = local.MapInZone(zone).SingleOrLater; &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;(Again, the names aren&amp;#39;t fixed in stone, and the second part could be methods instead of properties if we wanted.)&lt;/p&gt;  &lt;h3&gt;Variation c: return a sequence of results&lt;/h3&gt;  &lt;p&gt;If we return a sequence with 0, 1 or 2 ZonedDateTime values, the user can just use LINQ to get the one they want. Again, this can apply wherever we decide to put the method:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;var zoned = zone.At(local).Single(); &lt;/li&gt;    &lt;li&gt;var zoned = zone.At(local).First(); &lt;/li&gt;    &lt;li&gt;var zoned = zone.At(local).Last(); &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;So, it looks like we effectively have two mostly-orthogonal decisions here:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Where to &amp;quot;start&amp;quot; the conversion - the target type for the method call &lt;/li&gt;    &lt;li&gt;How to represent the multiple options &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;We&amp;#39;ll consider them separately.&lt;/p&gt;  &lt;h1&gt;Regarding the &amp;quot;source&amp;quot; type&lt;/h1&gt;  &lt;p&gt;To start with, I&amp;#39;ll reveal my bias: the existing implementation is option 2 (four methods on DateTimeZone). This was after a small amount of debate on the Noda Time mailing list, and this was the most relevant bit of the discussion:&lt;/p&gt;  &lt;p&gt;Me (&lt;em&gt;before&lt;/em&gt; going with the current implementation):&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;It feels a little odd to me to use the zone as the principal class here - just in terms of usability. It makes total sense in terms of the logic, but I tend to think of having a LocalDateTime first, and then converting &lt;em&gt;that&lt;/em&gt; to use a particular zone - it&amp;#39;s not an operation which feels like it acts on the zone itself.&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;David N:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;I actually feel the opposite: asking a DateTimeZone how a particular LocalDateTime would be represented in that zone feels natural, while asking the LocalDateTime how it would be represented in a zone feels odd. The zone is a first-class entity, with identity and behavior; the LocalDateTime is just a set of values. Why would the LocalDateTime be expected to know how it is represented in a particular zone?&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Even though I replied to that in a &amp;quot;maybe&amp;quot; kind of tone, the argument basically convinced me. The trouble is, a colleague was then &lt;em&gt;surprised&lt;/em&gt; when he read the documentation around calendar arithmetic and conversions. Surprising users is pretty much a cardinal sin when it comes to API design - and although in this case it was the relatively harmless sort of surprise (&amp;quot;I can&amp;#39;t find the member I want in A; oh, it turns out it&amp;#39;s in B&amp;quot;) rather than a &lt;em&gt;behavioural&lt;/em&gt; surprise (&amp;quot;I thought it would do X, but instead it does Y&amp;quot;) it&amp;#39;s still bad news. I should reveal my colleague&amp;#39;s bias too - he has experience of &lt;a href="http://joda-time.sourceforge.net/"&gt;Joda Time&lt;/a&gt;, where the relevant call is &lt;a href="http://joda-time.sourceforge.net/api-release/org/joda/time/LocalDateTime.html#toDateTime(org.joda.time.DateTimeZone)"&gt;LocalDateTime.toDateTime(DateTimeZone)&lt;/a&gt;. (There &lt;em&gt;are&lt;/em&gt; calls in DateTimeZone itself, but they&amp;#39;re lower-level.)&lt;/p&gt;  &lt;p&gt;We&amp;#39;ve discussed this a fair amount (leading to this blog post) and so far we&amp;#39;ve concluded that it really depends on how you &lt;em&gt;think&lt;/em&gt; about time zones. As a Noda Time user, would you consider them to be rich objects with complex behaviour, or would you think of them as mere &amp;quot;tokens&amp;quot; to be passed around and used without much direct interaction? The two ways of viewing the type aren&amp;#39;t necessarily in conflict - I&amp;#39;ve &lt;em&gt;deliberately&lt;/em&gt; designed CalendarSystem to hide its (fairly ugly) innards. There are a few public instance members, but most are internal. But what about time zones?&lt;/p&gt;  &lt;p&gt;There&amp;#39;s an argument to be made for &lt;em&gt;educating&lt;/em&gt; Noda Time users to think about time zones as more complex beasts than just tokens, and I&amp;#39;m happy to do that in other areas (such as choosing which type to use in the first place) but here it feels like it&amp;#39;s one step too far. On the other hand, I don&amp;#39;t want to stifle users who &lt;em&gt;are&lt;/em&gt; thinking of DateTimeZone in that way. In the mailing list thread, David also expressed a dislike for the approach of including functionality in multiple places - and to a certain extent I agree (one of the things I &lt;em&gt;dislike&lt;/em&gt; about its API is that it lets you do just about anything with anything)... but in this case it feels like it&amp;#39;s justified.&lt;/p&gt;  &lt;p&gt;Regardless of how you&amp;#39;re thinking about DateTimeZone, it&amp;#39;s more likely that you&amp;#39;re going to want to &lt;em&gt;use&lt;/em&gt; a LocalDateTime value which is the result of some other expression, and then apply some &amp;quot;constant&amp;quot; zone to it, then potentially keep going. If you think about a LINQ-style pipeline of operations, the part that varies in the conversion is much more likely to be the LocalDateTime than the time zone. As such, a method on LocalDateTime allows for a more fluent calling style:&lt;/p&gt;  &lt;div class="code"&gt;&lt;span class="Linq"&gt;var&lt;/span&gt; zoned = parseResult.Value     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; .PlusMonths(1)     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; .InZone(LondonTimeZone); &lt;/div&gt;  &lt;p&gt;versus:&lt;/p&gt;  &lt;div class="code"&gt;&lt;span class="Linq"&gt;var&lt;/span&gt; zoned = LondonTimeZone.AtExactly(parseResult.Value.PlusMonths(1)); &lt;/div&gt;  &lt;p&gt;Or to keep the code order the same as the execution order:&lt;/p&gt;  &lt;div class="code"&gt;&lt;span class="Linq"&gt;var&lt;/span&gt; local = parseResult.Value.PlusMonths(1);     &lt;br /&gt;&lt;span class="Linq"&gt;var&lt;/span&gt; zoned = LondonTimeZone.AtExactly(local); &lt;/div&gt;  &lt;p&gt;Obviously the effects become more noticeable the more operations you perform. Personally I&amp;#39;m happy with either the first or third approach - although it&amp;#39;s worth being aware that either of the first &lt;em&gt;two&lt;/em&gt; have the advantage of being one expression, and therefore easy to use when assigning a static readonly field or something similar.&lt;/p&gt;  &lt;p&gt;I&amp;#39;m &lt;em&gt;reasonably&lt;/em&gt; happy with having one method on each type, or possibly two (MapLocalDateTime and At*) on DateTimeZone and one (just InZone) on LocalDateTime. I &lt;em&gt;really&lt;/em&gt; don&amp;#39;t like the idea of having four methods on DateTimeZone and three methods on LocalDateTime. So, let&amp;#39;s consider the different variations which cut down the number of methods required.&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;h1&gt;Expressing &amp;quot;exactly,&amp;quot; &amp;quot;earlier,&amp;quot; and &amp;quot;later&amp;quot; in one method&lt;/h1&gt;  &lt;p&gt;This is essentially a discussion of the &amp;quot;variations&amp;quot; above. Just to recap, the possibilities we&amp;#39;ve come up with are:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Add another parameter to the method to indicate how to handle ambiguities (or impossibilities) - just return a ZonedDateTime &lt;/li&gt;    &lt;li&gt;Return a value of a different type (e.g. ZoneLocalMapping) which can be used to get at all the information you could want &lt;/li&gt;    &lt;li&gt;Return a sequence of possible ZonedDateTime values, expecting the caller to probably use LINQ&amp;#39;s First/Last/Single/FirstOrDefault etc to get at the value they want &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;The last of these is the only one which gives an easy way of handling the extreme corner case of a local time occurring &lt;em&gt;more&lt;/em&gt; than twice - for example, a time zone which goes back one hour at 2am (to 1am) and then goes back another two hours at 3am. I think it&amp;#39;s reasonable to dismiss this corner case; however mad time zones can be, I haven&amp;#39;t seen anything &lt;em&gt;quite&lt;/em&gt; that crazy yet.&lt;/p&gt;  &lt;p&gt;At the time of the original discussion, Noda Time was targeting .NET 2.0, which was one reason for not going with the final option here - we couldn&amp;#39;t guarantee that LINQ would be available. Now, Noda Time is targeting .NET 3.5 in order to use TimeZoneInfo, but it still doesn&amp;#39;t feel like an ideal fit:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Returning a sequence doesn&amp;#39;t give information about (say) the two zone intervals &amp;quot;surrounding&amp;quot; a gap &lt;/li&gt;    &lt;li&gt;A sequence may be surprising to users who expect just a single value &lt;/li&gt;    &lt;li&gt;The exceptions thrown by First, Single etc when their expectations aren&amp;#39;t met are very domain-neutral; we can give more information &lt;/li&gt;    &lt;li&gt;FirstOrDefault will return the default value for ZonedDateTime in the case of ambiguity. That would be unfortunate, as ZonedDateTime is a value type, and its default value is actually somewhat unhelpful. (It has a null calendar system, effectively. There&amp;#39;s not a lot we can do about this, but that&amp;#39;s a post for another day.) We could make it a sequence of Nullable&amp;lt;ZonedDateTime&amp;gt; and guarantee that any values in it are actually non-null, but that&amp;#39;s really straining things. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Putting these together, there are enough negative points to this idea that I&amp;#39;m happy to rule it out. But what about the first two?&lt;/p&gt;  &lt;p&gt;The first has the advantage that the caller only needs to make a single method call, effectively passing in a &amp;quot;magic token&amp;quot; (the ambiguity resolver) which they don&amp;#39;t &lt;em&gt;really&lt;/em&gt; need to understand. On the other hand, if they want more information, they&amp;#39;ll have to call a different method - and I&amp;#39;m not really sure we want to encourage too much of this &amp;quot;magic token&amp;quot; behaviour.&lt;/p&gt;  &lt;p&gt;The second has three disadvantages, all fairly slight:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;The user may initially expect the result of a method mapping a LocalDateTime to a ZonedDateTime to &lt;em&gt;be&lt;/em&gt; a ZonedDateTime... what&amp;#39;s this extra intermediate result doing? This is &amp;quot;only&amp;quot; a matter of user education, and it&amp;#39;s pretty discoverable. It&amp;#39;s an extra concept the user needs to understand, but it&amp;#39;s a &lt;em&gt;good&lt;/em&gt; concept to understand. &lt;/li&gt;    &lt;li&gt;Calling two methods or a method and a property (e.g. zone.MapLocalDateTime(localDateTime).Earlier) may end up being slightly more long-winded than a single method call. I can&amp;#39;t get excited about this. &lt;/li&gt;    &lt;li&gt;We have to allocate an extra object for the mapping, even when we know it&amp;#39;s unique. Usually, this object will become eligible for garbage collection immediately. We &lt;em&gt;could&lt;/em&gt; make it a struct, but I don&amp;#39;t think it&amp;#39;s a natural value type - I&amp;#39;d rather trust that allocating objects in gen0 is pretty cheap. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;With the second method, we can replace &lt;em&gt;all&lt;/em&gt; the existing methods in DateTimeZone with a single one (or rather, just remove the AtXyz methods, leaving MapLocalDateTime). We can then create pleasantly-named methods on ZoneLocalMapping (which isn&amp;#39;t quite right for this purpose at the moment).&lt;/p&gt;  &lt;h1&gt;Conclusion&lt;/h1&gt;  &lt;p&gt;This has been an interesting thought experiment for me, and it&amp;#39;s suggested some changes I &lt;em&gt;will&lt;/em&gt; be applying before v1. We&amp;#39;ll see how they pan out. If you want to follow them, look for relevant &lt;a href="http://code.google.com/p/noda-time/source/list"&gt;source code changes&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;The important points I&amp;#39;ve been thinking about are:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;What would a new user &lt;em&gt;expect&lt;/em&gt; to be available? If they haven&amp;#39;t read any documentation, what are they likely to try? &lt;/li&gt;    &lt;li&gt;What &lt;em&gt;should&lt;/em&gt; the user know about? Where there are important decisions to make, how can we provide guidance? &lt;/li&gt;    &lt;li&gt;What would an &lt;em&gt;experienced&lt;/em&gt; user (who is already thinking about the Noda Time concepts in the way that we want to encourage) expect to be available? &lt;/li&gt;    &lt;li&gt;Where does the balance lie between providing a &amp;quot;too crowded&amp;quot; API (with lots of different ways of achieving the same thing) and a &amp;quot;sparse&amp;quot; API (where there&amp;#39;s always one way of achieving a goal, but it may not be the most convenient one for your situation) &lt;/li&gt;    &lt;li&gt;How does our choice fit in with other technologies? For example, the final &amp;quot;variation&amp;quot; seems like it plays nicely with LINQ at first - but a few subtleties make it a worse fit than it might seem. &lt;/li&gt;    &lt;li&gt;How does this affect performance? (Performance is important in Noda Time - but there would have to be a &lt;em&gt;significant&lt;/em&gt; performance problem for me to deviate from an elegant solution.) &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;So, any other thoughts? Did we miss some options? What other factors should we have taken into consideration?&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;&lt;img src="http://msmvps.com/aggbug.aspx?PostID=1806600" width="1" height="1"&gt;</description><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/C_2300_/default.aspx">C#</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Noda+Time/default.aspx">Noda Time</category><category domain="http://msmvps.com/blogs/jon_skeet/archive/tags/Design/default.aspx">Design</category></item></channel></rss>