Skip to content

Commit 0b06ce7

Browse files
committed
feat: Adds formatting utilities for Unix microseconds
1 parent e5eb6e0 commit 0b06ce7

File tree

5 files changed

+142
-11
lines changed

5 files changed

+142
-11
lines changed

src/iceberg/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ add_iceberg_test(util_test
111111
formatter_test.cc
112112
location_util_test.cc
113113
string_util_test.cc
114+
timepoint_test.cc
114115
truncate_util_test.cc
115116
url_encoder_test.cc
116117
uuid_test.cc

src/iceberg/test/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ iceberg_tests = {
8787
'formatter_test.cc',
8888
'location_util_test.cc',
8989
'string_util_test.cc',
90+
'timepoint_test.cc',
9091
'truncate_util_test.cc',
9192
'url_encoder_test.cc',
9293
'uuid_test.cc',

src/iceberg/test/timepoint_test.cc

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#include "iceberg/util/timepoint.h"
21+
22+
#include <chrono>
23+
24+
#include <gtest/gtest.h>
25+
26+
#include "iceberg/util/timepoint.h"
27+
28+
namespace iceberg {
29+
30+
TEST(TimePointTest, FormatTimePointMs) {
31+
// Unix timestamp for 2026-01-01T00:00:00 is 1767225600000
32+
auto time_point = TimePointMsFromUnixMs(1767225600000).value();
33+
EXPECT_EQ("2026-01-01T00:00:00.000", FormatTimePointMs(time_point));
34+
35+
// Unix timestamp for 2026-01-01T12:20:00.123
36+
auto time_point2 =
37+
TimePointMsFromUnixMs(1767225600123 + (12 * 3600 + 20 * 60) * 1000).value();
38+
EXPECT_EQ("2026-01-01T12:20:00.123", FormatTimePointMs(time_point2));
39+
40+
// Test with a date before 1970 (Unix epoch) - 1969-01-01T00:00:00
41+
// Unix timestamp for 1969-01-01T00:00:00 is -31536000000 ms from epoch
42+
auto time_point_before_epoch = TimePointMsFromUnixMs(-31536000000).value();
43+
EXPECT_EQ("1969-01-01T00:00:00.000", FormatTimePointMs(time_point_before_epoch));
44+
}
45+
46+
TEST(TimePointTest, FormatUnixMicro) {
47+
// Test with whole seconds (micros = 0) - 2026-01-01T00:00:00.000000
48+
int64_t unix_micro = 1767225600000000;
49+
EXPECT_EQ("2026-01-01T00:00:00", FormatUnixMicro(unix_micro));
50+
51+
// Test with milliseconds precision (micros ending in 000)
52+
int64_t unix_micro_ms = 1767225600001000;
53+
EXPECT_EQ("2026-01-01T00:00:00.001", FormatUnixMicro(unix_micro_ms));
54+
55+
// Test with full microsecond precision
56+
int64_t unix_micro_full = 1767225600001234;
57+
EXPECT_EQ("2026-01-01T00:00:00.001234", FormatUnixMicro(unix_micro_full));
58+
59+
// Test with a value that has more micros than a second
60+
int64_t unix_micro_over = 1767225661123456;
61+
EXPECT_EQ("2026-01-01T00:01:01.123456", FormatUnixMicro(unix_micro_over));
62+
63+
// Test with a date before 1970
64+
int64_t unix_micro_before_epoch = -31536000000000;
65+
EXPECT_EQ("1969-01-01T00:00:00", FormatUnixMicro(unix_micro_before_epoch));
66+
}
67+
68+
TEST(TimePointTest, FormatUnixMicroTz) {
69+
// Test with whole seconds (micros = 0)
70+
int64_t unix_micro = 1767225600000000;
71+
EXPECT_EQ("2026-01-01T00:00:00+00:00", FormatUnixMicroTz(unix_micro));
72+
73+
// Test with milliseconds precision (micros ending in 000)
74+
int64_t unix_micro_ms = 1767225600001000;
75+
EXPECT_EQ("2026-01-01T00:00:00.001+00:00", FormatUnixMicroTz(unix_micro_ms));
76+
77+
// Test with full microsecond precision
78+
int64_t unix_micro_full = 1767225600001234;
79+
EXPECT_EQ("2026-01-01T00:00:00.001234+00:00", FormatUnixMicroTz(unix_micro_full));
80+
81+
// Test with a value that has more micros than a second
82+
int64_t unix_micro_over = 1767225661123456;
83+
EXPECT_EQ("2026-01-01T00:01:01.123456+00:00", FormatUnixMicroTz(unix_micro_over));
84+
85+
// Test with a date before 1970
86+
int64_t unix_micro_before_epoch = -31536000000000;
87+
EXPECT_EQ("1969-01-01T00:00:00+00:00", FormatUnixMicroTz(unix_micro_before_epoch));
88+
}
89+
90+
} // namespace iceberg

src/iceberg/util/timepoint.cc

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@
2020
#include "iceberg/util/timepoint.h"
2121

2222
#include <chrono>
23-
#include <iomanip>
24-
#include <sstream>
2523

2624
namespace iceberg {
2725

@@ -46,18 +44,35 @@ int64_t UnixNsFromTimePointNs(TimePointNs time_point_ns) {
4644
}
4745

4846
std::string FormatTimePointMs(TimePointMs time_point_ms) {
49-
auto unix_ms = UnixMsFromTimePointMs(time_point_ms);
50-
auto time_t = std::chrono::system_clock::to_time_t(time_point_ms);
47+
return std::format("{:%FT%T}", time_point_ms);
48+
}
49+
50+
std::string FormatUnixMicro(int64_t unix_micro) {
51+
auto tp = std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>{
52+
std::chrono::seconds(unix_micro / kMicrosPerSecond)};
5153

52-
// Format as ISO 8601-like string: YYYY-MM-DD HH:MM:SS
53-
std::ostringstream oss;
54-
oss << std::put_time(std::gmtime(&time_t), "%Y-%m-%d %H:%M:%S");
54+
auto micros = unix_micro % kMicrosPerSecond;
55+
if (micros == 0) {
56+
return std::format("{:%FT%T}", tp);
57+
} else if (micros % kMicrosPerMillis == 0) {
58+
return std::format("{:%FT%T}.{:03d}", tp, micros / kMicrosPerMillis);
59+
} else {
60+
return std::format("{:%FT%T}.{:06d}", tp, micros);
61+
}
62+
}
5563

56-
// Add milliseconds
57-
auto ms = unix_ms % 1000;
58-
oss << "." << std::setfill('0') << std::setw(3) << ms << " UTC";
64+
std::string FormatUnixMicroTz(int64_t unix_micro) {
65+
auto tp = std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>{
66+
std::chrono::seconds(unix_micro / kMicrosPerSecond)};
5967

60-
return oss.str();
68+
auto micros = unix_micro % kMicrosPerSecond;
69+
if (micros == 0) {
70+
return std::format("{:%FT%T}+00:00", tp);
71+
} else if (micros % kMicrosPerMillis == 0) {
72+
return std::format("{:%FT%T}.{:03d}+00:00", tp, micros / kMicrosPerMillis);
73+
} else {
74+
return std::format("{:%FT%T}.{:06d}+00:00", tp, micros);
75+
}
6176
}
6277

6378
} // namespace iceberg

src/iceberg/util/timepoint.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ using TimePointMs =
3434
using TimePointNs =
3535
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>;
3636

37+
constexpr int64_t kMillisPerSecond = 1000;
38+
constexpr int64_t kMicrosPerMillis = 1000;
39+
constexpr int64_t kMicrosPerSecond = 1000000;
40+
3741
/// \brief Returns a TimePointMs from a Unix timestamp in milliseconds
3842
ICEBERG_EXPORT Result<TimePointMs> TimePointMsFromUnixMs(int64_t unix_ms);
3943

@@ -49,4 +53,24 @@ ICEBERG_EXPORT int64_t UnixNsFromTimePointNs(TimePointNs time_point_ns);
4953
/// \brief Returns a human-readable string representation of a TimePointMs
5054
ICEBERG_EXPORT std::string FormatTimePointMs(TimePointMs time_point_ms);
5155

56+
/// \brief Returns a human-readable string representation of a Unix timestamp in
57+
/// microseconds
58+
///
59+
/// The output will be one of the following forms, according to the precision of the
60+
/// timestamp:
61+
/// - yyyy-MM-dd HH:mm:ss
62+
/// - yyyy-MM-dd HH:mm:ss.SSS
63+
/// - yyyy-MM-dd HH:mm:ss.SSSSSS
64+
ICEBERG_EXPORT std::string FormatUnixMicro(int64_t unix_micro);
65+
66+
/// \brief Returns a human-readable string representation of a Unix timestamp in
67+
/// microseconds with time zone
68+
///
69+
/// The output will be one of the following forms, according to the precision of the
70+
/// timestamp:
71+
/// - yyyy-MM-dd HH:mm:ss+00:00
72+
/// - yyyy-MM-dd HH:mm:ss.SSS+00:00
73+
/// - yyyy-MM-dd HH:mm:ss.SSSSSS+00:00
74+
ICEBERG_EXPORT std::string FormatUnixMicroTz(int64_t unix_micro);
75+
5276
} // namespace iceberg

0 commit comments

Comments
 (0)