Skip to content

Commit

Permalink
timezone (finos#3060)
Browse files Browse the repository at this point in the history
  • Loading branch information
AFine-gs authored Sep 11, 2024
1 parent 027c1ae commit c6f09a3
Show file tree
Hide file tree
Showing 17 changed files with 177 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1450,6 +1450,7 @@ private void registerDates()
h("meta::pure::functions::date::year_Date_$0_1$__Integer_$0_1$_", false, ps -> res("Integer", "zeroOne"), ps -> typeZeroOne(ps.get(0), DATE)));

register("meta::pure::functions::date::adjust_Date_1__Integer_1__DurationUnit_1__Date_1_", true, ps -> res("Date", "one"));
register("meta::pure::functions::date::convertTimeZone_DateTime_1__String_1__String_1__String_1_", false, ps -> res("String", "one"));

register(m(m(h("meta::pure::functions::date::date_Integer_1__Date_1_", true, ps -> res("Date", "one"), ps -> ps.size() == 1)),
m(h("meta::pure::functions::date::date_Integer_1__Integer_1__Date_1_", true, ps -> res("Date", "one"), ps -> ps.size() == 2)),
Expand Down Expand Up @@ -2450,6 +2451,7 @@ private Map<String, Dispatch> buildDispatch()
map.put("meta::pure::functions::constraints::warn_Boolean_1__String_1__Boolean_1_", (List<ValueSpecification> ps) -> ps.size() == 2 && isOne(ps.get(0)._multiplicity()) && ("Nil".equals(ps.get(0)._genericType()._rawType()._name()) || "Boolean".equals(ps.get(0)._genericType()._rawType()._name())) && isOne(ps.get(1)._multiplicity()) && ("Nil".equals(ps.get(1)._genericType()._rawType()._name()) || "String".equals(ps.get(1)._genericType()._rawType()._name())));
map.put("meta::pure::functions::date::add_StrictDate_1__Duration_1__StrictDate_1_", (List<ValueSpecification> ps) -> ps.size() == 2 && isOne(ps.get(0)._multiplicity()) && ("Nil".equals(ps.get(0)._genericType()._rawType()._name()) || "StrictDate".equals(ps.get(0)._genericType()._rawType()._name())) && isOne(ps.get(1)._multiplicity()) && ("Nil".equals(ps.get(1)._genericType()._rawType()._name()) || "Duration".equals(ps.get(1)._genericType()._rawType()._name())));
map.put("meta::pure::functions::date::adjust_Date_1__Integer_1__DurationUnit_1__Date_1_", (List<ValueSpecification> ps) -> ps.size() == 3 && isOne(ps.get(0)._multiplicity()) && Sets.immutable.with("Nil", "Date", "StrictDate", "DateTime", "LatestDate").contains(ps.get(0)._genericType()._rawType()._name()) && isOne(ps.get(1)._multiplicity()) && ("Nil".equals(ps.get(1)._genericType()._rawType()._name()) || "Integer".equals(ps.get(1)._genericType()._rawType()._name())) && isOne(ps.get(2)._multiplicity()) && ("Nil".equals(ps.get(2)._genericType()._rawType()._name()) || "DurationUnit".equals(ps.get(2)._genericType()._rawType()._name())));
map.put("meta::pure::functions::date::convertTimeZone_DateTime_1__String_1__String_1__String_1_", (List<ValueSpecification> ps) -> ps.size() == 3 && isOne(ps.get(0)._multiplicity()) && ("Nil".equals(ps.get(0)._genericType()._rawType()._name()) || "DateTime".equals(ps.get(0)._genericType()._rawType()._name())) && isOne(ps.get(1)._multiplicity()) && ("Nil".equals(ps.get(1)._genericType()._rawType()._name()) || "String".equals(ps.get(1)._genericType()._rawType()._name())) && isOne(ps.get(2)._multiplicity()) && ("Nil".equals(ps.get(2)._genericType()._rawType()._name()) || "String".equals(ps.get(2)._genericType()._rawType()._name())));
map.put("meta::pure::functions::date::dateDiff_Date_$0_1$__Date_$0_1$__DurationUnit_1__Integer_$0_1$_", (List<ValueSpecification> ps) -> ps.size() == 3 && matchZeroOne(ps.get(0)._multiplicity()) && Sets.immutable.with("Nil", "Date", "StrictDate", "DateTime", "LatestDate").contains(ps.get(0)._genericType()._rawType()._name()) && matchZeroOne(ps.get(1)._multiplicity()) && Sets.immutable.with("Nil", "Date", "StrictDate", "DateTime", "LatestDate").contains(ps.get(1)._genericType()._rawType()._name()) && isOne(ps.get(2)._multiplicity()) && ("Nil".equals(ps.get(2)._genericType()._rawType()._name()) || "DurationUnit".equals(ps.get(2)._genericType()._rawType()._name())));
map.put("meta::pure::functions::date::dateDiff_Date_1__Date_1__DurationUnit_1__Integer_1_", (List<ValueSpecification> ps) -> ps.size() == 3 && isOne(ps.get(0)._multiplicity()) && Sets.immutable.with("Nil", "Date", "StrictDate", "DateTime", "LatestDate").contains(ps.get(0)._genericType()._rawType()._name()) && isOne(ps.get(1)._multiplicity()) && Sets.immutable.with("Nil", "Date", "StrictDate", "DateTime", "LatestDate").contains(ps.get(1)._genericType()._rawType()._name()) && isOne(ps.get(2)._multiplicity()) && ("Nil".equals(ps.get(2)._genericType()._rawType()._name()) || "DurationUnit".equals(ps.get(2)._genericType()._rawType()._name())));
map.put("meta::pure::functions::date::datePart_Date_$0_1$__Date_$0_1$_", (List<ValueSpecification> ps) -> ps.size() == 1 && matchZeroOne(ps.get(0)._multiplicity()) && Sets.immutable.with("Nil", "Date", "StrictDate", "DateTime", "LatestDate").contains(ps.get(0)._genericType()._rawType()._name()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,14 @@ function meta::pure::functions::date::systemDefaultTimeZones():String[*]
['GMT', 'UTC']
}

function {doc.doc ='Adjust the timezone of a date time and format the time to the specifid format'}
meta::pure::functions::date::convertTimeZone(time:DateTime[1], targetZone:String[1],targetFormat:String[1]):String[1]
{
format( '%t{['+$targetZone+']'+$targetFormat+'}' ,$time);
}



function meta::pure::functions::date::hasYear(d:Date[1]):Boolean[1]
{
true;
Expand Down Expand Up @@ -641,5 +649,5 @@ function meta::pure::functions::date::toEpochValue(d:Date[1], unit:DurationUnit[
function meta::pure::functions::date::validateDateTimeFormat(dateTimeFormat:String[1]):Boolean[1]
{
// Supported Formats : yyyy, yyyy-M, yyyy-MM, yyyy-MM-d, yyyy-M-d, yyyy-MM-dd, yyyy/mm/dd, yyyyMMdd, yyyyMd, yyMd, dd/MM/yyyy, MM/dd/yyyy, MM/dd/yyyy "at" hh:mma z, yyyy-MM-dd HH:mm:ss, yyyy-MM-dd h:mm:ssa, yyyy-MM-dd HH:mm:ss.SSS, yyyy-MM-dd HH:mm:ss.SSSX, yyyy-MM-dd HH:mm:ss.SSSZ, yyyy-MM-dd HH:mm:ss.SSS z, yyyy-MM-dd HH:mm:ss.SSSS z, yyyy-MM-dd"T"HH:mm:ss, yyyy-MM-dd"T"HH:mm:ssZ, yyyy-MM-dd"T"HH:mm:ss.SSS, yyyy-MM-dd"T"HH:mm:ss.SSSZ, yyyy-MM-dd"T"HH:mm:ss.SSSSZ, yy-MM-dd"T"HH:mm:ss."000000", , 'yyyy-MM-dd"T"HH:mm:ss."000000"X', [EST]yyyy-MM-dd HH:mm:ss.SSSZ, [CST]yyyy-MM-dd HH:mm:ss.SSS z, [CET]yyyy-MM-dd HH:mm:ss.SSSX, [EST]yyyy-MM-dd and other timezones
$dateTimeFormat->matches('(\\[[A-Z][A-Z]([ACDKLMNORSTUVW])?([DSWT])?([T])?\\])?(MM/dd/|dd/MM/)?(?:y{4}|y{2})(([-/])?[mM]{1,2}(([-/])?(d{1,2}) *((\"T\")?(H{2}|h{1}):m{2}:s{2}.?((?:S{3,4} *[zZX]?|\"000000\"))?)?)?)? *(\"at\")? *(h{2}:m{2}a)? *[zZxX]?');
$dateTimeFormat->matches('(\\[[A-Z][A-Z]([ACDKLMNORSTUVW])?([DSWT])?([T])?\\])?(MM/dd/|dd/MM/)?(?:y{4}|y{2})(([-/])?[mM]{1,2}(([-/])?(d{1,2}) *((\"T\"|T)?(H{2}|h{1}):m{2}:s{2}.?((?:S{3,4} *[zZX]?|\"000000\"))?)?)?)? *(\"at\")? *(h{2}:m{2}a)? *[zZxX]?');
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,10 @@ function <<test.Test>> meta::pure::functions::date::tests::testValidateDateTimeF
let invalidFormats = ['yyy', 'yyyyy-M', 'yyyy-MMM', 'yyyy-MM-ddd', 'dd/mm/yyyy', 'MM/ddyy', 'MM/dd/yyyy "at" hh:ma z', 'yyyy-MMM-dd HH:mm:ss', 'yyyy-MM-dd hh:mm:ssa', 'yyyy-MM-dd HH:mm:ss.SSZ', 'yyyy-MM-dd HH:mm:ss.SZ', 'yyyy-MM-dd"TT"HH:mm:ss', 'yyyy-MM-dd"T"HH:mm:sss.SSS', 'yyyy-MM-dd""HH:mm:ss.SSSZ', 'yyyy-MM-dd"T"HH:mm:ss.SSSSSZ', 'yy-MM-dd"T"HH:mm:ss."00"', '(EST)yyyy-MM-dd'];
assert($invalidFormats->map(format | validateDateTimeFormat($format))->assertNotContains(true));
}


function <<test.Test>> meta::pure::functions::date::tests::testconvertTimeZone():Boolean[1]
{
assertEquals('2024-08-15T11:33:42.054-0400', convertTimeZone(%2024-08-15T15:33:42.054+0000,'America/New_York','yyyy-MM-dd"T"HH:mm:ss.SSSZ'));
}

Original file line number Diff line number Diff line change
Expand Up @@ -921,7 +921,8 @@ function meta::pure::router::routing::shouldStopFunctions(extensions:meta::pure:
sort_T_m__T_m_,
sort_T_m__Function_$0_1$__T_m_,
sortBy_T_m__Function_$0_1$__T_m_,
sort_T_m__Function_$0_1$__Function_$0_1$__T_m_
sort_T_m__Function_$0_1$__Function_$0_1$__T_m_,
meta::pure::functions::date::convertTimeZone_DateTime_1__String_1__String_1__String_1_
]->concatenate($extensions.routerExtensions().shouldStopRouting)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,9 @@ function <<access.private>> meta::relational::functions::sqlQueryToString::memsq
dynaFnToSql('toString', $allStates, ^ToSql(format='cast(%s as char)')),
dynaFnToSql('toTimestamp', $allStates, ^ToSql(format='%s', transform={p:String[2] | $p->transformToTimestampMemSQL()})),
dynaFnToSql('year', $allStates, ^ToSql(format='year(%s)')),
dynaFnToSql('weekOfYear', $allStates, ^ToSql(format='weekofyear(%s)'))
dynaFnToSql('weekOfYear', $allStates, ^ToSql(format='weekofyear(%s)')),
dynaFnToSql('convertTimeZone', $allStates, ^ToSql(format='%s', contextAwareTransform={p:String[3],s:SqlGenerationContext[1] | $p->transformConvertTimeZone($s)}))

];
}

Expand Down Expand Up @@ -179,6 +181,17 @@ function <<access.private>> meta::relational::functions::sqlQueryToString::memsq
let timestampFormat = $params->at(1);
'to_timestamp('+$params->at(0)+','+$timestampFormat+')';
}
function <<access.private>> meta::relational::functions::sqlQueryToString::memsql::transformConvertTimeZone(params:String[3],context:SqlGenerationContext[1]):String[1]
{
let sourceTZ = if($context.dbConfig.dbTimeZone->isEmpty(),|'GMT',|$context.dbConfig.dbTimeZone->toOne());
let unWrappedfmt = $params->at(2)->substring(1, $params->at(2)->length()-1);
assert($unWrappedfmt->validateDateTimeFormat(),'Found an invalid date format');
let formatpairs = meta::relational::functions::sqlQueryToString::default::defaultJavaToSQLTimeParts();
let msqlDate = $formatpairs->fold( {sub, date| $date->toOne()->replace($sub.first,$sub.second)},$params->at(2));
format('TO_CHAR(CONVERT_TZ(%s,\'%s\',%s),%s)',[$params->at(0),$sourceTZ,$params->at(1),$msqlDate]);
}



function <<access.private>> meta::relational::functions::sqlQueryToString::memsql::generateDateDiffExpressionForMemSQL(params:String[*]):String[1]
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,13 @@ function <<test.Test>> meta::relational::memsql::tests::mapping::sqlFunction::ba
meta::relational::runtime::DatabaseType.MemSQL, meta::relational::extension::relationalExtensions());
assertEquals('select cast(to_base64(`root`.alphaNumericString) as char) as `res1`, cast(from_base64(cast(to_base64(`root`.alphaNumericString) as char)) as char) as `res2` from dataTable as `root`', $s);
}



function <<test.Test>> meta::relational::memsql::tests::mapping::sqlFunction::convertTZ::testConvertTZ():Boolean[1]
{
let s = toSQLString(|SqlFunctionDemo.all()->project([s | $s.convertTimeZone,s|$s.dateTime->convertTimeZone('EST','yyyy-MM-ddTHH:mm:ss.SSS')], ['tzm','tzQuery']),
testMapping,
meta::relational::runtime::DatabaseType.MemSQL, meta::relational::extension::relationalExtensions());
assertEquals('select TO_CHAR(CONVERT_TZ(`root`.dateTime,\'GMT\',\'EST\'),\'YYYY-MM-DD HH24:MI:SS\') as `tzm`, TO_CHAR(CONVERT_TZ(`root`.dateTime,\'GMT\',\'EST\'),\'YYYY-MM-DDTHH24:MI:SS.FF3\') as `tzQuery` from dataTable as `root`',$s);
}
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,11 @@ function <<access.private>> meta::relational::functions::sqlQueryToString::snowf
dynaFnToSql('toString', $allStates, ^ToSql(format='cast(%s as varchar)')),
dynaFnToSql('toTimestamp', $allStates, ^ToSql(format='%s' , transform={p:String[2] | $p->transformToTimestampSnowflake()})),
dynaFnToSql('weekOfYear', $allStates, ^ToSql(format='WEEKOFYEAR(%s)')),
dynaFnToSql('year', $allStates, ^ToSql(format='year(%s)'))
dynaFnToSql('year', $allStates, ^ToSql(format='year(%s)')),
dynaFnToSql('convertTimeZone', $allStates, ^ToSql(format='%s', contextAwareTransform={p:String[3],s:SqlGenerationContext[1] | $p->transformConvertTimeZone($s)}))



];
}

Expand Down Expand Up @@ -404,6 +408,21 @@ function meta::relational::functions::sqlQueryToString::snowflake::preAndFinally
);
}



function <<access.private>> meta::relational::functions::sqlQueryToString::snowflake::transformConvertTimeZone(params:String[3],context:SqlGenerationContext[1]):String[1]
{
let unWrappedfmt = $params->at(2)->substring(1, $params->at(2)->length()-1); $unWrappedfmt->println();
assert($unWrappedfmt->validateDateTimeFormat(),'Found an invalid date format');
let formatpairs = meta::relational::functions::sqlQueryToString::default::defaultJavaToSQLTimeParts();
let format = $formatpairs->fold( {sub, date| $date->toOne()->replace($sub.first,$sub.second)},$params->at(2));
format('TO_CHAR(CONVERT_TIMEZONE(%s,%s),%s)',[$params->at(0),$params->at(1),$format]);
}





function <<access.private>> meta::relational::functions::sqlQueryToString::snowflake::snowflakeReservedWords():String[*]
{
// Based on https://docs.snowflake.com/en/sql-reference/reserved-keywords
Expand Down Expand Up @@ -494,4 +513,3 @@ function <<access.private>> meta::relational::functions::sqlQueryToString::snowf
]
}


Original file line number Diff line number Diff line change
Expand Up @@ -195,4 +195,12 @@ function <<test.Test>> meta::relational::tests::mapping::function::snowflake::te
testMapping,
meta::relational::runtime::DatabaseType.Snowflake, meta::relational::extension::relationalExtensions());
assertEquals('select to_boolean(\'true\') as "bolValue" from dataTable as "root"',$s);
}
}

function <<test.Test>> meta::relational::tests::mapping::function::snowflake::testConvertTimeZone():Boolean[1]
{
let s = toSQLString(|SqlFunctionDemo.all()->project([s | $s.convertTimeZone,s|$s.dateTime->convertTimeZone('EST','yyyy-MM-ddTHH:mm:ss')], ['tzm','tzQuery']),
testMapping,
meta::relational::runtime::DatabaseType.Snowflake, meta::relational::extension::relationalExtensions());
assertEquals('select TO_CHAR(CONVERT_TIMEZONE("root".dateTime,\'EST\'),\'YYYY-MM-DD HH24:MI:SS\') as "tzm", TO_CHAR(CONVERT_TIMEZONE("root".dateTime,\'EST\'),\'YYYY-MM-DDTHH24:MI:SS\') as "tzQuery" from dataTable as "root"',$s);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.apache.commons.text.similarity.LevenshteinDistance;
import org.finos.legend.engine.shared.core.ObjectMapperFactory;
import org.h2.tools.SimpleResultSet;
import org.h2.util.DateTimeUtils;
import org.h2.value.Value;
import org.h2.value.ValueArray;
import org.h2.value.ValueBigint;
Expand All @@ -30,13 +31,18 @@
import org.h2.value.ValueInteger;
import org.h2.value.ValueNull;
import org.h2.value.ValueReal;
import org.h2.value.ValueTimestamp;
import org.h2.value.ValueVarchar;

import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Types;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
Expand Down Expand Up @@ -331,4 +337,19 @@ public static Value legend_h2_extension_jaro_winkler_similarity(Value string1, V

return ValueDouble.get(new JaroWinklerSimilarity().apply(string1.getString(), string2.getString()));
}

public static Value legend_h2_extension_convertTimeZone(Value string1, Value string2)
{
if (string1 == ValueNull.INSTANCE || string2 == ValueNull.INSTANCE)
{
return ValueNull.INSTANCE;
}
String targetTimezone = string2.getString();
LocalDateTime localDateTime = LocalDateTime.parse(((ValueTimestamp) string1).getISOString());
ZonedDateTime utcTime = localDateTime.atZone(ZoneId.of("UTC"));
ZonedDateTime targetTime = utcTime.withZoneSameInstant(ZoneId.of(targetTimezone));
LocalTime time = targetTime.toLocalTime();
return DateTimeUtils.parseTimestamp(targetTime.toLocalDateTime().toString(),null,false);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,9 @@ private static List<String> getLegendH2ExtensionSQLs()
"CREATE ALIAS IF NOT EXISTS legend_h2_extension_flatten_array FOR \"org.finos.legend.engine.plan.execution.stores.relational.LegendH2Extensions.legend_h2_extension_flatten_array\";",
"CREATE ALIAS IF NOT EXISTS legend_h2_extension_split_part FOR \"org.finos.legend.engine.plan.execution.stores.relational.LegendH2Extensions.legend_h2_extension_split_part\";",
"CREATE ALIAS IF NOT EXISTS legend_h2_extension_edit_distance FOR \"org.finos.legend.engine.plan.execution.stores.relational.LegendH2Extensions.legend_h2_extension_edit_distance\";",
"CREATE ALIAS IF NOT EXISTS legend_h2_extension_jaro_winkler_similarity FOR \"org.finos.legend.engine.plan.execution.stores.relational.LegendH2Extensions.legend_h2_extension_jaro_winkler_similarity\";"
"CREATE ALIAS IF NOT EXISTS legend_h2_extension_jaro_winkler_similarity FOR \"org.finos.legend.engine.plan.execution.stores.relational.LegendH2Extensions.legend_h2_extension_jaro_winkler_similarity\";",
"CREATE ALIAS IF NOT EXISTS legend_h2_extension_convertTimeZone FOR \"org.finos.legend.engine.plan.execution.stores.relational.LegendH2Extensions.legend_h2_extension_convertTimeZone\";"

);
}

Expand All @@ -127,7 +129,9 @@ private static List<String> getLegendH2_1_4_200_ExtensionSQLs()
"CREATE ALIAS IF NOT EXISTS legend_h2_extension_flatten_array FOR \"org.finos.legend.engine.plan.execution.stores.relational.LegendH2Extensions_1_4_200.legend_h2_extension_flatten_array\";",
"CREATE ALIAS IF NOT EXISTS legend_h2_extension_split_part FOR \"org.finos.legend.engine.plan.execution.stores.relational.LegendH2Extensions_1_4_200.legend_h2_extension_split_part\";",
"CREATE ALIAS IF NOT EXISTS legend_h2_extension_edit_distance FOR \"org.finos.legend.engine.plan.execution.stores.relational.LegendH2Extensions_1_4_200.legend_h2_extension_edit_distance\";",
"CREATE ALIAS IF NOT EXISTS legend_h2_extension_jaro_winkler_similarity FOR \"org.finos.legend.engine.plan.execution.stores.relational.LegendH2Extensions_1_4_200.legend_h2_extension_jaro_winkler_similarity\";"
"CREATE ALIAS IF NOT EXISTS legend_h2_extension_jaro_winkler_similarity FOR \"org.finos.legend.engine.plan.execution.stores.relational.LegendH2Extensions_1_4_200.legend_h2_extension_jaro_winkler_similarity\";",
"CREATE ALIAS IF NOT EXISTS legend_h2_extension_convertTimeZone FOR \"org.finos.legend.engine.plan.execution.stores.relational.LegendH2Extensions_1_4_200.legend_h2_extension_convertTimeZone\";"

);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.apache.commons.text.similarity.LevenshteinDistance;
import org.finos.legend.engine.shared.core.ObjectMapperFactory;
import org.h2.tools.SimpleResultSet;
import org.h2.util.DateTimeUtils;
import org.h2.value.Value;
import org.h2.value.ValueArray;
import org.h2.value.ValueBoolean;
Expand All @@ -38,6 +39,11 @@
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Types;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
Expand Down Expand Up @@ -343,4 +349,18 @@ public static Value legend_h2_extension_jaro_winkler_similarity(Value string1, V

return ValueDouble.get(new JaroWinklerSimilarity().apply(string1.getString(), string2.getString()));
}

public static Value legend_h2_extension_convertTimeZone(Value string1, Value string2)
{
if (string1 == ValueNull.INSTANCE || string2 == ValueNull.INSTANCE)
{
return ValueNull.INSTANCE;
}
String targetTimezone = string2.getString();
LocalDateTime localDateTime = LocalDateTime.parse(string1.getString(),DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
ZonedDateTime utcTime = localDateTime.atZone(ZoneId.of("UTC"));
ZonedDateTime targetTime = utcTime.withZoneSameInstant(ZoneId.of(targetTimezone));
LocalTime time = targetTime.toLocalTime();
return DateTimeUtils.parseTimestamp(targetTime.toLocalDateTime().toString(),null,false);
}
}
Loading

0 comments on commit c6f09a3

Please sign in to comment.