I\'m wondering if there is some clever way of getting the clients current time and time zone in order to use it in the server.R
part of a Shiny application. If
I hadn't heard of Shiny until your post. Reading through the documentation, it would appear that the client-side portion of a Shiny application is written in R, but then renders as HTML/CSS/JavaScript so it can run in the browser. The information you're asking for would have to be sourced from JavaScript.
Getting the current time in JavaScript is quite simple:
var now = new Date();
The result is a Date
object that has the current date and time from the client's clock. Internally, it's tracked as the UTC time in milliseconds since Midnight 1/1/1970 UTC. However the Date
object will take the client's local time zone into account when producing output such as with .toString()
or when using many of the other functions. You can read more about the Date
object in the MDN reference documentation.
Now, if you actually need the time zone of the client, that's a different story. The Date
object can only give you the time zone offset of a particular date and time, using the .getTimezoneOffset()
function. For example, you can tell that the client is currently 420 minutes behind UTC, (UTC-07:00), but you cannot tell that the client is in the America/Los_Angeles
time zone - which alternates between UTC-07:00 and UTC-08:00 for daylight saving time. Read more in the timezone tag wiki.
There is one JavaScript library, jsTimeZoneDetect, that attempts to guess at the time zone, and it does a reasonably decent job.
So - now the question would be, how do you call custom JavaScript from a Shiny app in R? I'm no expert in this area, but it would appear to be covered by this part of the Shiny documentation.
All of this would be done client-side. You would then have to send it to the server to use the information in the server.R part of the application.
I found out a method that works and which is just a small modification of the stackoverflow answer to Reading javascript variable into shiny/R on app load. It does not retrieve the actuall time zone, but well the time zone offset.
In ui.R
HTML('<input type="text" id="client_time" name="client_time" style="display: none;"> '),
HTML('<input type="text" id="client_time_zone_offset" name="client_time_zone_offset" style="display: none;"> '),
tags$script('
$(function() {
var time_now = new Date()
$("input#client_time").val(time_now.getTime())
$("input#client_time_zone_offset").val(time_now.getTimezoneOffset())
});
')
This above created two div
s and the javascript code retrieves the clients time and time zone offset and put them in the div
s.
In server.R
client_time <- reactive(as.numeric(input$client_time) / 1000) # in s
time_zone_offset <- reactive(as.numeric(input$client_time_zone_offset) * 60 ) # in s
This above creates two reactive variables that can be used in the server code. For ease of handling I also transform input$client_time
from ms to s and input$client_time_zone_offset
from min to s.
You could fetch it over socket communication, but with much overhead, or if your time between the client is synchronized, you could trust your local time, fetch their timezone, evaluate the timezone difference between the server and the client, plus/minus the difference, then you would fetch their time and timezone without much work. (Somehow probably but in most case, by doing this, would lose a precision of 2 to 3 seconds due to computer keep up)
Another way to do it : send a message to the client at any time and retrieve the result with the input object. You will also measuring latency. (Or if the user changed of time zone during the shiny session...).
Get server time and time zone, send it to client via sendCustomMessage.
# R
triggerClientTime <- function(session=shiny::getDefaultReactiveDomain()){
serverTime <- Sys.time()
serverTimeZone <- as.integer(strftime(serverTime,"%z"))/100
session$sendCustomMessage(
type="getClientTime",
message=list(
serverPosix = as.numeric(serverTime),
serverTimeZone = serverTimeZone
)
)
}
Put this function in server file.
In javascript, get the message, retrieve time and zone offset, send it back to server as a new input entry :
// js
Shiny.addCustomMessageHandler("getClientTime",
function(s){
var d = new Date()
var clientPosix = parseInt(d.getTime()/1000);
var clientTimeZone = -(d.getTimezoneOffset() / 60);
var res = {
serverPosix:s.serverPosix,
serverTimeZone:s.serverTimeZone,
clientPosix:clientPosix,
clientTimeZone:clientTimeZone
}
Shiny.onInputChange("clientTime",res)
})
Put this code in a script tag or in a separate js file.
In a shiny session :
# Observe and print time from client and server
observe({
print(input$clientTime)
})
# Ask the client for current time and time zone (hours from UTC)
triggerClientTime()
It should output something like:
# output
$serverPosix
[1] 1449827815
$serverTimeZone
[1] 1
$clientPosix
[1] 1449827816
$clientTimeZone
[1] 1