static void TimeZoneNames_fillZoneStrings(JNIEnv* env, jclass, jstring javaLocaleName, jobjectArray result) {
  ScopedIcuLocale icuLocale(env, javaLocaleName);
  if (!icuLocale.valid()) {
    return;
  }

  UErrorCode status = U_ZERO_ERROR;
  UniquePtr<TimeZoneNames> names(TimeZoneNames::createInstance(icuLocale.locale(), status));
  if (maybeThrowIcuException(env, "TimeZoneNames::createInstance", status)) {
    return;
  }

  const UDate now(Calendar::getNow());

  static const UnicodeString kUtc("UTC", 3, US_INV);

  size_t id_count = env->GetArrayLength(result);
  for (size_t i = 0; i < id_count; ++i) {
    ScopedLocalRef<jobjectArray> java_row(env,
                                          reinterpret_cast<jobjectArray>(env->GetObjectArrayElement(result, i)));
    ScopedLocalRef<jstring> java_zone_id(env,
                                         reinterpret_cast<jstring>(env->GetObjectArrayElement(java_row.get(), 0)));
    ScopedJavaUnicodeString zone_id(env, java_zone_id.get());
    if (!zone_id.valid()) {
      return;
    }

    UnicodeString long_std;
    names->getDisplayName(zone_id.unicodeString(), UTZNM_LONG_STANDARD, now, long_std);
    UnicodeString short_std;
    names->getDisplayName(zone_id.unicodeString(), UTZNM_SHORT_STANDARD, now, short_std);
    UnicodeString long_dst;
    names->getDisplayName(zone_id.unicodeString(), UTZNM_LONG_DAYLIGHT, now, long_dst);
    UnicodeString short_dst;
    names->getDisplayName(zone_id.unicodeString(), UTZNM_SHORT_DAYLIGHT, now, short_dst);

    if (isUtc(zone_id.unicodeString())) {
      // ICU doesn't have names for the UTC zones; it just says "GMT+00:00" for both
      // long and short names. We don't want this. The best we can do is use "UTC"
      // for everything (since we don't know how to say "Universal Coordinated Time" in
      // every language).
      // TODO: check CLDR doesn't actually have this somewhere.
      long_std = short_std = long_dst = short_dst = kUtc;
    }

    bool okay =
        setStringArrayElement(env, java_row.get(), 1, long_std) &&
        setStringArrayElement(env, java_row.get(), 2, short_std) &&
        setStringArrayElement(env, java_row.get(), 3, long_dst) &&
        setStringArrayElement(env, java_row.get(), 4, short_dst);
    if (!okay) {
      return;
    }
  }
}
static jobjectArray TimeZones_getZoneStringsImpl(JNIEnv* env, jclass, jstring localeName, jobjectArray timeZoneIds) {
    Locale locale = getLocale(env, localeName);

    // We could use TimeZone::getDisplayName, but that's even slower
    // because it creates a new SimpleDateFormat each time.
    // We're better off using SimpleDateFormat directly.

    // We can't use DateFormatSymbols::getZoneStrings because that
    // uses its own set of time zone ids and contains empty strings
    // instead of GMT offsets (a pity, because it's a bit faster than this code).

    UErrorCode status = U_ZERO_ERROR;
    UnicodeString longPattern("zzzz", 4, US_INV);
    SimpleDateFormat longFormat(longPattern, locale, status);
    // 'z' only uses "common" abbreviations. 'V' allows all known abbreviations.
    // For example, "PST" is in common use in en_US, but "CET" isn't.
    UnicodeString commonShortPattern("z", 1, US_INV);
    SimpleDateFormat shortFormat(commonShortPattern, locale, status);
    UnicodeString allShortPattern("V", 1, US_INV);
    SimpleDateFormat allShortFormat(allShortPattern, locale, status);

    UnicodeString utc("UTC", 3, US_INV);

    // TODO: use of fixed dates prevents us from using the correct historical name when formatting dates.
    // TODO: use of dates not in the current year could cause us to output obsoleted names.
    // 15th January 2008
    UDate date1 = 1203105600000.0;
    // 15th July 2008
    UDate date2 = 1218826800000.0;

    // In the first pass, we get the long names for the time zone.
    // We also get any commonly-used abbreviations.
    std::vector<TimeZoneNames> table;
    typedef std::map<UnicodeString, UnicodeString*> AbbreviationMap;
    AbbreviationMap usedAbbreviations;
    size_t idCount = env->GetArrayLength(timeZoneIds);
    for (size_t i = 0; i < idCount; ++i) {
        ScopedLocalRef<jstring> javaZoneId(env,
                reinterpret_cast<jstring>(env->GetObjectArrayElement(timeZoneIds, i)));
        ScopedJavaUnicodeString zoneId(env, javaZoneId.get());
        UnicodeString id(zoneId.unicodeString());

        TimeZoneNames row;
        if (isUtc(id)) {
            // ICU doesn't have names for the UTC zones; it just says "GMT+00:00" for both
            // long and short names. We don't want this. The best we can do is use "UTC"
            // for everything (since we don't know how to say "Universal Coordinated Time").
            row.tz = NULL;
            row.longStd = row.shortStd = row.longDst = row.shortDst = utc;
            table.push_back(row);
            usedAbbreviations[utc] = &utc;
            continue;
        }

        row.tz = TimeZone::createTimeZone(id);
        longFormat.setTimeZone(*row.tz);
        shortFormat.setTimeZone(*row.tz);

        int32_t daylightOffset;
        int32_t rawOffset;
        row.tz->getOffset(date1, false, rawOffset, daylightOffset, status);
        if (daylightOffset != 0) {
            // The TimeZone is reporting that we are in daylight time for the winter date.
            // The dates are for the wrong hemisphere, so swap them.
            row.standardDate = date2;
            row.daylightSavingDate = date1;
        } else {
            row.standardDate = date1;
            row.daylightSavingDate = date2;
        }

        longFormat.format(row.standardDate, row.longStd);
        shortFormat.format(row.standardDate, row.shortStd);
        if (row.tz->useDaylightTime()) {
            longFormat.format(row.daylightSavingDate, row.longDst);
            shortFormat.format(row.daylightSavingDate, row.shortDst);
        } else {
            row.longDst = row.longStd;
            row.shortDst = row.shortStd;
        }

        table.push_back(row);
        usedAbbreviations[row.shortStd] = &row.longStd;
        usedAbbreviations[row.shortDst] = &row.longDst;
    }

    // In the second pass, we create the Java String[][].
    // We also look for any uncommon abbreviations that don't conflict with ones we've already seen.
    jobjectArray result = env->NewObjectArray(idCount, JniConstants::stringArrayClass, NULL);
    UnicodeString gmt("GMT", 3, US_INV);
    for (size_t i = 0; i < table.size(); ++i) {
        TimeZoneNames& row(table[i]);
        // Did we get a GMT offset instead of an abbreviation?
        if (row.shortStd.length() > 3 && row.shortStd.startsWith(gmt)) {
            // See if we can do better...
            UnicodeString uncommonStd, uncommonDst;
            allShortFormat.setTimeZone(*row.tz);
            allShortFormat.format(row.standardDate, uncommonStd);
            if (row.tz->useDaylightTime()) {
                allShortFormat.format(row.daylightSavingDate, uncommonDst);
            } else {
                uncommonDst = uncommonStd;
            }

            // If this abbreviation isn't already in use, we can use it.
            AbbreviationMap::iterator it = usedAbbreviations.find(uncommonStd);
            if (it == usedAbbreviations.end() || *(it->second) == row.longStd) {
                row.shortStd = uncommonStd;
                usedAbbreviations[row.shortStd] = &row.longStd;
            }
            it = usedAbbreviations.find(uncommonDst);
            if (it == usedAbbreviations.end() || *(it->second) == row.longDst) {
                row.shortDst = uncommonDst;
                usedAbbreviations[row.shortDst] = &row.longDst;
            }
        }
        // Fill in whatever we got.
        ScopedLocalRef<jobjectArray> javaRow(env, env->NewObjectArray(5, JniConstants::stringClass, NULL));
        ScopedLocalRef<jstring> id(env, reinterpret_cast<jstring>(env->GetObjectArrayElement(timeZoneIds, i)));
        env->SetObjectArrayElement(javaRow.get(), 0, id.get());
        setStringArrayElement(env, javaRow.get(), 1, row.longStd);
        setStringArrayElement(env, javaRow.get(), 2, row.shortStd);
        setStringArrayElement(env, javaRow.get(), 3, row.longDst);
        setStringArrayElement(env, javaRow.get(), 4, row.shortDst);
        env->SetObjectArrayElement(result, i, javaRow.get());
        delete row.tz;
    }

    return result;
}