diff --git a/metron-stellar/stellar-common/README.md b/metron-stellar/stellar-common/README.md index 468f358012..0f3bb6acac 100644 --- a/metron-stellar/stellar-common/README.md +++ b/metron-stellar/stellar-common/README.md @@ -169,6 +169,7 @@ Where: | [ `CHOP`](#chop) | | [ `CHOMP`](#chomp) | | [ `COUNT_MATCHES`](#count_matches) | +| [ `DATE_FORMAT`](#date_format) | [ `DAY_OF_MONTH`](#day_of_month) | | [ `DAY_OF_WEEK`](#day_of_week) | | [ `DAY_OF_YEAR`](#day_of_year) | @@ -379,6 +380,14 @@ Where: * substring/character - the substring or character to count, may be null. * Returns: the number of non-overlapping occurrences, 0 if either CharSequence is null. +### `DATE_FORMAT` + * Description: Takes an epoch timestamp and converts it to a date format. + * Input: + * format - DateTime format as a String. + * timestampField - Optional epoch time in Long format. Defaults to now. + * timezone - Optional timezone in String format. + * Returns: Formatted date. + ### `DAY_OF_MONTH` * Description: The numbered day within the month. The first day within the month has a value of 1. * Input: diff --git a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/functions/DateFunctions.java b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/functions/DateFunctions.java index 212d6e9fa1..17f5f8dcc0 100644 --- a/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/functions/DateFunctions.java +++ b/metron-stellar/stellar-common/src/main/java/org/apache/metron/stellar/dsl/functions/DateFunctions.java @@ -28,6 +28,7 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; +import java.util.Date; import java.util.List; import java.util.Optional; import java.util.TimeZone; @@ -109,6 +110,13 @@ public static long getEpochTime(String date, String format, Optional tim return sdf.parse(date).getTime(); } + public static String getDateFormat(String format, Optional epochTime, Optional timezone) { + Long time = epochTime.orElseGet(System::currentTimeMillis); + TimezonedFormat fmt = timezone.map(s -> new TimezonedFormat(format, s)).orElseGet(() -> new TimezonedFormat(format)); + SimpleDateFormat sdf = formatCache.get(fmt).get(); + return sdf.format(new Date(time)); + } + /** * Stellar Function: TO_EPOCH_TIMESTAMP @@ -144,6 +152,40 @@ public Object apply(List objects) { } } + @Stellar( name="DATE_FORMAT", + description = "Takes an epoch timestamp and converts it to a date format.", + params = {"format - DateTime format as a String." + , "timestampField - Optional epoch time in Long format. Defaults to now." + , "timezone - Optional timezone in String format."}, + returns="Formatted date." + ) + public static class DateFormat extends BaseStellarFunction { + + public Object apply(List objects) { + int size = objects.size(); + Optional formatObj = Optional.ofNullable(objects.get(0)); + Optional epochObj = Optional.empty(); + Optional tzObj = Optional.empty(); + if (size > 1) { + if (size == 2) { + if (objects.get(1) == null) { + return null; + } + epochObj = objects.get(1) instanceof Long ? Optional.of((Long) objects.get(1)) : Optional.empty(); + tzObj = objects.get(1) instanceof String ? Optional.of((String) objects.get(1)) : Optional.empty(); + } else { + epochObj = Optional.ofNullable((Long) objects.get(1)); + tzObj = Optional.ofNullable((String) objects.get(2)); + } + } + if(formatObj.isPresent()) { + return getDateFormat(formatObj.get().toString(), epochObj, tzObj); + } else { + return null; + } + } + } + /** * Gets the value from a list of arguments. * diff --git a/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/DateFunctionsTest.java b/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/DateFunctionsTest.java index 1f1f4f444a..959e7eac71 100644 --- a/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/DateFunctionsTest.java +++ b/metron-stellar/stellar-common/src/test/java/org/apache/metron/stellar/dsl/functions/DateFunctionsTest.java @@ -31,6 +31,7 @@ import java.util.Calendar; import java.util.HashMap; import java.util.Map; +import java.util.TimeZone; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -225,4 +226,41 @@ public void testDayOfYearNow() { public void testDayOfYearNull() { Object result = run("DAY_OF_YEAR(nada)"); } + + @Test + public void testDateFormat() { + Object result = run("DATE_FORMAT('EEE MMM dd yyyy hh:mm:ss zzz', epoch, 'EST')"); + assertEquals("Thu Aug 25 2016 08:27:10 EST", result); + } + + @Test + public void testDateFormatDefault() { + Object result = run("DATE_FORMAT('EEE MMM dd yyyy hh:mm:ss zzzz')"); + assertTrue(result.toString().endsWith(TimeZone.getDefault().getDisplayName(true, 1))); + } + + @Test + public void testDateFormatNow() { + Object result = run("DATE_FORMAT('EEE MMM dd yyyy hh:mm:ss zzz', 'GMT')"); + assertTrue(result.toString().endsWith("GMT")); + } + + @Test + public void testDateFormatDefaultTimezone() { + Object result = run("DATE_FORMAT('EEE MMM dd yyyy hh:mm:ss zzzz', epoch)"); + assertTrue(result.toString().endsWith(TimeZone.getDefault().getDisplayName(true, 1))); + } + + /** + * If refer to variable that does not exist, expect ParseException. + */ + @Test(expected = ParseException.class) + public void testDateFormatNull() { + Object result = run("DATE_FORMAT('EEE MMM dd yyyy hh:mm:ss zzz', nada, 'EST')"); + } + + @Test(expected = ParseException.class) + public void testDateFormatInvalid() { + Object result = run("DATE_FORMAT('INVALID DATE FORMAT', epoch, 'EST')"); + } }