Java-日期时间
Java 日期时间相关笔记
时区
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 小时
日期时间格式
日期时间格式表
Symbol | Meaning | Presentation | Example |
---|---|---|---|
G | era designator 纪元 | Text | AD |
y | year 年 | Number | 2009 |
M | month in year 年中的月份 | Text & Number | July & 07 |
d | day in month 月份中的天数 | Number | 10 |
h | hour in am/pm (1-12) am/pm 中的小时数(1-12) | Number | 12 |
H | hour in day (0-23) 一天中的小时数(0-23) | Number | 0 |
m | minute in hour 小时中的分钟数 | Number | 30 |
s | second in minute 分钟中的秒数 | Number | 55 |
S | millisecond 毫秒数 | Number | 978 |
E | day in week 星期中的天数 | Text | Tuesday |
D | day in year 年中的天数 | Number | 189 |
F | day of week in month 月份中的星期 | Number | 2 (2nd Wed in July) |
w | week in year 年中的周数 | Number | 27 |
W | week in month 月份中的周数 | Number | 2 |
a | am/pm marker Am/pm 标记 | Text | PM |
k | hour in day (1-24) 一天中的小时数(1-24) | Number | 24 |
K | hour in am/pm (0-11) am/pm 中的小时数(0-11) | Number | 0 |
z | time zone 时区 | Text | Pacific Standard Time |
Z | time zone 时区 | Text | Pacific Standard Time |
‘ | escape for text 文本转义符 | Delimiter | (none) |
‘ | single quote 单引号 | Literal | ‘ |
常用日期格式如下
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);
until() 计算时间差
public long until(Temporal endExclusive, TemporalUnit unit)
计算 endExclusive 减去 this 的时间差,返回时间单位为 unit。
如果 endExclusive 小于 this,返回为负数。
LocalDateTime todayStart = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);//当天零点
LocalDateTime todayEnd = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);//当天结束时间
System.out.println(todayStart.until(todayEnd, ChronoUnit.HOURS)); // 结果 23
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.Datepublic 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.Timestamp
是 java.util.Date
的子类,所以 Timestamp 本身就是 Date
public class Timestamp extends java.util.Date {
...
}
Timestamp 可直接赋值给 DateDate date = new Timestamp(System.currentTimeMillis())
Date 通过毫秒时间戳转换为 TimestampTimestamp 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.SimpleDateFormatpublic 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);
}
Calendar
java.util.Calendarpublic 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);
}
下一篇 Java-集合框架基础
页面信息
location:
protocol
: host
: hostname
: origin
: pathname
: href
: document:
referrer
: navigator:
platform
: userAgent
: