I have two tables Previous_Schedule and New_Schedule. Both the tables have 3 columns :
Objective_ID , START_DATE and END_DATE.
I need to create a NOT_PRESENT_IN_PREVIOUS_SCHEDULE Table having 3 columns :
Objective_ID , START_DATE and END_DATE.
If Previous Schedule is having sample data as:
Objective_id --Start_Date -- End_Date
1 -- 10-Jan-2014 -- 20-Jan-2014
If New_Schedule is having sample data as:
Objective_id -- Start_Date -- End_Date
1 -- 12-Jan-2014 -- 15-Jan-2014
My NOT_PRESENT_IN_PREVIOUS_SCHEDULE should have the following data based on the above scenario:
Objective_id -- Start_Date --End_Date
1 -- 10-Jan-2014 -- 11-Jan-2014
1 -- 16-Jan-2014 -- 20-Jan-2014
The logic having the NOT_PRESENT_IN_PREVIOUS_SCHEDULE output should be implemented in Java. It should be generic for any sort of PREVIOUS_SCHEDULE and NEW_SCHEDULE as an Input returning NOT_PRESENT_IN_PREVIOUS_SCHEDULE as an output.
Here’s my suggestion. The following method “subtracts” two lists of schedules by eliminating the date intervals from the first list that are in the second list. It uses a double loop where it first iterates over the schedules in the second list, those that should be subtracted. For each such schedule it subtracts it from each schedule from the first list, building a new list of the resulting schedules.
public static List<Schedule> scheduleListDiff(
List<Schedule> schedules, List<Schedule> schedulesToExclude) {
// eliminate dates from schedulesToExclude one schdule at a time
for (Schedule toExclude : schedulesToExclude) {
List<Schedule> result = new ArrayList<>();
for (Schedule originalSchedule : schedules) {
result.addAll(originalSchedule.notPresentIn(toExclude));
}
schedules = result;
}
return schedules;
}
You may call it this way
List<Schedule> notPresentInPreviousSchedule
= scheduleListDiff(previousSchedules, newSchedules);
With the lists from your question the result is the desired
1 -- 10-Jan-2014 -- 11-Jan-2014
1 -- 16-Jan-2014 -- 20-Jan-2014
I have fitted the Schedule class with an auxiliary method notPresentIn() to perform the actual comparison:
/** #return a list of 0, 1 or 2 schedules with the dates from this schedule that are not in other */
List<Schedule> notPresentIn(Schedule other) {
if (other.end.isBefore(start) || end.isBefore(other.start)) { // no overlap
return Collections.singletonList(this);
}
// now we know there is an overlap
List<Schedule> result = new ArrayList<>(2);
if (start.isBefore(other.start)) { // need to include day/s from the first part of this
// this bit must end the day before other.start
result.add(new Schedule(objectiveId, start, other.start.minusDays(1)));
}
if (end.isAfter(other.end)) { // need day/s from the last part
result.add(new Schedule(objectiveId, other.end.plusDays(1), end));
}
return result;
}
I have not tested thoroughly, there could easily be a bug somewhere, but I hope this gets you started.
I have not considered efficiency. If you have millions of schedules you may benefit from a more complicated algorithm that sorts the schedules chronologically first so you don’t need to compare every schedule from one list to every schedule of the other. With a few hundred schedules I heavily doubt that you need care.
I am using java.time.LocalDate for the dates in the Schedule class:
int objectiveId;
// dates are inclusive; end is on or after start
LocalDate start;
LocalDate end;
Edit: I ran my code on the sample data from the duplicate question find out cancelled period from given date. That sample has two new sample schedules within one previous schedule. So this previous schedule should be split up into three. The result was:
107 -- 10 May 2016 -- 11 May 2016
107 -- 14 May 2016 -- 15 May 2016
107 -- 19 May 2016 -- 20 May 2016
This works because each iteration in scheduleListDiff() uses the result from the previous iteration, so first the schedule is split into two, next iteration one of the two is further split.
To convert the date strings into Java date objects, you could use the SimpleDateFormat class. Solution for Java 7:
String string = "20-Jan-2014";
DateFormat format = new SimpleDateFormat("dd-MMM-yyyy", Locale.ENGLISH);
Date date = format.parse(string);
For Java 8 you could find a solution here.
To calculate the difference between two dates, you could use this operation:
long diff = date2.getTime() - date1.getTime();
System.out.println ("Days: " + TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS));
Calculate the difference between Previous_Schedule and New_Schedule
An idea on how to solve this problem could be to transform the date period in Previous_Schedule into single dates, stored in a Set.
Set<String> dates = new HashSet<String>();
dates.add( "10-Jan-2014" );
dates.add( "11-Jan-2014" );
dates.add( "12-Jan-2014" );
...
dates.add( "20-Jan-2014" );
Then you remove the dates from the period in New_Schedule from the Set:
dates.remove( "12-Jan-2014" );
...
dates.remove( "15-Jan-2014" );
The remaining elements in the Set would provide the basis to create NOT_PRESENT_IN_PREVIOUS_SCHEDULE.
Instead of using Strings, you could add date objects to the Set as well:
Set<Date> dates = new HashSet<Date>();
dates.add( date1 );
How to split a date period like 10-Jan-2014 -- 20-Jan-2014 into single dates and how to do the reverse task to create NOT_PRESENT_IN_PREVIOUS_SCHEDULE should come from your own creativity. Hint: you might use a loop to solve that task.
Related
I am using Time4j to parse recurring intervals like this:
IsoRecurrence.parseTimestampIntervals("R/2019-01-01T00:00:00/P1D")
This will give me an iterator with infinite number of daily recurring instances starting from the beginning of 2019.
Is it possible to only iterate the instances between a start date and end date, let's say e.g. for June, without changing the original rule?
Basically I would like to be able to define schedules with the ISO 8601 recurrence format but only need to generate instances for a given period.
Yes, it is possible but you have to introduce your own customizable condition to stop the infinite loop or stream. Example:
#Test
public void parseInfiniteTimestampIntervals() throws ParseException {
IsoRecurrence<TimestampInterval> intervals =
IsoRecurrence.parseTimestampIntervals("R/2019-01-01T00:00:00/P1D");
PlainDate start = PlainDate.of(2019, 6, 11);
PlainDate end = PlainDate.of(2019, 6, 15);
for (TimestampInterval interval : intervals) {
PlainDate current = interval.getStartAsTimestamp().getCalendarDate();
if (current.isAfterOrEqual(start)) {
if (current.isBeforeOrEqual(end)) {
System.out.println(interval); // or do your own stuff with the current interval
} else {
break; // end of infinite loop
}
}
}
}
Output:
[2019-06-11T00/2019-06-12T00)
[2019-06-12T00/2019-06-13T00)
[2019-06-13T00/2019-06-14T00)
[2019-06-14T00/2019-06-15T00)
[2019-06-15T00/2019-06-16T00)
However, infinite iterating requires special care how to model the stop condition and only exist in the class IsoRecurrence because the ISO-8601-standard has explicitly allowed this option. I hope that your ISO-expression (which is to be parsed) is not too wide in range because excessive iterating over many intervals should be avoided for sake of performance.
In case you only have daily intervals when the time of day is irrelevant, I recommend to use the type DateInterval.
First, sorry for my English who may be poor, I hope you will understand me
I do not see how to recover my object count per hour.
I hope you can help me find out more about my question.
I have a mission object that contains a mission list that each have as attribute
a STRING name and a STRING time (hhmmss format)
here is an example :
0 : name1 102101
1 : name2 102801
2 : name3 104801
3 : name4 110501
4 : name5 120301
I wish I could make an array allowing me to count the number of missions for each hour
In this example I would have :
10 => 3
11 => 1
12 => 1
I do not know if you see what I would like to get :)
If you ever have small tracks I'm interested
Thank you for reading me !
I wish you a good evening
TL;DR
As the comments mentioned, you may want to use a HashMap with String keys reflecting the hour and Integer values for the count (missions per hour).
Since you're dealing with hours, meaning that you have a maximum of 24 of them, you can also replace the HashMap with an Array of 24 items.
The Mission class
Basically, all is needed here is a getter for the time attribute. If you feel fancy, you can also add a getHour which will return the hour instead of the whole time string.
class Mission {
private String name;
private String time;
Mission(String name, String time) {
this.name = name;
this.time = time;
}
String getHour() {
// This gives us the 2 first characters into a String - aka the "hour"
return time.substring(0, 2);
}
}
Using the HashMap
We want to keep the count per hour in a HashMap. So we'll iterate over the missionsList and for each item, we'll get its count, then we'll increment it.
If the hour is not in the HashMap yet, we would normally receive a null. To handle that with minimal boilerplate, we'll use the getOrDefault method. We can call it like this map.getOrDefault("10", 0). This will return the missions count of hour 10, and if that count doesn't exist yet (which means we didn't add it to the map yet) we will receive 0 instead of null. The code will look like this
public static void main(String[] args) {
// This will built our list of missions
List<Mission> missionsList = Arrays.asList(
new Mission("name1", "102101"),
new Mission("name2", "102801"),
new Mission("name3", "104801"),
new Mission("name4", "110501"),
new Mission("name5", "120301")
);
// This map will keep the count of missions (value) per hour (key)
Map<String, Integer> missionsPerHour = new HashMap<>();
for (Mission mission : missionsList) {
// Let's start by getting the hour,
// this will act as the key of our map entry
String hour = mission.getHour();
// Here we get the count of the current hour (so far).
// This is the "value" of our map entry
int count = missionsPerHour.getOrDefault(mission.getHour(), 0);
// Here we increment it (by adding/replacing the entry in the map)
missionsPerHour.put(hour, count + 1);
}
// Once we have the count per hour,
// we iterate over all the keys in the map (which are the hours).
// Then we simply print the count per hour
for (String hour : missionsPerHour.keySet()) {
System.out.println(String.format(
"%s\t=>\t%d", hour, missionsPerHour.get(hour)
));
}
}
We are starting with a List of timestamps, which are Date objects. We need to group all timestamps which belong to a unique day. For example, when a user logs into our server, a timestamp is added to a single List. We want to be able to parse this list and separate all the Date objects which belong to the same day. The end goal is to be able to easily show all logins separated by day on a UI, as well as to show the number of logins which occurred per each day.
The end HashMap construct should look like this:
Key List<Date>
2018-07-11
2018-07-11 08:14:08.540000
2018-07-11 10:46:23.575000
2018-07-12
2018-07-12 12:51:48.928000
2018-07-12 13:09:00.701000
2018-07-12 16:04:45.890000
2018-07-13
2018-07-13 14:14:17.461000
Here's the java8 way of doing it.
List<LocalDateTime> loginTimes = Arrays.asList(LocalDateTime.of(2018, 5, 7, 8, 10),
LocalDateTime.of(2018, 5, 7, 9, 15, 20), LocalDateTime.of(2018, 6, 22, 7, 40, 30));
Map<LocalDate, Long> loginCountByDate = loginTimes.stream()
.collect(Collectors.groupingBy(LocalDateTime::toLocalDate, Collectors.counting()));
First group the login times by date and then count the number of logins by each date. This is the best solution I can suggest, but it mandates you to use Java8.
Here's the output.
{2018-05-07=2, 2018-06-22=1}
This is the solution we came up with. The parseTimeStamps method takes the list of timeStamps iterates over it, and uses a Calendar object to set the hours, minutes, seconds, and milliseconds to 0, thereby giving us just the day. We then check our groupedUserLogins HashMap to see if it contains a key with that date. If it doesn't, we create a key with that day, and a new list of Date objects as the value associated with that key. Subsequently, we add the timestamp(ts) to the list associated with that day.
In the next iteration, if we come across a key in our HashMap which matches our stripped Calender object, we immediately add that timestamp (ts) to the list associated with that existing day.
I created the following method, which returns a HashMap> where the key is a Date object and the value is a List of Date Objects. The method accepts a List of timeStamps and groups them by day. It then returns those grouped timestamps in the aforementioned HashMap construct.
public class GroupDatesByDay {
HashMap<Date, List<Date>> groupedUserLogins = new HashMap<Date, List<Date>>();
Calendar cal = Calendar.getInstance();
public HashMap<Date, List<Date>> parseTimeStamps(List<Date> timeStamps) {
DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss.SSS", Locale.US);
List<Date> timeStamps = new ArrayList<Date>();
for (Date ts : timeStamps) {
cal.setTime(ts);
cal.set(cal.HOUR_OF_DAY, 0);
cal.set(cal.MINUTE, 0);
cal.set(cal.SECOND, 0);
cal.set(cal.MILLISECOND, 0);
if (!groupedUserLogins.containsKey(cal.getTime())) {
groupedUserLogins.put(cal.getTime(), new ArrayList<Date>());
}
groupedUserLogins.get(cal.getTime()).add(ts);
}
keySet = groupedUserLogins.keySet();
keyList.addAll(keySet);
return groupedUserLogins;
}
}
We end up with groupedUserLogins which has now conveniently stored all unique days as keys pointing to a List which holds our timestamps, as Date objects. The biggest advantage of this data-structure is that the keys, as well as the values are still Date objects, giving us future flexibility.
Please feel free to provide alternate solutions, or improve upon what I have presented.
I've been looking everywhere for a solution but can't manage to find one that works.
I have a "Scoreboard" that needs to show the highest "times" (period between two instants) the app has calculated with Joda Time.
All the strings are stocked in an ArrayList and displayed through an ArrayAdapter and a ListView.
The problem : Collections.sort doesn't seem to work properly even with ISO format.
i'm saving the time using the format :
PeriodFormatter formatter = ISOPeriodFormat.standard();
Which gives out this : "PT1M15.664S"
(1 min 15seconds)
That i convert to a string and store into the ArrayList.
How can i sort these strings so it goes from the longest to the shortest amount of time in my Scoreboard ?
I've tried natural sorting and Alphanum Comparator with no luck. Every time it passes a cap (minutes, hours, days) the values get like this :
"PT2.455S"
"PT1.324S"
"PT1M15.333S"
Instead of what i would like :
"PT1M15.333S"
"PT2.455S"
"PT1.324S"
Using Collection.sort(myArrayList) doesn't work either.
Any idea what i should do ?
My code :
// set is a set<String> retrieving it's values from a stringset scores saved
in the sharedpreferences of the app
set = sharedPreferences.getStringSet("scores", null);
//scores is the ArrayList
scores.clear();
if (set != null){
scores.addAll(set);
}else{
scores.add("No Time Yet!");
set = new LinkedHashSet<String>();
set.addAll(scores);
sharedPreferences.edit().putStringSet("scores",set).apply();
}
//removing the String No Time Yet because it no longer serves a purpose here
if ((set != null)&& (set.size()>1)){
scores.remove("No Time Yet!");
}
arrayAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,scores);
listView.setAdapter(arrayAdapter);
Collections.sort(scores);
Thank you for you time.
Short answer: Use the class Duration, not Period.
Explanation:
Your general approach using the class Period is wrong. This type represents a tuple of various amount-unit-pairs. Some of them are not convertible or comparable. For example, it is impossible to determine if P30D is greater or equal to or smaller than P1M (think of February, April or August). So it is pretty clear why you cannot sort by periods resp. why this class does not implement the interface Comparable. And this objection is valid for the objects of type Period as well as for its canonical ISO-representation (as String).
But since you want
the highest "times" (period between two instants)
you can use Duration to determine the absolute amount of elapsed seconds and milliseconds between two given instants. This type is comparable and only has two minor constraints which are probably not important for you:
precision limited to milliseconds
ignores leap seconds
I recommend to compare duration objects, not strings because you want a chronological order, not a lexicographical order. So you could use the String-representation of Duration (like PT72.345S) for storage but parse it for comparison:
Instant i1 = new Instant(0);
Instant i2 = new Instant(72_345);
Duration d1 = new Duration(i1, i2);
Instant i3 = new Instant(60_000);
Instant i4 = new Instant(200_710);
Duration d2 = new Duration(i3, i4);
List<String> scoreTimes = new ArrayList<>();
scoreTimes.add(d1.toString());
scoreTimes.add(d2.toString());
// order from longest times to shortest times
Collections.sort(
scoreTimes,
new Comparator<String>() {
#Override
public int compare(String s1, String s2) {
return Duration.parse(s2).compareTo(Duration.parse(s1));
}
}
);
System.out.println(scoreTimes); // [PT140.710S, PT72.345S]
I have a list of objects called Activity:
class Activity {
public Date activityDate;
public double amount;
}
I want to iterate through List, group them by date and return a new list . Here's what I currently do:
private List<Activity> groupToList(List<Activity> activityList) {
SimpleDateFormatter sdf = new SimpleDateFormatter("YYYY-MM-DD");
Map<String,Activity> groupMap = new HashMap<String,Activity>();
for (Activity a in activityList) {
String key = sdf.format(a.getActivityDate());
Activity group = groupMap.get(key);
if (group == null) {
group = new Activity();
groupMap.add(key, group);
}
group.setAmount(group.getAmount() + a.getAmount());
}
return new ArrayList<Activity>(groupMap.values());
}
Is it a WTF to use the DateFormatter in this way?
I'm using the DateFormatter because each activityDate could have time information.
I would just use the date object itself as the key. If it it bothers you because the date object is mutable, then use its toString() value. No reason to go making formats.
If the issue is that you want to normalize the date by removing the time component, it would be much better to do that withing the Activity object and remove the time component. If the issue is still further that there are potential time zone issues, I would use JodaTime, but there is no object in the JDK currently that represents a pure date without time, so going with a string isn't outrageous, but it should be hidden behind a method in the Activity object and the fact that it is a date formatted string without a time component should be an implementation detail.
java.util.Date is a quite poor abstraction for your need; it is IMO fair to stick to strings if nothing better is around, HOWEVER Joda-time provides a good datatype for you: DateMidnight or alternatively LocalDate if Activity is strictly timezome-independant.
other than that, the code looks good to me, you might be able to shorten it a bit using an implementation of Multimap, to avoid messy null-checking code. to be honest, it doesn't get much shorter than your solution:
public List<Activity> groupedByDate(List<Activity> input) {
//group by day
final Multimap<DateMidnight, Activity> activityByDay
= Multimaps.index(input, new Function<Activity, DateMidnight>() {
#Override
public DateMidnight apply(Activity from) {
return new DateMidnight(from.activityDate);
}
});
//for each day, sum up amount
List<Activity> ret = Lists.newArrayList();
for (DateMidnight day : activityByDay.keySet()) {
Activity ins = new Activity();
ins.activityDate = day.toDate();
for (Activity activity : activityByDay.get(day)) {
ins.amount+=activity.amount;
}
}
return ret;
}
Why not simply create a HashMap<Date, Activity>() instead of the roundabout way with Strings?
Sorry, I didn't answer the question. The answer is: yes, unless I am an idiot ;)
You could do this using the Date as the key if you used a TreeMap and provided a Comparator that only compared the year, month and day and not the time.
As already mentioned the best solution is to represent your date with day precission. If this is not possible joda is nice library.
If you can ignore daylight saving time then grouping by date can be accomplished much easier. A unix time day is 86 400 s long. The timestamp does ignore leap seconds. (Your timer stops for one second or the leap second is distributed in some way.) All date values were day is equal are the same day:
int msPerDay = 86400 * 1000;
long day = new Date().getTime() / msPerDay
One minor point is to adjust the timezone. For my timezone CET (UTC/GMT +1 hour) the GMT day starts one our later:
new GregorianCalendar(2009, 10, 1, 1, 0).getTime().getTime() / msPerDay) ==
new GregorianCalendar(2009, 10, 2, 0, 59).getTime().getTime() / msPerDay) ==
new Date().getTime() / msPerDay
If the daylight saving time is significant the best way is to use joda. The rules are just to complicated and locale specific to implement.