Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#295 Proposal for extension of function fromMillis #296

Merged
merged 4 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import com.api.jsonata4java.expressions.functions.FormatBaseFunction;
import com.api.jsonata4java.expressions.functions.FormatNumberFunction;
import com.api.jsonata4java.expressions.functions.FromMillisFunction;
import com.api.jsonata4java.expressions.functions.FromMillisZonedFunction;
import com.api.jsonata4java.expressions.functions.FunctionBase;
import com.api.jsonata4java.expressions.functions.JoinFunction;
import com.api.jsonata4java.expressions.functions.KeysFunction;
Expand Down Expand Up @@ -111,6 +112,7 @@ public class Constants implements Serializable {
public static final String FUNCTION_REPLACE = "$replace";
public static final String FUNCTION_NOW = "$now";
public static final String FUNCTION_FROM_MILLIS = "$fromMillis";
public static final String FUNCTION_FROM_MILLIS_ZONED = "$fromMillisZoned";
public static final String FUNCTION_FORMAT_NUMBER = "$formatNumber";
public static final String FUNCTION_FORMAT_BASE = "$formatBase";
public static final String FUNCTION_BASE64_ENCODE = "$base64encode";
Expand Down Expand Up @@ -193,6 +195,7 @@ public class Constants implements Serializable {
FUNCTIONS.put(FUNCTION_REPLACE, new ReplaceFunction());
FUNCTIONS.put(FUNCTION_NOW, new NowFunction());
FUNCTIONS.put(FUNCTION_FROM_MILLIS, new FromMillisFunction());
FUNCTIONS.put(FUNCTION_FROM_MILLIS_ZONED, new FromMillisZonedFunction());
FUNCTIONS.put(FUNCTION_FORMAT_NUMBER, new FormatNumberFunction());
FUNCTIONS.put(FUNCTION_FORMAT_BASE, new FormatBaseFunction());
FUNCTIONS.put(FUNCTION_BASE64_ENCODE, new Base64EncodeFunction());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.time.LocalDateTime;
import java.time.Month;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.temporal.IsoFields;
import java.time.temporal.WeekFields;
import java.util.Collections;
Expand All @@ -36,14 +37,17 @@
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.TimeZone;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;

import com.api.jsonata4java.expressions.EvaluateRuntimeException;

public class DateTimeUtils implements Serializable {
Expand Down Expand Up @@ -718,6 +722,48 @@ public static String formatDateTime(long millis, String picture, String timezone
return result;
}

public static String formatDateTimeFromZoneId(long millis, String picture, String zoneId)
{
String workTimezone = zoneId;

if (workTimezone == null)
{
workTimezone = "Z";
}

TimeZone zone = TimeZone.getTimeZone(workTimezone);
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(millis), zone.toZoneId());

PictureFormat formatSpec;
if (picture == null)
{
if (iso8601Spec == null)
{
iso8601Spec = analyseDateTimePicture("[Y0001]-[M01]-[D01]T[H01]:[m01]:[s01].[f001][Z01:01t]");
}
formatSpec = iso8601Spec;
}
else
{
formatSpec = analyseDateTimePicture(picture);
}

String result = "";
for (SpecPart part : formatSpec.parts)
{
if (part.type.equals("literal"))
{
result += part.value;
}
else
{
result += formatComponent(zonedDateTime, part);
}
}

return result;
}

private static String formatComponent(LocalDateTime date, SpecPart markerSpec, int offsetHours, int offsetMinutes) {
String componentValue = getDateTimeFragment(date, markerSpec.component);

Expand Down Expand Up @@ -785,6 +831,119 @@ private static String formatComponent(LocalDateTime date, SpecPart markerSpec, i
return componentValue;
}

private static String formatComponent(ZonedDateTime date, SpecPart markerSpec)
{
String componentValue = getDateTimeFragment(date, markerSpec.component);

if ("YMDdFWwXxHhms".indexOf(markerSpec.component) != -1)
{
if (markerSpec.component == 'Y')
{
if (markerSpec.n != -1)
{
componentValue = "" + (int)(Integer.parseInt(componentValue) % Math.pow(10, markerSpec.n));
}
}
if (markerSpec.names != null)
{
if (markerSpec.component == 'M' || markerSpec.component == 'x')
{
componentValue = months[Integer.parseInt(componentValue) - 1];
}
else if (markerSpec.component == 'F')
{
componentValue = days[Integer.parseInt(componentValue)];
}
else
{
throw new EvaluateRuntimeException(String.format(Constants.ERR_MSG_INVALID_NAME_MODIFIER, markerSpec.component));
}
if (markerSpec.names == tcase.UPPER)
{
componentValue = componentValue.toUpperCase();
}
else if (markerSpec.names == tcase.LOWER)
{
componentValue = componentValue.toLowerCase();
}
if (markerSpec.width != null && componentValue.length() > markerSpec.width.getRight())
{
componentValue = componentValue.substring(0, markerSpec.width.getRight());
}
}
else
{
componentValue = formatInteger(Integer.parseInt(componentValue), markerSpec.integerFormat);
}
}
else if (markerSpec.component == 'f')
{
componentValue = formatInteger(Integer.parseInt(componentValue), markerSpec.integerFormat);
}
else if (markerSpec.component == 'Z' || markerSpec.component == 'z')
{
ZoneOffset zoneOffset = ZoneOffset.from(date.getOffset());
int offsetHours = 0;
int offsetMinutes = 0;

if (!zoneOffset.equals(ZoneOffset.UTC))
{
String[] offsetData = zoneOffset.getId().replaceAll("\\+\\-", "").split(":");
offsetHours = Integer.parseInt(offsetData[0]);
offsetMinutes = Integer.parseInt(offsetData[1]);
}

int offset = offsetHours * 100 + offsetMinutes;
if (markerSpec.integerFormat.regular)
{
componentValue = formatInteger(offset, markerSpec.integerFormat);
}
else
{
int numDigits = markerSpec.integerFormat.mandatoryDigits;
if (numDigits == 1 || numDigits == 2)
{
componentValue = formatInteger(offsetHours, markerSpec.integerFormat);
if (offsetMinutes != 0)
{
componentValue += ":" + formatInteger(offsetMinutes, "00");
}
}
else if (numDigits == 3 || numDigits == 4)
{
componentValue = formatInteger(offset, markerSpec.integerFormat);
}
else
{
throw new EvaluateRuntimeException(Constants.ERR_MSG_TIMEZONE_FORMAT);
}
}
if (offset >= 0)
{
componentValue = "+" + componentValue;
}
if (markerSpec.component == 'z')
{
componentValue = "GMT" + componentValue;
}
if (offset == 0 && markerSpec.presentation2 != null && markerSpec.presentation2 == 't')
{
componentValue = "Z";
}
}
else if (markerSpec.component == 'P')
{
// §9.8.4.7 Formatting Other Components
// Formatting P for am/pm
// getDateTimeFragment() always returns am/pm lower case so check for UPPER here
if (markerSpec.names == tcase.UPPER)
{
componentValue = componentValue.toUpperCase();
}
}
return componentValue;
}

private static String getDateTimeFragment(LocalDateTime date, Character component) {
String componentValue = "";
switch (component) {
Expand Down Expand Up @@ -851,6 +1010,77 @@ private static String getDateTimeFragment(LocalDateTime date, Character componen
return componentValue;
}

private static String getDateTimeFragment(ZonedDateTime date, Character component)
{
String componentValue = "";
switch (component)
{
case 'Y' : // year
componentValue = "" + date.getYear();
break;
case 'M' : // month in year
componentValue = "" + date.getMonthValue();
break;
case 'D' : // day in month
componentValue = "" + date.getDayOfMonth();
break;
case 'd' : // day in year
componentValue = "" + date.getDayOfYear();
break;
case 'F' : // day of week
componentValue = "" + date.getDayOfWeek().getValue();
break;
case 'W' : // week in year
componentValue = "" + date.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
break;
case 'w' : // week in month
componentValue = "" + date.get(WeekFields.ISO.weekOfMonth());
break;
case 'X' :
// TODO work these out once others verified
case 'x' :
componentValue = "" + -1;
break;
case 'H' : // hour in day (24 hours)
componentValue = "" + date.getHour();
break;
case 'h' : // hour in day (12 hours)
int hour = date.getHour();
if (hour > 12)
{
hour -= 12;
}
else if (hour == 0)
{
hour = 12;
}
componentValue = "" + hour;
break;
case 'P' :
componentValue = date.getHour() < 12 ? "am" : "pm";
break;
case 'm' :
componentValue = "" + date.getMinute();
break;
case 's' :
componentValue = "" + date.getSecond();
break;
case 'f' :
componentValue = "" + date.getNano() / 1000000;
break;
case 'Z' :
case 'z' :
break;
case 'C' :
componentValue = "ISO";
break;
case 'E' :
componentValue = "ISO";
break;
}
return componentValue;
}

public static Long parseDateTime(String timestamp, String picture) {
PictureFormat formatSpec = analyseDateTimePicture(picture);
PictureMatcher matchSpec = generateRegex(formatSpec);
Expand Down
2 changes: 2 additions & 0 deletions src/test/java/com/api/jsonata4java/AgnosticTestSuite.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import com.api.jsonata4java.expressions.FormatBaseFunctionTests;
import com.api.jsonata4java.expressions.FormatNumberFunctionTests;
import com.api.jsonata4java.expressions.FromMillisFunctionTests;
import com.api.jsonata4java.expressions.FromMillisZonedFunctionTests;
import com.api.jsonata4java.expressions.FunctionChainingTests;
import com.api.jsonata4java.expressions.InvalidSyntaxTest;
import com.api.jsonata4java.expressions.JoinFunctionTests;
Expand Down Expand Up @@ -352,6 +353,7 @@ private void init() throws Exception {
runComponentTest(FormatBaseFunctionTests.data());
runComponentTest(FormatNumberFunctionTests.data());
runComponentTest(FromMillisFunctionTests.data());
runComponentTest(FromMillisZonedFunctionTests.data());
runComponentTest(JoinFunctionTests.data());
runComponentTest(LengthFunctionTests.data());
runComponentTest(LowercaseFunctionTests.data());
Expand Down
Loading
Loading