Fixed unsafe conccurent access to generic SimpleDateFormat instances

SimpleDateFormat must not be used by concurrent threads without
synchronization for parsing or formating dates as it is not thread-safe
(internally holds a calendar instance that is not synchronized).

Prefer now DateTimeFormatter when possible as it is thread-safe without
concurrent access performance bottleneck (does not internally use
synchronization locks).
pull/186/head
luccioman 6 years ago
parent 38a3a5e5ad
commit e97580dfc7

@ -18,8 +18,8 @@
// along with this program; if not, write to the Free Software // along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
import net.yacy.cora.date.GenericFormatter; import net.yacy.cora.date.GenericFormatter;
import net.yacy.cora.protocol.RequestHeader; import net.yacy.cora.protocol.RequestHeader;
import net.yacy.data.UserDB; import net.yacy.data.UserDB;
@ -30,7 +30,7 @@ import net.yacy.server.serverSwitch;
public class ConfigAccountList_p { public class ConfigAccountList_p {
public static serverObjects respond(@SuppressWarnings("unused") final RequestHeader header, final serverObjects post, final serverSwitch env) { public static serverObjects respond(@SuppressWarnings("unused") final RequestHeader header, @SuppressWarnings("unused") final serverObjects post, final serverSwitch env) {
final serverObjects prop = new serverObjects(); final serverObjects prop = new serverObjects();
final Switchboard sb = (Switchboard) env; final Switchboard sb = (Switchboard) env;
@ -54,7 +54,8 @@ public class ConfigAccountList_p {
prop.putHTML("userlist_" + numUsers + "_firstname", entry.getFirstName()); prop.putHTML("userlist_" + numUsers + "_firstname", entry.getFirstName());
prop.putHTML("userlist_" + numUsers + "_address", entry.getAddress()); prop.putHTML("userlist_" + numUsers + "_address", entry.getAddress());
if (entry.getLastAccess() != null) { if (entry.getLastAccess() != null) {
prop.put("userlist_" + numUsers + "_lastaccess", GenericFormatter.FORMAT_SIMPLE.format(new Date(entry.getLastAccess()))); prop.put("userlist_" + numUsers + "_lastaccess",
GenericFormatter.formatSafely(entry.getLastAccess(), GenericFormatter.FORMAT_SIMPLE));
} else { } else {
prop.put("userlist_" + numUsers + "_lastaccess", "never"); prop.put("userlist_" + numUsers + "_lastaccess", "never");
} }

@ -25,6 +25,7 @@
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.time.Instant;
import java.util.Date; import java.util.Date;
import org.apache.http.Header; import org.apache.http.Header;
@ -131,11 +132,11 @@ public class IndexImportMediawiki_p {
if (status == 0 && post.getBoolean("iffresh")) { if (status == 0 && post.getBoolean("iffresh")) {
long lastModified = getLastModified(sourceURL); long lastModified = getLastModified(sourceURL);
if (lastExecutionDate != null && lastModified != 0L if (lastExecutionDate != null && lastModified != 0L && Instant.ofEpochMilli(lastModified)
&& lastModified <= lastExecutionDate.getTime()) { .isBefore(lastExecutionDate.toInstant())) {
status = 5; status = 5;
prop.put("import_status_lastImportDate", prop.put("import_status_lastImportDate", GenericFormatter
GenericFormatter.FORMAT_SIMPLE.format(lastExecutionDate)); .formatSafely(lastExecutionDate.toInstant(), GenericFormatter.FORMAT_SIMPLE));
/* the import is not performed, but we increase here the api call count */ /* the import is not performed, but we increase here the api call count */
if(sb.tables != null) { if(sb.tables != null) {

@ -19,6 +19,7 @@
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.ParseException; import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
@ -58,6 +59,8 @@ public class Table_API_p {
final Switchboard sb = (Switchboard) env; final Switchboard sb = (Switchboard) env;
final serverObjects prop = new serverObjects(); final serverObjects prop = new serverObjects();
final DateFormat dateFormat = GenericFormatter.newSimpleDateFormat();
prop.put("showexec", 0); prop.put("showexec", 0);
prop.put("showtable", 0); prop.put("showtable", 0);
@ -182,7 +185,7 @@ public class Table_API_p {
final int time = row.get(WorkTables.TABLE_API_COL_APICALL_SCHEDULE_TIME, 0); final int time = row.get(WorkTables.TABLE_API_COL_APICALL_SCHEDULE_TIME, 0);
final String dateNextExecStr = entry.getValue().trim(); final String dateNextExecStr = entry.getValue().trim();
try { try {
final Date dateNextExec = GenericFormatter.FORMAT_SIMPLE.parse(dateNextExecStr); final Date dateNextExec = dateFormat.parse(dateNextExecStr);
if(time != 0) { // Check there is effectively a schedule period on this row if(time != 0) { // Check there is effectively a schedule period on this row
if(dateNextExec.before(now)) { if(dateNextExec.before(now)) {
@ -390,8 +393,8 @@ public class Table_API_p {
prop.put("showtable_list_" + count + "_pk", UTF8.String(row.getPK())); prop.put("showtable_list_" + count + "_pk", UTF8.String(row.getPK()));
prop.put("showtable_list_" + count + "_count", count); prop.put("showtable_list_" + count + "_count", count);
prop.put("showtable_list_" + count + "_callcount", callcount); prop.put("showtable_list_" + count + "_callcount", callcount);
prop.put("showtable_list_" + count + "_dateRecording", date_recording == null ? "-" : GenericFormatter.FORMAT_SIMPLE.format(date_recording)); prop.put("showtable_list_" + count + "_dateRecording", date_recording == null ? "-" : dateFormat.format(date_recording));
prop.put("showtable_list_" + count + "_dateLastExec", date_last_exec == null ? "-" : GenericFormatter.FORMAT_SIMPLE.format(date_last_exec)); prop.put("showtable_list_" + count + "_dateLastExec", date_last_exec == null ? "-" : dateFormat.format(date_last_exec));
prop.put("showtable_list_" + count + "_editableDateNext", time != 0); prop.put("showtable_list_" + count + "_editableDateNext", time != 0);
final String enteredDateBeforeNow = nextExecDatesBeforeNow.get(rowPKStr); final String enteredDateBeforeNow = nextExecDatesBeforeNow.get(rowPKStr);
@ -407,7 +410,7 @@ public class Table_API_p {
} }
prop.put("showtable_list_" + count + "_editableDateNext_dateLastExecPattern", GenericFormatter.PATTERN_SIMPLE_REGEX); prop.put("showtable_list_" + count + "_editableDateNext_dateLastExecPattern", GenericFormatter.PATTERN_SIMPLE_REGEX);
prop.put("showtable_list_" + count + "_editableDateNext_dateNextExec", date_next_exec == null ? "-" : GenericFormatter.FORMAT_SIMPLE.format(date_next_exec)); prop.put("showtable_list_" + count + "_editableDateNext_dateNextExec", date_next_exec == null ? "-" : dateFormat.format(date_next_exec));
prop.put("showtable_list_" + count + "_editableDateNext_pk", rowPKStr); prop.put("showtable_list_" + count + "_editableDateNext_pk", rowPKStr);
prop.put("showtable_list_" + count + "_type", row.get(WorkTables.TABLE_API_COL_TYPE)); prop.put("showtable_list_" + count + "_type", row.get(WorkTables.TABLE_API_COL_TYPE));

@ -27,6 +27,13 @@ package net.yacy.cora.date;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.time.DateTimeException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.Locale; import java.util.Locale;
@ -44,37 +51,159 @@ public class GenericFormatter extends AbstractFormatter implements DateFormatter
public static final String PATTERN_ANSIC = "EEE MMM d HH:mm:ss yyyy"; public static final String PATTERN_ANSIC = "EEE MMM d HH:mm:ss yyyy";
public static final String PATTERN_SIMPLE = "yyyy/MM/dd HH:mm:ss"; public static final String PATTERN_SIMPLE = "yyyy/MM/dd HH:mm:ss";
/** A regular expression matching the PATTERN_SIMPLE pattern (does not control last day of month (30/31 or 28/29 for february). /**
* Can be used as a HTML5 input field validation pattern */ * A regular expression matching the PATTERN_SIMPLE pattern (does not control
public static final String PATTERN_SIMPLE_REGEX ="[0-9]{4}/(0[1-9]|1[012])/(0[1-9]|1[0-9]|2[0-9]|3[01]) (0[0-9]|1[0-9]|2[0-3])(:[0-5][0-9]){2}"; * last day of month (30/31 or 28/29 for february). Can be used as a HTML5 input
* field validation pattern
public static final SimpleDateFormat FORMAT_SHORT_DAY = new SimpleDateFormat(PATTERN_SHORT_DAY, Locale.US); */
public static final SimpleDateFormat FORMAT_SHORT_MINUTE = new SimpleDateFormat(PATTERN_SHORT_MINUTE, Locale.US); public static final String PATTERN_SIMPLE_REGEX = "[0-9]{4}/(0[1-9]|1[012])/(0[1-9]|1[0-9]|2[0-9]|3[01]) (0[0-9]|1[0-9]|2[0-3])(:[0-5][0-9]){2}";
public static final SimpleDateFormat FORMAT_SHORT_SECOND = new SimpleDateFormat(PATTERN_SHORT_SECOND, Locale.US);
public static final SimpleDateFormat FORMAT_SHORT_MILSEC = new SimpleDateFormat(PATTERN_SHORT_MILSEC, Locale.US); /**
public static final SimpleDateFormat FORMAT_RFC1123_SHORT = new SimpleDateFormat(PATTERN_RFC1123_SHORT, Locale.US); * A thread-safe date formatter using the
public static final SimpleDateFormat FORMAT_ANSIC = new SimpleDateFormat(PATTERN_ANSIC, Locale.US); * {@link GenericFormatter#PATTERN_SHORT_DAY} pattern with the US locale on the
public static final SimpleDateFormat FORMAT_SIMPLE = new SimpleDateFormat(PATTERN_SIMPLE, Locale.US); * UTC time zone.
*/
static { public static final DateTimeFormatter FORMAT_SHORT_DAY = DateTimeFormatter
// we want GMT times on the formats as well as they don't support any timezone .ofPattern(PATTERN_SHORT_DAY.replace("yyyy", "uuuu"), Locale.US).withZone(ZoneOffset.UTC);
FORMAT_SHORT_DAY.setTimeZone(UTCtimeZone);
FORMAT_SHORT_SECOND.setTimeZone(UTCtimeZone); /**
FORMAT_SHORT_MILSEC.setTimeZone(UTCtimeZone); * A thread-safe date formatter using the
} * {@link GenericFormatter#PATTERN_SHORT_MINUTE} pattern with the US locale on
* the system time zone.
*/
public static final DateTimeFormatter FORMAT_SHORT_MINUTE = DateTimeFormatter
.ofPattern(PATTERN_SHORT_MINUTE.replace("yyyy", "uuuu"), Locale.US).withZone(ZoneId.systemDefault());
/**
* A thread-safe date formatter using the
* {@link GenericFormatter#PATTERN_SHORT_SECOND} pattern with the US locale on
* the UTC time zone.
*/
public static final DateTimeFormatter FORMAT_SHORT_SECOND = DateTimeFormatter
.ofPattern(PATTERN_SHORT_SECOND.replace("yyyy", "uuuu"), Locale.US).withZone(ZoneOffset.UTC);
/**
* A thread-safe date formatter using the
* {@link GenericFormatter#PATTERN_SHORT_MILSEC} pattern with the US locale on
* the UTC time zone.
*/
public static final DateTimeFormatter FORMAT_SHORT_MILSEC = new DateTimeFormatterBuilder()
.appendPattern(PATTERN_SHORT_MILSEC.replace("yyyy", "uuuu").replaceAll("SSS", ""))
.appendValue(ChronoField.MILLI_OF_SECOND, 3).toFormatter().withLocale(Locale.US)
.withZone(ZoneOffset.UTC);/* we can not use here the 'SSS' pattern for milliseconds on JDK 8 (see https://bugs.openjdk.java.net/browse/JDK-8031085) */
/**
* A thread-safe date formatter using the
* {@link GenericFormatter#PATTERN_RFC1123_SHORT} pattern with the US locale on
* the system time zone.
*/
public static final DateTimeFormatter FORMAT_RFC1123_SHORT = DateTimeFormatter
.ofPattern(PATTERN_RFC1123_SHORT.replace("yyyy", "uuuu"), Locale.US).withZone(ZoneId.systemDefault());
/**
* A thread-safe date formatter using the {@link GenericFormatter#PATTERN_ANSIC}
* pattern with the US locale on the system time zone.
*/
public static final DateTimeFormatter FORMAT_ANSIC = DateTimeFormatter.ofPattern(PATTERN_ANSIC.replace("yyyy", "uuuu"), Locale.US)
.withZone(ZoneId.systemDefault());
/**
* A thread-safe date formatter using the
* {@link GenericFormatter#PATTERN_SIMPLE} pattern (adapted for the DateTimeFormatter class) with the US locale on the
* system time zone.
*/
public static final DateTimeFormatter FORMAT_SIMPLE = DateTimeFormatter.ofPattern(PATTERN_SIMPLE.replace("yyyy", "uuuu"), Locale.US)
.withZone(ZoneId.systemDefault());
/**
* @return a new SimpleDateFormat instance using the
* {@link GenericFormatter#PATTERN_SHORT_DAY} pattern with the US
* locale.
*/
public static SimpleDateFormat newShortDayFormat() {
final SimpleDateFormat dateFormat = new SimpleDateFormat(GenericFormatter.PATTERN_SHORT_DAY, Locale.US);
// we want GMT times on the formats as well as they don't support any timezone
dateFormat.setTimeZone(UTCtimeZone);
return dateFormat;
}
/**
* @return a new SimpleDateFormat instance using the
* {@link GenericFormatter#PATTERN_SHORT_MINUTE} pattern with the US
* locale.
*/
public static SimpleDateFormat newShortMinuteFormat() {
return new SimpleDateFormat(GenericFormatter.PATTERN_SHORT_MINUTE, Locale.US);
}
/**
* @return a new SimpleDateFormat instance using the
* {@link GenericFormatter#PATTERN_SHORT_SECOND} pattern with the US
* locale.
*/
public static SimpleDateFormat newShortSecondFormat() {
final SimpleDateFormat dateFormat = new SimpleDateFormat(GenericFormatter.PATTERN_SHORT_SECOND, Locale.US);
// we want GMT times on the formats as well as they don't support any timezone
dateFormat.setTimeZone(UTCtimeZone);
return dateFormat;
}
/**
* @return a new SimpleDateFormat instance using the
* {@link GenericFormatter#PATTERN_SHORT_MILSEC} pattern with the US
* locale.
*/
public static SimpleDateFormat newShortMilsecFormat() {
final SimpleDateFormat dateFormat = new SimpleDateFormat(GenericFormatter.PATTERN_SHORT_MILSEC, Locale.US);
// we want GMT times on the formats as well as they don't support any timezone
dateFormat.setTimeZone(UTCtimeZone);
return dateFormat;
}
/**
* @return a new SimpleDateFormat instance using the
* {@link GenericFormatter#PATTERN_RFC1123_SHORT} pattern with the US
* locale.
*/
public static SimpleDateFormat newRfc1123ShortFormat() {
return new SimpleDateFormat(GenericFormatter.PATTERN_RFC1123_SHORT, Locale.US);
}
/**
* @return a new SimpleDateFormat instance using the
* {@link GenericFormatter#PATTERN_ANSIC} pattern with the US locale.
*/
public static SimpleDateFormat newAnsicFormat() {
return new SimpleDateFormat(GenericFormatter.PATTERN_ANSIC, Locale.US);
}
/**
* @return a new SimpleDateFormat instance using the
* {@link GenericFormatter#PATTERN_SIMPLE} pattern with the US locale.
*/
public static SimpleDateFormat newSimpleDateFormat() {
return new SimpleDateFormat(GenericFormatter.PATTERN_SIMPLE, Locale.US);
}
public static final long time_second = 1000L; public static final long time_second = 1000L;
public static final long time_minute = 60000L; public static final long time_minute = 60000L;
public static final long time_hour = 60 * time_minute; public static final long time_hour = 60 * time_minute;
public static final long time_day = 24 * time_hour; public static final long time_day = 24 * time_hour;
public static final GenericFormatter SHORT_DAY_FORMATTER = new GenericFormatter(FORMAT_SHORT_DAY, time_minute); public static final GenericFormatter SHORT_DAY_FORMATTER = new GenericFormatter(newShortDayFormat(), time_minute);
public static final GenericFormatter SHORT_MINUTE_FORMATTER = new GenericFormatter(FORMAT_SHORT_MINUTE, time_second); public static final GenericFormatter SHORT_MINUTE_FORMATTER = new GenericFormatter(newShortMinuteFormat(), time_second);
public static final GenericFormatter SHORT_SECOND_FORMATTER = new GenericFormatter(FORMAT_SHORT_SECOND, time_second); public static final GenericFormatter SHORT_SECOND_FORMATTER = new GenericFormatter(newShortSecondFormat(), time_second);
public static final GenericFormatter SHORT_MILSEC_FORMATTER = new GenericFormatter(FORMAT_SHORT_MILSEC, 1); public static final GenericFormatter SHORT_MILSEC_FORMATTER = new GenericFormatter(newShortMilsecFormat(), 1);
public static final GenericFormatter RFC1123_SHORT_FORMATTER = new GenericFormatter(FORMAT_RFC1123_SHORT, time_minute); public static final GenericFormatter RFC1123_SHORT_FORMATTER = new GenericFormatter(newRfc1123ShortFormat(), time_minute);
public static final GenericFormatter ANSIC_FORMATTER = new GenericFormatter(FORMAT_ANSIC, time_second); public static final GenericFormatter ANSIC_FORMATTER = new GenericFormatter(newAnsicFormat(), time_second);
public static final GenericFormatter SIMPLE_FORMATTER = new GenericFormatter(FORMAT_SIMPLE, time_second); public static final GenericFormatter SIMPLE_FORMATTER = new GenericFormatter(newSimpleDateFormat(), time_second);
private final SimpleDateFormat dateFormat; private final SimpleDateFormat dateFormat;
private final long maxCacheDiff; private final long maxCacheDiff;
@ -201,6 +330,59 @@ public class GenericFormatter extends AbstractFormatter implements DateFormatter
} }
private final static DecimalFormat D2 = new DecimalFormat("00"); private final static DecimalFormat D2 = new DecimalFormat("00");
/**
* Safely format the given time value using the given formatter. Fallback to
* ISO-8601 representation or to raw time value without exception when the
* format can not be applied.
*
* @param time
* a time value as millisecnods from Epoch (1970-01-01T00:00:00Z)
* @param formatter
* the formatter to use
* @return a String representation of the time value
*/
public static String formatSafely(final long time, final DateTimeFormatter formatter) {
String res;
try {
res = formatSafely(Instant.ofEpochMilli(time), formatter);
} catch (final DateTimeException e) {
/*
* Can occur on Instant.ofEpochMilli when the time value is greater than
* Instant.MAX.toEpochMilli() or lower than Instant.MIN.toEpochMilli()
*/
res = String.valueOf(time);
}
return res;
}
/**
* Safely format the given instant using the given formatter. Fallback to
* ISO-8601 representation without exception when the format can not be applied.
*
* @param instant
* the instant to format
* @param formatter
* the formatter to use
* @return a String representation of the time value
*/
public static String formatSafely(final Instant instant, final DateTimeFormatter formatter) {
String res;
if (instant == null) {
res = "";
} else {
try {
if (formatter != null) {
res = formatter.format(instant);
} else {
res = instant.toString();
}
} catch (final DateTimeException e) {
res = instant.toString();
}
}
return res;
}
public static void main(String[] args) { public static void main(String[] args) {
System.out.println(UTCDiffString()); System.out.println(UTCDiffString());

@ -245,7 +245,7 @@ public class HeaderFramework extends TreeMap<String, String> implements Map<Stri
// RFC 1036/850 (old) "Monday, 12-Nov-07 10:11:12 GMT" // RFC 1036/850 (old) "Monday, 12-Nov-07 10:11:12 GMT"
FORMAT_RFC1036, FORMAT_RFC1036,
// ANSI C asctime() "Mon Nov 12 10:11:12 2007" // ANSI C asctime() "Mon Nov 12 10:11:12 2007"
GenericFormatter.FORMAT_ANSIC, GenericFormatter.newAnsicFormat(),
}; };

@ -674,8 +674,8 @@ public class DateDetection {
// check standard date formats // check standard date formats
try {d = CONFORM.parse(text);} catch (ParseException e) {} try {d = CONFORM.parse(text);} catch (ParseException e) {}
//if (d == null) try {d = GenericFormatter.FORMAT_SHORT_DAY.parse(text);} catch (ParseException e) {} // did not work well and fired for wrong formats; do not use //if (d == null) try {d = GenericFormatter.FORMAT_SHORT_DAY.parse(text);} catch (ParseException e) {} // did not work well and fired for wrong formats; do not use
if (d == null) try {d = GenericFormatter.FORMAT_RFC1123_SHORT.parse(text);} catch (ParseException e) {} if (d == null) try {d = GenericFormatter.newRfc1123ShortFormat().parse(text);} catch (ParseException e) {}
if (d == null) try {d = GenericFormatter.FORMAT_ANSIC.parse(text);} catch (ParseException e) {} if (d == null) try {d = GenericFormatter.newAnsicFormat().parse(text);} catch (ParseException e) {}
if (d == null) { if (d == null) {
// check other date formats // check other date formats

@ -103,7 +103,7 @@ public class ArrayStack implements BLOB {
private final ExecutorService executor; private final ExecutorService executor;
// use our own formatter to prevent concurrency locks with other processes // use our own formatter to prevent concurrency locks with other processes
private final static GenericFormatter my_SHORT_MILSEC_FORMATTER = new GenericFormatter(GenericFormatter.FORMAT_SHORT_MILSEC, 1); private final static GenericFormatter my_SHORT_MILSEC_FORMATTER = new GenericFormatter(GenericFormatter.newShortMilsecFormat(), 1);
public ArrayStack( public ArrayStack(

@ -32,6 +32,8 @@ import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.time.DateTimeException;
import java.time.Instant;
import java.util.AbstractMap; import java.util.AbstractMap;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
@ -144,9 +146,6 @@ public class MapHeap implements Map<byte[], Map<String, String>> {
} }
// use our own formatter to prevent concurrency locks with other processes
private final static GenericFormatter my_SHORT_SECOND_FORMATTER = new GenericFormatter(GenericFormatter.FORMAT_SHORT_SECOND, GenericFormatter.time_second);
/** /**
* write a whole byte array as Map to the table * write a whole byte array as Map to the table
* @param key the primary key * @param key the primary key
@ -159,7 +158,15 @@ public class MapHeap implements Map<byte[], Map<String, String>> {
assert key.length > 0; assert key.length > 0;
assert newMap != null; assert newMap != null;
key = normalizeKey(key); key = normalizeKey(key);
final String s = map2string(newMap, "W" + my_SHORT_SECOND_FORMATTER.format() + " "); String formattedTime;
try {
/* Prefer using first the shared and thread-safe DateTimeFormatter instance */
formattedTime = GenericFormatter.FORMAT_SHORT_SECOND.format(Instant.now());
} catch (final DateTimeException e) {
/* This should not happen, but rather than failing we fallback to the old formatter wich uses synchronization locks */
formattedTime = GenericFormatter.SHORT_SECOND_FORMATTER.format();
}
final String s = map2string(newMap, "W" + formattedTime + " ");
assert s != null; assert s != null;
final byte[] sb = UTF8.getBytes(s); final byte[] sb = UTF8.getBytes(s);
if (this.cache == null) { if (this.cache == null) {

@ -75,7 +75,7 @@ public class Tables implements Iterable<String> {
private int keymaxlen; private int keymaxlen;
// use our own formatter to prevent concurrency locks with other processes // use our own formatter to prevent concurrency locks with other processes
private final static GenericFormatter my_SHORT_MILSEC_FORMATTER = new GenericFormatter(GenericFormatter.FORMAT_SHORT_MILSEC, 1); private final static GenericFormatter my_SHORT_MILSEC_FORMATTER = new GenericFormatter(GenericFormatter.newShortMilsecFormat(), 1);
public Tables(final File location, final int keymaxlen) { public Tables(final File location, final int keymaxlen) {
this.location = new File(location.getAbsolutePath()); this.location = new File(location.getAbsolutePath());

@ -26,6 +26,9 @@ import java.awt.Dimension;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.text.ParseException; import java.text.ParseException;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -125,24 +128,12 @@ public class URIMetadataNode extends SolrDocument /* implements Comparable<URIMe
this.lon = (lons == null) ? 0.0d : Double.parseDouble(lons); this.lon = (lons == null) ? 0.0d : Double.parseDouble(lons);
this.lat = (lats == null) ? 0.0d : Double.parseDouble(lats); this.lat = (lats == null) ? 0.0d : Double.parseDouble(lats);
// create new formatters to make concurrency possible this.setField(CollectionSchema.last_modified.name(), parseShortDayDate(prop.getProperty("mod", "20000101")));
final GenericFormatter formatter = new GenericFormatter(GenericFormatter.FORMAT_SHORT_DAY, GenericFormatter.time_minute);
this.setField(CollectionSchema.load_date_dt.name(), parseShortDayDate(prop.getProperty("load", "20000101")));
try {
this.setField(CollectionSchema.last_modified.name(), formatter.parse(prop.getProperty("mod", "20000101"), 0).getTime()); this.setField(CollectionSchema.fresh_date_dt.name(), parseShortDayDate(prop.getProperty("fresh", "20000101")));
} catch (final ParseException e) {
this.setField(CollectionSchema.last_modified.name(), new Date());
}
try {
this.setField(CollectionSchema.load_date_dt.name(), formatter.parse(prop.getProperty("load", "20000101"), 0).getTime());
} catch (final ParseException e) {
this.setField(CollectionSchema.load_date_dt.name(), new Date());
}
try {
this.setField(CollectionSchema.fresh_date_dt.name(), formatter.parse(prop.getProperty("fresh", "20000101"), 0).getTime());
} catch (final ParseException e) {
this.setField(CollectionSchema.fresh_date_dt.name(), new Date());
}
this.setField(CollectionSchema.referrer_id_s.name(), prop.getProperty("referrer", "")); this.setField(CollectionSchema.referrer_id_s.name(), prop.getProperty("referrer", ""));
// this.setField(CollectionSchema.md5_s.name(), prop.getProperty("md5", "")); // always 0 (not used / calculated) // this.setField(CollectionSchema.md5_s.name(), prop.getProperty("md5", "")); // always 0 (not used / calculated)
this.setField(CollectionSchema.size_i.name(), Integer.parseInt(prop.getProperty("size", "0"))); this.setField(CollectionSchema.size_i.name(), Integer.parseInt(prop.getProperty("size", "0")));
@ -725,14 +716,56 @@ public class URIMetadataNode extends SolrDocument /* implements Comparable<URIMe
return null; return null;
} }
} }
/**
* Format a date using the short day format.
* @param date the date to format. Must not be null.
* @return the formatted date
* @throws NullPointerException when date is null.
*/
private String formatShortDayDate(final Date date) {
String formattedDate;
try {
/* Prefer using first the thread-safe shared instance of DateTimeFormatter */
formattedDate = GenericFormatter.FORMAT_SHORT_DAY.format(date.toInstant());
} catch (final DateTimeException e) {
/*
* Should not happen, but rather than failing it is preferable to use the old
* formatter which uses synchronization locks
*/
formattedDate = GenericFormatter.SHORT_DAY_FORMATTER.format(date);
}
return formattedDate;
}
/**
* Parse a date string with the short day format.
*
* @param dateStr
* a date representation as a String. Must not be null.
* @return the parsed Date or the current date when an parsing error occurred.
*/
private Date parseShortDayDate(final String dateStr) {
Date parsed;
try {
/* Prefer using first the thread-safe shared instance of DateTimeFormatter */
parsed = Date.from(LocalDate.parse(dateStr, GenericFormatter.FORMAT_SHORT_DAY).atStartOfDay()
.toInstant(ZoneOffset.UTC));
} catch (final RuntimeException e) {
/* Retry with the old formatter which uses synchronization locks */
try {
parsed = GenericFormatter.SHORT_DAY_FORMATTER.parse(dateStr, 0).getTime();
} catch (final ParseException pe) {
parsed = new Date();
}
}
return parsed;
}
protected StringBuilder corePropList() { protected StringBuilder corePropList() {
// generate a parseable string; this is a simple property-list // generate a parseable string; this is a simple property-list
final StringBuilder s = new StringBuilder(300); final StringBuilder s = new StringBuilder(300);
// create new formatters to make concurrency possible
final GenericFormatter formatter = new GenericFormatter(GenericFormatter.FORMAT_SHORT_DAY, GenericFormatter.time_minute);
try { try {
s.append("hash=").append(ASCII.String(this.hash())); s.append("hash=").append(ASCII.String(this.hash()));
s.append(",url=").append(crypt.simpleEncode(this.url().toNormalform(true))); s.append(",url=").append(crypt.simpleEncode(this.url().toNormalform(true)));
@ -742,9 +775,9 @@ public class URIMetadataNode extends SolrDocument /* implements Comparable<URIMe
s.append(",publisher=").append(crypt.simpleEncode(this.dc_publisher())); s.append(",publisher=").append(crypt.simpleEncode(this.dc_publisher()));
s.append(",lat=").append(this.lat()); s.append(",lat=").append(this.lat());
s.append(",lon=").append(this.lon()); s.append(",lon=").append(this.lon());
s.append(",mod=").append(formatter.format(this.moddate())); s.append(",mod=").append(formatShortDayDate(this.moddate()));
s.append(",load=").append(formatter.format(this.loaddate())); s.append(",load=").append(formatShortDayDate(this.loaddate()));
s.append(",fresh=").append(formatter.format(this.freshdate())); s.append(",fresh=").append(formatShortDayDate(this.freshdate()));
s.append(",referrer=").append(this.referrerHash() == null ? "" : ASCII.String(this.referrerHash())); s.append(",referrer=").append(this.referrerHash() == null ? "" : ASCII.String(this.referrerHash()));
//s.append(",md5=").append(this.md5()); // md5 never calculated / not used, also removed from this(prop) 2015-11-27 //s.append(",md5=").append(this.md5()); // md5 never calculated / not used, also removed from this(prop) 2015-11-27
s.append(",size=").append(this.filesize()); s.append(",size=").append(this.filesize());

@ -40,7 +40,6 @@ package net.yacy.peers;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
@ -49,6 +48,7 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import net.yacy.cora.date.GenericFormatter; import net.yacy.cora.date.GenericFormatter;
import net.yacy.cora.document.encoding.ASCII; import net.yacy.cora.document.encoding.ASCII;
import net.yacy.cora.document.feed.RSSFeed; import net.yacy.cora.document.feed.RSSFeed;
@ -188,11 +188,6 @@ public class Network
publishMySeed(); publishMySeed();
} }
// use our own formatter to prevent concurrency locks with other processes
private final static GenericFormatter my_SHORT_SECOND_FORMATTER = new GenericFormatter(
GenericFormatter.FORMAT_SHORT_SECOND,
GenericFormatter.time_second);
protected class publishThread extends Thread protected class publishThread extends Thread
{ {
private final Seed seed; private final Seed seed;
@ -256,13 +251,19 @@ public class Network
// update last seed date // update last seed date
if ( newSeed.getLastSeenUTC() >= this.seed.getLastSeenUTC() ) { if ( newSeed.getLastSeenUTC() >= this.seed.getLastSeenUTC() ) {
if ( log.isFine() ) { if ( log.isFine() ) {
log.fine("publish: recently handshaked " + this.seed.get(Seed.PEERTYPE, Seed.PEERTYPE_SENIOR) + " peer '" + this.seed.getName() + "' at " + this.seed.getIPs() + " with old LastSeen: '" + my_SHORT_SECOND_FORMATTER.format(new Date(newSeed.getLastSeenUTC())) + "'"); final String newSeedLastSeenStr = GenericFormatter.formatSafely(
newSeed.getLastSeenUTC(), GenericFormatter.FORMAT_SHORT_SECOND);
log.fine("publish: recently handshaked " + this.seed.get(Seed.PEERTYPE, Seed.PEERTYPE_SENIOR) + " peer '" + this.seed.getName() + "' at " + this.seed.getIPs() + " with old LastSeen: '" + newSeedLastSeenStr + "'");
} }
newSeed.setLastSeenUTC(); newSeed.setLastSeenUTC();
Network.this.sb.peers.peerActions.peerArrival(newSeed, true); Network.this.sb.peers.peerActions.peerArrival(newSeed, true);
} else { } else {
if ( log.isFine() ) { if ( log.isFine() ) {
log.fine("publish: recently handshaked " + this.seed.get(Seed.PEERTYPE, Seed.PEERTYPE_SENIOR) + " peer '" + this.seed.getName() + "' at " + this.seed.getIPs() + " with old LastSeen: '" + my_SHORT_SECOND_FORMATTER.format(new Date(newSeed.getLastSeenUTC())) + "', this is more recent: '" + my_SHORT_SECOND_FORMATTER.format(new Date(this.seed.getLastSeenUTC())) + "'"); final String newSeedLastSeenStr = GenericFormatter.formatSafely(
newSeed.getLastSeenUTC(), GenericFormatter.FORMAT_SHORT_SECOND);
final String thisSeedLastSeenStr = GenericFormatter.formatSafely(
newSeed.getLastSeenUTC(), GenericFormatter.FORMAT_SHORT_SECOND);
log.fine("publish: recently handshaked " + this.seed.get(Seed.PEERTYPE, Seed.PEERTYPE_SENIOR) + " peer '" + this.seed.getName() + "' at " + this.seed.getIPs() + " with old LastSeen: '" + newSeedLastSeenStr + "', this is more recent: '" + thisSeedLastSeenStr + "'");
} }
this.seed.setLastSeenUTC(); this.seed.setLastSeenUTC();
Network.this.sb.peers.peerActions.peerArrival(this.seed, true); Network.this.sb.peers.peerActions.peerArrival(this.seed, true);

@ -162,7 +162,7 @@ public class NewsDB {
} }
// use our own formatter to prevent concurrency locks with other processes // use our own formatter to prevent concurrency locks with other processes
private final static GenericFormatter my_SHORT_SECOND_FORMATTER = new GenericFormatter(GenericFormatter.FORMAT_SHORT_SECOND, GenericFormatter.time_second); private final static GenericFormatter my_SHORT_SECOND_FORMATTER = new GenericFormatter(GenericFormatter.newShortSecondFormat(), GenericFormatter.time_second);
private Record b2r(final Row.Entry b) { private Record b2r(final Row.Entry b) {
if (b == null) return null; if (b == null) return null;

@ -49,8 +49,11 @@ import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.time.DateTimeException;
import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
@ -2148,10 +2151,17 @@ public final class Protocol {
if ( targetHash != null ) parts.put("youare", UTF8.StringBody(targetHash)); if ( targetHash != null ) parts.put("youare", UTF8.StringBody(targetHash));
// time information for synchronization // time information for synchronization
// use our own formatter to prevent concurrency locks with other processes final long myTime = System.currentTimeMillis();
final GenericFormatter my_SHORT_SECOND_FORMATTER = new GenericFormatter(GenericFormatter.FORMAT_SHORT_SECOND, GenericFormatter.time_second); String formattedTime;
parts.put("mytime", UTF8.StringBody(my_SHORT_SECOND_FORMATTER.format())); try {
parts.put("myUTC", UTF8.StringBody(Long.toString(System.currentTimeMillis()))); /* Prefer using first the shared and thread-safe DateTimeFormatter instance */
formattedTime = GenericFormatter.FORMAT_SHORT_SECOND.format(Instant.ofEpochMilli(myTime));
} catch(final DateTimeException e) {
/* This should not happen, but rather than failing we fallback to the old formatter wich uses synchronization locks */
formattedTime = GenericFormatter.SHORT_SECOND_FORMATTER.format(new Date(myTime));
}
parts.put("mytime", UTF8.StringBody(formattedTime));
parts.put("myUTC", UTF8.StringBody(Long.toString(myTime)));
// network identification // network identification
parts.put(SwitchboardConstants.NETWORK_NAME, UTF8.StringBody(Switchboard.getSwitchboard().getConfig( parts.put(SwitchboardConstants.NETWORK_NAME, UTF8.StringBody(Switchboard.getSwitchboard().getConfig(

@ -49,6 +49,10 @@ import java.net.InetAddress;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.text.ParseException; import java.text.ParseException;
import java.time.DateTimeException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date; import java.util.Date;
@ -855,14 +859,15 @@ public class Seed implements Cloneable, Comparable<Seed>, Comparator<Seed>
return Integer.parseInt(port); return Integer.parseInt(port);
} }
/** puts the current time into the lastseen field and cares about the time differential to UTC */ /** puts the current UTC time into the lastseen field */
public final void setLastSeenUTC() { public final void setLastSeenUTC() {
// because java thinks it must apply the UTC offset to the current time, try {
// to create a string that looks like our current time, it adds the local UTC offset to the /* Prefer using first the shared and thread-safe DateTimeFormatter instance */
// time. To create a corrected UTC Date string, we first subtract the local UTC offset. this.dna.put(Seed.LASTSEEN, GenericFormatter.FORMAT_SHORT_SECOND.format(Instant.now()));
final GenericFormatter my_SHORT_SECOND_FORMATTER = new GenericFormatter(GenericFormatter.FORMAT_SHORT_SECOND, GenericFormatter.time_second); // use our own formatter to prevent concurrency locks with other processes } catch(final DateTimeException e) {
final String ls = my_SHORT_SECOND_FORMATTER.format(new Date(System.currentTimeMillis() /*- DateFormatter.UTCDiff()*/)); /* This should not happen, but rather than failing we fallback to the old formatter wich uses synchronization locks */
this.dna.put(Seed.LASTSEEN, ls); this.dna.put(Seed.LASTSEEN, GenericFormatter.SHORT_SECOND_FORMATTER.format(new Date()));
}
} }
/** /**
@ -870,9 +875,17 @@ public class Seed implements Cloneable, Comparable<Seed>, Comparator<Seed>
*/ */
public final long getLastSeenUTC() { public final long getLastSeenUTC() {
try { try {
final GenericFormatter my_SHORT_SECOND_FORMATTER = final String lastSeenStr = get(Seed.LASTSEEN, "20040101000000");
new GenericFormatter(GenericFormatter.FORMAT_SHORT_SECOND, GenericFormatter.time_second); // use our own formatter to prevent concurrency locks with other processes long t;
final long t = my_SHORT_SECOND_FORMATTER.parse(get(Seed.LASTSEEN, "20040101000000"), 0).getTime().getTime(); try {
/* Prefer using first the shared and thread-safe DateTimeFormatter instance */
t = LocalDateTime.parse(lastSeenStr, GenericFormatter.FORMAT_SHORT_SECOND).toInstant(ZoneOffset.UTC).toEpochMilli();
} catch(final RuntimeException e) {
/* Retry with the old date API parser */
final GenericFormatter my_SHORT_SECOND_FORMATTER =
new GenericFormatter(GenericFormatter.newShortSecondFormat(), GenericFormatter.time_second); // use our own formatter to prevent concurrency locks with other processes
t = my_SHORT_SECOND_FORMATTER.parse(lastSeenStr, 0).getTime().getTime();
}
// getTime creates a UTC time number. But in this case java thinks, that the given // getTime creates a UTC time number. But in this case java thinks, that the given
// time string is a local time, which has a local UTC offset applied. // time string is a local time, which has a local UTC offset applied.
// Therefore java subtracts the local UTC offset, to get a UTC number. // Therefore java subtracts the local UTC offset, to get a UTC number.
@ -903,13 +916,20 @@ public class Seed implements Cloneable, Comparable<Seed>, Comparator<Seed>
return this.birthdate; return this.birthdate;
} }
long b; long b;
try { final String bdateStr = get(Seed.BDATE, "20040101000000");
final GenericFormatter my_SHORT_SECOND_FORMATTER = try {
new GenericFormatter(GenericFormatter.FORMAT_SHORT_SECOND, GenericFormatter.time_second); // use our own formatter to prevent concurrency locks with other processes /* Prefer using first the shared and thread-safe DateTimeFormatter instance */
b = my_SHORT_SECOND_FORMATTER.parse(get(Seed.BDATE, "20040101000000"), 0).getTime().getTime(); b = LocalDateTime.parse(bdateStr, GenericFormatter.FORMAT_SHORT_SECOND).toInstant(ZoneOffset.UTC).toEpochMilli();
} catch (final ParseException e ) { } catch(final RuntimeException e) {
b = System.currentTimeMillis(); /* Retry with the old date API parser */
} try {
final GenericFormatter my_SHORT_SECOND_FORMATTER =
new GenericFormatter(GenericFormatter.newShortSecondFormat(), GenericFormatter.time_second); // use our own formatter to prevent concurrency locks with other processes
b = my_SHORT_SECOND_FORMATTER.parse(bdateStr, 0).getTime().getTime();
} catch (final ParseException pe) {
b = System.currentTimeMillis();
}
}
this.birthdate = b; this.birthdate = b;
return this.birthdate; return this.birthdate;
} }

@ -741,9 +741,9 @@ public final class Fulltext {
String s = new File(path, yacy_dump_prefix + String s = new File(path, yacy_dump_prefix +
"f" + GenericFormatter.FORMAT_SHORT_MINUTE.format(firstdate) + "_" + "f" + GenericFormatter.SHORT_MINUTE_FORMATTER.format(firstdate) + "_" +
"l" + GenericFormatter.FORMAT_SHORT_MINUTE.format(lastdate) + "_" + "l" + GenericFormatter.SHORT_MINUTE_FORMATTER.format(lastdate) + "_" +
"n" + GenericFormatter.FORMAT_SHORT_MINUTE.format(new Date(now)) + "_" + "n" + GenericFormatter.SHORT_MINUTE_FORMATTER.format(new Date(now)) + "_" +
"c" + String.format("%1$012d", doccount)).getAbsolutePath() + "_tc"; // the name ends with the transaction token ('c' = 'created') "c" + String.format("%1$012d", doccount)).getAbsolutePath() + "_tc"; // the name ends with the transaction token ('c' = 'created')
// create export file name // create export file name
@ -767,7 +767,7 @@ public final class Fulltext {
public static void main(String args[]) { public static void main(String args[]) {
Date firstdate = null; Date firstdate = null;
System.out.println(GenericFormatter.FORMAT_SHORT_MINUTE.format(firstdate)); System.out.println(GenericFormatter.SHORT_MINUTE_FORMATTER.format(firstdate));
} }
public Export export() { public Export export() {

@ -0,0 +1,87 @@
// GenericFormatterTest.java
// Copyright 2018 by luccioman; https://github.com/luccioman
//
// This is a part of YaCy, a peer-to-peer based web search engine
//
// LICENSE
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package net.yacy.cora.date;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Date;
import org.junit.Assert;
import org.junit.Test;
/**
* Unit tests for the {@link GenericFormatter} class.
*/
public class GenericFormatterTest {
/**
* Check that the date patterns are properly written : no error should occur
* when using them for formatting and parsing.
*/
@Test
public void testFormats() {
final Instant time = Instant.parse("2018-06-28T10:49:35.726Z");
String formatted = GenericFormatter.FORMAT_SHORT_DAY.format(time);
System.out.println("GenericFormatter.FORMAT_SHORT_DAY : " + formatted);
SimpleDateFormat oldApiFormat = GenericFormatter.newShortDayFormat();
Assert.assertEquals(oldApiFormat.format(Date.from(time)), formatted);
GenericFormatter.FORMAT_SHORT_DAY.parse("20180628");
formatted = GenericFormatter.FORMAT_SHORT_MINUTE.format(time);
System.out.println("GenericFormatter.FORMAT_SHORT_MINUTE : " + formatted);
oldApiFormat = GenericFormatter.newShortMinuteFormat();
Assert.assertEquals(oldApiFormat.format(Date.from(time)), formatted);
GenericFormatter.FORMAT_SHORT_MINUTE.parse("201806281407");
formatted = GenericFormatter.FORMAT_SHORT_SECOND.format(time);
System.out.println("GenericFormatter.FORMAT_SHORT_SECOND : " + formatted);
oldApiFormat = GenericFormatter.newShortSecondFormat();
Assert.assertEquals(oldApiFormat.format(Date.from(time)), formatted);
GenericFormatter.FORMAT_SHORT_SECOND.parse("20180628140713");
formatted = GenericFormatter.FORMAT_SHORT_MILSEC.format(time);
System.out.println("GenericFormatter.FORMAT_SHORT_MILSEC : " + formatted);
oldApiFormat = GenericFormatter.newShortMilsecFormat();
Assert.assertEquals(oldApiFormat.format(Date.from(time)), formatted);
GenericFormatter.FORMAT_SHORT_MILSEC.parse("20180628140713921");
formatted = GenericFormatter.FORMAT_RFC1123_SHORT.format(time);
System.out.println("GenericFormatter.FORMAT_RFC1123_SHORT : " + formatted);
oldApiFormat = GenericFormatter.newRfc1123ShortFormat();
Assert.assertEquals(oldApiFormat.format(Date.from(time)), formatted);
GenericFormatter.FORMAT_RFC1123_SHORT.parse("Thu, 28 Jun 2018");
formatted = GenericFormatter.FORMAT_ANSIC.format(time);
System.out.println("GenericFormatter.FORMAT_ANSIC : " + formatted);
oldApiFormat = GenericFormatter.newAnsicFormat();
Assert.assertEquals(oldApiFormat.format(Date.from(time)), formatted);
GenericFormatter.FORMAT_ANSIC.parse("Thu Jun 28 15:48:17 2018");
formatted = GenericFormatter.FORMAT_SIMPLE.format(time);
System.out.println("GenericFormatter.FORMAT_SIMPLE : " + formatted);
oldApiFormat = GenericFormatter.newSimpleDateFormat();
Assert.assertEquals(oldApiFormat.format(Date.from(time)), formatted);
GenericFormatter.FORMAT_SIMPLE.parse("2018/06/28 15:27:45");
}
}
Loading…
Cancel
Save