java-时间处理

4,373 阅读11分钟

概念

日期:2016-2-6 表示某一天他不是连续变换的,而是离散的
时间:20:21:59
时刻:2016-2-6 20:21:59 带日期的时间,他可以唯一的确定某一个时刻(Instant)
在不同的时区的同一时间,他们的本地时刻是不同的

时区的表示方式

  • GMT+08:00 表示东八区
  • UTC+08:00 表示东八区
  • CST 中国时间(缩写)
  • Asia/Shanghai 上海时间(国家/城市)

GMT与UTC基本一致,UTC使用原子钟计算更加精确

Locale概念

Locale用来针对当地用户习惯格式化日期、时间、数字、货币等:

  • zh_CN:2016-11-30 中国的locale
  • en_US:11/30/2016 美国的locale

计算机用locale在日期、时间、货币和字符之间进行转换:

理解数据的表示方式

数据的存储方式指的是指数据在内存中的保存方式

用日期和时间加上字符串展示时刻其实是时刻的展示方式:

那么计算机中时间的存储方式是什么?

在计算机中使用Epoch Time来存储时间。 Epoch Time:从1970年1月1日零点(格林威治时区/GMT+00:00)到现在的秒数,例如:

  • 北京2016-11-20 8:15:01=1479600901

当我们使用Epoch表示时间的时候实际上我们存储的是一个整数
当Epoch=1479600901在全球各地他的本地时间都能确定下来

在计算机中使用long类型的整形表示毫秒数,被称为Epoch Time或者Timestamp(时间戳)

long t=1479600901123L

java时间api

旧的

java.util 主要的类:Date、Calendar

Date

java.util.Date表示日期和时间,内部使用long表示epoch毫秒数
Date和long的转换
SimpleDateFormat:用于Date和String的解析和格式化

获取当前时间:

  • new Date().toString()获取GMT时间
  • long getTme() 获取时间戳

实例代码:1

import java.util.Date;
public class Main {

	public static void main(String[] args) throws Exception {
		// 获取系统当前时间戳:
		System.out.println(System.currentTimeMillis());
		// 获取当前时间:
		Date now = new Date();
		System.out.println(now);
		// 把Date转化为long:
		long t = now.getTime();
		System.out.println(t);
		// 把long转化为Date:
		System.out.println(new Date(t));
	}
}

输出

1554885481247
Wed Apr 10 16:38:01 CST 2019
1554885481249
Wed Apr 10 16:38:01 CST 2019

实例代码:2

import java.util.Date;
public class Date2String {
	public static void main(String[] args) throws Exception {
		// 获取当前时间:
		Date now = new Date();
		// 以当前时区打印日期时间:
		System.out.println(now.toString());
		// 以GMT+00:00时区打印日期时间:
		System.out.println(now.toGMTString());
		// 以当前时区+当前Locale打印日期时间:
		System.out.println(now.toLocaleString());
	}

}

输出

Wed Apr 10 16:33:33 CST 2019
10 Apr 2019 08:33:33 GMT
2019-4-10 16:33:33

把java.util.Date转换为string

  • toString()
  • toGMTString()
  • toLocaleString()
  • SimpleDateFormat上面3个是指定的格式,这个是自定义格式

SimpleDateFormat类的作用:参考文档

  • 将new Date()解析成想要格式的时间字符串
  • 将字符串解析成Date类型的时间对象

实例代码:1

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

public class Format {

	public static void main(String[] args) throws Exception {
		// 获取当前时间:
		Date now = new Date();
		// 指定格式打印:
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		System.out.println(sdf.format(now));
	}

}

输出

2019-04-10 16:39:12

实例代码:2

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

public class Parse {

	public static void main(String[] args) throws Exception {
		// 按系统Locale解析日期时间:
		String s1 = "2016-11-20 12:15:59";
		Date date1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(s1);
		System.out.println(date1);
		// 解析MMM时默认按照系统Locale:如果奥解析英文Nov需要指定解析环境
		String s2 = "Nov/20/2016 12:15:59";
		Date date2 = new SimpleDateFormat("MMM/dd/yyyy HH:mm:ss", Locale.US).parse(s2);
		System.out.println(date2);
		// 按ISO 8601标准格式解析:T用来分割时间和日期,所以解析的时候用单引号'T'来解析
		String iso = "2016-11-20T12:15:59";
		Date date3 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").parse(iso);
		System.out.println(date3);
	}

}

输出

Sun Nov 20 12:15:59 CST 2016
Sun Nov 20 12:15:59 CST 2016
Sun Nov 20 12:15:59 CST 2016

Date的问题:

  • java.util.Date不能转换时区
  • 日期和时间的加减比较困难
  • 不能计算两个日期相差多少天
  • 不能计算某个月第一个星期一是几号

Calendar

  • Calendar和Date、long可以互相转换
  • Calendar可以用set/get设置和获取指定字段
  • Calendar可以实现:
    • 设置特定的日期和时间
    • 设置时区并获得转换后的时间
    • 加减日期和时间
  • TimeZone表示时区,getAvailableIDs()可以枚举所有有效的时区ID

Java Calendar 日历类的时间操作 Timestamp Date Calendar 相互转换
示例代码:1

import java.util.Calendar;

public class Main {

	public static void main(String[] args) throws Exception {
		// 获取当前时间表示的Calendar:
		Calendar c = Calendar.getInstance();
		// 转换为Date打印:
		System.out.println(c.getTime());
		// 转换为long打印:
		System.out.println(c.getTimeInMillis());
		// 获取年月日时分秒:
		System.out.println("    Year = " + c.get(Calendar.YEAR));
		// 注意月份从0开始:1月=0,2月=1,...,12月=11:
		System.out.println("   Month = " + c.get(Calendar.MONTH));
		System.out.println("     Day = " + c.get(Calendar.DAY_OF_MONTH));
		// 注意星期从1开始:星期日=1,星期一=2,...,星期六=7:
		System.out.println(" Weekday = " + c.get(Calendar.DAY_OF_WEEK));
		System.out.println("    Hour = " + c.get(Calendar.HOUR_OF_DAY));
		System.out.println("  Minute = " + c.get(Calendar.MINUTE));
		System.out.println("  Second = " + c.get(Calendar.SECOND));
		System.out.println("  Millis = " + c.get(Calendar.MILLISECOND));
		// 默认时区:
		System.out.println("TimeZone = " + c.getTimeZone());
	}

}

输出

Wed Apr 10 22:14:57 CST 2019
1554905697700
    Year = 2019
   Month = 3
     Day = 10
 Weekday = 4
    Hour = 22
  Minute = 14
  Second = 57
  Millis = 700
TimeZone = sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=19,lastRule=null]

示例代码:2时间计算

import java.util.Calendar;

public class Calculate {

	public static void main(String[] args) throws Exception {
		// 获取当前时间表示的Calendar:
		Calendar c = Calendar.getInstance();
		// 转换为Date打印:
		System.out.println(c.getTime());
		// + 5 days:
		c.add(Calendar.DAY_OF_MONTH, 5);
		// - 2 hours:
		c.add(Calendar.HOUR_OF_DAY, -2);
		// 转换为Date打印:
		System.out.println(c.getTime());
	}
}

输出

Wed Apr 10 22:30:22 CST 2019
Mon Apr 15 20:30:22 CST 2019

实例代码:3设置时间

import java.util.Calendar;

public class SetTime {

	public static void main(String[] args) throws Exception {
		// 获取当前时间表示的Calendar:
		Calendar c = Calendar.getInstance();
		// 转换为Date打印:
		System.out.println(c.getTime());
		// 设置为指定时间:
		c.clear();
		c.set(Calendar.YEAR, 1999);
		c.set(Calendar.MONTH, 10); // 11月
		c.set(Calendar.DAY_OF_MONTH, 30);
		c.set(Calendar.HOUR_OF_DAY, 21);
		System.out.println(c.getTime());
	}

}

输出

Wed Apr 10 22:31:41 CST 2019
Tue Nov 30 21:00:00 CST 1999

示例代码:4时区

import java.util.Calendar;
import java.util.TimeZone;

public class Zone {

	public static void main(String[] args) throws Exception {
		// 获取当前时间:
		Calendar c = Calendar.getInstance();
		System.out.println(c.getTime());
		// 获取纽约时间:
		c.setTimeZone(TimeZone.getTimeZone("America/New_York"));
		int y = c.get(Calendar.YEAR);
		int m = c.get(Calendar.MONTH) + 1;
		int d = c.get(Calendar.DAY_OF_MONTH);
		int hh = c.get(Calendar.HOUR_OF_DAY);
		int mm = c.get(Calendar.MINUTE);
		int ss = c.get(Calendar.SECOND);
		System.out.println(y + "-" + m + "-" + d + " " + hh + ":" + mm + ":" + ss);
	}

}

输出

Wed Apr 10 22:33:00 CST 2019
2019-4-10 10:33:0

总结

date对象主要用于获取当前时间,或者给定一个时间按照不同格式进行转换输出,但是不能进行日期时间的计算。
Calendar对象处理可以获取当前时间,还可以进行时间运算等复杂的时间格式。

新的

java.time(JDK>=1.8) 主要类:LocalDate、LocalTime、ZonedDateTime、Instant 特点:

  • 严格区分日期和时间
  • 不变类 (类似String)
  • 修复了Month(1-12)和Week(1-7)的常量值
  • 增加了时间日期的运算

LocalDate、LocalTime、LocalDateTime

当我们打印LocalDate、LocalTime、LocalDateTime的时候他是严格按照ISO 8601格式输出的

由于这些新的时间API都是不可变对象,因此进行运算后需要使用一个新的LocalDate、LocalTime、LocalDateTime类型的变量来接收运算后的时间对象

示例代码1

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

public class Main {

	public static void main(String[] args) throws Exception {
		// 获取当前本地日期:
		LocalDate d1 = LocalDate.now();
		System.out.println(d1);
		System.out.println("Week = " + d1.getDayOfWeek().getValue());
		// 注意11月=11:
		LocalDate d2 = LocalDate.of(2016, 11, 30);
		System.out.println(d2);

		// 获取当前本地时间:
		LocalTime t1 = LocalTime.now();
		System.out.println(t1);
		LocalTime t2 = LocalTime.of(15, 16, 17);
		System.out.println(t2);

		// 获取当前本地日期和时间:
		LocalDateTime dt1 = LocalDateTime.now();
		System.out.println(dt1);
		// 用LocalDate和LocalTime组合:
		LocalDateTime dt2 = LocalDateTime.of(d2, t2);
		System.out.println(dt2);
	}

}

输出

2019-04-10
Week = 3
2016-11-30
23:30:54.903
15:16:17
2019-04-10T23:30:54.903
2016-11-30T15:16:17

代码示例2:时间计算

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.temporal.TemporalAdjusters;

public class Calculate {

	public static void main(String[] args) throws Exception {
		// 获取当前日期和时间:
		LocalDateTime ldt = LocalDateTime.now();
		System.out.println(ldt);
		// + 5 days:
		LocalDateTime ldt2 = ldt.plusDays(5);
		System.out.println(ldt2);
		// - 2 hours:
		LocalDateTime ldt3 = ldt2.minusHours(2);
		System.out.println(ldt3);
		// 获得当月第一天:
		LocalDate firstDay = LocalDate.now().withDayOfMonth(1);
		LocalDate firstDay2 = LocalDate.now().with(TemporalAdjusters.firstDayOfMonth());
		System.out.println(firstDay.equals(firstDay2));
		System.out.println(firstDay);
		// 获得当月最后一天:
		LocalDate lastDay = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth());
		System.out.println(lastDay);
		// 获得当月第一个星期日:
		LocalDate firstSunday = LocalDate.now().with(TemporalAdjusters.firstInMonth(DayOfWeek.SUNDAY));
		System.out.println(firstSunday);
		// 判断两个日期哪个在前:
		System.out.println(firstSunday.isBefore(LocalDate.now()));
		// 两个日期相差?年?月?天:
		Period p = LocalDate.now().until(LocalDate.of(2050, 1, 1));
		System.out.println(p);
		// 两个日期一共相差多少天:
		System.out.println(LocalDate.of(2050, 1, 1).toEpochDay() - LocalDate.now().toEpochDay());
	}

}

输出

2019-04-10T23:32:35.942
2019-04-15T23:32:35.942
2019-04-15T21:32:35.942
true
2019-04-01
2019-04-30
2019-04-07
true
P30Y8M22D
11224

示例代码3:时间格式化

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class Format {

	public static void main(String[] args) {
		// 格式化:
		DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
		System.out.println(dtf.format(LocalDateTime.now()));
		// 按ISO格式解析:
		LocalDateTime dt1 = LocalDateTime.parse("2016-11-30T15:16:17");
		System.out.println(dt1);
		// 按指定格式解析:
		LocalDateTime dt2 = LocalDateTime.parse("2016-11-30 15:16:17", dtf);
		System.out.println(dt2);
	}

}

输出

2019-04-10 23:33:55
2016-11-30T15:16:17
2016-11-30T15:16:17

小结

  • 不变类(类似String)
  • 默认按ISO8601标准格式化和解析
  • 使用 DateTimeFormatter自定义格式化和解析
  • 使用 plusDays()/minusHours()等方法对日期和时间进行加减,返回新对象
  • 使用 withDayOfMonthi0)/with()等方法调整日期和时间,返回新对象
  • 使用 isBefore()/ isAfter()/ equals(判断日期和时间的先后

ZonedDateTime

ZonedDateTime = LocalDateTime + ZoneId ZonedDateTime可以做时区转换:withZoneSameInstant ZoneId:新的时区对象 Instant:时刻,可以转换为long(注意单位是秒) ZonedDateTime、Instant和long可以互相转换 Period和Duration表示一段时间,可用于日期和时间的加减 其它底层API:

  • ZoneOffset 时区偏移量
  • OffsetDateTime 类似ZonedDateTime
  • OffsetTime 带offset的LocalTime
  • ChronoLocalDate / ChronoLocalDateTime用于实现其他历法

时间对象转换示意图:

继承关系:
TemporalAmout接口表示一段时间:1天/2小时/30分钟...
可以加减一个TemporalAmout对象

实例代码1

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class Main {

	public static void main(String[] args) throws Exception {
		// 获取当前默认时区的日期和时间:
		ZonedDateTime now = ZonedDateTime.now();
		System.out.println(now);
		// 打印时区:
		System.out.println(now.getZone());
		// 获取Instant:
		Instant ins = now.toInstant();
		System.out.println(ins.getEpochSecond());
		// 按指定时区获取当前日期和时间:
		ZonedDateTime london = ZonedDateTime.now(ZoneId.of("Europe/London")); // 伦敦时间
		System.out.println(london);
		// 把伦敦时间转换到纽约时间:
		ZonedDateTime newyork = london.withZoneSameInstant(ZoneId.of("America/New_York")); // 纽约时间
		System.out.println(newyork);
	}

}

输出

2019-04-11T15:02:51.919+08:00[Asia/Shanghai]
Asia/Shanghai
1554966171
2019-04-11T08:02:51.923+01:00[Europe/London]
2019-04-11T03:02:51.923-04:00[America/New_York]

代码示例2:LocalDateTime的转换

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class Local2Zoned {

	public static void main(String[] args) throws Exception {
		// 把LocalDateTime转换为ZonedDateTime:
		LocalDateTime ldt = LocalDateTime.of(2016, 11, 30, 8, 15, 59);
		// 关联到当前默认时区:
		ZonedDateTime bj = ldt.atZone(ZoneId.systemDefault());
		System.out.println(bj);
		// 关联到纽约时区:
		ZonedDateTime ny = ldt.atZone(ZoneId.of("America/New_York"));
		System.out.println(ny);
	}

}

输出

2016-11-30T08:15:59+08:00[Asia/Shanghai]
2016-11-30T08:15:59-05:00[America/New_York]

代码示例3:

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class ChangeZone {

	public static void main(String[] args) {
		// 把LocalDateTime转换为ZonedDateTime:
		LocalDateTime ldt = LocalDateTime.of(2016, 11, 30, 8, 15, 59);
		// 关联到当前默认时区:
		ZonedDateTime bj = ldt.atZone(ZoneId.systemDefault());
		System.out.println(bj);
		// 转换到纽约时区:
		ZonedDateTime ny = bj.withZoneSameInstant(ZoneId.of("America/New_York"));
		System.out.println(ny);
	}

}

输出

2016-11-30T08:15:59+08:00[Asia/Shanghai]
2016-11-29T19:15:59-05:00[America/New_York]

总结:

  • ZonedDateTime就是LocalDateTime关联Zoned
  • ZonedDateTime可以做时区转换
  • Zoned表示时区
  • Instant表示时刻(内部用long表示eqoch second)
  • ZonedDateTime、Instant和Long可以互相转换

最佳实践

关系型数据库datatime字段与javaAPI的映射关系

新旧API的相互转换

总结

尽量使用的新的api来处理时间日期java.time
储存到数据库的api接口与数据库字段映射:

  • 日期:LocalDate->DATE
  • 时间:LocalTime->TIME
  • 日期+时间:LocalDateTime->DATETIME
  • 时刻:long->BIGINT

如果要根据用户所在的时区不同来展示数据,数据表中最好保存long类型的时间戳,然后通过自定义的方法根据时区输出格式化后的日期时间,让JDK处理时区,不要手动调节时差。

连接数据库指定时区

如果你设置serverTimezone=UTC连接数据库正常,但是java代码将时间插入到数据库时间的时候会出现以下问题:
比如在java代码里面插入的时间为2019-01-20 10:14:01
但是在数据库里面显示的时间却为2019-01-20 02:14:01
有了8个小时的时差
UTC代表的是全球标准时间 ,中国使用的时间是北京时区也就是东八区,领先UTC八个小时。

正确的方式
  • mysql安装目录下my.ini配置文件中添加
#设置默认时区
default-time-zone='+08:00'
  • spring boot 配置文件application.properties中设置spring默认时区
spring.jackson.time-zone=GMT+8
  • spring boot 配置文件application.properties中设置jpa与数据库链接增加配置项默认时区
url: jdbc:mysql://127.0.0.1:3306/sys?serverTimezone=CTT&characterEncoding=utf8

或者

//北京时间东八区
serverTimezone=GMT%2B8 
//上海时间
serverTimezone=Asia/Shanghai

持续更新......