# Java Date operation

## 前情提要

Java Date 物件底層都是透過TimeZone 來去做時區判斷，但是這會依賴系統時間，即使透過TimeZone setDefaultTimeZone 來去做完時區轉換的方式，但是這無法一勞永逸且不具有彈性的設計，所以才會有這篇文章的誕生。\
我這邊也不賣關子，我自己測試下來的結論，Date物件不好操作，日後有關時區問題，我是會棄用他的。

### 人狠，話不多，上扣！！

<pre class="language-java"><code class="lang-java">/**
 * convert date String with time zone to LocalDateTime Object
 *
 * @param dateStr
 * @param formatterPattern
 * @param dateStrTimeZone
 * @param toTimeZon
 * @return
 * @throws ParseException
 */
private static LocalDateTime convertDateStrWithTimeZone(String dateStr, String formatterPattern, TimeZone dateStrTimeZone, ZoneId toTimeZon) throws ParseException {
    /**
     * 這邊會發生兩件事情
     * 1.SimpleDateFormat 認定字串 為 GMT+0
     * 2.Date Object 將轉換的GMT+0 加上系統預設時區
     * Ex. "2023-03-23 12:00:00" -> 是GMT+0 時區的時間
     * 當下系統 default 時區是 GMT+8
     * 轉換結果就會變成 2023-03-23 20:00:00[GMT+8]
     */

    // Create a SimpleDateFormat object for parsing the date string
    SimpleDateFormat sdf = new SimpleDateFormat(formatterPattern);

    // Set the time zone of the SimpleDateFormat object to GMT+0
    sdf.setTimeZone(dateStrTimeZone);

    // Parse the date string into an Instant object and transfer to LocalDataTime
    return sdf.parse(dateStr).toInstant().atZone(toTimeZon).toLocalDateTime();
}

/**
 * @param dateTime
 * @param fromZone
 * @param toZone
 * @return
 */
private static LocalDateTime convertTimeZoneUseZoneId(LocalDateTime dateTime, ZoneId fromZone, ZoneId toZone) {
    ZonedDateTime originZoneDateTime = <a data-footnote-ref href="#user-content-fn-1">ZonedDateTime</a>.of(dateTime, fromZone);
    ZonedDateTime transZoned = originZoneDateTime.withZoneSameInstant(toZone);
    return transZoned.toLocalDateTime();
}

/**
 * @param dateTime
 * @param fromOffset
 * @param toOffset
 * @return
 */
public static LocalDateTime convertTimeZoneUseZoneOffset(LocalDateTime dateTime, ZoneOffset fromOffset, ZoneOffset toOffset) {
    long timeDifference = toOffset.getTotalSeconds() - fromOffset.getTotalSeconds();
    return dateTime.plusSeconds(timeDifference);
}
</code></pre>

其實 `convertTimeZoneUseZoneId`跟 `convertTimeZoneUseZoneOffset` 背後是繼承關係，所以基本上是一樣的東西，有可能差別在於效能上，但這部分我就沒有花太多時間著墨了。

### 測試！

```java
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.TimeZone;


public class DateTimeConvertWithTimeZoneUtil {
    final static DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
    static SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    final static String GMT0_STR = "GMT+0";
    final static String GMT8_STR = "GMT+8";
    final static ZoneId gmt8Zone = ZoneId.of(GMT8_STR);
    final static ZoneId gmt0Zone = ZoneId.of(GMT0_STR);
    final static TimeZone timeZoneGMT0 = TimeZone.getTimeZone(GMT0_STR);
    final static TimeZone timeZoneGMT8 = TimeZone.getTimeZone(GMT8_STR);
    final static TimeZone timeZoneGMT_M8 = TimeZone.getTimeZone("GMT-8");

    public static void main(String[] args) throws ParseException {

//        String dateStr = "1970-01-01 00:00:00.000";
        String dateStr = "2023-01-05 8:00:00.000";
        String dateStrPattern = "yyyy-MM-dd HH:mm:ss";

        System.out.println("dateStr >> " + dateStr);
        System.out.println("GMT+8 dateStr to GMT+0 LocalDateTime >>> " + df.format(convertDateStrWithTimeZone(dateStr, dateStrPattern, timeZoneGMT8
                , gmt0Zone)));
        System.out.println("GMT+8 dateStr to GMT+8 LocalDateTime >>> " + df.format(convertDateStrWithTimeZone(dateStr, dateStrPattern,
        timeZoneGMT8, gmt8Zone)));
        System.out.println("GMT+0 dateStr to GMT+8 LocalDateTime >>> " + df.format(convertDateStrWithTimeZone(dateStr, dateStrPattern,
        timeZoneGMT0, gmt8Zone)));
        System.out.println("GMT+0 dateStr to GMT+0 LocalDateTime >>> " + df.format(convertDateStrWithTimeZone(dateStr, dateStrPattern,
        timeZoneGMT0, gmt0Zone)));

        System.out.println("=================================================");
        LocalDateTime GMT8LDT = convertDateStrWithTimeZone(dateStr, dateStrPattern, timeZoneGMT8, gmt8Zone);
        LocalDateTime GMT0LDT = convertDateStrWithTimeZone(dateStr, dateStrPattern, timeZoneGMT0, gmt0Zone);

        long start2 = System.currentTimeMillis();
        for (int i = 18; i >= -18; i--) {
            System.out.println("GMT+8 dateStr to GMT" + (i > 0 ? "+" + i : i) + " LocalDateTime >>> " +
                    df.format(convertTimeZoneUseZoneId(GMT8LDT, ZoneOffset.ofHours(8), ZoneOffset.ofHours(i))));
        }

        for (int i = 18; i >= -18; i--) {
            System.out.println("GMT+0 dateStr to GMT" + (i > 0 ? "+" + i : i) + " LocalDateTime >>> " +
                    df.format(convertTimeZoneUseZoneId(GMT0LDT, ZoneOffset.ofHours(0), ZoneOffset.ofHours(i))));
        }
        System.out.println("two end: >>>" + (System.currentTimeMillis() - start2));

        System.out.println("======== change ZoneId =======");

        long start1 = System.currentTimeMillis();
        for (int i = 18; i >= -18; i--) {
            System.out.println("GMT+8 dateStr to GMT" + (i > 0 ? "+" + i : i) + " LocalDateTime >>> " + df.format(convertTimeZoneUseZoneOffset(GMT8LDT,
                    ZoneOffset.ofHours(8), ZoneOffset.ofHours(i))));
        }

        System.out.println(" reverse ~~ ");

        for (int i = 18; i >= -18; i--) {
            System.out.println("GMT+0 dateStr to GMT" + (i > 0 ? "+" + i : i) + " LocalDateTime >>> " + df.format(convertTimeZoneUseZoneOffset(GMT0LDT,
                    ZoneOffset.ofHours(0), ZoneOffset.ofHours(i))));
        }

        System.out.println("one end: >>>" + (System.currentTimeMillis() - start1));

    }
}
```

## 結論

不斷的測試及驗證，最後發現Date 物件對於時區操作，並不是這麼靈活且適合，但我本身就不是那麼愛用Date，Java 8 過後新增的LocalDateTime 等等新增的時間類別，相較於Date 比較好用，但是呢～～～\
永遠都有但是工作上既有產品還是使用Date 所以你還是無法避免這狀況，只能將狀況變成最適合自己的操作方式，盡可能提高自己工作的效率。🔥

[^1]:


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://xu-min-chang.gitbook.io/caster-develop-note/java/java-date-operation.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
