Compact, timezone aware time and date library for JavaScript, for use in Node.js
and the browser, timezone is a database friendly, timezone aware replacement for
the Date
object that implements date parsing, date formatting, and date math.
The Timezone library uses the Olson timezone
database, to create a database of
timezone rules, one per contenent, in a compact JSON representation, so you can,
with some confidence, determine the correct local time of any place in the
world, since 1970. It replaces the Date
object with POSIX time, milliseconds
since the epoch, for a cross-platform, durable representation of time.
If you've worked with dates in JavaScript, you've already run up against the
limitations of the JavaScript Date
object. You probably already know that the
JavaScript Date
object offers no way to parse dates, or format dates, nor does
it have any way to add or subtract time from dates.
You might not be aware of that Date
has no concept of local time. Local time
is the time out there in the real world, the time on clock on the wall, offset
by a timezone, adjusted for daylight savings time. It is the time according to
the local government.
Local time is important if your program runs in more than one timezone. If your program runs on the public web, then it runs in more than one timezone.
The JavaScript Date
object has a usless dribble of support for local time. The
duplicate date field functions, some named getXyz
, other named getUTCXyz
,
might lead you to believe that it local time support is in effect, but it is
not. If you're formatting your dates by concatenating the Date.getXyz
methods
string, you're making a mistake.
// The quick and easy and wrong way to format a JavaScript date.
function brokenFormatDate (date) {
return '' + (date.getMonth() + 1) + '/' +
date.getDate() + '/' +
date.getFullYear();
}
}
When you create a Date
object it will offset the field values by the timezone
offset of the computer at the time of the Date
object's creation. You cannot
reset that offset.
Even if the Date
value came from a server in your control, this offset is in
effect when you create the date. If you are providing dates as strings, you
might build a local date with the following date parser.
function brokenParseDate (string) {
var parts = /(\d{2})\/\d{2}\/\d{4}/.exec(string);
for (var i = 1; i < 4; i++) {
parts[i] = parseInt(parts[i], 10);
}
parts[1]++;
return new Date(parts[3], parts[1], parts[2]);
}
The getUTCXyz
methods of the Date
object give you values of the different
date components in UTC, while the getXyz
methods give you the UTC value offset
by an arbitrary number of minutes. Yes, arbitrary, because the
preferred timezone of user of your web application should be dictated by your
web application, not by the settings of an arbitrary client computer.
Local time is more than a single arbitrary offset. Local time is entirely political. It is defined by borders and the whims of legislatures within those borders. They adjust for daylight savings time. Consider the muddle that is time in Indiana, and you'll see that a timezone is in no way scientific or standardized.
If your appliction works with dates in the past or the future, you need to know when and if daylight savings time adjusts, if the local government has chosen to apply daylight savings time at all. You need to know if the local government has decided to move to a different timezone all together, and when this timezone change took place.
Getting the number of minutes in a timestamp as an integer value is not terribly useful to an application developer.
When JavaScript was born, objects were all the rage. They were going to fix everything, for everybody, forever. With your best comic book guy voice, read the follow: A date, you see, is a collection of attributes, including the number of minutes of the date, and the day in year in which the day occoured. Using getters and setters we are able to alter the date by adjusting its various properties. Also, there is a timezone offset, but timezones are nasty political constructs that have no place in the, as Spock would say, logical world of computing.
We're rarely interested in a date as a component, composed of component parts. The number of minutes in a date as an integer is not often useful for application programming. Applications are going to use dates the way people use dates in the real world. They are not a collection of attributes, but a point on a timeline, interesting only relative to other points on that timeline.
Objects were all the rage, it is a wonder that we didn't have a regular expression factory pattern, and build regular expressions using getters and setters, instead of parsing regular expression patterns. That is what the date object is like. When you display the date on a page, you don't display as an exploded diagram.
Today's date is:
The major benefit of POSIX time over the Date
object is that it is sortable.
You can order POSIX time quickly, since POSIX time is simply an integer value.
The benefit of Timezone over Date
object is that is operates on a timeline. If
what you really want to do is get the number of minutes in a particular
timestamp as an integer, Timezone will do that. But, if what you'd rather do is
know the number of hours between a given timestamp and lunch the following
Friday, Timezone will do that too.
Hmm... Actually, do I want durations? Would that return object? Would it return an array?
For the most parts, applications use dates a timestamps, search for events within date ranges, or search for events before or after a certain date. We format dates for display, which takes timezone and locale into account. We parse date strings given by the user or by other systems.
What are you going to do? Display it? It would be nicer to have a date format
specifier to display the whole date, rather than catenating a string with
extremely verbose object method invocations to get each part, plus the pad
method that needs to be rewritten every time.
What are you going to do? Increment it? If you want to move forward by minutes, you could add the minutes and then set the value again, but if the new value is greater than 60 then, you need to carry the minutes into the next hour.
To my mind, I could imagine building regular expressions in JavaScript using
object composition. Wouldn't that be a nightmare? Dates are so common that they
deserve their own language, like regular expressions. The Timezone library
replaces the Date
object in applications with a domain specific language, so
that they feel more natural, like strings or regular expressions.
Dates feel unnatural because dates are represented as an object, as a box of parts, a set of cubbyholes with getters and setters for each component.
Like regular expressions, we turn on switches, so modifiers... Good old printf. We came back around to printf. As an example.
Make a slideshow and present. Use your silly examples there. Show a regular expression, then show it being bulit as an object using a factory pattern. Show an example of someone who is a slave to fashion, 80's fashion or some silly french fashion from the big crazy hair days.
You can get the integer value using a date format and the int modifier, which will be null if you screw up, because the format is a programmer supplied value.
Timezone is a timezone aware date library for JavaScript that
Timezone uses POSIX time, milliseconds since the epoch represented as a
JavaScript Number
. Timezone does not monkey patch the JavaScript Date
object. Timezone replaces the JavaScript Date
object with POSIX time.
Timezone works with one of two types of date value,
Timezone uses POSIX time, milliseconds since the epoch in UTC, for a univeral
representation of a point in time. If given a JavaScript Date
object, Timezone
will use the POSIX time value of the Date
object.
eq(88927498237492734927, bicentenial = tz("1976-07-04"));
eq(98327943274923794329, tz(bicentenial, "+1 milli"));
You can also create a date from an array, if you've gathered fields from another
means. Unlike the Date
object, the first month of the year is 1, not 0.
eq(tz("7/4/76"), tz([ 1976, 7, 4 ]));
eq(tz("6/21/69 2:36"), tz([ 1969, 6, 21, 2, 36 ]));
If you need pass in a Date
object, the value of Date.getTime()
is used.
// Note that the Date object is problematic. If you create a date using the
// Date constructor, the timezone offset is applied at creation, so it is
// aribitrary and dependent on the timezone settings of the local machine. For
// our example here, we use Date.UTC to create a Date in a convoluted way.
var moonwalk = new Date(Date.UTC(1969, 6, 21, 2, 36));
// Now we have a Date object, let's use tz to format it.
eq("6/21/69 2:36", tz(moonwalk, "%-m/%-d/%Y %-H:%M"));
Timezone will return ether a POSIX time or a formatted date string if a format
specifier is given as a parameter. It never generates JavaScript Date
objects.
Timezone is timezone aware. It uses the same timezone names found in tzdata. The timezone support is created from the same text database usd to create the tzdata timezone files found on most UNIX systems.
Timezone uses local time for date math, but uses POSIX time for date comparisons. POSIX time is used for comparisons so we can compare points in time, and know that we're not comparing timestamps in different timezones.
While Timezone uses local time for date math, an application should use POSIX time for date comparisons. POSIX time is used for comparisons so we can compare points in time, and know that we're not comparing timestamps in different timezones.
When creating an application with Timezone, POSIX time is used for persistent storage and for date comparisons. We store our dates in POSIX time and convert them to local time when we format them for presentation.
We convert all our dates to POSIX time Times should be stored using POSIX time, since queries against a timestamp in a database is a comparison.
If you're using a database, and you want to use TIMESTAMP, then make sure that your database won't trip you up by adding the timezone offset of the server. MySQL is not timezone aware, so I set the timezone of my MySQL servers to UTC, because MySQL always adds the host machines current timezone offset, which is arbitrary information that is external to and not recorded anywhere within the database. That is, if you export your MySQL database, it doesn't record what timezone it was running in when the database was created.
Thus, if we want to allow the user to move appointments forward by a number of hours on their calendar, which would be presented in their local time, Timezone will add the hours and then adjust the time if we enter or leave daylight savings time.
Timezone can perform date math in both POSIX time and local time. When adding or subtracting by hours, minutes, seconds, and milliseconds, Timezone will adjust for daylight savings time.
An an example, let's say the you're writing a calendar application for a travel web site, so that frequent travelers can choose from fights that do not conflict with their scheduled appointments. To use the feature, the customers sync their calendar with the reservation system using any iCalendar compatable calendar. Now when they search for flights, a flight that interferes with an appointment is flagged, so the user can see what appointments need to be rescheduled to take that flight.
If you are a New Yorker who wants to check to see if you're going to miss any conference calls if you take a particular flight from Vilinus to San Francisco, you need check to see if the dates in your Eastern Time calendar fit between an Easter Eurpoean Time departure and a Pacific Time arrival.
Obviously, this query needs to adjust these different times to a common timezone, which is UTC for POSIX time. The application needs to display the appointment dates, and the arrival and departure date in their with respective time zones offsets.
These days should all be converted to a common timezone to check for conficts, then the various times should be presented according to the local time of the event.
It works with the strategy of using POSIX time as the persistent representation of a timestamp, and converting that universal time to wall clock time for presentation.
POSIX time is the notion of the passage of time common to all POSIX compient UNIX systems. It is milliseconds since the epoch in UTC. This represents a specific point in time.
The milliseconds since the epoch in UTC value is an absolute value that is not affected by the politics of timezones and daylight savings time. It is considered a stable representation of a point in time, that is suitable for storage in a database.
Strings are used to represent wall clock time. If a time needs to be presented to a user,
You can use the tz
function to create a date format for display, one that
cannot be in turn parsed by the tz
function. That's fine.
Timezone exports a single funtion to keep from polluting the namespace of the client application. This single method accepts parameters similar to UNIX date.
The first parameter is always a date, either as milliseconds since the epoch as UTC or a date string to parse.
Other arguments can appear in any order.
EDT
or America/Detroit
.en_US
or zh_CN
.%Y/%d/%m
to return
the date as a formatted date string, or else a swith to indicate a canned
format specifier like --rfc822
or --rfc-3339=ns
.+1 day -43 minutes
.You can pass these in any order after the initial date parameter, the tz
function knows what you mean.
Only one timezone, locale or format can be applied to a date, so if a value is repeated for one of these parameter types, the first value is used, subsequent values are ignored.
The offsets parameter can be specified multiple times. The offsets are applied
to the date in the order in which they are passed to the tz
function.
The return value is always either milliseconds since the epoch as UTC or a date
string if the tz
function was passed a format specifier.
While other languages extend the JavaScript date object to
The API is really a domain specific language. The parameters can be passed in any order because the different parameter types have an unambiguous meaning.
The Date
object takes POSIX time and exposes the component values, which is
somewhat useful, but not not often what you need. You don't really don't often
need the number seconds in a timestamp as an integer value, you need to parse,
format, offset and compare dates. The POSIX time representation is perfect for
comparision. Formatting is easiest to express with a format pattern.
Even if it were timezone aware, the Date
object is not a particularly useful
representation of
If we land on a time missing due to the start daylight savings time, we continue in the direction we were going, adding an additional day if we are doing addition, subtracting an additional day if we are doing subtraction. We then go back by 24 hours. This gives us the same counter intuitive times as Java's Calendar.
Always adjust your dates just in time. Store your dates in UTC. Convert them when they are displayed. Record events using UTC on the server, not the client. You cannot trust the client time, you do not know if the clock is set correctly, you can't keep the user from adjusting it, even you try to account for skew.
UTC timestamps will always indicate a particular second since the epoch.
If you are doing math in hours, minutes, seconds or milliseconds, this will reflect the UTC time.
If you are day math, you may land on a daylight savings time shift. If this is the case, then the last day is treated as 24 hours. Otherwise, if are on a day at 6:00 PM standard time, and go back six months to the same day, different month, in daylight savings time, the time will still be 6:00 PM. (Use real Detroit, Michigan examples.)
The namespace.
One function to rule them all and in the darkness bind them.