2016年12月13日 星期二

日期、Base64 (java8 七)

※多執行緒用 SimpleDateFormat 有問題

DateFormat df = new SimpleDateFormat("yyyyMMdd HHmmss");
ExecutorService es = Executors.newFixedThreadPool(5);
    
Stream.iterate(0, i -> ++i).limit(1000).forEach(i -> {
    es.execute(() -> {
        try {
            // System.out.println(df.parse("20101112 235958"));
    
            // 要用時再 new 可解決,但浪費記憶體
            System.out.println(new SimpleDateFormat("yyyyMMdd HHmmss").parse("20101112 235958"));
    
            // synchronized 也可解決,但高併發時會阻塞
    
            // 用 ThreadLocal 也可解決
            System.out.println(ThreadLocalDateFormat.convertDate("20101112 235958"));
        } catch (ParseException e) {
            e.printStackTrace();
        }
    });
});
    
es.shutdown();
    
    
class ThreadLocalDateFormat {
    private static ThreadLocal<DateFormat> tl = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmmss"));
    
    static Date convertDate(String ymdhms) throws ParseException {
        return tl.get().parse(ymdhms);
    }
}


※日期

java.time開頭的5個package都是java8新增的
LocalDate、LocalTime、LocalDateTime看要不要有年月日或時分秒用的


※LocalDate

LocalDate today = LocalDate.now();
System.out.println("現在日期=" + today);
    
System.out.println("下星期=" + today.plus(1, ChronoUnit.WEEKS));
System.out.println("上一個月=" + today.plus(-1, ChronoUnit.MONTHS));
System.out.println("明年=" + today.plus(1, ChronoUnit.YEARS));
System.out.println("下一個十年" + today.plus(1, ChronoUnit.DECADES));

※ChronoUnit還可以加100年、1000年…等

※ChronoUnit.YEARS
ChronoUnit.WEEKS
ChronoUnit.MONTHS
ChronoUnit.DAYS
ChronoUnit.HOURS
ChronoUnit.MINUTES
ChronoUnit.SECONDS
ChronoUnit.MILLIS
ChronoUnit.NANOS
時、分、秒、毫秒、毫微秒要使用 LocalDateTime



※日期相差

LocalDate date1 = LocalDate.now();
System.out.println("date1=" + date1);
    
LocalDate date2 = date1.plus(1, ChronoUnit.MONTHS);
System.out.println("date2=" + date2);
    
// 右邊減左邊
Period period = Period.between(date1, date2);
System.out.println("相差=" + period);// P1M
    
LocalTime time1 = LocalTime.now();
System.out.println("time1=" + time1);
    
Duration twoHours = Duration.ofHours(-2);
LocalTime time2 = time1.plus(twoHours);
System.out.println("time2=" + time2);
    
// 右邊減左邊
Duration duration = Duration.between(time1, time2);
System.out.println("Duration: " + duration);// PT-2H

※開頭的P和PT固定會有,可以再 getXXX 取得數字,但時分秒是分開的,要小心


※上/下 一個日期

LocalDate today = LocalDate.now();
System.out.println("現在日期=" + today);
    
LocalDate nextTuesday = today.with(TemporalAdjusters.next(DayOfWeek.TUESDAY));
System.out.println("下個星期二=" + nextTuesday);
    
// 這個月的第一天
LocalDate one = LocalDate.of(today.getYear(), today.getMonth(), 1);
LocalDate two = one.with(TemporalAdjusters.nextOrSame(DayOfWeek.SATURDAY));
System.out.println("下一個星期六(如果1號也是星期六,那就是1號)=" + two);
    
LocalDate three = two.with(TemporalAdjusters.next(DayOfWeek.FRIDAY));
System.out.println("下一個星期五=" + three);

※要注意next和nextOrSame的差別


※LocalDateTime

LocalDateTime currentTime = LocalDateTime.now();
System.out.println("現在日期時間=" + currentTime);// 2016-12-13T17:43:00.427
    
System.out.println("年月日=" + currentTime.toLocalDate());// 2016-12-13
System.out.println("年=" + currentTime.getYear());// 2016
System.out.println("1月1日到現在是第幾天=" + currentTime.getDayOfYear());// 348
System.out.println("英文月=" + currentTime.getMonth());// DECEMBER
System.out.println("月=" + currentTime.getMonthValue());// 12
System.out.println("x月1日到現在是第幾天=" + currentTime.getDayOfMonth());// 13
System.out.println("星期=" + currentTime.getDayOfWeek());// TUESDAY
System.out.println("時=" + currentTime.getHour());// 17
System.out.println("分=" + currentTime.getMinute());// 43
System.out.println("秒=" + currentTime.getSecond());// 0
System.out.println("毫秒=" + currentTime.getNano());// 427000000
System.out.println("getChronology=" + currentTime.getChronology());// ISO
    
LocalDateTime defineymd = currentTime.withDayOfMonth(10).withYear(2012);
// 沒with的就以現在時間為主
System.out.println("自訂日期: " + defineymd);// 2012-12-10T17:43:00.427
System.out.println("年月日: " + LocalDate.of(2014, Month.DECEMBER, 12));// 2014-12-12
System.out.println("時分: " + LocalTime.of(22, 15));// 22:15
System.out.println("時分秒: " + LocalTime.parse("20:15:30"));// 20:15:30

※LocalDateTime 相關的 class,預設已經加上時區了,但不包括日光節約時間,
台灣日光節約日期可參考這裡,1975/4/1 - 1975/9/30 是其中一組,
所可以用 1975/3/31 23:59:59 加 1 秒會變成凌晨 1 點
1975/9/30 加 1 秒,會變成 1975-09-30T23:00+08:00[Asia/Taipei],
再扣一小時會變成 1975-09-30T23:00+09:00[Asia/Taipei],
也就是在日光節約時間裡是 UTC+9 

※預設時區的名字,可用 ZoneId.systemDefault() 替看


※ZonedDateTime和轉換

// ZoneId.getAvailableZoneIds().forEach(System.out::println);
ZonedDateTime d = ZonedDateTime.parse("2007-12-03T10:15:30+05:30[Asia/Karachi]");
System.out.println("d=" + d);
    
ZoneId id = ZoneId.of("Europe/Paris"); // ZoneId.of(ZoneId.SHORT_IDS.get("ECT"));
System.out.println("ZoneId=" + id);
    
System.out.println("================================");
ZoneId zoneId = ZoneId.systemDefault();
System.out.println("zoneId=" + zoneId);
ZonedDateTime zonedDateTime2 = ZonedDateTime.of(
                LocalDateTime.of(2021, 2, 20, 10, 0, 0),
                zoneId)
System.out.println(zonedDateTime2.getOffset().getTotalSeconds() / 60 / 60); // 取得 UTC 差幾小時
    
Date today = new Date();
System.out.println("現在時間=" + today);
    
Instant dateInstant = today.toInstant();
    
// Date轉LocalDateTime
LocalDateTime localDateTime = LocalDateTime.ofInstant(dateInstant, zoneId);
System.out.println("LocalDateTime=" + localDateTime);
    
// Date轉ZonedDateTime
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(dateInstant, zoneId);
System.out.println("ZonedDateTime=" + zonedDateTime);
    
// LocalDateTime轉Date
Instant instant = localDateTime.toInstant(ZoneOffset.ofHours(8));
Date date = Date.from(instant);
System.out.println(date);
    
// ZonedDateTime轉Date
System.out.println(Date.from(zonedDateTime.toInstant()));
    
System.out.println("================================");
Calendar calendar = Calendar.getInstance();
Instant calendarInstant = calendar.toInstant();
    
// Calendar轉LocalDateTime
LocalDateTime localDateTime2 = LocalDateTime.ofInstant(calendarInstant, zoneId);
System.out.println("LocalDateTime2=" + localDateTime2);
    
// Calendar轉ZonedDateTime
ZonedDateTime zonedDateTime2 = ZonedDateTime.ofInstant(calendarInstant, zoneId);
System.out.println("ZonedDateTime2=" + zonedDateTime2);
OffsetDateTime offsetDateTime2 = OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.of("-5"));

※ZonedDateTime比LocalDateTime多了ZoneOffset和ZoneId

※ZoneOffset就是+08:00這種東東;而parse裡面的[]就是ZoneId,可以使用ZoneId.systemDefault()取得

※ZoneOffset.ofHours(8)裡的8,上一行印出ZonedDateTime時,可以知道

※Date和Calendar在1.8也新增了toInstant方法,可以和新的日期做轉換使用

※Date有from可以轉成ZonedDateTime和LocalDateTime;Calendar沒看到有轉換成Instant的方法,只好和以前一樣,轉換成Date了

※OffsetDateTime 並沒有考慮日光節約時間;而 ZonedDateTime 有考慮到

※GMT 是用地球公轉來表示;UTC 是用銫原子去做,是人為的,但因為地球公轉有變慢的趨勢,所以有做加 1 秒的動作,會變成 23:59:60,時間越久,GMT 和 UTC 會越差越多,我測的 java 是在 windows 裡,沒有測出這個 

這裡可以查詢時區和日光節約時區的資訊

※如果有夏令時的國家 ZoneId 要用類似 America/Los_Angeles 這樣的格式才可以

※如果固定是 GMT +或-多少,直接寫死的就行,相當於 OffsetDateTime

※西方人的想法是以他們為主,所以西方的 GMT 是+的,東方是-的,如果要用西方的格式要寫成 Etc/GMT-8,用我們的想法要寫成 GMT+8

LocalDateTime localDateTime = LocalDateTime.of(2024, 3, 15, 0, 0, 0);
ZonedDateTime z1 = ZonedDateTime.of(localDateTime, ZoneId.of("Etc/GMT-8"));
ZonedDateTime z2 = ZonedDateTime.of(localDateTime, ZoneId.of("GMT+8"));

※z1 和 z2 是一樣的時間,差在東西方的格式而已


※有時間的字串轉換

時區要寫在 DateTimeFormatter 的 withZone 方法
final String DATE = "2018-09-18T19:13:00Z"; // Z 就是 zone 的意思
ZonedDateTime z0 = ZonedDateTime.parse(DATE);
ZonedDateTime z1 = ZonedDateTime.parse(DATE, DateTimeFormatter.ISO_ZONED_DATE_TIME.withZone(ZoneId.of("Asia/Taipei")));
ZonedDateTime z2 = ZonedDateTime.parse(DATE, DateTimeFormatter.ISO_ZONED_DATE_TIME.withZone(ZoneId.of("America/Indiana/Indianapolis")));
    
2018-09-18T19:13Z // 沒有 zone 資訊
2018-09-18T19:13+08:00[Asia/Taipei]  // +8小時後的時間
2018-09-18T19:13-04:00[America/Indiana/Indianapolis] // -4小時後的時間



※DateTimeFormatter 的 X

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss XXX");
ZonedDateTime tmpTimestamp = ZonedDateTime.parse("2018-07-31 12:13:14 +03:00", formatter);

偏移量X和x:它將根據圖案字母的數量來格式化偏移量。
除非分鐘非零,否則一個字母僅輸出小時,例如“+01”,在這種情況下,分鐘也會輸出,例如“+0130”。
兩個字母輸出小時和分鐘,不帶冒號,例如'+0130'。
三個字母輸出小時和分鐘,並帶有冒號,例如'+01:30'。
四個字母輸出小時和分鐘,可選秒,不帶冒號,例如“+013015”。
五個字母輸出小時和分鐘,可選秒,以冒號表示,例如“+01:30:15”。
六個或更多字母會引發IllegalArgumentException。
當要輸出的偏移量為零時,模式字母“X”(大寫)將輸出“Z”,而模式字母“x”(小寫)將輸出“+00”,“+ 0000”或“+00” :00'。

或者,您可以使用五個字母(XXXXX),也可以使用ZZZ或ZZZZZ代替XXX或XXXXX。


※Base64 

java.util包裡有三個Base64開頭的class,也都是java8新增的


try {
    // BASIC Encode
    String encode = Base64.getEncoder().encodeToString("xxx".getBytes("UTF-8"));
    System.out.println("encode=" + encode);
    
    // Decode
    byte[] decode = Base64.getDecoder().decode(encode);
    System.out.println("basic decode=" + new String(decode, "UTF-8"));
    
    System.out.println();
    // URL/Filename safe Encode
    String urlEncode = Base64.getUrlEncoder().encodeToString("http://www.google.com".getBytes("UTF-8"));
    System.out.println("urlEncode=" + urlEncode);
    
    // Decode
    System.out.println("url decode=" + new String(Base64.getDecoder().decode(urlEncode), "UTF-8"));
    
    System.out.println();
    // MIME Encode
    byte[] mimeBytes = new String("text/html").getBytes("UTF-8");
    String mimeEncode = Base64.getMimeEncoder().encodeToString(mimeBytes);
    System.out.println("mimeEncode=" + mimeEncode);
    
    // Decode
    System.out.println("mime decode=" + new String(Base64.getDecoder().decode(mimeEncode), "UTF-8"));
} catch (UnsupportedEncodingException e) {
    System.out.println("Error :" + e.getMessage());
}



沒有留言:

張貼留言