Nearest time-of-day value in List - java

Is there any way to display element of a list (or any other that might do similar function) comparing to current time.
So, I made an arraylist that contains time, which doesn't have any pattern of repetition, using method to increase it for 10 min (for instance) won't to.
I have following:
Date function that has current time:
Date date = new Date();
DateFormat dateformat = new SimpleDateFormat("HH:mm:ss");
ArrayList that has time that I need:
ArrayList<String> time = new ArrayList<String>();
time.add("5:40");
time.add("6:40");
time.add("8:30");
time.add("9:45");
time.add("10:35");
time.add("11:10");
time.add("11:55");
time.add("12:20");
time.add("13:30");
time.add("14:55");
time.add("16:00");
time.add("16:30");
time.add("17:30");
time.add("19:00");
time.add("20:10");
time.add("21:10");
Now I'm curious, and also because I'm new to this, is there a way to do next:
1) Considering there will be a lot of arrays that will have time, is there a way (perhaps with some code examples) to write a method that will have an argument so user can choose which 'time' they want it to be displayed?
2) Will there be need to use loop once (in this case 'time' array) it reaches the end, or can it be written so it automatically goes to beginning based on current time.
3) Code optimisation: can this be written in more practical way?
I am fairly new to this, so any help (with guidance and code examples) is much appreciated so I can use it in this project and as learning material for future ones.

java.time
Use the LocalTime class rather than String to represent your values. A LocalTime object is for time-of-day without a date and without a time zone.
Create your List.
List<LocalTime> list = new ArrayList<>();
Parsing
Populate the List by parsing the input strings. If your input strings comply with the ISO 8601 standard including a padded zero on single-digit hour, then you need not define any formatting pattern as LocalTime can directly parse such values.
list.add( LocalTime.parse( "09:45" ) );
list.add( LocalTime.parse( "10:35" ) );
list.add( LocalTime.parse( "11:10" ) );
If your input strings lack the padding zero, then define and use a DateTimeFormatter.
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "h:m" );
…
LocalTime localTime = LocalTime.parse( input , formatter );
Sorting
Sort the List. The Collections.sort static method is one way.
Collections.sort( list );
Current time
Get the current time. For this a time zone is required (ZoneId), as the wall-clock time for any moment various around the world by time zone. Use a proper time zone name, never the 3-4 letter abbreviations commonly seen such as EST or IST.
ZoneId zoneId = ZoneId.of( "America/Montreal" );
LocalTime now = LocalTime.now( zoneId );
Loop the list
Loop the List comparing each element in the list to now using whatever rules you have in mind for defining “nearest”. The Duration class tracks a span of time as a total number of seconds plus a fractional second in nanoseconds.
Note that Duration class has various methods to assist with comparing. The abs method gives an absolute value (converting negatives to positives). The compareTo method tells you if one duration is larger, smaller, or equal to another.
…
for ( LocalTime localTime : list ) {
Duration duration = Duration.between( now , localTime );
…
}
NavigableSet
If you don't mind losing any possible duplicate time values, use a NavigableSet and its ceiling method as shown in the correct answer by Assyrians.

Like #BasilBourque, I suggest storing the times as LocalTimes - but I would also suggest using a NavigableSet such as TreeSet instead of a List (I'm assuming that the times in your list are all unique).
The idea is that TreeSet has a ceiling method that does exactly what you need:
Returns the least element in this set greater than or equal to the given element, or null if there is no such element.
You can then write:
TreeSet<LocalTime> times = new TreeSet<> ();
times.add(LocalTime.parse("05:40"));
times.add(LocalTime.parse("06:40"));
times.add(LocalTime.parse("08:30"));
//...
LocalTime ceiling = times.ceiling(LocalTime.now());
if (ceiling != null) //do something with it

It would probably be best to use actual Date objects and just compare them. However, if you need to do it using strings, you could write a compare function that takes two dates and outputs the difference between them in minutes.
int compare(String d1, String d2) {
//split the strings based on the ":"
string d1Split[] = d1.split(":");
string d2Split[] = d2.split(":");
//convert each one to minutes - note this assumes the string will always be formatted like "hours:minutes:anything else is irrelevant"
d1Mins = d1Split[0] * 60 + d1Split[1];
d2Mins = d2Split[0] * 60 + d2Split[1];
//return the absolute value of the difference between the times in minutes - note this assumes the times will be part of the same day
return Math.abs(d1Mins - d2Mins);
}
Once you have a compare function, all you need to do is go through the array and compare each element to the current date, storing the closest one.
int leastDifference = 999999;
String now = dateformat.format(date);
String closest = "";
for (String toCompare : time) {
int comparison = compare(toCompare, now);
if (comparison < leastDifference) {
leastDifference = comparison;
closest = toCompare;
}
}
And closest will contain the string you are looking for.

get the HH:MM part of current time;
sort your time list
use binary search:
String search(String currHrs, List time, int start, int end) {
if (start == end && time.get(start) > currHrs)
return time.get(start);
else
return currHrs;
int mid = (start + end) >> 1;
// discard one half at a time.
return (time.get(mid) > currHrs) ? search(currHrs, time, start, mid): search(currHrs, time, mid + 1, end);
}

Related

How to code new appointments in a planner to be arranged by date

I was tasked with creating an appointment planner in Java. I have everything done except I am having trouble with making the dates of new appointments be sorted by date.
I have tried switching the order around but no matter what I cannot get new appointments to be sorted properly by date.
{"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
int ind1 = 0,ind2 = 0;
for(int i=0;i<12;i++) {
if(monthArray[i].equalsIgnoreCase(month)) {
ind1=i;
}
if(monthArray[i].equalsIgnoreCase(app.getMonth())) {
ind2=i;
}
}
if(ind1<ind2) {
return 1;
}
else if(ind1==ind2) {
if(this.day<app.getDay()) {
return 1;
}
else if(this.getDay()>app.getDay()) {
return -1;
}
else {
if(this.getHour()<app.getHour()) {
return 1;
}
else if(this.getHour()>app.getHour()) {
return -1;
}
else {
if(this.getMin()<app.getMin()) {
return 1;
}
else if(this.getMin()>app.getMin()) {
return -1;
}
else {
return 0;
}
}
}
}
else {
return -1;
}
}
}
I expect new appointments to be sorted by date but they are not.
Time Zone
A proper appointment system must account for the fact that politicians around the world have shown a proclivity for redefining the time zone(s) under their juridiction. They do so surprisingly often, and with little or no warning.
So your Appointment class should carry two member fields:
LocalDateTime to hold the date and the time of day. Note that this does not represent a moment, is not a point on the timeline. If it holds a value of 3 PM on January 23rd next year, we don’t know if that means 3 PM in Tokyo, Kolkata, Paris, or Montreal — all different moments, several hours apart.
ZoneId for the time zone in which we intend that 3 PM appointment.
Example:
LocalDate ld = LocalDate.of( 2020 , Month.JANUARY , 23 ) ;
LocalTime lt = LocalTime.of( 15 , 0 ) ;
this.localDateTime = LocalDateTime.of( ld , lt ) ;
this.zoneId = ZoneId.of( "America/Los_Angeles" ) ;
Sorting
For sorting purposes, your class can implement the Comparable interface, with the required compareTo method.
The trick is that you want the 3 PM appointments on the east coast of the US, for example, to sort above the 3 PM appointments of the west coast which occur a few hours later.
So the compareTo method must dynamically calculate a moment, determine a specific point on the timeline. Then, compare the moments of the various Appointment objects to sort properly.
First step is being sure that your JVM has been updated with the latest rules about the time zones. Remember, as mentioned above, these change quite often. Updates to Java will often include an update to the “tzdata” zone information. If a time zone you care about has changed more recently, you may need to update the tzdata yourself.
Next step is dynamically applying the zone to the date-time to determine a moment. Apply the ZoneId to the LocalDateTime to get a ZonedDateTime.
ZonedDateTime zdt = this.localDateTime.atZone( this.zoneId ) ;
Adjust that to UTC. Extract a Instant object, always in UTC by definition.
Instant thisInstant = zdt.toInstant() ;
Compare the Instant of this Appointment object with the other one passed to your compareTo. We can, in turn, call the Instant::compareTo method to do the work of actually comparing.
return thisInstant.compareTo( other.localDateTime.atZone( other.zoneId ).toInstant() ) ;
Alternatively, you might choose to use Objects.compare.
Java has a DateTime library that could be very helpful, but I'm assuming you don't want that seeing as you're doing it manually.
https://dzone.com/articles/java-comparable-interface-in-five-minutes
I'd recommend reading something like that, which gives a little bit of information about comparables. This allows you to create a method doing what you're doing, comparing two objects to each other. Then you can use a lot of standard solutions such as Collections.sort in order to test out and use your code more easily.
I'm not sure if that's what you're already doing so i thought I'd throw it out there.
But for the actual problem, the best solution is funnily enough using an inbuilt function.
Integer.compareTo(int a, int b) will compare two integers for you. All you're doing is repeatedly comparing integers. You can run your code like
int comp = Integer.compare(monthA, monthB;
if(comp != 0) return comp;
//proceed with rest of comparisons the same way you did the months
If you are getting appointment date then why dont you directing comparing them instead of comparing month ,date and time.
Though you can simply convert your appointment date to valid date object as below.
String sDate1="31/12/1998";
Date date1=new SimpleDateFormat("dd/MM/yyyy").parse(sDate1);
System.out.println(sDate1+"\t"+date1);
Reference link : https://www.javatpoint.com/java-string-to-date
Then simply compare date object
if (date1.compareTo(date2) > 0) {
some opertaion...
}
Reference link : https://www.mkyong.com/java/how-to-compare-dates-in-java/
Hope this will help you.

Find the Median Date between two dates using Java 8

I'm finding it difficult that what it sounds.
So, I have a max date and a min date and I need to find the median date between these two dates. I use Java 8 to find my max and min dates,
LocalDate gerbutsmin = YearMonth.now().plusMonths(2).atDay(1);
LocalDate gerbutsmax = YearMonth.now().plusMonths(15).atDay(1);
How would I go ahead after this? Maybe I need to switch back to Calander?
Try using DAYS.between():
LocalDate gerbutsmin = YearMonth.now().plusMonths(2).atDay(1);
LocalDate gerbutsmax = YearMonth.now().plusMonths(15).atDay(1);
long numDays = ChronoUnit.DAYS.between(gerbutsmin, gerbutsmax);
LocalDate median = gerbutsmin.plusDays(numDays / 2L); // plusDays takes a long
System.out.println(median);
2019-03-17
(output as of today, which is 2019-07-26)
Demo
There is a boundary condition should the difference between your min and max dates be an odd number. In this case, there is no formal median day, but rather the median would fall in between two days.
Note:
If you're wondering what happens exactly in the edge case, if the low date were today (2018-07-26) and the high date three days away (2018-07-29), then the median would be reported as 2018-07-27.
LocalDate gerbutsmin = YearMonth.now().plusMonths(2).atDay(1);
LocalDate gerbutsmax = YearMonth.now().plusMonths(15).atDay(1);
LocalDate median = gerbutsmin.plusDays(ChronoUnit.DAYS.between(gerbutsmin, gerbutsmax) / 2);
"Middle of two dates" is not unambiguously defined - you must decide how to handle dates an odd number of days apart (e.g. what is the middle date between 1st and 4th of a month, or between 1st and 2nd), and what to do with the time portion of the date object.
The concrete problem with your approach is that dates are not numbers, so you cannot add them and divide them by two. To do that, use the getTime() method to obtain the number of seconds since the epoch, and operate on that:
var middate = new Date((startdate.getTime() + enddate.getTime()) / 2.0);
This will give you the middle between two dates, treating them as points in time.
Click here
LocalDateTime startime = LocalDateTime.now();
LocalDateTime endtime = startime.plusDays(4);
long diff = endtime.until(startime, ChronoUnit.MINUTES);
LocalDateTime middleTime = startime.plusMinutes(diff / 2);

Getting a List of dates given a start and end date

Given two dates (a start date and an end date in the form of M,YYYY), I want to make an arraylist with each element a date in the form M,YYYY between the starting and end date, inclusively.
For example, given dates 10,2010 and 2,2011 my list should look like this:
list = {(10,2010),(11,2010),(12,2010),(1,2011),(2,2011)};
I can do this by creating a while loop(while startYear < endYear), account for the starting month, for loop to 12th month, insert date into list, etc.
But I feel there has to be a more efficient way of going about this as this would require multiple checks for the starting month and end year. Is there a better solution that I'm not seeing?
It would make more sense to store the dates as objects instead of "pairs" of year, month. For example, using the YearMonth class in the java.time API (Java 8 and later):
YearMonth from = YearMonth.of(2010, 10);
YearMonth to = YearMonth.of(2011, 2);
List<YearMonth> list = new ArrayList<> ();
for (YearMonth ym = from; !ym.isAfter(to); ym = ym.plusMonths(1)) {
list.add(ym);
}
Or to use the stream API (not sure that it is much cleaner in this case):
List<YearMonth> list = Stream.iterate(from, ym -> ym.plusMonths(1))
.limit(MONTHS.between(from, to) + 1)
.collect(toList());

Test a date within a day intervall range

I have a date and a number and want to check if this date and this number occurs in a list of other dates within:
+-20 date intervall with the same number
so for example 1, 1.1.2013 and 1,3.1.2013 should reuturn false.
I tried to implement the method something like that:
private List<EventDate> dayIntervall(List<EventDate> eventList) throws Exception {
List<EventDate> resultList = new ArrayList<EventDate>();
for (int i = 0; i < eventList.size(); i++) {
String string = eventList.get(i).getDate();
Date equalDate = new SimpleDateFormat("dd.MM.yyyy", Locale.GERMAN).parse(string);
for (int j = 0; j < eventList.size(); j++) {
String string1 = eventList.get(i).getDate();
Date otherDate = new SimpleDateFormat("dd.MM.yyyy", Locale.GERMAN).parse(string1);
if (check number of i with number of j && check Date) {
//do magic
}
}
}
return resultList;
}
The construction of the iteration method is not that hard. What is hard for me is the date intervall checking part. I tried it like that:
boolean isWithinRange(Date testDate, Date days) {
return !(testDate.before(days) || testDate.after(days));
}
However that does not work because days are not takes as days. Any suggestions on how to fix that?
I really appreciate your answer!
You question is difficult to follow. But given its title, perhaps this will help…
Span Of Time In Joda-Time
The Joda-Time library provides a trio of classes to represent a span of time: Interval, Period, and Duration.
Interval
An Interval object has specific endpoints that lie on the timeline of the Universe. A handy contains method tells if a DateTime object occurs within those endpoints. The beginning endpoint in inclusive while the last endpoint is exclusive.
Time Zones
Note that time zones are important, for handling Daylight Saving Time and other anomalies, and for handling start-of-day. Keep in mind that while a java.util.Date seems like it has a time zone but does not, a DateTime truly does know its own time zone.
Sample Code
Some code off the top of my head (untested)…
DateTimeZone timeZone = DateTimeZone.forID( "Europe/Berlin" );
DateTime dateTime = new DateTime( yourDateGoesHere, timeZone );
Interval interval = new Interval( dateTime.minusDays( 20 ), dateTime.plusDays( 20 ) );
boolean didEventOccurDuringInterval = interval.contains( someOtherDateTime );
Whole Days
If you want whole days, call the withTimeAtStartOfDay method to get first moment of the day. In this case, you probably need to add 21 rather than 20 days for the ending point. As I said above, the end point is exclusive. So if you want whole days, you need the first moment after the time period you care about. You need the moment after the stroke of midnight. If this does not make sense, see my answers to other questions here and here.
Note that Joda-Time includes some "midnight"-related methods and classes. Those are no longer recommended by the Joda team. The "withTimeAtStartOfDay" method takes their place.
DateTime start = dateTime.minusDays( 20 ).withTimeAtStartOfDay();
DateTime stop = dateTime.plusDays( 21 ).withTimeAtStartOfDay(); // 21, not 20, for whole days.
Interval interval = new Interval( start, stop );
You should avoid java.util.Date if at all possible. Using the backport of ThreeTen (the long awaited replacement date/time API coming in JDK8), you can get the number of days between two dates like so:
int daysBetween(LocalDate start, LocalDate end) {
return Math.abs(start.periodUntil(end).getDays());
}
Does that help?
You can get the number of dates in between the 2 dates and compare with your days parameter. Using Joda-Time API it is relatively an easy task: How do I calculate the difference between two dates?.
Code:
SimpleDateFormat format = new SimpleDateFormat("dd.MM.yyyy", Locale.GERMAN);
Date startDate = format.parse("1.1.2013");
Date endDate = format.parse("3.1.2013");
Days d = Days.daysBetween(new DateTime(startDate), new DateTime(endDate));
System.out.println(d.getDays());
Gives,
2
This is possible using Calendar class as well:
Calendar cal = Calendar.getInstance();
cal.setTime(startDate);
System.out.println(cal.fieldDifference(endDate, Calendar.DAY_OF_YEAR));
Gives,
2
This 2 can now be compared to your actual value (20).

Comparing only the time component of Date

Consider the following code to only determine if the time component of one Date object is before the time component of another Date object:
private boolean validStartStopTime( Date start, Date stop ) {
Calendar startCal = Calendar.getInstance();
Calendar stopCal = Calendar.getInstance();
startCal.clear();
stopCal.clear();
startCal.setTime( start );
stopCal.setTime( stop );
startCal.set( Calendar.YEAR, 2011 );
stopCal.set( Calendar.YEAR, 2011 );
startCal.set( Calendar.MONTH, 1 );
stopCal.set( Calendar.MONTH, 1 );
startCal.set( Calendar.DAY_OF_YEAR, 1 );
stopCal.set( Calendar.DAY_OF_YEAR, 1 );
return startCal.before( stopCal );
}
Would this insure that time comparison is correct? Is there a better alternative (Joda is not an option)? I believe that this is equivalent to setting the Calendar objects to current date/time and manually copying over the hour, minutes, and milliseconds component. You can assume that timezone are the same.
EDIT: To clarify what I mean by comparing only the time component of a Date object. I mean that when looking specifically at the time portion, the start time is before the stop time. The date portion is ABSOLUTELY irrelevant (in that start="Jan 2 20011 10AM" and end="Jan 1 2011 11AM" is perfectly fine), if I had a choice I'd simply use something that contained just the time but a Date object is what I'm given. I'd like to not write a sequence of if-else which is why I have the approach above but I welcome a cleaner/better approach.
Your code should work fine. You could also format just the time components in a zero-based string notation and compare them lexicographically:
public static boolean timeIsBefore(Date d1, Date d2) {
DateFormat f = new SimpleDateFormat("HH:mm:ss.SSS");
return f.format(d1).compareTo(f.format(d2)) < 0;
}
[Edit]
This is assuming that the dates have the same timezone offset. If not you'll have to adjust them manually beforehand (or as part of this function).
There are 86,400,000 milliseconds in a day, why not just use that to figure it out?
You could just mod timeInMilliseconds with that number and compare the results.

Categories