Reader small image

You're reading from  Java Coding Problems - Second Edition

Product typeBook
Published inMar 2024
PublisherPackt
ISBN-139781837633944
Edition2nd Edition
Right arrow
Author (1)
Anghel Leonard
Anghel Leonard
author image
Anghel Leonard

Anghel Leonard is a Chief Technology Strategist and independent consultant with 20+ years of experience in the Java ecosystem. In daily work, he is focused on architecting and developing Java distributed applications that empower robust architectures, clean code, and high-performance. Also passionate about coaching, mentoring and technical leadership. He is the author of several books, videos and dozens of articles related to Java technologies.
Read more about Anghel Leonard

Right arrow

Working with Date and Time

This chapter includes 20 problems covering different date-time topics. These problems are mainly focused on the Calendar API and on the JDK Date/Time API. About the latter, we will cover some less popular APIs such as ChronoUnit, ChronoField, IsoFields, TemporalAdjusters, and so on.

At the end of this chapter, you’ll have a ton of tips and tricks in your tool belt that will be very useful for solving a wide range of real-world date-time problems.

Problems

Use the following problems to test your programming prowess on date and time. I strongly encourage you to give each problem a try before you turn to the solutions and download the example programs:

  1. Defining a day period: 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.
  2. Converting between Date and YearMonth: Write an application that converts between java.util.Date and java.time.YearMonth and vice versa.
  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.
  4. Converting week/year to Date: Let’s consider that two integers are given representing a week and a year (for instance, week 10, year 2023). Write a program that converts 10...

68. Defining a day period

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, date is a new Date() and zoneId...

69. Converting between Date and YearMonth

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?

70. Converting between int and 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...

71. Converting week/year to Date

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

72. Checking for a leap year

This problem becomes easy as long as we know what a leap year is. In a nutshell, a leap year is any year divisible by 4 (so, year % 4 == 0) that it is not a century (for instance, 100, 200, …, n00). However, if the year represents a century that is divisible by 400 (so, year % 400 == 0), then it is a leap year. In this context, our code is just a simple chain of if statements as follows:

public static boolean isLeapYear(int year) {
  if (year % 4 != 0) {
    return false;
  } else if (year % 400 == 0) {
    return true;
  } else if (year % 100 == 0) {
    return false;
  }
  return true;
}

But, this code can be condensed using the GregorianCalendar as well:

public static boolean isLeapYear(int year) {
  return new GregorianCalendar(year, 1, 1).isLeapYear(year);
}

Or, starting with JDK 8, we can rely on the java.time.Year API as follows:

public static boolean isLeapYear(int year) {
  return Year.of(year).isLeap(); 
}

In...

73. Calculating the quarter of a given date

A year has 4 quarters (commonly denoted as Q1, Q2, Q3, and Q4) and each quarter has 3 months. If we consider that January is 0, February is 1, …, and December is 11, then we can observe that January/3 = 0, February/3 =0, March/3 = 0, and 0 can represent Q1. Next, 3/3 = 1, 4/3 = 1, 5/3 = 1, so 1 can represent Q2. Based on the same logic, 6/3 = 2, 7/3 = 2, 8/3 = 2, so 2 can represent Q3. Finally, 9/3 = 3, 10/3 = 3, 11/3 = 3, so 3 represents Q4.

Based on this statement and the Calendar API, we can obtain the following code:

public static String quarter(Date date) {
  String[] quarters = {"Q1", "Q2", "Q3", "Q4"};
  Calendar calendar = Calendar.getInstance();
  calendar.setTime(date);
  int quarter = calendar.get(Calendar.MONTH) / 3;
  return quarters[quarter];
}

But, starting with JDK 8, we can rely on java.time.temporal.IsoFields. This class contains fields (and units) that follow...

74. Getting the first and last day of a quarter

Let’s assume that we represent the first and last day of a quarter via this simple class:

public final class Quarter {
  private final Date firstDay;
  private final Date lastDay;
  ...
}

Next, we have a java.util.Date and we want the first and the last day of the quarter containing this date. For this, we can use JDK 8’s IsoFields.DAY_OF_QUARTER (we introduced IsoFields in the previous problem). But, before we can use IsoFields, we have to convert the given java.util.Date to a LocalDate as follows:

LocalDate localDate = date.toInstant()
  .atZone(ZoneId.systemDefault()).toLocalDate();

Once we have the given Date as a LocalDate, we can easily extract the first day of the quarter via IsoFields.DAY_OF_QUARTER. Next, we add 2 months to this day to move into the last month of the quarter (a quarter has 3 months, so a year has 4 quarters) and we rely on java.time.temporal.TemporalAdjusters, more precisely on...

75. Extracting the months from a given quarter

This problem becomes quite easy to solve if we are familiar with JDK 8’s java.time.Month. Via this API, we can find the first month (0 for January, 1 for February, …) of a quarter containing the given LocalDate as Month.from(LocalDate).firstMonthOfQuarter().getValue().

Once we have the first month, it is easy to obtain the other two as follows:

public static List<String> quarterMonths(LocalDate ld) {
  List<String> qmonths = new ArrayList<>();
  int qmonth = Month.from(ld)
    .firstMonthOfQuarter().getValue();
  qmonths.add(Month.of(qmonth).name());
  qmonths.add(Month.of(++qmonth).name());
  qmonths.add(Month.of(++qmonth).name());
  return qmonths;
}

How about passing the quarter itself as an argument? This can be done as a number (1, 2, 3, or 4) or as a string (Q1, Q2, Q3, or Q4). If the given quarter is a number, then the first month of the quarter can be obtained as quarter * 3 –...

76. Computing pregnancy due date

Let’s start with these two constants:

public static final int PREGNANCY_WEEKS = 40;
public static final int PREGNANCY_DAYS = PREGNANCY_WEEKS * 7;

Let’s consider the first day as a LocalDate and we want to write a calculator that prints the pregnancy due date, the number of remaining days, the number of passed days, and the current week.

Basically, the pregnancy due date is obtained by adding the PREGNANCY_DAYS to the given first day. Further, the number of remaining days is the difference between today and the given first day, while the number of passed days is PREGNANCY_DAYS minus the number of remaining days. Finally, the current week is obtained as the number of passed days divided by 7 (since a week has 7 days). Based on these statements, the code speaks for itself:

public static void pregnancyCalculator(LocalDate firstDay) {
  firstDay = firstDay.plusDays(PREGNANCY_DAYS);
  System.out.println("Due date: "...

77. Implementing a stopwatch

A classical implementation for a stopwatch relies on System.nanoTime(), System.currentTimeMillis(), or on Instant.now(). In all cases, we have to provide support for starting and stopping the stopwatch, and some helpers to obtain the measured time in different time units.

While the solutions based on Instant.now() and currentTimeMillis() are available in the bundled code, here we’ll show the one based on System.nanoTime():

public final class NanoStopwatch {
  private long startTime;
  private long stopTime;
  private boolean running;
  public void start() {
    this.startTime = System.nanoTime();
    this.running = true;
   }
   public void stop() {
    this.stopTime = System.nanoTime();
    this.running = false;
   }
  //elaspsed time in nanoseconds
   public long getElapsedTime() {
     if (running) {
       return System.nanoTime() - startTime;
     } else {
       return stopTime - startTime;
     } 
  }
}

If you need to return...

78. Extracting the count of milliseconds since midnight

So, we have a date-time (let’s say a LocalDateTime or LocalTime) and we want to know how many milliseconds have passed from midnight to this date-time. Let’s consider that the given date-time is right now:

LocalDateTime now = LocalDateTime.now();

Midnight is relative to now, so we can find the difference as follows:

LocalDateTime midnight = LocalDateTime.of(now.getYear(),
  now.getMonth(), now.getDayOfMonth(), 0, 0, 0);

Finally, compute the difference in milliseconds between midnight and now. This can be accomplished in several ways, but probably the most concise solution relies on java.time.temporal.ChronoUnit. This API exposes a set of units useful to manipulate a date, time, or date-time including milliseconds:

System.out.println("Millis: " 
  + ChronoUnit.MILLIS.between(midnight, now));

In the bundled code, you can see more examples of ChronoUnit.

79. Splitting a date-time range into equal intervals

Let’s consider a date-time range (bounded by a start date and an end date represented by two LocalDateTime instances) and an integer n. In order to split the given range into n equal intervals, we start by defining a java.time.Duration as follows:

Duration range = Duration.between(start, end);

Having this date-time range, we can rely on dividedBy() to obtain a copy of it divided by the specified n:

Duration interval = range.dividedBy(n - 1);

Finally, we can begin from the start date (the left head of the range) and repeatedly increment it with the interval value until we reach the end date (the right head of the range). After each step, we store the new date in a list that will be returned at the end. Here is the complete code:

public static List<LocalDateTime> splitInEqualIntervals(
       LocalDateTime start, LocalDateTime end, int n) {
  Duration range = Duration.between(start, end);
  Duration...

80. Explaining the difference between Clock.systemUTC() and Clock.systemDefaultZone()

Let’s start with the following three lines of code:

System.out.println(Clock.systemDefaultZone());
System.out.println(system(ZoneId.systemDefault()));
System.out.println(Clock.systemUTC());

The output reveals that the first two lines are similar. Both of them display the default time zone (in my case, Europe/Bucharest):

SystemClock[Europe/Bucharest]
SystemClock[Europe/Bucharest]

The third line is different. Here, we see Z time zone, which is specific to the UTC time zone and indicates the presence of a zone offset:

SystemClock[Z]

On the other hand, creating an Instant reveals that Clock.systemUTC() and Clock.systemDefaultZone() produce the same result:

System.out.println(Clock.systemDefaultZone().instant());
System.out.println(system(ZoneId.systemDefault()).instant());
System.out.println(Clock.systemUTC().instant());

The instant time is the same in all three...

81. Displaying the names of the days of the week

One of the hidden gems in Java is java.text.DateFormatSymbols. This class is a wrapper for date-time formatting data such as the names of the days of the week, and the names of the months. All these names are localizable.

Typically, you will use DateFormatSymbols via a DateFormat such as SimpleDateFormat, but in order to solve this problem, we can use it directly as in the following code:

String[] weekdays = new DateFormatSymbols().getWeekdays();
IntStream.range(1, weekdays.length)
    .mapToObj(t -> String.format("Day: %d -> %s",
       t, weekdays[t]))
    .forEach(System.out::println);

This code will output the weekdays’ names as follows:

Day: 1 -> Sunday
...
Day: 7 -> Saturday

Challenge yourself to come up with another solution.

82. Getting the first and last day of the year

Getting the first and last day of the given year (as a numeric value) can be done via LocalDate and the handy TemporalAdjusters, firstDayOfYear(), and lastDayOfYear(). First, we create a LocalDate from the given year. Next, we use this LocalDate with firstDayOfYear()/lastDayOfYear() as in the following code:

public static String fetchFirstDayOfYear(int year, boolean name) {
  LocalDate ld = LocalDate.ofYearDay(year, 1);
  LocalDate firstDay = ld.with(firstDayOfYear());
  if (!name) {
    return firstDay.toString();
  }
  return DateTimeFormatter.ofPattern("EEEE").format(firstDay);
}

And, for the last day, the code is almost similar:

public static String fetchLastDayOfYear(int year, boolean name) {
  LocalDate ld = LocalDate.ofYearDay(year, 31);
  LocalDate lastDay = ld.with(lastDayOfYear());
  if (!name) {
    return lastDay.toString();
  }
  return DateTimeFormatter.ofPattern("EEEE").format(lastDay);...

83. Getting the first and last day of the week

Let’s assume given an integer (nrOfWeeks) representing the number of weeks that we want to extract the first and last day of each week starting from now. For instance, for the given nrOfWeeks = 3 and a local date such as 06/02/2023, we want this:

[
Mon 06/02/2023,
Sun 12/02/2023,
Mon 13/02/2023,
Sun 19/02/2023,
Mon 20/02/2023,
Sun 26/02/2023
]

This is much easier than it might seem. We just need a loop from 0 to nrOfWeeks and two TemporalAdjusters to fit the first/last day of each week. More precisely, we need the nextOrSame(DayOfWeek dayOfWeek) and previousOrSame(DayOfWeek dayOfWeek) adjusters.

The nextOrSame() adjuster’s role is to adjust the current date to the first occurrence of the given day of week after the date being adjusted (this can be next or same). On the other hand, the previousOrSame() adjuster’s role is to adjust the current date to the first occurrence of the given day of week before...

84. Calculating the middle of the month

Let’s imagine that we have a LocalDate and we want to calculate from it another LocalDate representing the middle of the month. This can be achieved in seconds if we know that the LocalDate API has a method named lengthOfMonth(), which returns an integer representing the length of the month in days. So, all we have to do is calculate lengthOfMonth()/2 as in the following code:

public static LocalDate middleOfTheMonth(LocalDate date) {
  return LocalDate.of(date.getYear(), date.getMonth(), 
    date.lengthOfMonth() / 2); 
}

In the bundled code, you can see a solution based on the Calendar API.

85. Getting the number of quarters between two dates

This is just another problem that requires us to have a deep grasp of the Java Date/Time API. This time, we talk about java.time.temporal.IsoFields, which was introduced in Problem 73. One of the ISO fields is QUARTER_YEARS, which is a temporal unit representing the concept of a quarter-year. So, having two LocalDate instances, we can write this:

public static long nrOfQuarters(
    LocalDate startDate, LocalDate endDate) {
  return IsoFields.QUARTER_YEARS.between(startDate, endDate);
}

Feel free to challenge yourself to provide a solution for java.util.Date/Calendar.

86. Converting Calendar to LocalDateTime

In Problem 68, you saw that converting a java.util.Date (date) to a LocalTime can be done as follows:

LocalTime lt = date.toInstant().atZone(zoneId).toLocalTime();

In the same manner, we can convert a java.util.Date to a LocalDateTime (here, zoneId was replaced with ZoneId.systemDefault()):

LocalDateTime ldt = date.toInstant().atZone(
  ZoneId.systemDefault()).toLocalDateTime();

We also know that we can obtain a java.util.Date from a Calendar via the getTime() method. So, by gluing the pieces of the puzzle together, we obtain the following code:

public static LocalDateTime
       toLocalDateTime(Calendar calendar) {
  Date date = calendar.getTime();
  return date.toInstant().atZone(
    ZoneId.systemDefault()).toLocalDateTime();
}

The same result but following a shorter path can be obtained like this:

return LocalDateTime.ofInstant(Instant.ofEpochMilli(
  calendar.getTimeInMillis()), ZoneId.systemDefault());
...

87. Getting the number of weeks between two dates

If the given two dates are instances of LocalDate(Time), then we can rely on java.time.temporal.ChronoUnit. This API exposes a set of units useful to manipulate a date, time, or date-time and we have used it before in Problem 78. This time, let’s use it again to compute the number of weeks between two dates:

public static long nrOfWeeks(
    LocalDateTime startLdt, LocalDateTime endLdt) {
  return Math.abs(ChronoUnit.WEEKS.between(
    startLdt, endLdt));
}

On the other hand, if the given dates are java.util.Date, then you can choose to convert them to LocalDateTime and use the previous code or to rely on the Calendar API. Using the Calendar API is about looping from the start date to the end date while incrementing the calendar date week by week:

public static long nrOfWeeks(Date startDate, Date endDate) {
  Calendar calendar = Calendar.getInstance();
  calendar.setTime(startDate);
  int weeks = 0;
  while (calendar...

Summary

Mission accomplished! I hope you enjoyed this short chapter filled to the brim with tips and tricks about manipulating date-time in real-world applications. I strongly encourage you to also read the homologous chapter from Java Coding Problems, First Edition, which contains another 20 problems covering other date-time topics.

Join our community on Discord

Join our community’s Discord space for discussions with the author and other readers:

https://discord.gg/8mgytp5DGQ

lock icon
The rest of the chapter is locked
You have been reading a chapter from
Java Coding Problems - Second Edition
Published in: Mar 2024Publisher: PacktISBN-13: 9781837633944
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
undefined
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $15.99/month. Cancel anytime

Author (1)

author image
Anghel Leonard

Anghel Leonard is a Chief Technology Strategist and independent consultant with 20+ years of experience in the Java ecosystem. In daily work, he is focused on architecting and developing Java distributed applications that empower robust architectures, clean code, and high-performance. Also passionate about coaching, mentoring and technical leadership. He is the author of several books, videos and dozens of articles related to Java technologies.
Read more about Anghel Leonard