Mastering Date/Time APIs: Challenges With Java's Calendar and JDK Date/Time APIs
Test your Java skills with four problems focused on Calendar API and JDK Date/Time APIs, including ChronoUnit, IsoFields, TemporalAdjusters, and more.
Join the DZone community and get the full member experience.
Join For FreeIn this article, we'll address four problems covering different date-time topics. These problems are mainly focused on the Calendar API and on the JDK Date/Time API.
Disclaimer: This article is an abstract from my recent book Java Coding Problems, Second Edition.
Use the following problems to test your programming prowess on date and time. Remember that there usually isn’t a single correct way to solve a particular problem. Also, remember that the explanations shown here include only the most interesting and important details needed to solve the problems.
Download the example solutions to see additional details and to experiment with the programs.
1. Defining a Day Period
Problem: Write an application that goes beyond AM/PM flags and split the day into four periods: night, morning, afternoon, and evening. Depending on the given date-time and time zone generate one of these periods.
Let’s imagine that we want to say hello to a friend from another country (in a different time zone) via a message such as Good morning, Good afternoon, and so on based on their local time. So, having access to AM/PM flags is not enough, because we consider that a day (24 hours) can be represented by the following periods:
• 9:00 PM (or 21:00) – 5:59 AM = night
• 6:00 AM – 11:59 AM = morning
• 12:00 PM – 5:59 PM (or 17:59) = afternoon
• 6:00 PM (or 18:00) – 8:59 PM (or 20:59) = evening
Before JDK 16
First, we have to obtain the time corresponding to our friend’s time zone. For this, we can start from our local time given as a java.util.Date
, java.time.LocalTime
, and so on. If we start from a java. util.Date
, then we can obtain the time in our friend’s time zone as follows:
LocalTime lt = date.toInstant().atZone(zoneId).toLocalTime();
Here, the date is a new Date()
and zoneId
is java.time.ZoneId
. Of course, we can pass the zone ID as a String
and use the ZoneId.of(String zoneId)
method to get the ZoneId
instance.
If we prefer to start from LocalTime.now()
, then we can obtain the time in our friend’s time zone as follows:
LocalTime lt = LocalTime.now(zoneId);
Next, we can define the day periods as a bunch of LocalTime
instances and add some conditions to determine the current period. The following code exemplifies this statement:
public static String toDayPeriod(Date date, ZoneId zoneId) {
LocalTime lt = date.toInstant().atZone(zoneId).toLocalTime();
LocalTime night = LocalTime.of(21, 0, 0);
LocalTime morning = LocalTime.of(6, 0, 0);
LocalTime afternoon = LocalTime.of(12, 0, 0);
LocalTime evening = LocalTime.of(18, 0, 0);
LocalTime almostMidnight = LocalTime.of(23, 59, 59);
LocalTime midnight = LocalTime.of(0, 0, 0);
if((lt.isAfter(night) && lt.isBefore(almostMidnight))
|| lt.isAfter(midnight) && (lt.isBefore(morning))) {
return "night";
} else if(lt.isAfter(morning) && lt.isBefore(afternoon)) {
return "morning";
} else if(lt.isAfter(afternoon) && lt.isBefore(evening)) {
return "afternoon";
} else if(lt.isAfter(evening) && lt.isBefore(night)) {
return "evening";
}
return "day";
}
Now, let’s see how we can do this in JDK 16+.
JDK 16+
Starting with JDK 16+, we can go beyond AM/PM flags via the following strings: in the morning, in the afternoon, in the evening, and at night.
These friendly outputs are available via the new pattern, B. This pattern is available starting with JDK 16+ via DateTimeFormatter
and DateTimeFormatterBuilder
(you can find these APIs in Chapter 1, Problem 18, in my book; see the figure below for reference).
So, the following code uses the DateTimeFormatter
to exemplify the usage of pattern B
, representing a period of the day:
public static String toDayPeriod(Date date, ZoneId zoneId) {
ZonedDateTime zdt = date.toInstant().atZone(zoneId);
DateTimeFormatter formatter
= DateTimeFormatter.ofPattern("yyyy-MMM-dd [B]");
return zdt.withZoneSameInstant(zoneId).format(formatter);
}
Here is an output for Australia/Melbourne:
2023-Feb-04 at night
You can see more examples in the bundled code. Feel free to challenge yourself to adjust this code to reproduce the result from the first example.
2. Converting Between Date and YearMonth
Write an application that converts between java.util.Date
and java.time.YearMonth
and vice versa.
Converting a java.util.Date
to JDK 8 java.time.YearMonth
can be done based on YearMonth
. from(TemporalAccessor temporal)
. A TemporalAccessor
is an interface (more precisely, a framework-level interface) that exposes read-only access to any temporal object including date, time, and offset (a combination of these is also allowed). So, if we convert the given java.util.Date
to java
. time.LocalDate
, then the result of the conversion can be passed to YearMonth.from()
as follows:
public static YearMonth toYearMonth(Date date) {
return YearMonth.from(date.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDate());
}
Vice versa can be obtained via Date.from(Instant instant)
as follows:
public static Date toDate(YearMonth ym) {
return Date.from(ym.atDay(1).atStartOfDay(
ZoneId.systemDefault()).toInstant());
}
Well, that was easy, wasn’t it?
3. Converting Between Int and YearMonth
Let’s consider that a YearMonth
is given (for instance, 2023-02). Convert it to an integer representation (for instance, 24277) that can be converted back to YearMonth
.
Consider that we have YearMonth.now()
and we want to convert it to an integer (for example, this can be useful for storing a year/month date in a database using a numeric field). Check out the solution:
public static int to(YearMonth u) {
return (int) u.getLong(ChronoField.PROLEPTIC_MONTH);
}
The proleptic-month is a java.time.temporal.TemporalField
, which basically represents a date-time field such as month-of-year (our case) or minute-of-hour. The proleptic-month starts from 0 and counts the months sequentially from year 0. So, getLong()
returns the value of the specified field (here, the proleptic-month) from this year-month as a long. We can cast this long
to int
since the proleptic-month shouldn’t go beyond the int
domain (for instance, for 2023/2 the returned int
is 24277).
Vice versa can be accomplished as follows:
public static YearMonth from(int t) {
return YearMonth.of(1970, 1)
.with(ChronoField.PROLEPTIC_MONTH, t);
}
You can start from any year/month. The 1970/1 (known as the epoch and the starting point for java.time.Instant
) choice was just an arbitrary choice.
4. Converting Week/Year to Date
Problem Statement: Consider that two integers are given representing a week and a year (for instance, week 10, year 2023). Write a program that converts 10-2023 to a java.util.Date
via Calendar and to a LocalDate
via the WeekFields
API. Also, do vice versa: from a given Date/LocalDate extract the year and the week as integers.
Solutions: Let’s consider the year 2023, week 10. The corresponding date is Sun Mar 05 15:15:08 EET 2023 (of course, the time component is relative). Converting the year/week to java.util.Date
can be done via the Calendar
API as in the following self-explanatory snippet of code:
public static Date from(int year, int week) {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR, year);
calendar.set(Calendar.WEEK_OF_YEAR, week);
calendar.set(Calendar.DAY_OF_WEEK, 1);
return calendar.getTime();
}
If you prefer to obtain a LocalDate
instead of a Date
then you can easily perform the corresponding conversion or you can rely on java.time.temporal.WeekFields
. This API exposes several fields for working with week-of-year, week-of-month, and day-of-week. This being said, here is the previous solution written via WeekFields
to return a LocalDate
:
public static LocalDate from(int year, int week) {
WeekFields weekFields = WeekFields.of(Locale.getDefault());
return LocalDate.now()
.withYear(year)
.with(weekFields.weekOfYear(), week)
.with(weekFields.dayOfWeek(), 1);
}
On the other hand, if we have a java.util.Date
and we want to extract the year and the week from it, then we can use the Calendar
API. Here, we extract the year:
public static int getYear(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
return calendar.get(Calendar.YEAR);
}
And here, we extract the week:
public static int getWeek(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
return calendar.get(Calendar.WEEK_OF_YEAR);
}
Getting the year and the week from a LocalDate
is easy thanks to ChronoField.YEAR
and ChronoField
. ALIGNED_WEEK_OF_YEAR
:
public static int getYear(LocalDate date) {
return date.get(ChronoField.YEAR);
}
public static int getWeek(LocalDate date) {
return date.get(ChronoField.ALIGNED_WEEK_OF_YEAR);
}
Of course, getting the week can be accomplished via WeekFields as well:
return date.get(WeekFields.of(
Locale.getDefault()).weekOfYear());
Challenge yourself to obtain week/month and day/week from a Date/LocalDate
.
Opinions expressed by DZone contributors are their own.
Comments