12.5. 日付操作(Joda Time)¶
Caution
本ガイドラインでは日付操作には日付操作(JSR-310 Date and Time API)を使用することを推奨する。
スタックからもJoda-Timeは外れているため注意されたい。
また、本節は次版以降に削除する予定である。
12.5.1. Overview¶
java.util.Date
、java.util.Calendar
クラスのAPIは、非常に貧弱であるため、複雑な日付計算ができない。java.util.Date
の代わりに、org.joda.time.DateTime
、org.joda.time.LocalDate
やorg.joda.time.LocalTime
オブジェクトを用いて日付を表現する。org.joda.time.DateTime
、org.joda.time.LocalDate
やorg.joda.time.LocalTime
オブジェクトは、immutableである(日付計算等の結果は、新規オブジェクトである)。12.5.2. How to use¶
Joda Time, Joda Time JSP tags の利用方法を、以下で説明する。
12.5.2.1. 日付取得¶
12.5.2.1.1. 現在時刻を取得¶
org.joda.time.DateTime
,org.joda.time.LocalDate
,org.joda.time.LocalTime
を使い分けること。以下に、使用方法を示す。ミリ秒まで取得したい場合は、
org.joda.time.DateTime
を使用する。
DateTime dateTime = new DateTime();
TimeZoneと、時間を除いた日付だけが必要な場合は、
org.joda.time.LocalDate
を使用する。
LocalDate localDate = new LocalDate();
TimeZoneと、日付を除いた時間だけが必要な場合は、
org.joda.time.LocalTime
を使用する。
LocalTime localTime = new LocalTime();
日付開始時刻と現在日付を取得したい場合は、
org.joda.time.DateTime.withTimeAtStartOfDay()
を使用する。
DateTime dateTimeAtStartOfDay = new DateTime().withTimeAtStartOfDay();
Note
LocalDateとLocalTimeは、TimeZone情報を持たない。
Note
実際ServiceやControllerで現在時刻を取得するときのDateTime, LocalDate や、 LocalTimeのインスタンス取得には、org.terasoluna.gfw.common.date.jodatime.JodaTimeDateFactory
を利用することを推奨する。
DateTime dateTime = dataFactory.newDateTime();
DateFactoryの利用方法は、システム時刻を参照されたい。
LocalDateやLocalTimeの生成は
LocalDate localDate = dataFactory.newDateTime().toLocalDate();
LocalTime localTime = dataFactory.newDateTime().toLocalTime();
とすればよい。
12.5.2.1.2. タイムゾーンを指定して現在時刻を取得¶
org.joda.time.DateTimeZone
は、timezoneを表すクラスである。DateTime dateTime = new DateTime(DateTimeZone.forID("Asia/Tokyo"));
org.terasoluna.gfw.common.date.jodatime.JodaTimeDateFactory
を利用する場合は、以下のようになる。
// Fetching current system date using default TimeZone
DateTime dateTime = dataFactory.newDateTime();
// Changing to TimeZone of Tokyo
DateTime dateTimeTokyo = dateTime.withZone(DateTimeZone.forID("Asia/Tokyo"));
他の使用可能なTimezone ID文字列の一覧は、Available Time Zonesを参照されたい。
12.5.2.1.3. タイムゾーンを指定せず現在時刻を取得¶
LocalDateTime localDateTime = new LocalDateTime();
org.terasoluna.gfw.common.date.jodatime.JodaTimeDateFactory
を利用する場合は、以下のようになる。
// Fetching current system date using default TimeZone
LocalDateTime localDateTime = dateFactory.newDateTime().toLocalDateTime();
Note
TimeZoneを意識する必要がない場合は、DateTime
ではなくLocalDateTime
を利用することを推奨する。
12.5.2.1.4. 年月日時分秒を指定して取得¶
コンストラクタで、特定の時間を指定することができる。以下に例を示す。
ミリ秒まで指定して、DateTimeを取得したい場合
DateTime dateTime = new DateTime(year, month, day, hour, minite, second, millisecond);
年月日を指定して、LocalDateを取得したい場合
LocalDate localDate = new LocalDate(year, month, day);
時分秒を指定して、LocalDate取得したい場合
LocalTime localTime = new LocalTime(hour, minutes, seconds, milliseconds);
12.5.2.1.5. 年月日等の個別取得¶
DateTime dateTime = new DateTime(2013, 1, 10, 2, 30, 22, 123);
int year = dateTime.getYear(); // (1)
int month = dateTime.getMonthOfYear(); // (2)
int day = dateTime.getDayOfMonth(); // (3)
int week = dateTime.getDayOfWeek(); // (4)
int hour = dateTime.getHourOfDay(); // (5)
int min = dateTime.getMinuteOfHour(); // (6)
int sec = dateTime.getSecondOfMinute(); // (7)
int millis = dateTime.getMillisOfSecond(); // (8)
項番 |
説明 |
---|---|
(1)
|
年を取得する。本例では、
2013 が返却される。 |
(2)
|
月を取得する。本例では、”
1 “が返却される。 |
(3)
|
日を取得する。本例では、
10 が返却される。 |
(4)
|
曜日を取得する。本例では、”
4 “が返却される。返却される値と曜日の対応は、[1:月曜、2:火曜、3:水曜、4:木曜、5:金曜、6:土曜、7:日曜]となる。
|
(5)
|
時を取得する。本例では、”
2 “が返却される。 |
(6)
|
分を取得する。本例では、
30 が返却される。 |
(7)
|
秒を取得する。本例では、
22 が返却される。 |
(8)
|
ミリ秒を取得する。本例では、
123 が返却される。 |
Note
java.util.Calendar
の仕様とは異なり、getDayOfMonth()は、1始まりである。
12.5.2.2. 型変換¶
12.5.2.2.1. java.util.Dateとの相互運用性¶
java.util.Date
との型変換を、容易に行える。Date date = new Date();
DateTime dateTime = new DateTime(date); // (1)
Date convertDate = dateTime.toDate(); // (2)
項番 |
説明 |
---|---|
(1)
|
DateTimeのコンストラクタの引数に、
java.util.Date を引数に渡すことで、java.util.Date -> DateTime への変換を行う。 |
(2)
|
DateTime#toDate メソッドで、DateTime ->
java.util.Date への変換を行う。 |
12.5.2.2.2. 文字列へのフォーマット¶
DateTime dateTime = new DateTime();
dateTime.toString("yyyy-MM-dd HH:mm:ss"); // (1)
項番 |
説明 |
---|---|
(1)
|
“yyyy-MM-dd HH:mm:ss” 形式で変換された、文字列が取得される。
toStringの引数として指定可能な値については、Input and Outputを参照されたい。
|
12.5.2.2.3. 文字列からのパース¶
LocalDate localDate = DateTimeFormat.forPattern("yyyy-MM-dd").parseLocalDate("2012-08-09"); // (1)
項番 |
説明 |
---|---|
(1)
|
“yyyy-MM-dd” 形式の文字列を、LocalDate型に変換する。
DateTimeFormat#forPatternの引数として指定可能な値は、Formattersを参照されたい。
|
12.5.2.3. 日付操作¶
12.5.2.3.1. 日付の計算¶
LocalDate localDate = new LocalDate(); // localDate is 2013-01-10
LocalDate yesterday = localDate.minusDays(1); // (1)
LocalDate tomorrow = localDate.plusDays(1); // (2)
LocalDate afterThreeMonth = localDate.plusMonths(3); // (3)
LocalDate nextYear = localDate.plusYears(1); // (4)
項番 |
説明 |
---|---|
(1)
|
LocalDate#minusDays 引数に、指定した値分の日付が減算される。本例では
2013-01-09 となる。 |
(2)
|
LocalDate#plusDays 引数に、指定した値分の日付が加算される。本例では
2013-01-11 となる。 |
(3)
|
LocalDate#plusMonths 引数に、指定した値分の月数が加算される。本例では
2013-04-10 となる。 |
(4)
|
LocalDate#plusYears 引数に、指定した値分の年数が加算される。本例では
2014-01-10 となる。 |
上記で示したメソッド以外は、LocalDate JavaDocを参照されたい。
12.5.2.3.2. 月末月初の取得¶
LocalDate localDate = new LocalDate(); // dateTime is 2013-01-10
Property dayOfMonth = localDate.dayOfMonth(); // (1)
LocalDate firstDayOfMonth = dayOfMonth.withMinimumValue(); // (2)
LocalDate lastDayOfMonth = dayOfMonth.withMaximumValue(); // (3)
項番 |
説明 |
---|---|
(1)
|
現在月の日付に関する属性値を保持するPropertyオブジェクトを取得する。
|
(2)
|
Propertyオブジェクトから最小値を取得する事で、月初日を取得する事ができる。本例では
2013-01-01 となる。 |
(3)
|
Propertyオブジェクトから最大値を取得する事で、月末日を取得する事ができる。本例では
2013-01-31 となる。 |
12.5.2.3.3. 週末週初の取得¶
LocalDate localDate = new LocalDate(); // dateTime is 2013-01-10
Property dayOfWeek = localDate.dayOfWeek(); // (1)
LocalDate firstDayOfWeek = dayOfWeek.withMinimumValue(); // (2)
LocalDate lastDayOfWeek = dayOfWeek.withMaximumValue(); // (3)
項番 |
説明 |
---|---|
(1)
|
現在週の日付に関する属性値を保持するPropertyオブジェクトを取得する。
|
(2)
|
Propertyオブジェクトから最小値を取得する事で、週初日(月曜日)を取得する事ができる。本例では
2013-01-07 となる。 |
(3)
|
Propertyオブジェクトから最大値を取得する事で、週末日(日曜日)を取得する事ができる。本例では
2013-01-13 となる。 |
12.5.2.3.4. 日時の比較¶
日時を比較して過去か未来を判定できる。
DateTime dt1 = new DateTime();
DateTime dt2 = dt1.plusHours(1);
DateTime dt3 = dt1.minusHours(1);
System.out.println(dt1.isAfter(dt1)); // false
System.out.println(dt1.isAfter(dt2)); // false
System.out.println(dt1.isAfter(dt3)); // true
System.out.println(dt1.isBefore(dt1)); // false
System.out.println(dt1.isBefore(dt2)); // true
System.out.println(dt1.isBefore(dt3)); // false
System.out.println(dt1.isEqual(dt1)); // true
System.out.println(dt1.isEqual(dt2)); // false
System.out.println(dt1.isEqual(dt3)); // false
項番 |
説明 |
---|---|
(1)
|
isAfter メソッドは対象の日時が引数の日時より未来の場合にtrue を返す。 |
(2)
|
isBefore メソッドは対象の日時が引数の日時より過去の場合にtrue を返す。 |
(3)
|
isEqual メソッドは対象の日時が引数の日時と同じ場合にtrue を返す。 |
12.5.2.4. 期間の取得¶
Joda-Timeでは、期間に関して、いくつかのクラスが提供されている。ここでは以下の2クラスについて説明する。
org.joda.time.Interval
org.joda.time.Period
12.5.2.4.1. Interval¶
2つのインスタンス(DateTime)の期間を表すクラス。
Intervalで調べられることは、以下4つである。
期間内に指定の日付や期間が含まれるかのチェック
2つの期間が連続するかのチェック
2つの期間の差を期間で取得
2つの期間の重なった期間を取得
実装例は、以下を参照されたい。
DateTime start1 = new DateTime(2013,8,14,0,0,0);
DateTime end1 = new DateTime(2013,8,16,0,0,0);
DateTime start2 = new DateTime(2013,8,16,0,0,0);
DateTime end2 = new DateTime(2013,8,18,0,0,0);
DateTime anyDate = new DateTime(2013, 8, 15, 0, 0, 0);
Interval interval1 = new Interval(start1, end1);
Interval interval2 = new Interval(start2, end2);
interval1.contains(anyDate); // (1)
interval1.abuts(interval2); // (2)
DateTime start3 = new DateTime(2013,8,18,0,0,0);
DateTime end3 = new DateTime(2013,8,20,0,0,0);
Interval interval3 = new Interval(start3, end3);
interval1.gap(interval3); // (3)
DateTime start4 = new DateTime(2013,8,15,0,0,0);
DateTime end4 = new DateTime(2013,8,17,0,0,0);
Interval interval4 = new Interval(start4, end4);
interval1.overlap(interval4); // (4)
項番 |
説明 |
---|---|
(1)
|
Interval#containsメソッドで、期間内に指定の日付や期間が含まれるかのチェックを行う。
期間内に含まれる場合、”true”、含まれない場合、”false”を返却する。
|
(2)
|
Interval#abutsメソッドで、2つの期間が連続するかのチェックを行う。
2つの期間が連続する場合は”true”、連続しない場合は”false”を返却する。
|
(3)
|
Interval#gapメソッドで、2つの期間の差を期間(Interval)で取得する。
本例では、”2013-08-16~2013-08-18” の期間が取得される。
期間の差が存在しない場合、nullが戻り値となる。
|
(4)
|
Interval#overlapメソッドで、2つの期間の重なった期間(Interval)を取得する。
本例では、”2013-08-15~2013-08-16” の期間が取得される。
重なった期間が存在しない場合、nullが戻り値となる。
|
Interval同士を比較したい場合は、Periodに変換して行う。
月、日、などより抽象的な観点で比較をしたい場合は、Periodに変換すること。
// Convert to Period
interval1.toPeriod();
12.5.2.4.2. Period¶
Periodは、期間を、年、月、週などの単位で表すクラスである。
「3月1日」に「1ヶ月」というPeriodを追加したときの日数は「31日」
「4月1日」に「1ヶ月」というPeriodを追加したときの日数は「30日」
「1ヶ月」に相当するPeriodの追加は、対象のDateTimeによって、違う意味を持つ。
Single field Period (例:「1日」や「1ヶ月」など一つの単位の値しか持たないタイプ)
Any field Period (例:「1ヶ月2日4時間」など、複数の単位の値を持てて期間を表すタイプ)
詳細は、 Period を参照されたい。
12.5.2.5. JSP Tag Library¶
Warning
Jakarta EE 9 以降Joda-TimeのJSP Tag Libraryは使用できなくなった。
Controller
@Controller public class HomeController { @GetMapping("/") public String home(Model model) { DateTime dateTime = new DateTime(); model.addAttribute("currentDate", dateTime.toString()); model.addAttribute("formattedCurrentDateString", dateTime.toString("yyyy/MM/dd hh:mm:ss")); // omitted } }
jspファイル
<p>currentDate = ${f:h(currentDate)}.</p> <p>formattedCurrentDateString = ${f:h(formattedCurrentDateString)}.</p>
実行結果
<p>currentDate = 2015-10-25T14:40:15.648+09:00</p> <p>formattedCurrentDateString = 2015/10/25 14:40:15</p>
12.5.2.6. Thymeleafでのフォーマット¶
toString
メソッドを使用した文字列へのフォーマットが可能である。DateTime
オブジェクトをフォーマット文字列を指定してフォーマットする例を以下に示す。Controllerクラス
DateTime dateTime = new DateTime();
model.addAttribute("currentDateTime", dateTime); // (1)
テンプレートHTML
<p th:text="|currentDateTime = ${currentDateTime.toString('yyyy/MM/dd HH:mm:ss')}.|"></p> <!--/* (2) */-->
出力結果例(html)
<p>currentDate = 2013/10/25 13:02:32.</p> <!-- (3) -->
項番 |
説明 |
---|---|
(1)
|
Model オブジェクトにJoda TimeのDateTime オブジェクトを追加する。ここでは、現在日時を指定している。
|
(2)
|
DateTime オブジェクトを指定したフォーマット文字列でフォーマットする。ここでは、フォーマット文字列を
yyyy/MM/dd HH:mm:ss 形式で指定している。ここでは簡易な例を示しているため実装していないが、必要に応じて
toString メソッド実行前にnull チェックを実装すること。 |
(3)
|
現在の日付が2013年10月25日13時2分32秒の場合、
2013/10/25 13:02:32 と表示される。 |
12.5.2.7. 応用例(カレンダーの表示)¶
Spring MVCを使って、月単位のカレンダーを表示するサンプルを示す。
処理名 |
URL |
ハンドラメソッド |
---|---|---|
今月のカレンダー表示 |
/calendar |
today |
指定月のカレンダー表示 |
/calendar/month?year=yyyy&month=m |
month |
コントローラの実装は、以下のようになる。
@Controller
@RequestMapping("calendar")
public class CalendarController {
@GetMapping
public String today(Model model) {
LocalDate today = new LocalDate();
int year = today.getYear();
int month = today.getMonthOfYear();
return month(year, month, model);
}
@GetMapping("month")
public String month(@RequestParam("year") int year,
@RequestParam("month") int month, Model model) {
LocalDate firstDayOfMonth = new LocalDate(year, month, 1);
LocalDate lastDayOfMonth = firstDayOfMonth.dayOfMonth()
.withMaximumValue();
LocalDate firstDayOfCalendar = firstDayOfMonth.dayOfWeek()
.withMinimumValue();
LocalDate lastDayOfCalendar = lastDayOfMonth.dayOfWeek()
.withMaximumValue();
List<List<String>> calendar = new ArrayList<>();
List<String> weekList = null;
for (int i = 0; i < 100; i++) {
LocalDate d = firstDayOfCalendar.plusDays(i);
if (d.isAfter(lastDayOfCalendar)) {
break;
}
if (weekList == null) {
weekList = new ArrayList<String>();
calendar.add(weekList);
}
if (d.isBefore(firstDayOfMonth) || d.isAfter(lastDayOfMonth)) {
// skip if the day is not in this month
weekList.add(null);
} else {
weekList.add(d.toString("d"));
}
int week = d.getDayOfWeek();
if (week == DateTimeConstants.SUNDAY) {
weekList = null;
}
}
LocalDate nextMonth = firstDayOfMonth.plusMonths(1);
LocalDate prevMonth = firstDayOfMonth.minusMonths(1);
CalendarOutput output = new CalendarOutput();
output.setCalendar(calendar);
output.setFirstDayOfMonth(firstDayOfMonth.toString("yyyy-M"));
output.setYearOfNextMonth(nextMonth.getYear());
output.setMonthOfNextMonth(nextMonth.getMonthOfYear());
output.setYearOfPrevMonth(prevMonth.getYear());
output.setMonthOfPrevMonth(prevMonth.getMonthOfYear());
model.addAttribute("output", output);
return "calendar";
}
}
以下のCalendarOutput
クラスは、画面に出力する情報をまとめたJavaBeanである。
public class CalendarOutput {
private List<List<String>> calendar;
private String firstDayOfMonth;
private int yearOfNextMonth;
private int monthOfNextMonth;
private int yearOfPrevMonth;
private int monthOfPrevMonth;
// omitted getter/setter
}
Warning
このサンプルコードは単純なためControllerのハンドラメソッドに全ての処理を記述しているが、メンテナンス性向上のため本来この処理は、Helperクラスに記述すべきである。
JSPもしくはテンプレートHTMLで、それぞれ次のように出力する。
calender.jsp
<p>
<a
href="${pageContext.request.contextPath}/calendar/month?year=${f:h(output.yearOfPrevMonth)}&month=${f:h(output.monthOfPrevMonth)}">←
Prev</a> <a
href="${pageContext.request.contextPath}/calendar/month?year=${f:h(output.yearOfNextMonth)}&month=${f:h(output.monthOfNextMonth)}">Next
→</a> <br>
${f:h(output.firstDayOfMonth)}
</p>
<table>
<tr>
<th>Mon.</th>
<th>Tue.</th>
<th>Wed.</th>
<th>Thu.</th>
<th>Fri.</th>
<th>Sat.</th>
<th>Sun.</th>
</tr>
<c:forEach var="week" items="${output.calendar}">
<tr>
<c:forEach var="day" items="${week}">
<td><c:choose>
<c:when test="${day != null}">
${f:h(day)}
</c:when>
<c:otherwise> </c:otherwise>
</c:choose></td>
</c:forEach>
</tr>
</c:forEach>
</table>
calender.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link rel="stylesheet" th:href="@{/resources/app/css/styles.css}">
</head>
<body>
<div th:object="${output}">
<p>
<a href="calendar.html"
th:href="@{/calendar/month(year=*{yearOfPrevMonth}, month=*{monthOfPrevMonth})}">←Prev</a>
<a href="calendar.html"
th:href="@{/calendar/month(year=*{yearOfNextMonth}, month=*{monthOfNextMonth})}">Next→</a> <br>
<span th:text="*{firstDayOfMonth.toString('yyyy-M')}"></span>
</p>
<table>
<tr>
<th>Mon.</th>
<th>Tue.</th>
<th>Wed.</th>
<th>Thu.</th>
<th>Fri.</th>
<th>Sat.</th>
<th>Sun.</th>
</tr>
<tr th:each="week : *{calendar}">
<td th:each="day : ${week}">
<span th:text="${day != null}? ${day.toString('d')} : ' '"></span>
</td>
</tr>
</table>
</div>
</body>
</head>
</html>
{contextPath}/calendarにアクセスすると、以下のカレンダーが表示される(2012年11月時点での結果である)。
{contextPath}/calendar/month?year=2012&month=12にアクセスすると、以下のカレンダーが表示される。
12.5.3. Appendix¶
12.5.3.1. 和暦操作¶
java.time.chrono.JapaneseDate
もしくはjava.util.Calendar
を使用する。java.util.Calendar
で和暦操作するには、java.util.Calendar
クラス、java.text.DateFormat
クラスに以下のjava.util.Locale
を指定する必要がある。Locale locale = new Locale("ja", "JP", "JP");
Calendar
クラスを利用した和暦表示の例を示す。Locale locale = new Locale("ja", "JP", "JP");
Calendar cal = Calendar.getInstance(locale); // Ex, 2015-06-05
String format1 = "Gy.MM.dd";
String format2 = "GGGGyy/MM/dd";
DateFormat df1 = new SimpleDateFormat(format1, locale);
DateFormat df2 = new SimpleDateFormat(format2, locale);
df1.format(cal.getTime()); // "H27.06.05"
df2.format(cal.getTime()); // "平成27/06/05"
Locale locale = new Locale("ja", "JP", "JP");
String format1 = "Gy.MM.dd";
String format2 = "GGGGyy/MM/dd";
DateFormat df1 = new SimpleDateFormat(format1, locale);
DateFormat df2 = new SimpleDateFormat(format2, locale);
Calendar cal1 = Calendar.getInstance(locale);
Calendar cal2 = Calendar.getInstance(locale);
cal1.setTime(df1.parse("H27.06.05"));
cal2.setTime(df2.parse("平成27/06/05"));
Note
new Locale("ja", "JP", "JP")
をgetInstance
メソッドに指定することで、 和暦に対応したjava.util.JapaneseImperialCalendar
オブジェクトが作成される。その他を指定するとjava.util.GregorianCalendar
オブジェクトが作成されるため、留意されたい。