I want to show to a colleague that SimpleDateFormat is not thread-safe through a simple JUnit test. The following class fails to make my point (reusing SimpleDateFormat in a multi-threaded environment) and I don't understand why. Can you spot what is preventing my use of SDF from throwing a runtime exception?
public class SimpleDateFormatThreadTest
{
#Test
public void test_SimpleDateFormat_MultiThreaded() throws ParseException{
Date aDate = (new SimpleDateFormat("dd/MM/yyyy").parse("31/12/1999"));
DataFormatter callable = new DataFormatter(aDate);
ExecutorService executor = Executors.newFixedThreadPool(1000);
Collection<DataFormatter> callables = Collections.nCopies(1000, callable);
try{
List<Future<String>> futures = executor.invokeAll(callables);
for (Future f : futures){
try{
assertEquals("31/12/1999", (String) f.get());
}
catch (ExecutionException e){
e.printStackTrace();
}
}
}
catch (InterruptedException e){
e.printStackTrace();
}
}
}
class DataFormatter implements Callable<String>{
static SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
Date date;
DataFormatter(Date date){
this.date = date;
}
#Override
public String call() throws RuntimeException{
try{
return sdf.format(date);
}
catch (RuntimeException e){
e.printStackTrace();
return "EXCEPTION";
}
}
}
Lack of thread safety doesn't necessarily mean that the code will throw an exception. This was explained in Andy Grove's article, SimpleDateFormat and Thread Safety, which is no longer available online. In it, he showed SimpleDateFormat's lack of thread safety by showing that the output would not always be correct, given different inputs.
When I run this code, I get the following output:
java.lang.RuntimeException: date conversion failed after 3 iterations.
Expected 14-Feb-2001 but got 01-Dec-2007
Note that "01-Dec-2007" isn't even one of the strings in the test data. It is actually a combination of the dates being processed by the other two threads!
While the original article is no longer available online, the following code illustrates the issue. It was created based on articles that appeared to have been based on Andy Grove's initial article.
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
public class SimpleDateFormatThreadSafety {
private final SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
public static void main(String[] args) {
new SimpleDateFormatThreadSafety().dateTest(List.of("01-Jan-1999", "14-Feb-2001", "31-Dec-2007"));
}
public void dateTest(List<String> testData) {
testData.stream()
.map(d -> new Thread(() -> repeatedlyParseAndFormat(d)))
.forEach(Thread::start);
}
private void repeatedlyParseAndFormat(String value) {
for (int i = 0; i < 1000; i++) {
Date d = tryParse(value);
String formatted = dateFormat.format(d);
if (!value.equals(formatted)) {
throw new RuntimeException("date conversion failed after " + i
+ " iterations. Expected " + value + " but got " + formatted);
}
}
}
private Date tryParse(String value) {
try {
return dateFormat.parse(value);
} catch (ParseException e) {
throw new RuntimeException("parse failed");
}
}
}
Sometimes this conversion fails by returning the wrong date, and sometimes it fails with a NumberFormatException:
java.lang.NumberFormatException: For input string: ".E2.31E2"
Isn't this part from javadoc of SimpleDateFormatter has sufficent proof about it?
Synchronization
Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.
And the major observation of not being thread safe is to get unexpected results and not an exception.
It is not thread safe because of this code in SimpleDateFormat (in sun JVM 1.7.0_02):
private StringBuffer format(Date date, StringBuffer toAppendTo,
FieldDelegate delegate) {
// Convert input date to time field list
calendar.setTime(date);
....
}
Each call to format stores the date in a calendar member variable of the SimpleDateFormat, and then subsequently applies the formatting to the contents of the calendar variable (not the date parameter).
So, as each call to format happens the data for all currently running formats may change (depending on the coherence model of your architecture) the data in the calendar member variable that is used by every other thread.
So if you run multiple concurrent calls to format you may not get an exception, but each call may return a result derived from the date of one of the other calls to format - or a hybrid combination of data from many different calls to format.
Related
I have a homework and here is my task:
Create an object class which has:
a private instance fields of the LocalDate data type called deadline
a constructor which instantiates the field
a get method
a set method
The deadline cannot be on Saturday or Sunday - if it happens, the (IllegalArgumentException) exception with a message about inappropriate date (in the format dd.mm.yyyy) is thrown out. Demonstrate the method using appropriate and inappropriate deadlines. Hint: use the getDayOfWeek method to show the day of the week.
I have a problem a really do not understand how to use getDayOfWeek method properly and of course I tried do program from this side but my output is: Deadline#2d554825
I already tried use this method but I really do not understand which datatype it needs to return
public DayOfWeek getDayOfWeek() {
// what should I return?
}
Here is my code:
import java.time.DayOfWeek;
import java.time.LocalDate;
public class Deadline {
private LocalDate deadline;
public Deadline(LocalDate DeadLine) {
this.deadline = DeadLine;
}
public LocalDate getDeadline() {
return deadline;
}
public void setDeadline(LocalDate deadline) {
this.deadline = deadline;
}
public static void main(String[] args){
Deadline first = new Deadline(LocalDate.parse("2017-02-03"));
System.out.println(first);
}
}
First of all, if you want to know name of day of week, do it like this:
LocalDate a = LocalDate.parse("2017-02-03");
System.out.println(a.getDayOfWeek().name());
This way you can compare given day of week with String such as "SATURDAY" or "SUNDAY".
Second, if you want to do
Deadline first = new Deadline(LocalDate.parse("2017-02-03"));
System.out.println(first);
You need to Override #ToString in your Deadline class. For example:
class Deadline {
...
#Override
public String toString() {
return this.deadline.toString();
}
}
What you need is a proper toString() method and a localized name of the day of week.
You can achieve that by using the method getDisplayName(TextStyle, Locale), which I will show in the code below.
There is another thing that comes up reading your assignment task:
The deadline cannot be on Saturday or Sunday - if it happens, the (IllegalArgumentException) exception with a message about inappropriate date (in the format dd.mm.yyyy) is thrown out.
==> There is not IllegalArgumentException being thrown, there isn't even any check for invalid weekdays in your code. This Exception will not magically appear, you have to implement it.
Here is some example solution:
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.TextStyle;
import java.util.Locale;
public class Deadline {
private LocalDate deadline;
public Deadline(LocalDate deadLine) {
// check if the given date is a Saturday or Sunday and throw the desired Exception
if (deadLine.getDayOfWeek() == DayOfWeek.SATURDAY
|| deadLine.getDayOfWeek() == DayOfWeek.SUNDAY) {
throw new IllegalArgumentException("The deadline to be set is not valid ("
+ deadLine.getDayOfWeek()
.getDisplayName(TextStyle.FULL_STANDALONE, Locale.getDefault())
+ ", "
+ deadLine.format(DateTimeFormatter.ofPattern("dd.MM.yyyy"))
+ "). Please provide a deadline which is a weekday.");
} else {
// otherwise just set the deadline
this.deadline = deadLine;
}
}
public LocalDate getDeadline() {
return deadline;
}
public void setDeadline(LocalDate deadline) {
if (deadline.getDayOfWeek() == DayOfWeek.SATURDAY
|| deadline.getDayOfWeek() == DayOfWeek.SUNDAY) {
throw new IllegalArgumentException("The deadline to be set is not valid ("
+ deadline.getDayOfWeek()
.getDisplayName(TextStyle.FULL_STANDALONE, Locale.getDefault())
+ ", "
+ deadline.format(DateTimeFormatter.ofPattern("dd.MM.yyyy"))
+ "). Please provide a deadline which is a weekday.");
} else {
this.deadline = deadline;
}
}
#Override
public String toString() {
return deadline.getDayOfWeek()
.getDisplayName(TextStyle.FULL_STANDALONE, Locale.getDefault())
+ ", "
+ deadline.format(DateTimeFormatter.ISO_LOCAL_DATE);
}
public static void main(String[] args) {
// this is now a Saturday, which will throw the IllegalArgumentException
Deadline first = new Deadline(LocalDate.parse("2017-02-04"));
System.out.println(first.toString());
}
}
Please note that you don't necessarily have to use the localized display name of the enum DayOfWeek, but it may be useful to do so. You can also just call setDeadline(deadline) in your parametrized constructor instead of writing the same error handling there, but if you decide not to do, you will have to keep the redundant code.
I'm ingesting a stream of data into Flink. For each 'instance' of this data, I have a timestamp. I can detect if the machine I'm getting the data from is 'producing' or 'not producing', this is done via a custom flat map function that's located in it's own static class.
I want to calculate how long the machine has been producing / not producing.
My current approach is collecting the production and non production timestamps in two plain lists. For each 'instance' of the data, I calculate the current production/non-production duration by subtracting the latest timestamp from the earliest timestamp. This is giving me incorrect results, though. When the production state changes from producing to non producing, I clear the timestamp list for producing and vice versa, so that if the production starts again, the duration starts from zero.
I've looked into the two lists I collect the respective timestamps in and I see things I don't understand. My assumption is that, as long as the machine 'produces', the first timestamp in the production timestamp list stays the same, while new timestamps are added to the list per new instance of data.
Apparantly, this assumption is wrong since I get seemingly random timestamps in the lists. They are still correctly ordered, though.
Here's my code for the flatmap function:
public static class ImaginePaperDataConverterRich extends RichFlatMapFunction<ImaginePaperData, String> {
private static final long serialVersionUID = 4736981447434827392L;
private transient ValueState<ProductionState> stateOfProduction;
SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss.SS");
DateFormat timeDiffFormat = new SimpleDateFormat("dd HH:mm:ss.SS");
String timeDiffString = "00 00:00:00.000";
List<String> productionTimestamps = new ArrayList<>();
List<String> nonProductionTimestamps = new ArrayList<>();
public String calcProductionTime(List<String> timestamps) {
if (!timestamps.isEmpty()) {
try {
Date firstDate = dateFormat.parse(timestamps.get(0));
Date lastDate = dateFormat.parse(timestamps.get(timestamps.size()-1));
long timeDiff = lastDate.getTime() - firstDate.getTime();
if (timeDiff < 0) {
System.out.println("Something weird happened. Maybe EOF.");
return timeDiffString;
}
timeDiffString = String.format("%02d %02d:%02d:%02d.%02d",
TimeUnit.MILLISECONDS.toDays(timeDiff),
TimeUnit.MILLISECONDS.toHours(timeDiff) % TimeUnit.HOURS.toHours(1),
TimeUnit.MILLISECONDS.toMinutes(timeDiff) % TimeUnit.HOURS.toMinutes(1),
TimeUnit.MILLISECONDS.toSeconds(timeDiff) % TimeUnit.MINUTES.toSeconds(1),
TimeUnit.MILLISECONDS.toMillis(timeDiff) % TimeUnit.SECONDS.toMillis(1));
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println("State duration: " + timeDiffString);
}
return timeDiffString;
}
#Override
public void open(Configuration config) {
ValueStateDescriptor<ProductionState> descriptor = new ValueStateDescriptor<>(
"stateOfProduction",
TypeInformation.of(new TypeHint<ProductionState>() {}),
ProductionState.NOT_PRODUCING);
stateOfProduction = getRuntimeContext().getState(descriptor);
}
#Override
public void flatMap(ImaginePaperData ImaginePaperData, Collector<String> output) throws Exception {
List<String> warnings = new ArrayList<>();
JSONObject jObject = new JSONObject();
String productionTime = "0";
String nonProductionTime = "0";
// Data analysis
if (stateOfProduction == null || stateOfProduction.value() == ProductionState.NOT_PRODUCING && ImaginePaperData.actSpeedCl > 60.0) {
stateOfProduction.update(ProductionState.PRODUCING);
} else if (stateOfProduction.value() == ProductionState.PRODUCING && ImaginePaperData.actSpeedCl < 60.0) {
stateOfProduction.update(ProductionState.NOT_PRODUCING);
}
if(stateOfProduction.value() == ProductionState.PRODUCING) {
if (!nonProductionTimestamps.isEmpty()) {
System.out.println("Production has started again, non production timestamps cleared");
nonProductionTimestamps.clear();
}
productionTimestamps.add(ImaginePaperData.timestamp);
System.out.println(productionTimestamps);
productionTime = calcProductionTime(productionTimestamps);
} else {
if(!productionTimestamps.isEmpty()) {
System.out.println("Production has stopped, production timestamps cleared");
productionTimestamps.clear();
}
nonProductionTimestamps.add(ImaginePaperData.timestamp);
warnings.add("Production has stopped.");
System.out.println(nonProductionTimestamps);
//System.out.println("Production stopped");
nonProductionTime = calcProductionTime(nonProductionTimestamps);
}
// The rest is just JSON stuff
Do I maybe have to hold these two timestamp lists in a ListState?
EDIT: Because another user asked, here is the data I'm getting.
{'szenario': 'machine01', 'timestamp': '31.10.2018 09:18:39.432069', 'data': {1: 100.0, 2: 100.0, 101: 94.0, 102: 120.0, 103: 65.0}}
The behaviour I expect is that my flink program collects the timestamps in the two lists productionTimestamps and nonProductionTimestamps. Then I want my calcProductionTime method to subtract the last timestamp in the list from the first timestamp, to get the duration between when I first detected the machine is "producing" / "not-producing" and the time it stopped "producing" / "not-producing".
I found out that the reason for the 'seemingly random' timestamps is Apache Flink's parallel execution. When the parallelism is set to > 1, the order of events isn't guaranteed anymore.
My quick fix was to set the parallelism of my program to 1, this guarantees the order of events, as far as I know.
This question already has answers here:
SimpleDateFormat.parse() ignores the number of characters in pattern
(5 answers)
Closed 7 years ago.
I am working on a project where I need to validate multiple dates based on length and patterns. I am using simple date format and found many issues with that. My requirement is to strictly allow if date string matches "yyyy/MM/dd" and strictly 10 characters.
The below code is not giving expected results for various testing input strings.
public static boolean checkformat(String dateString){
boolean flag = false;
Date d1 = null;
SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd");
format.setLenient(false);
try {
d1 = format.parse(dateString);
flag=true;
} catch (ParseException ex) {
ex.printStackTrace();
return false;
}
return flag;
}
the above code is returning "true" for various inputs like "99/03/1" (should be 0099/03/01) and 99/1/1( should be 0099/01/1). Since the input strings are not coming from a from so I cant perform validations before passing them to this method. Please suggest any implementation which should act very strict towards the dateformat("yyyy/MM/dd").
I suggest that you should try to validate date with regex before format it.
user below code for validate
public static boolean checkformat(String dateString){
boolean flag = false;
Date d1 = null;
SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd");
format.setLenient(false);
try {
if (dateString.matches("([0-9]{4})/([0-9]{2})/([0-9]{2})")) { // use this regex
d1 = format.parse(dateString);
flag=true;
}
} catch (ParseException ex) {
ex.printStackTrace();
return false;
}
return flag;
}
Okay, first: You know what format you're expection. So why just parse it and catch an exception rather than checking preconditions ?
if(dateString.size() > 10) {
...
What you are actually doing is not checking your input format but rather parsing it - though the method is not expressing this contract -
so if your method is just for checking you could:
1. Use a regex
2. ... ?
I know that are quiet a lot of answers on the net which propose using SimpleDateFormat, but - to be frank -they are wrong.
If I am expecting a given format, e.g. as I know that conversions have been made on some user input, I can start parsing a string, and considering that something may have gone wrong, catch the exception. If I don't know which format is passed to me, I am at the validation layer and this layer should not try to perform a conversion but rather proof that the conversion would be valid.
You could try using the new java.time package from Java 8 and later. You could use it as so to replace the SimpleDateFormat:
public static boolean checkformat(String dateString){
boolean flag = false;
try {
TemporalAccessor ta = DateTimeFormatter.ofPattern("yyyyMMdd").parse(strDate);
flag=true;
} catch (DateTimeParseException ex) {
ex.printStackTrace();
return false;
}
return flag;
}
This would also limit the values from making no sense (e.g. month value being 18).
String[] removeSlashes=new String[3];
removeSlashes = enteredDate.split("/");
if(removeSlashes[0].length()!=4)
throw new IncorrectDateFormatException(); // user defined exception
if(removeSlashes[1].length()!=2)
throw new IncorrectDateFormatException();
if(removeSlashes[2].length()!=2)
throw new IncorrectDateFormatException();
//Then use SimpleDateFormat to verify
I am parsing JSON from server in my Android application by using Jackson JSON library. However, parsing requests fail whenever I receive DateTime since it's in this format:
"/Date(1277931782420)/"
I know I should do something like:
ObjectMapper om = new ObjectMapper();
om.setDateFormat(new TicksSinceFormat());
But I have no idea if I can use SimpleDateFormat at all (and what format string would I use?) or I need to write my own DateFormat parser. So, I would seriously appreciate if somebody could help with code example.
EDIT:
OK, see my answer for complete code.
This proved to be tougher then I expected:
public class TicksSinceFormat extends DateFormat {
#Override
public StringBuffer format(Date date, StringBuffer buffer, FieldPosition field) {
long millis = date.getTime();
return new StringBuffer("/Date(" + millis + ")/");
}
#Override
public Date parse(String string, ParsePosition position) {
int start = string.indexOf("(") + 1;
int end = string.indexOf(")");
String ms = string.substring(start, end);
Date date = new Date(Long.parseLong(ms));
position.setIndex(string.length() - 1); // MUST SET THIS
return date;
}
#Override
public Object clone() {
return new TicksSinceFormat(); // MUST SET THIS
}
}
Using class is then extremely simple, just do:
ObjectMapper om = new ObjectMapper();
om.setDateFormat(new TicksSinceFormat())
I presume that this can be coded better + that I'll need to deal with differences when it comes to .NET Ticks VS Java ticks - but for now this'll do. If somebody has better solution or more insight into mentioned problems I'll deal with later - feel free to post and I'll mark your answer as correct one if it's better.
EDIT: As I've explained in this question & answer I've switched to ServiceStack.Text library on the server and it returns different, ISO8601 format. For that format I'm using slightly different parsing (since Jackson has trouble parsing ISO8601 that contains milliseconds). Of course, as with other code I'm posting - let me know if you have better version (just please post code / edit this post, rather than resorting to philosophical rhetoric on how it should be done):
#SuppressLint("SimpleDateFormat")
public class JacksonSimpleDateFormat extends SimpleDateFormat {
public JacksonSimpleDateFormat() {
if (mParser == null) {
mParser = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
mParser.setTimeZone(TimeZone.getTimeZone("UTC"));
}
}
#Override
public StringBuffer format(Date date, StringBuffer buffer, FieldPosition field) {
return mParser.format(date, buffer, field);
}
private static SimpleDateFormat mParser;
#Override
public Date parse(String string, ParsePosition position) {
String str = string.split("\\.")[0];
Date date = null;
try {
date = mParser.parse(str);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
position.setIndex(string.length() - 1);
return date;
}
#Override
public Object clone() {
return new JacksonSimpleDateFormat();
}
}
I may be wrong on this, as I haven't gotten very far into Android development, but the format you presented:
"/Date(1277931782420)/"
Appears to be Unix epoch time.
If that is the case, you would not want/need to use SimpleDateFormat. Instead, try creating a Long from it and passing to the Date constructor, accounting for whether it is seconds or milliseconds-based epoch value.
Here is a StackOverflow post that provides the code for doing so: https://stackoverflow.com/a/535017/463196
When I run the following code I would expect a stacktrace, but instead it looks like it ignores the faulty part of my value, why does this happen?
package test;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test {
public static void main(final String[] args) {
final String format = "dd-MM-yyyy";
final String value = "07-02-201f";
Date date = null;
final SimpleDateFormat df = new SimpleDateFormat(format);
try {
df.setLenient(false);
date = df.parse(value.toString());
} catch (final ParseException e) {
e.printStackTrace();
}
System.out.println(df.format(date));
}
}
The output is:
07-02-0201
The documentation of DateFormat.parse (which is inherited by SimpleDateFormat) says:
The method may not use the entire text of the given string.
final String value = "07-02-201f";
In your case (201f) it was able to parse the valid string till 201, that's why its not giving you any errors.
The "Throws" section of the same method has defined as below:
ParseException - if the beginning of the specified string cannot be parsed
So if you try changing your string to
final String value = "07-02-f201";
you will get the parse exception, since the beginning of the specified string cannot be parsed.
You can look whether the entire string was parsed as follows.
ParsePosition position = new ParsePosition(0);
date = df.parse(value, position);
if (position.getIndex() != value.length()) {
throw new ParseException("Remainder not parsed: "
+ value.substring(position.getIndex()));
}
Furthermore when an exception was thrown by parse the position will also yield getErrorIndex().
Confirmed... I also found that "07-02-201", "07-02-2012 is the date" compiles. However, "bar07-02-2011" does not.
From the code in SimpleDateFormat, it seems like the parsing terminates the moment an illegal character is found that breaks the matching. However, if the String that has already been parsed up to that point is valid, it is accepted.