Java Instant

Instant 是 Java 8 引入的類別,代表時間線上的一個瞬時點,以 Unix 紀元(1970-01-01T00:00:00Z)為基準的時間戳記。它不含時區資訊,適合用於記錄事件發生的精確時刻。

引入套件

import java.time.Instant;

建立 Instant

取得當前瞬時

Instant now = Instant.now();
System.out.println(now);  // 2024-12-10T06:30:45.123456789Z

從時間戳記建立

// 從 Unix 秒數建立
Instant instant1 = Instant.ofEpochSecond(1702200000);
System.out.println(instant1);  // 2023-12-10T08:00:00Z

// 從 Unix 毫秒數建立
Instant instant2 = Instant.ofEpochMilli(1702200000000L);
System.out.println(instant2);  // 2023-12-10T08:00:00Z

// 秒數 + 奈秒調整
Instant instant3 = Instant.ofEpochSecond(1702200000, 500000000);
System.out.println(instant3);  // 2023-12-10T08:00:00.500Z

從字串解析

Instant instant = Instant.parse("2024-12-25T14:30:00Z");
System.out.println(instant);  // 2024-12-25T14:30:00Z

// ISO-8601 格式
Instant instant2 = Instant.parse("2024-12-25T14:30:00.123Z");

特殊常數

Instant epoch = Instant.EPOCH;  // 1970-01-01T00:00:00Z
Instant min = Instant.MIN;      // -1000000000-01-01T00:00:00Z
Instant max = Instant.MAX;      // +1000000000-12-31T23:59:59.999999999Z

取得時間資訊

Instant instant = Instant.now();

// 取得 Unix 時間戳記
long epochSecond = instant.getEpochSecond();  // 秒數
long epochMilli = instant.toEpochMilli();     // 毫秒數
int nano = instant.getNano();                  // 奈秒部分(0-999999999)

System.out.println("秒數: " + epochSecond);
System.out.println("毫秒數: " + epochMilli);
System.out.println("奈秒: " + nano);

時間運算

加減運算

Instant instant = Instant.now();

// 加上時間
Instant plus10Seconds = instant.plusSeconds(10);
Instant plus5Minutes = instant.plus(5, ChronoUnit.MINUTES);
Instant plus1Hour = instant.plus(Duration.ofHours(1));
Instant plusMillis = instant.plusMillis(500);
Instant plusNanos = instant.plusNanos(1000000);

// 減去時間
Instant minus30Seconds = instant.minusSeconds(30);
Instant minus1Day = instant.minus(1, ChronoUnit.DAYS);

使用 Duration

Instant instant = Instant.now();
Duration duration = Duration.ofHours(2).plusMinutes(30);

Instant later = instant.plus(duration);
Instant earlier = instant.minus(duration);

時間比較

Instant instant1 = Instant.now();
Instant instant2 = instant1.plusSeconds(60);

boolean isBefore = instant1.isBefore(instant2);  // true
boolean isAfter = instant1.isAfter(instant2);    // false

int result = instant1.compareTo(instant2);  // 負數

計算時間差

Instant start = Instant.now();

// ... 執行一些操作 ...

Instant end = Instant.now();

// 使用 Duration
Duration duration = Duration.between(start, end);
long millis = duration.toMillis();
long nanos = duration.toNanos();

System.out.println("耗時: " + millis + " 毫秒");

// 使用 ChronoUnit
long seconds = ChronoUnit.SECONDS.between(start, end);
long minutes = ChronoUnit.MINUTES.between(start, end);

與其他類型轉換

轉換為 ZonedDateTime

Instant instant = Instant.now();

// 加上時區轉換為 ZonedDateTime
ZonedDateTime zdt = instant.atZone(ZoneId.of("Asia/Taipei"));
System.out.println(zdt);  // 2024-12-10T14:30:45.123+08:00[Asia/Taipei]

ZonedDateTime zdtUtc = instant.atZone(ZoneId.of("UTC"));
System.out.println(zdtUtc);  // 2024-12-10T06:30:45.123Z[UTC]

轉換為 OffsetDateTime

Instant instant = Instant.now();

OffsetDateTime odt = instant.atOffset(ZoneOffset.ofHours(8));
System.out.println(odt);  // 2024-12-10T14:30:45.123+08:00

與 Date 互轉

// Instant 轉 Date
Instant instant = Instant.now();
Date date = Date.from(instant);

// Date 轉 Instant
Date oldDate = new Date();
Instant fromDate = oldDate.toInstant();

與 LocalDateTime 互轉

// Instant 轉 LocalDateTime(需要時區)
Instant instant = Instant.now();
LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneId.of("Asia/Taipei"));

// LocalDateTime 轉 Instant(需要時區)
LocalDateTime localDateTime = LocalDateTime.now();
Instant toInstant = localDateTime.atZone(ZoneId.of("Asia/Taipei")).toInstant();

時間截斷

Instant instant = Instant.parse("2024-12-25T14:30:45.123456789Z");

// 截斷到秒
Instant truncatedToSeconds = instant.truncatedTo(ChronoUnit.SECONDS);
System.out.println(truncatedToSeconds);  // 2024-12-25T14:30:45Z

// 截斷到分鐘
Instant truncatedToMinutes = instant.truncatedTo(ChronoUnit.MINUTES);
System.out.println(truncatedToMinutes);  // 2024-12-25T14:30:00Z

// 截斷到小時
Instant truncatedToHours = instant.truncatedTo(ChronoUnit.HOURS);
System.out.println(truncatedToHours);  // 2024-12-25T14:00:00Z

實用範例

計時器

public class Timer {
    private Instant startTime;
    private Instant endTime;
    
    public void start() {
        startTime = Instant.now();
        endTime = null;
    }
    
    public void stop() {
        endTime = Instant.now();
    }
    
    public long getElapsedMillis() {
        Instant end = (endTime != null) ? endTime : Instant.now();
        return Duration.between(startTime, end).toMillis();
    }
    
    public String getElapsedFormatted() {
        long millis = getElapsedMillis();
        long seconds = millis / 1000;
        long ms = millis % 1000;
        return String.format("%d.%03d 秒", seconds, ms);
    }
}

// 使用
Timer timer = new Timer();
timer.start();
// ... 執行操作 ...
timer.stop();
System.out.println("耗時: " + timer.getElapsedFormatted());

API 時間戳記

public class ApiResponse {
    private String data;
    private Instant timestamp;
    
    public ApiResponse(String data) {
        this.data = data;
        this.timestamp = Instant.now();
    }
    
    public long getTimestampMillis() {
        return timestamp.toEpochMilli();
    }
    
    public String getTimestampISO() {
        return timestamp.toString();
    }
}

ApiResponse response = new ApiResponse("Hello");
System.out.println("時間戳(毫秒): " + response.getTimestampMillis());
System.out.println("時間戳(ISO): " + response.getTimestampISO());

快取過期檢查

public class CacheEntry<T> {
    private T value;
    private Instant createdAt;
    private Duration ttl;
    
    public CacheEntry(T value, Duration ttl) {
        this.value = value;
        this.createdAt = Instant.now();
        this.ttl = ttl;
    }
    
    public boolean isExpired() {
        return Instant.now().isAfter(createdAt.plus(ttl));
    }
    
    public T getValue() {
        return isExpired() ? null : value;
    }
    
    public long getRemainingSeconds() {
        Instant expiresAt = createdAt.plus(ttl);
        long remaining = Duration.between(Instant.now(), expiresAt).getSeconds();
        return Math.max(0, remaining);
    }
}

// 建立 5 分鐘過期的快取
CacheEntry<String> cache = new CacheEntry<>("cached data", Duration.ofMinutes(5));
System.out.println("剩餘時間: " + cache.getRemainingSeconds() + " 秒");

事件日誌

public class EventLog {
    private String event;
    private Instant timestamp;
    
    public EventLog(String event) {
        this.event = event;
        this.timestamp = Instant.now();
    }
    
    @Override
    public String toString() {
        DateTimeFormatter formatter = DateTimeFormatter
            .ofPattern("yyyy-MM-dd HH:mm:ss.SSS")
            .withZone(ZoneId.systemDefault());
        return "[" + formatter.format(timestamp) + "] " + event;
    }
}

List<EventLog> logs = new ArrayList<>();
logs.add(new EventLog("應用程式啟動"));
logs.add(new EventLog("使用者登入"));
logs.add(new EventLog("資料載入完成"));

logs.forEach(System.out::println);

Instant vs 其他日期時間類別

類別用途時區
Instant時間戳記、事件記錄UTC(無時區)
LocalDateTime本地日期時間無時區
ZonedDateTime完整日期時間有時區
OffsetDateTime帶偏移的日期時間有偏移量

重點整理

  • Instant 代表 UTC 時間線上的一個瞬時點
  • 使用 Unix 紀元(1970-01-01T00:00:00Z)作為基準
  • 適合用於時間戳記、計時、快取過期等場景
  • 不含時區資訊,需要顯示本地時間時要轉換
  • 精度可達奈秒(10⁻⁹ 秒)
  • 不可變(immutable)類別
  • 資料庫儲存和 API 傳輸建議使用 Instant