UTC(Universal Coordinated Time) 协调世界时,又称世界统一时间、世界标准时间、国际协调时间。
GMT(Greenwich Mean Time) 格林尼治标准时间,是指位于英国伦敦郊区的皇家格林尼治天文台的标准时间,一般指世界时。
CST(China Standard Time) 中国标准时间。 在时区划分上,属东八区,比协调世界时早 8 小时,记为 UTC+8,与中华民国国家标准时间(旧称“中原标准时间”)、香港时间和澳门时间相同。 当格林威治时间为凌晨 0:00 时,中国标准时间刚好为上午 8:00。
时间转换: CST=UTC/GMT + 8 小时
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/Shanghaijava 启动脚本中增加 jvm 参数java -Duser.timezone=Asia/Shanghaijava -Duser.timezone=GMT+08 TZ环境变量export TZ=Asia/Shanghai Java8 新日期和时间 APIJava 8 的日期和时间类包含 LocalDate, LocalTime, LocalDateTime, Instant, Duration 以及 Period,这些类都包含在 java.time 包中。 Java 8新特性(四):新的时间和日期APIhttps://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 等开源项目。 LocalDateLocalDate 类表示一个具体的日期,但不包含具体时间,也不包含时区信息。可以通过 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(); LocalTimeLocalTime 包含具体时间 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 LocalDateTimeLocalDateTime 类是 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毫秒 DurationUtilsorg.apache.commons.lang3.time.DurationUtils TimeUnit 时间单位转 ChronoUnit 时间单位static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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/Shanghaijava 启动脚本中增加 jvm 参数java -Duser.timezone=Asia/Shanghaijava -Duser.timezone=GMT+08 TZ环境变量export TZ=Asia/Shanghai Java8 新日期和时间 APIJava 8 的日期和时间类包含 LocalDate, LocalTime, LocalDateTime, Instant, Duration 以及 Period,这些类都包含在 java.time 包中。 Java 8新特性(四):新的时间和日期APIhttps://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 等开源项目。 LocalDateLocalDate 类表示一个具体的日期,但不包含具体时间,也不包含时区信息。可以通过 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(); LocalTimeLocalTime 包含具体时间 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 LocalDateTimeLocalDateTime 类是 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毫秒 DurationUtilsorg.apache.commons.lang3.time.DurationUtils TimeUnit 时间单位转 ChronoUnit 时间单位static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
注意:常用的日期格式 yyyy-MM-dd HH:mm:ss 中,只有 M 和 H 是大写的1、h 大小写是为了区分12小时制和24小时制,小写的h是12小时制,大写的H是24小时制。2、小写 m 是分钟,大写 M 是月份。
yyyy-MM-dd HH:mm:ss
有时候看到世界带 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/Shanghaijava 启动脚本中增加 jvm 参数java -Duser.timezone=Asia/Shanghaijava -Duser.timezone=GMT+08 TZ环境变量export TZ=Asia/Shanghai Java8 新日期和时间 APIJava 8 的日期和时间类包含 LocalDate, LocalTime, LocalDateTime, Instant, Duration 以及 Period,这些类都包含在 java.time 包中。 Java 8新特性(四):新的时间和日期APIhttps://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 等开源项目。 LocalDateLocalDate 类表示一个具体的日期,但不包含具体时间,也不包含时区信息。可以通过 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(); LocalTimeLocalTime 包含具体时间 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 LocalDateTimeLocalDateTime 类是 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毫秒 DurationUtilsorg.apache.commons.lang3.time.DurationUtils TimeUnit 时间单位转 ChronoUnit 时间单位static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
@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
结果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/Shanghaijava 启动脚本中增加 jvm 参数java -Duser.timezone=Asia/Shanghaijava -Duser.timezone=GMT+08 TZ环境变量export TZ=Asia/Shanghai Java8 新日期和时间 APIJava 8 的日期和时间类包含 LocalDate, LocalTime, LocalDateTime, Instant, Duration 以及 Period,这些类都包含在 java.time 包中。 Java 8新特性(四):新的时间和日期APIhttps://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 等开源项目。 LocalDateLocalDate 类表示一个具体的日期,但不包含具体时间,也不包含时区信息。可以通过 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(); LocalTimeLocalTime 包含具体时间 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 LocalDateTimeLocalDateTime 类是 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毫秒 DurationUtilsorg.apache.commons.lang3.time.DurationUtils TimeUnit 时间单位转 ChronoUnit 时间单位static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
yyyy-MM-dd’T’HH:mm:ss.SSSZ后面的三个SSS指的是毫秒,Z代表的时区,中间的T代表可替换的任意字符。
System.out.println(TimeZone.getDefault()); final TimeZone timeZone = TimeZone.getTimeZone("Asia/Shanghai"); TimeZone.setDefault(timeZone); -Duser.timezone=Asia/Shanghaijava 启动脚本中增加 jvm 参数java -Duser.timezone=Asia/Shanghaijava -Duser.timezone=GMT+08 TZ环境变量export TZ=Asia/Shanghai Java8 新日期和时间 APIJava 8 的日期和时间类包含 LocalDate, LocalTime, LocalDateTime, Instant, Duration 以及 Period,这些类都包含在 java.time 包中。 Java 8新特性(四):新的时间和日期APIhttps://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 等开源项目。 LocalDateLocalDate 类表示一个具体的日期,但不包含具体时间,也不包含时区信息。可以通过 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(); LocalTimeLocalTime 包含具体时间 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 LocalDateTimeLocalDateTime 类是 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毫秒 DurationUtilsorg.apache.commons.lang3.time.DurationUtils TimeUnit 时间单位转 ChronoUnit 时间单位static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
System.out.println(TimeZone.getDefault()); final TimeZone timeZone = TimeZone.getTimeZone("Asia/Shanghai"); TimeZone.setDefault(timeZone);
-Duser.timezone=Asia/Shanghaijava 启动脚本中增加 jvm 参数java -Duser.timezone=Asia/Shanghaijava -Duser.timezone=GMT+08 TZ环境变量export TZ=Asia/Shanghai Java8 新日期和时间 APIJava 8 的日期和时间类包含 LocalDate, LocalTime, LocalDateTime, Instant, Duration 以及 Period,这些类都包含在 java.time 包中。 Java 8新特性(四):新的时间和日期APIhttps://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 等开源项目。 LocalDateLocalDate 类表示一个具体的日期,但不包含具体时间,也不包含时区信息。可以通过 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(); LocalTimeLocalTime 包含具体时间 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 LocalDateTimeLocalDateTime 类是 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毫秒 DurationUtilsorg.apache.commons.lang3.time.DurationUtils TimeUnit 时间单位转 ChronoUnit 时间单位static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
java 启动脚本中增加 jvm 参数java -Duser.timezone=Asia/Shanghaijava -Duser.timezone=GMT+08
java -Duser.timezone=Asia/Shanghai
java -Duser.timezone=GMT+08
export TZ=Asia/Shanghai
Java 8 的日期和时间类包含 LocalDate, LocalTime, LocalDateTime, Instant, Duration 以及 Period,这些类都包含在 java.time 包中。
LocalDate
LocalTime
LocalDateTime
Instant
Duration
Period
Java 8新特性(四):新的时间和日期APIhttps://lw900925.github.io/java/java8-newtime-api.html
Java 的 java.util.Date 和 java.util.Calendar 类易用性差,不支持时区,而且他们都不是线程安全的;
用于格式化日期的类 DateFormat 被放在 java.text 包中,它是一个抽象类,所以我们需要实例化一个 SimpleDateFormat 对象来处理日期格式化,并且 DateFormat 也是非线程安全,这意味着如果你在多线程程序中调用同一个 DateFormat 对象,会得到意想不到的结果。
对日期的计算方式繁琐,而且容易出错,因为月份是从0开始的,从 Calendar 中获取的月份需要加一才能表示当前月份。
由于以上这些问题,出现了一些三方的日期处理框架,例如 Joda-Time,date4j 等开源项目。
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(); LocalTimeLocalTime 包含具体时间 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 LocalDateTimeLocalDateTime 类是 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毫秒 DurationUtilsorg.apache.commons.lang3.time.DurationUtils TimeUnit 时间单位转 ChronoUnit 时间单位static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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() 来获取当前日期:
也可以调用静态方法 now() 来获取当前日期:
LocalDate now = LocalDate.now(); LocalTimeLocalTime 包含具体时间 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 LocalDateTimeLocalDateTime 类是 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毫秒 DurationUtilsorg.apache.commons.lang3.time.DurationUtils TimeUnit 时间单位转 ChronoUnit 时间单位static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
LocalDate now = LocalDate.now();
LocalTimeLocalTime 包含具体时间 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 LocalDateTimeLocalDateTime 类是 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毫秒 DurationUtilsorg.apache.commons.lang3.time.DurationUtils TimeUnit 时间单位转 ChronoUnit 时间单位static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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 LocalDateTimeLocalDateTime 类是 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毫秒 DurationUtilsorg.apache.commons.lang3.time.DurationUtils TimeUnit 时间单位转 ChronoUnit 时间单位static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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
LocalDateTimeLocalDateTime 类是 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毫秒 DurationUtilsorg.apache.commons.lang3.time.DurationUtils TimeUnit 时间单位转 ChronoUnit 时间单位static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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毫秒 DurationUtilsorg.apache.commons.lang3.time.DurationUtils TimeUnit 时间单位转 ChronoUnit 时间单位static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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毫秒 DurationUtilsorg.apache.commons.lang3.time.DurationUtils TimeUnit 时间单位转 ChronoUnit 时间单位static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
Instant 用于表示一个时间戳,它与我们常使用的 System.currentTimeMillis() 有些类似,不过 Instant 可以精确到纳秒(Nano-Second),System.currentTimeMillis() 方法只精确到毫秒(Milli-Second)。Instant 除了使用 now() 方法创建外,还可以通过 ofEpochSecond 方法创建:
System.currentTimeMillis()
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毫秒 DurationUtilsorg.apache.commons.lang3.time.DurationUtils TimeUnit 时间单位转 ChronoUnit 时间单位static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
Instant instant = Instant.ofEpochSecond(120, 100000); ofEpochSecond() 方法的第一个参数为秒,第二个参数为纳秒,上面的代码表示从1970-01-01 00:00:00开始后两分钟的10万纳秒的时刻
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毫秒 DurationUtilsorg.apache.commons.lang3.time.DurationUtils TimeUnit 时间单位转 ChronoUnit 时间单位static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
// 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毫秒 DurationUtilsorg.apache.commons.lang3.time.DurationUtils TimeUnit 时间单位转 ChronoUnit 时间单位static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
// 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毫秒 DurationUtilsorg.apache.commons.lang3.time.DurationUtils TimeUnit 时间单位转 ChronoUnit 时间单位static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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毫秒 DurationUtilsorg.apache.commons.lang3.time.DurationUtils TimeUnit 时间单位转 ChronoUnit 时间单位static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
// 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毫秒 DurationUtilsorg.apache.commons.lang3.time.DurationUtils TimeUnit 时间单位转 ChronoUnit 时间单位static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
几种方法中,通过 java.sql.Timestamp 转换是最方便的
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毫秒 DurationUtilsorg.apache.commons.lang3.time.DurationUtils TimeUnit 时间单位转 ChronoUnit 时间单位static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
// 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毫秒 DurationUtilsorg.apache.commons.lang3.time.DurationUtils TimeUnit 时间单位转 ChronoUnit 时间单位static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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毫秒 DurationUtilsorg.apache.commons.lang3.time.DurationUtils TimeUnit 时间单位转 ChronoUnit 时间单位static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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毫秒 DurationUtilsorg.apache.commons.lang3.time.DurationUtils TimeUnit 时间单位转 ChronoUnit 时间单位static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
可以通过 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毫秒 DurationUtilsorg.apache.commons.lang3.time.DurationUtils TimeUnit 时间单位转 ChronoUnit 时间单位static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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 对象还可以通过 of() 方法创建,该方法接受一个时间段长度,和一个时间单位作为参数:
Duration duration1 = Duration.of(5, ChronoUnit.DAYS); // 5天 Duration duration2 = Duration.of(1000, ChronoUnit.MILLIS); // 1000毫秒 DurationUtilsorg.apache.commons.lang3.time.DurationUtils TimeUnit 时间单位转 ChronoUnit 时间单位static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
Duration duration1 = Duration.of(5, ChronoUnit.DAYS); // 5天 Duration duration2 = Duration.of(1000, ChronoUnit.MILLIS); // 1000毫秒
DurationUtilsorg.apache.commons.lang3.time.DurationUtils TimeUnit 时间单位转 ChronoUnit 时间单位static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
org.apache.commons.lang3.time.DurationUtils
static ChronoUnit toChronoUnit(final TimeUnit timeUnit); toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
static ChronoUnit toChronoUnit(final TimeUnit timeUnit);
toDuration() 通过 TimeUnit 时间单位构造 Durationpublic 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
@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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
// 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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
// 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 格式化为中文时间段描述
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 durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
@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秒
结果: 364天14小时20分45秒
Unit must not have an estimated durationDuration 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
Duration d1 = Duration.of(3, ChronoUnit.YEARS);抛异常java.time.temporal.UnsupportedTemporalTypeException: Unit must not have an estimated duration原因是 Duration 是基于时间(时分秒)的时间段,年/月范围太大不精确,无法直接使用 年/月 构造 Duration 对象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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
Period period = Period.of(2, 3, 6); Period对象也可以通过between()方法创建,值得注意的是,由于Period是以年月日衡量时间段,所以between()方法只能接收LocalDate类型的参数:
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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
// 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
新的时区类 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
ZoneId shanghaiZoneId = ZoneId.of("Asia/Shanghai"); ZoneId systemZoneId = ZoneId.systemDefault(); of() 方法接收一个“区域/城市”的字符串作为参数,你可以通过 getAvailableZoneIds() 方法获取所有合法的“区域/城市”字符串:
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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
Set<String> zoneIds = ZoneId.getAvailableZoneIds(); 有了ZoneId,我们就可以将一个LocalDate、LocalTime或LocalDateTime对象转化为ZonedDateTime对象:
有了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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
可以使用 with() 方法的另一个重载方法,它接收一个 TemporalAdjuster 参数,可以使我们更加灵活的调整日期:
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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
如果当前时间 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
@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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
遍历闭区间 [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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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点,然后再减一秒
with(LocalTime.MAX)
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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
@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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
@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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
新的日期 API 中提供了一个 DateTimeFormatter 类用于处理日期格式化操作,它被包含在 java.time.format 包中,Java 8 的日期类有一个 format() 方法用于将日期格式化为字符串,该方法接收一个 DateTimeFormatter 类型参数:
DateTimeFormatter
java.time.format
format()
// 格式化 @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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
// 格式化 @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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
时间戳秒: 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")); Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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"));
Datejava.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
java.util.Datepublic class Date extends Object implements Serializable, Cloneable, Comparable<Date>类 Date 表示特定的瞬间,精确到毫秒。从 JDK 1.1 开始,应该使用 Calendar 类实现日期和时间字段之间转换,使用 DateFormat 类来格式化和分析日期字符串。Date 中的把日期解释为年、月、日、小时、分钟和秒值的方法已废弃。
public class Date extends Object implements Serializable, Cloneable, Comparable<Date>
public Date()构造方法,分配 Date 对象并初始化此对象,以表示分配它的时间(精确到毫秒)。例如:Date date = new Date(); //以当前的日期和时间作为其初始值
public Date()
Date date = new Date(); //以当前的日期和时间作为其初始值
public Date(long date)分配 Date 对象并初始化此对象,以表示自从标准基准时间(称为“历元(epoch)”,即 1970 年 1 月 1 日 00:00:00 GMT)以来的指定毫秒数。参数:date - 自 1970 年 1 月 1 日 00:00:00 GMT 以来的毫秒数。
public Date(long date)
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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
方法一,用 SimpleDateFormat
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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd 00:00:00"); String s = sdf.format(new Date()); Date date = sdf.parse(s); 方法二,用 Calendar
方法二,用 Calendar
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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
//方法 一 System.currentTimeMillis(); //方法 二 Calendar.getInstance().getTimeInMillis(); //方法 三 new Date().getTime(); 其中 Calendar.getInstance().getTimeInMillis() 这种方式速度最慢,这是因为Canlendar要处理时区问题会耗费较多的时间。
其中 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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
其实 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 static native long currentTimeMillis();
javaTimeMillis()
解决方法:用一个守护线程专门来获取时间戳并存入内存缓存中
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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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()的效率没有影响程序整体的效率时,就不必忙着做优化
使用的时候,直接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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
注意System.currentTimeMillis()潜在的性能问题https://www.jianshu.com/p/d2039190b1cb
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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
java.sql.Timestamp 是 java.util.Date 的子类,所以 Timestamp 本身就是 Date
java.util.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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
public class Timestamp extends java.util.Date { Timestamp 可直接赋值给 DateDate date = new Timestamp(System.currentTimeMillis()) Date 通过毫秒时间戳转换为 TimestampTimestamp ts = new Timestamp(new Date().getTime())
Timestamp 可直接赋值给 DateDate date = new Timestamp(System.currentTimeMillis())
Date date = new Timestamp(System.currentTimeMillis())
Date 通过毫秒时间戳转换为 TimestampTimestamp ts = new Timestamp(new Date().getTime())
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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
问题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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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 的秒,丢失了毫秒,导致比较结果不一致。
原因是如果用 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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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 比较的情况,都是取的毫秒比较:
如果使用 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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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() 比较也有问题
问题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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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 的秒部分,丢失了毫秒。
原因也是 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() 直接比较毫秒时间戳 SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
public boolean before(Date when) { return getMillisOf(this) < getMillisOf(when); public boolean after(Date when) { return getMillisOf(this) > getMillisOf(when); 解决方法:Date 和 Timestamp 比较时,调用 getTime() 直接比较毫秒时间戳
解决方法:Date 和 Timestamp 比较时,调用 getTime() 直接比较毫秒时间戳
SimpleDateFormatjava.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。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转StringDate date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
java.text.SimpleDateFormatpublic class SimpleDateFormat extends DateFormatSimpleDateFormat 是一个以与语言环境相关的方式来格式化和分析日期的具体类。它允许进行格式化(日期 -> 文本)、分析(文本 -> 日期)和规范化。SimpleDateFormat 使得可以选择任何用户定义的日期-时间格式的模式。但是,仍然建议通过 DateFormat 中的 getTimeInstance、getDateInstance 或 getDateTimeInstance 来新的创建日期-时间格式化程序。
public class SimpleDateFormat extends DateFormat
public SimpleDateFormat(String pattern)用给定的模式和默认语言环境的日期格式符号构造 SimpleDateFormat。注:此构造方法可能不支持所有语言环境。要覆盖所有语言环境,请使用 DateFormat 类中的工厂方法。
public SimpleDateFormat(String pattern)
Date parse(String text, ParsePosition pos)解析字符串的文本,生成 Date。 此方法试图解析从 pos 给定的索引处开始的文本。
Date parse(String text, ParsePosition pos)
StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition pos)将给定的 Date 格式化为日期/时间字符串,并将结果添加到给定的 StringBuffer。格式为类 DateFormat 中的 format。返回格式化的日期-时间字符串。
StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition pos)
Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date); String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = sdf.format(date);
String转DateDate 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2018-12-25 19:01:12"); 注意 SimpleDateFormat.parse() 方法会抛出 ParseException 受检异常,必须捕获或抛出处理。
注意 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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
如下面代码中,将 SimpleDateFormat 声明为 static ,开多个线程并发去parse日期
static
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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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修饰
执行结果如下图,直接异常退出
原因:SimpleDateFormat 以及其实现的抽象类 DateFormat 都是使用了内部的成员变量 protected Calendar calendar; 来做转换,多线程情况下共享 SimpleDateFormat 对象会导致线程间数据互相影响,导致出错。
DateFormat
protected Calendar calendar;
阿里巴巴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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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对象锁
不过会 会频繁地创建和销毁对象,效率较低。
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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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线程间隔离
并发量大的时候会对性能有影响,线程阻塞
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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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
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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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?你的项目崩没? Calendarjava.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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
还在使用SimpleDateFormat?你的项目崩没?
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();
public abstract class Calendar extends Object implements Serializable, Cloneable, Comparable<Calendar>
Calendar rightNow = Calendar.getInstance();
int DATE,get 和 set 的字段数字,指示一个月中的某天。它与 DAY_OF_MONTH 是同义词。一个月中第一天的值为 1。
int DATE
DAY_OF_MONTH
public static Calendar getInstance()使用默认时区和语言环境获得一个日历对象。返回的 Calendar已初始化为当前日期和时间,使用了默认时区和默认语言环境。例如:Calendar rightNow = Calendar.getInstance();
public static Calendar getInstance()
public final void setTime(Date date)使用给定的 Date 设置此 Calendar 的时间。
public final void setTime(Date date)
public final Date getTime()返回一个表示此 Calendar 时间值(从历元至现在的毫秒偏移量)的 Date 对象。返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。
public final Date getTime()
int get(int field)返回给定日历字段的值。
int get(int field)
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);
public abstract void add(int field, int amount)
set(f, get(f) + delta)
add(Calendar.DAY_OF_MONTH, -5);
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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
输入和输出参数都是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); 时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天
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);
时区UTC世界协调时GMT格林威治标准时(GMT=UTC)CST中国标准时(UTC/GMT+8)日期时间格式日期时间格式表h大小写和m大小写Java日期中的T和ZSSSZ(SSS毫秒Z时区)Java 时区设置TimeZone.setDefault()-Duser.timezone=Asia/ShanghaiTZ环境变量Java8 新日期和时间 API为什么要引入新的日期API?LocalDateLocalTimeLocalDateTimeInstant 时间戳LocalDateTime 和 Timestamp 互相转换LocalDateTime 和 Date 互相转换LocalDateTime 和时间戳秒/毫秒互转Duration 时间段(秒)Duration 构造DurationUtilsTimeUnit 时间单位转 ChronoUnit 时间单位toDuration() 通过 TimeUnit 时间单位构造 DurationDuration 操作Duration 格式化Unit must not have an estimated durationPeriod 时间段(天)ZoneId 时区日期操作使用 TemporalAdjuster 调整时间1小时前按小时取整5分钟前按分取整四舍五入对齐到上/下一个5分钟整遍历时间间隔内的全部5分钟整获取昨天/今天起始时间和结束时间抹去秒DateTimeFormatter 日期时间格式化DateDate() 当前时间Date(long) 毫秒时间戳指定的时间获取当前日期+23:59:59对应的时间Date去掉时分秒获取当前毫秒时间戳注意System.currentTimeMillis()潜在的性能问题Unix时间戳和Date互相转换Timestamp 和 Date 互相转换Date 与 Timestamp compareTo()/before()/after() 比较问题SimpleDateFormat构造方法parse()format()Date转StringString转DateSimpleDateFormat与线程安全Calendar字段摘要getInstance()setTime()getTime()get()add()获取今天前n天的日期获取指定日期的下一天