Calendars

Most Java developers are familiar with the Gregorian calendar and perhaps even Julian Day, but to have a well localized application it is often neccessary to go beyond that.
 
To do business across Asia you need to be able to handle the Chinese, Japanese, and Buddhist calendars.  Muslim nations require an Islamic Calendar, and Israel needs a Hebrew calendar.  Each of these poses problems for the internationalization developer, but thankfully core Java and, to a greater extent, ICU provide some excellent capabilities for handling diverse calendars.

Gregorian Calendar

Problem:

You want to create a standard calendar as used in the west.

Solution:

The Gregorian Calendar is the predominant calendar of western countries today. To create a new Gregorian Calendar call the Singleton getInstance method on the Calendar class. if you specify a Locale as an argument the Calendar returned will reflect the first day of the week etc. of the Locale.

Locale locale = new Locale("en","US");
Calendar calendar = Calendar.getInstance(locale);

 

Hebrew Calendar

 Problem:

You want to use a Hebrew Calendar in your application.

Solution:

The Hebrew calendar is a very interesting lunar-solar calendar that poses some interesting challenges for the pogrammer.  It uses a leap month to synch the lunar and solar components.  The first day of the year can vary depending upon how it will affect jewish holidays, and the day ends at sundown.

 

Thankfully icu4j provides an excellent Hebrew calendar.  It is a simple manner to create a calendar, and the usage is similar to a standard Gregorian calendar.

 

To get an instance of a Hebrew calendar simply call the getInstance method passing in a ULocale that has a calendar type specified.

//Specify the calendar type on the ULocale

ULocale he = new ULocale("he_IL@calendar=hebrew");

//Call getInstance passing the ULocale

Calendar cal = Calendar.getInstance(he);

 

 

To get the months of the Jewish Calendar get an instance of DateFormatSymbols with a ULocale specifying the Hebrew Calendar.

//Get a Hebrew calendar for a Hebrew locale

ULocale he = new ULocale("he_IL@calendar=hebrew");

//Get a Hebrew calendar on an English locale

ULocale en = new ULocale("en@calendar=hebrew");

//Get DateFormatSymols for the locales

DateFormatSymbols dfsHe = DateFormatSymbols.getInstance(he);

DateFormatSymbols dfsEn = DateFormatSymbols.getInstance(en);

//Get arrays of months

String[] hebrewMonths = dfsHe.getMonths();

String[] englishMonths = dfsEn.getMonths();

//loop through the arrays and print out the English and Hebrew months

for(int x = 0; x < hebrewMonths.length; x++){

   System.out.println(englishMonths[x] + " = " + hebrewMonths[x]);

}

 

The output is:

 

Tishri = תשרי

Heshvan = חשון

Kislev = כסלו

Tevet = טבת

Shevat = שבט

Adar I = אדר ראשון

Adar = אדר שני

Nisan = ניסן

Iyar = אייר

Sivan = סיון

Tamuz = תמוז

Av = אב

Elul = 

 

 

 

 

 

Japanese Calendar

 Problem:

You want a calendar that is most familiar to your Japanese users.

Solution:

The Japanese calendar is identical to the Gregorian calendar, except the year is represented by the year of the emperor's reign.  It is currently the 20th year of the Heisei emperor's reign, so the year is Heisei 20.

You can create a Japanese calendar by using icu4j .

To create a calendar object:

ULocale ul = new ULocale("ja_JP@calendar=japanese");

 

To format a simple date example:

ULocale ul = new ULocale("en@calendar=japanese");

DateFormat df = DateFormat.getDateInstance(DateFormat.FULL, ul);

System.out.println(df.format(new Date()));

 

 The result is "Friday, November 28, 20 Heisei".  If we change the ULocale to japanese the output is "平成20年11月28日金曜日"

To get the era of the current date:

//Get Japanese locale with attribute specifying japanese calendar

ULocale ul = new ULocale("ja_JP@calendar=japanese"); 

//calling getInstance using the locale bearing a calendar type will give us the appropriate locale

Calendar cal = Calendar.getInstance(ul);

//Output the era number.  This is a largely arbitrary number, but it can be used to compare

//against the static constants on Japanese Calendar

System.out.println(cal.get(JapaneseCalendar.ERA) + " Heisei era = " + JapaneseCalendar.HEISEI);

 

Chinese Calendar

Problem:

You want to create a Chinese calendar.

Solution:

Use icu4j to create a calendar instance.

//Get a ULocale for Chinese written in simplified Chinese for China using a Chinese calendar
ULocale chinese = new ULocale("zh_Hans_CN@calendar=chinese");
//Get a Calendar instance specifying our ULocale.  This will give us a Chinese calendar
Calendar c = Calendar.getInstance(chinese);
//Get a DateTimeFormat appropriate for the calendar.
DateFormat df = c.getDateTimeFormat(DateFormat.FULL, DateFormat.FULL, chinese);
//Output a formatted date.
System.out.println(df.format(new Date()));
//Output the pattern.
System.out.println(((SimpleDateFormat)df).toPattern());

 

Discussion:

The Chinese calendar is one of the most interesting and accurate calendars in use.  It is a lunisolar calendar that introduces a leap month to handle the justification of the lunar and solar systems.  The months are lunar months, but the leap month is added to avoid the calendar diverging from the solar cycle.  The calendar is still in wide use across Asia.

 

The Chinese calendar uses a system of measuring years in 60 year cycles.  The cycles are broken into earthly and heavenly stems of the elements and branches with animal names. 

 

Icu4j offers and excellent implementation of the Chinese calendar. It is very easy to implement.  By default it will output the year as "year x cycle".  I will demonstrate in another recipe how to create a more interesting format.

Find the Chinese zodiac for a Gregorian year

Problem:

You want to know the Chinese zodiac sign for a Gregorian year.

Solution:

Use the icu4j Chinese calendar to get the appropriate branch.  The following code illustrates and example:

//An array of heavenly and earthly elements.  The elements start with wood and end with
//water, but since we are using a modulus to determine the element we have moved earthly water
//to position 0
String[] enElements = {"Water","Wood","Wood","Fire","Fire","Earth",
        "Earth","Metal","Metal","Water"};
String[] zhElements = {"癸","甲","乙","丙","丁","戊","己","庚","辛","壬"};

//The 12 animals of the Chinese calendar.  Rat is first and Boar is last but we have
//moved boar to position zero so our modulus returns a correct result.
String[] enAnimals = {"Boar","Rat","Ox","Tiger","Rabbit","Dragon","Snake",
        "Horse","Sheep","Monkey","Rooster","Dog"};
String[] zhAnimals = {"亥","子","丑","寅","卯","辰","巳",
        "午","未","申","酉","戌"};
//Get an instance of a Chinese calendar by specifying a calendar type of Chinese on our ULocale
ULocale chinese = new ULocale("zh_Hans_CN@calendar=chinese");
Calendar c = Calendar.getInstance(chinese);
//Get a Gregorian calendar instance for setting our dates
Calendar g = Calendar.getInstance();
//We will set the year, month, and day since the stem and branch change on
//Chinese New Year, not January 1st.
//Set the year
g.set(Calendar.YEAR, 2008);
//Set the month.  Remember the month is 0 based so January is 0 and December is 11.
g.set(Calendar.MONTH, 0);
//Set the day of the month
g.set(Calendar.DATE, 7);
//Output the date to confirm
System.out.println(DateFormat.getDateInstance().format(g.getTime()));
//Set the Chinese calendar for your specified date
c.setTimeInMillis(g.getTimeInMillis());
//Get the element stem.  The current year mod 10 will give us a value between 0 and 9.
//We use that value to retrieve the element from the elements array.
String enElement = enElements[c.get(Calendar.YEAR)%10];
String zhElement = zhElements[c.get(Calendar.YEAR)%10];
//Get the animal branch. The current year mod 12 will give us a value between 0 and 11.
//We use that value to retrieve the animal from the animals array.
String enAnimal = enAnimals[c.get(Calendar.YEAR)%12];
String zhAnimal = zhAnimals[c.get(Calendar.YEAR)%12];
System.out.println(enElement + " " + enAnimal);
System.out.println(zhElement + zhAnimal);

Discussion:

The Chinese Calendar uses a 60 year cycle.  The year is represented by a branch representing heavenly and earthly examples for the 5 elements and the 12 animals of the zodiac.  The zodiac changes based upon the Chinese New Year, so the calculations are little more complex and require the month and day to be trully accurate.

 

 

Get the name of the current month

Problem:

You want to retrieve a localized display name of the current month.

Solution:

To get the name of a month you first retrieve an instance of Calendar for the desired locale. You use calendar.get(int) passing in the constant Calendar.MONTH to get the current month of the calendar. You then use that to pull the correct value out of the array of month names returned by getMonths() on an instance of DateFormatSymbols.

To get the display name for the current month:

//Get an instance of Calendar for a Japanese locale

Calendar calendar = Calendar.getInstance(Locale.JAPANESE);

//Get a new DateFormatSymbols object for the Japanese locale

DateFormatSymbols dfs = new DateFormatSymbols(Locale.JAPANESE);

//Get an Array of months

String[] months = dfs.getMonths();

System.out.println(months[calendar.get(Calendar.MONTH)]);


 

To loop through all of the months for a locale:

//Get a new DateFormatSymbols object for the Japanese locale

DateFormatSymbols dfs = new DateFormatSymbols(Locale.FRENCH);

//Get the months String[] months = dfs.getMonths();

//Months are zero based, so loop from 0 to 11

for(int x = 0; x < 12; x++){

  System.out.println(months[x]);

}

Get the first day of the week

Problem:

You want to find the first day of the week for a locale and display it.

Solution:

To get the first day of the week for a calendar in a particular locale you need to first get the Calendar instance by passing in the Locale, then retrieve a DateFormatSymbols object using the same Locale. You can then use the DateFormatSymbols to retrieve an array of weekdays and use the numeric first day of week value returned from the Calendar to get the a human readable first day of week.

 

To get a human readable first day of week for Great Britain.

Locale locale = new Locale("en","GB");

Calendar calendar = Calendar.getInstance(locale);

DateFormatSymbols dfs = new DateFormatSymbols(locale); System.out.println(dfs.getWeekdays()[calendar.getFirstDayOfWeek()]);

 

To do the same for Japan using the static Locale shortcut:

Calendar calendar = Calendar.getInstance(Locale.JAPANESE);

DateFormatSymbols dfs = new DateFormatSymbols(Locale.JAPANESE);

System.out.println(dfs.getWeekdays()[calendar.getFirstDayOfWeek()]);

Add time to Calendar

Problem:

You need to add or subtract time from a calendar.

Solution:

There are two ways to perform date math with a Calendar, use add(int,int) or roll(int,int). Roll will increment or decrement the specified field until the maximum or minimum value is reached, and then it will begin again. Adding 13 months to the calendar with roll will not increment the year.

Add will bubble up such that adding 13 months to a Calendar will also increment the year. To get the current date and add 3 months:

Calendar c = Calendar.getInstance(Locale.GERMAN); System.out.println(c.getTime()); c.add(Calendar.MONTH, 3); System.out.println(c.getTime());

To get the current date and subtract 7 weeks:

Calendar c = Calendar.getInstance(Locale.GERMAN); System.out.println(c.getTime()); c.add(Calendar.WEEK_OF_MONTH, -7); System.out.println(c.getTime());

Get an array of Holidays

 Problem:

You want to know what holidays are common for a locale.

Solution:

Icu4j contains a system to help the Java developer handle international holidays.  The Holiday class is simple to use, and has failry complete data.

 

To get an Array of all Holidays for Mexico:

//Get a ULocale

ULocale locale = new ULocale("es_MX");

//Get an array of Holidays

Holiday[] holidays = Holiday.getHolidays(locale);

//Loop through all of the Holidays and output the localized display name

for(int x = 0; x < holidays.length; x++){

    System.out.println(holidays[x].getDisplayName(locale));

}

 

The output:

 

New Year's Day

Constitution Day

Benito Juárez Day

May Day

Cinco de Mayo

Navy Day

Independence Day

Día de la Raza

All Saints' Day

Day of the Dead

Revolution Day

Flag Day

Christmas

 

 

Find the date for the Chinese New Year

Problem:

You want to find the date of the Chinese new year for a given year.

Solution:

Use icu4j's Chinese calendar to perform the calculations.  You will specify a year in a Gregorian calendar and use that to set the time of the Chinese calendar.  Then set the calendar tot he first month and first day.

//Get a ULocale for Chinese written in simplified Chinese for China using a Chinese calendar
ULocale chinese = new ULocale("zh_Hans_CN@calendar=chinese");
//Get a Calendar instance specifying our ULocale.  This will give us a Chinese calendar
Calendar c = Calendar.getInstance(chinese);
//Get a Gregorian calendar instance
Calendar c2 = Calendar.getInstance();
//Set the year you want to find new Years for.  We will also set the month
//to half way through the year since the Chinese New Year won't occur until perhaps late February
c2.set(Calendar.YEAR, 2008);
c2.set(Calendar.MONTH, 5);
//Set the year of the Chinese Calendar
c.setTimeInMillis(c2.getTimeInMillis());
//Set the month to the first month.  Remember months are zero based
c.set(Calendar.MONTH, 0);
//Set the day to the first.  Remember dates are one based.
c.set(Calendar.DATE, 1);
//Output the date.  getTime() returns a Gregorian date.
System.out.println(c.getTime());

The output:

Thu Feb 07 22:35:16 EST 2008 Shows us the New Years occured on Feb 7th in 2008.  The time output is irrelevant.

 Discussion:

The length of the Chinese calendar's year varies from year to year.  Since the Calendar uses a leap month, the New Year can occur anywhere from late January to late February. 

 

Get all the Era names for the Japanese Calendar

Problem:

You want to retrieve all of the era names for the Japanese calendar.

Solution:

Use the icu4j Japanese calendar and DateFormatSymbols to retrieve the era names.

//Get a ULocale that specifies a Japanese Calendar in Japanese
ULocale jp = new ULocale("ja_JP@calendar=japanese");
//Get a Japanese calendar instance
Calendar jc = Calendar.getInstance(jp);
//The era field contains the numeric identifier for the reigning emperor at the current time
System.out.println(jc.get(Calendar.ERA));
//Get an instance of the DateFormatSymbols for Japanese using the Japanese calendar
DateFormatSymbols dfs = DateFormatSymbols.getInstance(jp);
//getEras() returns a String array of era names
String[] eras = dfs.getEras();
//Loop through the eras and output
for(int x = 0; x < eras.length; x++){
    System.out.println(eras[x]);
}
//Now let's get the English Era names
//Get a ULocale that specifies a Japanese Calendar in English
ULocale en = new ULocale("en@calendar=japanese");
//Get the DateFormatSymbols for English using a Japanese Calendar
dfs = DateFormatSymbols.getInstance(en);
eras = dfs.getEras();
for(int x = 0; x < eras.length; x++){
    System.out.println(eras[x]);
}

Discussion:

The Japanese calendar counts years based upon the reign of the emperor.  Since the reign of an emperor varies in length it is impossible to calculate the year programmatically.  icu4j's DateFormatSymbols can provide us with a list of all Emperors.  To find the Japanese year for a Gregorian date use this recipe.

Get the Japanese era for a Gregorian date

Problem:

You want to know the Japanese era for a Gregorian date.

Solution:

Use a Japanese Calendar and DateFormatSymbols from icu4j to retrieve the Japanese year and era.

//Get a ULocale specifying a locale of Japanese using the Japanese calendar
ULocale jp = new ULocale("en@calendar=japanese");
//Get a calendar instance
Calendar jc = Calendar.getInstance(jp);
//Get a default Gregorian calendar instance
Calendar gc = Calendar.getInstance();
//Set calendar for desired date
gc.set(Calendar.YEAR, 1971);
//The month is zero based
gc.set(Calendar.MONTH, 9);
gc.set(Calendar.DATE, 22);
//Set the Japanese calendar using your Gregorian calendar
jc.setTimeInMillis(gc.getTimeInMillis());
//Get a DateFormat appropriate for the ULocale
DateFormat df = jc.getDateTimeFormat(DateFormat.FULL, DateFormat.FULL, jp);
//Output a formatted date
System.out.println(df.format(jc.getTime()));
//Output the era.
System.out.println(jc.get(Calendar.ERA));
//The era is a numeric value.  To get a meaningful value we need to
//get an instance of DateFormatSymbols and retrieve an array of era names.
//We can then use the era to retrieve the appropriate display name
DateFormatSymbols dfs = DateFormatSymbols.getInstance(jp);
//Concatenate the era and year.
String jYear = dfs.getEras()[jc.get(Calendar.ERA)]+jc.get(Calendar.YEAR);
System.out.println(jYear);

Output:

昭和46年10月22日金曜日 12時53分44秒アメリカ合衆国 (ニューヨーク)
234
昭和46

Discussion:

The Japanese year is represented by the year of the Emperors reign.  This makes it impossible to programmatically calculate the year since the length of an Emperor's reign fluctuates.  We will rely on the data stored in the CLDR and access it via icu4j.