2

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)));
}

}
bean
  • 329
  • 1
  • 5
  • 13
  • `currentList` is a list of strings (`List`). In your last line of code, you're passing these strings to the `format` method of `SimpleDateFormat`. That does not work - the `format` method can only format `Date` objects, not strings. – Jesper Jun 30 '20 at 21:10
  • The problem is, both Strings and Date objects make up a single element in the arrayList – bean Jun 30 '20 at 21:14
  • 1
    Instead of `SimpleDateFormat`, use [`LocalDate`](https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html) from the Java 8 time API. Use the `isAfter(ChronoLocalDate other)` or the `isBefore(ChronoLocalDate other)` to sort your Task type objects. – Soutzikevich Jun 30 '20 at 21:16
  • I'm very new to java, would you be able to show me? @Soutzikevich – bean Jun 30 '20 at 21:18
  • 1
    Change the last line `action-> System.out.println(format.format(action))` to `System.out::println`. –  Jun 30 '20 at 21:56
  • 1
    I recommend you don’t use `SimpleDateFormat` and `Date`. Those classes are poorly designed and long outdated, the former in particular notoriously troublesome. Instead use `LocalDate` and `DateTimeFormatter`, both from [java.time, the modern Java date and time API](https://docs.oracle.com/javase/tutorial/datetime/). – Ole V.V. Jul 01 '20 at 02:55
  • Does this answer your question? [Java : Cannot format given Object as a Date](https://stackoverflow.com/questions/10649782/java-cannot-format-given-object-as-a-date). Please search for more. – Ole V.V. Jul 01 '20 at 02:57

2 Answers2

2

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.

Table of all date-time types in Java, both modern and legacy

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 »

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
0

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.

sprinter
  • 27,148
  • 6
  • 47
  • 78