Saturday, April 23, 2022

Java Tutorial: Modules

Chapters

Java Modules

In java 9, java introduces Java modules. Java modules or Java platform module system (JPMS) is akin to Java packages but it's much secure than Java packages. A module is used to manage packages and communicate with another module. These are the benefits that we can get from modules:
  • Freedom to choose which packages to be distributed.
  • Strong encapsulation.
  • Missing classes can be detected early at startup.
These are the cons of modules:
  • No mutual dependency. It means that if a module requires the other module, the other module can't require the module back.
  • Split package not allowed. A split package happens if two modules export the same package, splitting the content of the package between modules.
  • Increased complexity. Unlike creating java packages, creating modules and linking them are more complex.
Java modules are divided into different types:

System Modules. These modules are provided by java. In java 9, java changes its package structure and moves predefined packages in modules. We can check this modules out by typing this command on your terminal or cmd(Windows): java --list-modules

Application Modules. These are user-made modules.

Automatic Modules. These modules contain Unofficial modules content of existing JAR files that don't have module-info.class. module-info.class holds information about how modules operate. This module will be automatically created if an existing JAR file without module-info.class is included in a module path.

Its name will be derived from the name of the JAR file. This module will be granted full read access to every module loaded by the module path. This module is useful when migrating old existing JAR files to modularity.

Unnamed Modules. These are modules that contain JAR files content loaded in classpath instead of module path. This module is useful for maintaining backward compatibility with written code in java 8 and below.

Modules can be distributed via JAR files or "exploded" directory tree corresponding to a package hierarchy. Now, let's discuss how to create a module. First off, we need a module descriptor. This descriptor is a java file named as module-info.java. Module descriptor holds information about how a module operates.

This is the structure of a module descriptor:
module module-name{
  //Directives...
}
module is a keyword, module-name is the name of a module block. Directives are statements that define how packages are used. Module descriptor must be placed at the root of packages. In the tutorial, we're gonna create modules using a terminal or command prompt on windows. For production, it's better to use IDEs to make things more easier.

Take note that this is an introductory tutorial. More information can be found in this article.

exports and requires Directives

exports and requires Keywords are keywords used in directive statements. By default, all packages in a module can't be accessed by other modules. Exports directive allows modules to access named package's public classes and their members and exported packages. Take note that export directive doesn't allow other modules to access non-public members of a class in the named package via reflection, even we invoke setAccessible method of java reflection.
Here's the syntax for exports directive: exports package-name;

requires keyword adds a dependency to a module. requires keyword will look for the named module of requires directive in module graph. Java compiler will complain if the named module can't be found.
Here's the syntax for requires directive: requires module-name;

Now, let's test these directives. First off, create this file structure:
|company.modules
  |resources
    |Consumer.java
  |module-info.java
|main.app
  |main
    |Main.java
  |module-info.java
company.modules and main.app are module packages. As you can see, each module has module-info.java. Also, if a module package has two or more words, It's recommended to separate them with dots(.) not underscores(_). resources and main are regular packages and they contain classes. Next, write this code to module-info.java in company.modules directory:
module company.modules{
  exports resources;
}
Write this code to module-info.java in main.app directory:
module main.app{
  requires company.modules;
}
As you can see, module names are equivalent to module packages. Write this code to Consumer.java:
package resources;

public class Consumer{
  public Consumer(){
    System.out.println("Consumer Created!");
  }
}
Write this code to Main.java:
package main;

import resources.Consumer;

public class Main{
	
	public static void main(String[] args){
		Consumer consumer = new Consumer();
	}
}
Now, let's start creating the modules. First off, let's build company.modules. Open your terminal(or cmd if you're using windows) in the directory where the module packages reside and type this command to compile Consumer.java:

javac -d output company.modules\resources\Consumer.java

After that, type this command to compile module-info.java in company.modules directory:

javac -d output company.modules\module-info.java

-d means destination directory. output is the destination directory, java will create one if the directory doesn't exist.

Next, let's put the compiled file to a JAR file. Type this command to create a JAR file and put the compiled files above in the JAR:

jar -c -f build\company.modules.jar -C output .

-c means create a JAR. -f means the location of the JAR file that is going to be created. Java will create necessary directories if directories in the location path don't exist. -C means the location of the content that is going to be put in the JAR file. output is the source location and . means include files starting from the root of output. Take note that a JAR file is limited to one module.

Next, let's build main.app module. First off, type this commands to compile module-info.java in main.app and Main.java:

javac --module-path build -d output2 main.app\module-info.java
javac --module-path build -d output2 main.app\main\Main.java

--module-path in javac command refers to a path where modules are located. company.modules is in build directory and since that module is an application module, we need to explicitly tell to the compiler where company.modules is located in order to compile the files. Otherwise, the compiler will complain.

Next, it's time to create a JAR file and put the compiled files in it. Type this command to create a JAR file:

jar -c -f build\main.app.jar -e main.Main -C output2 .

-e means location of a class with main method or the entry point. In the example above, Main.java is the entry point and it's located at main package.

Next, let's run our program. To run the program. Type this command:

java --module-path build -m main.app

--module-path in java command refers to a path where the module we wanna run is located. -m refers to the module with main class or entry point. If you successfully ran the program, the result is going to be:

Consumer Created!

You may repeat the steps if you failed to run the program. More details about java commands can be found in this article.

requires keyword has two additional variants:

requires static
requires transitive

requires static adds an optional dependency to a module. For example, take a look at this module descriptor:

module main.app{ requires work.modules; requires static optional.modules; }

During compilation, optional.modules will be checked. During runtime, optional.modules won't be checked. However, if optional.modules becomes present in the module graph(e.g. added manually with --add-modules), modules that have optional dependency to optional.modules can read the module.

One example where requires static can be used is when we set a maven dependency to 'compile' or 'provided' scope. For example, Lombok library is usually provided by a server. Thus, this dependency's scope is usually set to 'provided'. 'provided' scope excludes the dependency in our built project because it's expected to be provided by other sources such as servers.

requires transitive allows a module to read a module that's required by another module. This is called implied readability. For example:

module work.modules{
  exports resources;
}

module company.modules{
  exports inventory;
  requires transitive work.modules;
}

module main.app{
  requires company.modules;
}
In the example above, main.app can read work.modules without explicit requires directive coming from main.app.

exports keyword also has a variant. exports... to is more restrictive than exports. exports... to only allows packages to be exported to selected modules separated with comma(,). For example:
module work.modules{
  exports resources to company.modules, main.app;
}

uses and provides... with Directives

uses and provides... with directives can be used for creating services and service providers. In java, a service a service is a well-known interface or class for which zero, one, or many service providers exist. A service provider (or just provider) is a class that implements or subclasses the well-known interface or class.

Take note that interface or abstract class of a service is used in uses directive not the implementation. Let's create a service. first off, create this file structure:
|company.modules
  |resources
    |ButtonInterface.java
    |ButtonFactory.java
  |module-info.java
|main.app
  |main
    |Main.java
  |module-info.java
After that, create an interface.
package resources;

public interface ButtonInterface{
  void create(String button);
}
Next, create a concrete class that implements ButtonInterface.
package resources;

public class ButtonFactory implements ButtonInterface{
  
  @Override
  public void create(String button){
    System.out.println(button + " created!");
  }
}
In the main method, we can use the service via ServiceLoader.
package main;

import java.util.ServiceLoader;
import resources.ButtonInterface;

public class Main{
  
  public static void main(String[] args){
    ButtonInterface btn = 
    ServiceLoader.load(ButtonInterface.class).
    iterator().next();
    btn.create("Sample Button");
  }
}
Now, go to module-info.java in company.modules and type these directives:

exports resources;
provides resources.ButtonInterface with resources.ButtonFactory;


company.modules is providing the service we created. Thus, it's the service provider. Next, go to module-info.java in company.modules in main.app and type these directives:

requires company.modules;
uses resources.ButtonInterface;

uses directive is in main.app is the user of service. Next, compile the classes in resources package and also the module-info.java in company.modules.
javac -d output company.modules\resources\ButtonInterface.java
javac -d output -cp output company.modules\resources\ButtonFactory.java
jar -c -f build\company.modules.jar -C output .
I added -cp because ButtonFactory.java would look for .class file of ButtonInterface.java. Next, let's compile the classes in main.app package and also the module-info.java in main.app.
javac -d output2 --module-path build main.app\module-info.java
javac -d output2 --module-path build main.app\main\Main.java
jar -c -f build\main.app.jar -e main.Main -C output2 .
Then, run Main.java:

java --module-path build -m main.app

The result is going to be: Sample Button Created!

opens Directive and open Modifier

opens directive and open modifier allow non-public members of a class in a package to be accessed via reflection. open is a modifier that we can add before the module name. For example:
open module my.module{
  exports package1;
  exports package2;
  exports package3;
}
All non-public members of classes in exported packages of my.module can be accessed via reflection. If we don't want our module to be fully exposed, we can use the opens directive. For example:
module my.module{
  exports package1;
  exports package2;
  exports package3;
  
  opens package1;
}
In the example above, only the non-public members of classes of package1 are accessible via reflection. If we want to select which modules can access non-public members of classes of a package via reflection, we can use opens... to directive. For example:
module my.module{
  exports package1;
  opens package1 to module1, module2;
}

Sunday, April 17, 2022

Java Tutorial: Handling Date&Time

Chapters

Overview

Prior to java8, java.util.Date and java.sql.Date are widely used classes for handling date and time. In java 8 and above, classes in java.time are recommended to be used for handling date&time.

java.util.Date class is not recommended anymore due to lots of issues like implementing coordinated universal time(UTC). Although the Date class is intended to reflect coordinated universal time (UTC), it may not do so exactly, depending on the host environment of the Java Virtual Machine.

Nearly all modern operating systems assume that 1 day = 24 × 60 × 60 = 86400 seconds in all cases. In UTC, however, about once every year or two there is an extra second, called a "leap second". Prior to java 8, Joda-Time was used as an alternative for handling date & time.

java.util.Date and java.sql.Date

java.util.Date is a mutable class that represents a specific instant in time, with millisecond precision since the 1st of January 1970 00:00:00 GMT(the epoch time). Since java 8, several constructors and methods of this class have been deprecated. However, there are few constructors and methods left that we can use.

To instantiate a java.util.Date instance, we can use two constructors: Date() and Date(long date). This example demonstrates these two constructors.
import java.util.Date;

public class SampleClass{

  public static void main(String[] args){
    Date currentDate = new Date();
    Date specificDate = new Date(1332416372111L);
    Date epoch = new Date(0);
    
    System.out.println(currentDate);
    System.out.println(specificDate);
    System.out.println(epoch);
  }
}

Result(may vary)
Mon Mar 21 04:37:24 CST 2022
Thu Mar 22 19:39:32 CST 2012
Thu Jan 01 00:00:00 CST 1970
Date() constructor creates a Date instance with the current date&time of our machine. Date(long date) creates a Date instance with date&time extracted from the given timestamp. To compare to Date objects, we can use equals method. We can also check if a Date object is before or after another Date object by invoking before and after methods. Take a look at this example.
import java.util.Date;

public class SampleClass{

  public static void main(String[] args){
    Date currentDate = new Date();
    Date specificDate = new Date(1332416372111L);
    Date specificDate2 = new Date(1332416372111L);
    
    System.out.println(currentDate.after(specificDate));
    System.out.println(specificDate.before(currentDate));
    System.out.println(specificDate.equals(specificDate2));
  }
}

Result
true
true
true
For formatting date values in Date objects, use DateFormat and SimpleDateFormat classes. I explained them in this article. Here's an example of formatting date using SimpleDateFormat.
import java.text.SimpleDateFormat;
import java.util.Date;

public class SampleClass{

  public static void main(String[] args){
    String pattern = "MM/dd/yyyy hh:mm:ss a";
    Date date = new Date(1332416372111L);
    
    SimpleDateFormat sdf =
    new SimpleDateFormat(pattern);
    
    String result = sdf.format(date);
    System.out.println("Unformatted");
    System.out.println(date);
    System.out.println("Formatted");
    System.out.println(result);
  }
}

Result
Unformatted
Thu Mar 22 19:39:32 CST 2012
Formatted
03/22/2012 07:39:32 PM
java.sql.Date is a thin wrapper around a millisecond value that allows JDBC to identify the instance of this class as an SQL DATE value. A milliseconds value represents the number of milliseconds that have passed since January 1, 1970 00:00:00.00 GMT.

Instance of this class must be "normalized" or in other words the hours, minutes, seconds, and milliseconds must be set to zero. java.sql.Date is a subclass of java.util.Date.

Since java 8, several constructors and methods of this class have been deprecated. However, there are few constructors and methods left that we can use. Take a look at this example.
import java.sql.Date;

public class SampleClass{

  public static void main(String[] args){
    Date date1 = new Date(0);
    Date date2 = new Date(0);
    
    System.out.println(date1);
    System.out.println(date1.after(date2));
    System.out.println(date2.before(date1));
    System.out.println(date1.equals(date2));
  }
}

Result
1970-01-01
false
false
true
Date(long date) Constructs a Date object using the given milliseconds time value or timestamp. before method returns true if the Date object that invokes the method precedes the given Date object. Otherwise, returns false. after method is the opposite of before method. equals returns true if both Date objects are equal. Otherwise, returns false.

For formatting date values in Date objects, use DateFormat and SimpleDateFormat classes. I explained them in this article.

To support SQL data types related to time like SQL TIME and SQL TIMESTAMP, use Time and Timestamp classes. Time is a thin wrapper around the java.util.Date class that allows the JDBC API to identify this as an SQL TIME value. Timestamp is a thin wrapper around java.util.Date that allows the JDBC API to identify this as an SQL TIMESTAMP value.

In the description above, we can say that java.util.Date should be used for handling date&time in our program whereas java.sql.Date should be used as a wrapper for date values when dealing with databases.

Calendar Class

Calendar class is an abstract class that provides methods for converting between a specific instant in time and a set of calendar fields such as YEAR, MONTH, DAY_OF_MONTH, HOUR, and so on, and for manipulating the calendar fields, such as getting the date of the next week.

An instant in time can be represented by a millisecond value that is an offset from the Epoch, January 1, 1970 00:00:00.00 GMT (Gregorian). The class also provides additional fields and methods for implementing a concrete calendar system. Those fields and methods are defined as protected.

Since java 8, most setter and getter methods of java.util.Date and java.sql.Date are deprecated. However, java introduces Calendar class. This class has getter and setter methods that interact with date attributes like days, months, time, etc.

To instantiate an object related to Calendar object, we use the getInstance method. This method is a static method has for overloaded form.
getInstance()
getInstance(Locale aLocale)
getInstance(TimeZone zone)
getInstance(TimeZone zone, Locale aLocale)
This example demonstrates Calendar class.
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

public class SampleClass{

  public static void main(String[] args){
    Calendar calendar = Calendar.getInstance();
    
    System.out.println("System's Default Locale");
    System.out.println(Locale.getDefault());
    System.out.println("System's Default TimeZone");
    System.out.println(TimeZone.getDefault().getID());
    System.out.println();
    
    System.out.println("Available Calendar Types");
    for(String s : calendar.getAvailableCalendarTypes())
      System.out.println(s);
    System.out.println();
    
    System.out.println("Calendar Type: " + 
    calendar.getCalendarType());
    System.out.println("Calendar object instance" +
    " of GregorianCalendar?");
    System.out.println(calendar instanceof GregorianCalendar);
    System.out.println();
    
    Date date1 = calendar.getTime();
    System.out.println("getTime: " + date1);
    long timestamp = calendar.getTimeInMillis();
    Date date2 = new Date(timestamp);
    System.out.println("getTimeInMillis: " + date2);
    System.out.println();
    
  }
}

Result(may vary)
System's Default Locale
en_US
System's Default TimeZone
Asia/Taipei

Available Calendar Types
gregory
buddhist
japanese

Calendar Type: gregory
Calendar Object instance of GregorialCalendar?
true

getTime: Wed Mar 23 17:23:51 CST 2022
getTimeInMillis: Wed Mar 23 17:23:51 CST 2022
First off, I invoke getInstance() method. This method returns a Calendar object with default time zone and locale. In my system, the default locale "en-US" which is synonymous with Locale.US. My system's default timezone is "Asia/Taipei". Take note that getInstance() is inconsistent because every system may have different default locale, timezone and calendar type.

To create a Calendar with consistent locale and timezone, use getInstance(TimeZone zone, Locale aLocale) method and set the locale and timezone that you like. If you just want a consistent locale, use getInstance(Locale aLocale) method. If you just want a consistent timezone, use getInstance(TimeZone zone).

To get system's default locale, use Locale.getDefault() method. To get system's default timezone, use TimeZone.getDefault() method. To get the String representation of a timezone in TimeZone object, use getID() method.

getAvailableCalendarTypes method returns a Set<String> object that contains calendar type names. In the example above, there are three available calendar types: gregory, buddhist and japanese. The default calendar type is "gregory" which stands for "Gregorian". To use other calendar types, instantiate a Calendar object using Calendar.Builder.

For basic instantiation, call Calendar.Builder.setCalendarType(String type) method and then build the Calendar by calling build method. For example, Calendar c = Calendar.Builder.setCalendarType("japanese").build()

getCalendarType returns the type of a Calendar object. In the example above, the calendar type is "gregory". Notice that the Calendar object in the example is an instance of GregorianCalendar. GregorianCalendar is a concrete implementation of Calendar provides the standard calendar system used by most of the world, which is the Gregorian Calendar. We will discuss that class later.

getTime method returns a java.util.Date object where the date&time of the Calendar object is wrapped into. getTimeInMillis method returns the Calendar object's date&time in milliseconds. We can use this methods to convert Calendar object's date&time to java.util.Date or java.sql.Date.

To convert java.util.Date or java.sql.Date date&time to Calendar, use setTime(java.util.Date date). To convert a timestamp(millis) to Calendar, use setTimeInMillis method.

Displaying Date&Time Field Names/Values

To diplay Calendar field numerical value, we use get method. To display Calendar field name, use getDisplayName method. To display all fields of a Calendar field, use getDisplayNames method. Take note that not all Calendar fields have names and fields. This example demonstrates methods that are mentioned above.
import java.util.Calendar;
import java.util.Locale;
import java.util.Map;

public class SampleClass{

  public static void main(String[] args){
    Calendar calendar = Calendar.getInstance();
  
    System.out.println("Days");
    for(Map.Entry entry : calendar.getDisplayNames(
                          Calendar.DAY_OF_WEEK,
                          Calendar.LONG_FORMAT,
                          Locale.US).entrySet())
      System.out.println(entry.getKey() + " | " +
                         entry.getValue());
    System.out.println();
    
    System.out.println("First Day of Week: " +
    calendar.getFirstDayOfWeek());
    System.out.println("Day of Week(Plain): " + 
    calendar.get(Calendar.DAY_OF_WEEK));
    System.out.println("Day of Week(Short Format, US): " + 
    calendar.getDisplayName(Calendar.DAY_OF_WEEK,
                            Calendar.SHORT_FORMAT,
                            Locale.US));
    System.out.println("Day of Week(Short Format, France): " + 
    calendar.getDisplayName(Calendar.DAY_OF_WEEK,
                 Calendar.SHORT_FORMAT,
                 Locale.FRANCE));
    System.out.println("Day of Week(Long Format, US): " + 
    calendar.getDisplayName(Calendar.DAY_OF_WEEK,
                            Calendar.LONG_FORMAT,
                            Locale.US));
  }
}

Result(may vary)
Days
Monday | 2
Thursday | 5
Friday | 6
Sunday | 1
Wednesday | 4
Tuesday | 3
Saturday | 7

First Day of Week: 1
Day of Week(Plain): 4
Day of Week(Short Format, US): Wed
Day of Week(Short Format, France): mer.
Day of Week(Long Format, US): Wednesday
getDisplayNames returns a Map object that contains fields of a field. In the example above, Calendar.DAY_OF_WEEK has 7 fields that represent days. Field with a numerical value of 1 is the first day of a week. First day of a week is locale-dependent. For example, in France, first day of a week is CALENDAR.MONDAY. In US, the first day of a week is CALENDAR.SUNDAY

getDisplayNames(int field, int style, Locale locale) has three parameters. field is a Calendar field with fields. Take note that most Calendar fields have a numerical(int) identifiers that are compatible with particular methods. For example, the numerical identifier of Calendar.DAY_OF_WEEK is compatible with getDisplayNames method. It's also compatible with getDisplayName method.

CALENDAR.SUNDAY is not compatible with getDisplayNames because it doesn't have its own fields. It's not also compatible with getDisplayName because it doesn't have display name. Its display name is in Calendar.DAY_OF_WEEK. getDisplayNames and getDisplayName return null or an exception if the numerical identifier of a field is not compatible with them.

style is the format of the extracted field data. There are different types of field formatting that we can use. You can refer to Calendar.ALL_STYLES field to see all available styles. locale is the specific locale of you choosing.

getDisplayName is similar to getDisplayNames and has the same parameters. Although, getDisplayName extracts display names of a field. For example, getDisplayName extracts the display name of the current day of a week from Calendar.DAY_OF_WEEK.

If a Calendar field contains a numerical value, use get method. In the example above, get method extracts the numerical identifier of Calendar.WEDNESDAY which is 4. This number is the current numerical value of Calendar.DAY_OF_WEEK.

Don't be confused with numerical value and numerical identifier. Numerical value is a representation of something whereas numerical identifier is an identifier to identify a Calendar field. For example, the numerical value of Calendar.DAY_OF_MONTH represents current day of our Calendar object.

To clarify further, take a look at this example.
System.out.println("identifier: "+Calendar.DAY_OF_WEEK); //7
System.out.println("value: "+Calendar.get(Calendar.DAY_OF_WEEK)); //4
Assume that these codes are part of the codebase in the example above. The first println returns 7. This number is the numerical identifier. The second println returns 4. This number is the numerical value.

getFirstDayOfWeek returns the numerical identifier of the first day of a week. In the example above, this method returns 4 which is the numerical identifier of Calendar.WEDNESDAY.

If you wanna change the first day of a week, use setFirstDayOfWeek(int value) method. The value parameter should be one of the numerical identifier of fields of Calendar.DAY_OF_WEEK. For example:
setFirstDayOfWeek(Calendar.MONDAY)

Take note that changing the first day of a week may affect the week count of Calendar class.

Date&Time Fields

Calendar class has lots of fields that we can use to obtain information regarding date&time. This example demonstrates obtaining information from some Calendar fields.
import java.util.Calendar;
import java.util.Locale;

public class SampleClass{

  public static void main(String[] args){
    Calendar calendar = Calendar.getInstance();
  
    System.out.println("Month: " +
    calendar.getDisplayName(Calendar.MONTH, 
                            Calendar.LONG_FORMAT,
                            Locale.US));
    System.out.println("Day: " +
    calendar.get(Calendar.DAY_OF_MONTH));
    System.out.println("Year: " +
    calendar.get(Calendar.YEAR));
    System.out.println("Hour(24-hour format): " +
    calendar.get(Calendar.HOUR_OF_DAY));
    System.out.println("Hour(12-hour format): " +
    calendar.get(Calendar.HOUR));
    System.out.println("Minutes: " +
    calendar.get(Calendar.MINUTE));
    System.out.println("Seconds: " +
    calendar.get(Calendar.SECOND));
    System.out.println("AM_PM: " +
    calendar.getDisplayName(Calendar.AM_PM,
                            Calendar.SHORT_FORMAT,
                            Locale.US));
    System.out.println();
    
    //If isWeekDateSupported method returns false,
    //your Calendar object doesn't support 
    //week dates and week-related fields may not
    //work as intended
    System.out.println("Week Date Supported? " +
    calendar.isWeekDateSupported());
    
    System.out.println("Week Year: " + 
    calendar.getWeekYear());
    System.out.println("Minimal Days in First Week: " + 
    calendar.getMinimalDaysInFirstWeek());
    System.out.println("Weeks in Week Year: " + 
    calendar.getWeeksInWeekYear());
    System.out.println("Week of Month: " + 
    calendar.get(Calendar.WEEK_OF_MONTH));
    System.out.println("Week of Year: " + 
    calendar.get(Calendar.WEEK_OF_YEAR));
  }
}

Result(may vary)
Month: March
Day: 23
Year: 2022
Hour(24-hour format): 17
Hour(12-hour format): 5
Minutes: 34
Seconds: 31
AM_PM: PM

Week Date Supported? true
Week Year: 2022
Minimal Days in First Week: 1
Weeks in Week Year: 53
Week of Month: 4
Week of Year: 13
In the specified date&time in a Calendar object, Calendar.MONTH holds month. Calendar.DAY_OF_MONTH holds day in the month Calendar.YEAR holds year. Calendar.HOUR_OF_DAY holds hours in 24-hour format. Calendar.HOUR holds hours in 12-hour format. Calendar.MINUTE holds minutes. Calendar.SECOND holds seconds. Calendar.AM_PM holds AM or PM value.

Calendar.WEEK_OF_YEAR holds the week number in a year where Calendar.DAY_OF_MONTH belongs. In the example above, day 23 of March is in week 13 of year 2022. Calendar.WEEK_OF_MONTH holds the week number in a month where Calendar.DAY_OF_MONTH belongs.

In the example above, day 23 of March is in week 4 of March 2022. getWeeksInWeekYear returns the total number of weeks in a year. In the example above, year 2022 has a total of 53 weeks.

Take note that WEEK_OF_YEAR, DAY_OF_MONTH, WEEK_OF_MONTH and the return value of getWeeksInWeekYear are affected by minimal days in first week. Minimal days are minimum days required for a first week to be part of next month. Take a look at this example.
import java.util.GregorianCalendar;
import java.util.Calendar;
import java.util.Locale;

public class SampleClass{

  public static void main(String[] args){
    GregorianCalendar calendar1 =
    new GregorianCalendar(1998, Calendar.JANUARY, 1);
    calendar1.setFirstDayOfWeek(Calendar.MONDAY);
    calendar1.setMinimalDaysInFirstWeek(4);
    System.out.println("Output #1");
    System.out.println("Week Year: " + 
    calendar1.getWeekYear());
    System.out.println();
    
    calendar1.setFirstDayOfWeek(Calendar.SUNDAY);
    System.out.println("Output #2");
    System.out.println("Week Year: " + 
    calendar1.getWeekYear());
    System.out.println();
    
    calendar1.setFirstDayOfWeek(Calendar.TUESDAY);
    System.out.println("Output #3");
    System.out.println("Week Year: " + 
    calendar1.getWeekYear());
    System.out.println();
    
  }
}

Result
Output #1
Week Year: 1998

Output #2
Week Year: 1997

Output #3
Week Year: 1998
First off, getWeekYear returns the year where the week belongs. In the example above, minimal days in first week is set to 4 and first day of week is set to Calendar.MONDAY. getWeekYear returns 1998 because the week where jan 1 belongs is part of 1998. This week starts from first day of week which is monday. Monday in this week is dec 29.

If we count days from the first day of next month(jan 1) up to the day before the first day of week, those days are the minimal days. So, jan 1 is thursday, jan 2 is friday, jan 3 is saturday and jan 4 is sunday. Those days sum up to 4 which is the value that we assign to setMinimalDaysInFirstWeek. Thus, we can say that the week starting from monday, dec 29, 1997 to sunday, jan 4, 1998 is part of 1998.

In Output #2, the returned week year is 1997 because I changed the first day of week to sunday. If we count days from the first day of next month(jan 1) up to the day before the first day of week, the total minimal days is less than 4. Thus, we can say that the week starting from sunday, dec 28, 1997 to saturday, jan 3, 1998 is part of 1997.

In Output #3, the returned week year is 1998 because I changed the first day of week to tuesday. If we count days from the first day of next month(jan 1) up to the day before the first day of week, the total minimal days is greater than 4. Thus, we can say that the week starting from tuesday, dec 30, 1997 to monday, jan 5, 1998 is part of 1998.

In ISO 8601 standard, the default first day of week is monday and the default minimal days in first week is 4. To see the effect of minimal days in other week-related fields, take a look at this example.
import java.util.GregorianCalendar;
import java.util.Calendar;
import java.util.Locale;

public class SampleClass{

  public static void main(String[] args){
    GregorianCalendar calendar1 =
    new GregorianCalendar(1998, Calendar.JANUARY, 1);
    calendar1.setFirstDayOfWeek(Calendar.MONDAY);
    calendar1.setMinimalDaysInFirstWeek(4);
    System.out.println("Output #1");
    System.out.println("Week Year: " + 
    calendar1.getWeekYear());
    System.out.println("Week of Year: " + 
    calendar1.get(Calendar.WEEK_OF_YEAR));
    System.out.println("Week of Month: " + 
    calendar1.get(Calendar.WEEK_OF_MONTH));
    System.out.println();
    
    calendar1.setFirstDayOfWeek(Calendar.SUNDAY);
    System.out.println("Output #2");
    System.out.println("Week Year: " + 
    calendar1.getWeekYear());
    System.out.println("Week of Year: " + 
    calendar1.get(Calendar.WEEK_OF_YEAR));
    System.out.println("Week of Month: " + 
    calendar1.get(Calendar.WEEK_OF_MONTH));
    System.out.println();
    
  }
}

Result
Output #1
Week Year: 1998
Week of Year: 1
Week of Month: 1

Output #2
Week Year: 1997
Week of Year: 53
Week of Month: 0
In the result above, Output #1 indicates that the week is part of 1998. Output #2 indicates that the week is part of 1997. If you're wondering why week of month in Output #2 is 0, a zero week of month means that the week where the specified day belongs is part of previous month. Thus, we can say that the week where jan 1, 1998 belongs is part of december 1997.

add(), set() and roll() Methods

add, set and roll methods are essentials method in Calendar class. Let's start with set method. set method has multiple forms and I'm gonna demonstrate two of them:
set(int field, int value)
set(int year, int month, int date)
We can set a value to a particular Calendar field by using the first one. If we want to set year, month and day by invoking set method once, use the second form. This example demonstrates set method.
import java.util.Calendar;

public class SampleClass{

  public static void main(String[] args){
    Calendar calendar = Calendar.getInstance();
    System.out.println(calendar.getTime());
    calendar.set(Calendar.YEAR, 1994);
    System.out.println(calendar.getTime());
    calendar.set(2012, Calendar.SEPTEMBER, 1);
    System.out.println(calendar.getTime());
  }
}

Result(may vary)
Sat Mar 26 20:22:24 CST 2022
Sat Mar 26 20:22:24 CST 1994
Sat Sep 01 20:22:24 CST 2012
Take note that calling set method alone doesn't recompute time value in milliseconds until the next call to get, getTime, getTimeInMillis, add, or roll method is made.

Example: Consider a GregorianCalendar originally set to August 31, 1999. Calling set(Calendar.MONTH, Calendar.SEPTEMBER) sets the date to September 31, 1999. This is a temporary internal representation that resolves to October 1, 1999 if getTime()is then called.

However, a call to set(Calendar.DAY_OF_MONTH, 30) before the call to getTime() sets the date to September 30, 1999, since no recomputation occurs after set() itself. To check if a Calendar field has been set or will be changed, call isSet method.

If you wanna set time, use these overloaded forms of set method.
set(int year, int month, int date, int hourOfDay, int minute)
set(int year, int month, int date, int hourOfDay, int minute, int second)

add(f, delta) adds delta to field f. Unlike set method, this method recomputes and adjusts date&time values if it's invoked. This example demonstrates add method.
import java.util.Calendar;

public class SampleClass{

  public static void main(String[] args){
    Calendar calendar = Calendar.getInstance();
    calendar.set(2010, Calendar.JANUARY, 31);
    System.out.println(calendar.getTime());
    calendar.add(Calendar.MONTH, 13);
    System.out.println(calendar.getTime());
  }
}

Result(may vary)
Mon Jan 31 08:59:15 CST 2010
Wed Feb 28 08:59:15 CST 2011
In the example above, the original date is Jan 13, 2010 then, I added additional 13 months to Calendar.MONTH field. The date was changed to Feb and as you can see, the day was adjusted from 31 to 28. This adjustment was necessary because maximum day(no leap year) of February in Gregorian Calendar is day 28. Also, the year was changed to 2011.

roll method is just like add except for the adjustments. Unlike add, roll method doesn't adjust larger field. Larger field represents a larger unit of time than the given field. Smaller field represents a smaller unit of time than the given field. This example demonstrates roll method.
import java.util.Calendar;

public class SampleClass{

  public static void main(String[] args){
    Calendar calendar = Calendar.getInstance();
    calendar.set(2010, Calendar.JANUARY, 31);
    System.out.println(calendar.getTime());
    calendar.roll(Calendar.MONTH, 15);
    System.out.println(calendar.getTime());
  }
}

Result(may vary)
Sun Jan 31 09:26:08 CST 2010
Fri Apr 30 09:26:08 CST 2010
In the example above, the original date is Jan 31, 2010. We can see from the result that the day is adjusted to 30 and the day of week is changed to Fri. However, the year stays the same. Year doesn't change because Calendar.YEAR field is larger than Calendar.MONTH. Calendar.DAY_OF_MONTH field is smaller than Calendar.MONTH.

Just remember this general time diagram to easily determine which field is smaller and larger.
MILLISECOND < SECOND < MINUTE < HOUR < DAY_OF_MONTH < MONTH < YEAR
In the diagram above, MILLISECOND is the smallest unit of time and YEAR is the largest unit of time. Take note that week-related fields like DAY_OF_WEEK can be a given value for set, add and roll. Take a look at this example.
import java.util.Calendar;

public class SampleClass{

  public static void main(String[] args){
    Calendar calendar = Calendar.getInstance();
    calendar.setFirstDayOfWeek(Calendar.SUNDAY);
    calendar.set(2010, Calendar.DECEMBER, 31);
    System.out.println(calendar.getTime());
    calendar.roll(Calendar.DAY_OF_WEEK, 1);
    System.out.println(calendar.getTime());
    
    calendar.set(2010, Calendar.DECEMBER, 31);
    calendar.setFirstDayOfWeek(Calendar.SUNDAY);
    calendar.roll(Calendar.DAY_OF_WEEK, 2);
    System.out.println(calendar.getTime());
  }
}

Result(may vary)
Fri Dec 31 10:18:35 CST 2010
Sat Jan 01 10:18:35 CST 2011
Sun Dec 26 10:18:35 CST 2010
In the third println, the date rolled back to Dec 26. This happened because when roll is used with DAY_OF_WEEK, changes revolve within the boundary of week. In the example above, first day of week starts at sunday which is Dec 26. Dec 26, 2010(Sunday) to Jan 01, 2011(Saturday) is the week where the given date(Dec 31, 2010) belongs.

In the second println, the date didn't roll back because the new date is in the boundary of the week. In the third println, the date rolled back to Dec 26 because the new date(Jan 02, 2011) would have been out of the boundary of the week.

A more detailed explanation about set, add and roll methods can be found in the documentation.

getActualMinimum() getActualMaximum() Methods

getActualMinimum and getActualMaximum methods return the minimum and maximum value that the specified calendar field could have, given the time value of this Calendar. The default implementation of this method uses an iterative algorithm to determine the actual minimum value for the calendar field.

Subclasses should, if possible, override this with a more efficient implementation. For getActualMinimum, getMinimum is simply returned in many cases. This example demonstrates these two methods.
import java.util.Calendar;

public class SampleClass{

  public static void main(String[] args){
    Calendar calendar = Calendar.getInstance();
    calendar.set(2022, Calendar.FEBRUARY, 1);
    System.out.println("Max Day of Month: " + 
    calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
    calendar.set(2012, Calendar.FEBRUARY, 1);
    System.out.println("Max Day of Month: " + 
    calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
    System.out.println("Min Week of Month: " + 
    calendar.getActualMinimum(Calendar.WEEK_OF_MONTH));
  }
}

Result(may vary)
Max Day of Month: 28
Max Day of Month: 29
Min Week of Month: 0
setWeekDate() Method

This method sets the date of this Calendar with the given date specifiers - week year, week of year, and day of week. Unlike the set method, all of the calendar fields and time values are calculated upon return. This example demonstrates this method.
import java.util.Calendar;

public class SampleClass{

  public static void main(String[] args){
    Calendar calendar = Calendar.getInstance();
    System.out.println(calendar.getTime());
    if(calendar.isWeekDateSupported()){
      calendar.setWeekDate(2022, 40, Calendar.FRIDAY);
      System.out.println(calendar.getTime());
    }
    else System.out.println("Week Dates not Supported!");
  }
}

Result(may vary)
Mon Mar 28 20:20:59 CST 2022
Fri Sep 30 20:20:59 CST 2022
If your Calendar object doesn't support week dates, setWeekDate may not work properly or may throw UnsupportedOperationException.

Lenient and Non-Lenient Modes

Calendar has two modes for interpreting the calendar fields, lenient and non-lenient. When a Calendar is in lenient mode, it accepts a wider range of calendar field values than it produces. When a Calendar recomputes calendar field values for return by get(), all of the calendar fields are normalized. For example, a lenient GregorianCalendar interprets MONTH == JANUARY, DAY_OF_MONTH == 32 as February 1.

When a Calendar is in non-lenient mode, it throws an exception if there is any inconsistency in its calendar fields. For example, a GregorianCalendar always produces DAY_OF_MONTH values between 1 and the length of the month. For example, A non-lenient GregorianCalendar throws an exception upon calculating its time or calendar field values if any out-of-range field value has been set.

This example demonstrates lenient and non-lenient modes.
import java.util.Calendar;
import java.util.GregorianCalendar;

public class SampleClass{

  public static void main(String[] args){
    GregorianCalendar calendar =
    new GregorianCalendar(2010, Calendar.JANUARY, 1);
    
    System.out.println("Lenient? " + 
    calendar.isLenient());
    
    calendar.set(Calendar.DAY_OF_MONTH, 32);
    System.out.println(calendar.getTime());
    
    calendar = new GregorianCalendar(
               2010, Calendar.JANUARY, 1);
    calendar.setLenient(false);
    calendar.set(Calendar.DAY_OF_MONTH, 32);
    System.out.println(calendar.getTime());
  }
}

Result
Lenient? true
Mon Feb 01 00:00:00 CST 2010
...IllegalArgumentException...
isLenient methods returns true if a Calendar object is lenient. Otherwise, returns false. setLenient sets the Calendar object to lenient or non-lenient mode.

Comparing Dates

Calendar class has methods that can be used to make a comparison between dates. Take a look at this example.
import java.util.Calendar;

public class SampleClass{

  public static void main(String[] args){
   Calendar calendar1 = Calendar.getInstance();
   calendar1.clear();
   calendar1.set(1994, Calendar.SEPTEMBER, 1);
   Calendar calendar2 = Calendar.getInstance();
   calendar2.clear();
   calendar2.set(1994, Calendar.SEPTEMBER, 1);
   
   System.out.println(calendar1.after(calendar2));
   System.out.println(calendar2.before(calendar1));
   System.out.println();
   calendar1.setLenient(false);
   System.out.println(calendar1.compareTo(calendar2));
   System.out.println(calendar1.equals(calendar2));
  }
}

Result
false
false

0
false
clear method resets date and time of this calendar(calendar1) to Calendar-specific defaults. For example, the default date and time is January 1, 1970 00:00:00(also called Unix time or Epoch time). This method also sets isSet boolean flag to false for all the calendar fields, and the date and time calculations will treat the fields as if they had never been set.

If you wanna reset a single Calendar field, you can use clear(int field) method. This method set isSet boolean flag to false for the specific Calendar field. This method may reset the Calendar field to its Calendar-specific default value.

after method returns true if the date and time of Calendar object that calls the method is after the date and time of the given Calendar object. Otherwise, returns false. before method is the opposite of after method.

compareTo method returns 0 if the millisecond representation of both dates are equals. Otherwise, returns a non-zero integer. compareTo returns a negative integer if the millisecond(offset from epoch) of the given date is greater than the millisecond(offset from epoch) of the other date. Returns a positive integer if the millisecond of the given date is less than the millisecond of the other date.

equals method compares millisecond(offset from epoch) and some parameters of two Calendar objects. The Calendar parameters are the values represented by the isLenient, getFirstDayOfWeek, getMinimalDaysInFirstWeek and getTimeZone methods. If there is any difference in those parameters between the two Calendars, this method returns false.

GregorianCalendar Class

GregorianCalendar is a concrete subclass of Calendar and provides the standard calendar system used by most of the world. This class implements proleptic Gregorian and Julian calendars.

That is, dates are computed by extrapolating the current rules indefinitely far backward and forward in time. As a result,
GregorianCalendar
may be used for all years to generate meaningful and consistent results.

However, dates obtained using GregorianCalendar are historically accurate only from March 1, 4 AD onward, when modern Julian calendar rules were adopted. Before this date, leap year rules were applied irregularly, and before 45 BC the Julian calendar did not even exist.

Unlike Calendar class, this class has constructors that can set date and time individually. Take a look at this example.
import java.util.Calendar;
import java.util.GregorianCalendar;

public class SampleClass{

  public static void main(String[] args){
    GregorianCalendar gc = 
    new GregorianCalendar(2012, Calendar.FEBRUARY, 1);
    
    System.out.println("Actual Max: "+
    gc.getActualMaximum(Calendar.DAY_OF_MONTH));
    System.out.println("Max: "+
    gc.getMaximum(Calendar.DAY_OF_MONTH));
    System.out.println("Least Max: "+
    gc.getLeastMaximum(Calendar.DAY_OF_MONTH));
    System.out.println();
    
    System.out.println("Actual Min: "+
    gc.getActualMinimum(Calendar.DAY_OF_MONTH));
    System.out.println("Min: "+
    gc.getMinimum(Calendar.DAY_OF_MONTH));
    System.out.println("Greatest Min: "+
    gc.getGreatestMinimum(Calendar.DAY_OF_MONTH));
    System.out.println();
    
    System.out.println("Is 2012 a leap year? " +
    gc.isLeapYear(2012));
    System.out.println("Cutover Date: " + 
    gc.getGregorianChange());
  }
}

Result(may vary)
Actual Max: 29
Max: 31
Least Max: 28

Actual Min: 1
Min: 1
Greatest Min: 1

Is 2012 a leap year? true
Cutover Date: Fri Oct 15 00:00:00 CST 1582
GregorianCalendar(int year, int month, int dayOfMonth) constructs a GregorianCalendar with the given date set in the default time zone with the default locale. If you want to instantiate a GregorianCalendar with time set individally, use these two constructors.
GregorianCalendar(int year, int month, int dayOfMonth, int hourOfDay, int minute)
GregorianCalendar(int year, int month, int dayOfMonth, int hourOfDay, int minute, int second)

getActualMaximum returns the maximum value of a field in a specific date of the Calendar instance. If a year is leap year, the max day of month value that getActualMaximum can get in February is 29. In the example above, 2012 is a leap year. Thus, getActualMaximum returns 29.

getMaximum returns the maximum value of a field based on the maximum value that get method can get in any possible time value. The maximum day of month in Gregorian Calendar is 31. Thus, the maximum value that get method can get is 31.

getLeastMaximum returns the smallest max value that getActualMaximum can get in any possible time value. If a year is not leap year, the max day of month value that getActualMaximum can get in February is 28.

getActualMinimum returns the minimum value of a field in a specific date of the Calendar instance. getMinimum returns the minimum value of a field based on the minimum value that get method can get in any possible time value. getGreatestMinimum returns the highest min value that getActualMinimum can get in any possible time value.

These maximum and minimum related methods may be affected by the current values of the getFirstDayOfWeek, getMinimalDaysInFirstWeek, getGregorianChange and getTimeZone methods.

isLeapYear method returns true if the given year is a leap year. Otherwise, returns false.

getGregorianChange gets the Gregorian Calendar change date which is also called as the cutover date. This is the point when the switch from Julian dates to Gregorian dates occurred. Default is October 15, 1582 (Gregorian). Previous to this, dates will be in the Julian calendar. You can set your own cutover date by calling setGregorianChange(Date date) method.

You might ask what's the difference between Julian and Gregorian Calendar. The only difference between the Gregorian and the Julian calendar is the leap year rule. The Julian calendar specifies leap years every four years, whereas the Gregorian calendar specifies leap years every four years but omits century years which are not divisible by 400.

For example, In Gregorian Calendar, year 1600 and 2000 are leap years because they're divisible by 400. Year 1700, 1800 and 1900 are not leap years because they're not divisible by 400.

TimeZone Class

TimeZone is an abstract class that represents a time zone offset, and also figures out daylight savings. A time zone is an area that observes a uniform standard time for legal, commercial and social purposes.

To obtain the time zone where java platform is running, use the getDefault static method. If we want to obtain a time zone via ID name, use getTimeZone(String ID) static method. If you're using Calendar class, you can call getTimeZone() method. This example demonstrates TimeZone class and some of its methods.
import java.util.TimeZone;
import java.util.Calendar;

public class SampleClass{

  public static void main(String[] args){
    //get system's default time zone
    TimeZone tz = TimeZone.getDefault();
    
    System.out.println("My Default TimeZone ID");
    System.out.println(tz.getID());
    System.out.println("Display Name(Long Format)");
    System.out.println(
    tz.getDisplayName(false, TimeZone.LONG));
    System.out.println("Daylight Saving Time?");
    System.out.println(tz.observesDaylightTime());
    System.out.println("Offset from UTC+-00:00");
    long millis = Calendar.getInstance().getTimeInMillis();
    System.out.println("Millis: "+ tz.getOffset(millis));
    double hour = tz.getOffset(millis) / 1000 / 60 / 60;
    System.out.println("Hour: " + hour);
    System.out.println();
  }
}

Result(may vary)
My Default TimeZone ID
Asia/Taipei
Display Name(Long Format)
Taipe Standard Time
Daylight Saving Time?
false
Offset UTC+-00:00
Millis: 28800000
Hour: 8.0
getID method returns the ID name of a time zone. We can use ID names in getTimeZone(String ID). getDisplayName(boolean daylight, int style) returns the display name of a time zone. daylight is a boolean type that determines that if true, the returned display name is daylight savings even the specified time zone doesn't follow daylight saving time. If false, standard display name is returned.

display is the format of the display name. We can use TimeZone.SHORT or TimeZone.LONG for formatting time zones. getDisplayName has multiple forms. Look at them in the documentation.

observesDaylightTime method returns true if the specified time zone follows daylight saving time. Otherwise, returns false.

getOffset returns the offset of this time zone from UTC(UTC+-00:00) at the specified date. If Daylight Saving Time is in effect at the specified date, the offset value is adjusted with the amount of daylight saving.

This method returns a historically correct offset value if an underlying TimeZone implementation subclass supports historical Daylight Saving Time schedule and GMT offset changes. Take note that UTC is effectively a successor to Greenwich Mean Time.

This method returns the offset in milliseconds which can be converted to hour which one of the numbers that we see in this UTC/GMT time zone format:
UTC+-Hours:Minutes or GMT+-Hours:Minutes
Examples: UTC+08:00, GMT-10:00

If you're using Calendar class, a time offset from UTC is stored in Calendar.ZONE_OFFSET and Calendar.DST_OFFSET. Calendar.ZONE_OFFSET holds the raw offset. This offset is a time offset without adjustments due to Daylight Saving Time. Calendar.DST_OFFSET is a time offset with adjustments due to Daylight Saving Time.

SimpleTimeZone Class

SimpleTimeZone is a concrete subclass of TimeZone that represents a time zone for use with a Gregorian calendar. The class holds an offset from GMT, called raw offset, and start and end rules for a daylight saving time schedule.

Since it only holds single values for each, it cannot handle historical changes in the offset from GMT and the daylight saving schedule, except that the setStartYear method can specify the year when the daylight saving time schedule starts in effect.

This example demonstrates SimpleTimeZone and how to schedule daylight saving time.
import java.util.SimpleTimeZone;
import java.util.Calendar;
import java.util.GregorianCalendar;

public class SampleClass{

  public static void main(String[] args){
   SimpleTimeZone stz = 
   new SimpleTimeZone(1*60*60*1000, "Europe/Paris",
                      Calendar.APRIL, 1, -Calendar.SUNDAY,
                      3600000, SimpleTimeZone.UTC_TIME,
                      Calendar.OCTOBER, -1, Calendar.SUNDAY,
                      3600000, SimpleTimeZone.UTC_TIME,
                      3600000);
   stz.setStartYear(2021);
   System.out.println("Is SimpleTimeZone instance DST? " +
   stz.observesDaylightTime());
   
   System.out.println("Daylight Savings: " +
   stz.getDSTSavings());
   System.out.println("Raw Offset: " + 
   stz.getRawOffset());
   
   GregorianCalendar gc = new GregorianCalendar(
   2021, Calendar.APRIL, 4, 3, 0);
   gc.setTimeZone(stz);
   System.out.println("Is April 4, 2021 03:00 in DST? "+
   stz.inDaylightTime(gc.getTime()));
   System.out.println();
   
   System.out.println("Get time zone of gc");
   System.out.println("Time zone ID: "+
   gc.getTimeZone().getID());
 }
}

Result

Is SimpleTimeZone instance DST? true
Daylight Savings: 3600000
Raw Offset: 3600000
Is April 4, 2021 03:00 in DST? true

Get time zone of gc
Time zone ID: Europe/Paris
First off, let's examine this SimpleTimeZone constructor:
SimpleTimeZone(int rawOffset, String ID, int startMonth, int startDay, int startDayOfWeek, int startTime, int startTimeMode, int endMonth, int endDay, int endDayOfWeek, int endTime, int endTimeMode, int dstSavings)
rawOffset is the offset from UTC/GMT time. ID is the ID name that we want to assign to our time zone.

startMonth, startDay, startDayOfWeek, startTime and startTimeMode are parameters for the start-rule of our scheduled daylight saving time.

endMonth, endDay, endDayOfWeek, endTime and endTimeMode are parameters for the end-rule of our scheduled daylight saving time.

startTime is the time where our scheduled daylight saving time starts. endTime is the time where our scheduled daylight saving time ends.

dstSavings is the saved daylight time per day in daylight saving time.

Month, day and day-of-week have different combinations that have different effects in the start-rule and end-rule of daylight saving time. You can read the combinations in the documentation.

In the example above, this combination in the start-rule:
Calendar.APRIL, 1, -Calendar.SUNDAY
means first Sunday in April. Therefore, the daylight saving time of stz starts on the first Sunday in April. Next, this combination in the end-rule:
Calendar.OCTOBER, -1, Calendar.SUNDAY means last Sunday in October. Therefore, the daylight saving time of stz ends on the last Sunday in October.

Next, let's talk about time mode. startTimeMode is the time mode for the start-rule. endTimeMode is the time mode for the end-rule. There are three time modes: WALL_TIME, STANDARD_TIME and UTC_TIME. startTimeMode and endTimeMode are applied to the startTime and endTime, respectively.

WALL_TIME represents local time with daylight time applied to the end-rule.
STANDARD_TIME represents local time without daylight time applied to start-rule and end-rule.
UTC_TIME represents Universal Time(UTC+-00:00) and doesn't apply daylight time to start-rule and end-rule.

For example, In the example above, the local time is UTC+01:00(1*60*60*1000 or 3600000 millis or one hour offset). Then, the start-rule of SimpleTimeZone instance in the example above is set to first sunday of April 2021. starting time is 3600000 millis or 1:00am in UTC time(24-hour format).

The returned value of inDaylightTime method is true. This means that April 4, 2021 03:00 is in the daylight saving time of stz. Make sure that gc Calendar time zone is stz. Otherwise, you may get a different result. Use setTimeZone method to set time zone to a Calendar instance, GregorianCalendar in this case.

setStartYear sets the daylight saving time starting year. inDaylightTime returns true if the given date and time is in the daylight saving time(DST) of the SimpleTimeZone that invokes the method. Otherwise, returns false. Now, let's discuss why April 4, 2021 03:00 is in the daylight saving time of stz.

First off, Start time is 1:00am(3600000 millis) in UTC time. Then, we convert that start time to our local time which is UTC+1(UTC+01:00). Thus, the starting time in UTC+1 is 2:00am. Then, we move the time forward by an hour because we assign 1 hour or 3600000 millis to dstSavings parameter. Now, DST starts at sunday, April 4, 2021 03:00am in UTC+1 DST.

Now, for the end-rule, end time is 1:00am(3600000 millis) in UTC time. Then, we convert that end time to our local time which is UTC+1(UTC+01:00). Thus, the end time in UTC+1 is 2:00am. Then, we move the time backward by an hour because we assign 1 hour or 3600000 millis to dstSavings parameter. Now, DST ends at sunday, October 31, 2021 01:00am in UTC+1 time.

If we change the endTimeMode to STANDARD_TIME, DST ends at sunday, October 31, 2021 00:00am in UTC+1 time. The result is different because we use a different type of time mode. STANDARD_TIME uses the given local time which is UTC+1 in this case. That's why ending time is 01:00am UTC+1 standard time, move backward by an hour and DST ends at 00:00am.

If we change the endTimeMode to WALL_TIME, DST ends at saturday, October 30, 2021 23:00pm in UTC+1 time. The result is different because we use a different type of time mode. WALL_TIME when applied to end-rule, uses local time with daylight time adjustment.

That's why ending time is 01:00am UTC+1 DST, convert ending time back to standard UTC+1 which 00:00am UTC+1, move time backward by an hour and DST ends at 23:00pm.

Now, to test if our DST schedule is working, take a look at this example.
import java.util.SimpleTimeZone;
import java.util.TimeZone;
import java.util.Calendar;
import java.util.GregorianCalendar;

public class SampleClass{
 
  public static void main(String[] args){
    SimpleTimeZone stz =
    new SimpleTimeZone(1*60*60*1000, "Europe/Paris",
                       Calendar.APRIL, 1, -Calendar.SUNDAY,
                       3600000, SimpleTimeZone.UTC_TIME,
                       Calendar.OCTOBER, -1, Calendar.SUNDAY,
                       3600000, SimpleTimeZone.UTC_TIME,
                       3600000);
    stz.setStartYear(2021);
    
    GregorianCalendar gc = new GregorianCalendar(
    2021, Calendar.APRIL, 4, 1, 50);
    gc.setTimeZone(stz);
    TimeZone.setDefault(stz);
    
    System.out.println(gc.getTime());
    gc.add(Calendar.MINUTE, 30);
    System.out.println(gc.getTime());
  }
}

Result
Sun Apr 4 01:50:00 CET 2021
Sun Apr 4 03:20:00 CEST 2021
In the example above, we see that once the time reached 2:00am, an hour was added to the time and the timezone changed from CET(Central European Time) to CEST(Central European Summer Time).

Don't forget to set your program's default timezone to the timezone we created. Use TimeZone.setDefault method to set our program's default timezone. This ensures that other date-time related classes like java.util.Date follow the timezone we created.

If you want to use SimpleTimeZone rules in ZonedDateTime in java.time package, we can convert SimpleTimeZone to ZoneId by calling toZoneId() method.

Aside from the SimpleTimeZone constructor that I demonstrated in the example above, SimpleTimeZone has three more constructors. These constructors:
SimpleTimeZone(int rawOffset, String ID, int startMonth, int startDay, int startDayOfWeek, int startTime, int endMonth, int endDay, int endDayOfWeek, int endTime)

SimpleTimeZone(int rawOffset, String ID, int startMonth, int startDay, int startDayOfWeek, int startTime, int endMonth, int endDay, int endDayOfWeek, int endTime, int dstSavings)
Uses WALL_TIME as default time mode for startTime and endTime.

This constructor:
SimpleTimeZone(int rawOffset, String ID)
Doesn't have daylight time schedule. However, we can use setStartRule and setEndRule methods to set a start-rule and end-rule if we want to assign new DST schedule to SimpleTimeZone instance.

Month, day and day-of-week combinations that I explained above is applicable here. Take note that these methods(and their overloaded forms) use WALL_TIME as default time mode.

Next, let's discuss the methods that I used in the example above. observesDaylightTime returns true if a TimeZone instance observes DST. Otherwise, returns false. getRawOffset returns the offset of a time zone from UTC. This method doesn't include the effect of DST.

If you want to get offset from UTC with DST adjustment, use getOffset method. To set raw offset, use setRawOffset method. More information can be found in the documentation.

java.time Package

The classes defined in java.time package represent the principle date-time concepts, including instants, durations, dates, times, time-zones and periods. They are based on the ISO calendar system, which is the de facto world calendar following the proleptic Gregorian rules. All the classes are immutable and thread-safe.

This API is new and improved alternative to old date-and-time-related classes like java.util.Date. Since java 8, this API is recommended to be used for handling date and time.

In this topic, I'm gonna be discussing classes that are commonly used and follow ISO-8601 calendar system such as LocalDate, LocalTime, LocalDateTime and ZonedDateTime. If you want to use a calendar other than ISO calendar system, you should read the classes and documentation in java.time.chrono package.

Classes like Month, DayOfWeek, Year, YearMonth and MonthDay are also classes that follow ISO-8601 although they store single-value or partial date/time information compared to the classes mentioned above.

All the instances of the classes mentioned above are called temporal objects because those classes implement Temporal or its superinterface which is the TemporalAccessor. Also, the classes mentioned above are immutable and thread-safe.

If you want to extend this API like adding different computations of date and time, you should read the classes and documentation in java.time.temporal package.

To quantify date, use Period class. To quantify time, use Duration class.

Instant, LocalDate, LocalTime, LocalDateTime, OffsetTime, OffsetDateTime and ZonedDateTime Classes

We can access and obtain date and time from these classes: Instant, LocalDate, LocalTime, OffsetTime, LocalDateTime, OffsetDateTime and ZonedDateTime.

To format the values of these date-related classes, use DateTimeFormatter. I already discuss this class in this blogpost.

Getting Date/Time Information

To get the time in our system clock, we invoke now method. Take a look at this example.
import java.time.*;

public class SampleClass{

  public static void main(String[] args){
    Instant instant = Instant.now();
    System.out.println("Instant: " + instant);
    LocalDate locDate = LocalDate.now();
    System.out.println("Local Date: " + locDate);
    LocalTime locTime = LocalTime.now();
    System.out.println("Local Time: " + locTime);
    OffsetTime offsetTime = OffsetTime.now();
    System.out.println("Offset Time: " + offsetTime);
    LocalDateTime ldt = LocalDateTime.now();
    System.out.println("Local Date Time: " + ldt);
    OffsetDateTime odt = OffsetDateTime.now();
    System.out.println("Offset Date Time: " + odt);
    ZonedDateTime zdt = ZonedDateTime.now();
    System.out.println("Zoned Date Time: " + zdt);
  }
}

Result(may vary)
Instant: 2022-04-04T22:14:03.767119900Z
Local Date: 2022-04-05
Local Time: 06:41:03.792119900
Offset Time: 06:41:03.792119900+08:00
Local Date Time: 2022-04-05T06:41:03.792119900
Offset Date Time: 2022-04-05T06:41:03.792119900+08:00
Zone Date Time:
2022-04-05T06:41:03.792119900+08:00[Asia/Taipei]
Instant stores date and time(UTC+0). "T" stands for time and it separates date and time. "Z" stands for "Zulu" time which is another term for UTC time. LocalDate stores date without time. LocalTime stores time without date. OffsetTime stores time with offset from UTC.

LocalDateTime stores date and time. Time is adjusted based on the offset where the time is extracted. OffsetDateTime stores date and time with the offset from UTC. ZonedDateTime stores date and time with offset from UTC and ID of a time zone. Also, ZonedDateTime observes daylight saving time based on the rules of its ZoneRules that resides in its ZoneId.

Where possible, applications should use LocalDate, LocalTime and LocalDateTime to better model the domain. For example, a birthday should be stored in a code LocalDate. Bear in mind that any use of a time-zone, such as 'Europe/Paris', adds considerable complexity to a calculation.

Many applications can be written only using LocalDate, LocalTime and Instant, with the time-zone added at the user interface (UI) layer. For example, you get your system's date/time using ZonedDateTime. Then, display your system's time zone ID by calling getZone() which returns a ZoneId instance. If you wanna get offset only, call getOffset() which returns a ZoneOffset instance.

Then, call toLocalDate() that returns a LocalDate instance with the date of our ZonedDateTime instance. With this, we can compare dates easily without including time.

The offset-based date-time types OffsetTime and OffsetDateTime, are intended primarily for use with network protocols and database access. For example, most databases cannot automatically store a time-zone like 'Europe/Paris', but they can store an offset like '+02:00'.

Next, let's discuss how to get specific part of a date like month, day, year etc. This example demonstrates getter methods that get specific part of a date.
import java.time.*;
import java.time.temporal.ChronoField;

public class SampleClass{

  public static void main(String[] args){
    
    ZonedDateTime zdt = ZonedDateTime.now();
    
    LocalDate ld = zdt.toLocalDate();
    //These getter methods can be found in
    //LocalDate, LocalDateTime, OffsetDateTime
    //and ZonedDateTime
    System.out.println("Day of Month: " +
    ld.getDayOfMonth());
    System.out.println("Day of Week: " +
    ld.getDayOfWeek());
    System.out.println("Day of Year: " +
    ld.getDayOfYear());
    System.out.println("Month: " +
    ld.getMonth() + " | " + ld.getMonthValue());
    System.out.println("Year: " + ld.getYear());
    System.out.println();
    
    //These getter methods can be found in
    //LocalTime, LocalDateTime, OffsetTime,
    //OffsetDateTime and ZonedDateTime
    LocalTime lt = zdt.toLocalTime();
    System.out.println("Hour: " + lt.getHour());
    System.out.println("Minutes: " + 
    lt.getMinute());
    System.out.println("Seconds: " +
    lt.getSecond());
    System.out.println("Nano-of-Second: " +
    lt.getNano());
    System.out.println();
    
    //This getter method can be found in
    //OffsetTime, OffsetDateTime and 
    //ZonedDateTime
    System.out.println("Offset: " + 
    zdt.getOffset());
    
    //This getter method can be found in
    //ZonedDateTime
    System.out.println("Zone ID: " +
    zdt.getZone());
    System.out.println();
    
    //These getter methods can be found in
    //LocalDate
    System.out.println("Length of Month: "+
    ld.lengthOfMonth());
    System.out.println("Length of Year: "+
    ld.lengthOfYear());
    System.out.println("Is leap year? "+
    ld.isLeapYear());
    System.out.println("Era: "+
    ld.getEra());
    System.out.println();
    
    //These getter methods can be found in
    //LocalTime
    System.out.println("Nanoseconds of Day: " +
    lt.toNanoOfDay());
    System.out.println("Seconds of Day: " +
    lt.toSecondOfDay());
    System.out.println();
    
    //get(TemporalField field) method
    //can be found in every temporal
    //implementations highlighted in this topic
    if(zdt.isSupported(ChronoField.AMPM_OF_DAY))
      System.out.println("AM/PM of day: " +
      zdt.get(ChronoField.AMPM_OF_DAY));
  }
}

Result
Day of Month: 7
Day of Week: THURSDAY
Day of Year: 97
Month: APRIL | 4
Year: 2022

Hour: 10
Minutes: 42
Seconds: 31
Nano-of-Second: 432663400

Offset: +08:00
Zone ID: Asia/Taipei

Length of Month: 30
Length of Year: 365
Is leap year? false
Era: CE

Nanoseconds of Day:
38551432663400
Seconds of Day: 38551

AM/PM of Day: 0
getDayOfMonth() returns the day of the month, getDayOfWeek() returns a DayOfWeek enum that stores the day of the week and getDayOfYear() returns the day of the year.

getMonth() returns a Month enum that denotes the month. getMonthValue() returns the numerical representation of the month. In ISO calendar system, A value of 1 represents January. A value of 12 represents December. In the example above, the value 4 represents April. getYear() returns the year.

getHour() returns hour, getMinute() returns minutes and getSecond() returns seconds. getNano() returns nano-of-second or a fraction of a second in nanoseconds. getOffset() returns a ZoneOffset instance that stores the offset from UTC. getZone() returns a ZoneID instance that stores the ID of the time zone.

lengthOfMonth() returns the total number of days in a month. In the example above, the total number of days in March 2022 is 30. lengthOfYear() returns the total number of days in a year. In the example above, the total number of days in 2022 is 365. isLeapYear() returns true is a year is a leap year. Otherwise, returns false. getEra() returns an IsoEra instance which holds the current era of the specified date.

toNanoOfDay() returns the elapsed time in a day in nanoseconds. toSecondOfDay() returns the elapsed time in a day in seconds. get(TemporalField field) gets the value of the specified field from this date as an int.

In the example above, ChronoField.AMPM_OF_DAY holds a value that determines if this date and time is in AM or PM. If the field returns 0, date and time is AM. if 1, date and time is PM. This method will throw an exception if the given field is not supported by this instance or the value of the given field exceeds the max range of int. If the latter happened, use getLong(TemporalField field).

TemporalField is a field of date-time, such as month-of-year or minute-of-hour. ChronoField is an enum that contains a standard set of fields that are compatible with TemporalField. Other fields that are TemporalField type can be found in IsoFields, WeekFields and JulianFields.

isSupported(TemporalField field) returns true if the given field is supported by the class. In the example above, LocalDate supports ChronoField.AMPM_OF_DAY field that's why isSupported returns true.

Setting Date/Time Information

There are different ways of setting date and time. However, one of the common ways of setting date and time is by using of method. Take a look at this example.
import java.time.*;

public class SampleClass{

  public static void main(String[] args){
    //of(int year, int month, int dayOfMonth)
    LocalDate ld = LocalDate.of(2020, 5, 30);
    System.out.println("LocalDate: " + ld);
    
    //of(int hour, int minute)
    LocalTime lt = LocalTime.of(10, 45);
    System.out.println("LocalTime: " + lt);
    
    //of(LocalTime time, ZoneOffset offset)
    OffsetTime ot = 
    OffsetTime.of(LocalTime.of(20, 30),
                  ZoneOffset.of("+10:00"));
    System.out.println("OffsetTime: " + ot);
    
    //of(LocalDate date, LocalTime time)
    LocalDateTime ldt = 
    LocalDateTime.of(LocalDate.of(2015, 1, 15),
                     LocalTime.of(10, 10));
    System.out.println("LocalDateTime: " + ldt);
    
    //of(LocalDateTime dateTime, ZoneOffset offset)
    OffsetDateTime odt = 
    OffsetDateTime.of(ldt,
                      ZoneOffset.of("+08:00"));
    System.out.println("OffsetDateTime: " + odt);
    
    //of(LocalDateTime localDateTime, ZoneId zone)
    ZonedDateTime zdt = 
    ZonedDateTime.of(ldt,
                     ZoneId.of("Asia/Taipei"));
    System.out.println("ZonedDateTime: " + zdt);
  }
}

Result
LocalDate: 2020-05-30
LocalTime: 10:45
OffsetTime: 20:30+10:00
LocalDateTime: 2015-01-15T10:00+08:00
ZonedDateTime: 2015-01-15T10:00+08:00[Asia/Taipei]
Take note that of method has overloaded forms and each overloaded form may be unique to each temporal implementation.

Parsing Date/Time

If we want to parse a text to a temporal object, we use parse method. This method can be seen from every temporal implementations highlighted in this topic. This method has two forms:
parse(CharSequence text)
parse(CharSequence text, DateTimeFormatter formatter)
This example demonstrates parse(CharSequence text)
import java.time.*;

public class SampleClass{

  public static void main(String[] args){
    
    LocalDate ld = LocalDate.parse("2010-10-20");
    System.out.println("LocalDate: " + ld);
    
    ZonedDateTime zdt = 
    ZonedDateTime.parse(
    "2007-12-03T10:15:30+01:00[Europe/Paris]");
    System.out.println("ZonedDateTime: " + zdt);
  }
}

Result
LocalDate: 2010-10-20
ZonedDateTime: 
2007-12-03T10:15:30+01:00[Europe/Paris]
Take note that an ISO format must be followed when the first overloaded form of parse is used. For example, parse(CharSequence text) in LocalDate follows DateTimeFormatter.ISO_LOCAL_DATE. in ZonedDateTime, first overloaded form of parse follows DateTimeFormatter.ISO_ZONED_DATE_TIME. More information about formatting can be found in the DateTimeFormatter.

Adding/Subtracting Date/Time

To subtract or add date or time to a temporal implementations highligted in this topic, we use methods with minus or plus prefix. This example demonstrates some of those methods.
import java.time.*;

public class SampleClass{

  public static void main(String[] args){
  
    LocalDate ld = LocalDate.of(2020, 5, 20);
    //These methods can be found in LocalDate,
    //LocalDateTime, OffsetDateTime and
    //ZonedDateTime
    ld = ld.plusDays(1);
    ld = ld.plusMonths(1);
    ld = ld.plusYears(1);
    System.out.println("Date(plus): " + ld);
    
    ld = ld.minusDays(1);
    ld = ld.minusMonths(1);
    ld = ld.minusYears(1);
    System.out.println("Date(minus): " + ld);
    
    LocalTime lt = LocalTime.of(10, 45, 15);
    //These methods can be found in LocalTime,
    //LocalDateTime, OffsetTime, OffsetDateTime
    //and ZonedDateTime
    lt = lt.plusHours(1);
    lt = lt.plusMinutes(5);
    lt = lt.plusSeconds(15);
    System.out.println("Time(plus): " + lt);
    
    lt = lt.minusHours(1);
    lt = lt.minusMinutes(5);
    lt = lt.minusSeconds(15);
    System.out.println("Time(minus): " + lt);
    
  }
}

Result
Date(plus): 2021-06-21
Date(minus): 2020-05-20
Time(plus): 11:50:30
Time(minus): 10:45:15
The methods demonstrated above are easy to understand. Now, let's discuss plus and minus methods. These methods can found in every date-time-related classes highlighted in this topic and both of them have two overloaded forms:
plus(long amountToAdd, TemporalUnit unit)
plus(TemporalAmount amountToAdd)
minus(long amountToSubtract, TemporalUnit unit)
minus(TemporalAmount amountToSubtract)
This example demonstrates plus and minus methods.
import java.time.*;
import java.time.temporal.ChronoUnit;

public class SampleClass{

  public static void main(String[] args){
    LocalDate ld = LocalDate.of(2020, 5, 20);
    
    //plus(long amountToAdd, TemporalUnit unit)
    if(ld.isSupported(ChronoUnit.DAYS))
      ld = ld.plus(5, ChronoUnit.DAYS);
    else System.out.println("DAYS not supported!");
    
    //plus(TemporalAmount amountToAdd)
    ld = ld.plus(Period.ofYears(1));
    System.out.println("LocalDate: " + ld);
    
    LocalTime lt = LocalTime.of(16, 30);
    
    //minus(long amountToSubtract, TemporalUnit unit)
    if(lt.isSupported(ChronoUnit.MINUTES))
      lt = lt.minus(15, ChronoUnit.MINUTES);
      
    //minus(TemporalAmount amountToSubtract)
    lt = lt.minus(Duration.ofHours(1));
    System.out.println("LocalTime: " + lt);
  }
}

Result
LocalDate: 2021-05-25
LocalTime: 15:15
TemporalUnit A unit of date-time, such as Days or Hours. ChronoUnit is an enum that contains a standard set of fields that are compatible with TemporalUnit. Other fields that are TemporalUnit type can be found in IsoFields.

isSupported(TemporalUnit unit) returns true if the given field is supported by the class. In the example above, LocalDate supports ChronoUnit.DAYS field that's why isSupported returns true.

TemporalAmount is a framework-level interface defining an amount of time, such as "6 hours", "8 days" or "2 years and 3 months". This interface has two concrete implementations: Duration and Period.

Period is just like LocalDate. However, Period follows a different format and can be used to store single-value date information like months, years, days, etc.

Duration is just like LocalTime. However, Duration follows a different format and can be used to store single-value time information like hours, minutes, seconds, etc.

Modifying Date/Time

To modify date and time in temporal objects, we use with or methods with 'with' prefix. Take note that the methods throw an exception if the given value is not in the range of the specified field. This example demonstrates some of those methods.
import java.time.*;

public class SampleClass{

  public static void main(String[] args){
    
    LocalDate ld = LocalDate.of(2015, 6, 15);
    //These methods can be found in LocalDate,
    //LocalDateTime, OffsetDateTime and
    //ZonedDateTime
    ld = ld.withDayOfMonth(5);
    ld = ld.withMonth(4);
    ld = ld.withYear(2020);
    System.out.println("LocalDate: " + ld);
    
    LocalTime lt = LocalTime.of(10, 15, 10);
    //These methods can be found in LocalTime,
    //OffsetTime, OffsetDateTime and
    //ZonedDateTime
    lt = lt.withHour(2);
    lt = lt.withMinute(5);
    lt = lt.withSecond(15);
    System.out.println("LocalTime: " + lt);
    System.out.println();
    
    OffsetTime ot = 
    OffsetTime.of(lt, ZoneOffset.of("+02:00"));
    System.out.println("Original: " + ot);
    //These methods can be found in OffsetTime,
    //OffsetDateTime
    ot = ot.withOffsetSameInstant(
    ZoneOffset.of("+03:00"));
    System.out.println("withOffsetSameInstant: " +
    ot);
    ot = ot.withOffsetSameLocal(
    ZoneOffset.of("+04:00"));
    System.out.println("withOffsetSameLocal: " +
    ot);
    System.out.println();
    
    ZonedDateTime zdt = ZonedDateTime.of(
    ld, lt, ZoneId.of("Europe/Paris"));
    System.out.println("Original: " + zdt);
    //These methods can be found in
    //ZonedDateTime
    zdt = zdt.withZoneSameInstant(ZoneId.of("Asia/Taipei"));
    System.out.println("withZoneSameInstant: " +
    zdt);
    zdt = zdt.withZoneSameLocal(ZoneId.of("Europe/Paris"));
    System.out.println("withZoneSameLocal: " +
    zdt);
  }
}

Result
LocalDate: 2020-04-05
LocalTime: 02:05:15

Original: 02:05:15+02:00
withZoneSameInstant
03:03:15+03:00
03:03:15+04:00

Original: 
2020-04-05T02:05:15+02:00[Europe/Paris]
withZoneSameInstant
2020-04-05T02:08:15+08:00[Asia/Taipei]
withZoneSameLocal
2020-04-05T02:08:15+02:00[Europe/Paris]
withDayOfMonth, withMonth and withYear create new instances with the given day of month, month and year. withHour, withMinute and withSecond create new instances with the given hour, minutes and seconds.

withOffsetSameInstant creates a new instance with the given offset. Time will adjust based on the previous and new offset. withOffsetSameLocal creates a new instance with the given offset. The time won't be adjusted.

withZoneSameInstant creates a new instance with the given ZoneId and its offset. Time will adjust based on the rules and offset of the previous ZoneId to rules and offset of the new ZoneId. withZoneSameLocal creates a new instance with the given ZoneId and its offset from UTC. Time won't be adjusted.

Take note that ZoneId has rules that are defined by ZoneRules. ZonedDateTime follows the rules of ZoneId. "Europe/Paris" ZoneId has rules that follow daylight saving time. Thus, any ZonedDateTime that uses "Europe/Paris" Zone ID observes daylight saving time.

There are three methods with 'with' prefix that I wanna discuss. withEarlierOffsetAtOverlap, withLaterOffsetAtOverlap and withFixedOffsetZone can be found in ZonedDateTime. Take a look at this example.
import java.time.*;

public class SampleClass{

  public static void main(String[] args){
    ZonedDateTime zdt = 
    ZonedDateTime.of(
    LocalDateTime.of(2022, 10, 30, 2, 0),
    ZoneId.of("Europe/Paris"));
    
    ZonedDateTime fixedOffsetZone = 
    zdt.withFixedOffsetZone();
    fixedOffsetZone = 
    fixedOffsetZone.plusHours(1);
    System.out.println("Fixed: " + 
    fixedOffsetZone);
    
    System.out.println();
    System.out.println("Original: " + zdt);
    zdt = zdt.plusHours(1);
    System.out.println("Plus 1hr: " + zdt);
    System.out.println();
    
    zdt = zdt.withEarlierOffsetAtOverlap();
    System.out.println("Early Overlap: " + zdt);
    zdt = zdt.withLaterOffsetAtOverlap();
    System.out.println("Later Overlap: " + zdt);
  }
}

Result
Fixed: 2022-10-30T03:00+02:00

Original: 2022-10-30T02:00+02:00[Europe/Paris]
Plus 1hr: 2022-10-30T02:00+01:00[Europe/Paris]

Early Overlap: 2022-10-30T02:00+02:00[Europe/Paris]
Later Overlap: 2022-10-30T02:00+01:00[Europe/Paris]
In the example above, we can see that the time in fixedOffsetZone variable didn't adjust because there's no ZoneId is present in the instance. Thus, no daylight rules to follow. withFixedOffsetZone discards the ZoneId of a ZonedDateTime instance. Making it effectively equivalent to OffsetDateTime.

withLaterOffsetAtOverlap returns a ZonedDateTime with the later valid offset of the ZonedDateTime that invoked this method. withEarlierOffsetAtOverlap returns a ZonedDateTime with the earlier valid offset of the ZonedDateTime that invoked this method.

Overlap happens during transition from daylight saving time to standard time. Gap happens during transition from standard time to daylight saving time. This documentation has a definition about gaps and overlaps.

Next, let's discuss with method. This method can be found in every temporal implementations highlighted in this topic and this method has two forms:
with(TemporalAdjuster adjuster)
with(TemporalField field, long newValue)
This example demonstrates with method and its overloaded forms.
import java.time.*;
import java.time.temporal.TemporalAdjusters;
import java.time.temporal.ChronoField;

public class SampleClass{

  public static void main(String[] args){
    LocalDate ld = LocalDate.of(2020, 5, 20);
    
    //with(TemporalField field, long newValue)
    if(ld.isSupported(ChronoField.YEAR))
      ld.with(ChronoField.YEAR, 2025);
    
    //with(TemporalAdjuster adjuster)
    ld = ld.with(temporal -> 
            temporal.plus(Period.ofMonths(2)));
    ld = ld.with(TemporalAdjusters.lastDayOfMonth());
    
    System.out.println("LocalDate: " + ld);
  }
}

Result
LocalDate: 2020-07-31
TemporalAdjuster is a functional interface used for adjusting a temporal object. You can create your own lambda functions that return Temporal or use the pre-defined functions from TemporalAdjusters.

TemporalField is a field of date-time, such as month-of-year or minute-of-hour. ChronoField is an enum that contains a standard set of fields that are compatible with TemporalField. Other fields that are TemporalField type can be found in IsoFields, WeekFields and JulianFields.

Comparing Date/Time

Use equals method to compare date and time of two temporal objects with the same type. This method can be found in every temporal implementations highlighted in this topic. Take a look at this example.
import java.time.*;

public class SampleClass{

  public static void main(String[] args){
  
    LocalDate ld1 = LocalDate.of(2020, 5, 20);
    LocalDate ld2 = LocalDate.of(2020, 5, 20);
    LocalDate ld3 = LocalDate.of(2015, 5, 20);
    System.out.println("ld1 is equal to ld2? "
                       + ld1.equals(ld2));
    System.out.println("ld1 is equal to ld3? "
                       + ld1.equals(ld3));
  }
}

Result
ld1 is equal to ld2? true
ld1 is equal to ld3? false
take note that equals method works differently in every temporal objects. For example, in LocalDate, only date is being compared. In LocalTime, only time is compared. In LocalDateTime, date and time are used for comparing two instances. In ZonedDateTime, date, time and ZoneId are used for comparing two instances.

Querying Date/Time

a temporal object can be queried to extract information from it. To do that, we use the query method which can be found in every temporal implementations highlighted in this topic. Take a look at this example.
import java.time.*;
import java.time.temporal.TemporalQueries;
import java.time.chrono.Chronology;
import java.time.chrono.IsoChronology;

public class SampleClass{

  public static void main(String[] args){
  
    ZonedDateTime zdt = 
    ZonedDateTime.of(
    LocalDate.of(10, 5, 20),
    LocalTime.of(10, 15),
    ZoneId.of("Europe/Paris"));
    
    Chronology ch = 
    zdt.query(TemporalQueries.chronology());
    System.out.println("Chronology: " + ch);
    boolean isChrono = ch instanceof IsoChronology;
    System.out.println("is IsoChronology? " +
                       isChrono);
    
    //Quarter Year: standard calendar divides
    //months in a year in a quarter: Q1, Q2, Q3, Q4
    //January, February, and March (Q1) April, 
    //May, and June (Q2)
    //July, August, and September (Q3)
    //October, November and December (Q4)
    int quarterYear = zdt.query(temporal ->
    {
      LocalDate ld = LocalDate.from(temporal);
      return (ld.getMonthValue() / 3) + 1;
    });
    System.out.println("Quarter Year: " + 
                       quarterYear);
    
  }
}

Result
Chronology: ISO
is IsoChronology? true
Quarter Year: 2
TemporalQuery is a functional interface used for querying a temporal object. We can create our own lambda functions for TemporalQuery. Additionally, we can use pre-defined functions in TemporalQueries.

Chronology is an interface that defines a calendar system, used to organize and identify dates. Java supports different types of calendars and those calendars implement this interface. Most of the time, the default calendar system used by java platforms is the ISO calendar system. IsoChronology defines the rules of the ISO calendar system.

LocalDate.from extracts date-related information from the given temporal object and returns LocalDate instance with the extracted information.

Duration and Period Classes

Duration and Period implement TemporalAmount that is used as a parameter of methods such as plus, minus and with.

Duration class models a quantity or amount of time in terms of seconds and nanoseconds. Period class models a quantity or amount of time in terms of years, months and days. This example demonstrates some methods of Duration and Period.
import java.time.*;

public class SampleClass{

  public static void main(String[] args){
    Duration hours = Duration.parse("P2DT10H10M5S");
    Duration minutes = Duration.ofMinutes(30);
    Duration seconds = Duration.ofSeconds(15);
    
    hours = hours.plusMinutes(30);
    System.out.println("Duration1: " + 
    hours);
    minutes = minutes.plusMinutes(15);
    System.out.println("Duration2: " + 
    minutes);
    seconds = seconds.plusSeconds(15);
    System.out.println("Duration3: " + 
    seconds);
    System.out.println();
    
    System.out.println("toHoursPart: " + 
    hours.toHoursPart());
    System.out.println("toMinutesPart: " + 
    hours.toMinutesPart());
    System.out.println("toMinutes: " + 
    hours.toMinutes());
    System.out.println();
    
    Period date = Period.parse("P2022Y5M15D");
    Period months = Period.ofMonths(5);
    Period years = Period.ofYears(5);
    
    System.out.println("Period1: " + date);
    System.out.println("Period2: " + months);
    System.out.println("Period3: " + years);
    System.out.println();
    
    System.out.println("getYears: " + 
    date.getYears());
    System.out.println("getMonths: " + 
    date.getMonths());
    System.out.println("getDays: " + 
    date.getDays());
    System.out.println();
    
    Period date2 = Period.parse("P2022Y15M10D");
    System.out.println("Not normalized: " + date2);
    date2 = date2.normalized();
    System.out.println("Normalized: " + date2);
  }
}

Result
Duration1: PT58H40M5S
Duration2: PT45M
Duration3: PT30S

toHoursPart: 10
toMinutesPart: 40
toMinutes: 3520

Period1: 2022Y5M15D
Period2: P5M
Period3: P5Y

getYears: 2022
getMonths: 5
getDays: 15

Not normalized: P2022Y15M10D
Normalized: P2023Y3M10D
If you read several topics before this topic, you will notice that Duration and Period have similarities with the classes I've discussed previously. Duration.ofMinutes creates a Duration instance with the given minutes. Duration.ofSeconds creates a Duration instance with the given seconds. Duration.parse obtains a Duration from a text that follows particular textual formats such as PnDTnHnMn.nS. More information about Duration.parse formats can be found here.

Notice that the format P2022Y5M15D I put in Duration.parse is not equivalent to the output of System.out.println("Duration1: " + hours);. That's because Duration.toString formats the output String into particular formats such as PT8H6M12.345S. More information about Duration.toString formats can be found here.

toHoursPart method extracts the hours part in a Duration object. toMinutesPart method extracts the minutes part in a Duration object.toMinutes gets the number of minutes in this Duration object. plusMinutes creates a Duration object with the added minutes. plusSeconds creates a Duration object with the added seconds.

Period.ofMonths creates a Period instance with the given months. Period.ofYears creates a Period instance with the given seconds. Period.getYears returns the extracted years from the Period instance. Period.getMonths returns the extracted months from the Period instance. Period.getDays returns the extracted days from the Period instance.

Period.parse obtains a Period from a text that follows particular textual formats such as PnYnMnD. More information about Period.parse formats can be found here.

Period.normalized method returns a copy of this Period object with the years and months normalized. This normalizes the years and months units, leaving the days unit unchanged. The months unit is adjusted to have an absolute value less than 12, with the years unit being adjusted to compensate. For example, a period of "1 Year and 15 months" will be normalized to "2 years and 3 months".