基本概念
- 日期: 某一天, 不连续变化
- 时间: 带日期的时间, 和不带日期的时间
- 不带日期的时间无法确定一个唯一时刻的
本地时间
- 因时区问题, 全球时间并不是一致的
时区
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里面DateCalendarTimeZone
- 一套定义在
java.time: Java 8引入LocalDateTimeZonedDateTimeZoneId
- 遗留代码仍然使用旧的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)));
}