万字博文教你搞懂java源马的日期和时间相关用法

由源码可知,最后是调用**parsedDate = calb.establish(calendar).getTime();**获取返回值。方法的参数是calendar,calendar可以被多个线程访问到,存在线程不安全问题。.getTime();**获取返回值。方法的参数是calendar,calendar可以被多个线程访问到,存在线程不安全问题。")

我们再来看看**calb.establish(calendar)**的源码**的源码")

image-20210805827464

calb.establish(calendar)方法先后调用了cal.clear()和cal.set(),先清理值,再设值。但是这两个操作并不是原子性的,也没有线程安全机制来保证,导致多线程并发时,可能会引起cal的值出现问题了。方法先后调用了cal.clear()和cal.set(),先清理值,再设值。但是这两个操作并不是原子性的,也没有线程安全机制来保证,导致多线程并发时,可能会引起cal的值出现问题了。")

验证SimpleDateFormat线程不安全

public class SimpleDateFormatDemoTest { private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”); public static void main(String[] args) { //1、创建线程池 ExecutorService pool = Executors.newFixedThreadPool(5); //2、为线程池分配任务 ThreadPoolTest threadPoolTest = new ThreadPoolTest(); for (int i = 0; i < 10; i++) { pool.submit(threadPoolTest); } //3、关闭线程池 pool.shutdown(); } static class ThreadPoolTest implements Runnable{ @Override public void run() { String dateString = simpleDateFormat.format(new Date()); try { Date parseDate = simpleDateFormat.parse(dateString); String dateString2 = simpleDateFormat.format(parseDate); System.out.println(Thread.currentThread().getName()+" 线程是否安全: “+dateString.equals(dateString2)); } catch (Exception e) { System.out.println(Thread.currentThread().getName()+” 格式化失败 "); } } } } 复制代码; public static void main(String[] args) { //1、创建线程池 ExecutorService pool = Executors.newFixedThreadPool(5); //2、为线程池分配任务 ThreadPoolTest threadPoolTest = new ThreadPoolTest(); for (int i = 0; i < 10; i++) { pool.submit(threadPoolTest); } //3、关闭线程池 pool.shutdown(); } static class ThreadPoolTest implements Runnable{ @Override public void run() { String dateString = simpleDateFormat.format(new Date()); try { Date parseDate = simpleDateFormat.parse(dateString); String dateString2 = simpleDateFormat.format(parseDate); System.out.println(Thread.currentThread().getName()+" 线程是否安全: “+dateString.equals(dateString2)); } catch (Exception e) { System.out.println(Thread.currentThread().getName()+” 格式化失败 “); } } } } 复制代码”)

image-20210805754416

出现了两次false,说明线程是不安全的。而且还抛异常,这个就严重了。

解决方案

这个是阿里巴巴 java开发手册中的规定:

img

1、不要定义为static变量,使用局部变量

2、加锁:synchronized锁和Lock锁

3、使用ThreadLocal方式

4、使用DateTimeFormatter代替SimpleDateFormat(DateTimeFormatter是线程安全的,java 8+支持)

5、使用FastDateFormat 替换SimpleDateFormat(FastDateFormat 是线程安全的,Apache Commons Lang包支持,不受限于java版本)

解决方案1:不要定义为static变量,使用局部变量

就是要使用SimpleDateFormat对象进行format或parse时,再定义为局部变量。就能保证线程安全。

public class SimpleDateFormatDemoTest1 { public static void main(String[] args) { //1、创建线程池 ExecutorService pool = Executors.newFixedThreadPool(5); //2、为线程池分配任务 ThreadPoolTest threadPoolTest = new ThreadPoolTest(); for (int i = 0; i < 10; i++) { pool.submit(threadPoolTest); } //3、关闭线程池 pool.shutdown(); } static class ThreadPoolTest implements Runnable{ @Override public void run() { SimpleDateFormat simpleDateFormat = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”); String dateString = simpleDateFormat.format(new Date()); try { Date parseDate = simpleDateFormat.parse(dateString); String dateString2 = simpleDateFormat.format(parseDate); System.out.println(Thread.currentThread().getName()+" 线程是否安全: “+dateString.equals(dateString2)); } catch (Exception e) { System.out.println(Thread.currentThread().getName()+” 格式化失败 "); } } } } 复制代码 { //1、创建线程池 ExecutorService pool = Executors.newFixedThreadPool(5); //2、为线程池分配任务 ThreadPoolTest threadPoolTest = new ThreadPoolTest(); for (int i = 0; i < 10; i++) { pool.submit(threadPoolTest); } //3、关闭线程池 pool.shutdown(); } static class ThreadPoolTest implements Runnable{ @Override public void run() { SimpleDateFormat simpleDateFormat = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”); String dateString = simpleDateFormat.format(new Date()); try { Date parseDate = simpleDateFormat.parse(dateString); String dateString2 = simpleDateFormat.format(parseDate); System.out.println(Thread.currentThread().getName()+" 线程是否安全: “+dateString.equals(dateString2)); } catch (Exception e) { System.out.println(Thread.currentThread().getName()+” 格式化失败 “); } } } } 复制代码”)

image-20210805936439

由图可知,已经保证了线程安全,但这种方案不建议在高并发场景下使用,因为会创建大量的SimpleDateFormat对象,影响性能。

解决方案2:加锁:synchronized锁和Lock锁

加synchronized锁: SimpleDateFormat对象还是定义为全局变量,然后需要调用SimpleDateFormat进行格式化时间时,再用synchronized保证线程安全。

public class SimpleDateFormatDemoTest2 { private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”); public static void main(String[] args) { //1、创建线程池 ExecutorService pool = Executors.newFixedThreadPool(5); //2、为线程池分配任务 ThreadPoolTest threadPoolTest = new ThreadPoolTest(); for (int i = 0; i < 10; i++) { pool.submit(threadPoolTest); } //3、关闭线程池 pool.shutdown(); } static class ThreadPoolTest implements Runnable{ @Override public void run() { try { synchronized (simpleDateFormat){ String dateString = simpleDateFormat.format(new Date()); Date parseDate = simpleDateFormat.parse(dateString); String dateString2 = simpleDateFormat.format(parseDate); System.out.println(Thread.currentThread().getName()+" 线程是否安全: “+dateString.equals(dateString2)); } } catch (Exception e) { System.out.println(Thread.currentThread().getName()+” 格式化失败 "); } } } } 复制代码; public static void main(String[] args) { //1、创建线程池 ExecutorService pool = Executors.newFixedThreadPool(5); //2、为线程池分配任务 ThreadPoolTest threadPoolTest = new ThreadPoolTest(); for (int i = 0; i < 10; i++) { pool.submit(threadPoolTest); } //3、关闭线程池 pool.shutdown(); } static class ThreadPoolTest implements Runnable{ @Override public void run() { try { synchronized (simpleDateFormat){ String dateString = simpleDateFormat.format(new Date()); Date parseDate = simpleDateFormat.parse(dateString); String dateString2 = simpleDateFormat.format(parseDate); System.out.println(Thread.currentThread().getName()+" 线程是否安全: “+dateString.equals(dateString2)); } } catch (Exception e) { System.out.println(Thread.currentThread().getName()+” 格式化失败 “); } } } } 复制代码”)

正在上传…重新上传取消​ 如图所示,线程是安全的。定义了全局变量SimpleDateFormat,减少了创建大量SimpleDateFormat对象的损耗。但是使用synchronized锁, 同一时刻只有一个线程能执行锁住的代码块,在高并发的情况下会影响性能。但这种方案不建议在高并发场景下使用 加Lock锁: 加Lock锁和synchronized锁原理是一样的,都是使用锁机制保证线程的安全。

public class SimpleDateFormatDemoTest3 { private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”); private static Lock lock = new ReentrantLock(); public static void main(String[] args) { //1、创建线程池 ExecutorService pool = Executors.newFixedThreadPool(5); //2、为线程池分配任务 ThreadPoolTest threadPoolTest = new ThreadPoolTest(); for (int i = 0; i < 10; i++) { pool.submit(threadPoolTest); } //3、关闭线程池 pool.shutdown(); } static class ThreadPoolTest implements Runnable{ @Override public void run() { try { lock.lock(); String dateString = simpleDateFormat.format(new Date()); Date parseDate = simpleDateFormat.parse(dateString); String dateString2 = simpleDateFormat.format(parseDate); System.out.println(Thread.currentThread().getName()+" 线程是否安全: “+dateString.equals(dateString2)); } catch (Exception e) { System.out.println(Thread.currentThread().getName()+” 格式化失败 "); }finally { lock.unlock(); } } } } 复制代码; private static Lock lock = new ReentrantLock(); public static void main(String[] args) { //1、创建线程池 ExecutorService pool = Executors.newFixedThreadPool(5); //2、为线程池分配任务 ThreadPoolTest threadPoolTest = new ThreadPoolTest(); for (int i = 0; i < 10; i++) { pool.submit(threadPoolTest); } //3、关闭线程池 pool.shutdown(); } static class ThreadPoolTest implements Runnable{ @Override public void run() { try { lock.lock(); String dateString = simpleDateFormat.format(new Date()); Date parseDate = simpleDateFormat.parse(dateString); String dateString2 = simpleDateFormat.format(parseDate); System.out.println(Thread.currentThread().getName()+" 线程是否安全: “+dateString.equals(dateString2)); } catch (Exception e) { System.out.println(Thread.currentThread().getName()+” 格式化失败 “); }finally { lock.unlock(); } } } } 复制代码”)

正在上传…重新上传取消​ 由结果可知,加Lock锁也能保证线程安全。要注意的是,最后一定要释放锁,代码里在finally里增加了lock.unlock();,保证释放锁。 在高并发的情况下会影响性能。这种方案不建议在高并发场景下使用;,保证释放锁。 在高并发的情况下会影响性能。这种方案不建议在高并发场景下使用")

解决方案3:使用ThreadLocal方式

使用ThreadLocal保证每一个线程有SimpleDateFormat对象副本。这样就能保证线程的安全。

public class SimpleDateFormatDemoTest4 { private static ThreadLocal threadLocal = new ThreadLocal(){ @Override protected DateFormat initialValue() { return new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”); } }; public static void main(String[] args) { //1、创建线程池 ExecutorService pool = Executors.newFixedThreadPool(5); //2、为线程池分配任务 ThreadPoolTest threadPoolTest = new ThreadPoolTest(); for (int i = 0; i < 10; i++) { pool.submit(threadPoolTest); } //3、关闭线程池 pool.shutdown(); } static class ThreadPoolTest implements Runnable{ @Override public void run() { try { String dateString = threadLocal.get().format(new Date()); Date parseDate = threadLocal.get().parse(dateString); String dateString2 = threadLocal.get().format(parseDate); System.out.println(Thread.currentThread().getName()+" 线程是否安全: “+dateString.equals(dateString2)); } catch (Exception e) { System.out.println(Thread.currentThread().getName()+” 格式化失败 "); }finally { //避免内存泄漏,使用完threadLocal后要调用remove方法清除数据 threadLocal.remove(); } } } } 复制代码{ @Override protected DateFormat initialValue() { return new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”); } }; public static void main(String[] args) { //1、创建线程池 ExecutorService pool = Executors.newFixedThreadPool(5); //2、为线程池分配任务 ThreadPoolTest threadPoolTest = new ThreadPoolTest(); for (int i = 0; i < 10; i++) { pool.submit(threadPoolTest); } //3、关闭线程池 pool.shutdown(); } static class ThreadPoolTest implements Runnable{ @Override public void run() { try { String dateString = threadLocal.get().format(new Date()); Date parseDate = threadLocal.get().parse(dateString); String dateString2 = threadLocal.get().format(parseDate); System.out.println(Thread.currentThread().getName()+" 线程是否安全: “+dateString.equals(dateString2)); } catch (Exception e) { System.out.println(Thread.currentThread().getName()+” 格式化失败 “); }finally { //避免内存泄漏,使用完threadLocal后要调用remove方法清除数据 threadLocal.remove(); } } } } 复制代码”)

image-202108059729

使用ThreadLocal能保证线程安全,且效率也是挺高的。适合高并发场景使用。

解决方案4:使用DateTimeFormatter代替SimpleDateFormat

使用DateTimeFormatter代替SimpleDateFormat(DateTimeFormatter是线程安全的,java 8+支持) DateTimeFormatter介绍

public class DateTimeFormatterDemoTest5 {

private static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(“yyyy-MM-dd HH:mm:ss”);

public static void main(String[] args) {

//1、创建线程池

ExecutorService pool = Executors.newFixedThreadPool(5);

//2、为线程池分配任务

ThreadPoolTest threadPoolTest = new ThreadPoolTest();

for (int i = 0; i < 10; i++) {

pool.submit(threadPoolTest);

}

//3、关闭线程池

pool.shutdown();

}

static class ThreadPoolTest implements Runnable{

@Override

public void run() {

try {

String dateString = dateTimeFormatter.format(LocalDateTime.now());

TemporalAccessor temporalAccessor = dateTimeFormatter.parse(dateString);

String dateString2 = dateTimeFormatter.format(temporalAccessor);

System.out.println(Thread.currentThread().getName()+" 线程是否安全: "+dateString.equals(dateString2));

} catch (Exception e) {

e.printStackTrace();

System.out.println(Thread.currentThread().getName()+" 格式化失败 ");

}

}

}

}

复制代码

image-2021080591443373

使用DateTimeFormatter能保证线程安全,且效率也是挺高的。适合高并发场景使用

解决方案5:使用FastDateFormat 替换SimpleDateFormat

使用FastDateFormat 替换SimpleDateFormat(FastDateFormat 是线程安全的,Apache Commons Lang包支持,不受限于java版本)

public class FastDateFormatDemo6 {

private static FastDateFormat fastDateFormat = FastDateFormat.getInstance(“yyyy-MM-dd HH:mm:ss”);

public static void main(String[] args) {

//1、创建线程池

ExecutorService pool = Executors.newFixedThreadPool(5);

//2、为线程池分配任务

ThreadPoolTest threadPoolTest = new ThreadPoolTest();

for (int i = 0; i < 10; i++) {

pool.submit(threadPoolTest);

}

//3、关闭线程池

pool.shutdown();

}

static class ThreadPoolTest implements Runnable{

@Override

public void run() {

try {

String dateString = fastDateFormat.format(new Date());

Date parseDate = fastDateFormat.parse(dateString);

String dateString2 = fastDateFormat.format(parseDate);

System.out.println(Thread.currentThread().getName()+" 线程是否安全: "+dateString.equals(dateString2));

} catch (Exception e) {

e.printStackTrace();

System.out.println(Thread.currentThread().getName()+" 格式化失败 ");

}

}

}

}

复制代码

使用FastDateFormat能保证线程安全,且效率也是挺高的。适合高并发场景使用

FastDateFormat源码分析

Apache Commons Lang 3.5

复制代码

//FastDateFormat

@Override

public String format(final Date date) {

return printer.format(date);

}

@Override

public String format(final Date date) {

final Calendar c = Calendar.getInstance(timeZone, locale);

c.setTime(date);

return applyRulesToString©;

}

复制代码

源码中 Calender 是在 format 方法里创建的,肯定不会出现 setTime 的线程安全问题。这样线程安全疑惑解决了。那还有性能问题要考虑?

我们来看下FastDateFormat是怎么获取的

FastDateFormat.getInstance();

FastDateFormat.getInstance(CHINESE_DATE_TIME_PATTERN);

复制代码

看下对应的源码

/**

  • 获得 FastDateFormat实例,使用默认格式和地区

  • @return FastDateFormat

*/

public static FastDateFormat getInstance() {

return CACHE.getInstance();

}

/**

  • 获得 FastDateFormat 实例,使用默认地区

  • 支持缓存

  • @param pattern 使用{@link java.text.SimpleDateFormat} 相同的日期格式

  • @return FastDateFormat

  • @throws IllegalArgumentException 日期格式问题

*/

public static FastDateFormat getInstance(final String pattern) {

return CACHE.getInstance(pattern, null, null);

}

复制代码

这里有用到一个CACHE,看来用了缓存,往下看

private static final FormatCache CACHE = new FormatCache(){

@Override

protected FastDateFormat createInstance(final String pattern, final TimeZone timeZone, final Locale locale) {

return new FastDateFormat(pattern, timeZone, locale);

}

};

//

abstract class FormatCache {

private final ConcurrentMap<Tuple, F> cInstanceCache = new ConcurrentHashMap<>(7);

private static final ConcurrentMap<Tuple, String> C_DATE_TIME_INSTANCE_CACHE = new ConcurrentHashMap<>(7);

}

复制代码

image-20210728914309

在getInstance 方法中加了ConcurrentMap 做缓存,提高了性能。且我们知道ConcurrentMap 也是线程安全的。

实践

/**

  • 年月格式 {@link FastDateFormat}:yyyy-MM

*/

public static final FastDateFormat NORM_MONTH_FORMAT = FastDateFormat.getInstance(NORM_MONTH_PATTERN);

复制代码

image-2021072895013629

//FastDateFormat

public static FastDateFormat getInstance(final String pattern) {

return CACHE.getInstance(pattern, null, null);

}

复制代码

image-20210728205104833

image-2021072895259113

如图可证,是使用了ConcurrentMap 做缓存。且key值是格式,时区和locale(语境)三者都相同为相同的key。

问题

1、tostring()输出时,总以系统的默认时区格式输出,不友好。

2、时区不能转换

3、日期和时间的计算不简便,例如计算加减,比较两个日期差几天等。

4、格式化日期和时间的SimpleDateFormat对象是线程不安全的

5、Date对象本身也是线程不安全的

public class Date

implements java.io.Serializable, Cloneable, Comparable

{

}

复制代码

二:Calendar

==========

支持版本及以上


JDK1.1

介绍

Calendar类说明

Calendar类提供了获取或设置各种日历字段的各种方法,比Date类多了一个可以计算日期和时间的功能。

Calendar常用的用法

// 获取当前时间:

Calendar c = Calendar.getInstance();

int y = c.get(Calendar.YEAR);

int m = 1 + c.get(Calendar.MONTH);

int d = c.get(Calendar.DAY_OF_MONTH);

int w = c.get(Calendar.DAY_OF_WEEK);

int hh = c.get(Calendar.HOUR_OF_DAY);

int mm = c.get(Calendar.MINUTE);

int ss = c.get(Calendar.SECOND);

int ms = c.get(Calendar.MILLISECOND);

System.out.println(“返回的星期:”+w);

System.out.println(y + “-” + m + “-” + d + " " + " " + hh + “:” + mm + “:” + ss + “.” + ms);

复制代码

image-202107141981024

如上图所示,月份计算时,要+1;返回的星期是从周日开始计算,周日为1,1~7表示星期;

Calendar的跨年问题和解决方案

问题

背景:在使用Calendar 的api getWeekYear()读取年份,在跨年那周的时候,程序获取的年份可能不是我们想要的,例如在2019年30号时,要返回2019,结果是返回2020,是不是有毒

// 获取当前时间:

Calendar c = Calendar.getInstance();

c.clear();

String str = “2019-12-30”;

try {

c.setTime(new SimpleDateFormat(“yyyy-MM-dd”).parse(str));

int y = c.getWeekYear();

System.out.println(y);

} catch (ParseException e) {

e.printStackTrace();

}

复制代码

image-202107148203567

分析原因

老规矩,从源码入手

Calendar类


//@since 1.7

public int getWeekYear() {

throw new UnsupportedOperationException();

}

复制代码

这个源码有点奇怪,getWeekYear()方法是java 7引入的。它的实现怎么是抛出异常,但是执行时,又有结果返回。

断点跟进,通过Calendar.getInstance()获取的Calendar实例是GregorianCalendar

image-202107148065

GregorianCalendar


public int getWeekYear() {

int year = get(YEAR); // implicitly calls complete()

if (internalGetEra() == BCE) {

year = 1 - year;

}

// Fast path for the Gregorian calendar years that are never

// affected by the Julian-Gregorian transition

if (year > gregorianCutoverYear + 1) {

int weekOfYear = internalGet(WEEK_OF_YEAR);

if (internalGet(MONTH) == JANUARY) {

if (weekOfYear >= 52) {

–year;

}

} else {

if (weekOfYear == 1) {

++year;

}

}

return year;

}

}

复制代码

方法内获取的年份刚开始是正常的

image-20210714707548

image-20210714847423

在JDK中会把前一年末尾的几天判定为下一年的第一周,因此上面程序的结果是1

解决方案

使用Calendar类 get(Calendar.YEAR)获取年份

问题

1、读取月份时,要+1

2、返回的星期是从周日开始计算,周日为1,1~7表示星期

3、Calendar的跨年问题,获取年份要用c.get(Calendar.YEAR),不要用c.getWeekYear();

4、获取指定时间是一年中的第几周时,调用cl.get(Calendar.WEEK_OF_YEAR),要注意跨年问题,跨年的那一周,获取的值为1。离跨年最近的那周为52。

三:LocalDateTime

===============

支持版本及以上


jdk8

介绍

LocalDateTime类说明

表示当前日期时间,相当于:yyyy-MM-ddTHH:mm:ss

LocalDateTime常用的用法

获取当前日期和时间

LocalDate d = LocalDate.now(); // 当前日期

LocalTime t = LocalTime.now(); // 当前时间

LocalDateTime dt = LocalDateTime.now(); // 当前日期和时间

System.out.println(d); // 严格按照ISO 8601格式打印

System.out.println(t); // 严格按照ISO 8601格式打印

System.out.println(dt); // 严格按照ISO 8601格式打印

复制代码

image-20210714857780

由运行结果可行,本地日期时间通过now()获取到的总是以当前默认时区返回的

获取指定日期和时间

LocalDate d2 = LocalDate.of(2021, 07, 14); // 2021-07-14, 注意07=07月

LocalTime t2 = LocalTime.of(13, 14, 20); // 13:14:20

LocalDateTime dt2 = LocalDateTime.of(2021, 07, 14, 13, 14, 20);

LocalDateTime dt3 = LocalDateTime.of(d2, t2);

System.out.println(“指定日期时间:”+dt2);

System.out.println(“指定日期时间:”+dt3);

复制代码

image-20210714803165

日期时间的加减法及修改

LocalDateTime currentTime = LocalDateTime.now(); // 当前日期和时间

System.out.println(“------------------时间的加减法及修改-----------------------”);

//3.LocalDateTime的加减法包含了LocalDate和LocalTime的所有加减,上面说过,这里就只做简单介绍

System.out.println(“3.当前时间:” + currentTime);

System.out.println(“3.当前时间加5年:” + currentTime.plusYears(5));

System.out.println(“3.当前时间加2个月:” + currentTime.plusMonths(2));

System.out.println(“3.当前时间减2天:” + currentTime.minusDays(2));

System.out.println(“3.当前时间减5个小时:” + currentTime.minusHours(5));

System.out.println(“3.当前时间加5分钟:” + currentTime.plusMinutes(5));

System.out.println(“3.当前时间加20秒:” + currentTime.plusSeconds(20));

//还可以灵活运用比如:向后加一年,向前减一天,向后加2个小时,向前减5分钟,可以进行连写

System.out.println(“3.同时修改(向后加一年,向前减一天,向后加2个小时,向前减5分钟):” + currentTime.plusYears(1).minusDays(1).plusHours(2).minusMinutes(5));

System.out.println(“3.修改年为2025年:” + currentTime.withYear(2025));

System.out.println(“3.修改月为12月:” + currentTime.withMonth(12));

System.out.println(“3.修改日为27日:” + currentTime.withDayOfMonth(27));

System.out.println(“3.修改小时为12:” + currentTime.withHour(12));

System.out.println(“3.修改分钟为12:” + currentTime.withMinute(12));

System.out.println(“3.修改秒为12:” + currentTime.withSecond(12));

复制代码

image-20210714941902

LocalDateTime和Date相互转化


Date转LocalDateTime

System.out.println(“------------------方法一:分步写-----------------------”);

//实例化一个时间对象

Date date = new Date();

//返回表示时间轴上同一点的瞬间作为日期对象

Instant instant = date.toInstant();

//获取系统默认时区

ZoneId zoneId = ZoneId.systemDefault();

//根据时区获取带时区的日期和时间

ZonedDateTime zonedDateTime = instant.atZone(zoneId);

//转化为LocalDateTime

LocalDateTime localDateTime = zonedDateTime.toLocalDateTime();

System.out.println("方法一:原Date = " + date);

System.out.println("方法一:转化后的LocalDateTime = " + localDateTime);

System.out.println(“------------------方法二:一步到位(推荐使用)-----------------------”);

//实例化一个时间对象

Date todayDate = new Date();

//Instant.ofEpochMilli(long l)使用1970-01-01T00:00:00Z的纪元中的毫秒来获取Instant的实例

LocalDateTime ldt = Instant.ofEpochMilli(todayDate.getTime()).atZone(ZoneId.systemDefault()).toLocalDateTime();

System.out.println("方法二:原Date = " + todayDate);

System.out.println("方法二:转化后的LocalDateTime = " + ldt);

复制代码

image-20210714210839339

LocalDateTime转Date

System.out.println(“------------------方法一:分步写-----------------------”);

//获取LocalDateTime对象,当前时间

LocalDateTime localDateTime = LocalDateTime.now();

//获取系统默认时区

ZoneId zoneId = ZoneId.systemDefault();

//根据时区获取带时区的日期和时间

ZonedDateTime zonedDateTime = localDateTime.atZone(zoneId);

//返回表示时间轴上同一点的瞬间作为日期对象

Instant instant = zonedDateTime.toInstant();

//转化为Date

Date date = Date.from(instant);

System.out.println("方法一:原LocalDateTime = " + localDateTime);

System.out.println("方法一:转化后的Date = " + date);

System.out.println(“------------------方法二:一步到位(推荐使用)-----------------------”);

//实例化一个LocalDateTime对象

LocalDateTime now = LocalDateTime.now();

//转化为date

Date dateResult = Date.from(now.atZone(ZoneId.systemDefault()).toInstant());

System.out.println("方法二:原LocalDateTime = " + now);

System.out.println("方法二:转化后的Date = " + dateResult);

复制代码

image-20210714211035080

线程安全


网上大家都在说JAVA 8提供的LocalDateTime是线程安全的,但是它是如何实现的呢

今天让我们来挖一挖

public final class LocalDateTime

implements Temporal, TemporalAdjuster, ChronoLocalDateTime, Serializable {

}

复制代码

由上面的源码可知,LocalDateTime是不可变类。我们都知道一个Java并发编程规则:不可变对象永远是线程安全的。

对比下Date的源码 ,Date是可变类,所以是线程不安全的。

public class Date

implements java.io.Serializable, Cloneable, Comparable

{

}

复制代码

四:ZonedDateTime

===============

支持版本及以上


jdk8

介绍

ZonedDateTime类说明

表示一个带时区的日期和时间,ZonedDateTime可以理解为LocalDateTime+ZoneId

从源码可以看出来,ZonedDateTime类中定义了LocalDateTime和ZoneId两个变量。

且ZonedDateTime类也是不可变类且是线程安全的。

public final class ZonedDateTime

implements Temporal, ChronoZonedDateTime, Serializable {

/**

  • Serialization version.

*/

private static final long serialVersionUID = -6260982410461394882L;

/**

  • The local date-time.

*/

private final LocalDateTime dateTime;

/**

  • The time-zone.

*/

private final ZoneId zone;

}

复制代码

ZonedDateTime常用的用法

获取当前时间+带时区+时区转换

// 默认时区获取当前时间

ZonedDateTime zonedDateTime = ZonedDateTime.now();

// 用指定时区获取当前时间,Asia/Shanghai为上海时区

ZonedDateTime zonedDateTime1 = ZonedDateTime.now(ZoneId.of(“Asia/Shanghai”));

//withZoneSameInstant为转换时区,参数为ZoneId

ZonedDateTime zonedDateTime2 = zonedDateTime.withZoneSameInstant(ZoneId.of(“America/New_York”));

System.out.println(zonedDateTime);

System.out.println(zonedDateTime1);

System.out.println(zonedDateTime2);

复制代码

image-202107205246938

LocalDateTime+ZoneId变ZonedDateTime

LocalDateTime localDateTime = LocalDateTime.now();

ZonedDateTime zonedDateTime1 = localDateTime.atZone(ZoneId.systemDefault());

ZonedDateTime zonedDateTime2 = localDateTime.atZone(ZoneId.of(“America/New_York”));

System.out.println(zonedDateTime1);

System.out.println(zonedDateTime2);

复制代码

image-2021072094003

上面的例子说明了,LocalDateTime是可以转成ZonedDateTime的。

五:DateTimeFormatter

=====================================================================================

支持版本及以上


jdk8

介绍


DateTimeFormatter类说明

DateTimeFormatter的作用是进行格式化显示,且DateTimeFormatter是不可变类且是线程安全的。

public final class DateTimeFormatter { … } 复制代码

说到时间的格式化显示,就要说老朋友SimpleDateFormat了,之前格式化Date就要用上。但是我们知道SimpleDateFormat是线程不安全的,还不清楚的,请看这里–>

DateTimeFormatter常用的用法

ZonedDateTime zonedDateTime = ZonedDateTime.now();

DateTimeFormatter formatter = DateTimeFormatter.ofPattern(“yyyy-MM-dd’T’HH:mm ZZZZ”);

System.out.println(formatter.format(zonedDateTime));

DateTimeFormatter usFormatter = DateTimeFormatter.ofPattern(“E, MMMM/dd/yyyy HH:mm”, Locale.US);

System.out.println(usFormatter.format(zonedDateTime));

DateTimeFormatter chinaFormatter = DateTimeFormatter.ofPattern(“yyyy MMM dd EE HH:mm”, Locale.CHINA);

System.out.println(chinaFormatter.format(zonedDateTime));

复制代码

image-202107209416958

DateTimeFormatter的坑


1、在正常配置按照标准格式的字符串日期,是能够正常转换的。如果月,日,时,分,秒在不足两位的情况需要补0,否则的话会转换失败,抛出异常。

DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(“yyyy-MM-dd HH:mm:ss.SSS”);

LocalDateTime dt1 = LocalDateTime.parse(“2021-7-20 23:46:43.946”, DATE_TIME_FORMATTER);

System.out.println(dt1);

复制代码

会报错:

image-202107208183

java.time.format.DateTimeParseException: Text ‘2021-7-20 23:46:43.946’ could not be parsed at index 5

复制代码

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

码字不易,觉得有帮助的可以帮忙点个赞,让更多有需要的人看到

又是一年求职季,在这里,我为各位准备了一套Java程序员精选高频面试笔试真题,来帮助大家攻下BAT的offer,题目范围从初级的Java基础到高级的分布式架构等等一系列的面试题和答案,用于给大家作为参考

以下是部分内容截图
架构面试专题及架构学习笔记导图.png
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
imeFormatter.ofPattern(“yyyy-MM-dd HH:mm:ss.SSS”);

LocalDateTime dt1 = LocalDateTime.parse(“2021-7-20 23:46:43.946”, DATE_TIME_FORMATTER);

System.out.println(dt1);

复制代码

会报错:

image-202107208183

java.time.format.DateTimeParseException: Text ‘2021-7-20 23:46:43.946’ could not be parsed at index 5

复制代码

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-Cid735c1-1713440882710)]

[外链图片转存中…(img-hZpYKMaE-1713440882710)]

[外链图片转存中…(img-6SsJ8ZDT-1713440882710)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

码字不易,觉得有帮助的可以帮忙点个赞,让更多有需要的人看到

又是一年求职季,在这里,我为各位准备了一套Java程序员精选高频面试笔试真题,来帮助大家攻下BAT的offer,题目范围从初级的Java基础到高级的分布式架构等等一系列的面试题和答案,用于给大家作为参考

以下是部分内容截图
[外链图片转存中…(img-iSdmOIGx-1713440882711)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 13
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值