1. JDK 8之前的时间和日期
1.1 使用Date与DateFormat
如果想要取得系统时间,方法之一是使用System.currentTimeMillis()方法,返回的是long类型整数,代表1970年1月1日0时0分0秒0毫秒至今经过的毫秒数,可以使用Date类让这个整数变得有意义一些。
Date有两个构造函数可以使用:
- 一个可使用System.currentTimeMillis()的值创建;
- 一个为无自变量构造函数,调用Date的toString()可以用具意义的文字显示系统时间,调用getTime()可取得与System.currentTimeMillis()相同的结果。
public class DateDemo {
public static void main(String[] args) {
Date date1 = new Date(currentTimeMillis());
Date date2 = new Date();
out.println(date1.getTime());
out.println(date2.getTime());
}
}
如果想按照指定的格式输出日期或当中的时间怎么办?可以使用DateFormat来做格式化。
DateFormat是个抽象类,其操作类是java.text.SimpleDateFormat。可以直接创建SimpleDateFormat实例,或使用DateFormat的getDateInstance()、getTimeInstance()、getDateTimeInstance()等静态方法,用较简便方式依不同需求取得SimpleDateFormat实例。
public class DateFormatDemo {
public static void main(String[] args) {
Date date = new Date();
dateInstanceDemo(date);
timeInstanceDemo(date);
dateTimeInstanceDemo(date);
}
static void dateInstanceDemo(Date date) {
out.println("getDateInstance() demo");
out.printf("\tSHORT: %s%n", getDateInstance(LONG).format(date));
out.printf("\tSHORT: %s%n", getDateInstance(SHORT).format(date));
}
static void timeInstanceDemo(Date date) {
out.println("getTimeInstance() demo");
out.printf("\tLONG: %s%n", getTimeInstance(LONG).format(date));
out.printf("\tMEDIUM: %s%n", getTimeInstance(MEDIUM).format(date));
out.printf("\tSHORT: %s%n",getTimeInstance(SHORT).format(date));
}
static void dateTimeInstanceDemo(Date date) {
out.println("getDateTimeInstance() demo");
out.printf("\tLONG: %s%n",
getDateTimeInstance(LONG, LONG).format(date));
out.printf("\tMEDIUM: %s%n",
getDateTimeInstance(SHORT, MEDIUM).format(date));
out.printf("\tSHORT: %s%n",
getDateTimeInstance(SHORT, SHORT).format(date));
}
}
直接创建SimpleDateFormat的好处是,可使用模式字符串自定义格式。例如:
public static void main(String[] args) {
DateFormat dateFormat =
new SimpleDateFormat("EE-MM-dd-yyyy", Locale.JAPAN);
Locale locale = new Locale("ja", "JP", "JP");
DateFormat kanjiFormat = new SimpleDateFormat("GGGGy年M月d日", locale);
System.out.println(dateFormat.format(new Date()));
System.out.println(kanjiFormat.format(new Date()));
}
SimpleDateFormat会依指定的Locale设定显示对应语言,EE表示星期格式设定,MM表示月份格式设定,dd表示日期格式设定,而yyyy是公元格式设定。每个字符设定都有其意义,可参考SimpleDateFormat的API说明,了解每个字符的设定意义。
SimpleDateFormat还有个parse()方法,可以依创建SimpleDateFormat时指定的格式,将指定的字符串剖析为Date实例。例如:
public static void main(String[] args) throws Exception {
System.out.print("出生年月日(yyyy-mm-dd):");
DateFormat dateFormat = new SimpleDateFormat("yyyy-mm-dd");
Date birthDate = dateFormat.parse(new Scanner(System.in).nextLine());
Date currentDate = new Date();
long life = currentDate.getTime() - birthDate.getTime();
System.out.println("你今年的岁数为:" +
(life / (365 * 24 * 60 * 60 * 1000L)));
}
1.2 使用Calendar
可以使用Date来取得完整日期时间,可单纯使用toString()取得日期文字描述,或使用DateFormat格式化日期,但如果想要取得某个时间或日期信息该如何进行?
例如想知道现在时间是6月的第几天。
若查看Date的API文件,会发现许多方法都不再建议使用(Deprecated),而建议改用Calendar的相关方法取代。
Calendar是个抽象类,java.util.GregorianCalendar是其子类成果,通常通过Calendar的getInstance()来取得Calendar实例,取得的实例代表当时系统时间。例如:
Calendar now = Calendar.getInstance(Locale.JAPAN)
取得Calendar实例后,可以使用getTime()取得Date实例。如果想知道Calendar代表公元几年,可以使用get()方法。例如:
System.out.println(now.get(Calendar.YEAR));
如果现在是2019年,则上例会显示2019的数字。依照这个范例,假设现在时间是9月份,而你想使用程序取得目前月份,则对以下执行结果你可能会困惑:
System.out.println(now.get(Calendar.MONTH));
上面的程序片段会显示5这个数字,而不是你预期的6。
事实上,返回值是对应于Calendar.SEPTEMBER常数值,get()指定某些常数会返回对应某些常数的值,记得查询API文件确认。
假设有个需求,想看看今天是星期几,显示不同的信息,那该怎么做呢?
public class CalendarDemo {
public static void main(String[] args) {
Map<Integer, String> days = new HashMap<>();
days.put(Calendar.MONDAY, "星期一猴子穿新衣");
days.put(Calendar.TUESDAY, "星期二猴子肚子餓");
days.put(Calendar.WEDNESDAY, "星期三猴子去爬山");
days.put(Calendar.THURSDAY, "星期四猴子去考試");
days.put(Calendar.FRIDAY, "星期五猴子去跳舞");
days.put(Calendar.SATURDAY, "星期六猴子去斗六");
days.put(Calendar.SUNDAY, "星期天猴子乐翻天");
Calendar calendar = Calendar.getInstance();
int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
System.out.println(days.get(dayOfWeek));
}
}
如果想以区域化方式显示星期、月份名称,可以使用Calendar的getDisplayNames()或getDisplayName()方法。例如:
public class CalendarDemo2 {
public static void main(String[] args) {
Calendar rightNow = Calendar.getInstance();
System.out.println("現在時間是:");
System.out.printf("%s%d %n",
rightNow.getDisplayName(ERA, LONG, Locale.JAPAN),
rightNow.get(YEAR));
System.out.println(
rightNow.getDisplayName(MONTH, LONG, Locale.JAPAN));
System.out.printf("%d日%n",
rightNow.get(DAY_OF_MONTH));
System.out.println(
rightNow.getDisplayName(DAY_OF_WEEK, LONG, Locale.JAPAN));
}
}
在取得一个Calendar的实例后,可以使用add()方法,来改变Calendar的时间。例如:
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MONTH, 1);
System.out.println(calendar);
calendar.add(Calendar.HOUR, 12);
System.out.println(calendar);
calendar.add(Calendar.YEAR, -2);
System.out.println(calendar);
calendar.add(Calendar.DAY_OF_MONTH, 3);
System.out.println(calendar);
如果打算只针对日期中某个字段加减,则可以使用roll()方法。例如:
calendar.roll(Calendar.MONTH, 1);
System.out.println(calendar);
1.3 计算时间差
public static void main(String[] args) {
Calendar c1 = Calendar.getInstance();
c1.clear();
Calendar c2 = Calendar.getInstance();
c2.clear();
c1.set(2020, 0, 1);
c2.set(2020, 2, 1);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
System.out.println("Date 1: " + sdf.format(c1.getTime()));
System.out.println("Date 2: " + sdf.format(c2.getTime()));
long time1 = c1.getTimeInMillis();
long time2 = c2.getTimeInMillis();
long diff = time2 - time1;
long diffSec = diff / 1000;
System.out.println("Difference in seconds " + diffSec);
long diffMin = diff / (60 * 1000);
System.out.println("Difference in minutes " + diffMin);
long diffHours = diff / (60 * 60 * 1000);
System.out.println("Difference in hours " + diffHours);
long diffDays = diff / (24 * 60 * 60 * 1000);
System.out.println("Difference in days " + diffDays);
}
2. 新时间日期API
新的时间API位于java.time包下。
2.1 Instant
Instant 和 Date 一样,表示一个时间戳,用于描述一个时刻,只不过它较 Date 而言,可以描述更加精确的时刻。并且 Instant 是时区无关的。
Date 最多可以表示毫秒级别的时刻,而 Instant 可以表示纳秒级别的时刻。例如:
- public static Instant now():根据系统当前时间创建一个 Instant 实例,表示当前时刻
- public static Instant ofEpochSecond(long epochSecond):通过传入一个标准时间的偏移值来构建一个 Instant 实例
- public static Instant ofEpochMilli(long epochMilli):通过毫秒数值直接构建一个 Instant 实例
public static void main(String[] args) {
Instant instant = Instant.now();
System.out.println(instant);
Instant instant1 = Instant.ofEpochSecond(20);
System.out.println(instant1);
Instant instant2 = Instant.ofEpochSecond(30, 100);
System.out.println(instant2);
Instant instant3 = Instant.ofEpochMilli(1000);
System.out.println(instant3);
}
可以看到,Instant 和 Date 不同的是,它是时区无关的,始终是格林零时区相关的,也即是输出的结果始终格林零时区时间。
2.2 LocalDateTime,LocalDate,LocalTime
以Local开头的都是对时间的描述。
2.2.1 LocalDate
LocalDate 专注于处理日期相关信息。LocalDate 依然是一个不可变类,它关注时间中年月日部分,我们可以通过以下的方法构建和初始化一个 LocalDate 实例:
- public static LocalDate now():截断当前系统时间的年月日信息并初始化一个实例对象
- public static LocalDate of(int year, int month, int dayOfMonth):显式指定年月日信息
- public static LocalDate ofYearDay(int year, int dayOfYear):根据 dayOfYear 可以推出 month 和 dayOfMonth
- public static LocalDate ofEpochDay(long epochDay):相对于格林零时区时间的日偏移量
LocalDate localDate = LocalDate.now();
System.out.println(localDate);
LocalDate localDate1 = LocalDate.of(2019, 6, 29);
System.out.println(localDate1);
LocalDate localDate2 = LocalDate.ofYearDay(2019, 100);
System.out.println(localDate2);
LocalDate localDate3 = LocalDate.ofEpochDay(10);
System.out.println(localDate3);
其他的方法
- public int getYear():获取年份信息
- public int getMonthValue():获取月份信息
- public int getDayOfMonth():获取当前日是这个月的第几天
- public int getDayOfYear():获取当前日是这一年的第几天
- public boolean isLeapYear():是否是闰年
- public int lengthOfYear():获取这一年有多少天
- public DayOfWeek getDayOfWeek():返回星期信息
2.2.2 LocalTime
LocalTime 专注于时间的处理,它提供小时,分钟,秒,毫微秒的各种处理,我们依然可以通过类似的方式创建一个 LocalTime 实例。
- public static LocalTime now():根据系统当前时刻获取其中的时间部分内容
- public static LocalTime of(int hour, int minute):显式传入小时和分钟来构建一个实例对象
- public static LocalTime of(int hour, int minute, int second):通过传入时分秒构造实例
- public static LocalTime of(int hour, int minute, int second, int nanoOfSecond):传入时分秒和毫微秒构建一个实例
- public static LocalTime ofSecondOfDay(long secondOfDay):传入一个长整型数值代表当前日已经过去的秒数
- public static LocalTime ofNanoOfDay(long nanoOfDay):传入一个长整型代表当前日已经过去的毫微秒数
同样的,LocalTime 默认使用系统默认时区处理时间,看代码:
public static void main(String[] args) {
LocalTime localTime = LocalTime.now();
System.out.println(localTime);
LocalTime localTime1 = LocalTime.of(23, 59);
System.out.println(localTime1);
LocalTime localTime2 = LocalTime.ofSecondOfDay(10);
System.out.println(localTime2);
}
LocalTime 中也同样封装了很多好用的工具方法,例如:
- public int getHour()
- public int getMinute()
- public int getSecond()
- public int getNano()
- public LocalTime withHour(int hour):修改当前 LocalTime 实例中的 hour 属性并重新返回一个新的实例
- public LocalTime withMinute(int minute):类似
- public LocalTime withSecond(int second):类似
2.2 LocalDateTime
LocalDateTime 类则是集成了 LocalDate 和 LocalTime,它既能表示日期,又能表述时间信息,方法都类似
2.3 时区相关
无论是我们的 LocalDate,或是 LocalTime,甚至是 LocalDateTime,它们基本是时区无关的,内部并没有存储时区属性,而基本用的系统默认时区。往往有些场景之下,缺乏一定的灵活性。
ZonedDateTime 可以被理解为 LocalDateTime 的外层封装,它的内部存储了一个 LocalDateTime 的实例,专门用于普通的日期时间处理。此外,它还定义了 ZoneId 和 ZoneOffset 来描述时区的概念。
ZonedDateTime 和 LocalDateTime 的一个很大的不同点在于,后者内部并没有存储时区,所以对于系统的依赖性很强,往往换一个时区可能就会导致程序中的日期时间不一致。
而后者则可以通过传入时区的名称,使用 ZoneId 进行匹配存储,也可以通过传入与零时区的偏移量,使用 ZoneOffset 存储时区信息。
所以,构建一个 ZonedDateTime 实例有以下几种方式:
- public static ZonedDateTime now():系统将以默认时区计算并存储日期时间信息
- public static ZonedDateTime now(ZoneId zone):指定时区
- public static ZonedDateTime of(LocalDate date, LocalTime time, ZoneId zone):指定日期时间和时区
- public static ZonedDateTime of(LocalDateTime localDateTime, ZoneId zone)
- public static ZonedDateTime ofInstant(Instant instant, ZoneId zone):通过时刻和时区构建实例对象
ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println(zonedDateTime);
LocalDateTime localDateTime = LocalDateTime.now();
ZoneId zoneId = ZoneId.of("Asia/Tokyo");
ZonedDateTime zonedDateTime1 = ZonedDateTime.of(localDateTime, zoneId);
System.out.println(zonedDateTime1);
Instant instant = Instant.now();
ZoneId zoneId1 = ZoneId.of("GMT");
ZonedDateTime zonedDateTime2 = ZonedDateTime.ofInstant(instant, zoneId1);
System.out.println(zonedDateTime2);
LocalTime localTime = LocalTime.of(0, 0, 0);
LocalDate localDate = LocalDate.of(1975, 4, 1);
ZonedDateTime zonedDateTime3 = ZonedDateTime.of(localDate, localTime, ZoneId.of("Asia/Tokyo"));
out.println(zonedDateTime3);
out.println(zonedDateTime3.toEpochSecond());
out.println(zonedDateTime3.toInstant().toEpochMilli());
2.4 格式化日期时间
Java 8 的新式日期时间 API 中,DateTimeFormatter 作为格式化日期时间的主要类,它与之前的 DateFormat 类最大的不同就在于它是线程安全的,其他的使用上的操作基本类似。我们看看:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(formatter.format(localDateTime));
String str = "2019年06月28日 23:59:59";
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
LocalDateTime localDateTime2 = LocalDateTime.parse(str, formatter2);
System.out.println(localDateTime2);
格式化主要有两种情况,一种是将日期时间格式化成字符串,另一种则是将格式化的字符串装换成日期时间对象。
DateTimeFormatter 提供将 format 方法将一个日期时间对象转换成格式化的字符串,但是反过来的操作却建议使用具体的日期时间类自己的 parse 方法,这样可以省去类型转换的步骤。
2.5 计算时间差
现实项目中,我们也经常会遇到计算两个时间点之间的差值的情况,最粗暴的办法是,全部幻化成毫秒数并进行减法运算,最后在转换回日期时间对象。
但是 java.time 包中提供了两个日期时间之间的差值的计算方法,我们一起看看。
关于时间差的计算,主要涉及到两个类:
- Period:处理两个日期之间的差值
- Duration:处理两个时间之间的差值
public static void main(String[] args) {
LocalDate date = LocalDate.of(2014, 7, 22);
LocalDate date1 = LocalDate.now();
Period period = Period.between(date, date1);
System.out.println(period.getYears() + "年" + period.getMonths() + "月" + period.getDays() + "天");
LocalTime time = LocalTime.of(20, 30);
LocalTime time1 = LocalTime.of(23, 59);
Duration duration = Duration.between(time, time1);
System.out.println(duration.toMinutes() + "分钟");
}