Reputation: 57
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
Reputation: 86324
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
Oracle tutorial: Date Time explaining how to use java.time.
Upvotes: 1
Reputation: 339362
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 ) ;
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]}
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.
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
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
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
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.
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