I have created a To-Do-List program that records tasks input by a user. For each task, the user must input the name, date, etc.
When the user selects "5" from the menu, the program will sort these tasks by their date. I need to sort all tasks based on the ascending order of the task’s date and time, i.e., the tasks with earlier date and time will be listed before the tasks with later date and time, and display the sorted list.
When I run my code however, I receive these errors:
Exception in thread "main" java.lang.IllegalArgumentException: Cannot format given Object as a Date
at java.base/java.text.DateFormat.format(DateFormat.java:338)
at java.base/java.text.Format.format(Format.java:158)
at ToDoList.lambda$0(ToDoList.java:238)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
at ToDoList.sortTasks(ToDoList.java:238)
at ToDoList.main(ToDoList.java:106)
Here is my code so far: ( The sortTasks() is at the bottom )
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
class Task{
private String theTitle;
private Date theDate;
private String theTime;
private String theLocation;
private String theDuration;
private String theCategory;
SimpleDateFormat format=new SimpleDateFormat("dd/MM/yyyy");
Task(String title, Date date, String time, String location, String duration, String category) {
theTitle = title;
theDate = date;
theTime = time;
theLocation = location;
theDuration = duration;
theCategory = category;
}
public String getTitle() {
return theTitle;
}
public Date getDate() {
return theDate;
}
public String getTime() {
return theTime;
}
public String getLocation() {
return theLocation;
}
public String getDuration() {
return theDuration;
}
public String getCategory() {
return theCategory;
}
public String getItem() {
return theTitle + ", " + format.format(theDate) + ", " + theTime + ", " + theLocation + ", " + theDuration + ", " + theCategory;
}
}
public class ToDoList {
public Task myTaskObj;
SimpleDateFormat format=new SimpleDateFormat("dd/MM/yyyy");
private static List<String> currentList = new ArrayList<String>();
public ToDoList() {
}
public static void main (String[] args) throws ParseException {
ToDoList listObj = new ToDoList();
int menuItem = -1;
while (menuItem != 7) {
menuItem = listObj.printMenu();
switch (menuItem) {
case 1:
listObj.addItem();
break;
case 2:
listObj.removeItem();
break;
case 3:
listObj.removeAllTasks();
break;
case 4:
listObj.showList();
break;
case 5:
listObj.sortTasks();
break;
case 6:
listObj.searchTasks();
break;
case 7:
System.out.println("Goodbye!");
default:
System.out.println("Enter a valid option");
}
}
}
public int printMenu() {
Scanner scanner = new Scanner(System.in);
System.out.println();
System.out.println("----------------------");
System.out.println("Main Menu");
System.out.println("----------------------");
System.out.println("1. Add a task");
System.out.println("2. Delete a task");
System.out.println("3. Delete all tasks");
System.out.println("4. List all tasks");
System.out.println("5. Sort tasks by date");
System.out.println("6. Search for a task");
System.out.println("7. Exit the program");
System.out.println();
System.out.print("Enter choice: ");
int choice = scanner.nextInt();
return choice;
}
public void showList() {
System.out.println();
System.out.println("----------------------");
System.out.println("To-Do List");
System.out.println("----------------------");
int number = 0;
for (String item : currentList) {
System.out.println(++number + ". " + item);
}
System.out.println("----------------------");
}
public void addItem() throws ParseException {
System.out.println("Add a task");
System.out.println("----------------------");
System.out.print("Enter the task title: ");
Scanner scanner = new Scanner(System.in);
String title = scanner.nextLine();
System.out.print("Enter the task date (dd/mm/yyyy): ");
Scanner scanner2 = new Scanner(System.in);
Date date=format.parse(scanner2.next());
System.out.print("Enter the task time: ");
Scanner scanner3 = new Scanner(System.in);
String time = scanner3.nextLine();
System.out.print("Enter the task location: ");
Scanner scanner4 = new Scanner(System.in);
String location = scanner4.nextLine();
System.out.println("Enter the task duration (optional - press enter to skip): ");
Scanner scanner5 = new Scanner(System.in);
String duration = scanner5.nextLine();
System.out.println("Enter the task category (optional - press enter to skip): ");
Scanner scanner6 = new Scanner(System.in);
String category = scanner6.nextLine();
myTaskObj = new Task(title, date, time, location, duration, category);
String theItem = myTaskObj.getItem();
currentList.add(theItem);
System.out.println("Task Added!");
}
public void removeItem() {
System.out.println("Delete a task");
System.out.println("----------------------");
Scanner scanner = new Scanner(System.in);
System.out.print("What do you want to remove? (Enter number): ");
int index = scanner.nextInt();
if((index-1)<0 || index>currentList.size()) {
System.out.println("Wrong index number! Please enter in range of 1 to "+currentList.size());
}else {
currentList.remove(index-1);
}
System.out.println("----------------------");
System.out.println("Task Removed!");
}
public void removeAllTasks() {
System.out.println("Remove all tasks");
System.out.println("----------------------");
showList();
Scanner keyboard = new Scanner(System.in);
System.out.print("Are you sure you'd like to delete all tasks? 'Yes' or 'No': ");
String choice = keyboard.nextLine();
if(choice.equals("Yes")) {
currentList.removeAll(currentList);
System.out.println("All tasks deleted!");
}
else
if(choice.equals("No"))
System.out.println("Tasks not deleted");
}
public void sortTasks() {
System.out.println("Sorted tasks by date (earliest first): ");
Collections.sort(currentList);
currentList.forEach(action-> System.out.println(format.format(action)));
}
}
First, organize your code so it is easier to deal with. Put each class in its own .java file. Use proper indenting to show hierarchy of code. Your IDE can help with that.
And keep in mind separation of concerns. Your ToDoList class should be focused on maintaining valid state regarding a list of Task objects. The ToDoList class should not know anything about interacting with users on a console. For that user interaction, create a separate class.
Looking at the Task class, you should never use the legacy classes java.util.Date, java.sql.Date, and SimpleDateFormat. They were supplanted years ago by the java.time classes with the unanimous adoption of JSR 310. For a moment as seen in UTC, use Instant. For a date-only value without a time-of-day and without a time zone, use LocalDate. For parsing/generating text representing those values, use DateTimeFormatter.
For booking future appointments and such, we must store the date and time-of-day separate from a time zone. Politicians frequently change the offset used by the time zone(s) of their jurisdiction. So 3 PM next January 23rd may not be the same moment then as we would expect now.
So your Task class needs a pair of member fields: LocalDateTime for the date with the time-of-day, plus a ZoneId time zone object. I assume you meant for this to be when the task should start, since you also have an option duration field.
And speaking of duration, Java has a class for that, Duration. It represents a span-of-time unattached to the timeline, on a scale of 24-hour-generic-days, hours, minutes, and fractional seconds.
The formatter should not be hard-coded on your Task class. Instead, the calling method that is using Task objects should pass a Locale object along with a FormatStyle to automatically localize the display of the date-time value. Even better, one could argue that generating formatted date-time strings should not even be the job of the Task class. The task object should just return the projected moment when the task is expected to start, returning a ZonedDateTime object by applying the stored ZoneId object to the stored LocalDateTime object.
Here is the method to applying the ZoneId to LocalDateTime to determine a moment (a point on the timeline) in the form of a ZonedDateTime object.
public ZonedDateTime projectedStartingMoment ( )
{
ZonedDateTime zdt = this.startDateTime.atZone( this.zoneId );
return Objects.requireNonNull( zdt );
}
➥ This on-the-fly generated ZonedDateTime object is also what we need to sort theses tasks, the original purpose of your question. To sort the Task objects, we will implement the Comparable interface, which requires we write a compareTo method. In our compareTo, we generate the ZonedDateTime object, and compare that for sorting. Different tasks could have different time zones, so we cannot just compare the stored LocalDateTime objects.
// Implement `Comparable` interface.
#Override
public int compare ( Task task1 , Task task2 )
{
return task1.projectedStartingMoment().compareTo( task2.projectedStartingMoment() );
}
Here is a table to help you keep straight these various date-time types.
We ignore the location and category fields, as they are not germane to the question of sorting by date.
We need to be able to differentiate one Task definitively from another. In real work, we would likely have a primary key used by a database to track each task record. Often such a primary key is either an serial integer number or a UUID. Here we use a UUID. We override Object::equals and Object::hashCode to use this UUID identifier value. These methods may be used by our SortedSet collection of Task objects.
No need to prefix your member fields with the.
So that Task class looks something like this. By the way, the new Records feature coming in Java 15 could be used for this class, but we'll not do that here as Java 15 is not yet released.
package work.basil.example;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Comparator;
import java.util.Objects;
import java.util.UUID;
public class Task
implements Comparable < Task >
{
// Member fields.
private UUID id;
private String title;
private LocalDateTime startDateTime;
private ZoneId zoneId;
private Duration duration;
// Constructor
public Task ( UUID id , String title , LocalDateTime startDateTime , ZoneId zoneId , Duration duration )
{
this.id = id;
this.title = title;
this.startDateTime = startDateTime;
this.zoneId = zoneId;
this.duration = duration;
}
// Logic
public ZonedDateTime projectedStartingMoment ( )
{
ZonedDateTime zdt = this.startDateTime.atZone( this.zoneId );
return Objects.requireNonNull( zdt );
}
public ZonedDateTime projectedEndingMoment ( )
{
ZonedDateTime zdt = this.startDateTime.atZone( this.zoneId ).plus( this.duration ); // Half-Open approach, for spans-of-time that neatly abut one another without gaps.
return Objects.requireNonNull( zdt );
}
// Accessors
// Getters only, immutable object.
public UUID getId ( ) { return this.id; }
public String getTitle ( ) { return this.title; }
public LocalDateTime getStartDateTime ( ) { return this.startDateTime; }
public ZoneId getZoneId ( ) { return this.zoneId; }
public Duration getDuration ( ) { return this.duration; }
// Object overrides.
#Override
public String toString ( )
{
return "Task{ " +
"id=" + id +
" | title='" + title + '\'' +
" | startDateTime=" + startDateTime +
" | zoneId=" + zoneId +
" | duration=" + duration +
" | projectedStartingMoment=" + projectedStartingMoment() +
" }";
}
#Override
public boolean equals ( Object o )
{
if ( this == o ) return true;
if ( o == null || getClass() != o.getClass() ) return false;
Task task = ( Task ) o;
return getId().equals( task.getId() );
}
#Override
public int hashCode ( )
{
return Objects.hash( getId() );
}
#Override
public int compareTo ( Task other )
{
return this.projectedStartingMoment().compareTo( other.projectedStartingMoment() );
}
}
Next, we collect objects of that Task class into a ToDoList. The ToDoList class in your Question is mixing concerns, dealing with user interactions, and dealing with presentation. Both of those belong in your app class. Think of it this way, if you were to later add a GUI to your app in addition to the console user-interface, your ToDoList::showList would be a misfit, irrelevant. That tells us the "showList" work does not belong in the ToDoList class.
At this point our ToDoList class could simply be a List or Set, with no need for us to define our own class. But in real work, this class would likely have additional duties. So we will proceed in creating that class.
package work.basil.example;
import java.util.*;
public class ToDoList
{
private SortedSet < Task > tasks;
// Constructors
public ToDoList ( )
{
this.tasks = new TreeSet <>();
}
public ToDoList ( Collection < Task > tasks )
{
this(); // Call other constructor
this.tasks.addAll( tasks );
}
// Logic
public boolean addTask ( Task task )
{
Objects.requireNonNull( task ); // Fail fast. In real work, pass a message for the exception.
boolean result = this.tasks.add( task );
return result;
}
public boolean addTasks ( Collection tasks )
{
return this.tasks.addAll( Objects.requireNonNull( tasks ) );
}
public boolean remove ( Task task )
{
return this.tasks.remove( Objects.requireNonNull( task ) );
}
public void removeAll ( )
{
this.tasks.clear();
}
public List < Task > getTasksSortedByProjectedStartingMoment ( )
{
// Make a copy of our `SortedSet`, to be separate from our own here.
// This way the calling method can do what they want, as can this class,
// while not stepping on each other's feet.
Objects.requireNonNull( this.tasks ); // Paranoid check.
return List.copyOf( this.tasks );
}
}
Let's harness those classes to see them in action. I will use the beginnings of what can be your new app (main) class for interacting with user on console. But I will not do all the user-interaction code, as that is not germane to the Question. Here I just instantiate a few Task objects, put them in a ToDoList, and get back a list sorted by date.
To finally answer your Question, we call our ToDoList::getTasksSortedByProjectedStartingMoment method.
List < Task > tasksSorted = this.toDoList.getTasksSortedByProjectedStartingMoment();
Full example code.
package work.basil.example;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.ZoneId;
import java.util.List;
import java.util.UUID;
public class ToDoListEditorConsole
{
private ToDoList toDoList;
public static void main ( String[] args )
{
ToDoListEditorConsole app = new ToDoListEditorConsole();
app.launch();
}
private void launch ( )
{
this.toDoList = new ToDoList();
this.demo();
}
private void demo ( )
{
// Make a few `Task` objects. All on the same day in the same zone, but different time-of-day.
// Notice that our time-of-day values are *not* in chronological order.
List < Task > tasks = List.of(
new Task(
UUID.fromString( "98399344-bb31-11ea-b3de-0242ac130004" ) ,
"Eat apple" ,
LocalDateTime.of( 2021 , Month.JANUARY , 23 , 12 , 30 , 0 , 0 ) ,
ZoneId.of( "Africa/Tunis" ) , Duration.ofHours( 1 )
) ,
new Task(
UUID.fromString( "1e4ded04-bb32-11ea-b3de-0242ac130004" ) ,
"Eat banana" ,
LocalDateTime.of( 2021 , Month.JANUARY , 23 , 20 , 00 , 0 , 0 ) ,
ZoneId.of( "Africa/Tunis" ) , Duration.ofHours( 1 )
) ,
new Task(
UUID.fromString( "010fcde8-bb32-11ea-b3de-0242ac130004" ) ,
"Eat corn" ,
LocalDateTime.of( 2021 , Month.JANUARY , 23 , 15 , 00 , 0 , 0 ) ,
ZoneId.of( "Africa/Tunis" ) , Duration.ofMinutes( 30 )
)
);
this.toDoList.addTasks( tasks );
List < Task > tasksSorted = this.toDoList.getTasksSortedByProjectedStartingMoment();
System.out.println( "Result:" );
System.out.println( tasksSorted );
System.out.println( "« fin »" );
}
}
When run, notice how the banana and corn tasks (2nd & 3rd) switched places, now sorted chronologically.
Result:
[Task{ id=98399344-bb31-11ea-b3de-0242ac130004 | title='Eat apple' | startDateTime=2021-01-23T12:30 | zoneId=Africa/Tunis | duration=PT1H | projectedStartingMoment=2021-01-23T12:30+01:00[Africa/Tunis] }, Task{ id=010fcde8-bb32-11ea-b3de-0242ac130004 | title='Eat corn' | startDateTime=2021-01-23T15:00 | zoneId=Africa/Tunis | duration=PT30M | projectedStartingMoment=2021-01-23T15:00+01:00[Africa/Tunis] }, Task{ id=1e4ded04-bb32-11ea-b3de-0242ac130004 | title='Eat banana' | startDateTime=2021-01-23T20:00 | zoneId=Africa/Tunis | duration=PT1H | projectedStartingMoment=2021-01-23T20:00+01:00[Africa/Tunis] }]
« fin »
I suggest the main problem you're facing here is that you are storing the tasks as strings rather than as Task objects. If you stored them correctly then a lot of the actions would be significantly easier.
So, change your list to:
class ToDoList {
private final List<Task> tasks = new ArrayList<>();
...
}
Then sorting becomes pretty trivial. For example it could look like:
public void sortTasks(Comparator<Task> order) {
tasks.sort(order);
}
And the code reacting to the user might look like:
case 5: toDoList.sortTasks(Comparator.comparing(Task::getDate).thenComparing(Task::getTime));
Which would make it trivial to add options to sort by other criteria such as sortTasks(Comparator.comparing(Task::getTitle)).
This should also make output easier to read as it could be embedded in the task's toString method.
Getting the model right is fundamental to writing cleaner code.
I would like to calculate the number of the overlapping days between two date ranges. The 2 pairs of date ranges are read from the console in the format: yyyy-mm-dd;
For example, if the two dates are
2020-01-05
2020-03-31
and
2020-01-05
2020-03-20
the program should find the days between 2020-01-05 and 2020-03-20. However, it doesn't work. I would like to ask how can I fix this?
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.Scanner;
public class Dates {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String a = sc.nextLine();
String b = sc.nextLine();
String c = sc.nextLine();
String d = sc.nextLine();
LocalDate ldt1 = LocalDate.parse(a);
LocalDate ldt2 = LocalDate.parse(b);
LocalDate ldt3 = LocalDate.parse(c);
LocalDate ldt4 = LocalDate.parse(d);
System.out.println(ChronoUnit.DAYS.between(ldt1,ldt2,ldt3,ldt4));
}
}
It’s a little more complicated than that (not badly).
String i1StartStr = "2020-01-05";
String i1EndStr = "2020-03-31";
String i2StartStr = "2020-01-05";
String i2EndStr = "2020-03-20";
LocalDate i1Start = LocalDate.parse(i1StartStr);
LocalDate i1End = LocalDate.parse(i1EndStr);
LocalDate i2Start = LocalDate.parse(i2StartStr);
LocalDate i2End = LocalDate.parse(i2EndStr);
if (i1End.isBefore(i1Start) || i2End.isBefore(i2Start)) {
System.out.println("Not proper intervals");
} else {
long numberOfOverlappingDates;
if (i1End.isBefore(i2Start) || i2End.isBefore(i1Start)) {
// no overlap
numberOfOverlappingDates = 0;
} else {
LocalDate laterStart = Collections.max(Arrays.asList(i1Start, i2Start));
LocalDate earlierEnd = Collections.min(Arrays.asList(i1End, i2End));
numberOfOverlappingDates = ChronoUnit.DAYS.between(laterStart, earlierEnd);
}
System.out.println("" + numberOfOverlappingDates + " days of overlap");
}
Output from the code as it stands here is:
75 days of overlap
I have also used better variable names and have introduced validation of the intervals that the user inputs.
I know I was supposed to add some explanation here, but frankly, I find the code using java.time so clear to read in itself, I don’t know what needs to be explained. If you want the number of days inclusive of both start and end dates, remember to add 1 to the return value from ChronoUnit.DAYS.between(). Please follow up in the comments and let me know what further explanation will be appropriate.
I am trying to find and update the values of a string. The string is a recurrence rule sent by a client which I need to get the until property and add time to it. (Ps the setTime/setHour... is depricated)
What i have is:
import java.util.Calendar;
public class TestReplaceString {
/**
* #param args
*/
public static void main(final String[] args) {
String originalRecurrenceRule = "FREQ=WEEKLY;INTERVAL=1;BYDAY=FR;WKST=MO;UNTIL=20160731T223030Z;";
System.out.println("Unformated: " + originalRecurrenceRule);
System.out.println("Formated: " + originalRecurrenceRule.replaceAll("UNTIL.*?Z", "UNTIL=" + "sameDateAsPassedByTheCLient" + "T235959Z;"));
}
The problem with this is that I need to keep the date supplied by the client and only add time eg
The date 20170101T220000Z would become 20170101T235959Z
What i was trying to accomplish is something like a validator to get the property UNTIL from the string and change it.
Any suggestions are welcome.
Kind Regards
Something like this might be a bit more suitable:
String originalRecurrenceRule = "FREQ=WEEKLY;INTERVAL=1;BYDAY=FR;WKST=MO;UNTIL=20160731T223030Z;";
String until = originalRecurrenceRule.substring(originalRecurrenceRule.indexOf("UNTIL"), originalRecurrenceRule.indexOf(";", originalRecurrenceRule.indexOf("UNTIL")));
SimpleDateFormat sdf = new SimpleDateFormat();
Date date = sdf.parse(until.substring(until.indexOf("=") + 1),until.length() - 1);
date.setTime(timeInMilliseconds);
originalRecurrenceRule.replace(until, "UNTIL="+ date.toString() + ";");
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.
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.