public static List<Map<String, Object>> getZonesList(Context context) {
    final Locale locale = Locale.getDefault();
    final Date now = new Date();

    // The display name chosen for each zone entry depends on whether the zone is one associated
    // with the country of the user's chosen locale. For "local" zones we prefer the "long name"
    // (e.g. "Europe/London" -> "British Summer Time" for people in the UK). For "non-local"
    // zones we prefer the exemplar location (e.g. "Europe/London" -> "London" for English
    // speakers from outside the UK). This heuristic is based on the fact that people are
    // typically familiar with their local timezones and exemplar locations don't always match
    // modern-day expectations for people living in the country covered. Large countries like
    // China that mostly use a single timezone (olson id: "Asia/Shanghai") may not live near
    // "Shanghai" and prefer the long name over the exemplar location. The only time we don't
    // follow this policy for local zones is when Android supplies multiple olson IDs to choose
    // from and the use of a zone's long name leads to ambiguity. For example, at the time of
    // writing Android lists 5 olson ids for Australia which collapse to 2 different zone names
    // in winter but 4 different zone names in summer. The ambiguity leads to the users
    // selecting the wrong olson ids.

    // Get the list of olson ids to display to the user.
    List<String> olsonIdsToDisplay = readTimezonesToDisplay(context);

    // Create a lookup of local zone IDs.
    Set<String> localZoneIds = new TreeSet<String>();
    for (String olsonId : TimeZoneNames.forLocale(locale)) {
      localZoneIds.add(olsonId);
    }

    // Work out whether the long names for the local entries that we would show by default would
    // be ambiguous.
    Set<String> localZoneNames = new TreeSet<String>();
    boolean localLongNamesAreAmbiguous = false;
    for (String olsonId : olsonIdsToDisplay) {
      if (localZoneIds.contains(olsonId)) {
        TimeZone tz = TimeZone.getTimeZone(olsonId);
        String zoneLongName = getZoneLongName(locale, tz, now);
        boolean longNameIsUnique = localZoneNames.add(zoneLongName);
        if (!longNameIsUnique) {
          localLongNamesAreAmbiguous = true;
          break;
        }
      }
    }

    // Generate the list of zone entries to return.
    List<Map<String, Object>> zones = new ArrayList<Map<String, Object>>();
    for (String olsonId : olsonIdsToDisplay) {
      final TimeZone tz = TimeZone.getTimeZone(olsonId);
      // Exemplar location display is the default. The only time we intend to display the long
      // name is when the olsonId is local AND long names are not ambiguous.
      boolean isLocalZoneId = localZoneIds.contains(olsonId);
      boolean preferLongName = isLocalZoneId && !localLongNamesAreAmbiguous;
      String displayName = getZoneDisplayName(locale, tz, now, preferLongName);

      String gmtOffsetString = getGmtOffsetString(locale, tz, now);
      int offsetMillis = tz.getOffset(now.getTime());
      Map<String, Object> displayEntry =
          createDisplayEntry(tz, gmtOffsetString, displayName, offsetMillis);
      zones.add(displayEntry);
    }
    return zones;
  }