Felipe Godoy
Felipe Godoy

Reputation: 57

ArrayList<Date> how to count the number of occurrences by Month/Year

Im having some trouble to get out of this problem by my own. Basically I`m working whit a ArrayList with 4.611 registers of Dates. Those Dates are between the year of 2018 and 2020, and their format is "yyyy-MM-dd HH:SS"

Here is my code where I could pass the String data into Date. So for so good.

SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm");
                ArrayList<Date> dataUser = new ArrayList<Date>();

                User user;
                for (int i = 0; i < listaUser.size(); i++) {
                    user = (User) listaUser.get(i);
                    if (user != null) {
                        Date dataLead = formatter.parse(user.getCREATED_AT());
                        dataUser.add(dataLead);
                    }
                
                }

My problem is that I need to know how many dates ares included in every Year and every Month. For example; 2020-01-01 2020-01-03 2020-01-05 2020-04-01 2020-04-02 2020=04-29 2018-01-01 2018-01-04

The return must be: 3 for 2020-01 (January/2020) 3 for 2020-04 (April/2020) 2 for 2018-01 (January/2018)

Is there any way to do this? I`ve tried all kind of for loops but no success...

Upvotes: 1

Views: 969

Answers (4)

Anonymous
Anonymous

Reputation: 86324

java.time and a stream operation

Using java.time, the modern Java date and time API, and a stream with the right combination of collectors, this doesn’t take a lot:

    List<LocalDate> dataUser = List.of(
            LocalDate.of(2018, Month.JANUARY, 3),
            LocalDate.of(2020, Month.NOVEMBER, 26),
            LocalDate.of(2018, Month.JANUARY, 18),
            LocalDate.of(2019, Month.SEPTEMBER, 12));
    
    Map<YearMonth, Long> countsPerMonth = dataUser.stream()
            .collect(Collectors.groupingBy(YearMonth::from, Collectors.counting()));
    
    countsPerMonth.forEach((ym, n) -> System.out.println("" + ym + ": " + n + " occurrense/s"));

Output from this example is:

2018-01: 2 occurrense/s
2019-09: 1 occurrense/s
2020-11: 1 occurrense/s

Link

Oracle tutorial: Date Time explaining how to use java.time.

Upvotes: 1

Basil Bourque
Basil Bourque

Reputation: 339362

java.time

You are using terrible date-time classes that were supplanted years ago by the java.time classes. Never use java.util.Date.

LocalDateTime

Use LocalDateTime class to represent your input values, a date with a time-of-day but without a time zone.

ArrayList< LocalDateTime > dataUser = …

Your input strings are close to the standard ISO 8601 formats used by default in java.time. Just replace the SPACE in the middle with a T to comply with the standard.

LocalDateTime ldt = LocalDateTime.parse( input.replace( " " , "T" ) ;

Example code:

List < String > inputs = List.of( "2020-01-01 13:00:00" , "2020-01-03 11:00:00" , "2020-01-05 17:00:00" , "2020-04-01 01:00:00" ,
        "2020-04-02 19:00:00" , "2020-04-29 23:00:00" , "2018-01-01 14:00:00" , "2018-01-04 05:00:00" );
System.out.println( "inputs = " + inputs );

List < LocalDateTime > ldts = new ArrayList <>( inputs.size() );
for ( String input : inputs )
{
    try
    {
        LocalDateTime ldt = LocalDateTime.parse( input.replace( " " , "T" ) );
        Objects.requireNonNull( ldt );
        ldts.add( ldt );
    }
    catch ( Exception e )
    {
        // Faulty input.
        e.printStackTrace();
    }
}
System.out.println( "ldts = " + ldts );

When run.

inputs = [2020-01-01 13:00:00, 2020-01-03 11:00:00, 2020-01-05 17:00:00, 2020-04-01 01:00:00, 2020-04-02 19:00:00, 2020-04-29 23:00:00, 2018-01-01 14:00:00, 2018-01-04 05:00:00]
ldts = [2020-01-01T13:00, 2020-01-03T11:00, 2020-01-05T17:00, 2020-04-01T01:00, 2020-04-02T19:00, 2020-04-29T23:00, 2018-01-01T14:00, 2018-01-04T05:00]

YearMonth

From those LocalDate objects, get a YearMonth object that represents, well, the year and the month only, without the day-of-month.

YearMonth yearMonth = YearMonth.from( ldt ) ;

Group by year-month

Create a Map< YearMonth , List< LocalDate > > to segregate the date objects by year-month. Then interrogate the list for its size to get your desired count.

Map < YearMonth, List < LocalDateTime > > mapYearMonthToLdts = new TreeMap <>();

Loop through all your LocalDateTime, determine each one’s year-month. Look in the map to see if there is a list already assigned for that year-month. If not yet assigned a list, make a list, and assign it to the map. Lastly, add the LocalDateTime object to that list whether pre-existing or newly created.

for ( LocalDateTime ldt : ldts )
{
    List < LocalDateTime > listOfLdts = mapYearMonthToLdts.get( YearMonth.from( ldt ) );
    if ( listOfLdts == null )
    {
        listOfLdts = new ArrayList <>();
        mapYearMonthToLdts.put( YearMonth.from( ldt ) , listOfLdts );
    }
    listOfLdts.add( ldt );
}
System.out.println( "mapYearMonthToLdts = " + mapYearMonthToLdts );

When run.

mapYearMonthToLdts = {2018-01=[2018-01-01T14:00, 2018-01-04T05:00], 2020-01=[2020-01-01T13:00, 2020-01-03T11:00, 2020-01-05T17:00], 2020-04=[2020-04-01T01:00, 2020-04-02T19:00, 2020-04-29T23:00]}

Multimap

The map-related code above is old-school. Modern Java makes this much simpler.

Our map is actually a multimap, where the key leads to a collection of values rather than a single value. Modern Java provides multimap syntax via Map::computeIfAbsent method added to Java 8 and later.

for ( LocalDateTime ldt : ldts )
{
    mapYearMonthToLdts
            .computeIfAbsent( YearMonth.from( ldt ) , ( x -> new ArrayList <>() ) )  // Retrieves the value (a list) matching the key, after creating one if none found to be pre-existing.
            .add( ldt );  // Add this value to the retrieved/created list.
}
System.out.println( "mapYearMonthToLdts = " + mapYearMonthToLdts );

Output remains the same. This new-school code is not better, just shorter, and easier to comprehend when reviewing code later. If you are not yet comfortable with lambda syntax, use the old-school code seen earlier.

Report results

You wanted a count of elements per year-month.

We can build a report by looping the keys, looping the YearMonth objects. For each year-month key, we retrieve its List of LocalDateTime. We interrogate that list for its size.

for ( YearMonth yearMonth : mapYearMonthToLdts.keySet() )
{
    System.out.println( "yearMonth = " + yearMonth + " has a count of: " + mapYearMonthToLdts.get( yearMonth ).size() );
}

When run.

yearMonth = 2018-01 has a count of: 2
yearMonth = 2020-01 has a count of: 3
yearMonth = 2020-04 has a count of: 3

About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes. Hibernate 5 & JPA 2.2 support java.time.

Where to obtain the java.time classes?

Upvotes: 3

Gurpreet Singh Sran
Gurpreet Singh Sran

Reputation: 31

Add new method which will find occurrence of year + month like following

private void getCountOfDate(String date,Map<String,Integer> countMap){
        String dateStr = date.substring(0,date.lastIndexOf("-"));
        if(countMap.containsKey(dateStr)){
            countMap.put(dateStr, countMap.get(dateStr)+1);
        }else{
            countMap.put(dateStr, 1);
        }
     
     }

Call this method from within your code

            User user;
            Map<String,Integer> user.getCREATED_AT()= new HashMap<>();
            for (int i = 0; i < listaUser.size(); i++) {
                user = (User) listaUser.get(i);
                if (user != null) {
                    Date dataLead = formatter.parse(user.getCREATED_AT());
                    dataUser.add(dataLead);
                    getCountOfDate(user.getCREATED_AT(),user.getCREATED_AT())
                }
            
            }

Now iterate the map for keys and get your values.

Upvotes: 1

rzwitserloot
rzwitserloot

Reputation: 103244

Those Dates are between the year of 2018 and 2020, and their format is "yyyy-MM-dd HH:SS"

Nope. Not possible.

Date is lying to you. A bald-faced lie at that. a Date does not represent a date. Weird, huh?

It represents a moment in time. Like, when the sun had that flare, that kind of 'moment in time'.

The problem is, when that sun had the big flareup? It was october in russia but november in most of the rest of the world. It was the 30th in america and the 31st in europe. Etcetera.

It is not possible to turn a j.u.Date into a month and day-of-month. Not without providing a timezone.

The proper solution

Don't use Date; it is an obsolete type, and most of its methods (such as .getYear() are deprecated, with a warning they they are broken. The proper types to use for date and time stuff are all in the java.time package. This package also contains ways to parse things; you should parse your stuff in yyyy-MM-dd HH:mm format into a java.time.LocalDateTime object, which doesn't lie - that really does represent a date and a time, and a 'local' one (so no timezone information provided at all), which is exactly what you have.

LDTs have getYear() and `getMonth() methods which return sane values (vs. Calendar which has those, but they return insane values, so you don't want java.util.Calendar either. That is also obsolete API).

Once you have a List<LocalDateTime> this becomes easy, and it won't get messed up due to timezone settings, as this code is timezone free:

var pattern = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm", Locale.ENGLISH);

Map<String, List<LocalDateTime>> result =
  listaUser.stream()
  .map(a -> (User) a)
  .map(User::getCREATED_AT)
  .map(a -> LocalDateTime.parse(a, pattern))
  .collect(Collectors.groupingBy(
    a -> String.format("%4d-%2d", a.getYear(), a.getMonth())));

now your result map contains, for example, as key "2020-01" and the associated value is a list with all of the dates in your input with that year/month combo. If all you want is how many there are, .size() on that will tell you.

Upvotes: 3

Related Questions