J'ai mon horodatage en UTC et ISO8601, mais en utilisant le streaming structuré, il est automatiquement converti en heure locale. Existe-t-il un moyen d'arrêter cette conversion? Je voudrais l'avoir en UTC.
Je lis les données json de Kafka puis je les analyse en utilisant le from_json
Spark.
Contribution:
{"Timestamp":"2015-01-01T00:00:06.222Z"}
Couler:
SparkSession
.builder()
.master("local[*]")
.appName("my-app")
.getOrCreate()
.readStream()
.format("kafka")
... //some magic
.writeStream()
.format("console")
.start()
.awaitTermination();
Schéma:
StructType schema = DataTypes.createStructType(new StructField[] {
DataTypes.createStructField("Timestamp", DataTypes.TimestampType, true),});
Sortie:
+--------------------+
| Timestamp|
+--------------------+
|2015-01-01 01:00:...|
|2015-01-01 01:00:...|
+--------------------+
Comme vous pouvez le voir, l'heure s'est incrémentée d'elle-même.
PS: j'ai essayé d'expérimenter le from_utc_timestamp
Spark fonction, mais pas de chance.
Pour moi, cela a fonctionné d'utiliser:
spark.conf.set("spark.sql.session.timeZone", "UTC")
Il indique au SQL spark pour utiliser UTC comme fuseau horaire par défaut pour les horodatages. Je l'ai utilisé dans spark SQL par exemple:
select *, cast('2017-01-01 10:10:10' as timestamp) from someTable
Je sais que cela ne fonctionne pas dans 2.0.1. mais fonctionne dans Spark 2.2. J'ai également utilisé dans SQLTransformer
et cela a fonctionné.
Je ne suis pas sûr du streaming cependant.
Remarque :
Cette réponse est utile principalement dans Spark <2.2. Pour les versions plus récentes Spark version voir la réponse = par astro-asz
Cependant, nous devons noter qu'à partir d'aujourd'hui (Spark 2.4.0), spark.sql.session.timeZone
ne définit pas user.timezone
(Java.util.TimeZone.getDefault
). Ainsi, la définition de `` spark.sql.session.timeZone` seule peut entraîner une situation plutôt délicate où les composants SQL et non SQL utilisent des paramètres de fuseau horaire différents.
Par conséquent, je recommande toujours de définir user.timezone
explicitement, même si spark.sql.session.timeZone
est défini.
TL; DR Malheureusement, c'est ainsi que Spark gère les horodatages en ce moment et il n'y a vraiment aucune alternative intégrée, autre que de fonctionner directement à l'heure Epoch, sans utiliser les utilitaires de date/heure.
Vous pouvez une discussion perspicace sur la Spark: sémantique SQL TIMESTAMP vs SPARK-1835
La solution la plus propre que j'ai trouvée jusqu'à présent consiste à définir -Duser.timezone
à UTC
pour le pilote et les exécuteurs. Par exemple avec soumettre:
bin/spark-Shell --conf "spark.driver.extraJavaOptions=-Duser.timezone=UTC" \
--conf "spark.executor.extraJavaOptions=-Duser.timezone=UTC"
ou en ajustant les fichiers de configuration (spark-defaults.conf
):
spark.driver.extraJavaOptions -Duser.timezone=UTC
spark.executor.extraJavaOptions -Duser.timezone=UTC
Bien que deux très bonnes réponses aient été fournies, je les ai trouvées toutes les deux un peu lourdes pour résoudre le problème. Je ne voulais rien qui nécessiterait de modifier le comportement d'analyse du fuseau horaire dans l'ensemble de l'application, ou une approche qui modifierait le fuseau horaire par défaut de ma machine virtuelle Java. J'ai trouvé une solution après beaucoup de douleur, que je partagerai ci-dessous ...
Analyse des chaînes de temps [/ date] en horodatages pour les manipulations de date, puis restitue correctement le résultat
Tout d'abord, abordons la question de savoir comment obtenir Spark SQL pour analyser correctement une chaîne de date [/ heure] (en fonction d'un format) dans un horodatage, puis restituer correctement cet horodatage pour qu'il affiche la même date [/ heure] que l'entrée de chaîne d'origine. L'approche générale est la suivante:
- 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
Le code de test ci-dessous implémente cette approche. 'timezone we are in' est passé comme premier argument à la méthode timeTricks. Le code convertit la chaîne d'entrée "1970-01-01" en localizedTimeStamp (via from_utc_timestamp) et vérifie que la "valeurOf" de cet horodatage est la même que "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")
}
}
Solution au problème du Streaming Structuré Interprétant les chaînes de date [/ heure] entrantes comme UTC (pas l'heure locale)
Le code ci-dessous illustre comment appliquer les astuces ci-dessus (avec une légère modification) afin de corriger le problème des horodatages décalés par le décalage entre l'heure locale et 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()
}
}
Le code nécessite une entrée via une socket ... J'utilise le programme 'nc' (net cat) démarré comme ceci:
nc -l 9999
Ensuite, je démarre le programme Spark et je fournis à net cat une ligne d'entrée:
1970-01-01 4
La sortie que j'obtiens illustre le problème du décalage de décalage:
-------------------------------------------
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|
+------------------------------------------+-----+-------------------+-------------------+
Notez que le début et la fin de date_window sont décalés de huit heures par rapport à l'entrée (car je suis dans le fuseau horaire GMT-7/8, PST). Cependant, je corrige ce décalage en utilisant to_utc_timestamp pour obtenir les heures de date de début et de fin appropriées pour la fenêtre d'un jour qui subsume l'entrée: 1970-01-01 00: 00: 00,1970-01-02 00:00:00.
Notez que dans le premier bloc de code présenté, nous avons utilisé from_utc_timestamp, tandis que pour la solution de streaming structuré, nous avons utilisé to_utc_timestamp. Je n'ai pas encore trouvé lequel de ces deux utiliser dans une situation donnée. (Veuillez m'indiquer si vous le savez!).