ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

StarRocks from_unixtime 函数详解:UNIX 时间戳与日期时间格式的转换、时区处理与底层实现

StarRocks from_unixtime 函数详解:UNIX 时间戳与日期时间格式的转换、时区处理与底层实现 StarRocks from_unixtime 函数详解UNIX 时间戳与日期时间格式的转换、时区处理与底层实现【免费下载链接】starrocksThe worlds fastest open query engine for sub-second analytics both on and off the data lakehouse. With the flexibility to support nearly any scenario, StarRocks provides best-in-class performance for multi-dimensional analytics, real-time analytics, and ad-hoc queries. A Linux Foundation project.项目地址: https://gitcode.com/GitHub_Trending/st/starrocksfrom_unixtime是 StarRocks 中用于将 UNIX 时间戳从 1970-01-01 00:00:00 UTC 起算的秒数转换为人类可读的日期时间字符串的核心内置函数广泛用于报表展示、日志解析、分区裁剪与 ETL 时间字段格式化等场景。本文以官方函数文档 from_unixtime.md 为骨架结合 FE前端与 BE后端源码及测试用例深入讲解其语法、参数边界、格式符、时区行为与底层实现原理帮助你准确使用该函数并理解其在查询优化中的特殊地位。一、函数功能与典型应用场景from_unixtime将一个 BIGINT 类型的 UNIX 时间戳转换为指定格式的时间字符串默认输出格式为yyyy-MM-dd HH:mm:ss。典型应用场景包括将表中存储的 epoch 秒如事件日志的event_ts列在查询时格式化为可读时间按天/小时维度做时间分桶如from_unixtime(ts, %Y-%m-%d)与unix_timestamp()函数配合完成「时间 ↔ 时间戳」的双向转换。其逆函数为unix_timestamp()将日期时间转为时间戳二者在 FE 的 FunctionSet.java 中作为一组时间类内置函数注册还包括毫秒级变体from_unixtime_ms。二、语法与参数说明VARCHAR from_unixtime(BIGINT unix_timestamp[, VARCHAR string_format])unix_timestamp类型BIGINT在常量折叠场景下FE 同时接受 INT 与 BIGINT 两种入参见下文源码。取值范围0到253402243199。超出该范围时返回NULL。对应时间范围1970-01-01 00:00:00到9999-12-30 11:59:59具体边界会因会话时区而有所偏移文档原话为 varies because of timezone。上界常量在 FE 中定义为TimeUtils.MAX_UNIX_TIMESTAMP 253402243199L见 TimeUtils.java。string_format类型VARCHAR可选参数指定输出格式缺省时使用默认格式yyyy-MM-dd HH:mm:ss。支持的格式符如下其余格式符视为非法返回NULL%Y: Year e.g.: 2014, 1900 %m: Month e.g.: 12, 09 %d: Day e.g.: 11, 01 %H: Hour e.g.: 23, 01, 12 %i: Minute e.g.: 05, 11 %s: Second e.g.: 59, 01同时文档明确说明该函数也支持 date_format 中定义的格式集合。date_format文档给出了更完整的格式符参考同样适用于from_unixtime的格式化输出%a | Abbreviated weekday name (Sun to Sat) %b | Abbreviated month name (Jan to Dec) %c | Numeric month name (0-12) %D | Day of the month as a numeric value, followed by suffix in English %d | Day of the month as a numeric value (00-31) %e | Day of the month as a numeric value (0-31) %f | Microseconds %H | Hour (00-23) %h | Hour (01-12) %I | Hour (01-12) %i | Minutes (00-59) %j | Day of the year (001-366) %k | Hour (0-23) %l | Hour (1-12) %M | Month name in full %m | Month name as a numeric value (00-12) %p | AM or PM %r | Time in 12 hour (hh:mm:ss AM or PM) %S | Seconds (00-59) %s | Seconds (00-59) %T | Time in 24 hour format (hh:mm:ss) %U | Week (00-53) where Sunday is the first day of the week %u | Week (00-53) where Monday is the first day of the week %V | Week (01-53) where Sunday is the first day of the week. Used with %X. %v | Week (01-53) where Monday is the first day of the week. Used with %x. %W | Weekday name in full %w | Day of the week where Sunday0 and Saturday6 %X | Year for the week where Sunday is the first day of the week. 4-digital value. Used with %V. %x | Year for the week where Monday is the first day of the week. 4-digital value. Used with %v. %Y | Year. 4-digital value. %y | Year. 2-digital value. %% | Represent %.注意区分大小写分钟是%i、秒是%s而%S同样表示秒%S与%s等价小时 24 小时制为%H12 小时制为%h/%I。三、返回值返回值类型为VARCHAR。当string_format指定的是 DATE 格式即只含年月日不包含时间部分时返回的是 VARCHAR 类型的 DATE 值例如2007-12-01。当时间戳超出取值范围小于 0 或大于253402243199或string_format为非法格式时返回NULL。四、使用示例以下示例均来自官方文档原文可直接在 MySQL 客户端中执行验证MySQL select from_unixtime(1196440219); --------------------------- | from_unixtime(1196440219) | --------------------------- | 2007-12-01 00:30:19 | --------------------------- MySQL select from_unixtime(1196440219, yyyy-MM-dd HH:mm:ss); -------------------------------------------------- | from_unixtime(1196440219, yyyy-MM-dd HH:mm:ss) | -------------------------------------------------- | 2007-12-01 00:30:19 | -------------------------------------------------- MySQL select from_unixtime(1196440219, %Y-%m-%d); ----------------------------------------- | from_unixtime(1196440219, %Y-%m-%d) | ----------------------------------------- | 2007-12-01 | ----------------------------------------- MySQL select from_unixtime(1196440219, %Y-%m-%d %H:%i:%s); -------------------------------------------------- | from_unixtime(1196440219, %Y-%m-%d %H:%i:%s) | -------------------------------------------------- | 2007-12-01 00:30:19 | --------------------------------------------------由示例可见第二个示例中的yyyy-MM-dd HH:mm:ss即默认输出格式第三个示例仅保留日期部分得到 VARCHAR 类型的 DATE 值第四个示例展示了%H:%i:%s时:分:秒的典型组合结果与默认格式一致。实战扩展基于时间戳做分组统计-- 按天统计事件数量 SELECT from_unixtime(event_ts, %Y-%m-%d) AS event_day, COUNT(*) FROM event_log GROUP BY from_unixtime(event_ts, %Y-%m-%d); -- 按小时统计并过滤非法越界时间戳 SELECT from_unixtime(ts, %Y-%m-%d %H:00:00), COUNT(*) FROM fact_table WHERE ts BETWEEN 0 AND 253402243199 GROUP BY from_unixtime(ts, %Y-%m-%d %H:00:00);五、时区行为与边界范围from_unixtime的转换结果依赖 StarRocks 的会话时区time_zone会话变量同一个时间戳在不同时区下会得到不同的本地时间。官方文档特别指出其可转换的时间范围上界9999-12-30 11:59:59会因时区而偏移。从 BE 的底层单元测试 datetime_value_test.cpp 中可以直观看到时区的影响默认时区为Asia/Shanghai即 UTC8TEST_F(DateTimeValueTest, from_unixtime) { char str[MAX_DTVALUE_STR_LEN]; DateTimeValue value; value.from_unixtime(570672000, TimezoneUtils::default_time_zone); value.to_string(str); ASSERT_STREQ(1988-02-01 08:00:00, str); value.from_unixtime(253402271999, TimezoneUtils::default_time_zone); value.to_string(str); ASSERT_STREQ(9999-12-31 23:59:59, str); value.from_unixtime(0, TimezoneUtils::default_time_zone); value.to_string(str); ASSERT_STREQ(1970-01-01 08:00:00, str); ASSERT_FALSE(value.from_unixtime(1586098092, 20:00)); ASSERT_FALSE(value.from_unixtime(1586098092, foo)); }该测试揭示三个关键点时区偏移时间戳0UTC 的 1970-01-01 00:00:00在Asia/Shanghai时区下输出为1970-01-01 08:00:00上界能力底层DateTimeValue::from_unixtime可处理更大的秒值253402271999输出9999-12-31 23:59:59而 SQL 层from_unixtime函数的上界由253402243199约束超出即返回NULL非法时区处理非法时区标识如20:00、foo会使转换失败返回false上层据此返回NULL。BE 表达式层的测试 time_functions_test.cpp 也验证了时区语义24 * 60 * 60即 1 天86400 秒在默认时区08:00下格式化为1970-01-01 16:00:00TEST_F(TimeFunctionsTest, fromUnixToDatetimeWithFormat) { ... auto tc1 Int32Column::create(); tc1-append(24 * 60 * 60); ... ColumnPtr result TimeFunctions::from_unix_to_datetime_with_format_32(_utils-get_fn_ctx(), columns).value(); ASSERT_EQ([1970-01-01 16:00:00, 1970-01-01 16:01:01, 1970-01-01 17:03:09], result-debug_string()); ... }六、源码级原理FE 常量折叠与 BE 的 cctz 实现FE 侧常量折叠与函数注册在 FE 的 ScalarOperatorFunctions.java 中from_unixtime被注册为可做常量折叠constant folding的内置函数共有三组重载ConstantFunction.List(list { ConstantFunction(name from_unixtime, argTypes {INT}, returnType VARCHAR, isMonotonic true), ConstantFunction(name from_unixtime, argTypes {BIGINT}, returnType VARCHAR, isMonotonic true) }) public static ConstantOperator fromUnixTime(ConstantOperator unixTime) throws AnalysisException { long value 0; if (unixTime.getType().isInt()) { value unixTime.getInt(); } else { value unixTime.getBigint(); } if (value 0 || value TimeUtils.MAX_UNIX_TIMESTAMP) { throw new AnalysisException( unixtime should larger than zero and less than TimeUtils.MAX_UNIX_TIMESTAMP); } ConstantOperator dl ConstantOperator.createDatetime( LocalDateTime.ofInstant(Instant.ofEpochSecond(value), TimeUtils.getTimeZone().toZoneId())); return ConstantOperator.createVarchar(dl.toString()); }关键实现细节参数类型常量场景下同时接受INT和BIGINT非常量场景以文档语法中的 BIGINT 为准范围校验value 0 || value TimeUtils.MAX_UNIX_TIMESTAMP即253402243199L时在常量折叠路径会直接抛出AnalysisException——也就是说如果 SQL 中的时间戳是字面量且越界分析阶段就会报错而不是等到执行期返回 NULL单调性标记三组注册均标注isMonotonic true这使优化器可以基于其单调性做表达式重写见下文第七节时区来源默认使用TimeUtils.getTimeZone()会话时区第三组重载允许显式传入时区参数ConstantFunction.List(list { ConstantFunction(name from_unixtime, argTypes {INT, VARCHAR, VARCHAR}, returnType VARCHAR, isMonotonic true), ConstantFunction(name from_unixtime, argTypes {BIGINT, VARCHAR, VARCHAR}, returnType VARCHAR, isMonotonic true) }) public static ConstantOperator fromUnixTime(ConstantOperator unixTime, ConstantOperator fmtLiteral, ConstantOperator timezone) throws AnalysisException { ... ConstantOperator dl ConstantOperator.createDatetime( LocalDateTime.ofInstant(Instant.ofEpochSecond(value), TimeUtils.getOrSystemTimeZone(timezone.getVarchar()).toZoneId())); return dateFormat(dl, fmtLiteral); }带格式符的版本最终复用dateFormat()完成格式化ScalarOperatorFunctions.java该函数对「unix 风格」格式%Y等走DateUtils.unixDatetimeFormatter对 Java 风格格式则走DateTimeFormatter.ofPattern。此外还提供了毫秒级变体from_unixtime_ms(BIGINT)将毫秒除以 1000 后执行相同转换。BE 侧基于 cctz 的底层实现BE 的 datetime_value.cpp 提供了三个重载的from_unixtime最终统一收敛到带微秒参数的版本bool DateTimeValue::from_unixtime(int64_t timestamp, const std::string timezone) { cctz::time_zone ctz; if (!TimezoneUtils::find_cctz_time_zone(timezone, ctz)) { return false; } return from_unixtime(timestamp, ctz); } bool DateTimeValue::from_unixtime(int64_t timestamp, const cctz::time_zone ctz) { return from_unixtime(timestamp, 0, ctz); } bool DateTimeValue::from_unixtime(int64_t timestamp, int64_t microsecond, const cctz::time_zone ctz) { static const cctz::time_pointcctz::sys_seconds epoch std::chrono::time_point_castcctz::sys_seconds(std::chrono::system_clock::from_time_t(0)); cctz::time_pointcctz::sys_seconds t epoch cctz::seconds(timestamp); const auto tp cctz::convert(t, ctz); _neg 0; _type TIME_DATETIME; _year tp.year(); _month tp.month(); _day tp.day(); _hour tp.hour(); _minute tp.minute(); _second tp.second(); _microsecond microsecond; return true; }实现要点时间运算基于 Google 的cctz库epoch cctz::seconds(timestamp)构造绝对时间点再通过cctz::convert(t, ctz)换算为指定时区的日历字段年/月/日/时/分/秒时区解析失败非法时区标识时返回false上层函数据此返回NULL该底层方法同时也是 BE 中now()、curdate()、curtime()、utc_timestamp()、convert_tz()等时间函数的公共基石见 time_functions.cpp如utc_timestamp使用00:00固定 UTC 时区now/curdate/curtime使用会话时区state-timezone_obj()。七、查询优化单调性重写与 hour(from_unixtime) 简化from_unixtime在 FE 中注册为isMonotonic true这意味着它对时间戳是单调的时间戳越大输出时间越晚。该属性被优化器用于两类重写MIN/MAX 单调重写在涉及from_unixtime的谓词下推与分区裁剪场景中优化器可以借助单调性对MIN/MAX表达式进行等价改写相关逻辑引用见 RewriteMinMaxByMonotonicFunctionRule.java 与 ListPartitionPruner.java。hour(from_unixtime(ts)) → hour_from_unixtime(ts)在 SimplifiedPredicateRule.java 中优化器将hour(from_unixtime(ts))这类嵌套调用直接简化为专用的hour_from_unixtime(ts)函数省去「先转日期时间再取小时」的中间步骤// Simplify hour(from_unixtime(ts)) to hour_from_unixtime(ts) // Also simplify hour(to_datetime(ts)) and hour(to_datetime(ts, 0)) to hour_from_unixtime(ts) private static ScalarOperator simplifiedHourFromUnixTime(CallOperator call) { ... // Case 1: hour(from_unixtime(ts)) - hour_from_unixtime(ts) ScalarOperator fromUnixTime lookupChild(call, x - x instanceof CallOperator ((CallOperator) x).getFnName().equalsIgnoreCase(FunctionSet.FROM_UNIXTIME)); if (fromUnixTime ! null) { ... return new CallOperator(FunctionSet.HOUR_FROM_UNIXTIME, call.getType(), fromUnixTime.getChildren(), fn); } ... }对应地BE 侧实现了TimeFunctions::hour_from_unixtimetime_functions.cpp并有完整的单元测试覆盖time_functions_test.cpp。因此在写「按小时分析」类查询时使用hour(from_unixtime(ts))与hour_from_unixtime(ts)会被优化器统一处理为高效实现。八、Trino 语法兼容对于从 Trino/Presto 迁移的 SQLStarRocks 的语法兼容层 Trino2SRFunctionCallTransformer.java 提供了from_unixtime的三种改写规则// to_unixtime - unix_timestamp registerFunctionTransformer(to_unixtime, 1, unix_timestamp, List.of(Expr.class)); // from_unixtime(unixtime) - from_unixtime registerFunctionTransformer(from_unixtime, 1, from_unixtime, List.of(Expr.class)); // from_unixtime(unixtime, zone) - convert_tz(from_unixtime(unixtime), time_zone, zone) registerFunctionTransformer(from_unixtime, 2, new FunctionCallExpr(convert_tz, List.of( new FunctionCallExpr(from_unixtime, List.of( new PlaceholderExpr(1, Expr.class))), new VariableExpr(time_zone), new PlaceholderExpr(2, Expr.class)))); // from_unixtime(unixtime, hours, minutes) - hours_add(minutes_add(from_unixtime(unixtime), minutes), hours) registerFunctionTransformer(from_unixtime, 3, new FunctionCallExpr(hours_add, List.of( new FunctionCallExpr(minutes_add, List.of( new FunctionCallExpr(from_unixtime, List.of( new PlaceholderExpr(1, Expr.class))), new PlaceholderExpr(3, Expr.class))), new PlaceholderExpr(2, Expr.class))));即Trino 的from_unixtime(ts)原样映射from_unixtime(ts, zone)被改写为「先按会话时区转换、再convert_tz到目标时区」from_unixtime(ts, hours, minutes)被改写为在转换结果上叠加小时与分钟偏移。相关改写有 FE 单测覆盖见 TrinoFunctionTransformTest.java。九、使用注意事项与最佳实践入参类型语法要求BIGINT若时间戳存储为字符串请先显式CAST(... AS BIGINT)避免隐式转换带来的不确定性。范围校验时间戳必须落在0到253402243199之间否则返回NULL常量字面量越界时FE 分析阶段会直接报错unixtime should larger than zero and less than ...。数据清洗时建议先用WHERE ts BETWEEN 0 AND 253402243199过滤脏数据。格式符大小写分钟用%i、秒用%s/%S、24 小时制用%H。传错格式符如用%M表示分钟会导致返回NULL或输出不符合预期因为不支持的格式符按文档约定返回NULL。时区一致性转换结果依赖会话时区time_zone。跨时区数据分析时应显式统一会话时区如SET time_zone Asia/Shanghai或借助convert_tz()做时区换算避免不同客户端得到不同结果。性能from_unixtime在 BE 中按行向量化执行并针对常量格式做了from_unix_prepare/from_unix_close的局部状态预编译见 time_functions_test.cpp同一查询内格式串可复用配合hour(from_unixtime(ts))的优化器简化适合大规模扫描场景。毫秒时间戳若手头是毫秒级13 位或微秒级16 位时间戳不能直接传给from_unixtime应先除以1000毫秒或1000000微秒转为秒也可使用 FE 注册的毫秒级函数from_unixtime_ms(BIGINT)直接处理毫秒输入。十、相关函数unix_timestamp()/unix_timestamp(datetime)from_unixtime的逆函数将日期时间转为 UNIX 时间戳from_unixtime_ms(BIGINT)毫秒级时间戳的转换变体注册于 FunctionSet.java常量实现见 ScalarOperatorFunctions.javadate_format(DATETIME, format)对已有日期时间做格式化from_unixtime的格式符与其完全一致详见 date_format 文档to_datetime(unixtime[, scale])将时间戳直接转为 DATETIME 类型支持 0/3/6 三种精度分别对应秒/毫秒/微秒适合需要保留 DATETIME 类型而非字符串的场景convert_tz(datetime, from_tz, to_tz)时区换算常用于跨时区场景下对from_unixtime结果的二次处理。十一、小结from_unixtime是 StarRocks 时间体系中最常用的转换函数之一它把 epoch 秒换算为受会话时区控制的本地时间字符串支持%Y/%m/%d/%H/%i/%s及date_format全套格式符越界时间戳与非法格式均返回NULL。其实现横跨 FE 常量折叠ScalarOperatorFunctions.java与 BE 的 cctz 底层转换datetime_value.cpp并被优化器利用单调性做重写、被 Trino 兼容层做语法映射。掌握其参数边界、时区语义与格式符约定即可在报表、ETL 与实时分析中稳定、高效地完成时间戳格式化。【免费下载链接】starrocksThe worlds fastest open query engine for sub-second analytics both on and off the data lakehouse. With the flexibility to support nearly any scenario, StarRocks provides best-in-class performance for multi-dimensional analytics, real-time analytics, and ad-hoc queries. A Linux Foundation project.项目地址: https://gitcode.com/GitHub_Trending/st/starrocks创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表