Đếm số chữ số của một số nguyên dương trong C++
Một bài tập C++ thường gặp là cho một số nguyên dương n, yêu cầu đếm...
Kết quả sẽ mở trong tab mới, giới hạn trong website này.
Truy cập nhanh
Tìm kiếm gần đây
Hai chuỗi lạ trên hóa đơn MoMo thật ra là UUID và ULID, và cả hai đều giấu giờ bạn thanh toán. Bài này hướng dẫn giải mã chúng bằng Python.
Hóa đơn MoMo khi đặt xe Green SM có hai chuỗi trông rất “vô nghĩa”. Thật ra đó là UUID và ULID, và cả hai đều cho biết lúc hệ thống tạo đơn, gần như trùng giờ bạn thanh toán. Chỉ vài dòng Python là giải mã được.
Mục tiêu bài học:
Đây là phần “Thông tin đơn hàng” của một chuyến Green SM mình thanh toán qua MoMo:

ff0dd6c9-4f31-11f1-ab17-5ea1c3d07f2401KRJ0NW97G2J53JDWE0F7X90STheo tài liệu API của MoMo, orderId và orderInfo đều do đối tác gửi sang. Nên nhiều khả năng hai mã này do Green SM tạo.
UUID là số 128 bit, viết thành 32 ký tự hex theo dạng 8-4-4-4-12. Ký tự đầu của nhóm thứ ba cho biết phiên bản: 11f1 → UUID v1, loại tạo từ thời gian.
| Phần | Giá trị | Ý nghĩa |
|---|---|---|
| time_low | ff0dd6c9 | 32 bit thấp của thời gian |
| time_mid | 4f31 | 16 bit giữa của thời gian |
| version + time_hi | 1 + 1f1 | Phiên bản 1 và 12 bit cao của thời gian |
| variant + clock_seq | ab17 | 2 bit variant + 14 bit bộ đếm chống trùng (clock_seq = 0x2b17 = 11031) |
| node | 5ea1c3d07f24 | Địa chỉ MAC của máy chủ tạo mã, không phải điện thoại của bạn |
Thời gian = số khoảng 100 nano giây tính từ 15/10/1582 (ngày bắt đầu lịch Gregory).
ULID cũng 128 bit nhưng chỉ có 26 ký tự, dùng bảng chữ Crockford Base32:
01KRJ0NW97 (10 ký tự đầu): 48 bit thời gian Unix, tính bằng mili giây. Vì vậy ký tự đầu luôn từ 0 đến 7.G2J53JDWE0F7X90S (16 ký tự sau): 80 bit ngẫu nhiên.Crockford Base32 là cách viết số theo cơ số 32: dùng 0–9 và A–Z nhưng bỏ I, L, O, U để khỏi đọc nhầm với số 1 và 0. Mỗi ký tự là một “chữ số” có giá trị từ 0 đến 31 (A = 10, Z = 31). Thời gian nằm ở đầu, nên sắp ULID theo chữ cái cũng là sắp theo thời gian.
Python có sẵn module uuid. Với ULID, chỉ cần một hàm nhỏ đổi 10 ký tự đầu ra mili giây.
import uuid
from datetime import datetime, timedelta, timezone
VN = timezone(timedelta(hours=7)) # Múi giờ Việt Nam
# ----- 1. Giải mã UUID v1 (Mã đơn hàng) -----
ma_don = uuid.UUID("ff0dd6c9-4f31-11f1-ab17-5ea1c3d07f24")
print("Phiên bản UUID:", ma_don.version)
# UUID v1 đếm số khoảng 100 ns kể từ 15/10/1582
moc_1582 = datetime(1582, 10, 15, tzinfo=timezone.utc)
thoi_gian_uuid = moc_1582 + timedelta(microseconds=ma_don.time // 10)
print("Thời điểm UUID:", thoi_gian_uuid.astimezone(VN))
print("Clock sequence:", ma_don.clock_seq)
print("Node:", format(ma_don.node, "012x"))
# ----- 2. Giải mã ULID (Mô tả) -----
BANG_CHU = "0123456789ABCDEFGHJKMNPQRSTVWXYZ" # Crockford Base32
def ulid_sang_thoi_gian(ulid):
mili_giay = 0
for ky_tu in ulid[:10]: # 10 ký tự đầu = 48 bit thời gian
mili_giay = mili_giay * 32 + BANG_CHU.index(ky_tu)
return datetime.fromtimestamp(mili_giay / 1000, tz=VN)
mo_ta = "01KRJ0NW97G2J53JDWE0F7X90S"
thoi_gian_ulid = ulid_sang_thoi_gian(mo_ta)
print("Thời điểm ULID:", thoi_gian_ulid)
# ----- 3. So sánh hai mốc thời gian -----
chenh = (thoi_gian_uuid - thoi_gian_ulid).total_seconds() * 1000
print(f"UUID tạo sau ULID: {chenh:.0f} ms")Hai chỗ cần để ý:
ma_don.time // 10: ma_don.time là số khoảng 100 ns tính từ 15/10/1582. Dấu // là phép chia lấy phần nguyên trong Python (ví dụ 25 // 10 = 2). Vì 1 micro giây = 10 × 100 ns, chia 10 sẽ ra số micro giây, đúng đơn vị mà timedelta nhận.for trong hàm ULID: mỗi ký tự Base32 mang 5 bit, nên nhân 32 rồi cộng dồn.Phiên bản UUID: 1
Thời điểm UUID: 2026-05-14 08:12:37.628487+07:00
Clock sequence: 11031
Node: 5ea1c3d07f24
Thời điểm ULID: 2026-05-14 08:12:37.415000+07:00
UUID tạo sau ULID: 213 msVà… khớp luôn! Cả hai đều chỉ về 08:12:37 ngày 14/05/2026, ULID tạo trước UUID khoảng 213 ms. Nhiều khả năng ULID là mã chuyến đi nội bộ, còn UUID là mã giao dịch gửi sang MoMo.
| Loại | Độ dài | Có thời gian? | Sắp xếp theo thời gian? |
|---|---|---|---|
| UUID v1 | 36 ký tự | Có (100 ns) | Không theo chuỗi |
| UUID v4 | 36 ký tự | Không, ngẫu nhiên hoàn toàn | Không |
| UUID v7 | 36 ký tự | Có (mili giây) | Có |
| ULID | 26 ký tự | Có (mili giây) | Có |
ID tăng theo thời gian giúp index cơ sở dữ liệu chèn gọn, vì bản ghi mới luôn nằm cuối. UUID v4 thì chèn lung tung khắp index. Vì vậy nhiều hệ thống mới chọn ULID hoặc UUID v7.
Lưu ý: UUID v1, UUID v7 và ULID đều để lộ thời điểm tạo. Không dùng chúng làm token bí mật như mã đặt lại mật khẩu. Trường hợp đó hãy dùng chuỗi ngẫu nhiên, ví dụ module secrets của Python.
uuid.uuid1() tạo một UUID ngay trên máy bạn, giải mã bằng hàm trong bài rồi so với giờ hệ thống. Nếu chưa quen import, xem lại bài Sử dụng module và package trong Python.Tóm lại, một mã đơn hàng nhìn “vô hại” vẫn có thể tiết lộ giờ giao dịch. Biết cách đọc ID sẽ giúp bạn chọn đúng loại ID khi tự thiết kế hệ thống.