java create time range attribute for data class

Viewed 134

I'm new to java and currently working on a small exercise. No external libraries. I have a csv file consisting of data about covid cases. Now I need to create a class for this data. Each data object has two attributes: area and timerange. Area can be either continent or country. Time range is a pair of start date and end date. After entering the area and time range, it will display the rows fitting those entries. For example Vietnam and 6/1/2021 - 6/30/2021 will have the following results: 1

This is the code to read csv lines:

File inputFile = new File("covid-data.csv");
        Scanner sc = new Scanner(inputFile);
        String str;
        String[] tokens;
        while (sc.hasNext()) {
            str = sc.nextLine();
            tokens = str.split(",");
            Data d = new Data(tokens[2], tokens[3]);
        }

And this is my data class:

public class Data {
    String area;
    String time_range;

    public Data(String a, String time) {
        area = a;
        time_range = time;
    }

I don't know how to deal with the time range so I can only assign it to 'time' column for now. External libraries can't be used here. Do I need to create a hashmap with separate start date and end date? Also do I need to convert to date format or keep it as string? Thank you!!

2 Answers

No external libraries. I have a csv file consisting of data about covid cases.

Oof. Bad start. Reading CSVs is more complicated than you think and a very fine place for a dependency (for example, multi-line CSVs, which your code doesn't support. Your code will randomly start failing if any non-ASCII characters show up. Your code doesn't deal with quoted values. Your code doesn't let you easily marshall - all things CSV libraries do). Modern development is inherently bundled up with dependencies. Your aversion to them is something you need to fix.

Given a string containing a time range, you need to:

  • Extract the start and end time, for example using .split.
  • Convert the 'start' and 'end' string into a java.time.LocalDate instance, using LocalDate.parse and passing in the right format; it's probably just ISO format, there's a pre-fab DateTimeFormatter instance for it.
  • Then a simple for loop does the job:
LocalDate start = ...., end = ....;
for (LocalDate current = start; !current.isAfter(end); current = current.plusDays(1)) {
    // this code is called once for each date, and `current` holds that date.
}

Firstly, both points in Answer by rzwitserloot are correct and wise:

  • Forbidding the use of external libraries means missing out on the best reason to use Java: its vast and rich ecosystem of supporting libraries.
  • Use the java.time classes such as LocalDate for your work. Never use the legacy date-time class such as Date or Calendar.

Specifically, I suggest adding the ThreeTen-Extra library to make use of its LocalDateRange class to contain a pair of LocalDate objects. That class offers handy methods such as abuts, overlaps, contains, and so on.

I suggest you do all your date-handling by defining a span-of-time using the Half-Open approach where the beginning is inclusive while the ending is exclusive. So the month begins on the first and runs up to, but does not include, the first of the following months. June would be June 1 to July 1. The LocalDateRange uses Half-Open by default, but can also accommodate Fully-Closed (beginning and ending both inclusive) if you insist.

In Java 16 and later, use the records feature to define a class to hold your data transparently and immutably. You merely declare the type and name of each member field. The compiler implicitly creates the default constructor, getters, equals & hashCode, and toString. Bonus feature: a record can be defined locally in addition to nested or separately (ditto enum and interface).

So your entire class is one short line:

record CovidDaily ( String isoCode , String continent , String location, LocalDate date , … ) {}

To filter your rows, loop or use streams.

LocalDate start = LocalDate.of( 2021 , Month.JUNE , 1 ) ;
LocalDate end = LocalDate.of( 2021 , Month.JULY , 1 ) ;
List< CovidDaily > hits = new ArrayList<>() ;
List< CovidDaily > dailies = … ;
for( CovidDaily covidDaily: dailies ) 
{
    LocalDate ld = covidDaily.date() ;
    if( ( ! ld.isBefore( start ) ) && ( ld.isBefore( end ) ) 
    { hits.add( covidDaily) ; } // Else ignore this record and move on to next.
}

Generally best to return an unmodifiable result.

return List.copyOf( hits ) ;

Streaming approach is shorter.

LocalDate start = LocalDate.of( 2021 , Month.JUNE , 1 ) ;
LocalDate end = LocalDate.of( 2021 , Month.JULY , 1 ) ;
List< CovidDaily > dailies = … ;
return dailies.stream().filter( covidDaily -> ( ! covidDaily.date().isBefore( start ) ) && ( covidDaily.date().isBefore( end ) ) ).toList();

Using LocalDateRange.

LocalDateRange range = LocalDateRange.of( 
    LocalDate.of( 2021 , Month.JUNE , 1 ) ,
    LocalDate.of( 2021 , Month.JULY , 1 ) ) ;
List< CovidDaily > dailies = … ;
return dailies.stream().filter( covidDaily -> range.contains( covidDaily.date() ) ).toList();
Related