Java 时间与日期处理 从属于笔者的现代 Java 开发系列文章,涉及到的引用资料声明在 Java 学习与实践资料索引中。java
在 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 这样表明了通常时区概念、易于理解的对象。sql
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”。编程
人们常常会把时区与 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 倍。安全
YYYY/MM/DD HH:MM:SS ± timezone(时区用4位数字表示) // eg 1992/02/12 12:23:22+0800
国际标准化组织的国际标准 ISO 8601 是日期和时间的表示方法,全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》。目前最新为第三版 ISO8601:2004,初版为 ISO8601:1988,第二版为 ISO8601:2000。年由 4 位数组成,以公历公元 1 年为 0001 年,以公元前 1 年为 0000 年,公元前 2 年为 -0001 年,其余以此类推。应用其余纪年法要换算成公历,但若是发送和接受信息的双方有共同一致赞成的其余纪年法,能够自行应用。并发
YYYY-MM-DDThh:mm:ss ± timezone(时区用HH:MM表示) 1997-07-16T08:20:30Z // “Z”表示UTC标准时区,即"00:00",因此这里表示零时区的`1997年7月16日08时20分30秒` //转换成位于东八区的北京时间则为`1997年7月17日16时20分30秒` 1997-07-16T19:20:30+01:00 // 表示东一区的1997年7月16日19时20秒30分,转换成UTC标准时间的话是1997-07-16T18:20:30Z
在 Java 8 以前,咱们使用 java.sql.Timestamp
来表示时间戳对象,能够经过如下方式建立与获取对象:函数
// 利用系统标准时间建立 Timestamp timestamp = new Timestamp(System.currentTimeMillis()); // 从 Date 对象中建立 new Timestamp((new Date()).getTime()); // 获取自 1970-01-01 00:00:00 GMT 以来的毫秒数 timestamp.getTime();
在 Java 8 中,便可以使用 java.time.Instant
来表示自从 1970-01-01T00:00:00Z 以后通过的标准时间:学习
// 基于静态函数建立 Instant instant = Instant.now(); // 基于 Date 或者毫秒数转换 Instant someInstant = someDate.toInstant(); Instant someInstant = Instant.ofEpochMilli(someDate.getTime()); // 基于 TimeStamp 转换 Instant instant = timestamp.toInstant(); // 从 LocalDate 转化而来 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
对象。ui
Clock clock = Clock.systemDefaultZone(); long millis = clock.millis(); Instant instant = clock.instant(); Date legacyDate = Date.from(instant); // legacy java.util.Date
// 默认建立 Date date0 = new Date(); // 从 TimeStamp 中建立 Date date1 = new Date(time); // 基于 Instant 建立 Date date = Date.from(instant); // 从格式化字符串中获取 SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd"); java.util.Date dt=sdf.parse("2005-2-19"); // 从 LocalDateTime 中转化而来 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 类实现了这个接口。
Date 用于记录某一个含日期的、精确到毫秒的时间。重点在表明一刹那的时间自己。 Calendar 用于将某一日期放到历法中的互动——时间和年、月、日、星期、上午、下午、夏令时等这些历法规定互相做用关系和互动。咱们能够经过 Calendar 内置的构造器来建立实例:
Calendar.Builder builder =new Calendar.Builder(); Calendar calendar1 = builder.build(); Date date = calendar.getTime();
在 Calendar 中咱们则可以得到较为直观的年月日信息:
// 2017,再也不是 2017 - 1900 = 117 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 等方法进行日期的加减。
SimpleDateFormat 用来进行简单的数据格式化转化操做:
Date dNow = new Date( ); SimpleDateFormat ft = new SimpleDateFormat ("E yyyy.MM.dd 'at' hh:mm:ss a zzz");
// 取当前日期: LocalDate today = LocalDate.now(); // 根据年月日取日期,12月就是12: LocalDate crischristmas = LocalDate.of(2017, 5, 15); // 根据指定格式字符串取 LocalDate endOfFeb = LocalDate.parse("2017-05-15"); // 严格按照ISO yyyy-MM-dd验证,02写成2都不行,固然也有一个重载方法容许本身定义格式 LocalDate.parse("2014-02-29"); // 无效日期没法经过:DateTimeParseException: Invalid date // 经过自定义时间字符串格式获取 DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedDate(FormatStyle.MEDIUM) .withLocale(Locale.GERMAN); LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter); System.out.println(xmas); // 2014-12-24 // 获取其余时区下日期 LocalDate localDate = LocalDate.now(ZoneId.of("GMT+02:30")); // 从 LocalDateTime 中获取实例 LocalDateTime localDateTime = LocalDateTime.now(); LocalDate localDate = localDateTime.toLocalDate();
// 取本月第1天 LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth()); // 2014-12-01 // 取本月第2天 LocalDate secondDayOfThisMonth = today.withDayOfMonth(2); // 2014-12-02 // 取本月最后一天,不再用计算是28,29,30仍是31 LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth()); // 2014-12-31 // 取下一天 LocalDate firstDayOf2015 = lastDayOfThisMonth.plusDays(1); // 变成了2015-01-01 // 取2015年1月第一个周一 LocalDate firstMondayOf2015 = LocalDate.parse("2015-01-01").with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)); // 2015-01-05
// 获取其余时区下时间 LocalTime localTime = LocalTime.now(ZoneId.of("GMT+02:30")); // 从 LocalDateTime 中获取实例 LocalDateTime localDateTime = LocalDateTime.now(); LocalTime localTime = localDateTime.toLocalTime(); - 12:00 - 12:01:02 - 12:01:02.345
// 经过时间戳建立 LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochSecond(1450073569l), TimeZone.getDefault().toZoneId()); // 经过 Date 对象建立 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); // Nov 03, 2014 - 07:13
获取年、月、日等信息
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59); DayOfWeek dayOfWeek = sylvester.getDayOfWeek(); System.out.println(dayOfWeek); // WEDNESDAY Month month = sylvester.getMonth(); System.out.println(month); // DECEMBER long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY); System.out.println(minuteOfDay); // 1439
时间格式化展现
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); LocalDateTime dateTime = LocalDateTime.of(1986, Month.APRIL, 8, 12, 30); String formattedDateTime = dateTime.format(formatter); // "1986-04-08 12:30"
localDateTime.plusDays(1); localDateTime.minusHours(2);
Timezones 以 ZoneId
来区分。能够经过静态构造方法很容易的建立,Timezones 定义了 Instants 与 Local Dates 之间的转化关系:
System.out.println(ZoneId.getAvailableZoneIds()); // prints all available timezone ids ZoneId zone1 = ZoneId.of("Europe/Berlin"); ZoneId zone2 = ZoneId.of("Brazil/East"); System.out.println(zone1.getRules()); System.out.println(zone2.getRules()); // ZoneRules[currentStandardOffset=+01:00] // ZoneRules[currentStandardOffset=-03:00]
LocalDateTime ldt = ... ZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault()); Date output = Date.from(zdt.toInstant());
ZoneId losAngeles = ZoneId.of("America/Los_Angeles"); ZoneId berlin = ZoneId.of("Europe/Berlin"); // 2014-02-20 12:00 LocalDateTime dateTime = LocalDateTime.of(2014, 02, 20, 12, 0); // 2014-02-20 12:00, Europe/Berlin (+01:00) ZonedDateTime berlinDateTime = ZonedDateTime.of(dateTime, berlin); // 2014-02-20 03:00, America/Los_Angeles (-08:00) ZonedDateTime losAngelesDateTime = berlinDateTime.withZoneSameInstant(losAngeles); int offsetInSeconds = losAngelesDateTime.getOffset().getTotalSeconds(); // -28800 // a collection of all available zones Set<String> allZoneIds = ZoneId.getAvailableZoneIds(); // using offsets LocalDateTime date = LocalDateTime.of(2013, Month.JULY, 20, 3, 30); ZoneOffset offset = ZoneOffset.of("+05:00"); // 2013-07-20 03:30 +05:00 OffsetDateTime plusFive = OffsetDateTime.of(date, offset); // 2013-07-19 20:30 -02:00 OffsetDateTime minusTwo = plusFive.withOffsetSameInstant(ZoneOffset.ofHours(-2));
Period 类以年月日来表示日期差,而 Duration 以秒与毫秒来表示时间差;Duration 适用于处理 Instant 与机器时间。
// periods LocalDate firstDate = LocalDate.of(2010, 5, 17); // 2010-05-17 LocalDate secondDate = LocalDate.of(2015, 3, 7); // 2015-03-07 Period period = Period.between(firstDate, secondDate); int days = period.getDays(); // 18 int months = period.getMonths(); // 9 int years = period.getYears(); // 4 boolean isNegative = period.isNegative(); // false Period twoMonthsAndFiveDays = Period.ofMonths(2).plusDays(5); LocalDate sixthOfJanuary = LocalDate.of(2014, 1, 6); // add two months and five days to 2014-01-06, result is 2014-03-11 LocalDate eleventhOfMarch = sixthOfJanuary.plus(twoMonthsAndFiveDays); // durations Instant firstInstant= Instant.ofEpochSecond( 1294881180 ); // 2011-01-13 01:13 Instant secondInstant = Instant.ofEpochSecond(1294708260); // 2011-01-11 01:11 Duration between = Duration.between(firstInstant, secondInstant); // negative because firstInstant is after secondInstant (-172920) long seconds = between.getSeconds(); // get absolute result in minutes (2882) long absoluteResult = between.abs().toMinutes(); // two hours in seconds (7200) long twoHoursInSeconds = Duration.ofHours(2).getSeconds();