0

I'm trying to develop an app, which takes dates as an input from the user (obtained in with a datepicker, one by one), saves these as strings in a file and calculates the difference between these dates. There won't be more than 200 dates. The already stored values won't be edited again but I want to read them and use them in a function.

So what's the best way for appending data multiple times to a file and then reading the inputs one by one? I'm sorry if that's a dumb question, I'm new to android development.

I tried to use FileOutputStream() to write the data into a txt-file in internal storage but I didn't find a way to work with the data like I described above.

  • What is the scale of data, the count of pairs of dates? Ten? Ten million? – Basil Bourque Aug 05 '23 at 15:27
  • How often are you editing the already stored values? How do you know which pair of dates to edit? – Basil Bourque Aug 05 '23 at 15:28
  • @BasilBourque I was vague with my question, sorry. There won't be more than 200 dates. The already stored values won't be edited again but I want to read them and use them in a function. – Chig Bungus Aug 05 '23 at 15:57
  • Instead of storing them in a file maybe you can try storing them in database. You can use the Room library. This will make it very easy to store and read them. https://developer.android.com/training/data-storage/room – Pawan Singh Harariya Aug 05 '23 at 17:59
  • @PawanSinghHarariya Thank you! I started looking into this topic and a database really seems more fitting for my app. – Chig Bungus Aug 05 '23 at 18:47
  • 1
    @ChigBungus, in _Java_ most of the _IO_ functionality is bare-bones; there isn't necessarily a class used _just_ for the storage and retrieval of dates. I recommend reviewing both the _[FileReader](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/io/FileReader.html)_ and _[FileWriter](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/io/FileWriter.html)_ classes. Additionally, here is the _Java_ tutorial on file _IO_, _[Lesson: Basic I/O (The Java™ Tutorials > Essential Java Classes)](https://docs.oracle.com/javase/tutorial/essential/io/)_. – Reilas Aug 05 '23 at 19:49
  • Post additional details as edits to your Question, not Comments. Readers should not have to trawl through Comments to understand your question. – Basil Bourque Aug 05 '23 at 20:13
  • Will you be accessing the stored dates from across threads, or only from a single thread? – Basil Bourque Aug 05 '23 at 20:16

2 Answers2

1

I do not know about Android specifically, but I can give you some guidance on the Java platform. This may apply to Android as well. You will likely need to adjust the file paths for Android specifics.

tl;dr

Use java.nio.file.Files class to write to local file storage using org.threeten.extra.LocalDateRange from ThreeTen-Extra library.

Files.write ( 
    Paths.get ( "/Users/some_user/DateRangesExample.txt" ) , 
    List
        .of (
            LocalDateRange.of ( LocalDate.now ( ) , LocalDate.now ( ).plusDays ( 2 ) ) ,
            LocalDateRange.of ( LocalDate.of ( 2024 , Month.JANUARY , 23 ) , LocalDate.of ( 2024 , Month.JANUARY , 27 ) )
        )
        .stream ( )
        .map ( LocalDateRange :: toString )
        .toList ( )
);

Read.

List < LocalDateRange > localDateRanges =
    Files
        .readAllLines ( 
            Paths.get ( "/Users/some_user/DateRangesExample.txt" ) 
        )
        .stream ( )
        .map ( LocalDateRange :: parse )
        .toList ( )
;

Get days elapsed with LocalDateTime#lengthInDays.

File storage

You can write a file to storage to contain your 200 pairs of dates.

The code discussed here assumes you will strictly access the file only from a single thread.

Since Java 7, file storage is managed through the NIO.2 framework. See the free-of-cost tutorial by Oracle.

Let's write a Java record to represent your pair of dates.

package work.basil.example.storage;

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

public record DatePair( LocalDate start ,
                        LocalDate end ,
                        long days )
{
    public DatePair
    {
        if ( days != ChronoUnit.DAYS.between ( start , end ) )
        {
            throw new IllegalArgumentException ( "The days argument must be an accurate count of days elapsed between the two dates. Count must be Half-Open, where the beginning is *inclusive* while the ending is *exclusive*." );
        }
    }

    public static DatePair of ( LocalDate start , LocalDate end )
    {
        return new DatePair ( start , end , ChronoUnit.DAYS.between ( start , end ) );
    }
}

Records were added to Java 16. For earlier Java, use a conventional class as seen at bottom of this Answer.

We can use Files.write and Files.read to easily write and read your pairs of data in a textual data file. The .write method by default will (a) create the file if it does not yet exist, and (b) remove all data from an existing file.

Here is a demo app that starts with two pairs of dates, writes those, reads them back, adds a third pair, then does another write and read.

This code replaces all the data in the file every time it writes. You could append instead of replacing, but with so little data it hardly matters.

package work.basil.example.storage;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.Month;
import java.util.ArrayList;
import java.util.List;

public class App
{
    public static void main ( String[] args )
    {
        App app = new App ( );
        app.demo ( );
    }

    @SuppressWarnings ( "branch identical" )
    private void demo ( )
    {
        // Input data.
        List < DatePair > pairsOfDates_1 =
                List.of (
                        DatePair.of ( LocalDate.now ( ) , LocalDate.now ( ).plusDays ( 2 ) ) ,
                        DatePair.of ( LocalDate.of ( 2024 , Month.JANUARY , 23 ) , LocalDate.of ( 2024 , Month.JANUARY , 27 ) )
                );
        System.out.println ( "pairsOfDates_1 = " + pairsOfDates_1 );

        // Write.
        Path path = Paths.get ( "/Users/some_user/PairsOfDatesExample.txt" );
        this.writePairsOfDates ( pairsOfDates_1 , path );

        // Read.
        List < DatePair > pairsOfDates_2 = this.readPairsOfDates ( path );
        System.out.println ( "pairsOfDates_2 = " + pairsOfDates_2 );

        // Add data.
        List < DatePair > pairsOfDates_3 = new ArrayList <> ( pairsOfDates_2 );
        pairsOfDates_3.add (
                DatePair.of (
                        LocalDate.of ( 2030 , Month.FEBRUARY , 2 ) ,
                        LocalDate.of ( 2030 , Month.MARCH , 1 )
                )
        );
        System.out.println ( "pairsOfDates_3 = " + pairsOfDates_3 );

        // Write data again, replacing existing data file.
        this.writePairsOfDates ( pairsOfDates_3 , path );

        // Read new file.
        List < DatePair > pairsOfDates_4 = this.readPairsOfDates ( path );
        System.out.println ( "pairsOfDates_4 = " + pairsOfDates_4 );
    }

    private void writePairsOfDates ( List < DatePair > dates , Path path )
    {
        List < String > lines =
                dates
                        .stream ( )
                        .map ( datePair -> String.join ( "/" , datePair.start ( ).toString ( ) , datePair.end ( ).toString ( ) ) )
                        .toList ( );
        System.out.println ( "lines = " + lines );
        try
        {
            Files.write ( path , lines );
        }
        catch ( IOException e )
        {
            throw new RuntimeException ( e );
        }

    }

    private List < DatePair > readPairsOfDates ( final Path path )
    {
        try
        {
            List < String > lines = Files.readAllLines ( path );
            List < DatePair > datePairs =
                    lines
                            .stream ( )
                            .map ( ( String line ) ->
                            {
                                String[] parts = line.split ( "/" );
                                return DatePair.of ( LocalDate.parse ( parts[ 0 ] ) , LocalDate.parse ( parts[ 1 ] ) );
                            } )
                            .toList ( );
            return datePairs;
        }
        catch ( IOException e )
        {
            throw new RuntimeException ( e );
        }
    }
}

LocalDateRange

We need not write our own class for the pair of dates. Add the ThreeTen-Extra to access its LocalDateRange class.

That class simplifies our code.

package work.basil.example.storage;

import org.threeten.extra.LocalDateRange;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.Month;
import java.util.ArrayList;
import java.util.List;

public class App2
{
    public static void main ( String[] args )
    {
        App2 app = new App2 ( );
        app.demo ( );
    }

    @SuppressWarnings ( "branch identical" )
    private void demo ( )
    {
        // Input data.
        List < LocalDateRange > range_1 =
                List.of (
                        LocalDateRange.of ( LocalDate.now ( ) , LocalDate.now ( ).plusDays ( 2 ) ) ,
                        LocalDateRange.of ( LocalDate.of ( 2024 , Month.JANUARY , 23 ) , LocalDate.of ( 2024 , Month.JANUARY , 27 ) )
                );
        System.out.println ( "range_1 = " + range_1 );

        // Write.
        Path path = Paths.get ( "/Users/some_user/PairsOfDatesExample.txt" );
        this.write ( range_1 , path );

        // Read.
        List < LocalDateRange > range_2 = this.read ( path );
        System.out.println ( "range_2 = " + range_2 );

        // Add data.
        List < LocalDateRange > range_3 = new ArrayList <> ( range_2 );
        range_3.add (
                LocalDateRange.of (
                        LocalDate.of ( 2030 , Month.FEBRUARY , 2 ) ,
                        LocalDate.of ( 2030 , Month.MARCH , 1 )
                )
        );
        System.out.println ( "range_3 = " + range_3 );

        // Write data again, replacing existing data file.
        this.write ( range_3 , path );

        // Read new file.
        List < LocalDateRange > range_4 = this.read ( path );
        System.out.println ( "range_4 = " + range_4 );
    }

    private void write ( List < LocalDateRange > dates , Path path )
    {
        List < String > lines =
                dates
                        .stream ( )
                        .map ( LocalDateRange :: toString )
                        .toList ( );
        System.out.println ( "lines = " + lines );
        try
        {
            Files.write ( path , lines );
        }
        catch ( IOException e )
        {
            throw new RuntimeException ( e );
        }
    }

    private List < LocalDateRange > read ( final Path path )
    {
        try
        {
            List < String > lines = Files.readAllLines ( path );
            List < LocalDateRange > localDateRanges =
                    lines
                            .stream ( )
                            .map ( LocalDateRange :: parse )
                            .toList ( );
            return localDateRanges;
        }
        catch ( IOException e )
        {
            throw new RuntimeException ( e );
        }
    }
}

When run:

range_1 = [2023-08-05/2023-08-07, 2024-01-23/2024-01-27]
lines = [2023-08-05/2023-08-07, 2024-01-23/2024-01-27]
range_2 = [2023-08-05/2023-08-07, 2024-01-23/2024-01-27]
range_3 = [2023-08-05/2023-08-07, 2024-01-23/2024-01-27, 2030-02-02/2030-03-01]
lines = [2023-08-05/2023-08-07, 2024-01-23/2024-01-27, 2030-02-02/2030-03-01]
range_4 = [2023-08-05/2023-08-07, 2024-01-23/2024-01-27, 2030-02-02/2030-03-01]

When you need the number of days elapsed, call LocalDateTime#lengthInDays.

range_4.forEach ( localDateRange -> System.out.println ( localDateRange.toString ( ) + " = " + localDateRange.lengthInDays ( ) ) );
2023-08-05/2023-08-07 = 2
2024-01-23/2024-01-27 = 4
2030-02-02/2030-03-01 = 27

Note that the formats used by LocalDate & LocalDateRange when parsing/generating text are standard ISO 8601 formats.

Database

You could use a database for your purpose, but that would be overkill if you read/write the data file in a single thread. If you need to manage data across threads, then use a database engine such as H2.


Converting Java record to conventional class.

package work.basil.example.storage;

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.Objects;

public final class DatePair
{
    private final LocalDate start;
    private final LocalDate end;
    private final long days;

    public DatePair ( LocalDate start , LocalDate end , long days )
    {
        if ( days != ChronoUnit.DAYS.between ( start , end ) )
        {
            throw new IllegalArgumentException ( "The days argument must be an accurate count of days elapsed between the two dates. Count must be Half-Open, where the beginning is *inclusive* while the ending is *exclusive*." );
        }
        this.start = start;
        this.end = end;
        this.days = days;
    }

    public static DatePair of ( LocalDate start , LocalDate end )
    {
        return new DatePair ( start , end , ChronoUnit.DAYS.between ( start , end ) );
    }

    public LocalDate start ( )
    {
        return start;
    }

    public LocalDate end ( )
    {
        return end;
    }

    public long days ( )
    {
        return days;
    }

    @Override
    public boolean equals ( Object obj )
    {
        if ( obj == this ) return true;
        if ( obj == null || obj.getClass ( ) != this.getClass ( ) ) return false;
        var that = ( DatePair ) obj;
        return Objects.equals ( this.start , that.start ) &&
                Objects.equals ( this.end , that.end ) &&
                this.days == that.days;
    }

    @Override
    public int hashCode ( )
    {
        return Objects.hash ( start , end , days );
    }

    @Override
    public String toString ( )
    {
        return "DatePair[" +
                "start=" + start + ", " +
                "end=" + end + ", " +
                "days=" + days + ']';
    }

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

"... So what's the best way for appending data multiple times to a file and then reading the inputs one by one? ..."

There is not a specific class within the Java framework that does this exact procedure, although it is relatively simple to implement.

"... I tried to use FileOutputStream() to write the data into a txt-file in internal storage but I didn't find a way to work with the data like I described above."

I recommend reviewing the FileReader and FileWriter classes.
Additionally, here is the Java tutorial on file IO, Lesson: Basic I/O (The Java™ Tutorials > Essential Java Classes)

You can create a custom class to validate your data.

Here is a basic example of reading the values one by one.
I'm utilizing the BufferedReader class here, which has a readLine method.
The list object is a class field.

List<String> list;

void read() throws IOException {
    try (BufferedReader reader = new BufferedReader(new FileReader("files/example.txt"))) {
        list = new ArrayList<>();
        String line;
        while ((line = reader.readLine()) != null) list.add(line);
    }
}

As a recommendation, since you are using date values, you can utilize the epoch-timestamp.
Storing date values in this time-stamp is a common approach.

Here is a relevant article on time measurement in computer systems.
Wikipedia – Epoch (computing).

You may also find "separated-values" as a valid storage solution for your data.
This is where you store the data with a delimiter, usually a comma or a tab; realistically it could be any character, or sequence of characters.

Reilas
  • 3,297
  • 2
  • 4
  • 17