Java笔记十三——日期和时间

基本概念

  • 日期:2019-11-20
  • 时间:12:30:59
  • 本地时间:不同时区同一时刻,本地时间不同
  • 夏令时
  • 时区:GMT/UTC+08:00表示东八区
  • 本地化:Locale由语言_国家的字母缩写构成。zh_CN表示中文+中国;en_US表示英文+美国

通常使用Locale表示一个国家或地区的日期、时间、数字、货币等格式。

Date和Calendar

时间戳Epoch Time

计算从1970年1月1日零点(格林威治时区/GMT+00:00)到现在所经历的秒数。例如1574208900表示从从1970年1月1日零点GMT时区到该时刻一共经历了1574208900秒,换算成伦敦、北京和纽约时间分别是:
1574208900 = 北京时间2019-11-20 8:15:00
= 伦敦时间2019-11-20 0:15:00
= 纽约时间2019-11-19 19:15:00
时间戳在Java中是long表示的毫秒数
获取当前时间戳:System.currentTimeMillis()

标准库API

处理时间和日期的API:

  • 定义在java.util包里,主要包括Date、Calendar和TimeZone这几个类
  • Java8引入的,定义在java.time包里,包括LocalDateTime、ZoneDateTime、ZoneId等。

Date

import java.util.*;

public class Main {
    public static void main(String[] args) {
    	//获取当前时间
		Date date = new Date();
		System.out.println(date.getYear()+1900);//必须+1900
		System.out.println(date.getMonth()+1);//月份0~11,必须+1
        System.out.println(date.getDate()); // 1~31,不能加1
        // 转换为String:
        System.out.println(date.toString());
        // 转换为GMT时区:
        System.out.println(date.toGMTString());
        // 转换为本地时区:
        System.out.println(date.toLocaleString());		
    }
}

使用SimpleDateFormat对一个Date进行转换,控制日期和时间的格式:

Date date = new Date();
var sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//年-月-日 时:分:秒
System.out.println(sdf.format(date));

Date不能转换时区,除了toGMTString()可以按照GMT+0:00输出。Date总是按照当前计算机系统默认时区为基础进行输出。

Calendar

Calendar获取年月日这些信息变成了get(int field),返回的年份不必转换,返回的月份仍然要加1,返回的星期要特别注意,1~7分别表示周日,周一,……,周六。
Calendar只有一种方式获取,即Calendar.getInstance(),而且一获取到就是当前时间。如果我们想给它设置成特定的一个日期和时间,就必须先清除所有字段(clear方法)。利用Calendar.getTime()可以将一个Calendar对象转换成Date对象,然后就可以用SimpleDateFormat进行格式化了。

TimeZone

Calendar和Date相比,它提供了时区转换的功能。时区用TimeZone对象表示。

  • 获取当前时区:TimeZone tzDefault = TimeZone.getDefault();
  • 获取时区标识(以字符串标识的ID):tzDefault.getID(); //Asia/Shanghai

要列出系统支持的所有ID,请使用TimeZone.getAvailableIDs()。
有了时区,我们就可以对指定时间进行转换。例如,下面的例子演示了如何将北京时间2019-11-20 8:15:00转换为纽约时间:

import java.text.*;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        // 当前时间:
        Calendar c = Calendar.getInstance();
        // 清除所有:
        c.clear();
        // 设置为北京时区:
        c.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
        // 设置年月日时分秒:
        c.set(2019, 10 /* 11月 */, 20, 8, 15, 0);
        // 显示时间:
        var sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        sdf.setTimeZone(TimeZone.getTimeZone("America/New_York"));
        System.out.println(sdf.format(c.getTime()));
        // 2019-11-19 19:15:00
    }
}

利用Calendar进行时区转换的步骤是:
1、清除所有字段;
2、设定指定时区;
3、设定日期和时间;
4、创建SimpleDateFormat并设定目标时区;
5、格式化获取的Date对象(注意Date对象无时区信息,时区信息存储在SimpleDateFormat中)。
因此,本质上时区转换只能通过SimpleDateFormat在显示的时候完成。
Calendar也可以对日期和时间进行简单的加减:add()方法。

LocalDateTime

从Java 8开始,java.time包提供了新的日期和时间API,主要涉及的类型有:

  • 本地日期和时间:LocalDateTime,LocalDate,LocalTime;
  • 带时区的日期和时间:ZonedDateTime;
  • 时刻:Instant;
  • 时区:ZoneId,ZoneOffset;
  • 时间间隔:Duration。

以及一套新的用于取代SimpleDateFormat的格式化类型DateTimeFormatter

LocalDateTime dt = LocalDateTime.now(); // 当前日期和时间
LocalDate d = dt.toLocalDate(); // 转换到当前日期
LocalTime t = dt.toLocalTime(); // 转换到当前时间

通过now()方法获取到的本地日期和时间总是以当前默认时区返回的。
通过指定的日期和时间创建LocalDateTime可以通过of()方法。
通过parse()方法传入标准格式LocalDateTime dt = LocalDateTime.parse("2019-11-19T15:16:17");
日期和时间的分割符是T。

DateTimeFormatter

自定义输出格式:
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");//自定义格式化 System.out.println(dtf.format(LocalDateTime.now()));//用自定义格式解析

LocalDateTime提供了对日期和时间进行加减的非常简单的链式调用:plusDays() minusHours() minusMonths()方法等。
对日期和时间调整使用with()Xxx()方法。如WithHour(15)——将时设置成15

要判断两个LocalDateTime的先后,可以使用isBefore()、isAfter()方法,对于LocalDate和LocalTime类似

LocalDateTime无法与时间戳进行转换,因为LocalDateTime没有时区,无法确定某一时刻。
ZonedDateTime相当于LocalDateTime加时区的组合,它具有时区,可以与long表示的时间戳进行转换。

Duration和Period

Duration表示两个时刻之间的时间间隔。Period表示两个日期之间的天数。
Duration d = Duration.between(start, end);//start和end都是LocalDateTime类型
Period p = LocalDate.of(2019, 11, 19).until(LocalDate.of(2020, 1, 9));

ZonedDateTime

ZonedDateTime可以表示带时区的日期和时间,相当于LocalDateTime+ZoneId。

//通过now()方法返回当前时间
ZonedDateTime zbj = ZonedDateTime.now();//默认时区
ZonedDateTime zny = ZonedDateTime.now(ZoneId.of("America/New_York"));//用指定时区获取当前时间

通过给一个LocalDateTime附加一个ZoneId,就可以变成ZonedDateTime:它的日期和时间与LocalDateTime相同,但附加的时区不同,因此是两个不同的时刻.

LocalDateTime ldt = LocalDateTime.of(2019,9,15,15,16,17);
ZonedDateTime zbj = ldt.atZone(ZoneId.SystemDefault());

时区转换

ZonedDateTime对象的withZoneSameInstant()方法将关联时区转换到另一个时区,转换后日期和时间都会相应调整。

//以中国时区获取当前时间
ZonedDateTime zbj = ZoneDateTime.now(ZoneId.of("Asia/Shanghai"));
//转换为纽约时间
ZonedDateTime zny = zbj.withZoneSameInstant(ZoneId.of("America/New_York"));

有了ZonedDateTime,将其转换为本地时间就非常简单:

ZonedDateTime zdt = …
LocalDateTime ldt = zdt.toLocalDateTime();
ZonedDateTime仍然提供了plusDays()等加减操作。

DateTimeFormatter

DateTimeFormatter是不变对象,线程安全的,用于格式化显示。

import java.time.*;
import java.time.format.*;
import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        ZonedDateTime zdt = ZonedDateTime.now();
        var formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm ZZZZ");
        System.out.println(formatter.format(zdt));

        var zhFormatter = DateTimeFormatter.ofPattern("yyyy MMM dd EE HH:mm", Locale.CHINA);
        System.out.println(zhFormatter.format(zdt));

        var usFormatter = DateTimeFormatter.ofPattern("E, MMMM/dd/yyyy HH:mm", Locale.US);
        System.out.println(usFormatter.format(zdt));
    }

Instant

当前时间戳在java.time中以Instant类型表示,用Instant.now()获取当前时间戳。

Instance now = Instance.now();
now.getEpochSecond();//秒
now.toEpochMilli();//毫秒
//给某一个时间戳关联上ZoneId,就得到了ZonedDateTime,继而可以获取对应时区的LocalDateTime
// 以指定时间戳创建Instant:
Instant ins = Instant.ofEpochSecond(1568568760);
ZonedDateTime zdt = ins.atZone(ZoneId.systemDefault());
System.out.println(zdt); // 2019-09-16T01:32:40+08:00[Asia/Shanghai]

最佳实践

旧API转新API

旧式的Date或Calendar转换为新API对象,通过toInstant()方法转换为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

ZonedDateTime转换为旧的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);

在数据库中存储时间和日期

在数据库中,我们需要存储的最常用的是时刻(Instant),因为有了时刻信息,就可以根据用户自己选择的时区,显示出正确的本地时间。所以,最好的方法是直接用长整数long表示,在数据库中存储为BIGINT类型。

通过存储一个long型时间戳,我们可以编写一个timestampToString()的方法,非常简单地为不同用户以不同的偏好来显示不同的本地时间:

import java.time.*;
import java.time.format.*;
import java.util.Locale;

public class Main {
    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)));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值