Skip to content

Commit

Permalink
Adds timezone drop down for dynamic configuration modals
Browse files Browse the repository at this point in the history
  • Loading branch information
kingzacko1 committed Oct 28, 2021
1 parent e6f2fb2 commit 5b39367
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,19 @@
package org.graylog2.plugin.configuration.fields;

import com.google.common.collect.Maps;
import org.graylog2.shared.SuppressForbidden;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Instant;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
Expand All @@ -31,6 +40,8 @@ public class DropdownField extends AbstractConfigurationField {

private String defaultValue;
private final Map<String, String> values;
private static final int MILLIS_PER_HOUR = 3600000;
private static final int MILLIS_PER_MINUTE = 60000;

public DropdownField(String name, String humanName, String defaultValue, Map<String, String> values, Optional isOptional) {
this(name, humanName, defaultValue, values, null, isOptional);
Expand Down Expand Up @@ -79,10 +90,63 @@ public static Map<String, String> timeUnits() {
return units;
}

/**
* Returns a sorted map of available Time Zones that is first sorted by UTC offset and then alphabetically
*
* @return map of sorted timezones
*/
public static Map<String, String> timeZones() {
Map<Integer, List<DateTimeZone>> offsetsAndTimezones = buildSortedTimeZoneMap();
Map<String, String> timezones = new LinkedHashMap<>();
for (List<DateTimeZone> dtzList : offsetsAndTimezones.values()) {
for (DateTimeZone dtz : dtzList) {
timezones.put(dtz.getID(), buildTimeZoneDisplayName(dtz));
}
}
return timezones;
}

public static Map<String, String> valueMapFromEnum(Class<? extends Enum> enumClass, Function<Enum, String> valueMapping) {
return Arrays.stream(enumClass.getEnumConstants()).collect(Collectors.toMap(Enum::toString, valueMapping));
}

// Builds an ordered map of timezones sorted first by the timezone offset (keys of the TreeMap) and then
// alphabetically the timezones in that specific offset. Iterating over the map and keys maintains that order
@SuppressForbidden("Intentionally use system default timezone")
private static Map<Integer, List<DateTimeZone>> buildSortedTimeZoneMap() {
// get a sorted list of DateTimeZones based on their IDs
List<DateTimeZone> dtzList = DateTimeZone.getAvailableIDs().stream()
.map(DateTimeZone::forID)
.sorted(Comparator.comparing(DateTimeZone::getID, String.CASE_INSENSITIVE_ORDER))
.collect(Collectors.toList());

// iterate over the sorted list and group each DateTimeZone by offset
// TreeMap is an implementation of an OrderedMap where keys are ordered
Map<Integer, List<DateTimeZone>> offsetsAndTimezones = new TreeMap<>();
Instant now = new DateTime(DateTimeZone.getDefault()).toInstant();
for (DateTimeZone dtz : dtzList) {
int rawOffset = dtz.getOffset(now);
List<DateTimeZone> timezonesForOffset = offsetsAndTimezones.getOrDefault(rawOffset, new ArrayList<>());
timezonesForOffset.add(dtz);
offsetsAndTimezones.put(rawOffset, timezonesForOffset);
}

return offsetsAndTimezones;
}

@SuppressForbidden("Intentionally use system default timezone")
private static String buildTimeZoneDisplayName(DateTimeZone dtz) {
Instant now = new DateTime(DateTimeZone.getDefault()).toInstant();
int offset = dtz.getOffset(now);
int offsetHours = offset / MILLIS_PER_HOUR;
int remainderOffset = offset % MILLIS_PER_HOUR;
if (remainderOffset < 0) {
remainderOffset *= -1;
}
// some timezones have a half hour or three-quarter hour offsets included - handle them here
int offsetMinutes = remainderOffset / MILLIS_PER_MINUTE;
return String.format(Locale.getDefault(), "UTC%+03d:%02d - %s", offsetHours, offsetMinutes, dtz.getID());
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
*/
package org.graylog2.plugin.configuration.fields;

import org.graylog2.shared.SuppressForbidden;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Instant;
import org.junit.Test;

import java.util.Locale;
Expand All @@ -32,7 +36,33 @@ private enum TestEnum {
public void testBuildEnumMap() throws Exception {
final Map<String, String> enumMap = DropdownField.ValueTemplates.valueMapFromEnum(TestEnum.class, (t) -> t.name().toLowerCase(Locale.ENGLISH));
assertThat(enumMap)
.containsEntry("ONE", "one")
.containsEntry("TWO", "two");
.containsEntry("ONE", "one")
.containsEntry("TWO", "two");
}

/**
* Test that the timezone map for DropdownFields is ordered first by offset and then alphabetically
*/
@Test
@SuppressForbidden("Intentionally use system default timezone")
public void testBuildTimeZoneMap() {
Map<String, String> timezones = DropdownField.ValueTemplates.timeZones();
DateTimeZone curDTZ = null;
int curOffset = 0;
Instant now = new DateTime().withZone(DateTimeZone.getDefault()).toInstant();
for (String tz : timezones.keySet()) {
DateTimeZone nextDTZ = DateTimeZone.forID(tz);
int nextOffset = nextDTZ.getOffset(now);
if (curDTZ != null) {
// current offset should always be less than or equal to the next
assertThat(curOffset).isLessThanOrEqualTo(nextOffset);
// if the timezones have the same offset, current should come before next alphabetically
if (curOffset == nextOffset) {
assertThat(curDTZ.getID().toLowerCase().compareTo(nextDTZ.getID().toLowerCase())).isLessThan(0);
}
}
curDTZ = nextDTZ;
curOffset = nextOffset;
}
}
}

0 comments on commit 5b39367

Please sign in to comment.