已复制
全屏展示
复制代码

Java 时间处理函数总结


· 10 min read

一. Epoch Time

  • 时间的格式也分为存储格式和展示格式。根据不同的时区,全世界同一时刻的展示格式不同。
  • 时刻本质上是一个整数,我们称它为Epoch Time。Epoch Time是计算从1970年1月1日零点(格林威治时区/GMT+00:00)到现在所经历的秒数。
  • 因此只需要存储一个整数比如 1574208900 则可表示某一时刻。当需要显示为某一地区的当地时间时,我们就把它格式化为一个字符串展示。

Java有两套日期和时间的API:

  • 新版的 LocalDateTime、ZonedDateTime、DateTimeFormatter 等:位于 java.time 包
  • 旧版的 Date、Calendar 和 TimeZone:位于 java.util 包

二. 新版时间API

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

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

2.1 获取本地时间

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

public class Main {
    public static void main(String[] args) {
        LocalDate d = LocalDate.now();          // 当前日期
        LocalTime t = LocalTime.now();          // 当前时间
        LocalDateTime dt = LocalDateTime.now(); // 当前日期和时间
        System.out.println(d);                  // 严格按照ISO 8601格式打印
        System.out.println(t);                  // 严格按照ISO 8601格式打印
        System.out.println(dt);                 // 严格按照ISO 8601格式打印
    }
}

// 输出结果
2020-12-16
19:08:03.075111
2020-12-16T19:08:03.075138

2.2 截取日期和时间

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

// 输出结果
2020-12-16T19:14:05.476327
2020-12-16
19:14:05.476327

2.3 数字转换为时间

LocalDate d = LocalDate.of(2019, 11, 30);                       // 2019-11-30
LocalTime t = LocalTime.of(15, 16, 17);                         // 15:16:17
LocalDateTime dt1 = LocalDateTime.of(2019, 11, 30, 15, 16, 17);
LocalDateTime dt2 = LocalDateTime.of(d, t);

2.4 字符串转换为时间

LocalDate d = LocalDate.parse("2019-11-19");
LocalTime t = LocalTime.parse("15:16:17");
LocalDateTime dt = LocalDateTime.parse("2019-11-19T15:16:17");

2.5 自定义格式

ISO 8601标准格式

  • 日期:yyyy-MM-dd
  • 时间:HH:mm:ss
  • 带毫秒的时间:HH:mm:ss.SSS
  • 日期和时间:yyyy-MM-dd'T'HH:mm:ss
  • 带毫秒的日期和时间:yyyy-MM-dd'T'HH:mm:ss.SSS
// 自定义格式
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

String dt1 = dtf.format(LocalDateTime.now());
LocalDateTime dt2 = LocalDateTime.parse("2019-11-30 15:16:17", dtf);

System.out.println(dt1);
System.out.println(dt2);

// 输出结果
2021-05-30 11:29:49
2019-11-30T15:16:17

2.6 时刻增加减少

  • 在原来的基础上操作
LocalDateTime dtNow = LocalDateTime.now();
LocalDateTime dt2 = dtNow.plusDays(1);
LocalDateTime dt3 = dtNow.minusHours(1);
LocalDateTime dt4 = dtNow.plusDays(1).minusHours(1);

System.out.println("dtNow = " + dtNow);
System.out.println("dt2 = " + dt2);
System.out.println("dt3 = " + dt3);
System.out.println("dt4 = " + dt4);

// 输出结果
dtNow = 2021-05-30T11:34:31.236
dt2 = 2021-05-31T11:34:31.236
dt3 = 2021-05-30T10:34:31.236
dt4 = 2021-05-31T10:34:31.236
  • 直接指定具体的数字
LocalDateTime dt = LocalDateTime.now();
dt = dt.withYear(2030);
dt = dt.withHour(1).withMinute(0);
System.out.println("dt = " + dt);

// 输出结果
dt = 2030-05-30T01:00:26.089
  • 常见时间节点
LocalDateTime dt = LocalDate.now().withDayOfMonth(1).atStartOfDay();                    // 本月第一天0:00时刻
LocalDate dt1 = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth());               // 本月最后1天
LocalDate dt2 = LocalDate.now().with(TemporalAdjusters.firstDayOfNextMonth());          // 下月第1天
LocalDate dt3 = LocalDate.now().with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)); // 本月第1个周一

System.out.println("dt = " + dt);
System.out.println("dt1 = " + dt1);
System.out.println("dt2 = " + dt2);
System.out.println("dt3 = " + dt3);

// 输出结果
dt = 2021-05-01T00:00
dt1 = 2021-05-31
dt2 = 2021-06-01
dt3 = 2021-05-03

2.7 时刻的比较

LocalDateTime dt1 = LocalDateTime.of(2019, 11, 19, 8, 15, 0);
LocalDateTime dt2 = LocalDateTime.of(2019, 11, 19, 8, 16, 0);
System.out.println(dt1.isAfter(dt2));   // 大于
System.out.println(dt1.isBefore(dt2));  // 小于
System.out.println(dt1.equals(dt2));    // 等于

2.8 时刻的间隔

  • Duration时刻间隔:Duration表示两个时刻之间的时间间隔
  • Period相隔天数:Period表示两个日期之间的天数,只能精确到天
LocalDateTime start = LocalDateTime.of(2019, 11, 19, 8, 15, 0);
LocalDateTime end = LocalDateTime.of(2020, 1, 9, 19, 25, 30);
Duration d = Duration.between(start, end);
System.out.println(d);                                    // PT1235H10M30S

Period p = start.toLocalDate().until(end.toLocalDate());
System.out.println(p);                                    // P1M21D

Duration d1 = Duration.ofHours(10);                       // 创建10小时的Duration
Duration d2 = Duration.parse("P1DT2H3M");                 // 使用parse创建Duration
System.out.println(d1);
System.out.println(d2);

// 输出结果
PT1235H10M30S
P1M21D
PT10H
PT26H3M

2.9 带时区的时间

  • 可以简单地把 ZonedDateTime 理解成 LocalDateTime 加 ZoneId。ZoneId 是java.time 引入的新的时区类。
// 使用 ZoneDateTime 创建带时区的时间
ZonedDateTime dt = ZonedDateTime.now();                               // 默认时区
ZonedDateTime dt1 = ZonedDateTime.now(ZoneId.systemDefault());        // 默认时区
ZonedDateTime dt2 = ZonedDateTime.now(ZoneId.of("America/New_York")); // 用指定时区获取当前时间
System.out.println(dt);
System.out.println(dt1);
System.out.println(dt2);
// 输出结果
2021-05-30T11:44:10.491+08:00[Asia/Shanghai]
2021-05-30T11:44:10.491+08:00[Asia/Shanghai]
2021-05-29T23:44:10.493-04:00[America/New_York]


// 使用 LocalDateTime ZoneId 创建带时区的时间
LocalDateTime dt3 = LocalDateTime.of(2019, 9, 15, 15, 16, 17);
ZonedDateTime dt4 = dt3.atZone(ZoneId.systemDefault());
ZonedDateTime dt5 = dt3.atZone(ZoneId.of("America/New_York"));
System.out.println(dt3);
System.out.println(dt4);
System.out.println(dt5);
// 输出结果
2019-09-15T15:16:17
2019-09-15T15:16:17+08:00[Asia/Shanghai]
2019-09-15T15:16:17-04:00[America/New_York]


// 查看所有的 ZoneId
Set<String> zones = ZoneId.getAvailableZoneIds();
System.out.println(zones);
// 输出结果
[Asia/Aden, America/Cuiaba, Etc/GMT+9, Etc/GMT+8, Africa/Nairobi, America/Marigot,......]


// 将北京时间转换为纽约时间(带时区转换)
ZonedDateTime dt6 = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
ZonedDateTime dt7 = dt6.withZoneSameInstant(ZoneId.of("America/New_York"));
System.out.println(dt6);
System.out.println(dt7);
// 输出结果
2021-05-30T11:47:32.667+08:00[Asia/Shanghai]
2021-05-29T23:47:32.667-04:00[America/New_York]


// 将纽约时间转换为纽约本地时间(丢掉时区)
ZonedDateTime dt8 = ZonedDateTime.now(ZoneId.of("America/New_York"));
LocalDateTime dt9 = dt8.toLocalDateTime();
System.out.println(dt8);
System.out.println(dt9);
// 输出结果
2021-05-29T23:48:18.909-04:00[America/New_York]
2021-05-29T23:48:18.909

2.10 获取当前时间戳

// Instant 获取当前时间戳
Instant now = Instant.now();
System.out.println(now.getEpochSecond());             // 秒
System.out.println(now.toEpochMilli());               // 毫秒

// System 获取当前时间戳
System.out.println(System.currentTimeMillis()/1000);  // 秒
System.out.println(System.currentTimeMillis());       // 毫秒

2.11 时间对象转换时间戳

  • 注意:转换为时间戳时,必须要转换成 ZoneDateTime 以后再转换
// 方法1:直接获取 ZoneDateTIme
ZonedDateTime zdt = ZonedDateTime.now();
Instant instant = zdt.toInstant();
System.out.println(instant.getEpochSecond());

// 方法2:需要转换成 ZoneDateTIme
ZonedDateTime zdt = LocalDateTime.now().atZone(ZoneId.systemDefault());
Instant instant = zdt.toInstant();
System.out.println(instant.getEpochSecond());

2.12 时间戳转换时间对象

Instant instant = Instant.ofEpochSecond(1608133910);
ZonedDateTime zdt = instant.atZone(ZoneId.systemDefault());
System.out.println(zdt);

// 输出结果
2020-12-16T23:51:50+08:00[Asia/Shanghai]

三. 旧版时间API

  • 旧版的 Date、Calendar 和 TimeZone:位于 java.util 包。

3.1 java.util.Date

Date now = new Date();           // 获取当前时间
int year = now.getYear() + 1900; // 必须加上 1900
int month = now.getMonth() + 1;  // 0~11,必须加上1
int day = now.getDate();
System.out.println("year = " + year);
System.out.println("month = " + month);
System.out.println("day = " + day);

SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String nowString = df.format(now);
System.out.println("formatted = " + nowString);

// 输出结果
year = 2021
month = 5
day = 30
formatted = 2021-05-30 12:23:22


SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date dateObject= new Date(df.parse("2019-11-29 00:29:00").getTime());
System.out.println(df.format(dateObject));
// 时间字符串格式化
2019-11-29 00:29:00

3.2 java.util.Calendar

  • 获取时间的年月日时分秒

Calendar calendar = Calendar.getInstance();      // 获取当前时间
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH) + 1;    // 月份需要加 1
int day = calendar.get(Calendar.DAY_OF_MONTH);
int hour = calendar.get(Calendar.HOUR_OF_DAY);
int minute = calendar.get(Calendar.MINUTE);
int second = calendar.get(Calendar.SECOND);
int millisecond = calendar.get(Calendar.MILLISECOND);

SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SS");
List<Integer> nowList = Arrays.asList(year, month, day, hour, minute, second, millisecond);
String datetimeStr = nowList.stream().map(String::valueOf).collect(Collectors.joining("-"));
System.out.println("one = " + df.format(calendar.getTime()));
System.out.println("two = " + datetimeStr);

// 输出结果
one = 2021-05-30 14:07:09.938
two = 2021-5-30-14-7-9-938
  • 设置时间的年月日时分秒
Calendar calendar = Calendar.getInstance();  // 当前时间
calendar.clear();                            // 清除所有
calendar.set(Calendar.YEAR, 2019);           // 设置2019年
calendar.set(Calendar.MONTH, 8);             // 设置9月:注意8表示9月
calendar.set(Calendar.DATE, 2);              // 设置2日
calendar.set(Calendar.HOUR_OF_DAY, 21);      // 设置小时
calendar.set(Calendar.MINUTE, 22);           // 设置分钟
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(calendar.getTime()));
  • 时间的加减
Calendar calendar = Calendar.getInstance();           // 当前时间:
calendar.clear();                                     // 清除所有:
calendar.set(2019, Calendar.NOVEMBER, 20, 8, 15, 0);  // 设置: 2019年11月20号8时15分0秒
calendar.add(Calendar.DAY_OF_MONTH, 5);               // 加上5天
calendar.add(Calendar.HOUR_OF_DAY, -2);               // 减2小时
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(calendar.getTime()));

3.3 java.util.TimeZone


// 查看所有的时区id
// System.out.println(Arrays.toString(TimeZone.getAvailableIDs()));

TimeZone tzDefault = TimeZone.getDefault();                   // 当前时区
TimeZone tzGMT9 = TimeZone.getTimeZone("GMT+09:00");          // GMT+9:00时区
TimeZone tzNY = TimeZone.getTimeZone("America/New_York");     // 纽约时区

System.out.println(tzDefault.getID());                        // Asia/Shanghai
System.out.println(tzGMT9.getID());                           // GMT+09:00
System.out.println(tzNY.getID());                             // America/New_York

Calendar calendar = Calendar.getInstance();                   // 当前时间:
calendar.clear();                                             // 清除所有:
calendar.set(2019, Calendar.NOVEMBER, 20, 9, 0, 0);           // 设置: 2019年11月20号9时0分0秒
calendar.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));  // 设置时区,不设置时是本地默认时区

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("Europe/Landon"));       // 设置打印的时区
System.out.println(sdf.format(calendar.getTime()));


// 输出结果
Asia/Shanghai
GMT+09:00
America/New_York
2019-11-20 01:00:00

四. 最佳实践总结

4.1 旧API转新API

  • 如果要把旧式的Date或Calendar转换为新API对象,可以通过toInstant()方法转换为Instant对象,再继续转换为ZonedDateTime
// Date -> Instant -> ZonedDateTime
Instant ins1 = new Date().toInstant();
ZonedDateTime zdt1 = ins1.atZone(ZoneId.systemDefault());


// Calendar -> Instant -> ZonedDateTime
Calendar calendar = Calendar.getInstance();
Instant ins2 = calendar.toInstant();
ZonedDateTime zdt2 = ins2.atZone(calendar.getTimeZone().toZoneId());

4.2 新API转旧API

  • 如果要把新的ZonedDateTime转换为旧的API对象,只能借助 long 型时间戳做一个中转
// ZonedDateTime -> long:
ZonedDateTime zdt = ZonedDateTime.now();
long ts = zdt.toEpochSecond() * 1000;

// long -> Date:
Date date = new Date(ts);

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

  • 在使用Java程序操作数据库时,我们需要把数据库类型与Java类型映射起来。下表是数据库类型与Java新旧API的映射关系
数据库 对应Java类(旧) 对应Java类(新)
DATETIME java.util.Date LocalDateTime
DATE java.sql.Date LocalDate
TIME java.sql.Time LocalTime
TIMESTAMP java.sql.Timestamp LocalDateTime
  • 实际上只需要存储时刻(Instant)即可,因为时刻可以根据不同的时区,显示出本地时间。所以最好直接用长整数long表示,在数据库中存储为BIGINT类型。
  • 通过存储一个long型时间戳,我们可以编写一个 timestampToString() 的方法,非常简单地为不同地区的用户来显示不同的本地时间。
public class Main {
    public static void main(String[] args) {
        long ts = 1622346756000L;
        
        // System.out.println(ZoneId.getAvailableZoneIds());

        System.out.println(timestampToString(ts, "Asia/Shanghai"));
        System.out.println(timestampToString(ts, "America/New_York"));
        System.out.println(timestampToString(ts, "Europe/London"));
        System.out.println(timestampToString(ts, "Asia/Singapore"));
    }

    static String timestampToString(long epochMilli, String zoneId) {
        Instant instant = Instant.ofEpochMilli(epochMilli);
        DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        return df.format(ZonedDateTime.ofInstant(instant, ZoneId.of(zoneId)));
    }
    
}

// 输出格式
2021-05-30 11:52:36
2021-05-29 23:52:36
2021-05-30 04:52:36
2021-05-30 11:52:36

🔗

文章推荐