Help us understand the problem. What is going on with this article?

Java などでの日付処理について

More than 1 year has passed since last update.

Java などでの日付処理について

by kojikojio
1 / 22

2017年9月21日 Java 9 リリース🎉


ですが今日は Java8 で導入された Date and Time APIについて話します


歴史

Javaには java.util.Date が @Since 1.0 から存在していた。
Java 1.0 は1996年
DateはEpochからのミリ秒を保持。タイムゾーンに関する情報はもってない。
Mutableなのでよく事故が起こる
関数の引数にDateを渡したら、関数内部で書き換えられる可能性があるので
防御的コピー (Defensive copy)をした上で渡す必要があったりする。


Date

import java.text.SimpleDateFormat;
import java.util.Date;

public class Aaa {
   public static void main(String[] args) throws Exception {
       Date today = new Date();
       System.out.println("Today is: " + new SimpleDateFormat("yyyy-MM-dd").format(today));

       // 昨日の日付を表示したいな
       昨日の日付を表示(today);

       // 今日の日付を表示したいな
       System.out.println("Today is: " + new SimpleDateFormat("yyyy-MM-dd").format(today));
   }

   public static void 昨日の日付を表示(Date date) {
       // 昨日の日付を作る
       date.setTime(date.getTime() - 24L * 60 * 60 * 1000);
       System.out.println("Yesterday is: " + new SimpleDateFormat("yyyy-MM-dd").format(date));
   }
}

結果

Today is: 2017-12-19
Yesterday is: 2017-12-18
Today is: 2017-12-18

Calendarの時代

Java 1.1 で java.util.Calendar が追加された。
Java 1.1 は1997年
実質的に Calendar == GregorianCalendar
JDK 1.1 における国際化対応の一環として導入された
CalendarはEpochからのミリ秒(Date)とタイムゾーンに関する情報を保持
がAPIが酷い
これまたjava.util.Dateと同じくMutableなのでよく事故が起こる


日本時間の2001年12月25日の14時ちょうどのCalendarをつくりたいんだけど・・

import java.text.SimpleDateFormat;
import java.util.Calendar;

public class Bbb {
   public static void main(String[] args) throws Exception {
       Calendar cal = Calendar.getInstance();
       cal.set(Calendar.YEAR, 2001);
       cal.set(Calendar.MONTH, 11); // 0 はじまり
       cal.set(Calendar.DAY_OF_MONTH, 25);
       cal.set(Calendar.HOUR_OF_DAY, 14); // Calendar.HOUR を使うとAM/PMがおかしくなる
       cal.set(Calendar.MINUTE, 0);
       cal.set(Calendar.SECOND, 0);
       cal.set(Calendar.MILLISECOND, 0);

       System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ").format(cal.getTime()));
   }
}

長い冬の時代

Apache (Jakarta) commons-langのDateUtilsでがんばる日々・・
joda-time という選択肢もあった
2003年頃出てきた .Net Framework1.1 のDateTimeはしっかりImmutableになってた。

余談、Java 6.0 (2006年) で和暦を扱うJapaneseImperialCalendarが追加


新しい夜明け

Java 8.0 (2014年) で Date and Time API が追加された。
Joda-Time (初出は2002年ごろ?)をベースに JSR-310として策定された
Immutable
java.time パッケージで提供


Date and Time API

中心となるクラス

LocalDate

年月日を保持するクラス。ISO8601がベース
先発グレゴリオ暦で扱う
java.util.Dateはユリウス暦とグレゴリオ暦の切り替わりがある。

LocalTime

時分秒(精度はナノ秒)を保持するクラス。タイムゾーンを持たない

LocalDateTime

LocalDate + LocalTime のクラス。

ZoneId

Asia/Tokyo などの地理的なタイムゾーン情報を保持
java.util.TimeZoneがあったが、新たに作成された
単なるOffset情報だけでなく、夏時間の情報も保持
単なるOffsetを扱うためにZoneOffsetというサブクラスがある。

Instant

意味:瞬時、瞬間、(特定の)時点

ZonedDateTime

LocalDate + LocalTime + ZoneId


先発グレゴリオ暦とは・・・

「1582年10月から施行されたグレゴリオ暦の暦法を、1582年以前にも適用したもの」
https://ja.wikipedia.org/wiki/%E5%85%88%E7%99%BA%E3%82%B0%E3%83%AC%E3%82%B4%E3%83%AA%E3%82%AA%E6%9A%A6
ISO8601は先発グレゴリオ暦

1582年以前は処理系によって扱いは様々

ISO8601は西暦1年より前の扱いは規定していない
JavaのLocalDateは西暦1年の前年は0年で扱っている
LocalDate.of(1,1,1).minusDays(1) -> 0000-12-31
Postgresは1年の前年は-1年


データベースでの日付の扱い

DATE型といってもDBMSごとに扱いは様々

標準SQLでは、日付時間関連のデータ型は
* DATE型(日付)
* TIME型(時刻)
* TIMESTAMP型(日付と時刻を足したもの)
TIMEとTIMESTAMPはタイムゾーンは保持しない


Oracleでは・・

日付時間関連のデータ型は四種類
* DATE 年月日と時分秒を保持。タイムゾーンなし
* TIMESTAMP 年月日と時分秒+少数以下(9桁まで指定可能)を保持。タイムゾーンなし
* TIMESTAMP WITH TIME ZONE 上記にタイムゾーン情報を追加したもの
* TIMESTAMP WITH LOCAL TIME ZONE タイムゾーンを持っているけれどもDBのタイムゾーン
http://otndnld.oracle.co.jp/document/products/oracle11g/111/doc_dvd/server.111/E05765-03/datatype.htm#18523

DATEは内部的にユリウス日+秒を保持している。7byte。範囲は-4712/01/01 ~ 9999/12/31
ユリウス暦とグレゴリオ暦を使っている。
西暦1年の前年は0年、0年の前年を「西暦前1年」と言っている。


Postgresでは・・

  • DATE型(日付)
  • TIME型(時刻)
  • TIMESTAMP型(日付と時刻を足したもの) TIMEとTIMESTAMPはタイムゾーンの有り無しを指定できる

Oracleと同じくユリウス日 紀元前4713/01/01 ~ 294276/12/31
先発グレゴリオ暦を使用
西暦1年の前年は1 BC、1 BCの前年は2 BC
0001-01-01の前日は '0001-12-31 BC'


MySQLでは

  • DATE型(日付)0年月日
  • TIME型(時刻) '00:00:00' マイクロ秒まで。タイムゾーンない
  • DATETIME型(日付と時刻を足したもの) タイムゾーンない

DATEは3byteで格納。'1000-01-01' から '9999-12-31'
先発グレゴリオ暦を使用


その他


Git

Epochからの秒数とタイムゾーンを保持


Apache Spark

日付時間関連は2種類
* TimestampType 年月日と時分秒を保持。タイムゾーンの情報はない
* DateType 年月日を保持
https://spark.apache.org/docs/latest/sql-programming-guide.html#data-types


Parquet

日付時間関連は3種類
* DATE 1970-01-01からの経過日数をINT32で保持
* TIME_MILLIS 0:00からの経過ミリ秒をINT32で保持
* TIMESTAMP_MILLIS Epochからの経過ミリ秒をINT64で保持。タイムゾーンの情報はない
https://drill.apache.org/docs/parquet-format/#sql-types-to-parquet-logical-types


まとめ

日時の処理をするときは処理系ごとの動きを把握しよう
型のタイムゾーンの有無を考慮しよう
夏時間があってもちゃんと動きますか?
できれば処理するマシンのタイムゾーンを変更しても問題ないような作りにする


実行時点での日本のローカル日時を取得する
LocalDate.now(ZoneId.of("Asia/Tokyo"))
ZoneId.getAvailableZoneIds.asScala.foreach(println)
JapaneseDate.of(1900,1,1).getEra

Oracleでは・・
日付時間関連のデータ型は四種類

内部的にユリウス日を保持している。-4712/01/01 ~ 9999/12/31
ユリウス暦とグレゴリオ暦を使っている。
TO_CHAR(DATE'2001-01-01','J')
2451911

TO_CHAR (DATE '0001-01-01', 'J')
1721424

TO_CHAR(DATE'1582-10-04','J')
2299160
TO_CHAR(DATE'1582-10-15','J')
2299161

Postgresでは・・
Oracleと同じくユリウス日 紀元前4713/01/01 ~ 294276/12/31
先発グレゴリオ暦を使用
postgres=# SELECT TO_CHAR (DATE '2001-01-01', 'J');

to_char

2451911
(1 row)

postgres=# SELECT TO_CHAR (DATE '0001-01-01', 'J');

to_char

1721426
(1 row)

postgres=# SELECT TO_CHAR (DATE '1582-10-04', 'J');

to_char

2299150

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away