1011 lines
36 KiB
Plaintext
1011 lines
36 KiB
Plaintext
package routines;
|
|
|
|
import java.lang.Math;
|
|
import java.text.ParseException;
|
|
import java.text.DateFormat;
|
|
import java.text.SimpleDateFormat;
|
|
import java.time.Duration;
|
|
import java.time.LocalDate;
|
|
import java.time.LocalDateTime;
|
|
import java.time.LocalTime;
|
|
import java.time.OffsetDateTime;
|
|
import java.time.ZoneId;
|
|
import java.time.ZoneOffset;
|
|
import java.time.format.DateTimeFormatter;
|
|
import java.util.Calendar;
|
|
import java.util.Date;
|
|
import java.util.List;
|
|
import java.util.ArrayList;
|
|
import java.util.Locale;
|
|
|
|
|
|
|
|
/*
|
|
* user specification: the function's comment should contain keys as follows: 1. write about the function's comment.but
|
|
* it must be before the "{talendTypes}" key.
|
|
*
|
|
* 2. {talendTypes} 's value must be talend Type, it is required . its value should be one of: String, char | Character,
|
|
* long | Long, int | Integer, boolean | Boolean, byte | Byte, Date, double | Double, float | Float, Object, short |
|
|
* Short
|
|
*
|
|
* 3. {Category} define a category for the Function. it is required. its value is user-defined .
|
|
*
|
|
* 4. {param} 's format is: {param} <type>[(<default value or closed list values>)] <name>[ : <comment>]
|
|
*
|
|
* <type> 's value should be one of: string, int, list, double, object, boolean, long, char, date. <name>'s value is the
|
|
* Function's parameter name. the {param} is optional. so if you the Function without the parameters. the {param} don't
|
|
* added. you can have many parameters for the Function.
|
|
*
|
|
* 5. {example} gives a example for the Function. it is optional.
|
|
*/
|
|
public class LcagDateTime {
|
|
private static LcagLogger log = LcagLogger.getLogger(LcagDateTime.class);
|
|
|
|
public static String MDM_DATE_FORMAT_STR = "yyyy-MM-dd";
|
|
public static String MDM_DATETIME_FORMAT_STR = "yyyy-MM-dd'T'HH:mm:ss";
|
|
private static final String DATETIME_FORMAT_ISO8601_UTC_STR = "yyyy-MM-dd'T'HH:mm:ss'Z'";
|
|
public static final String TALEND_INTERNAL_DATE_FORMAT_STR = "yyyy-MM-dd";
|
|
|
|
// Used as file prefix for all relevant MDM job files
|
|
public static final String MDM_FILE_TIMESTAMP_FORMAT_STR = "yyyy-MM-dd_HH-mm-ss-SSS'Z'";
|
|
// Used as file prefix for all files sent to MDM consumers
|
|
public static final String MDM_CONSUMER_FILE_TIMESTAMP_FORMAT_STR = "yyyy-MM-dd_HH-mm-ss-SSS";
|
|
// Used in MDM messages sent to consumer queue.
|
|
public static final String MDM_CONSUMER_TIMESTAMP_FORMAT_STR = DATETIME_FORMAT_ISO8601_UTC_STR;
|
|
// Used in Talend UI XML trigger messages and as Default format for Date to String Conversions
|
|
public static final String TALEND_INTERNAL_DATETIME_FORMAT_STR = "yyyy-MM-dd HH:mm:ss";
|
|
|
|
// Deprecated: Do not use these in Jobs or other code, use Standard MDM formats defined in next paragraph
|
|
public static SimpleDateFormat DATETIME_FORMAT_ISO8601_UTC = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
|
public static SimpleDateFormat DATETIME_FORMAT_ISO8601_UTC_FILE = new SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss'Z'");
|
|
public static SimpleDateFormat DATE_FORMAT_ISO8601_UTC = new SimpleDateFormat("yyyy-MM-dd'Z'");
|
|
public static SimpleDateFormat DATE_FORMAT_ISO8601 = new SimpleDateFormat("yyyy-MM-dd");
|
|
|
|
public static final SimpleDateFormat MDM_FILE_TIMESTAMP_FORMAT = new SimpleDateFormat(MDM_FILE_TIMESTAMP_FORMAT_STR);
|
|
// Used as file prefix for all files sent to MDM consumers
|
|
public static final SimpleDateFormat MDM_CONSUMER_FILE_TIMESTAMP_FORMAT = new SimpleDateFormat(MDM_CONSUMER_FILE_TIMESTAMP_FORMAT_STR);
|
|
// Used in MDM messages sent to consumer queue.
|
|
public static final SimpleDateFormat MDM_CONSUMER_TIMESTAMP_FORMAT = new SimpleDateFormat(MDM_CONSUMER_TIMESTAMP_FORMAT_STR);
|
|
// Used in Talend UI XML trigger messages and as Default format for Date to String Conversions
|
|
public static final SimpleDateFormat TALEND_INTERNAL_DATETIME_FORMAT = new SimpleDateFormat(TALEND_INTERNAL_DATETIME_FORMAT_STR);
|
|
public static final SimpleDateFormat TALEND_INTERNAL_DATE_FORMAT = new SimpleDateFormat(TALEND_INTERNAL_DATE_FORMAT_STR);
|
|
|
|
// Standard MDM Date format to use in all Jobs
|
|
public static SimpleDateFormat MDM_DATE_FORMAT = new SimpleDateFormat(MDM_DATE_FORMAT_STR);
|
|
// Standard MDM Timestamp format to use in all Jobs for LastUpdateTimestamp
|
|
public static SimpleDateFormat MDM_DATETIME_FORMAT = new SimpleDateFormat(MDM_DATETIME_FORMAT_STR);
|
|
|
|
public final static class PeriodCompareResult {
|
|
// Set default values for result when comparing to null dates:
|
|
public boolean EXTENDS_BEFORE = true; /* A extends B directly before B */
|
|
public boolean EXTENDS_AFTER = true; /* extends B directly after B */
|
|
public boolean EXTENDS = true; /* extends before or after directly adjactent */
|
|
public boolean OVERLAP = false; /* fully or partially overlap with each other */
|
|
public boolean OVERLAPS_WITH_START = false; /* A overlaps with start of B */
|
|
public boolean OVERLAPS_WITH_END = false; /* A overlaps with end of B */
|
|
public boolean CONTAINS = false; /* A fully contains B */
|
|
public boolean CONTAINS_AT_START = false; /* A fully contains B starting at start of A */
|
|
public boolean CONTAINS_AT_END = false; /* A fully contains B at end at A */
|
|
public boolean IS_CONTAINED = false; /* B fully contains A */
|
|
public boolean HAS_GAP_BEFORE = false;
|
|
public boolean HAS_GAP_AFTER = false;
|
|
public boolean HAVE_GAP = false; /* There is a gap between A and B */
|
|
public boolean EQUAL = false;
|
|
public boolean EARLIER = false; /* period completely earlier */
|
|
public boolean LATER = false; /* period completely later */
|
|
public long NO_DAYS = 0; /* amount of days overlapping or in-between a gap */
|
|
|
|
public PeriodCompareResult() {} { }
|
|
|
|
public String toString() {
|
|
List<String> result = new ArrayList<String>();
|
|
|
|
if (this.EQUAL) { result.add("equals"); }
|
|
if (this.EXTENDS_BEFORE) { result.add("extends before"); }
|
|
if (this.EXTENDS_AFTER) { result.add("extends after"); }
|
|
if (this.EXTENDS) { result.add("extends"); }
|
|
if (this.OVERLAPS_WITH_START) { result.add("overlaps with start of"); }
|
|
if (this.OVERLAPS_WITH_END) { result.add("overlaps with end of"); }
|
|
if (this.OVERLAP) { result.add("overlaps"); }
|
|
if (this.CONTAINS) { result.add("contains"); }
|
|
if (this.CONTAINS_AT_START) { result.add("contains at start of"); }
|
|
if (this.CONTAINS_AT_END) { result.add("contains at end of"); }
|
|
if (this.IS_CONTAINED) { result.add("is contained in"); }
|
|
if (this.HAVE_GAP) { result.add("has a gap with"); }
|
|
if (this.HAS_GAP_BEFORE) { result.add("has gap before"); }
|
|
if (this.HAS_GAP_AFTER) { result.add("has gap after"); }
|
|
if (this.EARLIER) { result.add("is earlier than"); }
|
|
if (this.LATER) { result.add("is later than"); }
|
|
|
|
return result.toString();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* addDays: Add a number of days to a given date
|
|
*
|
|
*
|
|
* {talendTypes} Date
|
|
*
|
|
* {Category} LCAG-DateTime
|
|
*
|
|
* {param} Date("date") input: date to increase
|
|
* {param} int("days") input: number of days to add (can be negative
|
|
*
|
|
* {example} asDurationString("+12:04") = "PT12H04M".
|
|
*/
|
|
public static Date addDays(Date date, int days)
|
|
{
|
|
Calendar cal = Calendar.getInstance();
|
|
cal.setTime(date);
|
|
cal.add(Calendar.DATE, days); //minus number would decrement the days
|
|
return cal.getTime();
|
|
}
|
|
|
|
/**
|
|
* asDurationString: returns a XML duration format string.
|
|
*
|
|
*
|
|
* {talendTypes} String
|
|
*
|
|
* {Category} LCAG-DateTime
|
|
*
|
|
* {param} string("aTime") input: The time duration string in MDS format (+/-HH:MM) to be converted into a XML duration format string.
|
|
*
|
|
* {example} asDurationString("+12:04") = "PT12H04M".
|
|
*/
|
|
public static String asDurationString(String aTime) {
|
|
boolean inFuture = true;
|
|
String result = null;
|
|
int hours = 0, minutes = 0;
|
|
|
|
if (aTime.startsWith("+")) {
|
|
aTime = aTime.replaceFirst("+", "");
|
|
} else if (aTime.startsWith("-")) {
|
|
inFuture = false;
|
|
aTime = aTime.replaceFirst("-", "");
|
|
}
|
|
|
|
String values[] = aTime.split(":");
|
|
|
|
if (values.length == 2) {
|
|
hours = Integer.parseInt(values[0]);
|
|
minutes = Integer.parseInt(values[1]);
|
|
|
|
result = String.format("%sPT%02H%02M", inFuture ? "" : "-", hours, minutes);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/**
|
|
* asDuration: Return a Java Duration object from a MDS formatted String
|
|
*
|
|
*
|
|
* {talendTypes} String
|
|
*
|
|
* {Category} LCAG-DateTime
|
|
*
|
|
* {param} string("aTime") input: The time duration string in MDS format (+/-HH:MM) to be converted into a XML duration format string.
|
|
*
|
|
* {example} asDuration("+12:04") = "PT12H04M".
|
|
*/
|
|
public static Duration asDuration(String aTime) {
|
|
|
|
String durationString = asDurationString(aTime);
|
|
Duration result = Duration.parse(durationString);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* utcOffsetToDouble: convert UTC offset String to floating point number
|
|
*
|
|
* {talendTypes} Double
|
|
*
|
|
* {Category} LCAG-DateTime
|
|
*
|
|
* {param} String("offset") input: UTC offset string +04:30
|
|
*
|
|
*/
|
|
public static Double utcOffset2Double(String offset)
|
|
{
|
|
Double result = null;
|
|
|
|
if (offset != null)
|
|
{
|
|
String sign = offset.substring(0,1);
|
|
String hours = offset.substring(1,3);
|
|
String mins = offset.substring(4);
|
|
|
|
result = new Double(
|
|
( sign.equals("-") ? -1.0 : 1.0 )
|
|
*
|
|
(
|
|
Integer.parseInt(hours)
|
|
+
|
|
Integer.parseInt(mins) / 60.0
|
|
)
|
|
);
|
|
}
|
|
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* toDate: Returns a Date object from a "YYYY-MM-DD" formated String 'obj' or obj itself if it is already a Date object
|
|
*
|
|
*
|
|
* {talendTypes} Date
|
|
*
|
|
* {Category} LCAG-DateTime
|
|
*
|
|
* {param} Object("obj") input: The obj to convert to Date
|
|
*
|
|
* {example} toDate("2017-01-31"), toDate(new Date())
|
|
*/
|
|
public static Date toDate(Object obj) throws ParseException {
|
|
Date result = null;
|
|
|
|
if (obj != null) {
|
|
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
|
|
String dateString = null;
|
|
|
|
if (obj instanceof Date) {
|
|
result = (Date)obj;
|
|
dateString = df.format(result); // to strip the time part from date at next parse
|
|
} else {
|
|
dateString = obj.toString();
|
|
}
|
|
|
|
if ( ! StringUtil.isEmpty(dateString) ) {
|
|
result = df.parse(dateString);
|
|
log.trace(String.format("toDate(%s) = %s\n", dateString, df.format(result)));
|
|
} else {
|
|
log.debug("LcagDateTime.toDate called with empty (%s) string argument", dateString);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* toDate: Converts a Date string to a Date object if any String representation in the formatList is applicable
|
|
*
|
|
* @param aDate Date string to convert
|
|
* @param formatList list of String formats to try for conversion
|
|
* @return Date object or null if no conversion is applicable
|
|
*
|
|
* {talendTypes} Object
|
|
*
|
|
* {Category} LCAG-DateTime
|
|
*
|
|
* {param} String("aDate") aDate: Date string to convert
|
|
* {param} Object("formatList") formatList...: variable number of String formats to try for conversion
|
|
*
|
|
* {examples}
|
|
*/
|
|
public static Date toDate(String aDate, String... formatList)
|
|
{
|
|
Date result = null;
|
|
|
|
if (aDate == null)
|
|
return null;
|
|
|
|
// try all formats until first one succeeds:
|
|
for (String fmt: formatList) {
|
|
SimpleDateFormat df = new SimpleDateFormat(fmt);
|
|
|
|
try {
|
|
result = df.parse(aDate);
|
|
break;
|
|
} catch(Exception e) {}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* countDays: Returns the number of days with the Date period [d1 - d2]. If given by "YYYY-MM-DD" string it is converted first.
|
|
*
|
|
*
|
|
* {talendTypes} Long
|
|
*
|
|
* {Category} LCAG-DateTime
|
|
*
|
|
* {param} Object("d1") input: The first date to use
|
|
* {param} Object("d2") input: The second date to use
|
|
*
|
|
* {example} countDays("2017-01-01", "2017-01-03") = 3
|
|
*/
|
|
public static long countDays(Object d1, Object d2) throws ParseException {
|
|
long result = 0;
|
|
|
|
if (d1 != null && d2 != null) {
|
|
Date date1 = toDate(d1);
|
|
Date date2 = toDate(d2);
|
|
|
|
|
|
result = Math.round((date2.getTime() - date1.getTime()) / (60 * 60 * 24 * 1000.0));
|
|
result += 1;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* getStartOfToday: Converts a Date string to a Date object if any String representation in the formatList is applicable
|
|
*
|
|
* @return Date object for today, 00:00:00
|
|
*
|
|
* {talendTypes} Date
|
|
*
|
|
* {Category} LCAG-DateTime
|
|
*
|
|
* {examples}
|
|
*/
|
|
public static Date getStartOfToday()
|
|
{
|
|
Date result = new Date();
|
|
|
|
String s = DATE_FORMAT_ISO8601_UTC.format(new Date());
|
|
String resultDate = s.replace("Z", "T00:00:00Z");
|
|
|
|
try {
|
|
result = DATETIME_FORMAT_ISO8601_UTC.parse(resultDate);
|
|
} catch(Exception e) {
|
|
result = null;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* isEarlierToday: return true if the date passed is at a earlier time today
|
|
*
|
|
* @return boolean true if the date passed is at a earlier time today
|
|
*
|
|
* {talendTypes} boolean
|
|
*
|
|
* {Category} LCAG-DateTime
|
|
*
|
|
* {examples}
|
|
*/
|
|
public static boolean isEarlierToday(Date aDate)
|
|
{
|
|
Date now = new Date();
|
|
Date todayStart = getStartOfToday();
|
|
|
|
boolean result = (aDate.before(now) && aDate.after(todayStart));
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* isEarlierToday: return true if the date passed in seconds since 1970 is at a earlier time today
|
|
*
|
|
* @return boolean true if the date passed is at a earlier time today
|
|
*
|
|
* {talendTypes} boolean
|
|
*
|
|
* {Category} LCAG-DateTime
|
|
*
|
|
* {examples}
|
|
*/
|
|
public static boolean isEarlierToday(long aDateInSecs)
|
|
{
|
|
Date aDate = new Date();
|
|
|
|
aDate.setTime(aDateInSecs);
|
|
|
|
boolean result = isEarlierToday(aDate);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* compareDatePeriods: Compares two date ranges and returns a PeriodCompareResult object that lists the results:
|
|
* .EQUAL, if Period A is the same as B
|
|
* .EXTENDS, A extends B if B extends the range of A without overlapping
|
|
* .CONTAINS, A contains B completely (e.g. B1, B2)
|
|
* .OVERLAP, A overlaps B if range A overlaps B somehow, i.e. A intersects with B
|
|
* .EARLIER, A is completely earlier than B, i.e. both the “from date” and the “to date of A are before or equal to the start of B
|
|
* .LATER, A is completely later than B
|
|
*
|
|
* {talendTypes} PeriodCompareResult
|
|
*
|
|
* {Category} LCAG-DateTime
|
|
*
|
|
* {param} Date("fromTimeA") input: Start date of period A
|
|
* {param} Date("toTimeA") input: End date of period A
|
|
* {param} Date("fromTimeB") input: Start date of period B
|
|
* {param} Date("toTimeA") input: Start date of period B
|
|
*
|
|
* {example} compareDatePeriods(fromDateA, toDateA, fromDateB, toDateB).EXTENDS returns true if A extends B, i.e. if A is adjacent to B
|
|
* @throws ParseException
|
|
*/
|
|
|
|
public static LcagDateTime.PeriodCompareResult compareDatePeriods(Object fromA, Object toA, Object fromB, Object toB) throws ParseException {
|
|
Date fromDateA = toDate(fromA);
|
|
Date toDateA = toDate(toA);
|
|
Date fromDateB;
|
|
Date toDateB;
|
|
|
|
PeriodCompareResult result = new LcagDateTime.PeriodCompareResult();
|
|
|
|
if (fromA == null || toA == null || fromB == null || toB == null)
|
|
return result;
|
|
|
|
fromDateB = toDate(fromB);
|
|
toDateB = toDate(toB);
|
|
|
|
Date dayBeforeA = addDays(fromDateA, -1);
|
|
Date dayAfterA = addDays(toDateA, 1);
|
|
|
|
result.EQUAL =
|
|
(fromDateA.compareTo(fromDateB) == 0 && toDateA.compareTo(toDateB) == 0);
|
|
|
|
result.EXTENDS_AFTER = (dayBeforeA.compareTo(toDateB) == 0 && fromDateB.compareTo(fromDateA) < 0); /* B4 */
|
|
result.EXTENDS_BEFORE = (dayAfterA.compareTo(fromDateB) == 0 && toDateB.compareTo(toDateA) > 0); /* B5 */
|
|
result.EXTENDS = result.EXTENDS_BEFORE || result.EXTENDS_AFTER;
|
|
|
|
result.CONTAINS = (fromDateB.compareTo(fromDateA) >= 0 && toDateB.compareTo(toDateA) <= 0); /* fully B1,B2 */
|
|
result.CONTAINS_AT_START = (fromDateB.compareTo(fromDateA) == 0 && toDateB.compareTo(toDateA) <= 0);
|
|
result.CONTAINS_AT_END = (fromDateB.compareTo(fromDateA) >= 0 && toDateB.compareTo(toDateA) == 0);
|
|
result.IS_CONTAINED = (fromDateA.compareTo(fromDateB) >= 0) && (toDateA.compareTo(toDateB) <= 0); /* B2, B3*/
|
|
|
|
result.EARLIER = /* B5, B9 */
|
|
(dayAfterA.compareTo(fromDateB) <= 0);
|
|
result.LATER = /* B4, B8 */
|
|
(dayBeforeA.compareTo(toDateB) >= 0);
|
|
|
|
result.OVERLAPS_WITH_END =
|
|
(fromDateA.compareTo(toDateB) <= 0) /* B1,B2,B3,B5,B6,B7,B9 */ &&
|
|
(toDateA.compareTo(toDateB) > 0 /* B1,B4,B6,B8*/);
|
|
/* => Result: B1, B6 */
|
|
|
|
result.OVERLAPS_WITH_START =
|
|
(fromDateA.compareTo(fromDateB) < 0) /* B1,B5,B7,B9 */ &&
|
|
(toDateA.compareTo(fromDateB) >= 0 /* B1,B2,B3,B4,B6,B7,B8 */);
|
|
/* => Result: B1, B7 */
|
|
|
|
// Lastly, also consider partial overlapping between A and B:
|
|
result.OVERLAP = result.CONTAINS || result.IS_CONTAINED || result.OVERLAPS_WITH_START || result.OVERLAPS_WITH_END; /* B1,B2,B3, B6, B7 */
|
|
|
|
result.HAS_GAP_BEFORE = toDateB.before(dayBeforeA);
|
|
result.HAS_GAP_AFTER = fromDateB.after(dayAfterA);
|
|
|
|
result.HAVE_GAP = result.HAS_GAP_BEFORE || result.HAS_GAP_AFTER;
|
|
|
|
if (result.CONTAINS) {
|
|
result.NO_DAYS = countDays(fromDateB, toDateB);
|
|
} else if (result.IS_CONTAINED) {
|
|
result.NO_DAYS = countDays(fromDateA, toDateA);
|
|
} else if (result.OVERLAPS_WITH_START) {
|
|
result.NO_DAYS = countDays(fromDateB, toDateA);
|
|
} else if (result.OVERLAPS_WITH_END) {
|
|
result.NO_DAYS = countDays(fromDateA, toDateB);
|
|
} else if (result.HAS_GAP_BEFORE) {
|
|
result.NO_DAYS = countDays(toDateB, fromDateA) - 2;
|
|
} else if (result.HAS_GAP_AFTER) {
|
|
result.NO_DAYS = countDays(toDateA, fromDateB) - 2;
|
|
}
|
|
|
|
SimpleDateFormat df = new SimpleDateFormat("dd. MM YYYY");
|
|
String logText = String.format("[%1$td.%1$tm.%1$tY - %2$td.%2$tm.%2$tY] %3$s [%4$td.%4$tm.%4$tY - %5$td.%5$tm.%5$tY]", fromDateA, toDateA, result.toString(), fromDateB, toDateB);
|
|
log.info(logText);
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* dateInbetween: returns a comparison of three datetime objects, i.e. whether one date located between the other two dates
|
|
*
|
|
*
|
|
* {talendTypes} Boolean
|
|
*
|
|
* {Category} LCAG-DateTime
|
|
*
|
|
* {param} Object("from") input: The first date to use
|
|
* {param} Object("inbetween") input: The date that is used for comparison
|
|
* {param} Object("to") input: The last date to use
|
|
*
|
|
*/
|
|
public static boolean dateInbetween(Date from, Date inbetween, Date to) {
|
|
boolean result = false;
|
|
|
|
|
|
if (from != null && inbetween != null && to != null)
|
|
{
|
|
result = (from.equals(inbetween) || from.before(inbetween))
|
|
&&
|
|
(to.equals(inbetween) || to.after(inbetween))
|
|
;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/**
|
|
* secureParseDate: returns a Date object parsed from String daterepresentation and, hopefully, does return
|
|
*
|
|
*
|
|
* {talendTypes} Date
|
|
*
|
|
* {Category} LCAG-DateTime
|
|
*
|
|
* {param} Object("pattern") input: the pattern describing how to encode a datetime value YYYY-MM-dd HH:mm:ss
|
|
* {param} Object("daterepresentation") input: the date that is parsed
|
|
*
|
|
*/
|
|
public static Date secureParseDate(String pattern, String daterepresentation) {
|
|
Date result = null;
|
|
|
|
if (daterepresentation != null)
|
|
if (! daterepresentation.equals(""))
|
|
{
|
|
try {
|
|
result = TalendDate.parseDate(pattern, daterepresentation);
|
|
} catch(Exception e) {}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Converts a double value to a UTC time offset String
|
|
* (Used in MAD timezone conversion)
|
|
*
|
|
* @param d output: Talend or Dom4J or String xml document to be changed
|
|
* @return Time offset as String [+-]HH:MM
|
|
*
|
|
* {talendTypes} String
|
|
* {Category} LCAG-DateTime
|
|
*
|
|
* {param} Double(d) d: input: xpath lookup string
|
|
*
|
|
* {example} double2UtcOffset(6) = "+06:00", double2UtcOffset(6.5) = "+06:30"
|
|
*/
|
|
|
|
public static String double2UtcOffset(Double d)
|
|
{
|
|
String result = null;
|
|
|
|
if ( d == null)
|
|
return result;
|
|
|
|
if ( d <= -24.0 || d >= 24.0 )
|
|
throw new IllegalArgumentException(String.format("Argument must be > -24 and < 24, %f given", d));
|
|
|
|
if (d != null) {
|
|
String sign = "+";
|
|
|
|
if ( d < 0) {
|
|
sign = "-";
|
|
d = -d;
|
|
}
|
|
|
|
int hours = (int)Math.floor(d);
|
|
int minutes = (int)Math.round((d - hours) * 60.0);
|
|
|
|
if ( minutes % 15 > 0 ) {
|
|
log.warn("double2UtcOffset: %.2f is an UNUSUAL offset (not a a multiple of 15 mins) !", d);
|
|
}
|
|
result = sign + String.format("%02d:%02d", hours, minutes);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Checks if the first 2 letters of a string represent a IATA Country Code
|
|
* of a Country that has more than one time zone.
|
|
*
|
|
* Note: List of Countries is hard-coded
|
|
*
|
|
* @param aCode input: Code to check
|
|
* @return true if Code is valid as Multi-Timezone Country Code
|
|
*
|
|
* {talendTypes} String
|
|
* {Category} LCAG-DateTime
|
|
*
|
|
* {param} String(aCode) aCode: input: Code to check
|
|
*
|
|
* {example} isMultiTimezoneCountry("DE:01") = false,
|
|
* isMultiTimezoneCountry("DE0") = false
|
|
* isMultiTimezoneCountry("AU2A") = true
|
|
* isMultiTimezoneCountry("US:02") = true
|
|
*/
|
|
public static boolean isMultiTimezoneCountry(String aCode) {
|
|
// List of Countries that have multiple Timezones according to IATA Airline Coding Directory (ACD):
|
|
final List<String> multiTzCountries = java.util.Arrays.asList(
|
|
"AU", "BR", "CA", "CD", "CL",
|
|
"EC", "ES", "FM", "GL", "ID", "JO", "KI", "KZ", "MX", "NZ",
|
|
"PF", "PG", "PT", "RU", "UM", "US", "ZZ");
|
|
|
|
if ( aCode == null || aCode.length() < 2) {
|
|
throw new IllegalArgumentException(String.format("Illegal Code length of '%s': Must start with 2 letter Country Code !", aCode));
|
|
}
|
|
|
|
String countryCode = aCode.substring(0, 2);
|
|
|
|
boolean result = multiTzCountries.contains(countryCode);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Converts a Timezone Code in MDS format (should by IATA) into MAD format.
|
|
* Note: 1. Does not transform special values like for countries MX, BR,...
|
|
* 2. Includes a hard coded list of Country Codes having multiple Timezone Codes
|
|
* (Created for jobs updating Timezone)
|
|
*
|
|
* @param aCode input: MDS Timezone Code
|
|
* @return MAD Timezone Code format
|
|
*
|
|
* {talendTypes} String
|
|
* {Category} LCAG-DateTime
|
|
*
|
|
* {param} String(aCode) aCode: input: MDS Timezone Code
|
|
*
|
|
* {example} convertMDS_TimezoneCodeToMAD("DE:01") = "DE0",
|
|
* convertMDS_TimezoneCodeToMAD("PF:01") = "PF1" (PF is multi-tz country)
|
|
* convertMDS_TimezoneCodeToMAD("MX:05") = "MX5" (but MAD uses MX1B)
|
|
*/
|
|
public static String convertMDS_TimezoneCodeToMAD(String aCode) {
|
|
if ( aCode == null || aCode.length() < 5) {
|
|
throw new IllegalArgumentException(String.format("Illegal MDS Code '%s': Must have at least 5 characters!", aCode));
|
|
}
|
|
|
|
String countryCode = aCode.substring(0,2);
|
|
String tzNo = aCode.substring(3);
|
|
String result = countryCode;
|
|
|
|
if ( tzNo.startsWith("0") ) {
|
|
if ("01".equals(tzNo) && ! isMultiTimezoneCountry(aCode) ) {
|
|
// In MAD countries that have just one TZ always use "0" as last character
|
|
result += "0";
|
|
} else {
|
|
result += tzNo.substring(1); // drop leading "0"
|
|
}
|
|
} else {
|
|
result += tzNo;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Converts a Timezone Code in MAD format (should by IATA) into MDS format.
|
|
* Note: Does not transform special values like for countries MX, BR,...
|
|
*
|
|
* @param aCode input: MAD Timezone Code
|
|
* @return MDS Timezone Code format
|
|
*
|
|
* {talendTypes} String
|
|
* {Category} LCAG-DateTime
|
|
*
|
|
* {param} String(aCode) aCode: input: MAD Timezone Code
|
|
*
|
|
* {example} convertMAD_TimezoneCodeToMDS("DE0") = "DE:01", convertMAD_TimezoneCodeToMDS("PF1") = "PF:01"
|
|
* convertMAD_TimezoneCodeToMDS("MX1A") = "MX:1A" (but MDS uses MX:05)
|
|
*/
|
|
public static String convertMAD_TimezoneCodeToMDS(String aCode) {
|
|
|
|
if ( aCode == null || aCode.length() < 3) {
|
|
throw new IllegalArgumentException(String.format("Illegal MAD Code '%s': Must have at least 3 characters!", aCode));
|
|
}
|
|
|
|
String countryCode = aCode.substring(0,2);
|
|
String tzNo = aCode.substring(2);
|
|
String result = countryCode + ":";
|
|
|
|
if ("0".equals(tzNo)) {
|
|
// Code of a Country that has only one Timezone
|
|
result += "01";
|
|
} else if ( tzNo.length() == 1) {
|
|
result += "0" + tzNo;
|
|
} else {
|
|
result += tzNo;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Convert a Local Date with Time to UTC using the local time offset to UTC
|
|
*
|
|
* {talendTypes} OffsetDateTime
|
|
*
|
|
* {Category} LCAG-DateTime
|
|
*
|
|
* {param} LocalDateTime localDateTime : input: Date with time to convert
|
|
* {param} String("+04:30") offsetTime : input: Time offset to use, e.g. -04:00, +04:00, 04:00
|
|
*
|
|
* {example} convertLocalTimeToUTC(aDate, "-03:00")
|
|
*/
|
|
public static OffsetDateTime convertLocalTimeToUTC(LocalDateTime localDateTime, String offsetTime) {
|
|
String[] hoursMins = offsetTime.split(":");
|
|
|
|
if (hoursMins.length != 2) {
|
|
throw new IllegalArgumentException(String.format("Unsupport Offset Time ('%s'), required format is [+-]HH:MM", offsetTime) );
|
|
}
|
|
|
|
if (localDateTime == null) {
|
|
localDateTime = LocalDateTime.now();
|
|
}
|
|
|
|
if (! offsetTime.startsWith("-") && ! offsetTime.startsWith("+"))
|
|
offsetTime = "+" + offsetTime;
|
|
|
|
DateTimeFormatter df = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm");
|
|
String dateString = df.format(localDateTime) + ":00:000" + offsetTime;
|
|
|
|
DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss:SSSXXXXX");
|
|
OffsetDateTime localOffsetDateTime = OffsetDateTime.parse(dateString, DATE_TIME_FORMATTER);
|
|
OffsetDateTime utcOffsetDateTime = localOffsetDateTime.withOffsetSameInstant(ZoneOffset.UTC);
|
|
|
|
return utcOffsetDateTime;
|
|
}
|
|
|
|
/**
|
|
* Convert a Date with Time and Time Zone Offset to UTC.
|
|
*
|
|
* {talendTypes} OffsetDateTime
|
|
*
|
|
* {Category} LCAG-DateTime
|
|
*
|
|
* {param} OffsetDateTime dateTime : input: Date with time to convert
|
|
*/
|
|
public static OffsetDateTime convertToUTC(OffsetDateTime dateTime) {
|
|
|
|
if (dateTime == null) {
|
|
dateTime = OffsetDateTime.now();
|
|
}
|
|
|
|
OffsetDateTime utcOffsetDateTime = dateTime.withOffsetSameInstant(ZoneOffset.UTC);
|
|
|
|
return utcOffsetDateTime;
|
|
}
|
|
|
|
/**
|
|
* Convert a UTC Time to another timzone given by its offset to UTC
|
|
*
|
|
* {talendTypes} OffsetDateTime
|
|
*
|
|
* {Category} LCAG-DateTime
|
|
*
|
|
* {param} Date localDateTime : input: Date with time to convert
|
|
* {param} String("-HH:mm") offsetTime : input: Time offset to use, e.g. -04:00, +04:00, 04:00
|
|
*
|
|
* {example} convertUTCTimeToLocal(aDate, "+03:30")
|
|
*/
|
|
public static OffsetDateTime convertUTCTimeToLocal(Date localDateTime, String offsetTime) {
|
|
String[] hoursMins = offsetTime.split(":");
|
|
|
|
if (hoursMins.length != 2) {
|
|
throw new IllegalArgumentException(String.format("Unsupport Offset Time ('%s'), required format is [+-]HH:MM", offsetTime) );
|
|
}
|
|
|
|
if (localDateTime == null) {
|
|
localDateTime = new Date();
|
|
}
|
|
|
|
int hours = Integer.parseInt(hoursMins[0]);
|
|
int minutes = Integer.parseInt(hoursMins[1]);
|
|
|
|
if (hours < 0)
|
|
minutes = -minutes;
|
|
|
|
if (! offsetTime.startsWith("-") && ! offsetTime.startsWith("+"))
|
|
offsetTime = "+" + offsetTime;
|
|
|
|
SimpleDateFormat df = new SimpleDateFormat("dd/MM/yyyy'T'HH:mm");
|
|
String dateString = df.format(localDateTime) + ":00:000+00:00";
|
|
|
|
DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("dd/MM/uuuu'T'HH:mm:ss:SSSXXXXX");
|
|
OffsetDateTime utcOffsetDateTime = OffsetDateTime.parse(dateString, DATE_TIME_FORMATTER);
|
|
OffsetDateTime localOffsetDateTime = utcOffsetDateTime.withOffsetSameInstant(ZoneOffset.ofHoursMinutes(hours, minutes));
|
|
|
|
return localOffsetDateTime;
|
|
}
|
|
|
|
/**
|
|
* Adjusts a given date in MDS notation to a regular date. Interprets MDS special time 23:59 as 00:00 same day and
|
|
* 00:01 as 00:00 same day. MDS uses 00:01 for DST start time at the same day and 23:59 for DST end time at the same
|
|
* day.
|
|
* Example: the DST in Iran (IR:01) ends on Sep, 22th 2021 00:00 UTC, but MDS uses Sep, 22th, 23:59!
|
|
*
|
|
* {talendTypes} OffsetDateTime
|
|
*
|
|
* {Category} LCAG-DateTime
|
|
*
|
|
* {param} OffsetDateTime aDate : input: Date to use adjust
|
|
* {returns} Date assembled from input parameters
|
|
* {example} adjustMDS_Date(aDate whith value 2021-02-28 23:59) returns a Date for 2021-03-01 00:00:00.000)
|
|
*/
|
|
public static OffsetDateTime adjustMDS_Date(OffsetDateTime aDate) {
|
|
int hours = aDate.getHour();
|
|
int minutes = aDate.getMinute();
|
|
OffsetDateTime result = aDate.withSecond(0).withNano(0);
|
|
|
|
if ( hours == 0 && minutes == 1 ) {
|
|
result = result.withMinute(0);
|
|
} else if ( hours == 23 && minutes == 59 ) {
|
|
result = result.plusMinutes(1);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Assembles a DateTime object from a local Date and a local time string HH:mm that uses MDS special times 23:59 (00:00 next day) and
|
|
* 00:01 (00:00 same day).
|
|
*
|
|
* {talendTypes} Date
|
|
*
|
|
* {Category} LCAG-DateTime
|
|
*
|
|
* {param} LocalDate localDate : input: Date part to use
|
|
* {param} LocalTime localTime : input: Local Time to append to the date part
|
|
* {param} String offsetTime : input: Used to define the time zone of the LocalDate and LocalTime
|
|
* {returns} Date assembled from input parameters
|
|
* {example} assembleMDS_DateTime(aDate, "23:59", "+00:00") returns the same day at 00:00 as aDate
|
|
* @throws ParseException
|
|
*/
|
|
public static OffsetDateTime assembleMDS_DateTime(LocalDate localDate, LocalTime localTime, String offsetTime) throws ParseException {
|
|
|
|
if (! offsetTime.startsWith("-") && ! offsetTime.startsWith("+"))
|
|
offsetTime = "+" + offsetTime;
|
|
|
|
OffsetDateTime dateTime = OffsetDateTime.of(localDate, localTime, ZoneOffset.of(offsetTime));
|
|
OffsetDateTime result = adjustMDS_Date(dateTime);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Created for DST enrichment: Calculates the UTC time from a given local MDS DST start/end time
|
|
* using the offsetTime given
|
|
*
|
|
* {talendTypes} String
|
|
*
|
|
* {Category} LCAG-DateTime
|
|
*
|
|
* {param} Object resultDateFormat : input: Date format to return, either a format string or a SimpleDateFormat.
|
|
* {param} LocalDateTime localDateTime: input: Date with time to convert
|
|
* {param} String("+HH:mm") offsetTime : input: Time offset to use, e.g. -04:00, +04:00, 04:00
|
|
*
|
|
* @throws ParseException
|
|
*/
|
|
public static String convertLocalTimeToUTC(Object resultDateFormat, LocalDateTime localDateTime, String offsetTime) throws ParseException {
|
|
String result = null;
|
|
|
|
OffsetDateTime resultDate = convertLocalTimeToUTC(localDateTime, offsetTime);
|
|
|
|
result = formatDate(resultDateFormat, resultDate).toUpperCase();
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Created for CABS timezone export: Calculates the UTC time from a given local MDS DST start/end time
|
|
* using the offsetTime given
|
|
*
|
|
* {talendTypes} String
|
|
*
|
|
* {Category} LCAG-DateTime
|
|
*
|
|
* {param} Object resultDateFormat : input: Date format to return, either a format string or a SimpleDateFormat.
|
|
* {param} Date localDate : input: Date to convert
|
|
* {param} Object("HH:mm") localTime : input: Local Time to convert as String, e.g. 02:00 or a Date object to extract this info from
|
|
* {param} String("+HH:mm") offsetTime : input: Time offset to use, e.g. -04:00, +04:00, 04:00
|
|
*
|
|
* {example} calculate_UTC_TimeFromLocal("ddMMMyy_HHmm", aDate, "23:59", "+04:30")
|
|
* @throws ParseException
|
|
*/
|
|
public static String convertLocalDateTimeToUTC(Object resultDateFormat, Date localDate, Object localTime, String offsetTime) throws ParseException {
|
|
String result = null;
|
|
String localTimeString = null;
|
|
String[] hoursMins = offsetTime.split(":");
|
|
|
|
if (hoursMins.length != 2) {
|
|
throw new IllegalArgumentException(String.format("Unsupport Local Time ('%s'), required format is HH:mm", localTimeString) );
|
|
}
|
|
|
|
LocalDate ld = localDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
|
LocalTime lt = null;
|
|
|
|
if ( localTime instanceof Date) {
|
|
lt = ((Date)localTime).toInstant().atZone(ZoneId.systemDefault()).toLocalTime();
|
|
} else if ( localTime instanceof String ) {
|
|
localTimeString = (String)localTime + ":00";
|
|
|
|
lt = LocalTime.parse(localTimeString);
|
|
} else {
|
|
throw new IllegalArgumentException("Local time argument type not supported");
|
|
}
|
|
|
|
OffsetDateTime localDateTime = assembleMDS_DateTime(ld, lt, offsetTime);
|
|
OffsetDateTime odt = convertToUTC(localDateTime);
|
|
|
|
result = formatDate(resultDateFormat, odt).toUpperCase();
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Formats a Date into a date/time string.
|
|
*
|
|
* @param aFormatter the pattern to format.
|
|
* @param aDate the time value to be formatted into a time string.
|
|
* @return the formatted time string.
|
|
*
|
|
* {talendTypes} String
|
|
*
|
|
* {Category} LCAG-DateTime
|
|
*
|
|
* {param} Object("yyyy-MM-dd HH:mm:ss") aFormatter : the pattern to format. May be a formatting String, a java DateFormat or a DateTimeFormatter
|
|
*
|
|
* {param} Object(aDate) aDate : the date und time value to be formatted into a date time string. May be of Type Date or OffsetDateTime
|
|
*
|
|
* {example} formatDate("yyyy-MM-dd", new Date())
|
|
*/
|
|
|
|
public synchronized static String formatDate(Object aFormatter, Object aDate) {
|
|
String result = null;
|
|
DateFormat df = null;
|
|
DateTimeFormatter formatter = null;
|
|
|
|
if ( aFormatter instanceof String) {
|
|
String fmt = (String)aFormatter;
|
|
|
|
// Use the best suited formatter next! Otherwise you may get problems in time zone handling!!
|
|
if ( aDate instanceof Date ) {
|
|
df = new SimpleDateFormat(fmt.replaceAll("u", "y"), Locale.ENGLISH);
|
|
} if ( aDate instanceof OffsetDateTime ) {
|
|
formatter = DateTimeFormatter.ofPattern(fmt.replaceAll("y", "u"), Locale.ENGLISH);
|
|
}
|
|
} else if ( aFormatter instanceof DateFormat ) {
|
|
df = (DateFormat)aFormatter;
|
|
} else if ( aFormatter instanceof DateTimeFormatter ) {
|
|
formatter = (DateTimeFormatter)aFormatter;
|
|
} else {
|
|
throw new IllegalArgumentException("Formatter argument type not supported");
|
|
}
|
|
|
|
if (aDate instanceof Date) {
|
|
Date d = (Date)aDate;
|
|
|
|
if (df != null) {
|
|
result = df.format(d);
|
|
} else if (formatter != null) {
|
|
result = formatter.format(d.toInstant());
|
|
}
|
|
} else if (aDate instanceof Long) {
|
|
Date d = new Date((Long)aDate);
|
|
|
|
if (df != null) {
|
|
result = df.format(d);
|
|
} else if (df != null) {
|
|
result = formatter.format(d.toInstant());
|
|
}
|
|
} else if (aDate instanceof OffsetDateTime) {
|
|
OffsetDateTime d = (OffsetDateTime)aDate;
|
|
|
|
if (formatter != null) {
|
|
result = formatter.format(d);
|
|
} else if (df != null) {
|
|
result = df.format(Date.from(d.toInstant()));
|
|
}
|
|
} else {
|
|
throw new IllegalArgumentException("Date argument type not supported");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|