背景 在 Java 8 之前,我们最常见的时间与日期处理相关的类就是 Date、Calendar 以及 SimpleDateFormatter 等等。不过 java.util.Date
也是被诟病已久,它包含了日期、时间、毫秒数等众多繁杂的信息,其内部利用午夜 12 点来区分日期,利用 1970-01-01 来计算时间;并且其月份从 0 开始计数,而且用于获得年、月、日等信息的接口也是太不直观。除此之外,java.util.Date
与 SimpleDateFormatter
都不是类型安全的,而 JSR-310 中的 LocalDate
与 LocalTime
等则是不变类型,更加适合于并发编程。JSR 310 实际上有两个日期概念。第一个是 Instant,它大致对应于 java.util.Date
类,因为它代表了一个确定的时间点,即相对于标准 Java 纪元(1970年1月1日)的偏移量;但与 java.util.Date
类不同的是其精确到了纳秒级别。另一个则是 LocalDate、LocalTime 以及 LocalDateTime 这样代表了一般时区概念、易于理解的对象。
Java8中, java.time
包下包含下面几个主要的类:
1 2 3 4 5 6 7 8 9 Instant:时间戳 Duration:持续时间,时间差 LocalDate:只包含日期,比如:2016 -10 -20 LocalTime:只包含时间,比如:23 :12 :10 LocalDateTime:包含日期和时间,比如:2016 -10 -20 23 :14 :21 Period:时间段 ZoneOffset:时区偏移量,比如:+8 :00 ZonedDateTime:带时区的时间 Clock:时钟,比如获取目前美国纽约的时间
Class / Type
Description
Year
Represents a year.
YearMonth
A month within a specific year.
LocalDate
A date without an explicitly specified time zone.
LocalTime
A time without an explicitly specified time zone.
LocalDateTime
A combination date and time without an explicitly specified time zone.
最新 JDBC 映射将把数据库的日期类型和 Java 8 的新类型关联起来:
SQL
Java
date
LocalDate
time
LocalTime
timestamp
LocalDateTime
datetime
LocalDateTime
时间与日期基础概念 标准时间 GMT 即「格林威治标准时间」( Greenwich Mean Time,简称 G.M.T. ),指位于英国伦敦郊区的皇家格林威治天文台的标准时间,因为本初子午线被定义为通过那里的经线。然而由于地球的不规则自转,导致 GMT 时间有误差,因此目前已不被当作标准时间使用。UTC 是最主要的世界时间标准,是经过平均太阳时(以格林威治时间 GMT 为准)、地轴运动修正后的新时标以及以「秒」为单位的国际原子时所综合精算而成的时间。UTC 比 GMT 来得更加精准。其误差值必须保持在 0.9 秒以内,若大于 0.9 秒则由位于巴黎的国际地球自转事务中央局发布闰秒,使 UTC 与地球自转周期一致。不过日常使用中,GMT 与 UTC 的功能与精确度是没有差别的。协调世界时区会使用 “Z” 来表示。而在航空上,所有使用的时间划一规定是协调世界时。而且 Z 在无线电中应读作 “Zulu”(可参见北约音标字母),协调世界时也会被称为 “Zulu time”。
TimeZone&UTC Offsets: 时区与偏移 人们经常会把时区与 UTC 偏移量搞混,UTC 偏移量代表了某个具体的时间值与 UTC 时间之间的差异,通常用 HH:mm 形式表述。而 TimeZone 则表示某个地理区域,某个 TimeZone 中往往会包含多个偏移量,而多个时区可能在一年的某些时间有相同的偏移量。譬如 America/Chicago, America/Denver, 以及 America/Belize 在一年中不同的时间都会包含 -06:00 这个偏移。
时间戳 Unix 时间戳表示当前时间到 1970 年 1 月 1 日 00:00:00 UTC 对应的秒数。注意,JavaScript 内的时间戳指的是当前时间到 1970 年 1 月 1 日 00:00:00 UTC 对应的毫秒数,和 Unix 时间戳不是一个概念,后者表示秒数,差了 1000 倍。
时间数字字符串格式 RFC2822 1 2 YYYY/MM/ DD HH: MM: SS ± timezone(时区用4 位数字表示)
ISO 8601 国际标准化组织的国际标准 ISO 8601 是日期和时间的表示方法,全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》。目前最新为第三版 ISO8601:2004,第一版为 ISO8601:1988,第二版为 ISO8601:2000。年由 4 位数组成,以公历公元 1 年为 0001 年,以公元前 1 年为 0000 年,公元前 2 年为 -0001 年,其他以此类推。应用其他纪年法要换算成公历,但如果发送和接受信息的双方有共同一致同意的其他纪年法,可以自行应用。
1 2 3 4 5 6 YYYY-MM-DDThh:mm:ss ± timezone(时区用HH:MM表示) 1997 -07 -16 T08:20 :30 Z1997 -07 -16 T19:20 :30 +01 :00
时间戳 在 Java 8 之前,我们使用 java.sql.Timestamp
来表示时间戳对象,可以通过以下方式创建与获取对象:
1 2 3 4 5 6 7 8 Timestamp timestamp = new Timestamp(System.currentTimeMillis()); new Timestamp((new Date ()).getTime());timestamp.getTime();
在 Java 8 中,即可以使用 java.time.Instant
来表示自从 1970-01-01T00:00:00Z 之后经过的标准时间:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Instant instant = Instant.now();Instant someInstant = someDate.toInstant();Instant someInstant = Instant.ofEpochMilli(someDate.getTime());Instant instant = timestamp.toInstant();LocalDate.now().atStartOfDay().toInstant(ZoneOffset.UTC) // 从 LocalDateTime 转化而来 ldt.atZone(ZoneId.systemDefault()).toInstant(); // 获取毫秒 long timeStampMillis = instant.toEpochMilli();long timeStampSeconds = instant.getEpochSecond();
Clock 方便我们去读取当前的日期与时间。Clock 可以根据不同的时区来进行创建,并且可以作为System.currentTimeMillis()
的替代。这种指向时间轴的对象即是Instant
类。Instants 可以被用于创建java.util.Date
对象。
1 2 3 4 5 Clock clock = Clock .systemDefaultZone();long millis = clock.millis(); Instant instant = clock.instant(); Date legacyDate = Date .from(instant); // legacy java.util.Date
Date 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Date date0 = new Date ();Date date1 = new Date (time);Date date = Date . from(instant);SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd" ); java. util. Date dt=sdf. parse("2005-2-19" ); Date out = Date . from(ldt. atZone(ZoneId. systemDefault()). toInstant());
基于 Date 的日期比较常常使用以下方式:
使用 getTime() 方法获取两个日期(自1970年1月1日经历的毫秒数值),然后比较这两个值。
使用方法 before(),after() 和 equals()。例如,一个月的12号比18号早,则 new Date(99, 2, 12).before(new Date (99, 2, 18))
返回true。
使用 compareTo()
方法,它是由 Comparable 接口定义的,Date 类实现了这个接口。
Calendar Date 用于记录某一个含日期的、精确到毫秒的时间。重点在代表一刹那的时间本身。 Calendar 用于将某一日期放到历法中的互动——时间和年、月、日、星期、上午、下午、夏令时等这些历法规定互相作用关系和互动。我们可以通过 Calendar 内置的构造器来创建实例:
1 2 3 Calendar.Builder builder =new Calendar.Builder(); Calendar calendar1 = builder.build(); Date date = calendar.getTime()
在 Calendar 中我们则能够获得较为直观的年月日信息:
1 2 3 4 5 6 7 8 9 10 11 12 int year =calendar.get (Calendar.YEAR);int month =calendar.get (Calendar.MONTH)+1 ;int day =calendar.get (Calendar.DAY_OF_MONTH);int hour =calendar.get (Calendar.HOUR_OF_DAY);int minute =calendar.get (Calendar.MINUTE);int seconds =calendar.get (Calendar.SECOND);
除此之外,Calendar 还提供了一系列 set 方法来允许我们动态设置时间,还可以使用 add 等方法进行日期的加减。
LocalDateTime LocalDate 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 LocalDate today = LocalDate.now(); LocalDate crischristmas = LocalDate.of(2017, 5, 15); LocalDate endOfFeb = LocalDate.parse ("2017-05-15" ); LocalDate.parse ("2014-02-29" ); DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedDate(FormatStyle.MEDIUM) .withLocale(Locale.GERMAN); LocalDate xmas = LocalDate.parse ("24.12.2014" , germanFormatter); System.out .println(xmas); LocalDate localDate = LocalDate.now(ZoneId.of("GMT+02:30" )); LocalDateTime localDateTime = LocalDateTime.now(); LocalDate localDate = localDateTime.toLocalDate();
日期操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth()); LocalDate secondDayOfThisMonth = today.withDayOfMonth(2 ); LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth()); LocalDate firstDayOf2015 = lastDayOfThisMonth.plusDays(1 ); LocalDate firstMondayOf2015 = LocalDate.parse("2015-01-01" ).with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));
LocalTime 1 2 3 4 5 6 7 8 9 10 LocalTime localTime = LocalTime.now(ZoneId.of("GMT+02:30" )); LocalDateTime localDateTime = LocalDateTime.now(); LocalTime localTime = localDateTime.toLocalTime(); - 12 :00 - 12 :01 :02 - 12 :01 :02.345
LocalDateTime 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 LocalDateTime localDateTime = LocalDateTime. ofInstant(Instant. ofEpochSecond(1450073569 l), TimeZone. getDefault(). toZoneId()); Date in = new Date ();LocalDateTime ldt = LocalDateTime. ofInstant(in . toInstant(), ZoneId. systemDefault()); DateTimeFormatter formatter = DateTimeFormatter . ofPattern("MMM dd, yyyy - HH:mm" ); LocalDateTime parsed = LocalDateTime. parse("Nov 03, 2014 - 07:13" , formatter); String string = formatter. format(parsed);System. out. println(string );
1 2 3 4 5 6 7 8 9 10 LocalDateTime sylvester = LocalDateTime.of(2014 , Month.DECEMBER, 31 , 23 , 59 , 59 ); DayOfWeek dayOfWeek = sylvester.getDayOfWeek(); System.out.println(dayOfWeek); Month month = sylvester.getMonth(); System.out.println(month); long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);System.out.println(minuteOfDay);
1 2 3 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm" ); LocalDateTime dateTime = LocalDateTime.of(1986 , Month.APRIL, 8 , 12 , 30 ); String formattedDateTime = dateTime.format(formatter);
时间操作 1 2 local DateTime.plusDays(1 );local DateTime.minusHours(2 );
时区转换 Timezones 以 ZoneId
来区分。可以通过静态构造方法很容易的创建,Timezones 定义了 Instants 与 Local Dates 之间的转化关系:
1 2 3 4 5 6 7 8 9 10 System.out .println(ZoneId.getAvailableZoneIds()); ZoneId zone1 = ZoneId.of ("Europe/Berlin" ); ZoneId zone2 = ZoneId.of ("Brazil/East" ); System.out .println(zone1 .getRules()); System.out .println(zone2 .getRules());
1 2 3 LocalDateTime ldt = ... ZonedDateTime zdt = ldt. atZone(ZoneId. systemDefault()); Date output = Date . from(zdt. toInstant());
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ZoneId losAngeles = ZoneId.of("America/Los_Angeles" ); ZoneId berlin = ZoneId.of("Europe/Berlin" ); LocalDateTime dateTime = LocalDateTime.of(2014 , 02 , 20 , 12 , 0 ); ZonedDateTime berlinDateTime = ZonedDateTime.of(dateTime, berlin); ZonedDateTime losAngelesDateTime = berlinDateTime.withZoneSameInstant(losAngeles); int offsetInSeconds = losAngelesDateTime.getOffset().getTotalSeconds(); Set<String> allZoneIds = ZoneId.getAvailableZoneIds(); LocalDateTime date = LocalDateTime.of(2013 , Month.JULY, 20 , 3 , 30 ); ZoneOffset offset = ZoneOffset.of("+05:00" ); OffsetDateTime plusFive = OffsetDateTime.of(date, offset); OffsetDateTime minusTwo = plusFive.withOffsetSameInstant(ZoneOffset.ofHours(-2 ));
时差 Period 类以年月日来表示日期差,而 Duration 以秒与毫秒来表示时间差;Duration 适用于处理 Instant 与机器时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 LocalDate firstDate = LocalDate.of(2010 , 5 , 17 ); LocalDate secondDate = LocalDate.of(2015 , 3 , 7 ); Period period = Period.between(firstDate, secondDate); int days = period.getDays(); int months = period.getMonths(); int years = period.getYears(); boolean isNegative = period.isNegative(); Period twoMonthsAndFiveDays = Period.ofMonths(2 ).plusDays(5 ); LocalDate sixthOfJanuary = LocalDate.of(2014 , 1 , 6 ); LocalDate eleventhOfMarch = sixthOfJanuary.plus(twoMonthsAndFiveDays); Instant firstInstant= Instant.ofEpochSecond( 1294881180 ); Instant secondInstant = Instant.ofEpochSecond(1294708260 ); Duration between = Duration.between(firstInstant, secondInstant); long seconds = between.getSeconds();long absoluteResult = between.abs ().toMinutes();long twoHoursInSeconds = Duration.ofHours(2 ).getSeconds();
Date和LocalDateTime转换 Date转LocalDateTime 1 2 3 Date d = new Date (950152210000 L) LocalDateTime ldt = LocalDateTime.ofInstant (d.toInstant, ZoneId.of("America/New_York" ) )
LocalDateTime转Date 1 2 LocalDateTime ldt = LocalDateTime.now () Date d = Date.from (ldt.atZone(ZoneId.of("Asia/Shanghai" ) ).toInstant)
Date转ZonedDateTime 1 2 3 Date d = new Date (950152210000 L) ZonedDateTime zdt = ZonedDateTime.ofInstant (d.toInstant, ZoneId.of("America/New_York" ) )