Convert to local time zone from latitude and longitude R
Asked Answered
L

1

8

I have one data frame with a lot of locations (around 30.000), and I need to convert the time of each location for the local time. I tried some ideas like this one, and this one. But they did not work for me.

I have data like this:

dt = data.table(date = c("2018-01-16 22:02:37",
                         "2018-01-16 22:54:00", 
                         "2018-01-16 23:08:38"),
                lat = c(-54.5010,
                        -54.5246,
                        -54.5285),
                long = c(-25.0433, 
                         -25.0929,
                         -25.0832))

And I expected this output:

date                      lat           long
2018-01-16 20:02:37       -54.5010      -25.0433
2018-01-16 20:54:00       -54.5246      -25.0929
2018-01-16 21:08:38       -54.5285      -25.0832

One try:

library(sf)
dt = data.table(date = c("2018-01-16 22:02:37",
                         "2018-01-16 22:54:00", 
                         "2018-01-16 23:08:38"),
                lat = c(-54.5010,
                        -54.5246,
                        -54.5285),
                long = c(-25.0433, 
                         -25.0929,
                         -25.0832))

sdf = st_as_sf(dt, coords = c("long", "lat"), crs = 4326)

## import timezones (after extraction) and intersect with spatial points
tzs = st_read("timezones.geojson/combined.json", quiet = TRUE) #HERE DONT WORK
sdf = st_join(sdf, tzs)

## convert timestamps to local time
sdf$timeL = as.POSIXlt(sdf$time1, tz = as.character(sdf$tzid))
sdf$timeL
Cannot open data source timezones.geojson/combined.json

Error in CPL_read_ogr(dsn, layer, query, as.character(options), quiet,  :
  Open failed.

Then I tried:

library(lutz)
library(sf)
library(purrr)
library(dplyr)

download.file("https://github.com/evansiroky/timezone-boundary-builder/releases/download/2019a/timezones-with-oceans.geojson.zip",
              destfile = "tz.zip")
unzip("tz.zip", exdir = "data-raw/dist/")
tz_full <- read_sf("data-raw/dist/combined-with-oceans.json")

But this didn't work either.

Cannot open data source ~/Dropbox/Érika Project/mestrado_R/bhv_loc_R/tables/data-raw/dist/combined-with-oceans.json
Error in CPL_read_ogr(dsn, layer, query, as.character(options), quiet,  : 
  Open failed.

I got it just like this:

library(lutz)

v <- tz_lookup_coords(lat = dt$lat, lon = dt$lon, method = "accurate")
v1<-as.data.frame(v)

The output:

[1] "America/Bahia" "Etc/GMT+3"     "Etc/GMT+3"     "Etc/GMT+3"     "Etc/GMT+3"     "Etc/GMT+3"   

But with this output I dont know how can I convert the timezones.

I thought in do something like this:

v1$tzone <- NA
v1[v1$v == "America/Bahia", "tzone"] <- "+3" 
v1[v1$v == "America/Sao_Paulo", "tzone"] <- "+3" 
v1[v1$v == "Etc/GMT+2", "tzone"] <- "+2" 
v1[v1$v == "Etc/GMT+3", "tzone"] <- "+3" 

  if (v1$tzone == "+3" ) {
  v1$timeBR <- NA
  v1$timeBR <- strptime(v1$time, format = "%Y-%m-%d %H:%M:%S")
  v1$timeBR <- v1$timeBR -3*3600 #creating a column corresponding to local Brazilian time (UTC -3)
  v1$hourBR <- as.POSIXlt(v1$timeBR)$hour
  v1 <- v1[!is.na(v1$timeBR),]
  }

#But the function not works (I dont know do functions), would gonna be better one function with the two condition +3 and +2 

EDIT

With the suggestion:

> library(data.table)
data.table 1.12.8 using 2 threads (see ?getDTthreads).  Latest news: r-datatable.com
Warning message:
package ‘data.table’ was built under R version 3.5.2 
> library(lutz)
Warning message:
package ‘lutz’ was built under R version 3.5.2 
> library(purrr)

Attaching package: ‘purrr’

The following object is masked from ‘package:data.table’:

    transpose

> library(lubridate)

Attaching package: ‘lubridate’

The following objects are masked from ‘package:data.table’:

    hour, isoweek, mday, minute, month, quarter, second, wday, week, yday, year

The following object is masked from ‘package:base’:

    date

#t it is the original data
> head(t$time)
[1] 2017-10-16 17:01:00 2017-10-16 18:35:22 2017-10-16 20:38:54 2017-10-16 21:27:27 2017-10-16 21:43:20
[6] 2017-10-16 23:24:46
27092 Levels: 2016-10-24 15:42:00 2016-10-24 21:03:28 2016-10-24 22:04:35 2016-10-24 23:13:40 ... 2020-01-10 11:34:21
> class(t$time)
[1] "factor"
> date2<-t$time
> class(date2)
[1] "factor"
> date2<- as.character(t$time)
> class(date2)
[1] "character"
> head(date2)
[1] "2017-10-16 17:01:00" "2017-10-16 18:35:22" "2017-10-16 20:38:54" "2017-10-16 21:27:27" "2017-10-16 21:43:20"
[6] "2017-10-16 23:24:46"
> t[, date2 := as.POSIXct(date2, format = "%Y-%m-%d %H:%M:%S", tz = "GMT")][,
+                                                                          timezone := tz_lookup_coords(lat = lat, lon = long, method = "accurate")][,
+                                                                                                                                                    new_time := map2(.x = date2, .y = timezone, 
+                                                                                                                                                                     .f = function(x, y) {with_tz(time = x, tzone = y)})][]
Error in `:=`(date2, as.POSIXct(date2, format = "%Y-%m-%d %H:%M:%S", tz = "GMT")) : 
  Check that is.data.table(DT) == TRUE. Otherwise, := and `:=`(...) are defined for use in j, once only and in particular ways. See help(":=").

EDIT2

I found what was going wrong, my original data wasn't in data.table and data.frame format! Then, now I have this list inside the data. Im trying to convert in one new column

EDIT 3

NOW WORKED! THANKS TO ALL FOR COMMENTS AND HELP

t[, date2 := as.POSIXct(date2, format = "%Y-%m-%d %H:%M:%S", tz = "GMT")]
[,timezone := tz_lookup_coords(lat = lat, lon = lon, method = "accurate")]
[,new_time := map2(.x = date2, .y = timezone,
.f = function(x, y) {with_tz(time = x, tzone = y)})][]

newtime<-do.call(rbind, lapply(t$new_time, as.data.frame))
t$newtime<-paste(newtime$`X[[i]]`)

head(t$newtime)
[1] "2016-10-24 12:42:00" "2016-10-24 18:03:28" "2016-10-24 19:04:35" "2016-10-24 20:13:40" "2016-10-24 21:13:00"
[6] "2016-10-25 02:17:05"

Someone know how to do this? Thanks

Lorrielorrimer answered 21/1, 2020 at 3:7 Comment(9)
I'm not sure how this is any different from #23414840 which you linked? Can you give any detail about what part went wrong?Zink
both functions allow only few locations, or just one..Narra
The second answer to the linked question at https://mcmap.net/q/1412863/-convert-to-local-time-zone-using-latitude-and-longitude looks to only use local data and should support many points.Zink
I actualized the code aboveNarra
You need to download the file with the timezone boundaries from the linked site over there ( github.com/evansiroky/timezone-boundary-builder ), extract it to your working directory and then try the code again.Zink
i dont know what's going wrong, didnt worksNarra
I don't know much about sf, but you are using read_sf and not st_read as per the previous answer. Does that make a difference?Zink
I tried here now with st_read, dint work too..Narra
I managed it in a way, but I don't know how to convert the timezone with this outputNarra
H
5

I think this is what you want. I first created a date object. Then, I searched time zones with tz_lookup_coords() as you were trying. Then, I used with_tz(), which gets date-time in a different time zone. Note that new_time is a list as str(dt) indicates.

library(data.table)
library(lutz)
library(purrr)
library(lubridate)

dt[, date := as.POSIXct(date, format = "%Y-%m-%d %H:%M:%S", tz = "GMT")][,
    timezone := tz_lookup_coords(lat = lat, lon = long, method = "accurate")][,
      new_time := map2(.x = date, .y = timezone, 
                       .f = function(x, y) {with_tz(time = x, tzone = y)})][]

#                  date      lat     long  timezone            new_time
#1: 2018-01-16 22:02:37 -54.5010 -25.0433 Etc/GMT+2 2018-01-16 20:02:37
#2: 2018-01-16 22:54:00 -54.5246 -25.0929 Etc/GMT+2 2018-01-16 20:54:00
#3: 2018-01-16 23:08:38 -54.5285 -25.0832 Etc/GMT+2 2018-01-16 21:08:38

#str(dt)
#Classes ‘data.table’ and 'data.frame': 3 obs. of  5 variables:
# $ date    : POSIXct, format: "2018-01-16 22:02:37" "2018-01-16 22:54:00" "2018-01-16 23:08:38"
# $ lat     : num  -54.5 -54.5 -54.5
# $ long    : num  -25 -25.1 -25.1
# $ timezone: chr  "Etc/GMT+2" "Etc/GMT+2" "Etc/GMT+2"
# $ new_time:List of 3
#  ..$ : POSIXct, format: "2018-01-16 20:02:37"
#  ..$ : POSIXct, format: "2018-01-16 20:54:00"
#  ..$ : POSIXct, format: "2018-01-16 21:08:38"

A bit more help

If you have a data frame, you can use tidyverse approaches too. I used your dt here. I converted it to a data.frame object. The last thin you need is unnest(). Then, you will have time in a column.

setDF(dt) %>% 
mutate(date = as.POSIXct(date, format = "%Y-%m-%d %H:%M:%S", tz = "GMT"),
       timezone = tz_lookup_coords(lat = lat, lon = long, method = "accurate"),
       new_time = map2(.x = date, .y = timezone, 
                       .f = function(x, y) {with_tz(time = x, tzone = y)})) %>% 
unnest(new_time) 

   date                  lat  long timezone  new_time           
  <dttm>              <dbl> <dbl> <chr>     <dttm>             
1 2018-01-16 22:02:37 -54.5 -25.0 Etc/GMT+2 2018-01-16 20:02:37
2 2018-01-16 22:54:00 -54.5 -25.1 Etc/GMT+2 2018-01-16 20:54:00
3 2018-01-16 23:08:38 -54.5 -25.1 Etc/GMT+2 2018-01-16 21:08:38

> str(foo)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   3 obs. of  5 variables:
 $ date    : POSIXct, format: "2018-01-16 22:02:37" "2018-01-16 22:54:00" "2018-01-16 23:08:38"
 $ lat     : num  -54.5 -54.5 -54.5
 $ long    : num  -25 -25.1 -25.1
 $ timezone: chr  "Etc/GMT+2" "Etc/GMT+2" "Etc/GMT+2"
 $ new_time: POSIXct, format: "2018-01-16 20:02:37" "2018-01-16 20:54:00" "2018-01-16 21:08:38"
Hogen answered 21/1, 2020 at 13:23 Comment(2)
Thank you! When I run with the example its ok! but when I tried with my data, didn't work.. Error in :=(date2, as.POSIXct(date2, format = "%Y-%m-%d %H:%M:%S", tz = "GMT")) : Check that is.data.table(DT) == TRUE. Otherwise, := and :=(...) are defined for use in j, once only and in particular ways. See help(":="). I notice that in example the date was in character, then I convert my original data to character too, but didnt workNarra
@ÉrikaSoaresCoelho I added a tydyverse approach for you. The key is to use unnest(). I hope this is enough for you.Hogen

© 2022 - 2024 — McMap. All rights reserved.