d3 / d3-time-format

Parse and format times, inspired by strptime and strftime.
https://d3js.org/d3-time-format
ISC License
327 stars 100 forks source link

JSON of locale need no-periods attribute #82

Open ppKrauss opened 4 years ago

ppKrauss commented 4 years ago

Some countries, like Brazil, using pt-BR locale, need a boolean flag to express "we only use 24hs, please never use periods"... Or at least "please avoid periods".


NOTES

Some softwares like VEGA-lite are accepting "periods": [] but without use 24hs, that is also wrong. Another way to create a new convention is to accept explicit null as this kind of convention... But it is not perfect, because the "periods" translation can be useful for interface where user must to use it. So, a flag as prefer-no-periods: true is the better convention.

mbostock commented 4 years ago

The %I directive is defined as 12-hour time; this library isn’t designed to allow the locale to change that definition. For example, if we changed %I to be either 12- or 24-hour time depending on the locale, then a format like %I %p would have a trailing space for 24-hour time (assuming that %p is the empty string).

My guess here is that you’re using some other non-localized code that specifies %I, and you want to change that to use %H. Is it by chance the default time format used by d3.scaleTime and d3.scaleUtc?

https://github.com/d3/d3-scale/blob/732ed4b1cd5c643700571d1089c7deb8472242a6/src/time.js#L31-L32

The recommended solution is to provide your own time format definition, replacing the one in the README as desired. Maybe:

var formatMillisecond = d3.timeFormat(".%L"),
    formatSecond = d3.timeFormat(":%S"),
    formatMinute = d3.timeFormat("%H:%M"),
    formatHour = d3.timeFormat("%H"),
    formatDay = d3.timeFormat("%a %d"),
    formatWeek = d3.timeFormat("%b %d"),
    formatMonth = d3.timeFormat("%B"),
    formatYear = d3.timeFormat("%Y");

function multiFormat(date) {
  return (d3.timeSecond(date) < date ? formatMillisecond
      : d3.timeMinute(date) < date ? formatSecond
      : d3.timeHour(date) < date ? formatMinute
      : d3.timeDay(date) < date ? formatHour
      : d3.timeMonth(date) < date ? (d3.timeWeek(date) < date ? formatDay : formatWeek)
      : d3.timeYear(date) < date ? formatMonth
      : formatYear)(date);
}

Alternatively, we could perhaps move this definition (that is, the list of format strings %Y, %B, %b %d, %a %d, etc.) from d3-scale into d3-time-format, and then allow the locale to enumerate the desired values. Then the locale could replace %I with %H to change the default behavior of d3.scaleTime and d3.scaleUtc.

ppKrauss commented 4 years ago

Thanks @mbostock to the solution (!), it is a good alternative for programmers, and for my chart. And thanks to discuss! I am looking for wide and long-term use of the locale specification.... Can we extend the discussion?

Anticipating my apologies if so: I will try using my Bad-English, and long explanations, perhaps redundant. Before, answering your question:

My guess here is that you’re using some other non-localized code that specifies %I, and you want to change that to use %H. Is it by chance the default time format used by d3.scaleTime and d3.scaleUtc?

Yes, it is VEGA-lite (its inner axis "temporal" tickFormat when zoomed), it delegates to D3js. When user is non-English the VEGA locale is a copy/paste of D3js locale, like locale/pt-BR.json.
PS: VEGA user is not a programmer, so the suggested multiFormat() workaround is not valid, a function is not a part of the JSON locale specification.

VEGA and similar ones, and its users, we need high-level locale specification. Them, the solution must be originated in the JSON locale interpretation.

ppKrauss commented 4 years ago

Suggestion description

The aim is the locale folder, and the JSON schema used there.

My suggestion is to add a flag, a new locale JSON schema property:
  optional boolean periodsPrefer with default false.
It is the "cultural preference" of the country.
(and sometimes as in Brazil it have also the semantic of "official choice").

PS: it is not to change %I definition, it is only to user of non-English locale definition, and eventual algorithm of the d3-time-format lib (like the tickFormat() method), to decide what to do, using %H instead %I when locale say that is necessary.


Rationale and example.

Is ugly for any non-English language that use 24hs and not "PM/AM" (no use or very rare use of periods)... We need "plug and play" 24h standard (high-level locale specification) for it.

We can suppose that the property periodsPrefer is default true, so only in a locale definition that need false that it will be expressed. No impact. For example locale/de-DE.json stay as is, and locale/pt-BR.json will be something as:

{
  "dateTime": "%A, %e de %B de %Y. %X",
  "date": "...",
  "time": "%H:%M:%S",
  "periods": ["AM", "PM"],
  "periodsPrefer": false,
  "days": ["..."],
}

It is for all locale users, not only this library. Of course, it will be useful also here, at for example tickFormat() of time.js. Imagine a var userLocale, a locale copy... It is possible to change lines here to:

 formatMinute = userLocale.periodsPrefer? format("%I:%M"): format("%H:%M"),
   formatHour = userLocale.periodsPrefer? format("%I %p"):    format("%H"),

We can suppose some locale normalization before use above, at copy or initialization:

 if (userLocale.periodsPrefer===undefined)
     userLocale.periodsPrefer = (userLocale.periods && userLocale.periods.length>0);