Custom value types are like buses

You wait years to write one… and then six of them come along at once.

(Cross-posted to the Noda Time blog and my coding blog as it’s relevant to both.)

When we started converting Joda Time to .NET, there was always going to be the possibility of using custom value types (structs) – an opportunity which isn’t available in Java. This has meant reducing the type hierarchy a fair amount, but that’s actually made things simpler. However, I didn’t realise quite how many we’d end up with – or how many would basically just wrap a long.

So far, we have 4 value types whose only field is a long. They are:

  • Instant: an instant on the theoretical timeline, which doesn’t know about days of the week, time zones etc. It has a single reference point – the Unix epoch – but that’s only so that we can represent it in a concrete fashion. The long represents the number of ticks since the Unix epoch.
  • LocalInstant: this is a tricky one to explain, and I’m still looking for the right way of doing so. The basic idea is that it represents a day and a time within that day, but without reference to a time zone or calendar system. So if I’m talking to someone in a different time zone and an Islamic calendar, we can agree on the idea of "3pm tomorrow" even if we have different ideas of what month that’s in and when it starts. A LocalInstant is effectively the instant at which that date/time would occur if you were considering it in UTC… but importantly it’s not a genuine instant, in that it doesn’t unambiguously represent a point in time.
  • Duration: a number of ticks, which can be added to an instant to get another instant. This is a pure number of ticks – again, it doesn’t really know anything about days, months etc (although you can find the duration for the number of ticks in a standard day – that’s not the same as adding one day to a date and time within a time zone though, due to daylight savings).
  • Offset: very much like a duration, but only used to represent the offset due to a time zone. This is possibly the most unusual of the value types in Noda, because of the operators using it. You can add an offset to an instant to get a local instant, or you can subtract an offset from a local instant to get an instant… but you can’t do those things the other way round.

The part about the odd nature of the operators using Offset really gets to the heart of what I like about Joda Time and what makes Noda Time even better. You see, Joda Time already has a lot of types for different concepts, where .NET only has DateTime/DateTimeOffset/TimeSpan – having these different types and limiting what you can do with them helps to lead developers into the pit of success; the type system helps you naturally get things right.

However, the Joda Time API uses long internally to represent all of these, presumably for the sake of performance: Java doesn’t have custom value types, so you’d have to create a whole new object every time you manipulated anything. This could be quite significant in some cases. Using the types above has made the code a lot simpler and more obviously correct – except for a couple of cases where the code I’ve been porting appears to do some very odd things, which I’ve only noticed are odd because of the type system. James Keesey, who’s been porting the time zone compiler, has had similar experiences: since introducing the offset type with its asymmetric operators, found that he had a bug in some of his ported code – which immediately caused a compile-time error when he’d converted to using offsets.

When I first saw the C# spec, I was dubious about the value of user-defined value types and operator overloading. Indeed I still suspect that both features are overused… but when they’re used appropriately, they’re beautiful.

Noda Time is still a long way from being a useful API, but I’m extremely pleased with how it’s shaping up.

4 thoughts on “Custom value types are like buses”

  1. I think you’re overcomplicating things. I’ve written my own custom temporal types and they have full support of time zones, are very easy to use and compatible with the existing .NET temporal types. There is a ‘Time’ and a ‘Date’ type, which both have a ‘TimeZone’ property of type ‘TimeZoneInfo’ which tells you what time zone that time or date is referring to. Both can be joined in a ‘DateAndTime’ type. ‘Time’ has the method ‘public Time ToTimeIn(TimeZoneInfo timeZone, Date date)’ to do time zone conversions and the method ‘public static Time NowIn(TimeZoneInfo timeZone)’ to get the current time in the time zone passed in. Date has a similar method: ‘public static Date TodayIn(TimeZoneInfo timeZone)’. To pass around time-zone-less information, you can just use the existing DateTime type.

    Like

  2. @Hermann: While you’re welcome to your opinion, I personally think you’re oversimplifying things. I dare say those types are fine for the uses that you’ve had to put them to – but I suspect I’d find them somewhat limiting in the kind of work I’ve had to do.

    What does it even mean for a Time to have a time zone, for example? It can have an offset – but whether that offset changes or not when you advance it an hour will depend on the date. Likewise *just* a date shouldn’t have a time zone – the *start of a day* depends on the time zone, but a date itself is zone-less; my birthday is on June 19th independent of any time zone.

    I’ve used various date and time APIs over the years, and Joda is by far the richest and cleanest of any I’ve used.

    Like

  3. @Shuggy: I’m not sure about an implicit conversion, but we’ll certainly have a decent story around conversion between the “native” .NET types and the Noda types. I think it’s actually better to make that explicit, as it should be relatively rare to switch modes, as it were – and you should be very aware you’re doing it.

    Like

Leave a comment