时区

UTC世界协调时

UTC(Universal Coordinated Time) 协调世界时,又称世界统一时间、世界标准时间、国际协调时间。

GMT格林威治标准时(GMT=UTC)

GMT(Greenwich Mean Time) 格林尼治标准时间,是指位于英国伦敦郊区的皇家格林尼治天文台的标准时间,一般指世界时。

CST中国标准时(UTC/GMT+8)

CST(China Standard Time) 中国标准时间。
在时区划分上,属东八区,比协调世界时早 8 小时,记为 UTC+8,与中华民国国家标准时间(旧称“中原标准时间”)、香港时间和澳门时间相同。
当格林威治时间为凌晨 0:00 时,中国标准时间刚好为上午 8:00。

时间转换:
CST=UTC/GMT + 8 小时

日期时间格式

日期时间格式表

常用日期格式如下

Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2018-12-25 19:01:12");

h大小写和m大小写

注意:
常用的日期格式 yyyy-MM-dd HH:mm:ss 中,只有 M 和 H 是大写的
1、h 大小写是为了区分12小时制和24小时制,小写的h是12小时制,大写的H是24小时制。
2、小写 m 是分钟,大写 M 是月份。

Java日期中的T和Z

有时候看到世界带 T Z (如:2018-01-31T14:32:19Z)
这是 UTC 统一时间,T 代表后面跟着是时间,Z代表0时区(相差北京时间8小时),转换为北京时间(CST)需要+8小时
构造时间格式时 T 和 Z 要加单引号。

@Test
public void testUTC() throws Exception {
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
    simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
    String utcTime = "2020-10-28T03:02:19Z";
    System.out.println(simpleDateFormat.parse(utcTime));

结果
Wed Oct 28 11:02:19 CST 2020

SSSZ(SSS毫秒Z时区)

yyyy-MM-dd’T’HH:mm:ss.SSSZ
后面的三个SSS指的是毫秒,Z代表的时区,中间的T代表可替换的任意字符。

Java 时区设置

TimeZone.setDefault()

System.out.println(TimeZone.getDefault());
final TimeZone timeZone = TimeZone.getTimeZone("Asia/Shanghai");
TimeZone.setDefault(timeZone);

-Duser.timezone=Asia/Shanghai

java 启动脚本中增加 jvm 参数
java -Duser.timezone=Asia/Shanghai
java -Duser.timezone=GMT+08

TZ环境变量

export TZ=Asia/Shanghai

Java8 新日期和时间 API

Java 8 的日期和时间类包含 LocalDate, LocalTime, LocalDateTime, Instant, Duration 以及 Period,这些类都包含在 java.time 包中。

Java 8新特性(四):新的时间和日期API
https://lw900925.github.io/java/java8-newtime-api.html

为什么要引入新的日期API?

Java 的 java.util.Date 和 java.util.Calendar 类易用性差,不支持时区,而且他们都不是线程安全的;

用于格式化日期的类 DateFormat 被放在 java.text 包中,它是一个抽象类,所以我们需要实例化一个 SimpleDateFormat 对象来处理日期格式化,并且 DateFormat 也是非线程安全,这意味着如果你在多线程程序中调用同一个 DateFormat 对象,会得到意想不到的结果。

对日期的计算方式繁琐,而且容易出错,因为月份是从0开始的,从 Calendar 中获取的月份需要加一才能表示当前月份。

由于以上这些问题,出现了一些三方的日期处理框架,例如 Joda-Time,date4j 等开源项目。

LocalDate

LocalDate 类表示一个具体的日期,但不包含具体时间,也不包含时区信息。可以通过 LocalDate 的静态方法 of() 创建一个实例,LocalDate 也包含一些方法用来获取年份,月份,天,星期几等:

LocalDate localDate = LocalDate.of(2017, 1, 4);     // 初始化一个日期:2017-01-04
int year = localDate.getYear();                     // 年份:2017
Month month = localDate.getMonth();                 // 月份:JANUARY
int dayOfMonth = localDate.getDayOfMonth();         // 月份中的第几天:4
DayOfWeek dayOfWeek = localDate.getDayOfWeek();     // 一周的第几天:WEDNESDAY
int length = localDate.lengthOfMonth();             // 月份的天数:31
boolean leapYear = localDate.isLeapYear();          // 是否为闰年:false
LocalDateTime statDateStart = LocalDate.now().atStartOfDay(); // 当天0点

也可以调用静态方法 now() 来获取当前日期:

LocalDate now = LocalDate.now();

LocalTime

LocalTime 包含具体时间

LocalTime localTime = LocalTime.of(17, 23, 52);     // 初始化一个时间:17:23:52
int hour = localTime.getHour();                     // 时:17
int minute = localTime.getMinute();                 // 分:23
int second = localTime.getSecond();                 // 秒:52

LocalDateTime

LocalDateTime 类是 LocalDate 和 LocalTime 的结合体,可以通过 of() 方法直接创建,也可以调用 LocalDate 的 atTime() 方法或 LocalTime 的 atDate() 方法将 LocalDate 或 LocalTime 合并成一个 LocalDateTime:

LocalDateTime ldt1 = LocalDateTime.of(2017, Month.JANUARY, 4, 17, 23, 52);
LocalDate localDate = LocalDate.of(2017, Month.JANUARY, 4);
LocalTime localTime = LocalTime.of(17, 23, 52);
LocalDateTime ldt2 = localDate.atTime(localTime);

Instant 时间戳

Instant 用于表示一个时间戳,它与我们常使用的 System.currentTimeMillis() 有些类似,不过 Instant 可以精确到纳秒(Nano-Second),System.currentTimeMillis() 方法只精确到毫秒(Milli-Second)。
Instant 除了使用 now() 方法创建外,还可以通过 ofEpochSecond 方法创建:

Instant instant = Instant.ofEpochSecond(120, 100000);

ofEpochSecond() 方法的第一个参数为秒,第二个参数为纳秒,上面的代码表示从1970-01-01 00:00:00开始后两分钟的10万纳秒的时刻

LocalDateTime 和 Timestamp 互相转换

// LocalDateTime 转 Timestamp
Timestamp timestamp = Timestamp.valueOf(LocalDateTime.now());
// Timestamp 转 LocalDateTime
LocalDateTime localDateTime = timestamp.toLocalDateTime();

LocalDateTime 和 Date 互相转换

LocalDateTime 和 Date 的互相转换需要通过 Timestamp 或 Instant 完成。

// LocalDateTime 和 Date 互转
@Test
public void testConvert2Date() throws Exception {
    // LocalDateTime 转 Date
    LocalDateTime localDateTime = LocalDateTime.now();
    Date date1 = new Date(Timestamp.valueOf(localDateTime).getTime()); // 通过 Timestamp
    Date date2 = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); // 通过 Instant
    System.out.println("Date: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date1));
    System.out.println("Date: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date2));
    Thread.sleep(1000L);
    // Date 转 LocalDateTime
    Date date = new Date();
    LocalDateTime localDateTime1 = new Timestamp(date.getTime()).toLocalDateTime(); // 通过 Timestamp
    LocalDateTime localDateTime2 = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); // 通过 Instant
    LocalDateTime localDateTime3 = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); // 通过 Instant
    System.out.println("LocalDateTime: " + localDateTime1.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
    System.out.println("LocalDateTime: " + localDateTime2.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
    System.out.println("LocalDateTime: " + localDateTime3.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

LocalDateTime 和时间戳秒/毫秒互转

几种方法中,通过 java.sql.Timestamp 转换是最方便的

// LocalDateTime 和时间戳互转
@Test
public void testConvert2Timestamp() {
    LocalDateTime localDateTime = LocalDateTime.now();
    // LocalDateTime 转 时间戳毫秒
    System.out.println("时间戳毫秒: " + Timestamp.valueOf(localDateTime).getTime());
    System.out.println("时间戳毫秒: " + localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
    System.out.println("时间戳毫秒: " + localDateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli());
    // LocalDateTime 转 时间戳秒
    System.out.println("时间戳秒: " + Timestamp.valueOf(localDateTime).getTime() / 1000);
    System.out.println("时间戳秒: " + localDateTime.atZone(ZoneId.systemDefault()).toInstant().getEpochSecond());
    System.out.println("时间戳秒: " + localDateTime.toEpochSecond(ZoneOffset.of("+8")));
    // 时间戳毫秒 转 LocalDateTime
    long tsMillis = System.currentTimeMillis();
    LocalDateTime localDateTime2 = new Timestamp(tsMillis).toLocalDateTime();
    System.out.println("LocalDateTime: " + localDateTime2.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

Duration 时间段(秒)

Duration 的内部实现与 Instant 类似,也是包含两部分:seconds 表示秒,nanos 表示纳秒。两者的区别是 Instant 用于表示一个时间戳(或者说是一个时间点),而 Duration 表示一个时间段,所以 Duration 类中不包含 now() 静态方法。

Duration 可方便的转为其他时间单位

long days = duration.toDays();              // 这段时间的总天数
long hours = duration.toHours();            // 这段时间的小时数
long minutes = duration.toMinutes();        // 这段时间的分钟数
long seconds = duration.getSeconds();       // 这段时间的秒数
long milliSeconds = duration.toMillis();    // 这段时间的毫秒数
long nanoSeconds = duration.toNanos();      // 这段时间的纳秒数

Duration 构造

可以通过 Duration.between() 方法创建 Duration 对象,表示两个时间之间时间段:

LocalDateTime from = LocalDateTime.of(2017, Month.JANUARY, 5, 10, 7, 0);    // 2017-01-05 10:07:00
LocalDateTime to = LocalDateTime.of(2017, Month.FEBRUARY, 5, 10, 7, 0);     // 2017-02-05 10:07:00
Duration duration = Duration.between(from, to);     // 表示从 2017-01-05 10:07:00 到 2017-02-05 10:07:00 这段时间

Duration 对象还可以通过 of() 方法创建,该方法接受一个时间段长度,和一个时间单位作为参数:

Duration duration1 = Duration.of(5, ChronoUnit.DAYS);       // 5天
Duration duration2 = Duration.of(1000, ChronoUnit.MILLIS);  // 1000毫秒

DurationUtils

org.apache.commons.lang3.time.DurationUtils

TimeUnit 时间单位转 ChronoUnit 时间单位
static ChronoUnit toChronoUnit(final TimeUnit timeUnit);
toDuration() 通过 TimeUnit 时间单位构造 Duration
public static Duration toDuration(final long amount, final TimeUnit timeUnit);

Duration 操作

Duration 可以进行一系列操作

@Test
public void testDuration() {
    Duration fiveMinutes = Duration.of(5, ChronoUnit.MINUTES);
    Duration oneMinutes = fiveMinutes.dividedBy(5); // 5分钟除以5,得1分钟
    Assertions.assertTrue(Duration.of(1, ChronoUnit.MINUTES).equals(oneMinutes));
    Assertions.assertTrue(oneMinutes.compareTo(fiveMinutes) < 0); // 比较,1分钟 - 5分钟 得负数表示小于
    LocalDateTime now = LocalDateTime.now();
    LocalDateTime fiveMinutesBefore = now.minus(fiveMinutes); // 当前时间减去5分钟的Duration
    Assertions.assertTrue(fiveMinutesBefore.isBefore(now));

Duration 格式化

// Duration 格式化
@Test
public void testDurationFormat() {
    Duration fiveMinutes = Duration.of(5, ChronoUnit.MINUTES);
    System.out.println(DurationFormatUtils.formatDuration(fiveMinutes.toMillis(), "d:H:mm:ss", false)); // 0:0:5:0
    System.out.println(DurationFormatUtils.formatDuration(fiveMinutes.toMillis(), "d:H:mm:ss", true)); // 0:0:05:00

Duration 格式化为中文时间段描述

@Test
public void testDurationFormatChinese() {
    Duration duration = Duration.of(31501245, ChronoUnit.SECONDS);
    long seconds = duration.getSeconds();
    long days = seconds / 86400;
    seconds = seconds % 86400;
    long hours = seconds / 3600;
    long minutes = (seconds % 3600) / 60;
    seconds = (seconds % 3600) % 60;
    String format = String.format("%d分%d秒", minutes, seconds);
    if (hours > 0) {
        format = String.format("%d小时%s", hours, format);
    if (days > 0) {
        format = String.format("%d天%s", days, format);
    System.out.println(format);

结果: 364天14小时20分45秒

Unit must not have an estimated duration

Duration d1 = Duration.of(3, ChronoUnit.YEARS);
抛异常
java.time.temporal.UnsupportedTemporalTypeException: Unit must not have an estimated duration
原因是 Duration 是基于时间(时分秒)的时间段,年/月范围太大不精确,无法直接使用 年/月 构造 Duration 对象
Period 才是基于日期(年月日)的视觉段

Period 时间段(天)

Period 在概念上和 Duration 类似,区别在于 Period 是以年月日来衡量一个时间段,比如2年3个月6天:

Period period = Period.of(2, 3, 6);

Period对象也可以通过between()方法创建,值得注意的是,由于Period是以年月日衡量时间段,所以between()方法只能接收LocalDate类型的参数:

// 2017-01-05 到 2017-02-05 这段时间
Period period = Period.between(
                LocalDate.of(2017, 1, 5),
                LocalDate.of(2017, 2, 5));

ZoneId 时区

新的时区类 java.time.ZoneId 是原有的 java.util.TimeZone 类的替代品。ZoneId 对象可以通过 ZoneId.of() 方法创建,也可以通过 ZoneId.systemDefault() 获取系统默认时区:

ZoneId shanghaiZoneId = ZoneId.of("Asia/Shanghai");
ZoneId systemZoneId = ZoneId.systemDefault();

of() 方法接收一个“区域/城市”的字符串作为参数,你可以通过 getAvailableZoneIds() 方法获取所有合法的“区域/城市”字符串:

Set<String> zoneIds = ZoneId.getAvailableZoneIds();

有了ZoneId,我们就可以将一个LocalDate、LocalTime或LocalDateTime对象转化为ZonedDateTime对象:

LocalDateTime localDateTime = LocalDateTime.now();
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, shanghaiZoneId);

日期操作

LocalDate date = LocalDate.of(2017, 1, 5);          // 2017-01-05
LocalDate date1 = date.withYear(2016);              // 修改为 2016-01-05
LocalDate date2 = date.withMonth(2);                // 修改为 2017-02-05
LocalDate date3 = date.withDayOfMonth(1);           // 修改为 2017-01-01
LocalDate date4 = date.plusYears(1);                // 增加一年 2018-01-05
LocalDate date5 = date.minusMonths(2);              // 减少两个月 2016-11-05
LocalDate date6 = date.plus(5, ChronoUnit.DAYS);    // 增加5天 2017-01-10

使用 TemporalAdjuster 调整时间

可以使用 with() 方法的另一个重载方法,它接收一个 TemporalAdjuster 参数,可以使我们更加灵活的调整日期:

LocalDate date7 = date.with(nextOrSame(DayOfWeek.SUNDAY));      // 返回下一个距离当前时间最近的星期日
LocalDate date9 = date.with(lastInMonth(DayOfWeek.SATURDAY));   // 返回本月最后一个星期六

1小时前按小时取整

LocalDateTime timeStart = LocalDateTime.now().minusHours(1).withMinute(0).withSecond(0).withNano(0); // 前 1 小时向下取整
LocalDateTime timeEnd = LocalDateTime.now().withMinute(0).withSecond(0).withNano(0); // 当前小时向下取整

5分钟前按分取整

LocalDateTime timeStart = LocalDateTime.now().minusMinutes(5).withSecond(0).withNano(0);
LocalDateTime timeEnd = LocalDateTime.now().withSecond(0).withNano(0);

四舍五入对齐到上/下一个5分钟整

如果当前时间 dt 就是 5 分钟整,则结果就是自己

@Test
public void testAligned5Min() {
    LocalDateTime dt = LocalDateTime.now();
    LocalDateTime nextAlignedDt = dt.withSecond(0).withNano(0).plusMinutes((65 - dt.getMinute()) % 5);
    LocalDateTime preAlignedDt = dt.withSecond(0).withNano(0).minusMinutes((dt.getMinute()) % 5);
    System.out.println("当前时间:" + dt);
    System.out.println("下一个5分钟整:" + nextAlignedDt);
    System.out.println("上一个5分钟整:" + preAlignedDt);

遍历时间间隔内的全部5分钟整

遍历闭区间 [dt1, dt2] 之间的全部 5分钟整 时间

LocalDateTime dt1 = LocalDateTime.of(2022, 7, 10, 11, 68, 46);
LocalDateTime dt2 = LocalDateTime.of(2022, 7, 11, 17, 68, 46);
// 下一个整 5 分钟对应的时间点
LocalDateTime nextAlignedDateTime = dt1.withSecond(0).withNano(0).plusMinutes((65 - startTimeFrom.getMinute()) % 5);
while (nextAlignedDateTime.isBefore(dt2) || nextAlignedDateTime.equals(dt2)) {
    System.out.println(nextAlignedDateTime);
    nextAlignedDateTime = nextAlignedDateTime.plusMinutes(5);

获取昨天/今天起始时间和结束时间

Java 8 LocalDateTime 获取当天和昨天的起始时间和结束时间
注意 with(LocalTime.MAX) 产生的是当天 2022-06-23 23:59:59.9999 如果直接存到不带毫秒精度的 MySQL datetime 字段上,会显示为第二天零点 2022-06-24 00:00:00,如果想获得准确的 2022-06-23 23:59:59.0000 可以先获取第二天0点,然后再减一秒

LocalDateTime yesterdayStart = LocalDateTime.now().plusDays(-1).with(LocalTime.MIN); // 昨天零点 2022-06-23 00:00:00.0000
LocalDateTime yesterdayEnd = LocalDateTime.now().plusDays(-1).with(LocalTime.MAX); // 昨天结束时间 2022-06-23 23:59:59.9999
LocalDateTime yesterdayEnd2 = LocalDateTime.now().with(LocalTime.MIN).minusSeconds(1); // 昨天23点59分59秒 2022-06-23 23:59:59.0000
LocalDateTime todayStart = LocalDateTime.of(LocalDate.now(), LocalTime.MIN); //当天零点
LocalDateTime todayStart2 = LocalDateTime.now().with(LocalTime.MIN); //当天零点
LocalDateTime todayEnd = LocalDateTime.of(LocalDate.now(), LocalTime.MAX); //当天结束时间 2022-06-23 23:59:59.9999
LocalDateTime todayEnd2 = LocalDateTime.now().with(LocalTime.MAX); //当天结束时间 2022-06-23 23:59:59.9999
System.out.println(yesterdayStart);
System.out.println(yesterdayEnd);
System.out.println(todayStart);
System.out.println(todayStart2);
System.out.println(todayEnd);
System.out.println(todayEnd2);

抹去秒

@Test
public void testCeilFloor() {
    LocalDateTime now = LocalDateTime.now();
    System.out.println("当前时间: " + now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
    // 5分钟前向下取整(抹去秒)
    System.out.println("5分钟前抹去秒: " + now.minusMinutes(5).withSecond(0).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
    // 当前时间按分钟向下取整(抹去秒)
    System.out.println("当前时间抹去秒: " + now.withSecond(0).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));

DateTimeFormatter 日期时间格式化

新的日期 API 中提供了一个 DateTimeFormatter 类用于处理日期格式化操作,它被包含在 java.time.format 包中,Java 8 的日期类有一个 format() 方法用于将日期格式化为字符串,该方法接收一个 DateTimeFormatter 类型参数:

// 格式化
@Test
public void testFormatter() {
    LocalDateTime dateTime = LocalDateTime.now();
    System.out.println("时间戳秒: " + dateTime.atZone(ZoneId.systemDefault()).toInstant().getEpochSecond());
    System.out.println("时间戳毫秒: " + dateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
    System.out.println(dateTime.format(DateTimeFormatter.BASIC_ISO_DATE));
    System.out.println(dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE));
    System.out.println(dateTime.format(DateTimeFormatter.ISO_LOCAL_TIME));
    System.out.println(dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
    System.out.println(dateTime.format(DateTimeFormatter.ofPattern("今天是:yyyy年 MM月 dd日 E", Locale.CHINESE)));
时间戳秒: 1587357985
时间戳秒: 1587357985393
20200420
2020-04-20
12:46:25.393
2020-04-20
今天是:2020年 04月 20日 星期一

同样,日期类也支持将一个字符串解析成一个日期对象,例如:

String strDate6 = "2017-01-05";
String strDate7 = "2017-01-05 12:30:05";
LocalDate date = LocalDate.parse(strDate6, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
LocalDateTime dateTime1 = LocalDateTime.parse(strDate7, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

Date

java.util.Date
public class Date extends Object implements Serializable, Cloneable, Comparable<Date>
类 Date 表示特定的瞬间,精确到毫秒。从 JDK 1.1 开始,应该使用 Calendar 类实现日期和时间字段之间转换,使用 DateFormat 类来格式化和分析日期字符串。Date 中的把日期解释为年、月、日、小时、分钟和秒值的方法已废弃。

Date() 当前时间

public Date()
构造方法,分配 Date 对象并初始化此对象,以表示分配它的时间(精确到毫秒)。
例如:Date date = new Date(); //以当前的日期和时间作为其初始值

Date(long) 毫秒时间戳指定的时间

public Date(long date)
分配 Date 对象并初始化此对象,以表示自从标准基准时间(称为“历元(epoch)”,即 1970 年 1 月 1 日 00:00:00 GMT)以来的指定毫秒数。
参数:date - 自 1970 年 1 月 1 日 00:00:00 GMT 以来的毫秒数。

获取当前日期+23:59:59对应的时间

sdf的日志格式字符串中直接写入固定时间即可

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd 23:59:59");
String s = sdf.format(new Date());
Date date =  sdf.parse(s);

Date去掉时分秒

方法一,用 SimpleDateFormat

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String s = sdf.format(new Date());
Date date =  sdf.parse(s);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd 00:00:00");
String s = sdf.format(new Date());
Date date =  sdf.parse(s);

方法二,用 Calendar

Calendar calender = Calendar.getInstance();
calender.setTime(new Date());
// 将时分秒,毫秒域清零
calender.set(Calendar.HOUR_OF_DAY, 0);
calender.set(Calendar.MINUTE, 0);
calender.set(Calendar.SECOND, 0);
calender.set(Calendar.MILLISECOND, 0);
System.out.printf("%1$tF %1$tT\n", calender.getTime());// calender.getTime()返回的Date已经是更新后的对象

java 8:只取年月日的java.util.Date(时分秒清零)对象
https://blog.csdn.net/10km/article/details/53906993

获取当前毫秒时间戳

获取当前时间戳,单位毫秒:

//方法 一
System.currentTimeMillis();
//方法 二
Calendar.getInstance().getTimeInMillis();
//方法 三
new Date().getTime();

其中 Calendar.getInstance().getTimeInMillis() 这种方式速度最慢,这是因为Canlendar要处理时区问题会耗费较多的时间。

注意System.currentTimeMillis()潜在的性能问题

其实 System.currentTimeMillis() 在调用频次高时也会有性能问题,
public static native long currentTimeMillis();
是个 native 方法,调用了 hotspot/src/os/linux/vm/os_linux.cpp 中的 javaTimeMillis() 方法,此方法中
调用gettimeofday()需要从用户态切换到内核态;
1、gettimeofday()的表现受Linux系统的计时器(时钟源)影响,在HPET计时器下性能尤其差;HPET计时器性能较差的原因是会将所有对时间戳的请求串行执行
2、系统只有一个全局时钟源,高并发或频繁访问会造成严重的争用。
3、基于以上几点,所以 System.currentTimeMillis() 在调用频次高时也会有性能问题

解决方法:用一个守护线程专门来获取时间戳并存入内存缓存中

public class CurrentTimeMillisClock {
    private volatile long now;
    private CurrentTimeMillisClock() {
        this.now = System.currentTimeMillis();
        scheduleTick();
    private void scheduleTick() {
        new ScheduledThreadPoolExecutor(1, runnable -> {
            Thread thread = new Thread(runnable, "current-time-millis");
            thread.setDaemon(true);
            return thread;
        }).scheduleAtFixedRate(() -> {
            now = System.currentTimeMillis();
        }, 1, 1, TimeUnit.MILLISECONDS);
    public long now() {
        return now;
    public static CurrentTimeMillisClock getInstance() {
        return SingletonHolder.INSTANCE;
    private static class SingletonHolder {
        private static final CurrentTimeMillisClock INSTANCE = new CurrentTimeMillisClock();

使用的时候,直接CurrentTimeMillisClock.getInstance().now()就可以了。不过,在System.currentTimeMillis()的效率没有影响程序整体的效率时,就不必忙着做优化

注意System.currentTimeMillis()潜在的性能问题
https://www.jianshu.com/p/d2039190b1cb

Unix时间戳和Date互相转换

http://www.cnblogs.com/killbug/archive/2013/04/08/3008764.html
时间戳转Date

request.setStartTime(new Date(1530720000 * 1000L));
request.setEndTime(new Date(1531238400L * 1000L));

Date转时间戳

params.put("start_time", (request.getStartTime().getTime() / 1000) + "");
params.put("end_time", (request.getEndTime().getTime() / 1000) + "");

Timestamp 和 Date 互相转换

java.sql.Timestampjava.util.Date 的子类,所以 Timestamp 本身就是 Date

public class Timestamp extends java.util.Date {

Timestamp 可直接赋值给 Date
Date date = new Timestamp(System.currentTimeMillis())

Date 通过毫秒时间戳转换为 Timestamp
Timestamp ts = new Timestamp(new Date().getTime())

Date 与 Timestamp compareTo()/before()/after() 比较问题

问题1、相同的 Date 和 Timestamp 用 Date.compareTo() 比较但不相等

Date date = new Date();
Timestamp timestampEqual = new Timestamp(date.getTime());
Assertions.assertEquals(0, timestampEqual.compareTo(date));  // 相等
Assertions.assertNotEquals(0, date.compareTo(timestampEqual));  // 不相等
Assertions.assertEquals(date.getTime(), timestampEqual.getTime());  // 相等

原因是如果用 Date.compareTo() 比较,都会转换为 Date 类型,使用 Date 内的 compareTo() 和 getMillisOf() 方法比较,直接取了 Timestamp.fastTime 字段,但 Timestamp 里的 fastTime 字段只存储秒,Timestamp 中使用单独的 nanos 字段存储毫秒,从而 getMillisOf() 只获取了 Timestamp 的秒,丢失了毫秒,导致比较结果不一致。

public int compareTo(Date anotherDate) {
    long thisTime = getMillisOf(this);
    long anotherTime = getMillisOf(anotherDate);
    return (thisTime<anotherTime ? -1 : (thisTime==anotherTime ? 0 : 1));
static final long getMillisOf(Date date) {
    if (date.cdate == null || date.cdate.isNormalized()) {
        return date.fastTime;
    BaseCalendar.Date d = (BaseCalendar.Date) date.cdate.clone();
    return gcal.getTime(d);

如果使用 Timestamp.compareTo() 比较是没问题的,内部考虑到了 Timestamp 和 Date 比较的情况,都是取的毫秒比较:

public int compareTo(java.util.Date o) {
    if(o instanceof Timestamp) {
        // When Timestamp instance compare it with a Timestamp
        // Hence it is basically calling this.compareTo((Timestamp))o);
        // Note typecasting is safe because o is instance of Timestamp
        return compareTo((Timestamp)o);
    } else {
        // When Date doing a o.compareTo(this)
        // will give wrong results.
        Timestamp ts = new Timestamp(o.getTime());
        return this.compareTo(ts);

问题2、Date 和 Timestamp 之间的 before() 和 after() 比较也有问题

Date date = new Date();
Timestamp timestampPlus1Mills = new Timestamp(date.getTime() + 1);
Assertions.assertTrue(timestampPlus1Mills.getTime() > date.getTime()); // true
Assertions.assertFalse(timestampPlus1Mills.after(date)); // false
Assertions.assertFalse(date.before(timestampPlus1Mills)); // false

原因也是 Date 和 Timestamp 之间的 before() 和 after() 比较也都会先转为 Date 类型再使用 Date 内的 before()/after() 比较,内部都调用了 getMillisOf(),还是只取了 Timestamp 的秒部分,丢失了毫秒。

public boolean before(Date when) {
    return getMillisOf(this) < getMillisOf(when);
public boolean after(Date when) {
    return getMillisOf(this) > getMillisOf(when);

解决方法:
Date 和 Timestamp 比较时,调用 getTime() 直接比较毫秒时间戳

SimpleDateFormat

java.text.SimpleDateFormat
public class SimpleDateFormat extends DateFormat
SimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。
SimpleDateFormat 使得可以选择任何用户定义的日期-时间格式的模式。但是,仍然建议通过 DateFormat 中的 getTimeInstance、getDateInstance 或 getDateTimeInstance 来新的创建日期-时间格式化程序。

构造方法

public SimpleDateFormat(String pattern)
用给定的模式和默认语言环境的日期格式符号构造 SimpleDateFormat。注:此构造方法可能不支持所有语言环境。要覆盖所有语言环境,请使用 DateFormat 类中的工厂方法。

  • 参数:pattern - 描述日期和时间格式的模式
  • NullPointerException - 如果给定的模式为 null
  • IllegalArgumentException - 如果给定的模式无效
  • parse()

    Date parse(String text, ParsePosition pos)
    解析字符串的文本,生成 Date。 此方法试图解析从 pos 给定的索引处开始的文本。

    format()

    StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition pos)
    将给定的 Date 格式化为日期/时间字符串,并将结果添加到给定的 StringBuffer。格式为类 DateFormat 中的 format。返回格式化的日期-时间字符串。

    Date转String

    Date date = new Date();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String dateStr = sdf.format(date);
    

    String转Date

    Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2018-12-25 19:01:12");
    

    注意 SimpleDateFormat.parse() 方法会抛出 ParseException 受检异常,必须捕获或抛出处理。

    SimpleDateFormat与线程安全

    如下面代码中,将 SimpleDateFormat 声明为 static ,开多个线程并发去parse日期

    public class SimpleDateFormatTest {
        private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        public static String format(Date date) {
            return sdf.format(date);
        public static Date parse(String dateStr) throws ParseException {
            return sdf.parse(dateStr);
        @Test
        public void testSDFConcurrent() throws InterruptedException {
            ExecutorService executorService = Executors.newCachedThreadPool();
            for (int i = 0; i < 20; i++) {
                executorService.execute(() -> {
                    try {
                        System.out.println(parse(format(new Date())));
                    } catch (ParseException e) {
                        e.printStackTrace();
            // 等待线程池中的线程执行完毕
            executorService.shutdown();
            // 阻塞当前线程,等待线程池执行完毕
            executorService.awaitTermination(10, TimeUnit.SECONDS);
    

    执行结果如下图,直接异常退出


    SimpleDateFormat并发问题

    原因:
    SimpleDateFormat 以及其实现的抽象类 DateFormat 都是使用了内部的成员变量 protected Calendar calendar; 来做转换,多线程情况下共享 SimpleDateFormat 对象会导致线程间数据互相影响,导致出错。

    阿里巴巴Java开发手册中强制禁止将 SimpleDateFormat 定义为static

    【强制】SimpleDateFormat 是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类。

    解决方法:
    1、只在需要的时候创建新实例,不用static修饰

    public static String format(Date date) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.format(date);
    public static Date parse(String strDate) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.parse(strDate);
    

    不过会 会频繁地创建和销毁对象,效率较低。

    2、synchronized对象锁

    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public static String format(Date date) throws ParseException {
        synchronized(sdf){
            return sdf.format(date);
    public static Date parse(String strDate) throws ParseException {
        synchronized(sdf){
            return sdf.parse(strDate);
    

    并发量大的时候会对性能有影响,线程阻塞

    3、ThreadLocal线程间隔离

    private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public static Date parse(String dateStr) throws ParseException {
        return threadLocal.get().parse(dateStr);
    public static String format(Date date) {
        return threadLocal.get().format(date);
    

    ThreadLocal可以确保每个线程都可以得到单独的一个SimpleDateFormat的对象,那么自然也就不存在竞争问题了。

    4、使用Java8的DateTimeFormatter

    private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    public static String format2(LocalDateTime date) {
        return formatter.format(date);
    public static LocalDateTime parse2(String dateNow) {
        return LocalDateTime.parse(dateNow, formatter);
    

    还在使用SimpleDateFormat?你的项目崩没?

    Calendar

    java.util.Calendar
    public abstract class Calendar extends Object implements Serializable, Cloneable, Comparable<Calendar>
    Calendar 类是一个抽象类,它为特定瞬间与一组诸如 YEAR、MONTH、DAY_OF_MONTH、HOUR 等 日历字段之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)提供了一些方法。瞬间可用毫秒值来表示,它是距历元(即格林威治标准时间 1970 年 1 月 1 日的 00:00:00.000,格里高利历)的偏移量。
    与其他语言环境敏感类一样,Calendar 提供了一个类方法 getInstance,以获得此类型的一个通用的对象。Calendar 的 getInstance 方法返回一个 Calendar 对象,其日历字段已由当前日期和时间初始化:Calendar rightNow = Calendar.getInstance();

    字段摘要

    int DATE,get 和 set 的字段数字,指示一个月中的某天。它与 DAY_OF_MONTH 是同义词。一个月中第一天的值为 1。

    getInstance()

    public static Calendar getInstance()
    使用默认时区和语言环境获得一个日历对象。返回的 Calendar已初始化为当前日期和时间,使用了默认时区和默认语言环境。
    例如:Calendar rightNow = Calendar.getInstance();

    setTime()

    public final void setTime(Date date)
    使用给定的 Date 设置此 Calendar 的时间。

    getTime()

    public final Date getTime()
    返回一个表示此 Calendar 时间值(从历元至现在的毫秒偏移量)的 Date 对象。
    返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。

    get()

    int get(int field)
    返回给定日历字段的值。

    add()

    public abstract void add(int field, int amount)
    根据日历的规则,为给定的日历字段添加或减去指定的时间量。
    add(f, delta) 将 delta 添加到 f 字段中。这等同于调用 set(f, get(f) + delta),但要带以下两个调整:
    Add 规则 1:调用后 f 字段的值减去调用前 f 字段的值等于 delta,以字段 f 中发生的任何溢出为模。溢出发生在字段值超出其范围时,结果,下一个更大的字段会递增或递减,并将字段值调整回其范围内。
    例如,要从当前日历时间减去 5 天,可以通过调用以下方法做到这一点:
    add(Calendar.DAY_OF_MONTH, -5);

    获取今天前n天的日期

    public static Integer getNdaysBeforeNow(int n){
        Calendar cal=Calendar.getInstance();
        cal.add(Calendar.DAY_OF_MONTH, -n);
        SimpleDateFormat sdf=new SimpleDateFormat("yyyyMMdd");
        return Integer.parseInt(sdf.format(cal.getTime()));
    

    获取指定日期的下一天

    输入和输出参数都是Integer

    public static Integer getNextDay(Integer datein) {
        String dateinStr = Integer.toString(datein);
        SimpleDateFormat sdf=new SimpleDateFormat("yyyyMMdd");
        java.util.Date date=null;
        try {
            date = sdf.parse(dateinStr);
        }catch(java.text.ParseException e) {
            e.printStackTrace();
        Calendar cal=Calendar.getInstance();
        cal.setTime(date);
        cal.add(cal.DATE, 1); //日期加1天
        String dateoutStr = sdf.format(cal.getTime());
        return Integer.valueOf(dateoutStr);
    			
    1. 时区
      1. UTC世界协调时
      2. GMT格林威治标准时(GMT=UTC)
      3. CST中国标准时(UTC/GMT+8)
    2. 日期时间格式
      1. 日期时间格式表
      2. h大小写和m大小写
      3. Java日期中的T和Z
      4. SSSZ(SSS毫秒Z时区)
    3. Java 时区设置
      1. TimeZone.setDefault()
      2. -Duser.timezone=Asia/Shanghai
      3. TZ环境变量
    4. Java8 新日期和时间 API
      1. 为什么要引入新的日期API?
      2. LocalDate
      3. LocalTime
      4. LocalDateTime
      5. Instant 时间戳
        1. LocalDateTime 和 Timestamp 互相转换
        2. LocalDateTime 和 Date 互相转换
        3. LocalDateTime 和时间戳秒/毫秒互转
      6. Duration 时间段(秒)
        1. Duration 构造
        2. DurationUtils
          1. TimeUnit 时间单位转 ChronoUnit 时间单位
          2. toDuration() 通过 TimeUnit 时间单位构造 Duration
        3. Duration 操作
        4. Duration 格式化
        5. Unit must not have an estimated duration
      7. Period 时间段(天)
      8. ZoneId 时区
      9. 日期操作
        1. 使用 TemporalAdjuster 调整时间
        2. 1小时前按小时取整
        3. 5分钟前按分取整
        4. 四舍五入对齐到上/下一个5分钟整
        5. 遍历时间间隔内的全部5分钟整
        6. 获取昨天/今天起始时间和结束时间
        7. 抹去秒
      10. DateTimeFormatter 日期时间格式化
    5. Date
      1. Date() 当前时间
      2. Date(long) 毫秒时间戳指定的时间
      3. 获取当前日期+23:59:59对应的时间
      4. Date去掉时分秒
      5. 获取当前毫秒时间戳
      6. 注意System.currentTimeMillis()潜在的性能问题
      7. Unix时间戳和Date互相转换
      8. Timestamp 和 Date 互相转换
      9. Date 与 Timestamp compareTo()/before()/after() 比较问题
    6. SimpleDateFormat
      1. 构造方法
      2. parse()
      3. format()
      4. Date转String
      5. String转Date
      6. SimpleDateFormat与线程安全
    7. Calendar
      1. 字段摘要
      2. getInstance()
      3. setTime()
      4. getTime()
      5. get()
      6. add()
      7. 获取今天前n天的日期
      8. 获取指定日期的下一天
    Symbol Meaning Presentation Example era designator 纪元 year 年 Number month in year 年中的月份 Text & Number July & 07 day in month 月份中的天数 Number hour in am/pm (1-12) am/pm 中的小时数(1-12) Number hour in day (0-23) 一天中的小时数(0-23) Number minute in hour 小时中的分钟数 Number second in minute 分钟中的秒数 Number millisecond 毫秒数 Number day in week 星期中的天数 Tuesday day in year 年中的天数 Number day of week in month 月份中的星期 Number 2 (2nd Wed in July) week in year 年中的周数 Number week in month 月份中的周数 Number am/pm marker Am/pm 标记 hour in day (1-24) 一天中的小时数(1-24) Number hour in am/pm (0-11) am/pm 中的小时数(0-11) Number time zone 时区 Pacific Standard Time time zone 时区 Pacific Standard Time escape for text 文本转义符 Delimiter (none) single quote 单引号 Literal