Nocturnus has asked for the wisdom of the Perl Monks concerning the following question:

Dear monks,

I am developing a web application for an international audience and have the following problem:

At first, the user chooses his country. Based on that selection, the application should offer him appropriate time zones and appropriate locales.

For the time zones, this is no problem. I am using DateTime::TimeZone -> names_in_country to get all time zones which a certain country has.

But I don't get how to do this for the locales. I did not find any module or function which allows me to extract all locales which are used in a certain country. The usual locale codes like en-US denote the language (en) and something which seems to be called a "territory" (US), and I don't think that (all) territory codes are valid country codes or vice versa.

Could anybody give me a hint how to solve this problem? Perhaps the basic idea of offering appropriate locales based on a country code is wrong?

Regards,

Nocturnus

Replies are listed 'Best First'.
Re: Relationship between timezone country and locale territory? (updated x3)
by haukex (Archbishop) on Sep 14, 2016 at 19:07 UTC

    Hi Nocturnus,

    Not an expert on locales, but I think the modules Locale::Codes::Language and Locale::Codes::Country might be helpful. I played with this a bit and here's my naive implementation which seems to work on my Linux box. See also Finding locales in perllocale.

    use Locale::Codes::Language 'code2language'; use Locale::Codes::Country 'code2country'; for (`locale -a`) { chomp; my ($lang,$terr) = /^(\w+)_(\w+)\b/; print "$_ - ",code2language($lang)//'(unknown)', " - ",code2country($terr)//'(unknown)',"\n"; }

    Update: You said "I don't think that (all) territory codes are valid country codes or vice versa". The information I am finding is that locale names are typically (apparently this varies from system to system) made up of the ISO 639 (language) and ISO 3166-1 (country) codes, which is what the two aforementioned modules handle. Also, at least on Debian-based systems, the list of all supported locales (as opposed to just the ones installed reported by locale -a) is in the file /usr/share/i18n/SUPPORTED.

    Update 2: I assume the list of supported locales is not an exhaustive list of which languages are spoken in each country. You'll have to draw the line somewhere though, depending on what you want the user's choice to mean in your application (do you plan on supporting hundreds of locales?). Anyway, I revised the script based on the above:

    Update 3: Ok, last update for tonight ;-) You asked how to match the locale codes to timezone names, and the documentation for DateTime::TimeZone->names_in_country says that it accepts two-letter ISO3166 codes, which is apparently the same code typically used in locale names, so you should be able to just match those up.

    Hope this helps,
    -- Hauke D

      Hi haukex,

      thank you very much for your hints and code. Due to the different wording, I had been quite sure that the country code and the territory code could not be the same. So your hints regarding the structure of locales' codes have been very important for me, and your code shows how to solve the problem.

      By the way, I have no idea if even one single user will sign up for the application. But in the unlikely case that it becomes successful, I actually will have to support several dozens or even hundreds of locales. The application, if successful, will be translated into at least 8 languages and addresses private persons as well as commercial and non-commercial organizations all over the world.

      The application does a lot of input, output and calculations of dates, times, currencies and other numbers, and it is of utmost importance that every user (after having logged in) is working in the time zone he has chosen, gets output in the locale he has chosen, and can give input in the locale he has chosen. Due to the nature of the application, the user should consistently use always the same locale and time zone, regardless of the browser he uses and from where he works.

      Thanks again,

      Nocturnus

Re: Relationship between timezone country and locale territory?
by Your Mother (Archbishop) on Sep 14, 2016 at 21:20 UTC

    The basic idea, as you ask, probably is wrong. Let the user tell you what she wants with the Accept headers. That way a German visiting Malawi gets German from her laptop browser settings instead of Chichewa. Then add your entire locale list as optional selections after and cookie it in case the user is at an Internet café or something and the default is off base.

    Locale::Util might be where you want to start with this.

      Hi Your Mother,

      thanks for mentioning the headers. Unfortunately, due to the nature of the application, every user (once logged in) consistently should always work in the time zone and with the locale he has chosen, regardless of the browser he uses and from where he uses it. Thus, the time zone and the locale will be part of the master data of every user's profile.

      Thanks again,

      Nocturnus

        This is the second half of what I suggested. :P Cookieing the user with her preferences. The argument I'm making is that the only sane autodetection is the Accept header and the user should always be able to choose to override it. This doesn't sound at odds with what you're doing at all.

        due to the nature of the application, every user (once logged in) consistently should always work in the time zone [...] he has chosen, regardless of the browser he uses and from where he uses it.

        That's quite strange. Maybe the users are trained to work like this, or maybe the users don't travel. I would prefer to see the timezone of the place where I am, not of the place where I have been some days ago. Another option would be that all users (are forced to) agree to use the same time zone, regardless of their location. This seems to be usual for aviation, using UTC instead of local timezones.

        Anyway, here is a trick that I used years ago in a CGI-based application, when Perl 5.6, Netscape 4.7x, and Internet Explorer 5.0 were state of the art:

        The Date object in Javascript has a getTimezoneOffset() method, since Javascript 1.0. I planned to use that offset to present date and time to the user, while using UTC internally for any date and time. My plan was to detect the offset during login, then use it on the server side to adjust date and time. But then, minutes later, I discovered that the Date object can do better: You can get and set the number of milliseconds since the Unix epoch (1970-01-01 00:00:00 UTC) using getTime() and setTime(), and the Date object automatically converts from and to local time. I didn't have to care about time zones at all, the browser did that for me. I simply passed around timestamps in seconds since the epoch, with some trivial Javascript code to convert from and to milliseconds since the epoch. Some more Javascript code formatted the date to match the application's settings. (The toLocaleString() and toGMTString() method return strings depending on the OS settings and the browser language, so I had to replace them with custom functions.)

        It turned out later that, when time was omitted and only date was displayed, some dates magically changed by one day, heavily confusing the user. The problem was that I stored dates as timestamps for the day's local midnight, which is yesterday's 23:00 one timezone to the west. The simple workaround was to store dates as the timestamp for the day's noon. This gave consistent dates even across several timezones.

        Implementing an "aviation mode" that uses UTC instead of the local timezone would have been quite easy, just adding an if-then-else to the formatting functions to use the getUTCXxx() methods instead of the local timezone methods of the Date object; and of course require that people stop using Internet Explorer 4.x (which does not support the UTC methods).

        Alexander

        --
        Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)