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 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.