Floating times

Intended audience: users, HTML coders, script developers (PHP, JSP, etc.), CSS coders, schema developers (DTDs, XML Schema, RelaxNG, etc.), XSLT developers, Web project managers, and anyone who is new to internationalization and needs guidance on topics to consider and ways to get into the material on the site. yyy adapt this to describe the intended reader of the article.

Question

What is a floating time and how do I handle floating times in my Web application?

Quick answer

A floating time or a floating date is a date/time value that isn't tied to a specific time zone. Applications that work with floating times need to avoid pitfalls that can arise from implicit conversion to incremental time values or the improper use of time zones when displaying or processing a floating time value.

Details

The Working with Timezones WG Note says:

Some observed time values are not related to a specific moment in incremental time. Instead, they need to be combined with local information to determine a range of acceptable incremental time values. We refer to these sorts of time values as "floating times" because they are not fixed to a specific incremental time value. Floating times are not attached and should never be attached to a particular time zone.

That is, a floating time value is a time value that is associated with many different discrete instants in time which share some specific relationship. Common examples of floating times include concepts such as birth date, starting or ending dates for a worldwide event, holidays, or publication dates.

An Example: Martina's Newspaper

As an example, let's consider the publication date of an online newspaper. On 30 April 2017 this newspaper published an online Sunday edition.

Martina is a subscriber who lives in Buenos Aires, Argentina. When she visits the newspaper's web site, she is given the option of selecting which edition she wants to view. The HTML for the form control might look like this:

<label for="publicationDate">Which issue would you like:</label>
<input id="publicationDateInput" type="datetime-local">

When Martina fills in the form and submits it, though, she is surprised to see that the edition she receives is dated Saturday, 29 April 2017 instead. The actual newspaper is the correct Sunday edition. But the screen says the wrong date. What's going on?

The publication date of the newspaper is an example of a floating time value: the date of the newspaper stays the same, regardless of which time zone one views the newspaper in. However, most time values in programming languages and environments are based on incremental time. The JavaScript Date object is an example of an incremental time: it represents the number of milliseconds since an epoch date. In the case of JavaScript, the epoch data is the same as that used by Unix, the Java programming language, and many other systems: it's defined as midnight, January 1, 1970 in the UTC (Universal Coordinated Time) time zone. So for Martina's newspaper, the value in the HTML form she submitted might be converted in JavaScript to a Date object with the value: 1493510400000 (in milliseconds since the epoch). That long integer value corresponds to the instant of midnight, 30 April, 2017 in Coordinated Universal Time (UTC). At that moment in Buenos Aires, Argentina, it was still only 9:00 PM (21:00) on the previous day, Saturday, 29 April, 2017. When the Date with value 1493510400000 is displayed in the local time zone, it's interpreted as the preceding Saturday.

Here's an example. Suppose the input element publicationDateInput above contained the value used in the example ("2017-04-30"). The code might look like this:

var date = new Date(publicationDateInput.value);
var target = document.getElementById("date-target");
target.appendChild(document.createTextNode(date.toDateString()));

You'd expect the element whose ID is date-target to contain a string representing Sunday, April 30th. However, if you live in a time zone west of UTC (such as Martina does, in the America/Buenos_Aires time zone) you would see something like this instead:

Sat Apr 29 2017

Many of the Date methods in JavaScript are sensitive to the local system's time zone and they try to present the date's incremental time value using local time zone rules. Because the time of day was not specified in the string "2017-04-30", the time is treated as midnight, so anyone displaying the underlying value in a time zone following UTC will naturally see the previous day. In the case of a system running in the America/Buenos_Airestime zone, which has an offset from UTC of either -3 hours or -2 hours (depending on whether or not daylight savings time is in effect) the value presented to the user from the JavaScript is incorrect because the the time portion of the publication date is zeroed out. Subtracting two or three hours from the Date object results in a presentation string that is on the previous day. That day might be in the wrong month or even the wrong year.

Here's a more complete example showing the interaction between floating and incremental time values, including how the new JavaScript Intl extension interacts with date values:


  var mypanel = document.createElement('div');
  
  var date = new Date("2017-04-30"); // 1493510400000
  var content = document.createElement('p');
  // this produces "Sat Apr 29 2017"
  
  content.appendChild(document.createTextNode(date.toDateString()));
  mypanel.appendChild(content);
  
  // the Intl extension provides locale-awareness and
  // supports different time zones, in most modern browsers.
  // this code formats the same Date value using the Buenos Aires
  // time zone and a Spanish/Argentina locale ('es-AR')
  content = document.createElement('p');
  var options = {
    month:        'long',
    day:          'numeric',
    year:         'numeric',
    hour:         'numeric',
    minute:       'numeric',
    timeZoneName: 'short',
    timeZone:     'America/Buenos_Aires'
  };
  content.appendChild(document.createTextNode(
      new Intl.DateTimeFormat('es-AR', options).format(date)));
  mypanel.appendChild(content);
  
  // the above shows: "29 de abril de 2017 21:00 ART"
  
  content = document.createElement('p');
  options = {
    month:        'long',
    day:          'numeric',
    year:         'numeric',
    hour:         'numeric',
    minute:       'numeric',
    timeZoneName: 'short',
    timeZone:     'UTC'
  };
  content.appendChild(document.createTextNode(
      new Intl.DateTimeFormat('es-AR', options).format(date)));
      
  // Using UTC finally results in displaying the expected value:
  // "30 de abril de 2017 00:00 UTC"
      
  mypanel.appendChild(content);
  
  document.body.appendChild(mypanel);
  

Because the local interpretation of a given date depends on the local time—that is, it depends on the local calendar, clock, and time zone rules—the range of incremental times that might be described as 2017-04-30 turns out to span as many as fifty hours. The earliest time that can be called "30 April 2017" starts in Kiribati in the South Pacific, some 14 hours before midnight on 30 April 2017 occurs in UTC. The last moment that can be called part of "30 April 2017" occurs just before midnight of "1 May 2017" on the island of Midway's time zone, some 11 hours after "30 April 2017" has ended in UTC (there is an even later zone, but it is uninhabited). Depending on the time zone used to create and then later to display an incremental time value, the same floating date value could appear to be as much as two days off.

That's because, while a floating time is not tied to a specific time zone, any incremental time values depend on time zone for their display and processing. Since data formats for time values in both server-side (such as Java or PHP) and client-side languages (such as JavaScript) are usually incremental times, this can lead to problems such as Martina's mysterious newspaper publication date.

How to handle floating times

There are several ways to work around this. One method is to always use the UTC time zone for processing floating time values. Developers have to be careful to convert time values into incremental time objects using an offset of zero and segregate code that deals with floating times (especially formatting these time values for display) from code dealing with actual incremental time values such as time stamps.

In some programming languages and environments there are data types or APIs that can help by keeping floating times "floating". For example, if you're a Java developer writing server code, you can (carefully) use data types from Joda time (part of the JDK since 1.8), such as LocalDate to create and process time values without imposing a specific time zone. A Java LocalDate is an example of a floating time value. The care that must be excercised is that implicit or explict conversion to incremental time values such as Instant or Date often takes place in formatting code or when applying certain date processes, such as comparison.

In client-side scripting with JavaScript, the Date single argument string constructor uses UTC, with a local time offset of 0, to compute the underlying incremental time value. Methods that use the local time zone of the browser, such as toDateString or toString will cause the problems such as Martina experienced above.

If instead the developer uses the field-based time constructor new Date(year, month[, date[, hours[, minutes[, seconds[, milliseconds]]]]]);, then the browser's local time offset is used to compute the underlying incremental time. In the Martina example, var Date = new Date(2017,04,30); creates a time value 1493521200000, which seems to work if only the date is shown. But when Martina visits her friend Noboyuki in Japan, the problem appears in the opposite direction:

const  date = new Date(1493478000000); // 2017-04-30 in Japan Standard Time  
var content = document.createElement('p');

  content = document.createElement('p');

  var options = {
    month:        'long',
    day:          'numeric',
    year:         'numeric',
    hour:         'numeric',
    minute:       'numeric',
    timeZoneName: 'short',
    timeZone:     'America/Buenos_Aires'
  };
  console.log(new Intl.DateTimeFormat('es-AR', options).format(date));
  
  // 29 de abril de 2017 12:00 ART
  
  content = document.createElement('p');
  options.timeZone = 'UTC';

  console.log(new Intl.DateTimeFormat('es-AR', options).format(date));
  
  // 29 de abril de 2017 15:00 GMT
  
  content = document.createElement('p');
  options.timeZone = 'Asia/Tokyo';
  
  // 30 de abril de 2017 00:00 GMT+9
  console.log(new Intl.DateTimeFormat('es-AR', options).format(date));

This is even visible in the MDN article on Intl.DateTimeFormat:

const date = new Date(Date.UTC(2020, 11, 20, 3, 23, 16, 738));
// Results below assume UTC timezone - your results may vary

// Specify default date formatting for language (locale)
console.log(new Intl.DateTimeFormat('en-US').format(date));
// expected output: "12/20/2020"

// Specify default date formatting for language with a fallback language (in this case Indonesian)
console.log(new Intl.DateTimeFormat(['ban', 'id']).format(date));
// expected output: "20/12/2020"

// Specify date and time format using "style" options (i.e. full, long, medium, short)
console.log(new Intl.DateTimeFormat('en-GB', { dateStyle: 'full', timeStyle: 'long' }).format(date));
// Expected output "Sunday, 20 December 2020 at 14:23:16 GMT+11"

If this code is executed on a browser in a time zone west of UTC, the "expected output" won't be shown: instead one sees values such as 12/19/2020.

Ultimately, Web developers need to remain aware of the use of incremental times in the underlying APIs. Using floating or "local" time formats is one way to accmoplish this. Or developers can use UTC or a consistent local time offset for floating time values to allow these values to be processed and displayed consistently.