I wonder why teachers are still teaching the old API (Date
, Calendar
and SimpleDateFormat
), because they have lots of problems and design issues, and they're being replaced by the new APIs. (Java 8 was released in 2014, btw).
Anyway, if you have a GregorianCalendar
, you can convert it to the new java.time
classes and do the rest with them.
First, you can use the calendar to create an Instant
:
Instant instant = Instant.ofEpochMilli(calendar.getTimeInMillis());
The only problem is that, if you create a Calendar
and set the day, month and year, it will have the current time (hour/minute/seconds), so the Instant
above will have the current time in UTC. If that's ok, you can convert this instant to your timezone:
ZoneId zone = ZoneId.of("America/Sao_Paulo");
ZonedDateTime start = instant.atZone(zone);
I used America/Sao_Paulo
, but you can change to the timezone that makes sense to your system. The API uses IANA timezones names (always in the format Region/City
, like America/Sao_Paulo
or Europe/Berlin
).
Avoid using the 3-letter abbreviations (like CST
or PST
) because they are ambiguous and not standard.
You can get a list of available timezones (and choose the one that fits best your system) by calling ZoneId.getAvailableZoneIds()
. You can also use the system's default if you want (ZoneId.systemDefault()
), but note that this can be changed without notice, even at runtime, so it's always better to specify which timezone you're using. If you want to work with dates in UTC, you can use the built-in constant ZoneOffset.UTC
.
The code above will create a ZonedDateTime
with the calendar's date and time adjusted to the specified timezone. Just reminding that, if you do something like this:
Calendar calendar = new GregorianCalendar();
calendar.set(2017, 7, 12);
The date will be equivalent to August 12th 2017 (because months in the Calendar
API start at zero, so month 7 is August), and the time will be the current time when the calendar is created.
If you want to specify the hour, you have some options to adjust it:
// change the hour/minute/second to 10:20:45
start = start.with(LocalTime.of(10, 20, 45));
// change just the hour to 10
start = start.withHour(10);
// set to start of the day
start = start.toLocalDate().atStartOfDay(zone);
With this, you can change the time (and also date) fields accordingly. Check the javadoc and Oracle's tutorial to see all the options available. The method atStartOfDay
is better because it takes care of Daylight Saving Time changes (depending on DST shift, the day can start at 1AM instead of midnight, and this method takes care of all the details).
If you don't want to rely on Calendar
, you can also create the date directly:
// creating August 12th 2017, at 10:00
start = ZonedDateTime.of(2017, 8, 12, 10, 0, 0, 0, zone);
Note that August is month 8 (one of the best and most obvious improvements from the old API).
Now that you have the starting date, you can loop through a whole year and check the dates according to your rules. I'm using the example of sending the email each 16 days and adjust to next monday if it's a weekend:
ZonedDateTime d = start;
// ends in 1 year - this method already takes care of leap years
ZonedDateTime end = start.plusYears(1);
while (end.isAfter(d)) {
d = d.plusDays(16);
if (d.getDayOfWeek() == DayOfWeek.SUNDAY || d.getDayOfWeek() == DayOfWeek.SATURDAY) {
// weekend, adjust to next monday
d = d.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
}
// send email
}
If you're using Java <= 7, you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes.
The only difference from Java 8 is the package names (in Java 8 is java.time
and in ThreeTen Backport (or Android's ThreeTenABP) is org.threeten.bp
), but the classes and methods names are the same.
As @BasilBourque reminded me in the comments, you can also convert a GregorianCalendar
to a ZonedDateTime
using the toZonedDateTime()
method (this will use the calendar's timezone - usually the system's default, if you don't set it). You can also convert it to an Instant
using the toInstant()
method. The only restriction is that those methods are only available in Java 8 (so, if you're using ThreeTen Backport, just use the way it's described above).