Java笔记09 - 日期和时间

基本概念

  • 日期: 某一天, 不连续变化
  • 时间: 带日期的时间, 和不带日期的时间
  • 不带日期的时间无法确定一个唯一时刻的

本地时间

  • 因时区问题, 全球时间并不是一致的

时区

  • GMT/UTC加时区偏移表示
  • GMT+08:00/UTC+08:00
  • 全球时刻相同

夏令时

  • 夏天开始的时候, 推后一个小时. 结束的时候, 再往前拨一个小时
  • 夏令时使用标准库提供的相关类, 并不需要自己计算

本地化

  • Locale语言_国家的字母缩写构成
  • zh_CN表示中文+中国
  • en_US表示英文+美国
  • 语言小写, 国家大写
  • 不同Local, 时间表示不同
    • zh-CN: 2016-11-30
    • en_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)));
  }
posted @ 2020-04-24 14:09  张润昊  阅读(270)  评论(0编辑  收藏  举报