时区
格林尼治时间已经不再被作为标准时间使用,UTC 是最主要的世界时间标准。
JAVA提供了获取当前时间的方法
例如,要测量一些代码需要执行多长时间,实现如下,
long startTime = System.nanoTime(); //...the code being measured long estimatedTime = System.nanoTime() - startTime;
时间粒度
事实上System.currentTimeMillis()方法的时间粒度是大于1毫秒的。如果你反复执行这个方法,你会发现短时间内得到的结果是相同的,随后又突然在某一次结果增加了几十毫秒(也可能更多)。这是很正常的,毕竟这个方法肯定不是世界上最精密的计时器。
旧的时间API
旧的时间API存在诸多问题,例如
java.util.Date类
java.util.Date类用于封装日期及时间信息,一般仅用它显示某个日期,不对他作任何操作处理,作处理推荐用Calendar类,计算方便。以下已过时的方法没有列出,可自行查看jdk文档
构造方法
//1、使用Date类获取当前系统时间 Date date = new Date(); System.out.println(date); //由于Date类覆盖了toString()方法,所以可以直接输出Date类型的对象 //输出结果为Fri May 31 10:51:18 GMT+08:00 2019 /*给Date设置年份需要减去 1900 *输出结果Tue Jul 01 00:00:00 GMT+08:00 3919 *原来这里存在一个起始年份 1900,实际年份是要在你的年份参数上加上个起始年份。 */ Date date1 = new Date(2019,5,31); System.out.println(date1); //2.分配 Date 对象并初始化此对象,以表示自从标准基准时间(称为“历元(epoch)”,即 1970 年 1 月 1 日 00:00:00 GMT)以来的指定毫秒数。 Date date2 = new Date(System.currentTimeMillis());//参数为19701月1日以来的毫秒数 Date date3 = new Date(1559284293556l); //long类型要加l System.out.println(date2); System.out.println(date3); //其他Date方法摘要可查看api
Calendar类与GregorianCalendar
java.util.Calendar类用于封装日历信息,其主作用在于其方法可以对时间分量进行运算。Calendar类是一个抽象类,它为特定瞬间与一组诸如 YEAR、MONTH、DAY_OF_MONTH、HOUR 等 日历字段之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)提供了一些方法。
由于Calendar类是抽象类,且Calendar类的构造方法是protected的,所以无法使用Calendar类的构造方法来创建对象,API中提供了getInstance方法用来创建对象。
Java只提供java.util.GregorianCalendar这一种java.util.Calendar的实现类,可以直接新建出来使用:
Calendar calendar = new GregorianCalendar();//新建出来的calendar默认时间为当前时间,或者说创建出这个对象的时间。
通过Calendar的静态方法获取一个实例该方法会根据当前系统所在地区来自行决定时区,帮我们创建Calendar实例,这里要注意,实际上根据不同的地区,Calendar有若干个子类实现。而Calendar本身是抽象类,不能被实例化!我们不需要关心创建的具体实例为哪个子类,我们只需要根据Calendar规定的方法来使用就可以了。
日历类所解决的根本问题是简化日期的计算,要想表示某个日期还应该使用Date类描述。Calendar是可以将其描述的时间转化为Date的,我们只需要调用其getTime()方法就可以获取描述的日期的Date对象了。
通过日历类计算时间:为日历类设置时间,日历类设置时间使用通用方法set。set(int field,int value),field为时间分量,Calendar提供了相应的常量值,value为对应的值。
只有月份从0开始:0为1月,以此类推,11为12月,其他时间是正常的从1开始。也可以使用Calendar的常量 calendar.NOVEMBER……等。
Calendar.DAY_OF_MONTH 月里边的天---号; Calendar.DAY_OF_WEEK 星期里的天---星期几 Calendar.DAY_OF_YEAR 年里的天 Calendar calendar=Calendar.getInstance();//构造出来表示当前时间的日历类 Date now=calendar.getTime();//获取日历所描述的日期 calendar.set(Calendar.YEAR, 2019);//设置日历表示2019年 calendar.set(Calendar.DAY_OF_MONTH,15);//设置日历表示15号 calendar.add(Calendar.DAY_OF_YEAR, 22);//想得到22天以后是哪天 calendar.add(Calendar.DAY_OF_YEAR, -5);//5天以前是哪天 calendar.add(Calendar.MONTH, 1);得到1个月后是哪天 System.out.println(calendar.getTime());
获取当前日历表示的日期中的某个时间单位可以使用get方法。
int year = calendar.get(Calendar.YEAR); int month = calendar.get(Calendar.MONTH) int day = calendar.get(Calendar.DAY_OF_MONTH); System.out.println(year+"年"+(month+1)+"月"+day+"日");//month要处理
SimpleDateFormat类
常用构造方法
SimpleDateFormat(String pattern),pattern -为描述日期和时间格式的模式
用给定的模式和默认语言环境的日期格式符号构造 SimpleDateFormat。 注:此构造方法可能不支持所有语言环境。要覆盖所有语言环境,请使用 DateFormat 类中的工厂方法。
常用方法
public final String format(Date date)将一个 Date 格式化为日期/时间字符串 public Date parse(String source)throws ParseException从给定字符串的开始解析文本,以生成一个日期。
字符串转成Date对象
//创建一个SimpleDateFormat并且告知它要读取的字符串格式 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); String dateFormat = "2019-05-31";//创建一个日期格式字符串 //将一个字符串转换为相应的Date对象 Date date = sdf.parse(dateFormat);//要先捕获异常 System.out.println(date);//输出这个Date对象
Date对象转成字符串
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); Date date = new Date(); String dateStr = sdf.format(date);//把日期对象传进去,继承自DateFormat类的方法。将一个Date格式化为日期/时间字符串
在日期格式中,-和空格无特殊意义。无特殊含义的都原样输出
//将当前系统时间转换为2012/05/14 17:05:22的效果 SimpleDateFormat format1 = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss"); dateStr=format1.format(date); System.out.println(dateStr);
DateFormat类
java.text.DateFormat类(抽象类)是SimpleDateFormat类的父类,用的少,没SimpleDateFormat灵活。
java.sql.Date
java.sql.Date继承java.util.Date,为了把前者转为后者,需要以下代码
Date date = new Date(); //java.sql.Date 不支持Date参数的构造器,传入long类型的时间 java.sql.Date d = new java.sql.Date(date.getTime()); System.out.println(d); System.out.println(d.getHours());
输出结果
2019-05-31 Exception in thread "main" java.lang.IllegalArgumentException at java.sql.Date.getHours(Unknown Source) at DateTest.DateTest1.main(DateTest1.java:40)
这是由于java.sql.Date是SQL中的单纯的日期类型,没有时分秒。故一般通过JDBC插入java.sql.Date数据时,会发现时分秒都没有。因此,如果同时需要日期和时间,应该使用Timestamp。它也是 java.util.Date 的子类,Timestamp 则包含时间戳的完整信息。
java.sql.Timestamp是java.util.Date的派生类(继承),所以在java.util.Date上能做的事,也可以在java.sql.Timestamp上做。
如果当前是2019-06-01 14:35,你创建一个java.sql.Date 将只记下2019-06-01这个信息。若你需要保留时间进行JDBC操作,请使用java.sql.Timestamp代替。
long time = System.currentTimeMillis(); java.sql.Timestamp timestamp = new java.sql.Timestamp(time); timestamp.setNanos(123456); int nanos = timestamp.getNanos(); // nanos = 123456
现在的Date类中大部分方法已经弃用,现在一般使用旧的API,Date只负责存储一个时间,并对Calendar和DateFormat提供操作接口。Calendar获取Date中特定的信息,对日期时间进行操作,SimpleDateFormat对日期时间进行格式化输入输出。
总的来说,Date、Calendar 和 DateFormat 已经能够处理一般的时间日期问题了。但是不可避免的是,它们依然很繁琐,不好用并且这些日期类都是可变且线程不安全的
Joda-Time
详情请查看https://www.ibm.com/developerworks/cn/java/j-jodatime.html
Java 8 时间日期API
特性
Java8日期时间的默认格式如下:yyyy-MM-dd-HH-mm-ss.zzz
几个主要的核心类:
下面看看这些类具体如何使用
LocalDate、LocalTime、LocalDateTime
LocalDate是不变的日期时间对象代表一个日期,往往被视为年月日。其他日期字段,如一年中的一天,一周和一周的一天,也可以访问。例如,“2007年10月2日”的值可以被存储在一个LocalDate。
LocalTime是不变的日期时间对象代表一个时间,往往被视为小时分钟秒。时间为代表的纳秒级精度。例如,值“13:45.30.123456789”可以存储在一个LocalTime。
LocalDateTime是不变的日期时间对象代表一个日期时间,往往被视为年、月、日、时、分、秒。其他日期和时间字段,如一年中的一天,一周和一周的一天,也可以访问。时间为代表的纳秒级精度。例如,值“2007年10月2日在13:45.30.123456789”可以存储在一个LocalDateTime。
//now()在默认时区中从系统时钟获取当前日期。 LocalDate today = LocalDate.now(); LocalDate nextWeek = today.plus(1, ChronoUnit.WEEKS); //等价于 today.plusWeeks(1) //of(int year, int month, int dayOfMonth) 获得 LocalDate实例从一年、月、日。 LocalDate date = LocalDate.of(2019,5,31); LocalTime time = LocalTime.of(20,31,20); //of(LocalDate date, LocalTime time) 获得 LocalDateTime实例的日期和时间。 LocalDateTime dateTime = LocalDateTime.of(date,time); System.out.println(dateTime); //LocalDate结合LocalTime成一个LocalDateTime LocalDateTime dateTime2 = date.atTime(time); System.out.println(dateTime2); //2019-05-31T20:31:20
DateTimeFormatter
格式器用于解析日期字符串和格式化日期输出,创建格式器最简单的方法是通过 DateTimeFormatter 的静态工厂方法以及常量。创建格式器一般有如下三种方式:
和旧的 java.util.DateFormat 相比较,所有的 DateTimeFormatter 实例都是线程安全的。
使用DateTimeFormatter完成格式化
//获取默认时区中从系统时钟获取当前日期时间。 LocalDateTime localDateTime = LocalDateTime.now(); //创建一个格式化程序使用指定的模式。 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); String formatDateTime = localDateTime.format(formatter); System.out.println(formatDateTime); //DateTimeFormatter提供了一些默认的格式化器,DateTimeFormatter.ISO_LOCAL_DATE_TIME 格式 yyyy-MM-ddTHH:mm:ss.SSS String dateTime2 = localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); System.out.println(dateTime2);
使用DateTimeFormatter完成解析字符串
//获得 LocalDate实例,从使用特定格式的文本字符串解析,文字使用格式化程序解析,返回一个日期。 LocalDate localDate = LocalDate.parse("2018/11/11",DateTimeFormatter.ofPattern("yyyy/MM/dd")); System.out.println(localDate); //2018-11-11
Instant
Instant 表示时间线上的一点(与 Date 类似),它只是简单地表示自 1970 年 1 月 1 日 0 时 0 分 0 秒(UTC)开始的秒数。
Instant 由两部分组成,一是从原点开始到指定时间点的秒数 s, 二是距离该秒数 s 的纳秒数。它以 Unix 时间戳的形式存储日期时间,不提供处理人类意义上的时间单位(年月日等)。
你可以通过Instant类的工厂方法创建一个Instant实例
//调用instant.now()来创建一个确切的表达当前时间的Instant对象.另外也有一些其它方法能创建Instant,具体请查阅Java官方文档。 Instant now = Instant.now(); Instant later = now.plusSeconds(3); Instant earlier = now.minusSeconds(3); //第一个参数是秒,第二个是纳秒参数,纳秒的存储范围是0至999,999,999 //2s之后的在加上100万纳秒(1s) Instant instant = Instant.ofEpochSecond(2,1000000000); System.out.println(instant); //1970-01-01T00:00:03Z Instant instant1 = Instant.now(); System.out.println(instant1); //2019-05-31T16:19:28.719Z Instant instant2 = Instant.parse("2018-11-11T10:12:35.342Z"); System.out.println(instant2); //2018-11-11T10:12:35.342Z //java.util.Date与Instant可相互转换 Instant timestamp = new Date().toInstant(); Date.from(Instant.now()); //为了更好的显示,代码改写为 Date date = new Date(); Instant timestamp = date.toInstant(); System.out.println(date); System.out.println(timestamp); Instant now1 = Instant.now(); Date date1 = Date.from(now1); System.out.println(now1); System.out.println(date1); //输出结果 Sat Jun 01 00:29:42 GMT+08:00 2019 2019-05-31T16:29:42.566Z 2019-05-31T16:29:42.588Z Sat Jun 01 00:29:42 GMT+08:00 2019
Clock
Clock用于查找当前时刻,可以用来获取某个时区下当前的日期和时间,也可以用来代替旧的System.currentTimeMillis()方法和TimeZone.getDefault()方法。
//返回系统默认时间 Clock clock = Clock.systemDefaultZone(); System.out.println(clock.instant().toString()); //世界协调时UTC Clock clock = Clock.systemUTC(); //通过Clock获取当前时刻 System.out.println("当前时刻为:" + clock.instant()); //获取clock对应的毫秒数,与System.currentTimeMillis()输出相同 System.out.println(clock.millis()); System.out.println(System.currentTimeMillis());
Duration
一个Duration实例是不可变的,当创建出对象后就不能改变它的值了。你只能通过Duration的计算方法,来创建出一个新的Durtaion对象。你会在之后的教程中见到的。一个Duration对象表示两个Instant间的一段时间。
创建Duration实例,使用Duration类的工厂方法来创建一个Duration对象
Instant first = Instant.now(); // wait some time while something hAppens Instant second = Instant.now(); Duration duration = Duration.between(first, second); //获得Duration表示秒数,然后获得在此期间的分钟数、小时数、天数 Duration d = Duration.ofSeconds(6000); System.out.println("6000秒相当于" + d.toMinutes() + "分"); System.out.println("6000秒相当于" + d.toHours() + "小时"); System.out.println("6000秒相当于" + d.toDays() + "天");
访问Duration的时间
一个Duration对象里有两个域:纳秒值(小于一秒的部分),秒钟值(一共有几秒),他们的组合表达了时间长度。注意与使用System.getCurrentTimeMillis()时不同,Duration不包含毫秒这个属性。
你可以通过以下两个方法得到它们的值:getSeconds()和getNano()
Period
Period 是以年月日来衡量一个时间段,用于计算两个日期间隔,所以 between() 方法只能接收 LocalDate 类型的参数。
LocalDate start = LocalDate.of(2018, Month.JANUARY, 1); LocalDate end = LocalDate.of(2020, Month.NOVEMBER, 11); System.out.println("相隔月数:"+Period.between(start, end).getMonths()); System.out.println("相隔天数:"+Period.between(start, end).getDays()); //输出结果 相隔月数:10 相隔天数:10
值得注意的是,Period 得到的是差值的绝对值(对应年月日直接计算数学上的差值),而并不表示真正的区间距离。
long distanceMonth = start.until(end, ChronoUnit.MONTHS); long distanceDay= start.until(end, ChronoUnit.DAYS); System.out.println("相隔月数"+distanceMonth); System.out.println("相隔天数"+distanceDay); //输出结果 相隔月数:34 相隔天数:1045
ZonedDateTime和ZonedId
ZonedDateTime类是Java 8中日期时间功能里,用于表示带时区的日期与时间信息的类。ZonedDateTime 类的值是不可变的,所以其计算方法会返回一个新的ZonedDateTime 实例。
Java 使用 ZoneId 来标识不同的时区,从基准 UTC 开始的一个固定偏移。
创建一个ZonedDateTime实例
//使用当前时间作为值新建对象 ZonedDateTime dateTime = ZonedDateTime.now(); //使用指定的年月日、时分、纳秒以及时区ID来新建对象 //时区是用ZoneId类表示的,可以使用ZoneId.now()或ZoneId.of(“xxx”)来实例化: //传给of()方法的参数是时区的ID,如“UTC+1”指距离UTC(格林威治时间)有一小时的时差 ZoneId zoneId = ZoneId.of("UTC+1"); ZonedDateTime dateTime2 = ZonedDateTime.of(2019, 6, 1, 14, 40, 48, 1234, zoneId); //也可以使用另一种方式表示zone id,即使用地区名字 ZoneId zoneId2 = ZoneId.of("Europe/Copenhagen"); //GregorianCalendar与ZonedDateTime相互转换 ZonedDateTime zonedDateTime = new GregorianCalendar().toZonedDateTime(); GregorianCalendar.from(zonedDateTime);
TemporalAdjusters
有的时候,你需要进行一些更加复杂的操作,比如,将日期调整到下个周日、下个工作日,或者是本月的最后一天。
简单应用例子
LocalDate localDate = LocalDate.now(); // 1. 本月第一天 LocalDate firstDayOfMonth = localDate.with(TemporalAdjusters.firstDayOfMonth()); // 2. 本月最后一天 LocalDate lastDayOfMonth = localDate.with(TemporalAdjusters.lastDayOfMonth()); // 3. 本年第一天 LocalDate firstDayOfYear = localDate.with(TemporalAdjusters.firstDayOfYear()); // 4. 下个月第一天 LocalDate firstDayOfNextMonth = localDate.with(TemporalAdjusters.firstDayOfNextMonth()); // 5. 本年度最后一天 LocalDate lastDayOfYear = localDate.with(TemporalAdjusters.lastDayOfYear()); System.out.println(firstDayOfMonth); System.out.println(lastDayOfMonth); System.out.println(firstDayOfYear); System.out.println(firstDayOfNextMonth); System.out.println(lastDayOfYear);
这时,你可以使用重载版本的with方法,向其传递一个提供了更多定制化选择的TemporalAdjuster对象,更加灵活地处理日期。TemporalAdjusters类通过静态方法提供了大量的常用的TemporalAdjuster的实现供我们使用。
/** * 时间校正器TemporalAdjuster */ @Test public void Test() { LocalDateTime now1 = LocalDateTime.now(); //获取月中的第一天 now1.with(TemporalAdjusters.firstDayOfMonth()); //获取下一年的第一天 now1.with(TemporalAdjusters.firstDayOfNextYear()); //获取年中第一天 now1.with(TemporalAdjusters.lastDayOfYear()); //获取月中最后一天 now1.with(TemporalAdjusters.lastDayOfMonth()); //获取下个星期一 now1.with(TemporalAdjusters.next(DayOfWeek.MONDAY)); //自定时时间:下一个工作日,因为这里需要一个接口,所以完全可以自定义方法 now1.with((e) -> { LocalDateTime now = (LocalDateTime)e; DayOfWeek dow = now.getDayOfWeek(); if (dow.equals(DayOfWeek.FRIDAY)) return now.plusDays(3); else if (dow.equals(DayOfWeek.SATURDAY)) return now.plusDays(2); return now.plusDays(1); }); }
转换
java.util.Date 与 LocalDate、LocalTime、LocalDateTime 转换
将Date转换为LocalDate,LocalTime,LocalDateTime可以借助于ZonedDateTime和Instant,实现如下:
Date date = new Date(); System.out.println("current date: " + date); // Date -> LocalDateTime LocalDateTime localDateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); System.out.println("localDateTime by Instant: " + localDateTime); // Date -> LocalDate LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); System.out.println("localDate by Instant: " + localDate); // Date -> LocalTime LocalTime localTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalTime(); System.out.println("localTime by Instant: " + localTime); //2. Date -> LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); System.out.println("localDateTime by ofInstant: " + localDateTime);
由于JDK8实现了向下兼容,所以Date里在JDK8版本引入了2个方法,from和
toInstant,所以我们可以借助这两个方法来实现LocalDateTime到Date的转换。将LocalDateTime转为Date如下:
LocalDateTime localDateTime = LocalDateTime.now(); System.out.println("localDateTime: " + localDateTime); // LocalDateTime -> Date Date date = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); System.out.println("LocalDateTime -> current date: " + date); // LocalDate -> Date,时间默认都是00 LocalDate localDate = LocalDate.now(); date = Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()); System.out.println("LocalDate -> current date: " + date);
日期与字符串的转换
通过LocalDate,LocalTime,LocalDateTime的parse方法和DateTimeFormatter来实现:
//字符串->日期 LocalDate localDate = LocalDate.parse("2018-09-09", DateTimeFormatter.ofPattern("yyyy-MM-dd")); LocalDateTime localDateTime = LocalDateTime.parse("2018-09-10 12:12:12", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); //日期->字符串 String localDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); String localDateTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); // 也可以通过DateTimeFormatter的format方法 DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); localDateTime = dateTimeFormatter.format(LocalDateTime.now());
时间戳与LocalDateTime转换
具体实现如下:
//时间戳->LocalDateTime public static LocalDateTime convertToDate(long timestamp) { // ofEpochSecond 以秒为单位, ofEpochMilli 以毫秒为单位 // Instant.ofEpochSecond(timestamp); Instant instant = Instant.ofEpochMilli(timestamp); return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); } //LocalDateTime->时间戳 public static long convertToTimestamp() { LocalDateTime localDateTime = LocalDateTime.now(); return localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); }
总结