DateFormat 類是一個非線程安全的類。javadocs 文檔里面提到:"Date formats是不能同步的。 我們建議為每個線程創建獨立的日期格式。 如果多個線程同時訪問一個日期格式,這需要在外部加上同步代碼塊。"
如何並發使用DateFormat類?
1. 同步
最簡單的方法就是在做日期轉換之前,為DateFormat對象加鎖。這種方法使得一次只能讓一個線程訪問DateFormat對象,而其他線程只能等待。
public class DateUtil { private static final String MESSAGE_FORMAT = "MM-dd HH:mm:ss.ms"; private static final SimpleDateFormat format=new SimpleDateFormat(MESSAGE_FORMAT, Locale.getDefault()); public static final DateFormat getDateFormat() { return format; } public Date parse(String str) { try { synchronized(format){ return format.parse(str); } } catch (ParseException e) { } return null; } }
這樣沒問題,但是加了鎖,效率嚴重下降。
2.使用ThreadLocal的匿名內部類寫法,推薦此方法
另外一個方法就是使用ThreadLocal變量去容納DateFormat對象,也就是說每個線程都有一個屬於自己的副本,並無需等待其他線程去釋放它。這種方法會比使用同步塊更高效。
public class DateUtil1 {
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyyMMdd");
}
};
public static Date convert(String source) throws ParseException {
Date d = df.get().parse(source);
return d;
}
}
阿里巴巴操作手冊關於SimpleDateFormat的描述如下:
【強制】 SimpleDateFormat 是線程不安全的類,一般不要定義為 static 變量,如果定義為static ,必須加鎖,或者使用 DateUtils 工具類。
正例:注意線程安全,使用 DateUtils 。亦推薦如下處理:
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() { @ Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd"); } };
說明:如果是 JDK 8 的應用,可以使用 Instant 代替 Date , LocalDateTime 代替 Calendar ,DateTimeFormatter 代替 SimpleDateFormat ,官方給出的解釋: simple beautiful strong immutable thread - safe 。
Java 8 已經普遍使用了,可是還在有人用 Java Calendar 處理時間和日期,不僅僅性能差,很切代碼很冗余,就不能用 Java 8 提供的新 API 嗎?所以 CTO 強制了,必須用 Java 8 處理日期,否則一律開除。下面是整理的 18 種處理日期的方式,可以收藏起來,一定有用。
Java處理日期、日歷和時間的方式一直為社區所詬病,將 java.util.Date設定為可變類型,以及SimpleDateFormat的非線程安全使其應用非常受限。
新API基於ISO標准日歷系統,java.time包下的所有類都是不可變類型而且線程安全。
示例1:Java 8中獲取今天的日期
Java 8 中的 LocalDate 用於表示當天日期。和java.util.Date不同,它只有日期,不包含時間。當你僅需要表示日期時就用這個類。
import java.time.LocalDate; public class Demo01 { public static void main(String[] args) { LocalDate today = LocalDate.now(); System.out.println("今天的日期:"+today); } }
結果為:今天的日期:2022-08-24
示例2:Java 8中獲取年、月、日信息
import java.time.LocalDate; public class Demo02 { public static void main(String[] args) { LocalDate today = LocalDate.now(); int year = today.getYear(); int month = today.getMonthValue(); int day = today.getDayOfMonth(); System.out.println("year:"+year); System.out.println("month:"+month); System.out.println("day:"+day); } }
結果如下:
year:2022 month:8 day:24
示例3:Java 8中處理特定日期
我們通過靜態工廠方法now()非常容易地創建了當天日期,你還可以調用另一個有用的工廠方法LocalDate.of()創建任意日期, 該方法需要傳入年、月、日做參數,返回對應的LocalDate實例。這個方法的好處是沒再犯老API的設計錯誤,比如年度起始於1900,月份是從0開 始等等。
import java.time.LocalDate; public class Demo03 { public static void main(String[] args) { LocalDate date = LocalDate.of(2018,2,6); System.out.println("自定義日期:"+date); } }
結果:自定義日期:2018-02-06
示例4:Java 8中判斷兩個日期是否相等
import java.time.LocalDate; public class Demo04 { public static void main(String[] args) { LocalDate date1 = LocalDate.now(); LocalDate date2 = LocalDate.of(2018,2,5); if(date1.equals(date2)){ System.out.println("時間相等"); }else{ System.out.println("時間不等"); } } }
結果:時間不等
示例5:Java 8中檢查像生日這種周期性事件
import java.time.LocalDate; import java.time.MonthDay; public class Demo05 { public static void main(String[] args) { LocalDate date1 = LocalDate.now(); LocalDate date2 = LocalDate.of(2018,2,6); MonthDay birthday = MonthDay.of(date2.getMonth(),date2.getDayOfMonth()); MonthDay currentMonthDay = MonthDay.from(date1); System.out.println(birthday); System.out.println(currentMonthDay); if(currentMonthDay.equals(birthday)){ System.out.println("是你的生日"); }else{ System.out.println("你的生日還沒有到"); } } }
結果如下:
--02-06 --08-24 你的生日還沒有到
只要當天的日期和生日匹配,無論是哪一年都會打印出祝賀信息。你可以把程序整合進系統時鍾,看看生日時是否會受到提醒,或者寫一個單元測試來檢測代碼是否運行正確。
示例6:Java 8中獲取當前時間
import java.time.LocalTime; public class Demo06 { public static void main(String[] args) { LocalTime time = LocalTime.now(); System.out.println("獲取當前的時間,不含有日期:"+time); } }
結果如下:獲取當前的時間,不含有日期:11:24:20.506
可以看到當前時間就只包含時間信息,沒有日期
示例7:Java 8中獲取當前時間
通過增加小時、分、秒來計算將來的時間很常見。Java 8除了不變類型和線程安全的好處之外,還提供了更好的plusHours()方法替換add(),並且是兼容的。注意,這些方法返回一個全新的LocalTime實例,由於其不可變性,返回后一定要用變量賦值。
import java.time.LocalTime; public class Demo07 { public static void main(String[] args) { LocalTime time = LocalTime.now(); LocalTime newTime = time.plusHours(3); System.out.println("三個小時后的時間為:"+newTime); } }
結果:三個小時后的時間為:14:26:33.649
示例8:Java 8如何計算一周后的日期
和上個例子計算3小時以后的時間類似,這個例子會計算一周后的日期。LocalDate日期不包含時間信息,它的plus()方法用來增加天、周、月,ChronoUnit類聲明了這些時間單位。由於LocalDate也是不變類型,返回后一定要用變量賦值。
import java.time.LocalDate; import java.time.temporal.ChronoUnit; public class Demo08 { public static void main(String[] args) { LocalDate today = LocalDate.now(); System.out.println("今天的日期為:"+today); LocalDate nextWeek = today.plus(1, ChronoUnit.WEEKS); System.out.println("一周后的日期為:"+nextWeek); } }
結果如下:
今天的日期為:2022-08-24 一周后的日期為:2022-08-31
可以看到新日期離當天日期是7天,也就是一周。你可以用同樣的方法增加1個月、1年、1小時、1分鍾甚至一個世紀,更多選項可以查看Java 8 API中的ChronoUnit類
示例9:Java 8計算一年前或一年后的日期
利用minus()方法計算一年前的日期
import java.time.LocalDate; import java.time.temporal.ChronoUnit; public class Demo09 { public static void main(String[] args) { LocalDate today = LocalDate.now(); LocalDate previousYear = today.minus(1, ChronoUnit.YEARS); System.out.println("一年前的日期 : " + previousYear); LocalDate nextYear = today.plus(1, ChronoUnit.YEARS); System.out.println("一年后的日期:"+nextYear); } }
結果如下:
一年前的日期 : 2021-08-24 一年后的日期:2023-08-24
示例10:Java 8的Clock時鍾類
Java 8增加了一個Clock時鍾類用於獲取當時的時間戳,或當前時區下的日期時間信息。以前用到System.currentTimeInMillis()和TimeZone.getDefault()的地方都可用Clock替換。
import java.time.Clock; public class Demo10 { public static void main(String[] args) { // Returns the current time based on your system clock and set to UTC. Clock clock = Clock.systemUTC(); System.out.println("Clock : " + clock.millis()); // Returns time based on system clock zone Clock defaultClock = Clock.systemDefaultZone(); System.out.println("Clock : " + defaultClock.millis()); } }
結果如下:
Clock : 1661311871963 Clock : 1661311872026
示例11:如何用Java判斷日期是早於還是晚於另一個日期
另一個工作中常見的操作就是如何判斷給定的一個日期是大於某天還是小於某天?在Java 8中,LocalDate類有兩類方法isBefore()和isAfter()用於比較日期。調用isBefore()方法時,如果給定日期小於當前日期則返回true。
import java.time.LocalDate; import java.time.temporal.ChronoUnit; public class Demo11 { public static void main(String[] args) { LocalDate today = LocalDate.now(); LocalDate tomorrow = LocalDate.of(2022,8,24); if(tomorrow.isAfter(today)){ System.out.println("之后的日期:"+tomorrow); } LocalDate yesterday = today.minus(1, ChronoUnit.DAYS); if(yesterday.isBefore(today)){ System.out.println("之前的日期:"+yesterday); } } }
結果如下:
之后的日期:2022-08-25 之前的日期:2022-08-23
示例12:Java 8中處理時區
Java 8不僅分離了日期和時間,也把時區分離出來了。現在有一系列單獨的類如ZoneId來處理特定時區,ZoneDateTime類來表示某時區下的時間。這在Java 8以前都是 GregorianCalendar類來做的。下面這個例子展示了如何把本時區的時間轉換成另一個時區的時間。
import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; public class Demo12 { public static void main(String[] args) { // Date and time with timezone in Java 8 ZoneId america = ZoneId.of("America/New_York"); LocalDateTime localtDateAndTime = LocalDateTime.now(); System.out.println(localtDateAndTime); ZonedDateTime dateAndTimeInNewYork = ZonedDateTime.of(localtDateAndTime, america ); System.out.println("Current date and time in a particular timezone : " + dateAndTimeInNewYork); } }
結果如下:
2022-08-24T11:44:09.609 2022-08-24T11:44:09.609-04:00[America/New_York] Current date and time in a particular timezone : 2022-08-24T11:44:09.609-04:00[America/New_York]
示例13:如何表示信用卡到期這類固定日期,答案就在YearMonth
與 MonthDay檢查重復事件的例子相似,YearMonth是另一個組合類,用於表示信用卡到期日、FD到期日、期貨期權到期日等。還可以用這個類得到 當月共有多少天,YearMonth實例的lengthOfMonth()方法可以返回當月的天數,在判斷2月有28天還是29天時非常有用。
import java.time.*; public class Demo13 { public static void main(String[] args) { YearMonth currentYearMonth = YearMonth.now(); System.out.printf("Days in month year %s: %d%n", currentYearMonth, currentYearMonth.lengthOfMonth()); YearMonth creditCardExpiry = YearMonth.of(2019, Month.FEBRUARY); System.out.printf("Your credit card expires on %s %n", creditCardExpiry); } }
結果如下:
Days in month year 2022-08: 31 Your credit card expires on 2019-02
示例14:如何在Java 8中檢查閏年
import java.time.LocalDate; public class Demo14 { public static void main(String[] args) { LocalDate today = LocalDate.now(); if(today.isLeapYear()){ System.out.println("This year is Leap year"); }else { System.out.println("2018 is not a Leap year"); } } }
結果:2018 is not a Leap year
示例15:計算兩個日期之間的天數和月數
有一個常見日期操作是計算兩個日期之間的天數、周數或月數。在Java 8中可以用java.time.Period類來做計算。
下面這個例子中,我們計算了當天和將來某一天之間的月數。
import java.time.LocalDate; import java.time.Period; public class Demo15 { public static void main(String[] args) { LocalDate today = LocalDate.now(); LocalDate java8Release = LocalDate.of(2022, 12, 14); Period periodToNextJavaRelease = Period.between(today, java8Release); System.out.println("Months left between today and Java 8 release : " + periodToNextJavaRelease.getMonths() ); } }
結果:Months left between today and Java 8 release : 3
示例16:在Java 8中獲取當前的時間戳
Instant類有一個靜態工廠方法now()會返回當前的時間戳,如下所示:
import java.time.Instant; public class Demo16 { public static void main(String[] args) { Instant timestamp = Instant.now(); System.out.println("What is value of this instant " + timestamp.toEpochMilli()); } }
結果:What is value of this instant 1661313298160
時間戳信息里同時包含了日期和時間,這和java.util.Date很像。實際上Instant類確實等同於 Java 8之前的Date類,你可以使用Date類和Instant類各自的轉換方法互相轉換,例如:Date.from(Instant) 將Instant轉換成java.util.Date,Date.toInstant()則是將Date類轉換成Instant類。
示例17:Java 8中如何使用預定義的格式化工具去解析或格式化日期
import java.time.LocalDate; import java.time.format.DateTimeFormatter; public class Demo17 { public static void main(String[] args) { String dayAfterTommorrow = "20180205"; LocalDate formatted = LocalDate.parse(dayAfterTommorrow, DateTimeFormatter.BASIC_ISO_DATE); System.out.println(dayAfterTommorrow+" 格式化后的日期為: "+formatted); } }
結果:20220825 格式化后的日期為: 2022-08-25
示例18:字符串互轉日期類型
import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; public class Demo18 { public static void main(String[] args) { LocalDateTime date = LocalDateTime.now(); DateTimeFormatter format1 = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); //日期轉字符串 String str = date.format(format1); System.out.println("日期轉換為字符串:"+str); DateTimeFormatter format2 = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); //字符串轉日期 LocalDate date2 = LocalDate.parse(str,format2); System.out.println("日期類型:"+date2); } }
結果如下:
日期轉換為字符串:2022/08/24 12:00:24 日期類型:2022-08-24
示例19:將字符串類型的日期轉成Date類型
public class demo { public static void main(String[] args) throws ParseException {// 日期字符串轉LocalDateTime String today = "2022-08-24 18:00:10"; DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); // 轉為自定義格式 LocalDateTime localDateTime = LocalDateTime.parse(today, dateTimeFormatter); String format = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(localDateTime); System.out.println("localDateTime:" + localDateTime); System.out.println("自定義格式:" + format); } }
結果如下:
localDateTime:2022-08-24T18:00:10 自定義格式:20220824180010
示例20:獲取今天的開始時間和結束時間
public class demo { public static void main(String[] args) throws ParseException { LocalDate today = LocalDate.now(); LocalDate yesterday = today.minus(1, ChronoUnit.DAYS); System.out.println(yesterday); LocalDateTime startOfDay = LocalDateTime.of(LocalDate.now(), LocalTime.MIN); LocalDateTime endOfDay = LocalDateTime.of(LocalDate.now(), LocalTime.MAX); System.out.println(startOfDay); System.out.println(endOfDay); LocalDateTime startOfDay1 = LocalDateTime.of(today.minus(1, ChronoUnit.DAYS), LocalTime.MIN); LocalDateTime endOfDay1 = LocalDateTime.of(today.minus(1, ChronoUnit.DAYS), LocalTime.MAX); System.out.println(startOfDay1); System.out.println(endOfDay1); } }
結果:
2022-08-28 2022-08-29T00:00 2022-08-29T23:59:59.999999999 2022-08-28T00:00 2022-08-28T23:59:59.999999999
常用轉換
Calendar c = Calendar.getInstance(); //c.add(Calendar.DAY_OF_YEAR, -1); Date todayDate = c.getTime(); //Date yesterdayDate = c.getTime(); Instant instant = todayDate.toInstant(); // 使用 Instant 代替 Date ZoneId zoneId = ZoneId.systemDefault(); LocalDateTime localDateTime = instant.atZone(zoneId).toLocalDateTime(); // LocalDateTime 代替 Calendar System.out.println(localDateTime); // LocalDateTime 代替 Calendar ,DateTimeFormatter 代替 SimpleDateFormat DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); //String yesterdayDateStr = dateTimeFormatter.format(localDateTime); String todayDateStr = dateTimeFormatter.format(localDateTime); DateTimeFormatter dateTimeFormatter02 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); String curTime = dateTimeFormatter02.format(localDateTime);
java.time.LocalDate --> java.util.Date
private Date LocalDateToDate(String date){ LocalDate localDate = LocalDate.parse(date); // LocalDate轉換位Date ZoneId zoneId = ZoneId.systemDefault(); Instant instant = localDate.atStartOfDay().atZone(zoneId).toInstant(); return Date.from(instant); }
java.util.Date --> java.time.LocalDate
public void DateToLocalDate() { java.util.Date date = new java.util.Date(); Instant instant = date.toInstant(); ZoneId zone = ZoneId.systemDefault(); LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone); LocalDate localDate = localDateTime.toLocalDate(); }
java.util.Date --> java.time.LocalDateTime
public void DateToLocalDateTime() { java.util.Date date = new java.util.Date(); Instant instant = date.toInstant(); ZoneId zone = ZoneId.systemDefault(); LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone); }
java.time.LocalDateTime --> java.util.Date
public void LocalDateTimeTodate() { LocalDateTime localDateTime = LocalDateTime.now(); ZoneId zone = ZoneId.systemDefault(); Instant instant = localDateTime.atZone(zone).toInstant(); java.util.Date date = Date.from(instant); }
java.util.Date --> java.time.LocalTime
public void DateToLocalTime() { java.util.Date date = new java.util.Date(); Instant instant = date.toInstant(); ZoneId zone = ZoneId.systemDefault(); LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone); LocalTime localTime = localDateTime.toLocalTime(); }
java.time.LocalTime --> java.util.Date
public void LocalTimeTodate() { LocalTime localTime = LocalTime.now(); LocalDate localDate = LocalDate.now(); LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime); ZoneId zone = ZoneId.systemDefault(); Instant instant = localDateTime.atZone(zone).toInstant(); java.util.Date date = Date.from(instant); }
字符串(yyyy-MM-dd HH:mm:ss)轉LocalDateTime
LocalDateTime localDate=LocalDateTime.parse(str, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
時間戳轉日期
Date date = new Date(Long.parseLong("1657508223")); LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault()) .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
日期轉時間戳
//yyyy-MM-dd格式轉時間戳 LocalDate localDate = LocalDate.parse("2022-01-01", DateTimeFormatter.ofPattern("yyyy-MM-dd")); LocalDateTime localDateTime = localDate.atStartOfDay(); Long time = localDateTime.toInstant(ZoneOffset.ofHours(8)).toEpochMilli(); //yyyy-MM-dd HH:mm:ss格式轉時間戳 LocalDateTime localDate = LocalDateTime .parse("2022-01-01 10:09:09", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); Long time = localDate.toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
3.Apache commons-lang中的FastDateFormat
private String initDate() { Date d = new Date(); FastDateFormat fdf = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss"); return fdf.format(d); }