tl;dr
ZoneId z = ZoneId.of( "America/Montreal" );
LocalDate today = LocalDate.now( z );
ZonedDateTime start = today.with( TemporalAdjusters.firstDayOfMonth() )
.atStartOfDay( z ) ;
ZonedDateTime stop = today.with( TemporalAdjusters.firstDayOfNextMonth() )
.atStartOfDay( z ) ;
- Determining a date requires a time zone.
- Better to specify explicitly than rely implicitly on JVM’s current default time zone.
Avoid legacy classes
You are using troublesome old date-time classes, now legacy, supplanted by the java.time classes.
Using java.time
This work is much easier with the java.time classes.
LocalDate
The LocalDate
class represents a date-only value without time-of-day and without time zone.
Time zone
A time zone is crucial in determining a date. For any given moment, the date varies around the globe by zone. For example, a few minutes after midnight in Paris France is a new day while still “yesterday” in Montréal Québec.
Specify a proper time zone name in the format of continent/region
, such as America/Montreal
, Africa/Casablanca
, or Pacific/Auckland
. Never use the 3-4 letter abbreviation such as EST
or IST
as they are not true time zones, not standardized, and not even unique(!).
ZoneId z = ZoneId.of( "America/Montreal" );
LocalDate today = LocalDate.now( z );
Always specify the time zone explicitly. If omitted, the JVM’s current default time zone is implicitly applied. This default can be changed at any moment by any code of any app within the JVM. So if crucial, ask the user for the desired/expected time zone. If not crucial, you can ask for the default explicitly to make your intentions clear in your code rather than the ambiguity of relying on the implicit default.
ZoneId z = ZoneId.systemDefault(); // Get JVM’s current default time zone.
LocalDate today = LocalDate.now( z );
TemporalAdjuster
The TemporalAdjuster
interface provides for classes that can adjust date-time values. The TemporalAdjusters
class provides several handy implementations.
LocalDate firstOfThisMonth = today.with( TemporalAdjusters.firstDayOfMonth() );
ZonedDateTime
To turn that date-only into a date-with-time-of-day, apply a time zone ZoneId
to get a ZonedDateTime
object.
Do not assume the first moment of the day is 00:00:00. Because of anomalies such as Daylight Saving Time (DST), the first moment might be something like the time 01:00:00. Let java.time figure this out by calling atStartOfDay
.
ZonedDateTime zdtStartOfMonth = firstOfThisMonth.atStartOfDay( z );
Half-Open
You are taking the wrong tack by trying to determine the last moment of the month. That last moment has an infinitely divisible fractional second. Trying to resolve to a particular granularity is ill-advised as different systems use different granularities. Old Java date-time classes use milliseconds, some databases such as Postgres use microseconds, the java.time classes use nanoseconds, and other systems use still other variations.
The wiser approach commonly used in date-time work for defining spans of time is Half-Open, where the beginning is inclusive while the ending is exclusive. This means a month begins at the first moment of the day of the first of the month and runs up to, but not including, the first moment of the first of the following month.
LocalDate firstOfNextMonth = today.with( TemporalAdjusters.firstOfNextMonth() );
Adjust into a time zone to get a specific moment.
ZonedDateTime zdtStartOfNextMonth = firstOfNextMonth.atStartOfDay( z );
The logic for comparing a moment to this span of time is “Is this moment (a) equal to or after the beginning, and (b) less than the ending?”. Notice the lack of "or is equal" in part 'b'. That means we are running up to, but not including, the ending.
Also, a shorter way of saying part 'a' is “not before the beginning”. So we can ask more simply, “Is this moment not before the beginning AND is before the ending?”.
ZonedDateTime moment = ZonedDateTime.now( z );
Boolean spanContainsMoment = ( ! moment.isBefore( zdtStartOfMonth ) ) && ( moment.isBefore( zdtStartOfNextMonth ) ) ;
By the way, the standard ISO 8601 format for formatting a textual representation of a span of time uses a slash character to join the beginning and ending.
String output = zdtStartOfMonth.toString() + "/" + zdtStartOfNextMonth.toString() ;
Interval
You can represent this span of time using the Interval
class in the ThreeTen-Extra library. That class tracks the beginning and ending in UTC as Instant
objects. You can extract an Instant
from a ZonedDateTime
object.
Interval interval = Interval.of( zdtStartOfMonth.toInstant() , zdtStartOfNextMonth.toInstant() );
YearMonth
By the way, you may find the YearMonth
class useful in your work.
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?
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.