基本概念
- 日期: 某一天, 不连续变化
- 时间: 带日期的时间, 和不带日期的时间
- 不带日期的时间无法确定一个唯一时刻的
本地时间
- 因时区问题, 全球时间并不是一致的
时区
GMT
/UTC
加时区偏移表示GMT+08:00
/UTC+08:00
- 全球时刻相同
夏令时
- 夏天开始的时候, 推后一个小时. 结束的时候, 再往前拨一个小时
- 夏令时使用标准库提供的相关类, 并不需要自己计算
本地化
Locale
由语言_国家
的字母缩写构成zh_CN
表示中文+中国en_US
表示英文+美国- 语言小写, 国家大写
- 不同
Local
, 时间表示不同zh-CN
: 2016-11-30en_US
: 11/30/2016
- 根据
Local
针对当地用户习惯格式化日期, 时间, 数字, 货币等
Date和Calendar
数据和存储和展示
- 定义一个整型变量并赋值
- 编译器会把程序源码作为字符串, 编译成字节码
- 变量
n
指向内存实际上一个指定大小的字节区域 - 计算机的内存中除了二进制的
0/1
没有其他格式 - 我们可以用十六进制打印这个整数
- 也可以用特定的价格格式表示
System.out.println(n);
System.out.println(Integer.toHexString(n));
System.out.println(NumberFormat.getCurrencyInstance(Locale.CHINA).format(n));
- 整数是存储格式, 展示格式可以有各种形式
- 同一时刻在计算机中存储的是同一个整数, 成为
Epoch Time
, 纪元时间, 时间戳 - 表示从固定时间到现在经历的一共经历的秒数
- 时间戳, 通常使用
long
System.currentTimeMillis()
Java获取时间戳的方式
标准库API
- 一套定义在
java.util
里面Date
Calendar
TimeZone
- 一套定义在
java.time
: Java 8引入LocalDateTime
ZonedDateTime
ZoneId
- 遗留代码仍然使用旧的API, 需要对旧API进行了解
Date
- 用于表示一个日期和时间的对象
SimpleDateFormat
: 对Date
进行转换- yyyy: 年
- MM: 月
- dd: 日
- HH: 小时
- mm: 分钟
- ss: 秒
- 不能转换时区
- 很难对日期和时间进行加减
Calendar
- 获取并设置年, 月, 日, 时, 分, 秒
- 可以做简单的日期和时间运算
- 一获取就是当前日期, 想要设置的话, 需要先清除
Calendar.getTime()
可以将一个Calendar
对象抓换成Date
对象
TimeZone
- 时区功能
- 可以获取时区ID, 然后对指定时区进行转换
- 清除所有字段
- 设定指定时区
- 设定日期和时间
- 创建
SimpleDateFormat
并设定目标时区 - 格式化获取的
Date
对象
- 时区只能在显示的时候转换
add()
对日期进行加减
LocalDateTime
java.time
包提供了新的日期和时间API- 本地日期和时间:
LocalDateTime
,LocalDate
,LocalTime
- 带有时区的日期和时间:
ZonedDateTime
- 时刻:
Instant
- 时区:
ZoneId
,ZoneOffset
- 时间间隔:
Duration
- 还有一套取代
SimpleDateFormat
的格式化类型DateTimeFormat
- 本地日期和时间:
- 严格区分时刻, 本地日期, 本地时间, 和带时区的日期时间
- 对日期和时间进行原酸更改方便
- 修正:
- Month: 1-12: 1月-12月
- Week: 1-7: 周一到周日
- 几乎都是不变类型
再次LocalDateTime
- 表示本地时间和日期
- 通过
of()
创建指定日期和时间的LocalDateTime
- 字符串转换为
LocalDateTime
就可以传入标准格式 - 时间和日期的分隔符是
T
DateTimeFormatter
- 自定义格式输出
- 自定义格式解析
// 自定义格式化
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
System.out.println(dtf.format(LocalDateTime.now()));
// 用自定义格式解析
LocalDateTime dt2 = LocalDateTime.parse("2019/11/30 15:11:11", dtf);
System.out.println(dt2);
-
提供了对时间和时间非常简单的链式调用
-
月份加减会自动调整日期
-
对日期和时间进行调整可以使用
withXXX()
-
奇淫技巧
// 本月第一天
LocalDateTime firstDay = LocalDate.now().withDayOfMonth(1).atStartOfDay();
System.out.println(firstDay);
// 本月最后一天
LocalDate lastDay = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth();
System.out.println(lastDay);
// 下个月第一天
LocalDate nextMonthFirstDay = LocalDate.now().with(TemporalAdjusters.firstDayOfNextMonth());
System.out.println(nextMonthFirstDay);
// 本月第一个周一
LocalDate firstWeekday = LocalDate.now().with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));
System.out.println(firstWeekday);
isBefore()
/isAfter()
: 判断两个LocalDateTime()
先后.- 因为
LocalDateTime
没有时区, 无法确定某一时刻, 所以无法与时间戳进行互换 ZonedDateTime
可以与时间戳互换
Duration和Period
- Duration: 表示两个时刻之间的时间间隔
- Period: 表示两个日期之间的天数
- 使用
P...T...
进行表示 - 使用
of()
或者parse()
可以直接创建Duration
Duration d1 = Duration.ofHours(10);
Duration d2 = Duration.parse("P1DT2H3M");
ZonedDateTime
- 表示带有时区的本地日期和时间
- 创建方式:
- 通过
now()
创建 - 通过给一个
LocalDateTime()
附加一个ZonedId
- 通过
时区转换
withZoneSameInstant()
进行时区转换toLocalDateTime()
转换成本地时间
DateTimerFormatter
- 使用
LocalDateTime
或者ZonedLocalDateTime
, 使用DateTimerFormatter
进行格式化 SimpleDateFormat
不是线程安全的, 只能在方法内部创建新的局部变量DateTimerFormatter
是线程安全的- 通过传入格式化字符串实现, 或者可以同时指定
Local
- 固定字符
'xxx'
表示 toString()
显示字符串默认按照ISO 8601
格式- 可以通过
DateTimeFormatter
预定义的几个静态变量引用
Instant
- 时间戳
- 本质上只是一个不断递增的整数
System.currentTimeMillis()
获取Instant.now()
获取当前时间戳
Instant now = Instant.now();
System.out.println(now.getEpochSecond()); // second
System.out.println(now.toEpochMilli()); // millisecond
- 通过
atZoneId
, 得到ZonedDateTime
LocalDateTime
,ZoneId
,ZonedDateTime
, 和long
都可以互相转换- 留意
long
是毫秒, 还是秒.
最佳实践
- 除非遇到遗留代码, 否则应该坚持使用新API
旧API转新API
Date
/Calendar
通过toInstant()
, 转化为Instant
对象Instant
再转换为ZonedDateTime
// Date -> Instant
Instant ins1 = new Date().toInstant();
// Calendar -> Instant -> ZonedDateTime
Calendar calendar = Calendar.getInstance();
Instant ins2 = Calendar.getInstance().toInstant();
ZonedDateTime zdt = ins2.atZone(calendar.getTimeZone().toZoneId());
新API转旧API
- 借助
long
型时间戳作为中转
// ZonedDateTime -> long
ZonedDateTime zdt = ZonedDateTime.now();
long ts = zdt.toEpochSecond() * 1000;
// long -> Date
Date date = new Date(ts);
// long -> Calendar
Calendar calendar = Calendar.getInstance();
calendar.clear();
calendar.setTimeZone(TimeZone.getTimeZone(zdt.getZone().getId()));
calendar.setTimeInMillis(zdt.toEpochSecond() * 1000);
在数据库中存储日期和时间
-
数据库类时间类型
- DATETIME: 旧: java.util.Date; 新: LocalDateTime
- DATE: 旧: java.sql.Date 新: LocalDate
- TIME: 旧: java.sql.Time 新: LocalTime
- TIMESTAMP: 旧: java.sql.Timestamp 新: LocalDateTime
-
数据库中最好是用时刻
Instant
,long
型表示, 数据库中存储为BIGINT
型 -
然后为不同用户, 以不同的偏好进行显示
public static void main(String[] args) {
long ts = 1574208900000L;
System.out.println(timestampToString(ts, Locale.CHINA, "Asia/Shanghai"));
System.out.println(timestampToString(ts, Locale.US, "America/New_York"));
}
static String timestampToString(long epochMilli, Locale lo, String zoneId) {
Instant ins = Instant.ofEpochMilli(epochMilli);
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT);
return f.withLocale(lo).format(ZonedDateTime.ofInstant(ins, ZoneId.of(zoneId)));
}