Instant: nó là gì, dấu thời gian và ứng dụng trong phát triển

Tác giả: IT Sectr Đã đăng: 2026-07-13 Thời gian đọc: 9 phút

Instant — một lớp bất biến từ gói java.time, đại diện cho một điểm trên dòng thời gian trong UTC với độ chính xác nano giây. Không giống như LocalDateTime, Instant không chứa ngày và giờ ở định dạng con người có thể đọc được — đây là biểu diễn máy của một khoảnh khắc. Theo đặc tả Oracle Java 17 (2024), Instant được thiết kế để trao đổi máy các dấu thời gian và tương tự như System.currentTimeMillis(), nhưng với độ chính xác nano giây.

Những Điểm Chính

  • Instant — một khoảnh khắc trong thời gian ở UTC với độ chính xác nano giây, bất biến và an toàn luồng.
  • Lưu trữ thời gian dưới dạng số giây từ kỷ nguyên (1970-01-01T00:00:00Z) cộng với nano giây.
  • Instant.now() trả về khoảnh khắc hiện tại ở UTC bất kể múi giờ của thiết bị.
  • Để chuyển đổi sang định dạng con người có thể đọc, atZone(ZoneId) được sử dụng, trả về ZonedDateTime.
  • Instant là loại ưa thích để truyền dấu thời gian trong API và hệ thống phân tán.

Instant là gì?

Instant là một lớp mô hình hóa một điểm duy nhất trên dòng thời gian. Biểu diễn bên trong của nó bao gồm hai trường: long seconds (số giây từ 1970-01-01T00:00:00Z) và int nanos (nano giây trong giây hiện tại, từ 0 đến 999999999).

Phạm vi giá trị của Instant là từ -31557014167219200 đến 31556889864403199 giây từ kỷ nguyên, bao phủ khoảng 292 triệu năm theo cả hai hướng. Điều này đủ cho mọi tác vụ thực tế, bao gồm cả tính toán thiên văn.

Theo Baeldung (2024), Instant là cầu nối giữa các loại con người có thể đọc (LocalDateTime, ZonedDateTime) và định dạng máy (dấu thời gian tính bằng mili giây). Instant được sử dụng cho ghi nhật ký, lưu đệm, đồng bộ hóa và tất cả các tác vụ nơi một khoảnh khắc tuyệt đối trong thời gian là quan trọng.

Lớp này triển khai các giao diện Comparable (để so sánh khoảnh khắc) và Temporal (để sử dụng trong API java.time chung). Instant là bất biến — tất cả các phương thức đều trả về một đối tượng mới.

Instant vs Date vs Long

Trước Java 8, java.util.Date và System.currentTimeMillis() được sử dụng để làm việc với các khoảnh khắc thời gian. Cả hai cách tiếp cận đều có nhược điểm. Date có thể thay đổi, không an toàn luồng, lưu trữ thời gian bằng mili giây từ kỷ nguyên, nhưng tên phương thức của nó đã lỗi thời (getYear() trả về 116 cho năm 2016).

Long (một dấu thời gian đơn giản) nhanh và nhỏ gọn, nhưng không có hỗ trợ tích hợp cho nano giây, không hiển thị ở định dạng có thể đọc và yêu cầu phân tích thủ công khi gỡ lỗi. Cách tiếp cận Long cũng không phân biệt loại dữ liệu — nhà phát triển có thể truyền một giá trị không chính xác.

Instant giải quyết tất cả những vấn đề này. Nó bất biến, chứa thông tin độ chính xác rõ ràng (giây + nano giây), tuần tự hóa sang định dạng ISO-8601 “2026-07-21T15:00:00Z” và có API phong phú cho các chuyển đổi. Theo SonarSource (2024), Instant là sự thay thế được khuyến nghị cho Date trong tất cả các dự án mới.

Tạo và Đọc Instant

Khoảnh khắc hiện tại được lấy qua Instant.now(). Không giống như LocalDateTime.now(), Instant.now() luôn trả về thời gian ở UTC, bỏ qua múi giờ của thiết bị. Điều này làm cho nó lý tưởng cho dấu thời gian máy chủ.

Từ các giá trị hiện có: Instant.ofEpochSecond(long epochSecond) — từ giây kể từ kỷ nguyên, Instant.ofEpochMilli(long epochMilli) — từ mili giây, Instant.parse(CharSequence) — từ chuỗi ISO-8601 (“2026-07-21T15:00:00Z”).

Để đọc: getEpochSecond() — số giây từ kỷ nguyên, toEpochMilli() — số mili giây, getNano() — nano giây. Phương thức toString() trả về một chuỗi ở định dạng ISO-8601.

kotlin
val now = Instant.now()

val fromSeconds = Instant.ofEpochSecond(1784700000)
val fromMillis = Instant.ofEpochMilli(1784700000000)
val parsed = Instant.parse("2026-07-21T15:00:00Z")

val epochSecond = now.getEpochSecond()
val epochMilli = now.toEpochMilli()
val nanos = now.getNano()

Chuyển đổi Instant sang Định dạng Khác

Instant được chuyển đổi thành ZonedDateTime qua atZone(ZoneId). Ví dụ, Instant.now().atZone(ZoneId.of(“Europe/Moscow”)) trả về ZonedDateTime cho Moscow. Nếu không có múi giờ, việc chuyển đổi là không thể — Instant không chứa thông tin lịch.

Để chuyển đổi Instant thành LocalDateTime: atZone(ZoneId).toLocalDateTime(). Cách tiếp cận này rõ ràng và không mất thông tin. Chuyển đổi ngược: LocalDateTime.atZone(ZoneId).toInstant().

Để tương thích với java.util.Date: Date.from(instant) và date.toInstant(). Đây là chuyển đổi hai chiều bảo toàn độ chính xác đến mili giây (Date không hỗ trợ nano giây). Cho java.sql.Timestamp, sử dụng Timestamp.from(instant) với hỗ trợ nano giây.

kotlin
val instant = Instant.now()

val zoned = instant.atZone(ZoneId.of("Europe/Moscow"))
val localDateTime = instant
    .atZone(ZoneId.systemDefault())
    .toLocalDateTime()

val oldDate = Date.from(instant)
val backToInstant = oldDate.toInstant()

Instant và Múi Giờ

Đặc điểm chính của Instant là nó hoàn toàn độc lập với múi giờ. Instant.now() trả về cùng một kết quả trên mọi thiết bị ở bất kỳ nơi nào trên thế giới. Điều này đạt được bằng cách cố định thời gian ở UTC.

Múi giờ chỉ cần thiết để hiển thị Instant cho con người. Để làm điều này, atZone(ZoneId) được sử dụng. ZoneId.systemDefault() trả về múi giờ của thiết bị được đặt trong hệ điều hành. ZoneOffset.UTC là hằng số cho UTC.

Trong các hệ thống phân tán, nên lưu trữ và truyền tất cả dấu thời gian ở Instant (hoặc OffsetDateTime với ZoneOffset.UTC). Việc chuyển đổi sang giờ địa phương chỉ được thực hiện trên máy khách trước khi hiển thị cho người dùng. Điều này ngăn ngừa nhầm lẫn về múi giờ.

Đồng bộ hóa Thời gian qua Instant

Trong các ứng dụng Android phân tán, đồng bộ hóa thời gian rất quan trọng cho việc lưu đệm chính xác, thông báo và chỉnh sửa cộng tác. Instant là lựa chọn tự nhiên cho tác vụ này nhờ neo vào UTC.

Khi so sánh dấu thời gian từ các thiết bị khác nhau, cần xem xét rằng đồng hồ hệ thống có thể khác nhau. Nên sử dụng thời gian máy chủ làm tham chiếu. Máy chủ trả về Instant ở UTC, và máy khách chỉ so sánh với Instant cục bộ cho các tính toán tương đối.

Để tính chênh lệch giữa hai khoảnh khắc, sử dụng Duration.between(Instant start, Instant end). Phương thức này trả về một Duration có thể chuyển đổi thành giờ, phút, giây. Các phương thức isAfter() và isBefore() cho phép so sánh các khoảnh khắc.

kotlin
fun isCacheExpired(
    cachedAt: Instant,
    ttlMinutes: Long
): Boolean {
    val elapsed = Duration.between(cachedAt, Instant.now())
    return elapsed.toMinutes() >= ttlMinutes
}

Ví dụ Thực tế trong Android

Ví dụ đầu tiên là ghi nhật ký sự kiện với dấu thời gian. Instant được lưu vào cơ sở dữ liệu Room và gửi đến máy chủ. Dấu thời gian được ghi nhật ký ở UTC để diễn giải rõ ràng.

kotlin
data class EventLog(
    val id: Long = 0,
    val eventName: String,
    val timestamp: Instant
)

class Converters {
    @TypeConverter
    fun fromInstant(value: Instant?): Long? {
        return value?.toEpochMilli()
    }

    @TypeConverter
    fun toInstant(value: Long?): Instant? {
        return value?.let { Instant.ofEpochMilli(it) }
    }
}

Ví dụ thứ hai là xác định thời gian đã trôi qua kể từ một sự kiện. Chúng tôi sử dụng Duration.between để hiển thị “5 phút trước”, “2 giờ trước” — định dạng phổ biến trong tin nhắn và mạng xã hội.

kotlin
fun timeAgo(instant: Instant): String {
    val duration = Duration.between(instant, Instant.now())
    return when {
        duration.toMinutes() < 1 -> "just now"
        duration.toHours() < 1 -> "${duration.toMinutes()} min ago"
        duration.toDays() < 1 -> "${duration.toHours()} h ago"
        else -> "${duration.toDays()} d ago"
    }
}

Ví dụ thứ ba là đồng bộ hóa dữ liệu giữa máy chủ và máy khách. Chúng tôi sử dụng Instant để theo dõi thời gian cập nhật cuối cùng.

kotlin
class SyncManager {
    private var lastSyncAt: Instant? = null

    fun sync() {
        val syncStart = Instant.now()
        // server request with lastSyncAt
        lastSyncAt = syncStart
    }

    fun shouldSync(intervalMinutes: Long): Boolean {
        val last = lastSyncAt ?: return true
        return Duration.between(last, Instant.now())
            .toMinutes() >= intervalMinutes
    }
}

Lỗi Thường Gặp

Lỗi đầu tiên là sử dụng Instant.now().toString() để hiển thị cho người dùng. Instant xuất ra ở định dạng UTC “2026-07-21T15:00:00Z”, không thể đọc được đối với con người. Luôn chuyển đổi Instant qua atZone() sang múi giờ địa phương trước khi hiển thị.

Lỗi thứ hai là mất nano giây khi chuyển đổi sang java.util.Date. Date chỉ hỗ trợ mili giây. Nếu Instant có nano giây, chúng sẽ bị loại bỏ trong Date.from(instant). Sử dụng Instant.truncatedTo(ChronoUnit.MILLIS) để chỉ định rõ ràng độ chính xác.

Lỗi thứ ba là nhầm lẫn giữa toEpochMilli() và getEpochSecond(). toEpochMilli() trả về số mili giây từ kỷ nguyên (long), trong khi getEpochSecond() trả về số giây (long). Nhầm lẫn các phương thức này có thể dẫn đến lỗi gấp 1000 lần.

Lỗi thứ tư là cho rằng Instant.now() được đồng bộ hóa trên tất cả các thiết bị. Đồng hồ hệ thống có thể chênh lệch vài phút hoặc thậm chí vài giờ. Đối với các hoạt động nhạy cảm với thời gian (xác thực, thanh toán), hãy sử dụng Instant máy chủ làm nguồn sự thật.

Câu Hỏi Thường Gặp

Instant khác System.currentTimeMillis() như thế nào?

System.currentTimeMillis() trả về một long — số mili giây từ kỷ nguyên không có ràng buộc múi giờ. Instant cung cấp cùng chức năng nhưng với độ chính xác nano giây và API phong phú cho chuyển đổi, so sánh và tương thích với java.time.

Làm thế nào để lưu Instant trong Room Database?

Room không hỗ trợ Instant trực tiếp. Sử dụng TypeConverter chuyển đổi Instant thành Long (toEpochMilli) và ngược lại (Instant.ofEpochMilli). Đối với độ chính xác nano giây, lưu hai trường: kỷ nguyên-giây và nano giây.

Có thể sử dụng Instant làm khóa trong HashMap không?

Có, Instant là bất biến và triển khai đúng equals() và hashCode(). Hai Instant có cùng giá trị sẽ bằng nhau. Điều này làm cho nó trở thành khóa đáng tin cậy cho HashMap và các bộ sưu tập khác, không giống như java.util.Date có thể thay đổi.

Làm thế nào để lấy chênh lệch giữa hai Instant?

Sử dụng Duration.between(start, end) để lấy Duration hoặc ChronoUnit.SECONDS.between(start, end) cho chênh lệch tính bằng giây (long). Duration cung cấp các phương thức toMinutes(), toHours(), toDays() và toNanos().

Tại sao Instant.parse() yêu cầu hậu tố Z hoặc độ lệch?

Instant được thiết kế như một điểm tuyệt đối trên dòng thời gian. Nếu không chỉ định múi giờ hoặc UTC, việc phân tích là không thể vì Instant không chứa thông tin lịch. Hậu tố “Z” biểu thị độ lệch không (UTC) và là bắt buộc cho định dạng ISO-8601.

Tóm tắt

  • Instant — một lớp bất biến cho khoảnh khắc thời gian tuyệt đối ở UTC với độ chính xác nano giây, được ưa thích cho dấu thời gian trong hệ thống phân tán.
  • Không giống như java.util.Date, Instant là bất biến, an toàn luồng và có độ chính xác nano giây. Không giống như Long — nó được phân loại rõ ràng và không cho phép nhầm lẫn với các giá trị số khác.
  • Để hiển thị cho người dùng, Instant được chuyển đổi qua atZone(ZoneId) thành ZonedDateTime, nếu không thời gian sẽ được hiển thị ở UTC.
  • Để truyền trong API, sử dụng Instant.toString() (ISO-8601) hoặc toEpochMilli() để tương thích với các định dạng cũ.
  • Khi làm việc với Room, lưu Instant dưới dạng Long qua TypeConverter sử dụng phương thức toEpochMilli().
  • Để tính thời gian đã trôi qua, sử dụng Duration.between(), xử lý chính xác nano giây.
  • Đừng dựa vào đồng bộ hóa đồng hồ thiết bị — cho các hoạt động quan trọng, hãy sử dụng Instant máy chủ làm nguồn sự thật.

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm