我有UTC和ISO8601的时间戳,但使用结构化流,它会自动转换为本地时间。有没有办法阻止这种转换?我想在UTC中使用它。
我从Kafka读取json数据,然后使用from_json
Spark函数解析它们。
输入:
{"Timestamp":"2015-01-01T00:00:06.222Z"}
流速:
SparkSession
.builder()
.master("local[*]")
.appName("my-app")
.getOrCreate()
.readStream()
.format("kafka")
... //some magic
.writeStream()
.format("console")
.start()
.awaitTermination();
架构:
StructType schema = DataTypes.createStructType(new StructField[] {
DataTypes.createStructField("Timestamp", DataTypes.TimestampType, true),});
输出:
+--------------------+
| Timestamp|
+--------------------+
|2015-01-01 01:00:...|
|2015-01-01 01:00:...|
+--------------------+
如您所见,小时数自行增加。
PS:我试图尝试from_utc_timestamp
Spark功能,但没有运气。
答案 0 :(得分:21)
对我来说,它可以使用:
spark.conf.set("spark.sql.session.timeZone", "UTC")
它告诉spark SQL使用UTC作为时间戳的默认时区。我在spark SQL中使用它,例如:
select *, cast('2017-01-01 10:10:10' as timestamp) from someTable
我知道它在2.0.1中不起作用。但适用于Spark 2.2。我也在SQLTransformer
中使用过它。
我不确定流媒体。
答案 1 :(得分:12)
注意:
这个答案在Spark<中主要 非常有用2.2。对于较新的Spark版本,请the answer
查看astro-asz但是我们应该注意到,截至今天(Spark 2.4.0),spark.sql.session.timeZone
未设置user.timezone
(java.util.TimeZone.getDefault
)。因此,单独设置``spark.sql.session.timeZone`会导致SQL和非SQL组件使用不同时区设置的相当尴尬的情况。
因此,即使设置了user.timezone
,我仍建议明确设置spark.sql.session.timeZone
。
TL; DR 不幸的是,这就是Spark现在处理时间戳的方式,除了直接在纪元时间上运行之外,实际上没有内置替代方案,而不使用日期/时间工具。
您可以对Spark开发人员列表进行深入讨论:SQL TIMESTAMP semantics vs. SPARK-18350
到目前为止,我发现最干净的解决方法是为驱动程序和执行程序设置-Duser.timezone
到UTC
。例如,提交:
bin/spark-shell --conf "spark.driver.extraJavaOptions=-Duser.timezone=UTC" \
--conf "spark.executor.extraJavaOptions=-Duser.timezone=UTC"
或通过调整配置文件(spark-defaults.conf
):
spark.driver.extraJavaOptions -Duser.timezone=UTC
spark.executor.extraJavaOptions -Duser.timezone=UTC
答案 2 :(得分:0)
尽管提供了两个非常好的答案,但我发现它们都有些沉重,无法解决问题。我不想要任何需要修改整个应用程序中的时区解析行为的方法,或者想要一种可以更改我的JVM的默认时区的方法。痛苦过后,我确实找到了解决方案,我将在下面分享...
将时间[/ date]字符串解析为时间戳以进行日期操作,然后正确地将结果呈现回
首先,让我们解决以下问题:如何使Spark SQL正确地将date [/ time]字符串(给定格式)解析为时间戳,然后正确地将该时间戳渲染回以使其显示相同的日期[/ time] ]作为原始字符串输入。通用方法是:
- convert a date[/time] string to time stamp [via to_timestamp]
[ to_timestamp seems to assume the date[/time] string represents a time relative to UTC (GMT time zone) ]
- relativize that timestamp to the timezone we are in via from_utc_timestamp
下面的测试代码实现了这种方法。 “我们所在的时区”作为第一个参数传递给timeTricks方法。该代码将输入字符串“ 1970-01-01”转换为localizedTimeStamp(通过from_utc_timestamp),并验证该时间戳记的“ valueOf”是否与“ 1970-01-01 00:00:00”相同。
object TimeTravails {
def main(args: Array[String]): Unit = {
import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.functions._
val spark: SparkSession = SparkSession.builder()
.master("local[3]")
.appName("SparkByExample")
.getOrCreate()
spark.sparkContext.setLogLevel("ERROR")
import spark.implicits._
import java.sql.Timestamp
def timeTricks(timezone: String): Unit = {
val df2 = List("1970-01-01").toDF("timestr"). // can use to_timestamp even without time parts !
withColumn("timestamp", to_timestamp('timestr, "yyyy-MM-dd")).
withColumn("localizedTimestamp", from_utc_timestamp('timestamp, timezone)).
withColumn("weekday", date_format($"localizedTimestamp", "EEEE"))
val row = df2.first()
println("with timezone: " + timezone)
df2.show()
val (timestamp, weekday) = (row.getAs[Timestamp]("localizedTimestamp"), row.getAs[String]("weekday"))
timezone match {
case "UTC" =>
assert(timestamp == Timestamp.valueOf("1970-01-01 00:00:00") && weekday == "Thursday")
case "PST" | "GMT-8" | "America/Los_Angeles" =>
assert(timestamp == Timestamp.valueOf("1969-12-31 16:00:00") && weekday == "Wednesday")
case "Asia/Tokyo" =>
assert(timestamp == Timestamp.valueOf("1970-01-01 09:00:00") && weekday == "Thursday")
}
}
timeTricks("UTC")
timeTricks("PST")
timeTricks("GMT-8")
timeTricks("Asia/Tokyo")
timeTricks("America/Los_Angeles")
}
}
结构化流传输问题的解释为将输入日期[/ time]字符串解释为UTC(而非本地时间)
下面的代码说明了如何应用上述技巧(稍作修改),以纠正时间戳因本地时间与GMT之间的偏移而发生偏移的问题。
object Struct {
import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.functions._
def main(args: Array[String]): Unit = {
val timezone = "PST"
val spark: SparkSession = SparkSession.builder()
.master("local[3]")
.appName("SparkByExample")
.getOrCreate()
spark.sparkContext.setLogLevel("ERROR")
val df = spark.readStream
.format("socket")
.option("host", "localhost")
.option("port", "9999")
.load()
import spark.implicits._
val splitDf = df.select(split(df("value"), " ").as("arr")).
select($"arr" (0).as("tsString"), $"arr" (1).as("count")).
withColumn("timestamp", to_timestamp($"tsString", "yyyy-MM-dd"))
val grouped = splitDf.groupBy(window($"timestamp", "1 day", "1 day").as("date_window")).count()
val tunedForDisplay =
grouped.
withColumn("windowStart", to_utc_timestamp($"date_window.start", timezone)).
withColumn("windowEnd", to_utc_timestamp($"date_window.end", timezone))
tunedForDisplay.writeStream
.format("console")
.outputMode("update")
.option("truncate", false)
.start()
.awaitTermination()
}
}
代码需要通过套接字输入...我使用像这样启动的程序“ nc”(网猫):
nc -l 9999
然后我启动Spark程序,并为net cat提供一行输入:
1970-01-01 4
我得到的输出说明了偏移偏移的问题:
-------------------------------------------
Batch: 1
-------------------------------------------
+------------------------------------------+-----+-------------------+-------------------+
|date_window |count|windowStart |windowEnd |
+------------------------------------------+-----+-------------------+-------------------+
|[1969-12-31 16:00:00, 1970-01-01 16:00:00]|1 |1970-01-01 00:00:00|1970-01-02 00:00:00|
+------------------------------------------+-----+-------------------+-------------------+
请注意,date_window的开始和结束距离输入有8个小时的偏移(因为我位于GMT-7 / 8时区PST)。但是,我使用to_utc_timestamp纠正了这一转变,以获取包含输入的一天窗口的正确开始和结束日期时间:1970-01-01 00:00:00,1970-01-02 00:00:00。 / p>
请注意,在显示的第一段代码中,我们使用了from_utc_timestamp,而在结构化流解决方案中,我们使用了to_utc_timestamp。我还没有弄清楚在给定情况下要使用这两个中的哪一个。 (如果您知道,请提示我!)。
答案 3 :(得分:0)
另一个对我有用的解决方案是将jvm默认时区设置为目标时区(在您的情况下为UTC)。
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
我在将spark数据帧写入数据库之前添加了以上代码。