J'ai un champ d'horodatage dans un fichier csv que je charge dans un cadre de données à l'aide de la bibliothèque spark csv. Le même morceau de code fonctionne sur ma machine locale avec la version Spark 2.0 mais génère une erreur sur Azure Hortonworks HDP 3.5 et 3.6.
J'ai vérifié et Azure HDInsight 3.5 utilise également la même version de Spark, donc je ne pense pas que ce soit un problème avec la version de Spark.
import org.Apache.spark.sql.types._
val sourceFile = "C:\\2017\\datetest"
val sourceSchemaStruct = new StructType()
.add("EventDate",DataTypes.TimestampType)
.add("Name",DataTypes.StringType)
val df = spark.read
.format("com.databricks.spark.csv")
.option("header","true")
.option("delimiter","|")
.option("mode","FAILFAST")
.option("inferSchema","false")
.option("dateFormat","yyyy/MM/dd HH:mm:ss.SSS")
.schema(sourceSchemaStruct)
.load(sourceFile)
L'exception entière est la suivante:
Caused by: Java.lang.IllegalArgumentException: Timestamp format must be yyyy-mm-dd hh:mm:ss[.fffffffff]
at Java.sql.Timestamp.valueOf(Timestamp.Java:237)
at org.Apache.spark.sql.catalyst.util.DateTimeUtils$.stringToTime(DateTimeUtils.scala:179)
at org.Apache.spark.sql.execution.datasources.csv.UnivocityParser$$anonfun$makeConverter$9$$anonfun$apply$13$$anonfun$apply$2.apply$mcJ$sp(UnivocityParser.scala:142)
at org.Apache.spark.sql.execution.datasources.csv.UnivocityParser$$anonfun$makeConverter$9$$anonfun$apply$13$$anonfun$apply$2.apply(UnivocityParser.scala:142)
at org.Apache.spark.sql.execution.datasources.csv.UnivocityParser$$anonfun$makeConverter$9$$anonfun$apply$13$$anonfun$apply$2.apply(UnivocityParser.scala:142)
at scala.util.Try.getOrElse(Try.scala:79)
at org.Apache.spark.sql.execution.datasources.csv.UnivocityParser$$anonfun$makeConverter$9$$anonfun$apply$13.apply(UnivocityParser.scala:139)
at org.Apache.spark.sql.execution.datasources.csv.UnivocityParser$$anonfun$makeConverter$9$$anonfun$apply$13.apply(UnivocityParser.scala:135)
at org.Apache.spark.sql.execution.datasources.csv.UnivocityParser.org$Apache$spark$sql$execution$datasources$csv$UnivocityParser$$nullSafeDatum(UnivocityParser.scala:179)
at org.Apache.spark.sql.execution.datasources.csv.UnivocityParser$$anonfun$makeConverter$9.apply(UnivocityParser.scala:135)
at org.Apache.spark.sql.execution.datasources.csv.UnivocityParser$$anonfun$makeConverter$9.apply(UnivocityParser.scala:134)
at org.Apache.spark.sql.execution.datasources.csv.UnivocityParser.org$Apache$spark$sql$execution$datasources$csv$UnivocityParser$$convert(UnivocityParser.scala:215)
at org.Apache.spark.sql.execution.datasources.csv.UnivocityParser.parse(UnivocityParser.scala:187)
at org.Apache.spark.sql.execution.datasources.csv.UnivocityParser$$anonfun$5.apply(UnivocityParser.scala:304)
at org.Apache.spark.sql.execution.datasources.csv.UnivocityParser$$anonfun$5.apply(UnivocityParser.scala:304)
at org.Apache.spark.sql.execution.datasources.FailureSafeParser.parse(FailureSafeParser.scala:61)
... 27 more
Le fichier csv a une seule ligne comme suit:
"EventDate"|"Name"
"2016/12/19 00:43:27.583"|"adam"
TL; DR Utilise l'option timestampFormat
(pas dateFormat
).
J'ai réussi à le reproduire dans la dernière version de Spark 2.3.0-SNAPSHOT (construit à partir du maître).
// OS Shell
$ cat so-43259485.csv
"EventDate"|"Name"
"2016/12/19 00:43:27.583"|"adam"
// spark-Shell
scala> spark.version
res1: String = 2.3.0-SNAPSHOT
case class Event(EventDate: Java.sql.Timestamp, Name: String)
import org.Apache.spark.sql.Encoders
val schema = Encoders.product[Event].schema
scala> spark
.read
.format("csv")
.option("header", true)
.option("mode","FAILFAST")
.option("delimiter","|")
.schema(schema)
.load("so-43259485.csv")
.show(false)
17/04/08 11:03:42 ERROR Executor: Exception in task 0.0 in stage 7.0 (TID 7)
Java.lang.IllegalArgumentException: Timestamp format must be yyyy-mm-dd hh:mm:ss[.fffffffff]
at Java.sql.Timestamp.valueOf(Timestamp.Java:237)
at org.Apache.spark.sql.catalyst.util.DateTimeUtils$.stringToTime(DateTimeUtils.scala:167)
at org.Apache.spark.sql.execution.datasources.csv.UnivocityParser$$anonfun$makeConverter$9$$anonfun$apply$17$$anonfun$apply$6.apply$mcJ$sp(UnivocityParser.scala:146)
at org.Apache.spark.sql.execution.datasources.csv.UnivocityParser$$anonfun$makeConverter$9$$anonfun$apply$17$$anonfun$apply$6.apply(UnivocityParser.scala:146)
at org.Apache.spark.sql.execution.datasources.csv.UnivocityParser$$anonfun$makeConverter$9$$anonfun$apply$17$$anonfun$apply$6.apply(UnivocityParser.scala:146)
at scala.util.Try.getOrElse(Try.scala:79)
La ligne correspondante dans les sources Spark est la "cause première" du problème:
Timestamp.valueOf(s)
Après avoir lu le javadoc de Timestamp.valueOf , vous pouvez apprendre que l’argument doit être:
horodatage au format
yyyy-[m]m-[d]d hh:mm:ss[.f...]
. Les fractions de secondes peuvent être omises. Le zéro initial pour mm et dd peut également être omis.
Remarque "Les secondes fractionnaires peuvent être omises", nous allons donc le couper en chargeant d'abord EventDate en tant que chaîne et seulement après avoir supprimé les fractions de secondes inutiles, convertissons-les en horodatage.
val eventsAsString = spark.read.format("csv")
.option("header", true)
.option("mode","FAILFAST")
.option("delimiter","|")
.load("so-43259485.csv")
Il s'avère que pour les champs de type TimestampType
Spark utilise l'option timestampFormat
first si elle est définie et que si elle n'utilise pas le code qu'il utilise Timestamp.valueOf
.
Il s'avère que le correctif consiste simplement à utiliser l'option timestampFormat
(et non pas dateFormat
!).
val df = spark.read
.format("com.databricks.spark.csv")
.option("header","true")
.option("delimiter","|")
.option("mode","FAILFAST")
.option("inferSchema","false")
.option("timestampFormat","yyyy/MM/dd HH:mm:ss.SSS")
.schema(sourceSchemaStruct)
.load(sourceFile)
scala> df.show(false)
+-----------------------+----+
|EventDate |Name|
+-----------------------+----+
|2016-12-19 00:43:27.583|adam|
+-----------------------+----+
Utilisez l'inférence de schéma dans CSV en utilisant l'option inferSchema
avec votre timestampFormat
personnalisé.
Il est important de déclencher l'inférence de schéma en utilisant inferSchema
pour timestampFormat
pour prendre effet.
val events = spark.read
.format("csv")
.option("header", true)
.option("mode","FAILFAST")
.option("delimiter","|")
.option("inferSchema", true)
.option("timestampFormat", "yyyy/MM/dd HH:mm:ss")
.load("so-43259485.csv")
scala> events.show(false)
+-------------------+----+
|EventDate |Name|
+-------------------+----+
|2016-12-19 00:43:27|adam|
+-------------------+----+
scala> events.printSchema
root
|-- EventDate: timestamp (nullable = true)
|-- Name: string (nullable = true)
val events = eventsAsString
.withColumn("date", split($"EventDate", " ")(0))
.withColumn("date", translate($"date", "/", "-"))
.withColumn("time", split($"EventDate", " ")(1))
.withColumn("time", split($"time", "[.]")(0)) // <-- remove millis part
.withColumn("EventDate", concat($"date", lit(" "), $"time")) // <-- make EventDate right
.select($"EventDate" cast "timestamp", $"Name")
scala> events.printSchema
root
|-- EventDate: timestamp (nullable = true)
|-- Name: string (nullable = true)
events.show(false)
scala> events.show
+-------------------+----+
| EventDate|Name|
+-------------------+----+
|2016-12-19 00:43:27|adam|
+-------------------+----+
Depuis Spark 2.2, vous pouvez utiliser la fonction to_timestamp
pour effectuer la conversion de chaîne en horodatage.
eventsAsString.select($"EventDate", to_timestamp($"EventDate", "yyyy/MM/dd HH:mm:ss.SSS")).show(false)
scala> eventsAsString.select($"EventDate", to_timestamp($"EventDate", "yyyy/MM/dd HH:mm:ss.SSS")).show(false)
+-----------------------+----------------------------------------------------+
|EventDate |to_timestamp(`EventDate`, 'yyyy/MM/dd HH:mm:ss.SSS')|
+-----------------------+----------------------------------------------------+
|2016/12/19 00:43:27.583|2016-12-19 00:43:27 |
+-----------------------+----------------------------------------------------+
J'ai cherché ce problème et découvert la page officielle du problème Github https://github.com/databricks/spark-csv/pull/280 qui a corrigé un bug lié à l'analyse des données avec le format de date personnalisé. J’ai passé en revue certains codes sources et, selon le code code , pour connaître le motif de votre problème qui est défini par inferSchema
avec la valeur par défaut false
comme ci-dessous.
inferSchema
: déduit automatiquement les types de colonne. Il nécessite un passage supplémentaire sur les données et est false par défaut
Veuillez changer inferSchema
avec true
pour votre format de date yyyy/MM/dd HH:mm:ss.SSS
en utilisant SimpleDateFormat
.