Datas e timestamps
Essa documentação foi descontinuada e pode não estar atualizada. O produto, serviço ou tecnologia mencionados neste conteúdo não são mais suportados. Veja os padrões de data e hora.
Os tipos de dados Date
e Timestamp
mudaram significativamente no Databricks Runtime 7.0. Este artigo descreve:
- O tipo
Date
e o calendário associado. - O tipo
Timestamp
e como ele se relaciona com os fusos horários. Ele também explica os detalhes da resolução de deslocamento de fuso horário e as mudanças sutis de comportamento na nova API de tempo no Java 8, usada pelo Databricks Runtime 7.0. - APIs para construir valores de data e registro de data e hora.
- Armadilhas comuns e práticas recomendadas para coletar objetos de data e carimbo de data/hora no driver do Apache Spark.
Datas e calendários
Um Date
é uma combinação dos campos de ano, mês e dia, como (ano=2012, mês=12, dia=31). No entanto, os valores dos campos de ano, mês e dia têm restrições para garantir que o valor da data seja uma data válida no mundo real. Por exemplo, o valor do mês deve ser de 1 a 12, o valor do dia deve ser de 1 a 28,29,30 ou 31 (dependendo do ano e do mês) e assim por diante. O tipo Date
não considera os fusos horários.
Calendários
As restrições nos campos Date
são definidas por um dos muitos calendários possíveis. Alguns, como o calendário lunar, são usados somente em regiões específicas. Alguns, como o calendário juliano, são usados apenas na história. O padrão internacional de fato é o calendário gregoriano, usado em quase todo o mundo para fins civis. Foi introduzido em 1582 e também foi estendido para suportar datas anteriores a 1582. Esse calendário estendido é chamado de calendário gregoriano proléptico.
Databricks Runtime O 7.0 usa o calendário gregoriano proléptico, que já está sendo usado por outros sistemas de dados, como Pandas, R e Apache Arrow. Databricks Runtime 6.x e abaixo usaram uma combinação dos calendários juliano e gregoriano: para datas anteriores a 1582, foi usado o calendário juliano; para datas posteriores a 1582, foi usado o calendário gregoriano. Isso é herdado da API legada java.sql.Date
, que foi substituída no Java 8 por java.time.LocalDate
, que usa o calendário gregoriano proléptico.
Carimbos de data/hora e fusos horários
O tipo Timestamp
estende o tipo Date
com novos campos: hora, minuto, segundo (que podem ter uma parte fracionária) e junto com um fuso horário global (com escopo de sessão). Ele define um instante de tempo concreto. Por exemplo, (ano=2012, mês=12, dia=31, hora=23, minuto=59, segundo=59,123456) com o fuso horário da sessão UTC+ 01:00. Ao gravar valores de carimbo de data/hora em uma fonte de dados que não seja de texto, como Parquet, os valores são apenas instantes (como carimbo de data/hora em UTC) que não têm informações de fuso horário. Se você escrever e ler um valor de carimbo de data/hora com um fuso horário de sessão diferente, poderá ver valores diferentes dos campos de hora, minuto e segundo, mas eles são o mesmo instante de tempo concreto.
Os campos de hora, minuto e segundo têm intervalos padrão: 0—23 para horas e 0—59 para minutos e segundos. O Spark suporta segundos fracionários com precisão de até microssegundos. O intervalo válido para frações é de 0 a 999.999 microssegundos.
Em qualquer instante concreto, dependendo do fuso horário, você pode observar muitos valores diferentes de relógios de parede:
Por outro lado, o valor de um relógio de parede pode representar muitos instantes de tempo diferentes.
O deslocamento de fuso horário permite vincular inequivocamente um carimbo de data/hora local a um instante de horário. Normalmente, os desvios de fuso horário são definidos como deslocamentos em horas doHorário Médio de Greenwich (GMT) ou UTC+0(Horário Universal Coordenado). Essa representação das informações de fuso horário elimina a ambiguidade, mas é inconveniente. A maioria das pessoas prefere indicar uma localização como America/Los_Angeles
ou Europe/Paris
. Esse nível adicional de abstração das compensações de zona facilita a vida, mas traz complicações. Por exemplo, agora você precisa manter um banco de dados de fuso horário especial para mapear nomes de fuso horário em compensações. Como o Spark é executado no JVM, ele delega o mapeamento para a biblioteca padrão Java, que carrega dados do banco de dados de fuso horário da Internet Assigned Numbers Authority (IANA TZDB). Além disso, o mecanismo de mapeamento na biblioteca padrão do Java tem algumas nuances que influenciam o comportamento do Spark.
Desde o Java 8, o JDK expôs uma API diferente para manipulação de data e hora e resolução de deslocamento de fuso horário, e o Databricks Runtime 7.0 usa essa API. Embora o mapeamento de nomes de fuso horário para offsets tenha a mesma origem, IANA TZDB, ele é implementado de forma diferente em Java 8 e acima em comparação com Java 7.
Por exemplo, dê uma olhada em um carimbo de data/hora anterior ao ano de 1883 no fuso horário America/Los_Angeles
: 1883-11-10 00:00:00
. Este ano se destaca dos demais porque, em 18 de novembro de 1883, todas as ferrovias norte-americanas mudaram para um novo sistema de horário padrão. Usando a API de tempo do Java 7, o senhor pode obter um deslocamento de fuso horário no registro de data e hora local como -08:00
:
java.time.ZoneId.systemDefault
res0:java.time.ZoneId = America/Los_Angeles
java.sql.Timestamp.valueOf("1883-11-10 00:00:00").getTimezoneOffset / 60.0
res1: Double = 8.0
A API Java 8 equivalente retorna um resultado diferente:
java.time.ZoneId.of("America/Los_Angeles").getRules.getOffset(java.time.LocalDateTime.parse("1883-11-10T00:00:00"))
res2: java.time.ZoneOffset = -07:52:58
Antes de 18 de novembro de 1883, a hora do dia na América do Norte era um assunto local, e a maioria das cidades e vilas usava alguma forma de hora solar local, mantida por um relógio conhecido (no campanário de uma igreja, por exemplo, ou na vitrine de um joalheiro). É por isso que você vê uma diferença de fuso horário tão estranha.
O exemplo demonstra que as funções do Java 8 são mais precisas e levam em account conta os dados históricos do IANA TZDB. Depois de mudar para a API de tempo do Java 8, o Databricks Runtime 7.0 se beneficiou do aprimoramento automaticamente e se tornou mais preciso na forma como resolve os deslocamentos de fuso horário.
O Databricks Runtime 7.0 também mudou para o calendário gregoriano proléptico para o tipo Timestamp
. O padrão ISO SQL:2016 declara que o intervalo válido para carimbos de data/hora é de 0001-01-01 00:00:00
a 9999-12-31 23:59:59.999999
. O Databricks Runtime 7.0 está em total conformidade com o padrão e suporta todos os registros de data e hora nesse intervalo. Em comparação com Databricks Runtime 6.x e abaixo, observe os seguintes subintervalos:
0001-01-01 00:00:00..1582-10-03 23:59:59.999999
. Databricks Runtime 6.x e abaixo usa o calendário juliano e não está em conformidade com o padrão. O Databricks Runtime 7.0 corrige o problema e aplica o calendário gregoriano proléptico em operações internas em registros de data e hora, como obter ano, mês, dia etc. Devido aos diferentes calendários, algumas datas que existem no Databricks Runtime 6.x e abaixo não existem no Databricks Runtime 7.0. Por exemplo, 1000-02-29 não é uma data válida porque 1000 não é um ano bissexto no calendário gregoriano. Além disso, o Databricks Runtime 6.x e o abaixo resolvem incorretamente o nome do fuso horário para os deslocamentos de fuso para esse intervalo de registro de data e hora.1582-10-04 00:00:00..1582-10-14 23:59:59.999999
. Esse é um intervalo válido de registros de data e hora locais em Databricks Runtime 7.0, em contraste com Databricks Runtime 6.x e abaixo, onde esses registros de data e hora não existiam.1582-10-15 00:00:00..1899-12-31 23:59:59.999999
. Databricks Runtime 7.0 resolve corretamente os deslocamentos de fuso horário usando dados históricos do IANA TZDB. Em comparação com o Databricks Runtime 7.0, o Databricks Runtime 6.x e o abaixo podem resolver incorretamente os deslocamentos de fuso horário dos nomes de fuso horário em alguns casos, conforme mostrado no exemplo anterior.1900-01-01 00:00:00..2036-12-31 23:59:59.999999
. Tanto o Databricks Runtime 7.0 quanto o Databricks Runtime 6.x e abaixo estão em conformidade com o padrão ANSI SQL e usam o calendário gregoriano em operações de data e hora, como a obtenção do dia do mês.2037-01-01 00:00:00..9999-12-31 23:59:59.999999
. Databricks Runtime 6.x e abaixo podem resolver incorretamente os deslocamentos de fuso horário e de horário de verão. O Databricks Runtime 7.0 não faz isso.
Mais um aspecto do mapeamento de nomes de fuso horário para compensações é a sobreposição de carimbos de data/hora locais que podem ocorrer devido ao horário de verão (DST) ou à mudança para outro deslocamento de fuso horário padrão. Por exemplo, em 3 de novembro de 2019, 02:00:00, a maioria dos estados dos EUA atrasou os relógios em 1 hora para 01:00:00. O timestamp local 2019-11-03 01:30:00 America/Los_Angeles
pode ser mapeado para 2019-11-03 01:30:00 UTC-08:00
ou 2019-11-03 01:30:00 UTC-07:00
. Se o senhor não especificar o deslocamento e apenas definir o nome do fuso horário (por exemplo, 2019-11-03 01:30:00 America/Los_Angeles
), o Databricks Runtime 7.0 assume o deslocamento anterior, normalmente correspondente ao "verão". O comportamento diverge do Databricks Runtime 6.x e do abaixo, que usa o deslocamento "winter". No caso de uma lacuna, em que os relógios avançam, não há compensação válida. Para uma mudança típica de uma hora no horário de verão, o Spark move esses registros de data e hora para o próximo registro de data e hora válido correspondente ao horário de "verão".
Como você pode ver nos exemplos anteriores, o mapeamento de nomes de fuso horário para compensações é ambíguo e não é individual. Nos casos em que isso é possível, ao criar carimbos de data/hora, recomendamos especificar deslocamentos de fuso horário exatos, por exemplo, 2019-11-03 01:30:00 UTC-07:00
.
Carimbos de data e hora ANSI SQL e Spark SQL
O padrão ANSI SQL define dois tipos de registros de data e hora:
TIMESTAMP WITHOUT TIME ZONE
ouTIMESTAMP
: Carimbo de data/hora local como (YEAR
,MONTH
,DAY
,HOUR
,MINUTE
,SECOND
). Esses carimbos de data/hora não estão vinculados a nenhum fuso horário e são carimbos de data/hora de relógios de parede.TIMESTAMP WITH TIME ZONE
: Carimbo de data/hora dividido em zonas como (YEAR
,MONTH
,DAY
,HOUR
,MINUTE
,SECOND
,TIMEZONE_HOUR
,TIMEZONE_MINUTE
). Esses carimbos de data/hora representam um instante no fuso horário UTC mais uma diferença de fuso horário (em horas e minutos) associada a cada valor.
A diferença de fuso horário de um TIMESTAMP WITH TIME ZONE
não afeta o ponto físico que o carimbo de data/hora representa, pois isso é totalmente representado pelo instante de horário UTC fornecido pelos outros componentes do carimbo de data/hora. default Em vez disso, o deslocamento de fuso horário afeta apenas o comportamento de um valor de carimbo de data/hora para exibição, extração de componente de data/hora (por exemplo, EXTRACT
) e outras operações que exigem o conhecimento de um fuso horário, como adicionar meses a um carimbo de data/hora.
O Spark SQL define o tipo de carimbo de data/hora como TIMESTAMP WITH SESSION TIME ZONE
, que é uma combinação dos campos (YEAR
, MONTH
, DAY
, HOUR
, MINUTE
, SECOND
, SESSION TZ
) em que os campos YEAR
a SECOND
identificam um instante de tempo no fuso horário UTC e em que SESSION TZ é obtido da configuração SQL spark.sql.session.timeZone. O fuso horário da sessão pode ser definido como:
- Desvio de zona
(+|-)HH:mm
. Esse formulário permite que você defina de forma inequívoca um ponto físico no tempo. - Nome do fuso horário na forma da ID de região
area/city
, comoAmerica/Los_Angeles
. Essa forma de informação de fuso horário sofre de alguns dos problemas descritos anteriormente, como a sobreposição de carimbos de data/hora locais. No entanto, cada instante de horário UTC é associado de forma inequívoca a um deslocamento de fuso horário para qualquer ID de região e, como resultado, cada carimbo de data/hora com um fuso horário baseado em ID de região pode ser convertido de forma inequívoca em um carimbo de data/hora com um deslocamento de zona. Em default, o fuso horário da sessão é definido como o fuso horário default da máquina virtual Java.
Spark TIMESTAMP WITH SESSION TIME ZONE
é diferente de:
TIMESTAMP WITHOUT TIME ZONE
, porque um valor desse tipo pode ser mapeado para vários instantes de tempo físicos, mas qualquer valor deTIMESTAMP WITH SESSION TIME ZONE
é um instante de tempo físico concreto. O tipo SQL pode ser emulado usando um deslocamento de fuso horário fixo em todas as sessões, por exemplo, UTC+0. Nesse caso, você pode considerar os carimbos de data/hora em UTC como carimbos de data/hora locais.TIMESTAMP WITH TIME ZONE
O senhor pode usar o tipo de coluna "Time Zone", pois, de acordo com o padrão SQL, os valores de coluna do tipo podem ter diferentes deslocamentos de fuso horário. Isso não é suportado pelo Spark SQL.
O senhor deve observar que os carimbos de data e hora associados a um fuso horário global (com escopo de sessão) não são algo recém-inventado pelo Spark SQL. RDBMSs como o Oracle fornecem um tipo similar para carimbos de data/hora: TIMESTAMP WITH LOCAL TIME ZONE
.
Construa datas e carimbos de data/hora
O Spark SQL fornece alguns métodos para a construção de valores de data e registro de data e hora:
- Construtores padrão sem parâmetros:
CURRENT_TIMESTAMP()
eCURRENT_DATE()
. - De outros tipos primitivos do Spark SQL, como
INT
,LONG
, eSTRING
- De tipos externos como datetime do Python ou classes Java
java.time.LocalDate
/Instant
. - Desserialização de fontes de dados, como CSV, JSON, Avro, Parquet, ORC, e assim por diante.
A função MAKE_DATE
introduzida no Databricks Runtime 7.0 recebe três parâmetros -YEAR
, MONTH
e DAY
- e constrói um valor DATE
. Todos os parâmetros de entrada são convertidos implicitamente para o tipo INT
sempre que possível. A função verifica se as datas resultantes são datas válidas no calendário gregoriano proléptico, caso contrário, ela retorna NULL
. Por exemplo:
spark.createDataFrame([(2020, 6, 26), (1000, 2, 29), (-44, 1, 1)],['Y', 'M', 'D']).createTempView('YMD')
df = sql('select make_date(Y, M, D) as date from YMD')
df.printSchema()
root
|-- date: date (nullable = true)
Para imprimir o conteúdo do DataFrame, chame a ação show()
, que converte as datas para strings no executor e transfere o strings para o driver para que seja emitido no console:
df.show()
+-----------+
| date|
+-----------+
| 2020-06-26|
| null|
|-0044-01-01|
+-----------+
Da mesma forma, você pode construir valores de timestamp usando as funções MAKE_TIMESTAMP
. Como MAKE_DATE
, ele executa a mesma validação para campos de data e, além disso, aceita os campos de hora HORA (0-23), MINUTO (0-59) e SEGUNDO (0-60). SECOND tem o tipo Decimal(precision = 8, escala = 6) porque os segundos podem ser passados com a parte fracionária até a precisão de microssegundos. Por exemplo:
df = spark.createDataFrame([(2020, 6, 28, 10, 31, 30.123456), \
(1582, 10, 10, 0, 1, 2.0001), (2019, 2, 29, 9, 29, 1.0)],['YEAR', 'MONTH', 'DAY', 'HOUR', 'MINUTE', 'SECOND'])
df.show()
+----+-----+---+----+------+---------+
|YEAR|MONTH|DAY|HOUR|MINUTE| SECOND|
+----+-----+---+----+------+---------+
|2020| 6| 28| 10| 31|30.123456|
|1582| 10| 10| 0| 1| 2.0001|
|2019| 2| 29| 9| 29| 1.0|
+----+-----+---+----+------+---------+
df.selectExpr("make_timestamp(YEAR, MONTH, DAY, HOUR, MINUTE, SECOND) as MAKE_TIMESTAMP")
ts.printSchema()
root
|-- MAKE_TIMESTAMP: timestamp (nullable = true)
Quanto às datas, imprima o conteúdo do DataFrame ts usando a ação show(). De forma semelhante, show()
converte carimbos de data/hora para strings, mas agora leva em account o fuso horário da sessão definido pela configuração SQL spark.sql.session.timeZone
.
ts.show(truncate=False)
+--------------------------+
|MAKE_TIMESTAMP |
+--------------------------+
|2020-06-28 10:31:30.123456|
|1582-10-10 00:01:02.0001 |
|null |
+--------------------------+
O Spark não pode criar o último registro de data e hora porque essa data não é válida: 2019 não é um ano bissexto.
O senhor pode notar que não há informações de fuso horário no exemplo anterior. Nesse caso, o Spark obtém um fuso horário da configuração do SQL spark.sql.session.timeZone
e o aplica às invocações de função. Você também pode escolher um fuso horário diferente passando-o como o último parâmetro de MAKE_TIMESTAMP
. Aqui está um exemplo:
df = spark.createDataFrame([(2020, 6, 28, 10, 31, 30, 'UTC'),(1582, 10, 10, 0, 1, 2, 'America/Los_Angeles'), \
(2019, 2, 28, 9, 29, 1, 'Europe/Moscow')], ['YEAR', 'MONTH', 'DAY', 'HOUR', 'MINUTE', 'SECOND', 'TZ'])
df = df.selectExpr('make_timestamp(YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, TZ) as MAKE_TIMESTAMP')
df = df.selectExpr("date_format(MAKE_TIMESTAMP, 'yyyy-MM-dd HH:mm:ss VV') AS TIMESTAMP_STRING")
df.show(truncate=False)
+---------------------------------+
|TIMESTAMP_STRING |
+---------------------------------+
|2020-06-28 13:31:00 Europe/Moscow|
|1582-10-10 10:24:00 Europe/Moscow|
|2019-02-28 09:29:00 Europe/Moscow|
+---------------------------------+
Conforme demonstrado no exemplo, o site Spark leva em conta account os fusos horários especificados, mas ajusta todos os registros de data e hora locais ao fuso horário da sessão. Os fusos horários originais passados para a função MAKE_TIMESTAMP
são perdidos porque o tipo TIMESTAMP WITH SESSION TIME ZONE
assume que todos os valores pertencem a um fuso horário e nem mesmo armazena um fuso horário para cada valor. De acordo com a definição do TIMESTAMP WITH SESSION TIME ZONE
, o site Spark armazena carimbos de data/hora locais no fuso horário UTC e usa o fuso horário da sessão ao extrair campos de data/hora ou converter os carimbos de data/hora para strings.
Além disso, os carimbos de data/hora podem ser construídos a partir do tipo LONG usando fundição. Se uma coluna LONG contiver o número de segundos desde a época 1970-01-01 00:00:00Z, ela poderá ser convertida em um Spark SQL TIMESTAMP
:
select CAST(-123456789 AS TIMESTAMP);
1966-02-02 05:26:51
Infelizmente, essa abordagem não permite que você especifique a parte fracionária dos segundos.
Outra forma é construir datas e timestamps a partir de valores do tipo STRING
. Você pode criar literais usando palavras-chave especiais:
select timestamp '2020-06-28 22:17:33.123456 Europe/Amsterdam', date '2020-07-01';
2020-06-28 23:17:33.123456 2020-07-01
Como alternativa, você pode usar a conversão que pode ser aplicada a todos os valores em uma coluna:
select cast('2020-06-28 22:17:33.123456 Europe/Amsterdam' as timestamp), cast('2020-07-01' as date);
2020-06-28 23:17:33.123456 2020-07-01
O carimbo de data/hora de entrada strings é interpretado como carimbos de data/hora locais no fuso horário especificado ou no fuso horário da sessão se um fuso horário for omitido nas cadeias de caracteres de entrada. As cadeias de caracteres com padrões incomuns podem ser convertidas em carimbo de data/hora usando a função to_timestamp()
. Os padrões suportados são descritos em Padrões de data e hora para formatação e análise:
select to_timestamp('28/6/2020 22.17.33', 'dd/M/yyyy HH.mm.ss');
2020-06-28 22:17:33
Se você não especificar um padrão, a função se comportará de forma semelhante a CAST
.
Para facilitar a utilização, o site Spark SQL reconhece valores especiais de cadeias de caracteres em todos os métodos que aceitam cadeias de caracteres e retornam um carimbo de data/hora ou data:
epoch
é um alias para data1970-01-01
ou timestamp1970-01-01 00:00:00Z
.now
é o timestamp ou data atual no fuso horário da sessão. Em uma única consulta, ela sempre produz o mesmo resultado.today
é o início da data atual para o tipoTIMESTAMP
ou apenas a data atual para o tipoDATE
.tomorrow
é o início do dia seguinte para carimbos de data/hora ou apenas o dia seguinte para o tipoDATE
.yesterday
é o dia anterior ao atual ou seu início para o tipoTIMESTAMP
.
Por exemplo:
select timestamp 'yesterday', timestamp 'today', timestamp 'now', timestamp 'tomorrow';
2020-06-27 00:00:00 2020-06-28 00:00:00 2020-06-28 23:07:07.18 2020-06-29 00:00:00
select date 'yesterday', date 'today', date 'now', date 'tomorrow';
2020-06-27 2020-06-28 2020-06-28 2020-06-29
O Spark permite que o senhor crie Datasets
a partir de coleções existentes de objetos externos no lado do driver e crie colunas de tipos correspondentes. O Spark converte instâncias de tipos externos em representações internas semanticamente equivalentes. Por exemplo, para criar um Dataset
com as colunas DATE
e TIMESTAMP
das coleções Python, o senhor pode usar:
import datetime
df = spark.createDataFrame([(datetime.datetime(2020, 7, 1, 0, 0, 0), datetime.date(2020, 7, 1))], ['timestamp', 'date'])
df.show()
+-------------------+----------+
| timestamp| date|
+-------------------+----------+
|2020-07-01 00:00:00|2020-07-01|
+-------------------+----------+
PySpark converte os objetos de data e hora do site Pythonem representações internas do site Spark SQL no lado do driver usando o fuso horário do sistema, que pode ser diferente da configuração de fuso horário da sessão do site Spark spark.sql.session.timeZone
. Os valores internos não contêm informações sobre o fuso horário original. Operações futuras sobre os valores de data e carimbo de data/hora paralelizados levam em account apenas o fuso horário das sessões Spark SQL de acordo com a definição do tipo TIMESTAMP WITH SESSION TIME ZONE
.
De maneira semelhante, o Spark reconhece os seguintes tipos como tipos de data e hora externos nas APIs Java e Scala:
java.sql.Date
ejava.time.LocalDate
como tipos externos para o tipoDATE
java.sql.Timestamp
ejava.time.Instant
para o tipoTIMESTAMP
.
Há uma diferença entre os tipos java.sql.*
e java.time.*
. java.time.LocalDate
e java.time.Instant
foram adicionados em Java 8 e os tipos são baseados no calendário gregoriano proléptico, o mesmo calendário usado por Databricks Runtime 7.0 e acima. java.sql.Date
e java.sql.Timestamp
têm outro calendário embaixo - o calendário híbrido (juliano + gregoriano desde 1582-10-15), que é o mesmo calendário legado usado pelo Databricks Runtime 6.x e abaixo. Devido aos diferentes sistemas de calendário, o site Spark precisa realizar operações adicionais durante as conversões para as representações internas do site Spark SQL e rebasear as datas de entrada/carimbo de data/hora de um calendário para outro. O rebase operações tem uma pequena sobrecarga para registros de data e hora modernos após o ano de 1900 e pode ser mais significativo para registros de data e hora antigos.
O exemplo a seguir mostra como criar registros de data e hora a partir de coleções Scala. O primeiro exemplo constrói um objeto java.sql.Timestamp
a partir de uma cadeia de caracteres. O método valueOf
interpreta a entrada strings como um registro de data e hora local no fuso horário default JVM , que pode ser diferente do fuso horário da sessão Spark. Se você precisar construir instâncias de java.sql.Timestamp
ou java.sql.Date
em um fuso horário específico, dê uma olhada em java.text.SimpleDateFormat (e seu setTimeZone
método) ou java.util.Calendar.
Seq(java.sql.Timestamp.valueOf("2020-06-29 22:41:30"), new java.sql.Timestamp(0)).toDF("ts").show(false)
+-------------------+
|ts |
+-------------------+
|2020-06-29 22:41:30|
|1970-01-01 03:00:00|
+-------------------+
Seq(java.time.Instant.ofEpochSecond(-12219261484L), java.time.Instant.EPOCH).toDF("ts").show
+-------------------+
| ts|
+-------------------+
|1582-10-15 11:12:13|
|1970-01-01 03:00:00|
+-------------------+
Da mesma forma, você pode criar uma coluna DATE
a partir de coleções de java.sql.Date
ou java.sql.LocalDate
. A paralelização de instâncias java.sql.LocalDate
é totalmente independente da sessão Sparkou do fuso horário JVM default , mas o mesmo não ocorre com a paralelização de instâncias java.sql.Date
. Existem nuances:
java.sql.Date
representam datas locais no fuso horário default JVM do driver.- Para que as conversões para os valores Spark SQL estejam corretas, o fuso horário default JVM do driver e do executor deve ser o mesmo.
Seq(java.time.LocalDate.of(2020, 2, 29), java.time.LocalDate.now).toDF("date").show
+----------+
| date|
+----------+
|2020-02-29|
|2020-06-29|
+----------+
Para evitar qualquer problema relacionado a calendário e fuso horário, recomendamos os tipos Java 8 java.sql.LocalDate
/Instant
como tipos externos na paralelização de coleções Java/Scala de carimbos de data/hora ou datas.
Colete datas e carimbos de data e hora
A operação inversa da paralelização é coletar datas e registros de data e hora do executor para o driver e retornar uma coleção de tipos externos. No exemplo acima, o senhor pode puxar o DataFrame
de volta para o driver usando a ação collect()
:
df.collect()
[Row(timestamp=datetime.datetime(2020, 7, 1, 0, 0), date=datetime.date(2020, 7, 1))]
Spark transfere valores internos de colunas de datas e carimbos de data/hora como instantes de tempo no fuso horário UTC do executor para o driver e realiza conversões para objetos datetime Python no fuso horário do sistema no driver, sem usar o fuso horário da sessão Spark SQL. collect()
é diferente da ação show()
descrita na seção anterior. show()
usa o fuso horário da sessão ao converter carimbos de data/hora em strings e coleta as strings resultantes no driver.
Em Java e Scala APIs, Spark realiza as seguintes conversões por default:
- Os valores do Spark SQL
DATE
são convertidos em instâncias dejava.sql.Date
. - Os valores do Spark SQL
TIMESTAMP
são convertidos em instâncias dejava.sql.Timestamp
.
Ambas as conversões são realizadas no fuso horário default JVM do driver. Dessa forma, para ter os mesmos campos de data e hora que o senhor pode obter usando Date.getDay()
, getHour()
e assim por diante, e usando as funções Spark SQL DAY
, HOUR
, o fuso horário default JVM no driver e o fuso horário da sessão no executor devem ser os mesmos.
De forma semelhante à criação de datas/carimbos de data/hora em java.sql.Date
/Timestamp
, o Databricks Runtime 7.0 executa o rebase do calendário gregoriano proléptico para o calendário híbrido (juliano + gregoriano). Essa operação é quase gratuita para datas modernas (após o ano de 1582) e carimbos de data/hora (após o ano de 1900), mas pode trazer alguma sobrecarga para datas e carimbos de data/hora antigos.
O senhor pode evitar esses problemas relacionados ao calendário e pedir ao Spark que retorne os tipos java.time
, que foram adicionados a partir do Java 8. Se o senhor definir a configuração do SQL spark.sql.datetime.java8API.enabled
como true, a ação Dataset.collect()
retornará:
java.time.LocalDate
para Spark SQLDATE
digitejava.time.Instant
para Spark SQLTIMESTAMP
digite
Agora, as conversões não sofrem com os problemas relacionados ao calendário porque os tipos Java 8 e Databricks Runtime 7.0 e acima são ambos baseados no calendário gregoriano proléptico. A ação collect()
não depende do fuso horário default JVM . As conversões de timestamp não dependem em nada do fuso horário. As conversões de data usam o fuso horário da sessão da configuração do SQL spark.sql.session.timeZone
. Por exemplo, considere um Dataset
com colunas DATE
e TIMESTAMP
, com o fuso horário default JVM definido como Europe/Moscow
e o fuso horário da sessão definido como America/Los_Angeles
.
java.util.TimeZone.getDefault
res1: java.util.TimeZone = sun.util.calendar.ZoneInfo[id="Europe/Moscow",...]
spark.conf.get("spark.sql.session.timeZone")
res2: String = America/Los_Angeles
df.show
+-------------------+----------+
| timestamp| date|
+-------------------+----------+
|2020-07-01 00:00:00|2020-07-01|
+-------------------+----------+
A ação show()
imprime o timestamp no horário da sessão America/Los_Angeles
, mas se você coletar o Dataset
, ele será convertido em java.sql.Timestamp
e o método toString
imprimirá Europe/Moscow
:
df.collect()
res16: Array[org.apache.spark.sql.Row] = Array([2020-07-01 10:00:00.0,2020-07-01])
df.collect()(0).getAs[java.sql.Timestamp](0).toString
res18: java.sql.Timestamp = 2020-07-01 10:00:00.0
Na verdade, o timestamp local 2020-07-01 00:00:00 é 2020-07-01T 07:00:00 Z no UTC. O senhor pode observar que, se ativar o Java 8 API e coletar o conjunto de dados:
df.collect()
res27: Array[org.apache.spark.sql.Row] = Array([2020-07-01T07:00:00Z,2020-07-01])
O senhor pode converter um objeto java.time.Instant
em qualquer registro de data e hora local, independentemente do fuso horário global da JVM. Essa é uma das vantagens do java.time.Instant
em relação ao java.sql.Timestamp
. O primeiro requer a alteração da configuração global da JVM, que influencia outros registros de data e hora na mesma JVM. Portanto, se seus aplicativos processam datas ou carimbos de data/hora em fusos horários diferentes e os aplicativos não devem entrar em conflito uns com os outros ao coletar dados para o driver usando a API Java ou Scala Dataset.collect()
, recomendamos mudar para a API Java 8 usando a configuração SQL spark.sql.datetime.java8API.enabled
.