I am searching for a method to find the last date of a input day of week from now on? For example: If today is Monday, the 5th march 2018 the results should look like this:
Input Output
Monday 05.03.2018
Tuesday 27.02.2018
Wednesday 28.02.2018
Thursday 01.03.2018
Friday 02.03.2018
Saturday 03.03.2018
Sunday 04.03.2018
I hope you get the idee. I couldn't really find a post with something similar, so any help would be much appreciated
As posted in comments, this is the code I have atm:
private String getLastDateOfDayOfWeek(String day, String returnDateFormat) throws ParseException {
int dayOfWeek = parseDayOfWeek(day, Locale.ENGLISH);
Calendar cal = Calendar.getInstance(); // Today, now
if (cal.get(Calendar.DAY_OF_WEEK) != dayOfWeek) {
// ...
}
return new SimpleDateFormat(returnDateFormat).format(cal.getTime());
}
private static int parseDayOfWeek(String day, Locale locale)
throws ParseException {
SimpleDateFormat dayFormat = new SimpleDateFormat("EEEE", locale);
Date date = dayFormat.parse(day);
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
return dayOfWeek;
}
Atm I have two functions: one that can convert a string to a Calender dayOfWeek number and another one, which is the method i am searching for. Currently it only handles todays day of week correct, the part that should do the work for every other day of the week is missing(comment with ...)
tl;dr
LocalDate.now().with( // Get today’s date, then move to another date.
TemporalAdjusters.previousOrSame( // An implementation of `TemporalAdjuster` interface, used for algorithms to move to another date.
DayOfWeek.valueOf( “Monday”.toUpperCase() ) // Get the enum Object with te corresponding hard-coded name in English such as `DayOfWeek.MONDAY`.
)
)
DayOfWeek
The DayOfWeek enum holds seven objects, one for each day of the week.
English
The names of these seven objects are in English, all uppercase. So you can get the Object from the English word.
String input = “Monday”.toUpperCase() ;
DayOfWeek dow = DayOfWeek.valueOf( input ) ;
Other languages
For languages other than English, define a List populated with the name of each day-of-week. Use the name generated from DayOfWeek::getDisplayName, a method that automatically localizes. Start the list with Monday, per the ISO 8601 standard. Search that list to find a match with your input. Get the ordinal number of your match, 1-7 (not the index number 0-6). Pass that number to DayOfWeek.valueOf to get a DayOfWeek object. In some languages you’ll need a pair of such lists to be searched, as an alternate spelling may be invoked for a “standalone” use of the day-of-week without the context of a date.
Here is an example of such a class. Beware: I just whipped up this class without much thought and without any serious testing. Use at your own risk. Usage: DayOfWeekDelocalizer.of( Locale.CANADA_FRENCH ).parse( "lundi" ) → DayOfWeek.MONDAY
package com.basilbourque.example;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.time.DayOfWeek;
import java.time.format.TextStyle;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
// For a given name of day-of-week in some language, determine the matching `java.time.DayOfWeek` enum object.
// This class is the opposite of `DayOfWeek.getDisplayName` which generates a localized string for a given `DayOfWeek` object.
// Usage… DayOfWeekDelocalizer.of( Locale.CANADA_FRENCH ).parse( "lundi" ) → DayOfWeek.MONDAY
// Assumes `FormatStyle.FULL`, for day-of-week names without abbreviation.
// USE AT YOUR OWN RISK. Rough draft, quickly written. No serious testing.
public class DayOfWeekDelocalizer
{
#NotNull
private Locale locale;
#NotNull
private List < String > dayOfWeekNames, dayOfWeekNamesStandalone; // Some languages use an alternate spelling for a “standalone” day-of-week used without the context of a date.
// Constructor. Private, for static factory method.
private DayOfWeekDelocalizer ( #NotNull Locale locale )
{
this.locale = locale;
// Populate the pair of arrays, each having the translated day-of-week names.
int daysInWeek = 7; // Seven days in the week.
this.dayOfWeekNames = new ArrayList <>( daysInWeek );
this.dayOfWeekNamesStandalone = new ArrayList <>( daysInWeek );
for ( int i = 1 ; i <= daysInWeek ; i++ )
{
this.dayOfWeekNames.add( DayOfWeek.of( i ).getDisplayName( TextStyle.FULL , this.locale ) );
this.dayOfWeekNamesStandalone.add( DayOfWeek.of( i ).getDisplayName( TextStyle.FULL_STANDALONE , this.locale ) );
}
// System.out.println( this.dayOfWeekNames );
// System.out.println( this.dayOfWeekNamesStandalone );
}
// Constructor. Private, for static factory method.
// Personally, I think it unwise to default implicitly to a `Locale`. But I included this in case you disagree with me, and to follow the lead of the *java.time* classes. --Basil Bourque
private DayOfWeekDelocalizer ( )
{
this( Locale.getDefault() );
}
// static factory method, instead of constructors.
// See article by Dr. Joshua Bloch. http://www.informit.com/articles/article.aspx?p=1216151
// The `Locale` argument determines the human language and cultural norms used in de-localizing input strings.
synchronized static public DayOfWeekDelocalizer of ( #NotNull Locale localeArg )
{
DayOfWeekDelocalizer x = new DayOfWeekDelocalizer( localeArg ); // This class could be optimized by caching this object.
return x;
}
// Attempt to translate the name of a day-of-week to look-up a matching `DayOfWeek` enum object.
// Returns NULL if the passed String value is not found to be a valid name of day-of-week for the human language and cultural norms of the `Locale` specified when constructing this parent object, `DayOfWeekDelocalizer`.
#Nullable
public DayOfWeek parse ( #NotNull String input )
{
int index = this.dayOfWeekNames.indexOf( input );
if ( - 1 == index )
{ // If no hit in the contextual names, try the standalone names.
index = this.dayOfWeekNamesStandalone.indexOf( input );
}
int ordinal = ( index + 1 );
DayOfWeek dow = ( ordinal > 0 ) ? DayOfWeek.of( ordinal ) : null; // If we have a hit, determine the DayOfWeek. Else return null.
return dow;
}
// `Object` overrides.
#Override
public boolean equals ( Object o )
{
if ( this == o ) return true;
if ( o == null || getClass() != o.getClass() ) return false;
DayOfWeekDelocalizer that = ( DayOfWeekDelocalizer ) o;
return locale.equals( that.locale );
}
#Override
public int hashCode ( )
{
return locale.hashCode();
}
public static void main ( String[] args )
{
// Quick testing.
// DayOfWeekDelocalizer x = DayOfWeekDelocalizer.of( Locale.JAPAN );
if ( DayOfWeekDelocalizer.of( Locale.CANADA_FRENCH ).parse( "lundi" ).equals( DayOfWeek.MONDAY ) )
{
System.out.println( "GOOD - Canada French 'lundi' is parsing to DayOfWeek.MONDAY." );
} else
{
System.out.println( "BAD - Canada French 'lundi' is NOT parsing to DayOfWeek.MONDAY." );
}
}
}
Tip: Using a String to represent a DayOfWeek is clumsy. Your code should instead be passing around a DayOfWeek enum object.
LocalDate
Next we need the current date.
LocalDate today = LocalDate.now() ;
Better to explicitly state your desired/expected time zone than rely implicitly on the JVM’s current default time zone.
ZoneId z = ZoneId.of( “Africa/Tunis” ) ;
LocalDate today = LocalDate.now( z ) ;
TemporalAdjuster
Move to another date by applying a TemporalAdjuster. The TemporalAdjusters class offers the implementation we need.
TemporalAdjuster ta = TemporalAdjusters.previousOrSame( dow ) ;
LocalDate ld = today.with( ta ) ;
Note that java.time uses Immutable Objects. So rather than modify an object, methods produce a new distinct object with altered values based on the original.
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.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
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.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
Related
I'm trying to get Months from a certain quarter. Using the code below, I successfully get the names of the months in the current quarter from the LocalDate.now() instance.
How would I get a quarter's months from just a quarter String (e.g. "Q1")?
int monthInt = Month.from(LocalDate.now()).firstMonthOfQuarter().getValue();
for (int j = 1; j <= 3; j++) { //for each month in quarter
System.out.println(Month.of(monthInt).name()); //January, February, March
monthInt++;
}
We can find out how the JDK calculates the quarter by looking at the declaration of getFrom of IsoFields.QUARTER_OF_YEAR:
public long getFrom(TemporalAccessor temporal) {
if (isSupportedBy(temporal) == false) {
throw new UnsupportedTemporalTypeException("Unsupported field: QuarterOfYear");
}
long moy = temporal.getLong(MONTH_OF_YEAR);
return ((moy + 2) / 3);
}
Notice how it uses the formula quarter = (moy + 2) / 3. Therefore, to find the starting month of a quarter, we just need to rearrange it in terms of moy - moy = quarter * 3 - 2.
You can write a method like this:
private static List<String> monthNamesFromQuarter(int quarter) {
// you can do the validation of quarter yourself
int start = quarter * 3 - 2;
return IntStream.range(start, start + 3)
.mapToObj(Month::of)
.map(Month::name)
.collect(Collectors.toList());
}
tl;dr
Use org.threeten.extra.YearQuarter class, along with Quarter, ZoneId, LocalDate, and Month.
YearQuarter // Represent an entire quarter of a specific year.
.now( ZoneId.of( "Asia/Tokyo" ) ) // Determine the current quarter as seen via the wall-clock time used by the people of a particular region (a time zone).
.with( // Move to another quarter.
Quarter.valueOf( "Q1" ) // Or, `Quarter.of( 1 )` if starting with an integer number rather than a `String` object.
) // Returns another `YearQuarter` object, rather than modifying the original.
.atDay( 1 ) // Returns a `LocalDate` object.
.getMonth() // Returns a `Month` enum object.
.getDisplayName( // Automatically localize the name of the month.
TextStyle.FULL , // How long or abbreviated do you want the translation.
Locale.US // Or Locale.CANADA_FRENCH and so on.
) // Returns a `String` object.
January
YearQuarter in ThreeTen-Extra
The ThreeTen-Extra library has a class you might find useful for this work: YearQuarter.
Get the current quarter. We need a time zone to determine the current date, and therefore the current quarter. For any given moment, the date varies around the globe by time zone.
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
YearQuarter currentYearQuarter = YearQuarter.now( z ) ;
But you want to determine a quarter by parsing a string.
If you have a string styled similar to ISO 8601 (the standard does not actually specify quarters) YYYY-Qq then YearQuarter can directly parse.
String input = "2020-Q1" ;
YearQuarter yearQuarter = YearQuarter.parse( input ) ;
If you have only the quarter part without the year, use Quarter enum. If your input string is Q1 and such, use valueOf to retrieve the matching enum object.
String input = "Q1" ;
Quarter quarter = Quarter.valueOf( input ) ;
If you have a number instead of a string, that is, 1 or 2 or 3 or 4, then use static method Quarter.of. By the way, in your own code you should be passing around these Quarter objects rather than a mere integer or string, to make your code more self-documenting, to provide type-safety, and to ensure valid values.
int input = 1 ; // Domain: 1, 2, 3, 4.
Quarter quarter = Quarter.of( input ) ;
Apply that Quarter instance to our current YearQuarter instance to get another YearQuarter instance. These classes use the immutable objects pattern, so we are not modifying existing instance, we are generating new instances.
YearQuarter yearQuarter = currentYearQuarter.with( quarter ) ;
yearQuarter.toString(): 2019-Q1
Get first date (LocalDate), and year-month (YearMonth), and Month enum object, from that year-quarter.
LocalDate firstDate = yearQuarter.atDay( 1 ) ;
YearMonth yearMonth1 = YearMonth.from( firstDate ) ;
YearMonth yearMonth2 = yearMonth1.plusMonths( 1 ) ;
YearMonth yearMonth3 = yearMonth1.plusMonths( 2 ) ;
Generate a string containing the automatically localized name of month.
Locale locale = Locale.US ; // Or Locale.CANADA_FRENCH and so on.
String output1 = yearMonth1.getMonth().getDisplayName( TextStyle.FULL , locale ) ;
January
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.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, Java SE 10, Java SE 11, and later - Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Most of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
I suggest we build a map of the months contained in each quarter and make a simple map lookup whenever we need the months for some quarter string. A stream pipeline and an adequate DateTimeFormatter will build such a map in just a few lines:
static Map<String, List<Month>> monthsPerQuarter = Arrays.stream(Month.values())
.collect(Collectors.groupingBy(
DateTimeFormatter.ofPattern("QQQ", Locale.ENGLISH)::format));
IMO the great advantage here is that we need to do no math ourselves. While math converting from quarter number to month number may seem simple for the person writing it, it is not only error-prone, it will also be unclear and hard to decipher for many readers.
Now we can lookup for example like this:
System.out.println(monthsPerQuarter.get("Q3"));
Output is:
[JULY, AUGUST, SEPTEMBER]
If you need the months individually:
monthsPerQuarter.get("Q4").forEach(System.out::println);
OCTOBER
NOVEMBER
DECEMBER
If your quarter was originally a number in the range from 1 through 4, you may use this map instead:
static Map<Integer, List<Month>> monthsPerQuarter = Arrays.stream(Month.values())
.collect(Collectors.groupingBy(m -> m.get(IsoFields.QUARTER_OF_YEAR)));
A reason why I recommend the map approach is: it’s easy to convert from Month to quarter string, but java.time offers no easy way to convert from quarter string to month. Just for the demonstration and not the way I recommend, one might do:
DateTimeFormatter quarterFormatter = new DateTimeFormatterBuilder()
.appendPattern("QQQ")
// Any year will do since all years have the same 4 quarters # the same 3 months
.parseDefaulting(ChronoField.YEAR, 2000)
.parseDefaulting(IsoFields.DAY_OF_QUARTER, 1)
.toFormatter(Locale.ENGLISH);
String qString = "Q1";
Month firstMonthOfQuarter = Month.from(quarterFormatter.parse(qString));
IntStream.range(0, 3)
.mapToObj(firstMonthOfQuarter::plus)
.forEach(System.out::println);
JANUARY
FEBRUARY
MARCH
It’s 11 code lines instead of 4 with no gain that I can see.
For quarter > firstmonth the rule is
Q1 -> 1
Q2 -> 4
Q3 -> 7
Q4 -> 10
With math : quarterValue *3 -2
So applying this in Java :
String quarter = "Q1";
int monthInt = Integer.parseInt(quarter.replaceAll("\\D", "")) * 3 - 2;
for (int j = 0; j < 3; j++) {
System.out.println(Month.of(monthInt + j).name());
}
List<String> quarters = Arrays.asList("Q1", "Q2", "Q3", "Q4");
for (String quarter : quarters) {
System.out.print(quarter);
int monthInt = Integer.parseInt(quarter.replaceAll("\\D", "")) * 3 - 2;
for (int j = 0; j < 3; j++) {
System.out.print(" " + Month.of(monthInt + j).name());
}
System.out.println();
}
Q1 JANUARY FEBRUARY MARCH
Q2 APRIL MAY JUNE
Q3 JULY AUGUST SEPTEMBER
Q4 OCTOBER NOVEMBER DECEMBER
I'm trying to create a method that would add an appointment to an arraylist appointmentCalndar. This method would validate the date to see if the user input is equal to the SimpleDateFormat in my code, the startTime and the endTime of the appointment and to see if its in the future or not.
I have tried using the java Date api to check this but when I try to extend the class to get access to the attributes it always causes an error on compile time. So overall my question is what would the best way to compare an object of appointment type to an object of type date? I try using accesors to getDate() and startTime and endTime but it won't allow me to get them also.
public AppointmentDate(String appString)
{
// 1) split ithe string into Date/from/to
// 2) consturct the Date object for the appDate
// 3) consturct the Date object for the startTime
// 4) consturct the Date object for the endTime
String[] appDetails = appString.split(",");
if(appDetails.length == 2)
{
try {
SimpleDateFormat df = new SimpleDateFormat("dd/MM/yyyy");
this.appDate = df.parse(appDetails[0]);
DateFormat formatter = new SimpleDateFormat("dd/MM/yyyy,mm:HH");
String dFormat = appDetails[0] + "," + appDetails[1];
this.startTime = formatter.parse(dFormat);
dFormat = appDetails[0] + "," + appDetails[2];
this.endTime = formatter.parse(dFormat);
}
catch (Exception ex)
{
}
}
else
{
System.out.print("User Date is Invalid");
}
}
public void setStartTime(Date startTime)
{
this.startTime = startTime;
}
public Date getStartTime()
{
return startTime;
}
public void setEndTime(Date endTime)
{
this.endTime = endTime;
}
public Date getEndTime()
{
return endTime;
}
public void setAppdate(Date appDate)
{
this.appDate = appDate;
}
public Date getAppDate()
{
return appDate;
}
public void add(Appointment a)
{
if (a.equals(a.getDate()))
{
if(a.getStartTime() < a.getEndTime())
{
}
}
else
{
System.out.print("");
}
}
Static block (almost)
Your code to exercise the class is in the wrong place. You have it stuck at the top of the class which is not syntactically correct. We can run code at the top, as a static block, but it needs to be labeled static { … }. A static block is not commonly used in my experience. And certainly is not the right place for what you are doing there.
main method
Instead you should be using a main method. This non-OOP little thingie is a trick, a hack, to solve the chicken-or-the-egg conundrum, to get us from no-app-running to our OOP idea of heaven with a bunch of objects floating around and passing messages to one another.
When first learning Java, do not try to understand all the syntax and purpose of the main method. Just accept it as a necessary evil to get the app running, it is merely the entrance point to execute the app. Focus on learning the OOP concepts and practices. Later, the main method and syntax will make more sense.
Accessors
Here is a simplified re-write of your example code. We are just using a LocalDate for simplicity, just enough to show (a) a main method, and (b) getter/setter accessor methods.
package com.basilbourque.example;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
public class AppointmentDate {
private LocalDate localDate;
// Constructor
public AppointmentDate ( LocalDate localDate ) {
this.localDate = localDate;
}
public LocalDate getLocalDate ( ) {
return localDate;
}
public void setLocalDate ( LocalDate localDate ) {
this.localDate = localDate;
}
#Override
public String toString ( ) {
return "AppointmentDate{ " +
"localDate=" + localDate +
" }";
}
// Not really a part of this class. A `main` method is just a hack to get our app launched.
public static void main ( String[] args ) {
String input = "23/01/2018";
DateTimeFormatter f = DateTimeFormatter.ofPattern( "dd/MM/uuuu" );
LocalDate ld = LocalDate.parse( input , f );
AppointmentDate ad = new AppointmentDate( ld );
ad.setLocalDate( ld.plusWeeks( 1 ) );
LocalDate newValue = ad.getLocalDate();
System.out.println( newValue.toString() ); // Generate text representing the value of this `LocalDate` object in standard ISO 8601 format.
List < AppointmentDate > list = new ArrayList <>( 3 );
list.add( ad );
list.add( new AppointmentDate( LocalDate.parse( "2018-02-13" ) ) );
list.add( new AppointmentDate( LocalDate.parse( "2018-12-21" ) ) );
System.out.println( list );
}
}
2018-01-30
[AppointmentDate{ localDate=2018-01-30 }, AppointmentDate{ localDate=2018-02-13 }, AppointmentDate{ localDate=2018-12-21 }]
java.time
You are using terrible old date-time classes that were supplanted years ago by the java.time classes. Never use Date, Calendar, SimpleDateFormat, etc.
Appointments are tricky
While appointment tracking may seem intuitively simple, you are actually working on a very tricky subject.
The core problem is that politicians around the world are fond of redefining the time zone(s) under their jurisdiction. They do so quite frequently. They do so in both times of relative quiet and in times of turmoil.
The US and Canada have changed their offsets multiple times in recent decades. Turkey and Russia have changed their minds about going on or off DST multiple times in the last several years.
And politicians change their time zones with very little advance notice. And the notice seems to be growing shorter, despite the increased disturbance this causes in ever-more-computerized societies. Just last month, Morocco announced their country would stay on Daylight Saving Time (DST) permanent, cancelling on a Friday the DST cutover scheduled for that Sunday, leaving 0 business days warning — what a mess for IT staff. In May this year, North Korea slipped their clock a half-hour to sync with South Korea, with apparently immediate effect (no advance notice at all).
These frequent and unpredictable changes mean we cannot responsibly track future appointments as moments, as specific points on the timeline. When we say something like “3 PM on January 23rd” we usually mean 3 PM after politicians may have made their changes to the clock.
So we must store future appointments as a date and a time-of-day without a time zone or offset-from-UTC. Then, when calculating a calendar we must dynamically apply the rules for the intended time zone as currently defined on that day. If we perform this dynamic-determination of a moment today, and again in three days, if the politicians have announced a change in the time zone definition, and if we have been able to update our tzdata data files in our operating systems, database engines, Java Virtual Machines, and various libraries, then we will arrive at a different moment in time.
LocalDateTime
The Local… types in Java purposely lack any concept of time zone or offset-from-UTC. So they cannot represent a moment. So we never use these to pinpoint actual events that happened in the past. But these types are what we need for future appointments.
The LocalDateTime class represents a date with a time-of-day without any zone/offset.
LocalDate ld = LocalDate.of( 2018 , Month.JANUARY , 23 ) ;
LocalTime lt = LocalTime.of( 15 , 0 ) ; // 3 PM in 24-hour time.
LocalDateTime ldt= LocalDateTime.of( ld , lt ) ;
ZonedDateTime
When calculating a calendar, when we need a specific moment, we apply a time zone (ZoneId) to get a ZonedDateTime object.
ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = ldt.atZone( z ) ; // Determine a moment, a specific point on the timeline.
Instant
We can view that same moment in UTC by extracting a Instant.
Instant instant = zdt.toInstant() ; // Adjust to UTC.
Duration
Appointments are generally best stored as a starting point plus a duration. No need to store the stopping point as that can be calculated.
Duration d = Duration.ofHours( 1 ) ; // A one-hour appointment.
While we often want to adjust into a time zone for presentation to a user, generally behind-the-scenes it is best practice to track moments in UTC. So the starting and stopping points of an appointment calculated as moments should be done as a pair of Instant objects.
Instant start = ldt.atZone( z ).toInstant() ;
Instant stop = start.plus( d ) ;
Interval
We can leverage a class to represent this pair of Instant objects, Interval.
This class is found in the ThreeTen-Extra library, a project led by the same man than led the Joda-Time, JSR 310, and java.time projects, Stephen Colebourne.
This class has very handy methods for comparison such as abuts, overlaps, contains, and so on. You will likely want to use these methods in a scheduling app.
Appointment.java
Put this all together and we get a class like this:
package com.basilbourque.example;
import org.threeten.extra.Interval;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class Appointment {
private LocalDateTime start;
private Duration duration;
// Constructor.
public Appointment ( LocalDateTime start , Duration duration ) {
this.start = start;
this.duration = duration;
}
// Might add some getter/setter methods in here.
// Dynamically determine the start and stop points of this appointment, given today’s definition of the intended time zone.
public Interval toInterval ( ZoneId zoneId ) {
ZonedDateTime zdtStart = this.start.atZone( zoneId );
Interval interval = Interval.of( zdtStart.toInstant() , this.duration );
return interval;
}
}
When you generate a Interval by calling your toInterval method, you may want the individual start and stop moments.
Instant start = interval.getStart() ;
Instant stop = interval.getEnd() ;
Those two Instant objects are in UTC by definition. If you want to see them through the lens of the wall-clock time used by the people of a particular region, apply a ZoneId to get a ZonedDateTime object.
ZoneId zAuckland = ZoneId.of( "Pacific/Auckland" ) ;
ZonedDateTime zdtStart = start.atZone( z ) ; // Adjust from UTC to some time zone. Same moment, same point on the timeline, different wall-clock time.
ZonedDateTime zdtStop = stop.atZone( z ) ;
Future
You asked about checking to see if this appointment is in the future. Again, we need a time zone to properly answer than. The time zones around the world currently cover a range of about 26 to 27 hours. So within in that many hours of the current moment we cannot tell if a LocalDateTime is in the future or past without considering a time zone.
So let's add a method testing for the future that requires passing a time zone.
// Dynamically determine if this appointment will be in the future for some specific time zone.
public Boolean isFuture ( ZoneId zoneId ) {
Objects.requireNonNull( zoneId , "Must pass a time zone to determine if an appointment is in the future. Message # e1c64bc1-9a44-4d15-b20d-e68414fb5ab5.");
ZonedDateTime zdtStart = this.start.atZone( zoneId );
ZonedDateTime zdtNow = ZonedDateTime.now( zoneId );
boolean isInTheFuture = zdtNow.isBefore( zdtStart );
return isInTheFuture ;
}
Start/Stop moments
Continuing the same theme on dynamically determining moments, let's add some methods to return the starting moment (inclusive) and stopping moment (exclusive). As discussed above, this requires passing a time zone.
The calling programmer could do this work herself. But I suspect this might be frequently needed enough to warrant adding these methods as a convenience.
// Get start moment for a particular time zone.
public ZonedDateTime toStartMoment ( ZoneId zoneId ) {
ZonedDateTime zdt = this.toInterval( zoneId ).getStart().atZone( zoneId );
return zdt;
}
// Get stop moment for a particular time zone.
public ZonedDateTime toStopMoment ( ZoneId zoneId ) {
ZonedDateTime zdt = this.toInterval( zoneId ).getEnd().atZone( zoneId );
return zdt;
}
Notice that I did not name these methods with the get…. Accessor methods, getters & setters, by convention imply accessing a simple property stored within the object. But here we are not storing the ZonedDateTime objects. These are dynamically-determined, so using a get… method could be misleading. Instead, I am trying to follow the naming conventions laid down in the java.time project.
Immutable objects
Another lesson to learn from the java.time project is the immutable objects pattern.
Certain kinds of classes lend themselves to being read-only, created but not modified. The java.time classes certainly qualify. Whereas an invoice, for example, is expected to “mutate” (change), intuitively as a programmer I do not expect the date on the invoice to change unless I explicitly replace the date with a new object. So I want invoice to be a mutable object, but I want the LocalDate object stored on that invoice to be immutable.
I suspect our Appointment class might also be best designed an an immutable. So we have no setter methods involved. To effectively alter an appointment in your scheduling app, create a new Appointment object based on some of the values of the existing Appointment object. Notice in the java.time classes how this is done with various with methods, where the methods return a new object based on the original’s values but with some alteration.
Appointment.java version 2
Let's put all that together into one example class.
And let's add a main method to exercise this class. First we create one appointment, and look at its dynamically-determined moments in UTC. Second, we collect some Appointment objects in a collection.
We have added a toString method override to report on the object’s state.
package com.basilbourque.example;
import org.threeten.extra.Interval;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
// An example class to show date-time handling for future appointments.
// Not necessarily ready for production use. Use at your own risk.
// Methods named according to the java.time naming conventions:
// https://docs.oracle.com/javase/tutorial/datetime/overview/naming.html
public class Appointment {
private LocalDateTime start;
private Duration duration;
// Constructor.
public Appointment ( LocalDateTime start , Duration duration ) {
this.start = start;
this.duration = duration;
}
// Dynamically determine the start and stop points of this appointment, given today’s definition of the intended time zone.
public Interval toInterval ( ZoneId zoneId ) {
Objects.requireNonNull( zoneId , "Must pass a time zone to get the start/stop interval of an appointment. Message # bbf021e6-baa7-468d-83ad-cf73acb6702e." );
ZonedDateTime zdtStart = this.start.atZone( zoneId );
Interval interval = Interval.of( zdtStart.toInstant() , this.duration );
return interval;
}
// Get start moment for a particular time zone.
public ZonedDateTime toStartMoment ( ZoneId zoneId ) {
ZonedDateTime zdt = this.toInterval( zoneId ).getStart().atZone( zoneId );
return zdt;
}
// Get stop moment for a particular time zone.
public ZonedDateTime toStopMoment ( ZoneId zoneId ) {
ZonedDateTime zdt = this.toInterval( zoneId ).getEnd().atZone( zoneId );
return zdt;
}
// Dynamically determine if this appointment will be in the future for some specific time zone.
public Boolean isFuture ( ZoneId zoneId ) {
Objects.requireNonNull( zoneId , "Must pass a time zone to determine if an appointment is in the future. Message # e1c64bc1-9a44-4d15-b20d-e68414fb5ab5." );
ZonedDateTime zdtStart = this.start.atZone( zoneId );
ZonedDateTime zdtNow = ZonedDateTime.now( zoneId );
boolean isInTheFuture = zdtNow.isBefore( zdtStart );
return isInTheFuture;
}
// -----------| Object overrides |---------------------------
#Override
public String toString ( ) {
return "Appointment{ " +
"start=" + start +
" | duration=" + duration +
" }";
}
// -----------| main |-------------
public static void main ( String[] args ) {
// See if a new appointment is in the future.
Appointment a = new Appointment( LocalDateTime.of( 2018 , 12 , 25 , 0 , 0 , 0 , 0 ) , Duration.ofHours( 2 ) );
ZoneId z = ZoneId.of( "America/Montreal" );
System.out.println( "For time zone: " + z + ", appointment interval is: " + a.toInterval( z ) );
System.out.println( "Start: " + a.toStartMoment( z ) );
System.out.println( "Stop: " + a.toStopMoment( z ) );
Boolean isFuture = a.isFuture( z );
System.out.println( a.toString() + " is future t/f: " + isFuture );
// Collect some appointments.
List < Appointment > list = new ArrayList <>( 3 );
list.add( a );
list.add( new Appointment( LocalDateTime.of( 2018 , 12 , 13 , 15 , 0 , 0 , 0 ) , Duration.ofMinutes( 90 ) ) );
list.add( new Appointment( LocalDateTime.of( 2018 , 12 , 30 , 16 , 0 , 0 , 0 ) , Duration.ofHours( 1 ) ) );
System.out.println( list );
}
}
When run.
For time zone: America/Montreal, appointment interval is: 2018-12-25T05:00:00Z/2018-12-25T07:00:00Z
Start: 2018-12-25T00:00-05:00[America/Montreal]
Stop: 2018-12-25T02:00-05:00[America/Montreal]
Appointment{ start=2018-12-25T00:00 | duration=PT2H } is future t/f: true
[Appointment{ start=2018-12-25T00:00 | duration=PT2H }, Appointment{ start=2018-12-13T15:00 | duration=PT1H30M }, Appointment{ start=2018-12-30T16:00 | duration=PT1H }]
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.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
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.
Where to obtain the java.time classes?
Java SE 8, Java SE 9, Java SE 10, Java SE 11, and later - Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and Java SE 7
Most of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
Later versions of Android bundle implementations of the java.time classes.
For earlier Android (<26), the ThreeTenABP project adapts ThreeTen-Backport (mentioned above). See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
I'd like to have a random millisecond value from an (inverse) linear distribution of values (if I get the term right).
In essence I want to have a random point-in-time t (Date in my case) between two time points early and late where a t towards early has a much greater probability then those towards late. late itself may have a probability of 0.0.
My current java code just uses uniform distribution, so I plan to modify this to a (inverse) linear distribution:
public Date getRandomDate(Date early, Date late) {
long diff = late.getTime() - early.getTime();
final int randVal = rand.nextInt((int) diff);
Calendar cal = Calendar.getInstance();
cal.setTime(early);
cal.add(Calendar.MILLISECOND, randVal);
return cal.getTime();
}
Piggy-backing off of this answer to a similar question, you could just take the minimum of two rand calls:
final int randVal = Math.min(rand.nextInt((int) diff), rand.nextInt((int) diff));
Finally, here is another more complex way that solves for x using the cumulative distribution function (x^2):
int randVal = (int) Math.floor(diff * (1.0 - Math.sqrt(rand.nextDouble())));
if(randVal >= diff) randVal = 0; // handle the edge case
To meet your specified requirements, the square root has been subtracted from 1.0 to invert the distribution, i.e. putting the greater density at the bottom of the range.
The accepted Answer by Parker seems to be correct and well-done.
Using java.time
The Question uses outmoded troublesome date-time classes that are now legacy, supplanted by the java.time classes. Here is the same kind of code, along with Parker’s solution, rewritten in java.time.
Instant
First, if you must work with java.util.Date objects, convert to/from Instant. The Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds (up to nine (9) digits of a decimal fraction). To convert, look to new methods added to the old classes.
Instant instant = myJavaUtilDate.toInstant(); // From legacy to modern class.
java.util.Date myJavaUtilDate = java.util.Date.from( instant ) ; // From modern class to legacy.
Let's rewrite the method signature but passing and returning Instant objects.
public Instant getRandomDate( Instant early , Instant late) {
Verify the early argument is indeed earlier than the later argument. Alternatively, assert that Duration seen below is not negative ( ! duration.isNegative() ).
if( early.isAfter( late) ) { … } // Assert `early` is not after `late`.
Half-Open
Calculate the delta between the earliest and latest moments. This is done in the Half-Open approach often used to define spans of time, where the beginning is inclusive and the ending is exclusive.
Duration
The Duration class represents such a span in terms of a total number of seconds plus a fractional second in nanoseconds.
Duration duration = Duration.between( early , late ) ;
To do our random math, we want a single integer. To handle nanoseconds resolution, we need a 64-bit long rather than a 32-bit int.
ThreadLocalRandom
Tip: If generating these values across threads, use the class ThreadLocalRandom. To quote the doc:
When applicable, use of ThreadLocalRandom rather than shared Random objects in concurrent programs will typically encounter much less overhead and contention.
We can specify the range in Half-Opened style with the origin being inclusive and the bound being exclusive by calling ThreadLocalRandom::nextLong( origin , bound ).
long bound = duration.toNanos() ;
long nanos1 = ThreadLocalRandom.current().nextLong( 0 , bound );
long nanos2 = ThreadLocalRandom.current().nextLong( 0 , bound );
long nanos = Math.min( nanos1 , nanos2 ); // Select the lesser number.
Instant instant = early.plusNanos( nanos );
return instant ;
}
Live example
See the code below run live at IdeOne.com.
We extract the number of date-time values generated for each date-only (LocalDate) as a casual way to survey the results to verify our desired results skewed towards earlier dates.
The test harness shows you how to assign a time zone (ZoneId) to an Instant to get a ZonedDateTime object, and from there extract a LocalDate. Use that as a guide if you wish to view the Instant objects through the lens of some particular region’s wall-clock time rather than in UTC.
/* package whatever; // don't place package name! */
import java.util.*;
import java.lang.*;
import java.io.*;
import java.util.concurrent.ThreadLocalRandom ;
import java.util.TreeMap ;
import java.time.*;
import java.time.format.*;
import java.time.temporal.*;
/* Name of the class has to be "Main" only if the class is public. */
class Ideone
{
public static void main (String[] args) throws java.lang.Exception
{
Ideone app = new Ideone();
app.doIt();
}
public void doIt() {
ZoneId z = ZoneId.of( "America/Montreal" ) ;
int count = 10 ;
LocalDate today = LocalDate.now( z );
LocalDate laterDate = today.plusDays( count );
Instant start = today.atStartOfDay( z ).toInstant();
Instant stop = laterDate.atStartOfDay( z ).toInstant();
// Collect the frequency of each date. We want to see bias towards earlier dates.
List<LocalDate> dates = new ArrayList<>( count );
Map<LocalDate , Integer > map = new TreeMap<LocalDate , Integer >();
for( int i = 0 ; i <= count ; i ++ ) {
LocalDate localDate = today.plusDays( i ) ;
dates.add( localDate ); // Increment to next date and remember.
map.put( localDate , new Integer( 0 ) ); // Prepopulate the map with all dates.
}
for( int i = 1 ; i <= 10_000 ; i ++ ) {
Instant instant = this.getRandomInstantBetween( start , stop );
LocalDate localDate = instant.atZone( z ).toLocalDate();
Integer integer = map.get( localDate );
map.put( localDate , integer + 1); // Increment to count each time get a hit on this date.
}
System.out.println( map );
}
public Instant getRandomInstantBetween( Instant early , Instant late) {
Duration duration = Duration.between( early , late ) ;
// Assert the duration is positive or zero: ( ! duration.isNegative() )
long bound = duration.toNanos() ;
ThreadLocalRandom random = ThreadLocalRandom.current() ;
long nanos1 = random.nextLong( 0 , bound ); // Zero means the `early` date is inclusive, while `bound` here is exclusive.
long nanos2 = random.nextLong( 0 , bound );
long nanos = Math.min( nanos1 , nanos2 ); // Select the lesser number.
Instant instant = early.plusNanos( nanos );
return instant;
}
}
Here are some sample results. These look good to me, but I'm no statistician. Use at your own risk.
{2017-02-24=1853, 2017-02-25=1697, 2017-02-26=1548, 2017-02-27=1255, 2017-02-28=1130, 2017-03-01=926, 2017-03-02=706, 2017-03-03=485, 2017-03-04=299, 2017-03-05=101, 2017-03-06=0}
{2017-02-25=930, 2017-02-26=799, 2017-02-27=760, 2017-02-28=657, 2017-03-01=589, 2017-03-02=470, 2017-03-03=342, 2017-03-04=241, 2017-03-05=163, 2017-03-06=49, 2017-03-07=0}
{2017-02-25=878, 2017-02-26=875, 2017-02-27=786, 2017-02-28=676, 2017-03-01=558, 2017-03-02=440, 2017-03-03=370, 2017-03-04=236, 2017-03-05=140, 2017-03-06=41, 2017-03-07=0}
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.
The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
Where to obtain the java.time classes?
Java SE 8 and SE 9 and later
Built-in.
Part of the standard Java API with a bundled implementation.
Java 9 adds some minor features and fixes.
Java SE 6 and SE 7
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
Android
The ThreeTenABP project adapts ThreeTen-Backport (mentioned above) for Android specifically.
See How to use ThreeTenABP….
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, andfz more.
Perhaps you could apply analogy to Date as shown in this answer.
Java: random integer with non-uniform distribution
I want a custom calendar like this:
enum TradingDays {Monday, Tuesday, Wednesday, Thursday, Friday};
Then I need to iterate over it and check if a particular enum element is the day of week TODAY. The problem is that the JAVA calendar does not match to days of week from my calendar. So:
Calendar now = Calendar.getInstance();
TradingDays.Monday is not equal to any of now.get(Calendar.DAY_OF_WEEK);
So how do I assign Monday, Tuesday etc from my calendar TradingDays the same type (in this case an integer value) from the JAVA calendar?
P.S. I need to have that calendar TradingDays like that because it is then shown to the user so he/she chooses on which days to trade.
tl;dr
Set<DayOfWeek> tradingDays = EnumSet.range( DayOfWeek.MONDAY , DayOfWeek.FRIDAY ) ; // Mon, Tue, Wed, Thu, & Fri.
Boolean todayIsTradingDay = tradingDays.contains( LocalDate.now( ZoneId.of( "America/Montreal" ) ).getDayOfWeek() ) ;
Details
This functionality is built into Java.
java.time.DayOfWeek enum
Java includes the java.time.DayOfWeek enum.
Pass around objects of this enum rather than mere integers 1-7 to make your code more self-documenting, provide type-safety, and ensure a range of valid values.
invoices.printReportForDayOfWeek( DayOfWeek.MONDAY );
Numbering is 1-7 for Monday to Sunday, per ISO 8601 standard.
Get a localized name of the day by calling getDisplayName.
String output = DayOfWeek.MONDAY.getDisplayName( TextStyle.FULL , Locale.CANADA_FRENCH ) ; // Or Locale.US, etc.
Collection of day-of-week objects
To track multiple days of the week, use an EnumSet (implementation of Set) or EnumMap (implementation of Map).
Set<DayOfWeek> weekend = EnumSet.of( DayOfWeek.SATURDAY , DayOfWeek.SUNDAY ) ;
…
DayOfWeek dowToday =
LocalDate
.now( ZoneId.of( "America/Montreal" ) )
.getDayOfWeek() ;
Boolean todayIsWeekend = weekend.contains( dowToday ) ;
Or in the case of this Question specifically, a collection of the weekdays. Perhaps define as a static final constant if the definition does not change during execution of the app.
static final Set<DayOfWeek> tradingDays = EnumSet.of( DayOfWeek.MONDAY , DayOfWeek.TUESDAY , DayOfWeek.WEDNESDAY , DayOfWeek.THURSDAY , DayOfWeek.FRIDAY ) ;
Or make that even shorter by defining an EnumSet as a range of enum objects defined in a sequential order. Specify MONDAY & FRIDAY and let EnumSet fill in the values in between. Use EnumSet.range.
static final Set<DayOfWeek> tradingDays = EnumSet.range( DayOfWeek.MONDAY , DayOfWeek.FRIDAY ) ; // Mon, Tue, Wed, Thu, & Fri.
Then test for today. Note that a time zone is crucial in determining the current date. For any given moment the date varies around the globe by zone.
ZoneId zoneId = ZoneId.of( "America/Montreal" ) ;
LocalDate today = LocalDate.now( zoneId ) ;
DayOfWeek dowToday = today.getDayOfWeek() ;
Boolean todayIsTradingDay = tradingDays.contains( dowToday ).getDayOfWeek() ) ;
About java.time
The java.time framework is built into Java 8 and later. These classes supplant the old troublesome date-time classes such as java.util.Date, .Calendar, & java.text.SimpleDateFormat.
The Joda-Time project, now in maintenance mode, advises migration to java.time.
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations.
Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport and further adapted to Android in ThreeTenABP.
The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.
You can try using a constructor inside your enum, like in this example:
public enum Currency {
PENNY(1),
NICKLE(5),
DIME(10),
QUARTER(25);
private final int value;
private Currency(int value) {
this.value=value;
}
}
While iterating you can use coin.value like this:
for(Currency coin: Currency.values()){
System.out.println(coin+" "+coin.value);
if(coin.value==1){
System.out.println("THIS is the PENNY");
}
}
Which output is:
PENNY 1
THIS is the PENNY
NICKLE 5
DIME 10
QUARTER 25
With enums you can do a lot of things that probably are familiar to you:
File TradingDays.java:
public enum TradingDays {
Monday(Calendar.MONDAY),
Tuesday(Calendar.TUESDAY),
Wednesday(Calendar.WEDNESDAY),
Thursday(Calendar.THURSDAY),
Friday(Calendar.FRIDAY);
private int calendarValue;
TradingDays(int calendarValue) {
this.calendarValue = calendarValue;
}
public static TradingDays today() {
return fromCalendarValue(Calendar.getInstance().get(Calendar.DAY_OF_WEEK));
}
public static TradingDays fromCalendarValue(int calendarValue) {
for(TradingDays td : TradingDays.values()) {
if(td.calendarValue == calendarValue) {
return td;
}
}
throw new IllegalArgumentException(calendarValue + " is not a valid TradingDays calendarValue");
// or simply return null
}
};
Create a constructor inside your enum which takes an int value:
public class CalendarMain {
enum TradingDays {
Monday(2), Tuesday(3), Wednesday(4), Thursday(5), Friday(6);
private int value;
private TradingDays(int value) {
this.value = value;
}
};
public static void main(String[] args) {
Calendar now = Calendar.getInstance();
if (now.get(Calendar.DAY_OF_WEEK) == TradingDays.Tuesday.value) {
// Chose Wednesday as the day to trade
System.out.println(now.get(Calendar.DAY_OF_WEEK));
System.out.println(TradingDays.Tuesday.value);
}
}
}
I have a list of Date objects, and a target Date. I want to find the date in the list that's nearest to the target date, but only dates that are before the target date.
Example:
2008-10-1
2008-10-2
2008-10-4
With a target date of 2008-10-3, I want to get 2008-10-2
What is the best way to do it?
Sietse de Kaper solution assumes a reverse sorted list, definitely not the most natural thing to have around
The natural sort order in java is following the ascending natural ordering. (see Collection.sort http://java.sun.com/j2se/1.5.0/docs/api/java/util/Collections.html#sort(java.util.List) documentation)
From your example,
target date = 2008-10-03
list = 2008-10-01 2008-10-02 2008-10-04
If another developper uses your method with a naive approach he would get 2008-10-01 which is not what was expected
Don't make assumptions as to the ordering of the list.
If you have to for performance reasons try to follow the most natural convention (sorted ascending)
If you really have to follow another convention you really should document the hell out of it.
private Date getDateNearest(List<Date> dates, Date targetDate){
Date returnDate = targetDate
for (Date date : dates) {
// if the current iteration'sdate is "before" the target date
if (date.compareTo(targetDate) <= 0) {
// if the current iteration's date is "after" the current return date
if (date.compareTo(returnDate) > 0){
returnDate=date;
}
}
}
return returnDate;
}
edit - I also like the Treeset answer but I think it might be slightly slower as it is equivalent to sorting the data then looking it up => nlog(n) for sorting and then the documentation implies it is log(n) for access so that would be nlog(n)+log(n) vs n
private Date getDateNearest(List<Date> dates, Date targetDate){
return new TreeSet<Date>(dates).lower(targetDate);
}
Doesn't require a pre-sorted list, TreeSort fixes that. It'll return null if it can't find one though, so you will have to modify it if that's a problem. Not sure of the efficency either :P
I currently use the following method, but I'm not sure it's the most effective one, because this assumes an already sorted list, and (potentially) iterates over every single date in the list.
private Date getDateNearest(List<Date> dates, Date targetDate){
for (Date date : dates) {
if (date.compareTo(targetDate) <= 0) return date;
}
return targetDate;
}
Although the answer from Keeg is valid in 1.6 in 1.5 there is no method lower() (We're unfortunate to develop against 1.5 :-( )
this one works in 1.5
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.TreeSet;
public class GetNearestDate {
public static void main( String[] args ) throws ParseException {
final SimpleDateFormat simpleDateFormat = new SimpleDateFormat( "dd.MM.yyyy HH:mm:ss" );
List< Date > otherDates = Arrays.asList( new Date[]{
simpleDateFormat.parse( "01.01.2008 01:00:00" ) ,
simpleDateFormat.parse( "01.01.2008 01:00:02" ) } );
System.out.println( simpleDateFormat.parse( "01.01.2008 01:00:00" ).equals(
get( otherDates , simpleDateFormat.parse( "01.01.2008 01:00:01" ) ) ) );
System.out.println( simpleDateFormat.parse( "01.01.2008 01:00:02" ).equals(
get( otherDates , simpleDateFormat.parse( "01.01.2008 01:00:03" ) ) ) );
System.out.println( null == get( otherDates , simpleDateFormat.parse( "01.01.2008 01:00:00" ) ) );
}
public static Date get( List< Date > otherDates , Date dateToApproach ) {
final TreeSet< Date > set = new TreeSet< Date >( otherDates );
set.add( dateToApproach );
final ArrayList< Date > list = new ArrayList< Date >( set );
final int indexOf = list.indexOf( dateToApproach );
if ( indexOf == 0 )
return null;
return list.get( indexOf - 1 );
}
}
NavigableSet::lower
The Answer by Keeg is cleverly brief. The idea there is to make use of the lower method defined in the NavigableSet interface and implemented in the TreeSet class.
But like the other answers it uses the old outmoded date-time classes bundled with the earliest versions of Java. Below is an updated version using java.time classes.
The old question and answers are using either java.util.Date which is a moment on the timeline in UTC representing both a date and a time-of-day, or the java.sql.Date which awkwardly extends util.Date while pretending it does not have a time-of-day. A confusing mess.
java.time
Those troublesome old classes have been supplanted by the java.time classes built into Java 8 and later. See Oracle Tutorial. Much of the functionality has been back-ported to Java 6 & 7 in ThreeTen-Backport and further adapted to Android in ThreeTenABP.
LocalDate
The LocalDate class represents a date-only value without time-of-day and without time zone. While these objects store no time zone, note that time zone (ZoneId) is crucial in determining the current date. For any given moment the date varies around the globe by time zone.
ZoneId zoneId = ZoneId.of( "America/Montreal" );
LocalDate today = LocalDate.now( zoneId ); // 2016-06-25
ISO 8601
Tip: Pad those month and day-of-month numbers with a leading zero. This makes them comply with the ISO 8601 standard date-time formats. These formats are used by default in java.time when parsing/generating strings that represent date-time values.
So use 2008-10-01 rather than 2008-10-1. If padding is not feasible, parse using DateTimeFormatter.
NavigableSet dates = new TreeSet( 3 );
dates.add( LocalDate.parse( "2008-10-01" );
dates.add( LocalDate.parse( "2008-10-02" );
dates.add( LocalDate.parse( "2008-10-04" );
LocalDate target = LocalDate.parse( "2008-10-03" );
LocalDate hit = dates.lower( target );
// Reminder: test for `null == hit` to see if anything found.
Have you looked at the JodaTime API? I seem to recall a feature like this being available.