Как мне заполнить список часовых поясов IANA / Olson из Noda Time?

Я использую NodaTime в приложении, и мне нужно, чтобы пользователь выбрал свой часовой пояс из раскрывающегося списка. У меня следующие мягкие требования:

1) Список содержит только те варианты выбора, которые разумно действительны для настоящего и ближайшего будущего для реальных мест. Исторические, неясные и общие часовые пояса должны быть отфильтрованы.

2) Список должен быть отсортирован сначала по смещению UTC, а затем по названию часового пояса. Мы надеемся, что это разместит их в порядке, значимом для пользователя.

Я написал следующий код, который действительно работает, но не совсем то, что мне нужно. Фильтр, вероятно, необходимо настроить, и я бы предпочел, чтобы смещение представляло базовое (не dst) смещение, а не текущее смещение.

Предложения? Рекомендации?

var now = Instant.FromDateTimeUtc(DateTime.UtcNow);
var tzdb = DateTimeZoneProviders.Tzdb;
var list = from id in tzdb.Ids
           where id.Contains("/") && !id.StartsWith("etc", StringComparison.OrdinalIgnoreCase)
           let tz = tzdb[id]
           let offset = tz.GetOffsetFromUtc(now)
           orderby offset, id
           select new
           {
               Id = id,
               DisplayValue = string.Format("({0}) {1}", offset.ToString("+HH:mm", null), id)
           };

// ultimately we build a dropdown list, but for demo purposes you can just dump the results
foreach (var item in list)
    Console.WriteLine(item.DisplayValue);

person Matt Johnson-Pint    schedule 24.10.2012    source источник


Ответы (2)


Noda Time 1.1 содержит данные zone.tab., поэтому теперь вы можете сделать следующее:

/// <summary>
/// Returns a list of valid timezones as a dictionary, where the key is
/// the timezone id, and the value can be used for display.
/// </summary>
/// <param name="countryCode">
/// The two-letter country code to get timezones for.
/// Returns all timezones if null or empty.
/// </param>
public IDictionary<string, string> GetTimeZones(string countryCode)
{
    var now = SystemClock.Instance.Now;
    var tzdb = DateTimeZoneProviders.Tzdb;

    var list = 
        from location in TzdbDateTimeZoneSource.Default.ZoneLocations
        where string.IsNullOrEmpty(countryCode) ||
              location.CountryCode.Equals(countryCode, 
                                          StringComparison.OrdinalIgnoreCase)
        let zoneId = location.ZoneId
        let tz = tzdb[zoneId]
        let offset = tz.GetZoneInterval(now).StandardOffset
        orderby offset, zoneId
        select new
        {
            Id = zoneId,
            DisplayValue = string.Format("({0:+HH:mm}) {1}", offset, zoneId)
        };

    return list.ToDictionary(x => x.Id, x => x.DisplayValue);
}

Альтернативный подход

Вместо того, чтобы предоставлять раскрывающийся список, вы можете использовать средство выбора часового пояса на основе карты.

введите описание изображения здесь

person Matt Johnson-Pint    schedule 13.06.2013

Получить стандартное смещение очень просто - tz.GetZoneInterval(now).StandardOffset. Это даст вам "текущее" стандартное смещение (зона может меняться со временем).

Фильтрация может подойти вам - я бы точно не сказал. Это определенно не идеально в том смысле, что идентификаторы на самом деле не предназначены для отображения. В идеале вы должны использовать «пример» Unicode CLDR, но в данный момент у нас нет интеграции с CLDR на этом фронте.

person Jon Skeet    schedule 25.10.2012
comment
Есть ли какой-нибудь чистый способ отфильтровать исторические часовые пояса? В базе данных TZ есть даты от и до, для которых действительны часовые пояса. Разоблачает ли Нода это каким-либо образом? - person Matt Johnson-Pint; 25.10.2012
comment
@MattJohnson: Нет - какая часть TZDB раскрывает это? Должен признаться, я никогда полностью не понимал формат, но если это что-то, что мы можем добавить, мы можем это сделать (хотя, вероятно, не для 1.0). (От и до обычно используются для определенных правил в пределах часового пояса, и я думал, что последнее правило всегда имеет максимальное значение до). - person Jon Skeet; 25.10.2012
comment
Я наткнулся на эту страницу 69.36.11.139/tzdb/tz-how-to.html что довольно хорошо объясняет. Кажется, что в записях Правил есть нужные мне данные. - person Matt Johnson-Pint; 25.10.2012
comment
Для фильтрации, я думаю, мне было бы интересно удалить любые часовые пояса, для которых не было правила max, но было окончательное значение To date, которое уже прошло. Но я предполагаю, что всегда есть хотя бы одно правило для каждой зоны, и я не знаю, так ли это или нет. - person Matt Johnson-Pint; 25.10.2012
comment
@MattJohnson: Не могли бы вы поднять запрос функции со ссылкой на эту веб-страницу и приведением Гонолулу в качестве примера? В настоящее время мы предполагаем (на самом деле в разных местах), что часовые пояса вечны ... похоже, что мы не должны. Это могло быть ... гм, интересно. - person Jon Skeet; 25.10.2012
comment
@MattJohnson: Отлично, спасибо. Надеюсь, что скоро выйдет 1.0 (правда, скоро) - это может быть первая функция post-v1, на которую я смотрю :) - person Jon Skeet; 25.10.2012
comment
Я только что нашел кое-что более полезное. timezonedb.com/download Я могу использовать его для создания селектора с двумя списками, где первый список - это страна код, а второй - список часовых поясов, отфильтрованный по выбранной стране. Должен сделать интерфейс более чистым, и я все еще могу использовать Noda для расчетов на основе выбранного часового пояса. - person Matt Johnson-Pint; 25.10.2012
comment
@MattJohnson: Круто - спасибо, что поделились этим; внимательно рассмотрим это. - person Jon Skeet; 25.10.2012
comment
Я обнаружил, что файл с timezonedb.com - это просто перехэш файла zone.tab в tzdata. Не похоже, что noda в настоящее время включает этот файл, поэтому я встроил его в свое собственное приложение. Я обновил вопрос кодом, который в конечном итоге использовал. - person Matt Johnson-Pint; 25.10.2012