From ece7b8548d0cfcb596bb942c2c7a9e00c801dcc6 Mon Sep 17 00:00:00 2001 From: Leo Romanovsky Date: Mon, 15 Apr 2024 16:36:53 -0700 Subject: [PATCH] Ensure assignment date is in ISO8601 format (FF-1933) --- eppo/build.gradle | 2 +- .../java/cloud/eppo/android/EppoClient.java | 16 +++-- .../eppo/android/logging/Assignment.java | 17 ++++- .../java/cloud/eppo/android/util/Utils.java | 10 ++- .../java/cloud/eppo/android/UtilsTest.java | 62 +++++++++++++++++++ 5 files changed, 93 insertions(+), 14 deletions(-) create mode 100644 eppo/src/test/java/cloud/eppo/android/UtilsTest.java diff --git a/eppo/build.gradle b/eppo/build.gradle index 8138dc60..b123caac 100644 --- a/eppo/build.gradle +++ b/eppo/build.gradle @@ -4,7 +4,7 @@ plugins { } group = "cloud.eppo" -version = "1.0.7" +version = "1.0.8" android { compileSdk 33 diff --git a/eppo/src/main/java/cloud/eppo/android/EppoClient.java b/eppo/src/main/java/cloud/eppo/android/EppoClient.java index 55f62611..d6ee1e8c 100644 --- a/eppo/src/main/java/cloud/eppo/android/EppoClient.java +++ b/eppo/src/main/java/cloud/eppo/android/EppoClient.java @@ -39,7 +39,8 @@ public class EppoClient { // Useful for testing in situations where we want to mock the http client private static EppoHttpClient httpClientOverride = null; - private EppoClient(Application application, String apiKey, String host, AssignmentLogger assignmentLogger, boolean isGracefulMode) { + private EppoClient(Application application, String apiKey, String host, AssignmentLogger assignmentLogger, + boolean isGracefulMode) { EppoHttpClient httpClient = httpClientOverride == null ? new EppoHttpClient(host, apiKey) : httpClientOverride; ConfigurationStore configStore = new ConfigurationStore(application); requestor = new ConfigurationRequestor(configStore, httpClient); @@ -133,7 +134,7 @@ protected EppoValue getTypedAssignment(String subjectKey, String flagKey, Subjec String allocationKey = rule.getAllocationKey(); Allocation allocation = flag.getAllocations().get(allocationKey); if (allocation == null) { - Log.w(TAG, "unexpected unknown allocation key \""+allocationKey+"\""); + Log.w(TAG, "unexpected unknown allocation key \"" + allocationKey + "\""); return null; } @@ -157,9 +158,14 @@ protected EppoValue getTypedAssignment(String subjectKey, String flagKey, Subjec variationToLog = typedValue.stringValue(); } - Assignment assignment = new Assignment(experimentKey, - flagKey, allocationKey, variationToLog, - subjectKey, Utils.getISODate(new Date()), subjectAttributes); + Assignment assignment = Assignment.createWithCurrentDate( + experimentKey, + flagKey, + allocationKey, + variationToLog, + subjectKey, + subjectAttributes + ); assignmentLogger.logAssignment(assignment); } diff --git a/eppo/src/main/java/cloud/eppo/android/logging/Assignment.java b/eppo/src/main/java/cloud/eppo/android/logging/Assignment.java index 97aba54c..666f36d5 100644 --- a/eppo/src/main/java/cloud/eppo/android/logging/Assignment.java +++ b/eppo/src/main/java/cloud/eppo/android/logging/Assignment.java @@ -1,6 +1,9 @@ package cloud.eppo.android.logging; import cloud.eppo.android.dto.SubjectAttributes; +import cloud.eppo.android.util.Utils; + +import java.util.Date; public class Assignment { private String experiment; @@ -18,8 +21,7 @@ public Assignment( String variation, String subject, String timestamp, - SubjectAttributes subjectAttributes - ) { + SubjectAttributes subjectAttributes) { this.experiment = experiment; this.featureFlag = featureFlag; this.allocation = allocation; @@ -29,6 +31,17 @@ public Assignment( this.subjectAttributes = subjectAttributes; } + public static Assignment createWithCurrentDate( + String experiment, + String featureFlag, + String allocation, + String variation, + String subject, + SubjectAttributes subjectAttributes) { + return new Assignment(experiment, featureFlag, allocation, variation, subject, + Utils.getISODate(new Date()), subjectAttributes); + } + public String getExperiment() { return experiment; } diff --git a/eppo/src/main/java/cloud/eppo/android/util/Utils.java b/eppo/src/main/java/cloud/eppo/android/util/Utils.java index e84d11ee..2f5c28d4 100644 --- a/eppo/src/main/java/cloud/eppo/android/util/Utils.java +++ b/eppo/src/main/java/cloud/eppo/android/util/Utils.java @@ -6,10 +6,9 @@ import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; -import java.util.TimeZone; +import java.util.Locale; import cloud.eppo.android.dto.ShardRange; @@ -50,10 +49,9 @@ public static void validateNotEmptyOrNull(String input, String errorMessage) { } public static String getISODate(Date date) { - TimeZone tz = TimeZone.getTimeZone("UTC"); - DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - df.setTimeZone(tz); - return df.format(date); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); + dateFormat.setTimeZone(java.util.TimeZone.getTimeZone("UTC")); + return dateFormat.format(date); } public static SharedPreferences getSharedPrefs(Context context) { diff --git a/eppo/src/test/java/cloud/eppo/android/UtilsTest.java b/eppo/src/test/java/cloud/eppo/android/UtilsTest.java new file mode 100644 index 00000000..658afd1f --- /dev/null +++ b/eppo/src/test/java/cloud/eppo/android/UtilsTest.java @@ -0,0 +1,62 @@ +package cloud.eppo.android; + +import org.junit.Test; +import static org.junit.Assert.*; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +import cloud.eppo.android.util.Utils; + +public class UtilsTest { + + @Test + public void testGetISODate() { + String isoDate = Utils.getISODate(new Date()); + assertNotNull("ISO date should not be null", isoDate); + + // Verify the format + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + try { + Date date = dateFormat.parse(isoDate); + assertNotNull("Parsed date should not be null", date); + + // Optionally, verify the date is not too far from the current time + long currentTime = System.currentTimeMillis(); + long parsedTime = date.getTime(); + assertTrue("The parsed date should be within a reasonable range of the current time", + Math.abs(currentTime - parsedTime) < 10000); // for example, within 10 seconds + } catch (ParseException e) { + fail("Parsing the ISO date failed: " + e.getMessage()); + } + } + + @Test + public void testGetCurrentDateISOInDifferentLocale() { + // Arrange + Locale defaultLocale = Locale.getDefault(); + try { + // Set locale to Arabic + Locale.setDefault(new Locale("ar")); + String isoDate = Utils.getISODate(new Date()); + + // Act + // Check if the date is in the correct ISO 8601 format + // This is a simple regex check to see if the string follows the + // YYYY-MM-DDTHH:MM:SSZ pattern + boolean isISO8601 = isoDate.matches("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z"); + + // Assert + assertTrue("Date should be in ISO 8601 format", isISO8601); + + } catch (Exception e) { + fail("Test failed with exception: " + e.getMessage()); + } finally { + // Reset locale back to original + Locale.setDefault(defaultLocale); + } + } +} \ No newline at end of file