Skip to content

Commit

Permalink
Update time zone handling to handle GMT times (#429)
Browse files Browse the repository at this point in the history
* Update time zone handling to handle GMT times

* Use zoned date time

* Correct the currentTime

* Add tests

* Make compatible with Android 16

* MainActivity cleanup

* Fix test syntax

* Fix tests part 2

* nit

* Revert to previous toJson formatting

* Test Darwin time zone (and use local time zone formatting)

* Match iOS, ending wiht Z or -05:00

* Bump version

* 3.20.0 to match iOS
  • Loading branch information
lmeier authored Jan 22, 2025
1 parent 07f2210 commit 94eeaea
Show file tree
Hide file tree
Showing 8 changed files with 294 additions and 28 deletions.
13 changes: 12 additions & 1 deletion example/src/main/java/io/radar/example/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,18 @@ class MainActivity : AppCompatActivity() {
Radar.reverseGeocode(reverseGeocodeLocation) { status, addresses ->
Log.v(
"example",
"Reverse geocode: status = $status; coordinate = ${addresses?.first()?.formattedAddress}"
"Reverse geocode: status = $status; coordinate = ${addresses?.first()?.formattedAddress}, timeZone = ${addresses?.first()?.timeZone?.toJson()}"
)
}

val reverseGeocodeLocationLondon = Location("example")
reverseGeocodeLocationLondon.latitude = 51.5074
reverseGeocodeLocationLondon.longitude = -0.1278

Radar.reverseGeocode(reverseGeocodeLocationLondon) { status, addresses ->
Log.v(
"example",
"Reverse geocode: status = $status; coordinate = ${addresses?.first()?.formattedAddress}, timeZone = ${addresses?.first()?.timeZone?.toJson()}"
)
}

Expand Down
2 changes: 1 addition & 1 deletion sdk/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ apply plugin: "org.jetbrains.dokka"
apply plugin: 'io.radar.mvnpublish'

ext {
radarVersion = '3.19.0'
radarVersion = '3.20.0'
}

String buildNumber = ".${System.currentTimeMillis()}"
Expand Down
19 changes: 13 additions & 6 deletions sdk/src/main/java/io/radar/sdk/RadarUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import java.security.MessageDigest
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.*
import java.time.Instant
import java.time.ZonedDateTime
import kotlin.math.abs

internal object RadarUtils {
Expand Down Expand Up @@ -136,13 +138,18 @@ internal object RadarUtils {
if (str == null) {
return null
}

val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)
dateFormat.timeZone = TimeZone.getTimeZone("UTC")

try {
return dateFormat.parse(str)
} catch (pe: ParseException) {
return null;
return Date.from(ZonedDateTime.parse(str).toInstant())
} catch (e: Exception) {
// Fall back to old date format
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)
dateFormat.timeZone = TimeZone.getTimeZone("UTC")
return try {
dateFormat.parse(str)
} catch (pe: ParseException) {
null
}
}
}

Expand Down
66 changes: 47 additions & 19 deletions sdk/src/main/java/io/radar/sdk/model/RadarTimeZone.kt
Original file line number Diff line number Diff line change
Expand Up @@ -56,22 +56,26 @@ class RadarTimeZone(
if (obj == null) {
return null
}
val id = obj.getString("id")
val name = obj.getString("name")
val code = obj.getString("code")
val currentTime = obj.getString("currentTime")
val utcOffset = obj.getInt("utcOffset")
val dstOffset = obj.getInt("dstOffset")
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZZZ", Locale.US)
return try {
val id = obj.getString("id")
val name = obj.getString("name")
val code = obj.getString("code")
val utcOffset = obj.getInt("utcOffset")
val dstOffset = obj.getInt("dstOffset")
val currentTimeStr = obj.getString(FIELD_CURRENT_TIME)
val parsedDate = RadarUtils.isoStringToDate(currentTimeStr) ?: return null

return RadarTimeZone(
id,
name,
code,
dateFormat.parse(currentTime)!!,
utcOffset,
dstOffset,
)
return RadarTimeZone(
id,
name,
code,
currentTime = parsedDate,
utcOffset,
dstOffset,
)
} catch (e: Exception) {
null
}
}
}

Expand All @@ -80,11 +84,35 @@ class RadarTimeZone(
obj.putOpt(FIELD_ID, id)
obj.putOpt(FIELD_NAME, name)
obj.putOpt(FIELD_CODE, code)
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZZZ", Locale.US)
obj.putOpt(FIELD_CURRENT_TIME, dateFormat.format(currentTime))

// Create formatter based on the timezone offset
val dateFormat = if (utcOffset == 0) {
// For UTC/GMT times, use 'Z' format
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
} else {
// For offset times, use ZZZZZ format
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZZZ", Locale.US)
}

// Set the timezone based on the id and offset
val tz = if (utcOffset == 0) {
TimeZone.getTimeZone("UTC")
} else {
TimeZone.getTimeZone(id)
}
dateFormat.timeZone = tz

val formattedTime = dateFormat.format(currentTime)
val finalTime = if (utcOffset == 0) {
formattedTime
} else {
// Insert colon between hours and minutes of timezone offset
formattedTime.substring(0, formattedTime.length - 2) + ":" + formattedTime.substring(formattedTime.length - 2)
}

obj.putOpt(FIELD_CURRENT_TIME, finalTime)
obj.putOpt(FIELD_UTC_OFFSET, utcOffset)
obj.putOpt(FIELD_DST_OFFSET, dstOffset)
return obj
}

}
}
130 changes: 130 additions & 0 deletions sdk/src/test/java/io/radar/sdk/RadarTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1347,6 +1347,136 @@ class RadarTest {

assertEquals(Radar.RadarStatus.SUCCESS, callbackStatus)
assertAddressesOk(callbackAddresses)

// Add timezone verification
val address = callbackAddresses?.get(0)
assertNotNull(address?.timeZone)
assertEquals("America/New_York", address?.timeZone?.id)
assertEquals("Eastern Standard Time", address?.timeZone?.name)
assertEquals("EST", address?.timeZone?.code)
assertEquals(-18000, address?.timeZone?.utcOffset)
assertEquals(0, address?.timeZone?.dstOffset)

// Test the Date object
val timeZoneDate = address?.timeZone?.currentTime
assertNotNull(timeZoneDate)
// January 21, 2025 12:19:23 EST
val expectedTime = Calendar.getInstance(TimeZone.getTimeZone("America/New_York")).apply {
set(2025, Calendar.JANUARY, 21, 12, 19, 23)
set(Calendar.MILLISECOND, 0)
}.time
assertEquals(expectedTime, timeZoneDate)

// Test the formatted string representation
val timeZoneJson = address?.timeZone?.toJson()
val formattedTime = timeZoneJson?.optString("currentTime")
assertNotNull(formattedTime)
assertTrue("NYC time should end with -05:00 offset but was: $formattedTime",
formattedTime != null && formattedTime.toString().endsWith("-05:00"))
}

@Test
fun test_Radar_reverseGeocode_london_location_success() {
permissionsHelperMock.mockFineLocationPermissionGranted = false
apiHelperMock.mockStatus = Radar.RadarStatus.SUCCESS
apiHelperMock.mockResponse = RadarTestUtils.jsonObjectFromResource("/geocode_london.json")

val mockLocation = Location("RadarSDK")
mockLocation.latitude = 51.5074
mockLocation.longitude = -0.1278

val latch = CountDownLatch(1)
var callbackStatus: Radar.RadarStatus? = null
var callbackAddresses: Array<RadarAddress>? = null

Radar.reverseGeocode(mockLocation) { status, addresses ->
callbackStatus = status
callbackAddresses = addresses
latch.countDown()
}

ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
latch.await(LATCH_TIMEOUT, TimeUnit.SECONDS)

assertEquals(Radar.RadarStatus.SUCCESS, callbackStatus)

// Add timezone verification
val address = callbackAddresses?.get(0)
assertNotNull(address?.timeZone)
assertEquals("Europe/London", address?.timeZone?.id)
assertEquals("Greenwich Mean Time", address?.timeZone?.name)
assertEquals("GMT", address?.timeZone?.code)
assertEquals(0, address?.timeZone?.utcOffset)
assertEquals(0, address?.timeZone?.dstOffset)

// Test the Date object
val timeZoneDate = address?.timeZone?.currentTime
assertNotNull(timeZoneDate)
// January 21, 2025 17:22:19 UTC
val expectedTime = Calendar.getInstance(TimeZone.getTimeZone("UTC")).apply {
set(2025, Calendar.JANUARY, 21, 17, 22, 19)
set(Calendar.MILLISECOND, 0)
}.time
assertEquals(expectedTime, timeZoneDate)

// Test the formatted string representation
val timeZoneJson = address?.timeZone?.toJson()
val formattedTime = timeZoneJson?.optString("currentTime")
assertNotNull(formattedTime)
assertTrue("London time should end with Z but was: $formattedTime",
formattedTime != null && formattedTime.toString().endsWith("Z"))
}

@Test
fun test_Radar_reverseGeocode_darwin_location_success() {
permissionsHelperMock.mockFineLocationPermissionGranted = false
apiHelperMock.mockStatus = Radar.RadarStatus.SUCCESS
apiHelperMock.mockResponse = RadarTestUtils.jsonObjectFromResource("/geocode_darwin.json")

val mockLocation = Location("RadarSDK")
mockLocation.latitude = -12.463872
mockLocation.longitude = 130.844064

val latch = CountDownLatch(1)
var callbackStatus: Radar.RadarStatus? = null
var callbackAddresses: Array<RadarAddress>? = null

Radar.reverseGeocode(mockLocation) { status, addresses ->
callbackStatus = status
callbackAddresses = addresses
latch.countDown()
}

ShadowLooper.runUiThreadTasksIncludingDelayedTasks()
latch.await(LATCH_TIMEOUT, TimeUnit.SECONDS)

assertEquals(Radar.RadarStatus.SUCCESS, callbackStatus)

// Add timezone verification
val address = callbackAddresses?.get(0)
assertNotNull(address?.timeZone)
assertEquals("Australia/Darwin", address?.timeZone?.id)
assertEquals("Australian Central Standard Time", address?.timeZone?.name)
assertEquals("ACST", address?.timeZone?.code)
assertEquals(34200, address?.timeZone?.utcOffset)
assertEquals(0, address?.timeZone?.dstOffset)

// Test the Date object
val timeZoneDate = address?.timeZone?.currentTime
assertNotNull(timeZoneDate)
// January 22, 2025 04:17:35 ACST (+09:30)
val expectedTime = Calendar.getInstance(TimeZone.getTimeZone("Australia/Darwin")).apply {
set(2025, Calendar.JANUARY, 22, 4, 17, 35)
set(Calendar.MILLISECOND, 0)
}.time
assertEquals(expectedTime, timeZoneDate)

// Test the formatted string representation
val timeZoneJson = address?.timeZone?.toJson()
val formattedTime = timeZoneJson?.optString("currentTime")
assertNotNull(formattedTime)
assertTrue("Darwin time should end with +09:30 but was: $formattedTime",
formattedTime != null && formattedTime.toString().endsWith("+09:30"))
}

@Test
Expand Down
10 changes: 9 additions & 1 deletion sdk/src/test/resources/geocode.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@
"street": "Jay Street",
"number": "20",
"addressLabel": "20 Jay Street",
"confidence": "exact"
"confidence": "exact",
"timeZone": {
"id": "America/New_York",
"name": "Eastern Standard Time",
"code": "EST",
"currentTime": "2025-01-21T12:19:23-05:00",
"utcOffset": -18000,
"dstOffset": 0
}
}
]
}
41 changes: 41 additions & 0 deletions sdk/src/test/resources/geocode_darwin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"meta": {
"code": 200
},
"addresses": [
{
"formattedAddress": "5 Cavenagh Street, Darwin CBD, NT, 0800, AU",
"addressLabel": "5 Cavenagh Street",
"number": "5",
"street": "Cavenagh Street",
"dependentLocality": "Darwin CBD",
"city": "Darwin",
"county": "Darwin",
"state": "Northern Territory",
"stateCode": "NT",
"postalCode": "0800",
"countryCode": "AU",
"country": "Australia",
"layer": "address",
"latitude": -12.463872,
"longitude": 130.844064,
"geometry": {
"type": "Point",
"coordinates": [
130.844064,
-12.463872
]
},
"distance": 41.20331642776327,
"countryFlag": "🇦🇺",
"timeZone": {
"id": "Australia/Darwin",
"name": "Australian Central Standard Time",
"code": "ACST",
"currentTime": "2025-01-22T04:17:35+09:30",
"utcOffset": 34200,
"dstOffset": 0
}
}
]
}
41 changes: 41 additions & 0 deletions sdk/src/test/resources/geocode_london.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"meta": {
"code": 200
},
"addresses": [
{
"formattedAddress": "60 Trafalgar Square, Strand, London, WC2N 5DS, UK",
"addressLabel": "60 Trafalgar Square",
"number": "60",
"street": "Trafalgar Square",
"dependentLocality": "Strand",
"city": "London",
"county": "London",
"state": "England",
"stateCode": "ENG",
"postalCode": "WC2N 5DS",
"countryCode": "GB",
"country": "United Kingdom",
"layer": "address",
"latitude": 51.5072349,
"longitude": -0.1284617,
"geometry": {
"type": "Point",
"coordinates": [
-0.1284617,
51.5072349
]
},
"distance": 49.35395812723442,
"countryFlag": "🇬🇧",
"timeZone": {
"id": "Europe/London",
"name": "Greenwich Mean Time",
"code": "GMT",
"currentTime": "2025-01-21T17:22:19Z",
"utcOffset": 0,
"dstOffset": 0
}
}
]
}

0 comments on commit 94eeaea

Please # to comment.