1717package org .apache .spark .sql .catalyst .expressions
1818
1919import org .apache .spark .sql .GlutenTestsTrait
20+ import org .apache .spark .sql .catalyst .util .DateTimeTestUtils .{withDefaultTimeZone , ALL_TIMEZONES , UTC , UTC_OPT }
21+ import org .apache .spark .sql .catalyst .util .DateTimeUtils .{fromJavaTimestamp , millisToMicros , TimeZoneUTC }
2022import org .apache .spark .sql .internal .SQLConf
23+ import org .apache .spark .sql .types ._
24+ import org .apache .spark .util .DebuggableThreadUtils
25+
26+ import java .sql .{Date , Timestamp }
27+ import java .util .{Calendar , TimeZone }
2128
2229class GlutenCastWithAnsiOffSuite extends CastWithAnsiOffSuite with GlutenTestsTrait {
2330 override def beforeAll (): Unit = {
@@ -28,4 +35,213 @@ class GlutenCastWithAnsiOffSuite extends CastWithAnsiOffSuite with GlutenTestsTr
2835 // In Vanilla spark, the checkEvaluation method doesn't invoke RowEncoder.
2936 conf.setConf(SQLConf .PRESERVE_CHAR_VARCHAR_TYPE_INFO , true )
3037 }
38+
39+ // Override: Gluten uses session-level timezone for cast. The original test sets per-expression
40+ // timezone via Cast(..., Option(tz)), which Gluten ignores. We sync session timezone with
41+ // withSQLConf to match per-expression timezone.
42+ testGluten(" data type casting" ) {
43+ val sd = " 1970-01-01"
44+ val d = Date .valueOf(sd)
45+ val zts = sd + " 00:00:00"
46+ val sts = sd + " 00:00:02"
47+ val nts = sts + " .1"
48+ val ts = withDefaultTimeZone(UTC )(Timestamp .valueOf(nts))
49+
50+ // SystemV timezones are a legacy way of specifying timezones in Unix-like OS.
51+ // It is not supported by Velox.
52+ for (
53+ tz <- ALL_TIMEZONES
54+ .filterNot(_.getId.contains(" SystemV" ))
55+ .filterNot(_.getId.contains(" America/Coyhaique" ))
56+ ) {
57+ withSQLConf(
58+ SQLConf .SESSION_LOCAL_TIMEZONE .key -> tz.getId
59+ ) {
60+ val timeZoneId = Option (tz.getId)
61+ var c = Calendar .getInstance(TimeZoneUTC )
62+ c.set(2015 , 2 , 8 , 2 , 30 , 0 )
63+ checkEvaluation(
64+ cast(
65+ cast(new Timestamp (c.getTimeInMillis), StringType , timeZoneId),
66+ TimestampType ,
67+ timeZoneId),
68+ millisToMicros(c.getTimeInMillis))
69+ c = Calendar .getInstance(TimeZoneUTC )
70+ c.set(2015 , 10 , 1 , 2 , 30 , 0 )
71+ checkEvaluation(
72+ cast(
73+ cast(new Timestamp (c.getTimeInMillis), StringType , timeZoneId),
74+ TimestampType ,
75+ timeZoneId),
76+ millisToMicros(c.getTimeInMillis))
77+ }
78+ }
79+
80+ checkEvaluation(cast(" abdef" , StringType ), " abdef" )
81+ checkEvaluation(cast(" 12.65" , DecimalType .SYSTEM_DEFAULT ), Decimal (12.65 ))
82+
83+ checkEvaluation(cast(cast(sd, DateType ), StringType ), sd)
84+ checkEvaluation(cast(cast(d, StringType ), DateType ), 0 )
85+
86+ withSQLConf(
87+ SQLConf .SESSION_LOCAL_TIMEZONE .key -> UTC_OPT .get
88+ ) {
89+ checkEvaluation(cast(cast(nts, TimestampType , UTC_OPT ), StringType , UTC_OPT ), nts)
90+ checkEvaluation(
91+ cast(cast(ts, StringType , UTC_OPT ), TimestampType , UTC_OPT ),
92+ fromJavaTimestamp(ts))
93+
94+ // all convert to string type to check
95+ checkEvaluation(
96+ cast(cast(cast(nts, TimestampType , UTC_OPT ), DateType , UTC_OPT ), StringType ),
97+ sd)
98+ checkEvaluation(
99+ cast(cast(cast(ts, DateType , UTC_OPT ), TimestampType , UTC_OPT ), StringType , UTC_OPT ),
100+ zts)
101+ }
102+
103+ checkEvaluation(cast(cast(" abdef" , BinaryType ), StringType ), " abdef" )
104+
105+ checkEvaluation(
106+ cast(
107+ cast(cast(cast(cast(cast(" 5" , ByteType ), ShortType ), IntegerType ), FloatType ), DoubleType ),
108+ LongType ),
109+ 5 .toLong)
110+
111+ checkEvaluation(cast(" 23" , DoubleType ), 23d )
112+ checkEvaluation(cast(" 23" , IntegerType ), 23 )
113+ checkEvaluation(cast(" 23" , FloatType ), 23f )
114+ checkEvaluation(cast(" 23" , DecimalType .USER_DEFAULT ), Decimal (23 ))
115+ checkEvaluation(cast(" 23" , ByteType ), 23 .toByte)
116+ checkEvaluation(cast(" 23" , ShortType ), 23 .toShort)
117+ checkEvaluation(cast(123 , IntegerType ), 123 )
118+
119+ checkEvaluation(cast(Literal .create(null , IntegerType ), ShortType ), null )
120+ }
121+
122+ // Override: Gluten's glutenCheckExpression uses collect(), which triggers
123+ // toJavaTimestamp -> rebaseGregorianToJulianMicros. Long.MinValue micros (~292000 BC) overflows
124+ // during rebase. Velox computes correctly; only the collect path fails. Skip Long.MinValue.
125+ testGluten(" cast from timestamp II" ) {
126+ checkEvaluation(cast(Double .NaN , TimestampType ), null )
127+ checkEvaluation(cast(1.0 / 0.0 , TimestampType ), null )
128+ checkEvaluation(cast(Float .NaN , TimestampType ), null )
129+ checkEvaluation(cast(1.0f / 0.0f , TimestampType ), null )
130+ checkEvaluation(cast(Literal (Long .MaxValue ), TimestampType ), Long .MaxValue )
131+ // Skip Long.MinValue: Velox result is correct but collect() path overflows in
132+ // rebaseGregorianToJulianMicros when converting extreme timestamp to java.sql.Timestamp.
133+ }
134+
135+ // Override: sync session timezone with per-expression timezone and run single-threaded.
136+ testGluten(" cast string to timestamp" ) {
137+ DebuggableThreadUtils .parmap(
138+ ALL_TIMEZONES
139+ .filterNot(_.getId.contains(" SystemV" ))
140+ .filterNot(_.getId.contains(" Europe/Kyiv" ))
141+ .filterNot(_.getId.contains(" America/Ciudad_Juarez" ))
142+ .filterNot(_.getId.contains(" America/Coyhaique" ))
143+ .filterNot(_.getId.contains(" Antarctica/Vostok" ))
144+ .filterNot(_.getId.contains(" Pacific/Kanton" ))
145+ .filterNot(_.getId.contains(" Asia/Tehran" ))
146+ .filterNot(_.getId.contains(" Iran" )),
147+ prefix = " CastSuiteBase-cast-string-to-timestamp" ,
148+ maxThreads = 1
149+ ) {
150+ zid =>
151+ withSQLConf(
152+ SQLConf .SESSION_LOCAL_TIMEZONE .key -> zid.getId
153+ ) {
154+ def checkCastStringToTimestamp (str : String , expected : Timestamp ): Unit = {
155+ checkEvaluation(cast(Literal (str), TimestampType , Option (zid.getId)), expected)
156+ }
157+
158+ val tz = TimeZone .getTimeZone(zid)
159+ var c = Calendar .getInstance(tz)
160+ c.set(2015 , 0 , 1 , 0 , 0 , 0 )
161+ c.set(Calendar .MILLISECOND , 0 )
162+ checkCastStringToTimestamp(" 2015" , new Timestamp (c.getTimeInMillis))
163+ c = Calendar .getInstance(tz)
164+ c.set(2015 , 2 , 1 , 0 , 0 , 0 )
165+ c.set(Calendar .MILLISECOND , 0 )
166+ checkCastStringToTimestamp(" 2015-03" , new Timestamp (c.getTimeInMillis))
167+ c = Calendar .getInstance(tz)
168+ c.set(2015 , 2 , 18 , 0 , 0 , 0 )
169+ c.set(Calendar .MILLISECOND , 0 )
170+ checkCastStringToTimestamp(" 2015-03-18" , new Timestamp (c.getTimeInMillis))
171+ checkCastStringToTimestamp(" 2015-03-18 " , new Timestamp (c.getTimeInMillis))
172+
173+ c = Calendar .getInstance(tz)
174+ c.set(2015 , 2 , 18 , 12 , 3 , 17 )
175+ c.set(Calendar .MILLISECOND , 0 )
176+ checkCastStringToTimestamp(" 2015-03-18 12:03:17" , new Timestamp (c.getTimeInMillis))
177+ checkCastStringToTimestamp(" 2015-03-18T12:03:17" , new Timestamp (c.getTimeInMillis))
178+
179+ // If the string value includes timezone string, it represents the timestamp string
180+ // in the timezone regardless of the timeZoneId parameter.
181+ c = Calendar .getInstance(TimeZone .getTimeZone(UTC ))
182+ c.set(2015 , 2 , 18 , 12 , 3 , 17 )
183+ c.set(Calendar .MILLISECOND , 0 )
184+ checkCastStringToTimestamp(" 2015-03-18T12:03:17Z" , new Timestamp (c.getTimeInMillis))
185+ checkCastStringToTimestamp(" 2015-03-18 12:03:17Z" , new Timestamp (c.getTimeInMillis))
186+
187+ c = Calendar .getInstance(TimeZone .getTimeZone(" GMT-01:00" ))
188+ c.set(2015 , 2 , 18 , 12 , 3 , 17 )
189+ c.set(Calendar .MILLISECOND , 0 )
190+ // Unsupported timezone format for Velox backend.
191+ // checkCastStringToTimestamp("2015-03-18T12:03:17-1:0", new Timestamp(c.getTimeInMillis))
192+ checkCastStringToTimestamp(" 2015-03-18T12:03:17-01:00" , new Timestamp (c.getTimeInMillis))
193+
194+ c = Calendar .getInstance(TimeZone .getTimeZone(" GMT+07:30" ))
195+ c.set(2015 , 2 , 18 , 12 , 3 , 17 )
196+ c.set(Calendar .MILLISECOND , 0 )
197+ checkCastStringToTimestamp(" 2015-03-18T12:03:17+07:30" , new Timestamp (c.getTimeInMillis))
198+
199+ c = Calendar .getInstance(TimeZone .getTimeZone(" GMT+07:03" ))
200+ c.set(2015 , 2 , 18 , 12 , 3 , 17 )
201+ c.set(Calendar .MILLISECOND , 0 )
202+ // Unsupported timezone format for Velox backend.
203+ // checkCastStringToTimestamp("2015-03-18T12:03:17+7:3",
204+ // new Timestamp(c.getTimeInMillis))
205+
206+ // tests for the string including milliseconds.
207+ c = Calendar .getInstance(tz)
208+ c.set(2015 , 2 , 18 , 12 , 3 , 17 )
209+ c.set(Calendar .MILLISECOND , 123 )
210+ checkCastStringToTimestamp(" 2015-03-18 12:03:17.123" , new Timestamp (c.getTimeInMillis))
211+ checkCastStringToTimestamp(" 2015-03-18T12:03:17.123" , new Timestamp (c.getTimeInMillis))
212+
213+ // If the string value includes timezone string, it represents the timestamp string
214+ // in the timezone regardless of the timeZoneId parameter.
215+ c = Calendar .getInstance(TimeZone .getTimeZone(UTC ))
216+ c.set(2015 , 2 , 18 , 12 , 3 , 17 )
217+ c.set(Calendar .MILLISECOND , 456 )
218+ checkCastStringToTimestamp(" 2015-03-18T12:03:17.456Z" , new Timestamp (c.getTimeInMillis))
219+ checkCastStringToTimestamp(" 2015-03-18 12:03:17.456Z" , new Timestamp (c.getTimeInMillis))
220+
221+ c = Calendar .getInstance(TimeZone .getTimeZone(" GMT-01:00" ))
222+ c.set(2015 , 2 , 18 , 12 , 3 , 17 )
223+ c.set(Calendar .MILLISECOND , 123 )
224+ // Unsupported timezone format for Velox backend.
225+ // checkCastStringToTimestamp("2015-03-18T12:03:17.123-1:0",
226+ // new Timestamp(c.getTimeInMillis))
227+ checkCastStringToTimestamp(
228+ " 2015-03-18T12:03:17.123-01:00" ,
229+ new Timestamp (c.getTimeInMillis))
230+
231+ c = Calendar .getInstance(TimeZone .getTimeZone(" GMT+07:30" ))
232+ c.set(2015 , 2 , 18 , 12 , 3 , 17 )
233+ c.set(Calendar .MILLISECOND , 123 )
234+ checkCastStringToTimestamp(
235+ " 2015-03-18T12:03:17.123+07:30" ,
236+ new Timestamp (c.getTimeInMillis))
237+
238+ c = Calendar .getInstance(TimeZone .getTimeZone(" GMT+07:03" ))
239+ c.set(2015 , 2 , 18 , 12 , 3 , 17 )
240+ c.set(Calendar .MILLISECOND , 123 )
241+ // Unsupported timezone format for Velox backend.
242+ // checkCastStringToTimestamp("2015-03-18T12:03:17.123+7:3",
243+ // new Timestamp(c.getTimeInMillis))
244+ }
245+ }
246+ }
31247}
0 commit comments