This is a companion page to the "core types quick reference", and "choosing between types" pages, describing the fundamental concepts in Noda Time.
One of the benefits of Noda Time over the Base Class Library (BCL)
time support is the representation of different concepts with
different types. The downside of this is that there are more types
to learn about, and you're forced to make decisions between subtly
different concepts where in the BCL you could just use DateTime
and hope you were doing the right thing.
By forcing you to think a little bit more upfront, we're hoping you'll gain a greater understanding not only of how your code works but sometimes what data you're trying to represent anyway - you should expect the use of Noda Time to clarify your thinking around date and time data for your whole project.
This document introduces the core concepts, but in order to avoid it being too overwhelming, we won't go into the fine details. See individual pages (particularly the "choosing between types" page) and the API documentation for more information.
Many of the concepts can be considered as either local or global. These terms are fairly common within other APIs and documentation, but they can be confusing to start with. (Global values are also called absolute values in some writing, although we don't use this name in Noda Time.)
The key difference is that people all around the world will agree on
a global value simultaneously, whereas they may all see different
local values for the same global value, due to time zones. A local
value has no associated time zone in Noda Time; in particular it is
not just "a date and time fixed to the local time of the computer
running the code" as a DateTime
with a Kind
of Local
is
interpreted in .NET.
Internally, Noda Time has a concept of a LocalInstant
which is a
local value without reference to any particular calendar system, but
this is currently not exposed. Similarly, LocalTime
does
not refer to a calendar system - we assume that all calendars are based
around solar days 24 hours in length (give or take daylight saving
changes). However, the LocalDate
and
LocalDateTime
types do both refer to calendar
systems... see the later section on calendars for more information.
Instant
Noda Time assumes a single non-relativistic time line: global time
progresses linearly, and everyone in the universe would agree on the
global concept of "now". Time is measured with a granularity of
ticks where there are 10,000 ticks in a millisecond. This is the
same concept of a tick which is used in DateTime
and TimeSpan
in
.NET, although a Stopwatch
tick in .NET can vary based on the
timer used.
The "zero point" used everywhere in Noda Time is the Unix epoch: midnight at the start of January 1st 1970, UTC. (UTC itself did not exist in 1970, but that's another matter.) The Unix epoch happens to be a common zero point used in many other systems - but if we had used some other zero point (such a January 1st 2000 UTC) it would simply have offset the values, and changed the maximum and minimum representable values. It's just an origin.
The Noda Time Instant
type represents a point on this
global timeline: the number of ticks which have elapsed since the Unix
epoch. The value can be negative for dates and times before 1970 of
course - the range of supported dates is from around 27000 BCE to
around 31000 CE in the Gregorian calendar.
An Instant
has no concept of a particular time zone - it is
just the number of ticks which have occurred since
the Unix epoch. The fact that the Unix epoch is defined in terms of
UTC is irrelevant - it could have been defined in terms of a
different time zone just as easily, such as "1am on January 1st 1970
in the Europe/London time zone" (as the UK time zone was
experimentally an hour ahead of UTC at the time). The tick values
would remain the same.
Similarly, an Instant
has no concept of a particular calendar
system - it is meaningless to ask which month an instant occurs in,
as the concept of a month (or year, etc) is only relevant within a
particular calendar system.
Instant
is a good type to use for "when something happened" - a
timestamp in a log file, for example. The instant can then be
interpreted in a particular time zone and calendar system, in a
way which is useful to the person looking at the log.
Humans break up time into units such as years, months, days, hours, minutes and so on. While time itself has no such concept, it makes life more convenient for people. Unfortunately - and this is a recurrent theme in software - humanity hasn't done very well in agreeing a single system to use. So there are multiple different calendars, including Gregorian, Julian, Coptic and Buddhist. These allow different people to talk about the same local time in different ways - the Unix epoch occurred on December 19th 1969 in the Julian calendar system, for example.
The calendar system you use is one of the few things Noda Time is willing to use a default for: unless you specify otherwise, the ISO-8601 calendar is used. If you don't know which calendar you ought to use, this is almost certainly the one for you.
In Noda Time, the CalendarSystem
type handles the
details of different calendar systems. Most of the methods are internal,
although a few useful methods are exposed. Most of the time even if you
do need to use a CalendarSystem
, you can just fetch a reference to
an appropriate object, and then pass it to other constructors etc as a
little bundle of magic which simply does the right thing for you.
In the most basic sense, a time zone is a mapping between UTC
date/times and local date/times - or equivalently, a mapping from
UTC instants to offsets, where an offset is simply the difference
between UTC and local time. In Noda Time, time zones are represented
by the DateTimeZone
class, and offsets are represented
by the Offset
struct.
An offset is positive if local time is later than (ahead of) UTC, and negative if local time is earlier than (behind) UTC. For example, the offset in France is +1 hour during winter in the Northern Hemisphere and +2 hours in the summer; the offset in California is -8 hours in the winter and -7 hours in the summer. So at noon UTC in winter, it's 4am in California and 1pm in France.
As well as mapping any particular instant to an offset,
DateTimeZone
allows you to find out the name of the part of the
time zone for that instant, as well as when the next or previous
change occurs - usually for daylight saving changes.
Most of the time when you use a DateTimeZone
you won't need
worry about that - the main purpose is usually to convert between a
ZonedDateTime
and a LocalDateTime
,
where the names mean exactly what you expect them to. There's a slight
twist to this: converting from an Instant
or a ZonedDateTime
to a
LocalDateTime
is unambiguous; at any point in time, all the (accurate)
clocks in a particular time zone will show the same time... but the
reverse isn't true. Any one local time can map to:
Noda Time makes it reasonably easy to handle these situations, but
you need to work out what you want to happen. See the DateTimeZone
documentation for more details and options.
There are various different sources of time zone information available, and
Noda Time handles two of them: it is able to map BCL TimeZoneInfo
objects
using BclDateTimeZone
, or it can use the tz database (also known as
the IANA Time Zone database, or zoneinfo or Olson database). A version of TZDB
is embedded within the Noda Time distribution, and if you need a more recent
one, there are instructions on how to download and use new data.
We generally recommend that you isolate yourself from the provider you're using
by only depending on IDateTimeZoneProvider
, and
injecting the appropriate provider in the normal way. "Stock" providers are
available via the DateTimeZoneProviders
class.
The IANA time zones page lists the time zones for each release of the time zone database.
Also note that in some cases, you may not have full time zone information,
but have just a local time and an offset. For example, if you're parsing the string
"2012-06-26T20:41:00+01:00" that gives the information that the local time is one hour
ahead of UTC - but it doesn't give any indication of what the offset would be at any other
time. In this situation, you should use OffsetDateTime
.
There are two similar types in Noda Time used to represent "lengths of time". The
simplest is Duration
which is equivalent to TimeSpan
in the BCL.
This is a fixed number of ticks - it's the same length of time wherever it's
applied. Duration
is used for arithmetic on Instant
and ZonedDateTime
values.
A more complex type is Period
, which is a set of values associated with different
calendar-based periods: years, months, weeks, days, hours, minutes and so on. Some of
these periods represent different lengths of time depending on what they're applied
to - if you add "one month" to January 1st, that's going to be 31 days long. Adding the
same period to February 1st will give a shorter length of time - which then depends
on whether the year is a leap year or not. Periods based on smaller units (hours, minutes
and so on) will always represent the same length of time, but they're still
available within periods. Period
is used for arithmetic on locally-based values
(LocalDateTime
, LocalDate
, LocalTime
).
See the arithmetic page for more information.