概念
日期: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 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
类型的变量来接收运算后的时间对象

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用于实现其他历法
时间对象转换示意图:



实例代码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
持续更新......