对于 1989Dec31 和 31Dec1989 等日期,pyspark 无法识别 spark.read.load() 中的 MMM dateFormat 模式

pyspark doesn't recognize MMM dateFormat pattern in spark.read.load() for dates like 1989Dec31 and 31Dec1989

我在 macOS Sierra 上使用 pyspark 时遇到了一个非常奇怪的问题。我的目标是解析 ddMMMyyyy 格式的日期(例如:31Dec1989)但会出错。我 运行 Spark 2.0.1,Python 2.7.10 和 Java 1.8.0_101。我也尝试使用 Anaconda 4.2.0(它附带 Python 2.7.12),但也出现错误。

具有相同 Java 版本和 Python 2.7.9 的 Ubuntu 服务器 15.04 上的相同代码可以正常工作。

关于 spark.read.load()official documentation 指出:

dateFormat – sets the string that indicates a date format. Custom date formats follow the formats at java.text.SimpleDateFormat. This applies to date type. If None is set, it uses the default value value, yyyy-MM-dd.

official Java documentation 谈到 MMM 是解析月份名称(如 JanDec 等的正确格式,但它会抛出很多以以下开头的错误java.lang.IllegalArgumentException。 文档指出 LLL 也可以使用,但 pyspark 无法识别并抛出 pyspark.sql.utils.IllegalArgumentException: u'Illegal pattern component: LLL'.

我知道 dateFormat 的另一种解决方案,但这是解析数据的最快方法,也是最简单的编码方法。我在这里错过了什么?

为了运行以下示例,您只需将test.csvtest.py放在同一目录中,然后运行<spark-bin-directory>/spark-submit <working-directory>/test.py

我的测试用例使用 ddMMMyyyy 格式

我有一个名为 test.csv 的纯文本文件,其中包含以下两行:

col1
31Dec1989

代码如下:

from pyspark.sql import SparkSession
from pyspark.sql.types import *

spark = SparkSession \
    .builder \
    .appName("My app") \
    .config("spark.some.config.option", "some-value") \
    .getOrCreate()

struct = StructType([StructField("column", DateType())])
df = spark.read.load(   "test.csv", \
                            schema=struct, \
                            format="csv", \
                            sep=",", \
                            header="true", \
                            dateFormat="ddMMMyyyy", \
                            mode="FAILFAST")
df.show()

我收到错误。我也尝试在日期和年份之前或之后移动月份名称(例如:1989Dec31yyyyMMMdd)但没有成功。

使用 ddMMyyyy 格式的工作示例

除日期格式外,此示例与上一个示例相同。 test.csv 现在包含:

col1
31121989

以下代码打印test.csv的内容:

from pyspark.sql import SparkSession
from pyspark.sql.types import *

spark = SparkSession \
    .builder \
    .appName("My app") \
    .config("spark.some.config.option", "some-value") \
    .getOrCreate()

struct = StructType([StructField("column", DateType())])
df = spark.read.load(   "test.csv", \
                            schema=struct, \
                            format="csv", \
                            sep=",", \
                            header="true", \
                            dateFormat="ddMMyyyy", \
                            mode="FAILFAST")
df.show()

输出如下(我省略了各种冗长的行):

+----------+
|    column|
+----------+
|1989-12-31|
+----------+

UPDATE1

我做了一个简单的 Java class 使用 java.text.SimpleDateFormat:

import java.text.*;
import java.util.Date;

class testSimpleDateFormat 
{
    public static void main(String[] args) 
    {
        SimpleDateFormat format = new SimpleDateFormat("yyyyMMMdd");
        String dateString = "1989Dec31";

        try {
            Date parsed = format.parse(dateString);
            System.out.println(parsed.toString());
        }
        catch(ParseException pe) {
            System.out.println("ERROR: Cannot parse \"" + dateString + "\"");
        }       
    }
}

此代码不适用于我的环境并抛出此错误:

java.text.ParseException: Unparseable date: "1989Dec31"

但在另一个系统上运行完美 (Ubuntu 15.04)。这似乎是一个 Java 问题,但我不知道如何解决。我安装了 Java 的最新可用版本并且我的所有软件都已更新。

有什么想法吗?


UPDATE2

我找到了如何通过指定 Locale.US:

使其在纯 Java 下工作
import java.text.*;
import java.util.Date;
import java.util.*;

class HelloWorldApp 
{
    public static void main(String[] args) 
    {
        SimpleDateFormat format = new SimpleDateFormat("yyyyMMMdd", Locale.US);
        String dateString = "1989Dec31";

        try {
            Date parsed = format.parse(dateString);
            System.out.println(parsed.toString());
        }
        catch(ParseException pe) {
            System.out.println(pe);
            System.out.println("ERROR: Cannot parse \"" + dateString + "\"");
        }       
    }
}

现在,问题变成了:如何在 pyspark 中指定 Java 的语言环境?

您已经将问题确定为 Spark 的 JVM 中的语言环境之一。您可以在启动 spark shell 后转到 http://localhost:4040/environment/ 来检查您的 Spark JVM 使用的默认国家和语言设置。在系统属性部分下搜索 "user.language" 和 user.country"。它应该是 USen

如果需要,您可以像这样更改它们。

选项 1:编辑 {SPARK_HOME}/conf 文件夹中的 spark-defaults.conf 文件。添加以下设置:

spark.executor.extraJavaOptions  -Duser.country=US -Duser.language=en
spark.driver.extraJavaOptions -Duser.country=US -Duser.language=en

选项 2:将选项作为命令行选项传递给 pyspark

  $pyspark  --conf spark.driver.extraJavaOptions="-Duser.country=US,-Duser.language=en" spark.executor.extraJavaOptions="-Duser.country=US,-Duser.language=en"

选项 3:更改 Mac OS 中的语言和地区。例如 - What settings in Mac OS X affect the `Locale` and `Calendar` inside Java?

P.S。 - 我只验证了选项 1 有效。我还没有尝试过其他 2 个。有关 Spark 配置的更多详细信息在此处 - http://spark.apache.org/docs/latest/configuration.html#runtime-environment

可能值得注意的是,这已于 2016 年 10 月 24 日在 Spark mailing list 上解决。根据原始发帖人:

This worked without setting other options: spark/bin/spark-submit --conf "spark.driver.extraJavaOptions=-Duser.language=en" test.py

并在 Spark 2.0.1 中被报告为 SPARK-18076(将 DateFormat、NumberFormat 中使用的默认区域设置修复为 Locale.US)并在 Spark 2.1.0 中得到解决。

此外,如果使用 Spark 2.1.0,提交者提出的特定问题不再需要上述解决方法(传入 --conf "spark.driver.extraJavaOptions=-Duser.language=en"),但一个显着的副作用是 Spark 2.1.0用户,如果您想解析非英语日期,则不能再传递 --conf "spark.driver.extraJavaOptions=-Duser.language=fr" 之类的内容,例如"31mai1989".

事实上,从 Spark 2.1.0 开始,当使用 spark.read() 加载 csv 时,我认为不再可能使用 dateFormat 选项来解析日期,例如 "31mai1989 ",即使您的默认语言环境是法语。我什至将 OS 中的默认区域和语言更改为法语,并传入了我能想到的几乎所有区域设置排列,即

JAVA_OPTS="-Duser.language=fr -Duser.country=FR -Duser.region=FR" \
JAVA_ARGS="-Duser.language=fr -Duser.country=FR -Duser.region=FR" \
LC_ALL=fr_FR.UTF-8 \
spark-submit \
--conf "spark.driver.extraJavaOptions=-Duser.country=FR -Duser.language=fr -Duser.region=FR" \
--conf "spark.executor.extraJavaOptions=-Duser.country=FR -Duser.language=fr -Duser.region=FR" \
test.py

无济于事,导致

java.lang.IllegalArgumentException
    at java.sql.Date.valueOf(Date.java:143)
    at org.apache.spark.sql.catalyst.util.DateTimeUtils$.stringToTime(DateTimeUtils.scala:137)

但同样,这只会影响在 Spark 2.1.0 中解析非英语日期。

我还没有测试过这个,但我会尝试以下方法:

--conf spark.executor.extraJavaOptions="-Duser.timezone=America/Los_Angeles"

--conf spark.driver.extraJavaOptions="-Duser.timezone=America/Los_Angeles"