确定用户的时区

是否有一种标准方法让Web服务器能够在网页中确定用户的时区?

可能来自HTTP标头或user-agent字符串的一部分?

270154 次浏览

到目前为止,还没有报告客户端时区的HTTP标头,尽管有人建议将其包含在HTTP规范中。

如果是我,我可能会尝试使用客户端JavaScript获取时区,然后使用Ajax或其他方法将其提交给服务器。

JavaScript是获取客户端本地时间的最简单方法。我建议使用XMLHttpRequest相关文档发送回本地时间,如果失败,则返回到根据其IP地址检测到的时区。

至于地理定位,我在几个项目中使用了MaxMind GeoIP,效果很好,尽管我不确定他们是否提供时区数据。这是一项你付费的服务,他们每月提供数据库更新。他们提供多种Web语言的包装器。

确定时区的最流行的(==标准?)方法我见过简单地问用户自己。如果你的网站需要订阅,这可以保存在用户的个人资料数据中。对于匿名用户,日期可以显示为UTC或GMT或其他。

我并不是自作聪明,只是有时候有些问题在编程环境之外有更好的解决方案。

所有的魔法似乎都在

visitortime.getTimezoneOffset()

这很酷,我不知道。它在Internet Explorer等中工作吗?从那里你应该能够使用JavaScript到Ajax,设置cookie等等。我可能会自己走cookie路线。

不过,你需要允许用户更改它。我们不久前尝试使用地理位置(通过maxmind)来执行此操作,它是错误的,不值得这样做。所以我们让用户在他们的个人资料中设置它,并向尚未设置的用户显示通知。

如果您碰巧使用OpenID进行身份验证,简单注册扩展将解决经过身份验证的用户的问题(您需要从tz转换为数字)。

另一种选择是从用户代理的国家偏好中推断时区。这是一种有点粗糙的方法(不适用于en-US),但它是一个很好的近似值。

-new Date().getTimezoneOffset()/60;

方法getTimezoneOffset()将从GMT中减去您的时间并返回分钟数。因此,如果您生活在GMT-8,它将返回480。

要将其转换为小时,请除以60。此外,请注意该符号与您需要的相反-它计算的是GMT与您的时区的偏移量,而不是您的时区与GMT的偏移量。要解决此问题,只需乘以-1。

另请注意w3School表示:

返回值不是常量,因为使用的做法 夏令时

以下是一篇文章(包含源代码),解释了如何在ASP.NET(VB.NET,C#)应用程序中确定和使用本地化时间:

是时候了

简而言之,所描述的方法依赖于JavaScriptgetTimezoneOffset函数,该函数返回保存在会话cookie中的值,并由代码隐藏用于调整GMT和本地时间之间的时间值。好的方面是用户不需要指定时区(代码自动完成)。还有更多的参与(这就是为什么我链接到文章),但提供的代码使其非常易于使用。我怀疑你可以将逻辑转换为PHP和其他语言(只要你理解ASP.NET)。

使用PHPdate函数,您将获得站点所在服务器的日期时间。获取用户时间的唯一方法是使用JavaScript。

但我建议你,如果你的网站需要注册,那么最好的方法是要求用户注册为必填字段。您可以在注册页面中列出各种时区并将其保存在数据库中。之后,如果用户登录到网站,那么您可以根据用户选择的时区设置该会话的默认时区。

您可以使用PHP函数date_default_timezone_set设置任何特定的时区。这将为用户设置指定的时区。

基本上用户的时区是去客户端,所以我们必须使用JavaScript。

下面是使用PHP和JavaScript获取用户时区的脚本。

<?php
#http://www.php.net/manual/en/timezones.php List of Time Zones
function showclienttime()
{
if(!isset($_COOKIE['GMT_bias']))
{
?>


<script type="text/javascript">
var Cookies = {};
Cookies.create = function (name, value, days) {
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
var expires = "; expires=" + date.toGMTString();
}
else {
var expires = "";
}
document.cookie = name + "=" + value + expires + "; path=/";
this[name] = value;
}


var now = new Date();
Cookies.create("GMT_bias",now.getTimezoneOffset(),1);
window.location = "<?php echo $_SERVER['PHP_SELF'];?>";
</script>


<?php


}
else {
$fct_clientbias = $_COOKIE['GMT_bias'];
}


$fct_servertimedata = gettimeofday();
$fct_servertime = $fct_servertimedata['sec'];
$fct_serverbias = $fct_servertimedata['minuteswest'];
$fct_totalbias = $fct_serverbias – $fct_clientbias;
$fct_totalbias = $fct_totalbias * 60;
$fct_clienttimestamp = $fct_servertime + $fct_totalbias;
$fct_time = time();
$fct_year = strftime("%Y", $fct_clienttimestamp);
$fct_month = strftime("%B", $fct_clienttimestamp);
$fct_day = strftime("%d", $fct_clienttimestamp);
$fct_hour = strftime("%I", $fct_clienttimestamp);
$fct_minute = strftime("%M", $fct_clienttimestamp);
$fct_second = strftime("%S", $fct_clienttimestamp);
$fct_am_pm = strftime("%p", $fct_clienttimestamp);
echo $fct_day.", ".$fct_month." ".$fct_year." ( ".$fct_hour.":".$fct_minute.":".$fct_second." ".$fct_am_pm." )";
}


showclienttime();
?>

但根据我的观点,最好向用户询问您的项目中是否强制注册。

这里有一个更完整的方法。

  1. 获取用户的时区偏移量
  2. 在夏令时边界上测试一些日子,以确定它们是否在使用夏令时的区域中。

摘录如下:

function TimezoneDetect(){
var dtDate = new Date('1/1/' + (new Date()).getUTCFullYear());
var intOffset = 10000; //set initial offset high so it is adjusted on the first attempt
var intMonth;
var intHoursUtc;
var intHours;
var intDaysMultiplyBy;


// Go through each month to find the lowest offset to account for DST
for (intMonth=0;intMonth < 12;intMonth++){
//go to the next month
dtDate.setUTCMonth(dtDate.getUTCMonth() + 1);


// To ignore daylight saving time look for the lowest offset.
// Since, during DST, the clock moves forward, it'll be a bigger number.
if (intOffset > (dtDate.getTimezoneOffset() * (-1))){
intOffset = (dtDate.getTimezoneOffset() * (-1));
}
}


return intOffset;
}

从JS获取TZ和DST(via Way Back Machine)

使用Unkhntech的方法,我使用jQuery和PHP编写了一个函数。这是经过测试的,确实有效!

在您希望将时区作为变量的PHP页面上,将这段代码片段放在页面顶部附近的某个地方:

<?php
session_start();
$timezone = $_SESSION['time'];
?>

这将读取会话变量“time”,我们现在要创建它。

在同一页面上,在中,您首先需要包含jQuery:

<script type="text/javascript" src="http://code.jquery.com/jquery-latest.min.js"></script>

同样在中,在jQuery下方,粘贴:

<script type="text/javascript">
$(document).ready(function() {
if("<?php echo $timezone; ?>".length==0){
var visitortime = new Date();
var visitortimezone = "GMT " + -visitortime.getTimezoneOffset()/60;
$.ajax({
type: "GET",
url: "http://example.org/timezone.php",
data: 'time='+ visitortimezone,
success: function(){
location.reload();
}
});
}
});
</script>

您可能已经或可能没有注意到,但您需要将URL更改为您的实际域。

最后一件事。你可能想知道timezone.php是什么。嗯,简单地说: (创建一个名为timezone.php的新文件,并使用上述URL指向它)

<?php
session_start();
$_SESSION['time'] = $_GET['time'];
?>

如果此操作正确,它将首先加载页面,执行JavaScript并重新加载页面。然后您将能够读取$timezone变量并随心所欲地使用它!它返回当前UTC/GMT时区偏移量(GMT-7)或您所在的任何时区。

JavaScript:

function maketimus(timestampz)
{
var linktime = new Date(timestampz * 1000);
var linkday = linktime.getDate();
var freakingmonths = new Array();


freakingmonths[0]  = "jan";
freakingmonths[1]  = "feb";
freakingmonths[2]  = "mar";
freakingmonths[3]  = "apr";
freakingmonths[4]  = "may";
freakingmonths[5]  = "jun";
freakingmonths[6]  = "jul";
freakingmonths[7]  = "aug";
freakingmonths[8]  = "sep";
freakingmonths[9]  = "oct";
freakingmonths[10] = "nov";
freakingmonths[11] = "dec";


var linkmonthnum = linktime.getMonth();
var linkmonth = freakingmonths[linkmonthnum];
var linkyear = linktime.getFullYear();
var linkhour = linktime.getHours();
var linkminute = linktime.getMinutes();


if (linkminute < 10)
{
linkminute = "0" + linkminute;
}


var fomratedtime = linkday + linkmonth + linkyear + " " +
linkhour + ":" + linkminute + "h";
return fomratedtime;
}

只需将Unix时间戳格式的时间提供给此函数;JavaScript已经知道用户的时区。

像这样:

PHP:

echo '<script type="text/javascript">
var eltimio = maketimus('.$unix_timestamp_ofshiz.');
document.write(eltimio);
</script><noscript>pls enable javascript</noscript>';

这将始终根据该人在他/她的计算机时钟上设置的时区正确显示时间。没有必要向任何人询问任何事情并将其保存到某个地方,感谢上帝!

一个简单的方法是使用:

new Date().getTimezoneOffset();

我是这样做的。这将把PHP默认时区设置为用户的本地时区。只需将以下内容粘贴在所有页面的顶部:

<?php
session_start();


if(!isset($_SESSION['timezone']))
{
if(!isset($_REQUEST['offset']))
{
?>
<script>
var d = new Date()
var offset= -d.getTimezoneOffset()/60;
location.href = "<?php echo $_SERVER['PHP_SELF']; ?>?offset="+offset;
</script>
<?php
}
else
{
$zonelist = array('Kwajalein' => -12.00, 'Pacific/Midway' => -11.00, 'Pacific/Honolulu' => -10.00, 'America/Anchorage' => -9.00, 'America/Los_Angeles' => -8.00, 'America/Denver' => -7.00, 'America/Tegucigalpa' => -6.00, 'America/New_York' => -5.00, 'America/Caracas' => -4.30, 'America/Halifax' => -4.00, 'America/St_Johns' => -3.30, 'America/Argentina/Buenos_Aires' => -3.00, 'America/Sao_Paulo' => -3.00, 'Atlantic/South_Georgia' => -2.00, 'Atlantic/Azores' => -1.00, 'Europe/Dublin' => 0, 'Europe/Belgrade' => 1.00, 'Europe/Minsk' => 2.00, 'Asia/Kuwait' => 3.00, 'Asia/Tehran' => 3.30, 'Asia/Muscat' => 4.00, 'Asia/Yekaterinburg' => 5.00, 'Asia/Kolkata' => 5.30, 'Asia/Katmandu' => 5.45, 'Asia/Dhaka' => 6.00, 'Asia/Rangoon' => 6.30, 'Asia/Krasnoyarsk' => 7.00, 'Asia/Brunei' => 8.00, 'Asia/Seoul' => 9.00, 'Australia/Darwin' => 9.30, 'Australia/Canberra' => 10.00, 'Asia/Magadan' => 11.00, 'Pacific/Fiji' => 12.00, 'Pacific/Tongatapu' => 13.00);
$index = array_keys($zonelist, $_REQUEST['offset']);
$_SESSION['timezone'] = $index[0];
}
}


date_default_timezone_set($_SESSION['timezone']);


//rest of your code goes here
?>

简单,只需像这样使用JavaScriptgetTimezoneOffset函数:

-new Date().getTimezoneOffset()/60;

试试这个PHP代码:

<?php
$ip = $_SERVER['REMOTE_ADDR'];
$json = file_get_contents("http://api.easyjquery.com/ips/?ip=" . $ip . "&full=true");
$json = json_decode($json,true);
$timezone = $json['LocalTimeZone'];
?>

这是一个强大的JavaScript解决方案来确定浏览器所处的时区。

>>> var timezone = jstz.determine();
>>> timezone.name();
"Europe/London"

https://github.com/pellepim/jstimezonedetect

我仍然没有在这里看到获得时区的详细答案。您不需要通过IP地址进行地理编码或使用PHP(lol)或从偏移量错误地猜测。

首先,时区不仅仅是格林尼治标准时间的偏移量。它是由当地标准设置时间规则的土地区域。一些国家有夏令时,并且会在不同的时间打开DST。通常重要的是获得实际的区域,而不仅仅是当前的偏移量。

如果您打算存储此时区,例如在用户首选项中,您需要区域而不仅仅是偏移量。对于实时转换,这无关紧要。

现在,要使用javascript获取时区,您可以使用以下命令:

>> new Date().toTimeString();
"15:46:04 GMT+1200 (New Zealand Standard Time)"
//Use some regular expression to extract the time.

然而,我发现简单地使用这个强大的插件更容易返回Olsen格式的时区:

https://github.com/scottwater/jquery.detect_timezone

使用jQuery在AJAX请求上将时区偏移作为HTTP标头提交

$.ajaxSetup({
beforeSend: function(xhr, settings) {
xhr.setRequestHeader("X-TZ-Offset", -new Date().getTimezoneOffset()/60);
}
});

您还可以通过使用http://momentjs.com/timezone/docs/#/using-timezones/guessing-user-timezone/中的moment.tz.guess();来执行类似的操作来获取实际的时区名称

不要使用IP地址来确定位置(以及时区)-这是因为使用NAT,代理(越来越流行)和VPN,IP地址不一定真实地反映用户的实际位置,而是实现这些协议的服务器所在的位置。

类似于美国区号对于定位电话用户不再有用,因为号码可移植性很受欢迎。

上面显示的IP地址和其他技术对于用户可以调整/更正的表明违约很有用。

首先,了解JavaScript中的时区检测是不完美的。您可以在Date对象的实例上使用getTimezoneOffset获取特定日期和时间的本地时区偏移,但这与完整的IANA时区(如America/Los_Angeles)不太一样。

但是有一些选项可以工作:

const tzid = Intl.DateTimeFormat().resolvedOptions().timeZone;
console.log(tzid);

结果是一个字符串,其中包含运行代码的计算机的IANA时区设置。

支持的环境列出了在Intl兼容性表中。展开DateTimeFormat部分,查看名为resolvedOptions().timeZone defaults to the host environment的功能。

  • 某些库(例如卢克松)使用此API通过luxon.Settings.defaultZoneName等函数来确定时区。

  • 如果你需要支持更广泛的环境集,例如较旧的Web浏览器,你可以使用库在时区制作有根据的猜测。他们的工作原理是首先尝试Intl API(如果可用),当它不可用时,他们会在几个不同的时间点询问Date对象的getTimezoneOffset函数,使用结果从内部数据集中选择适当的时区。

    开始检测视频内容jsTimezoneDetect时刻时区都有这个功能。

      // using jsTimeZoneDetect
    var tzid = jstz.determine().name();
    
    
    // using moment-timezone
    var tzid = moment.tz.guess();
    

    在这两种情况下,结果只能被认为是猜测。猜测在许多情况下可能是正确的,但不是全部。

    此外,这些库必须定期更新,以抵消许多较旧的JavaScript实现仅了解其本地时区的当前夏令时规则的事实。更多细节在这里。

最终,更好的方法是实际询问您的用户他们的时区。提供他们可以更改的设置。您可以使用上述选项之一来选择默认设置,但不要使其无法在您的应用程序中偏离该设置。

还有一种与没有完全不同的方法,完全依赖于用户计算机的时区设置。相反,如果你可以收集纬度和经度坐标,你可以使用这些方法之一将它们解析为时区。这在移动设备上效果很好。

一种可能的选择是使用Date头字段,该字段在rfc7231中定义,应该包含时区。当然,不能保证该值真的是客户端的时区,但它可以是一个方便的起点。

使用JavaScript和PHP很简单:

即使用户可以弄乱他/她的内部时钟和/或时区,到目前为止,我找到的获得偏移量的最佳方法仍然是new Date().getTimezoneOffset();。它是非侵入性的,不会让人头疼,也不需要依赖第三方。

假设我有一个表users,其中包含一个字段date_created int(13),用于存储Unix时间戳;

假设客户端creates a new account,数据由post接收,我需要insert/updatedate_created column与客户端的Unix时间戳,而不是服务器的。

由于在插入/更新时需要timezoneOffset,因此当客户端提交表单时,它会作为额外的$_POST元素传递,从而无需将其存储在会话和/或cookie中,也无需额外的服务器命中。

var off = (-new Date().getTimezoneOffset()/60).toString();//note the '-' in front which makes it return positive for negative offsets and negative for positive offsets
var tzo = off == '0' ? 'GMT' : off.indexOf('-') > -1 ? 'GMT'+off : 'GMT+'+off;

假设服务器接收到tzo作为$_POST['tzo']

$ts = new DateTime('now', new DateTimeZone($_POST['tzo']);
$user_time = $ts->format("F j, Y, g:i a");//will return the users current time in readable format, regardless of whether date_default_timezone() is set or not.
$user_timestamp = strtotime($user_time);

插入/更新date_created=$user_timestamp

检索date_created时,您可以像这样转换时间戳:

$date_created = // Get from the database
$created = date("F j, Y, g:i a",$date_created); // Return it to the user or whatever

现在,这个例子可能符合人们的需要,当涉及到插入first时间戳时…当涉及到额外的时间戳或表时,您可能需要考虑将tzo值插入用户表以供将来参考,或将其设置为会话或cookie。

P. S.但是如果用户旅行并切换时区怎么办?在GMT+4登录,快速移动到GMT-1并再次登录。最后一次登录将在未来。

我想……我们想太多了。

您可以使用时刻时区在客户端上执行此操作并将值发送到服务器;示例用法:

> moment.tz.guess()
"America/Asuncion"

在PHP中获取有效的TZ数据库时区名称是一个两步过程:

  1. 使用JavaScript,通过getTimezoneOffset以分钟为单位获取时区偏移量。如果本地时区落后于UTC,此偏移量将为正,如果领先于UTC,则为负。因此,您必须为偏移量添加相反的符号。

    var timezone_offset_minutes = new Date().getTimezoneOffset();
    timezone_offset_minutes = timezone_offset_minutes == 0 ? 0 : -timezone_offset_minutes;
    

    将此偏移量传递给PHP。

  2. 在PHP中,使用timezone_name_from_abbr函数将此偏移量转换为有效的时区名称。

    // Just an example.
    $timezone_offset_minutes = -360;  // $_GET['timezone_offset_minutes']
    
    
    // Convert minutes to seconds
    $timezone_name = timezone_name_from_abbr("", $timezone_offset_minutes*60, false);
    
    
    // America/Chicago
    echo $timezone_name;</code></pre>
    

I've written a blog post on it: How to Detect User Timezone in PHP. It also contains a demo.

在实际的超文本标记语言代码或任何user-agent字符串中没有这样的方法来计算时区,但是您可以做的是使用JavaScript创建一个基本函数来获取它。

我还不知道如何使用JavaScript编码,所以我的函数可能需要时间来制作。

但是,您可以尝试使用JavaScript在Date部分中的getTzimezoneOffset()函数或简单地new Date().getTimezoneOffset();来获取实际的时区。

我认为@Matt Johnson-Pints是迄今为止最好的,CanIuse搜索显示现在它被广泛采用:

https://caniuse.com/?search=Intl.DateTimeFormat

不过,挑战之一是考虑为什么你想知道时区。因为我认为大多数人都错过的一件事是他们可以改变!如果用户带着他的笔记本电脑从欧洲旅行到美国,如果你之前将其存储在数据库中,他们的时区现在不正确(即使用户从未实际更新他们的设备时区)。这也是@Mads Kristiansen答案的问题,因为用户旅行-你不能依赖它作为给定的。

例如,我的Linux笔记本电脑关闭了“自动时区”。虽然时间可能会更新我的时区,但没有。

所以我相信答案是-你需要它做什么?客户端似乎提供了一种更容易确定它的方法,但客户端和服务器端代码都将取决于用户更新他们的时区或它自动更新。我当然可能错了。

有几种方法可以确定浏览器中的时区。如果您的浏览器有可用且支持的标准功能,那就是您应该使用的。以下是以不同格式获取相同信息的三种方法。避免使用基于某些假设或硬编码区域列表进行任何猜测的非标准解决方案,尽管如果无法执行其他操作,它们可能会有所帮助。

获得此信息后,您可以将其作为非标准请求标头传递给服务器并在那里使用它。如果您还需要时区偏移量,您还可以将其传递给服务器标头或请求有效负载,可以使用dateObj.getTimezoneOffset()检索。

  1. 使用Intl API获取Olson格式(标准和推荐方式):请注意,并非所有浏览器都支持此功能。有关浏览器支持的详细信息,请参阅此链接。 此API让您以Olson格式获取时区,例如Asia/Kolkata,America/New_York等。

Intl.DateTimeFormat().resolvedOptions().timeZone

  1. Use Date object to get the long format such as India Standard Time, Eastern Standard Time etc: This is supported by all browsers.

let dateObj = new Date(2021, 11, 25, 09, 30, 00);


//then


dateObj.toString()


//yields


Sat Dec 25 2021 09:30:00 GMT+0530 (India Standard Time) //I am located in India (IST)

请注意,字符串包含长格式和短格式的时区信息。您现在可以使用regex来获取此信息:

let longZoneRegex = /\((.+)\)/;
dateObj.toString().match(longZoneRegex);


//yields


['(India Standard Time)', 'India Standard Time', index: 34, input: 'Sat Dec 25 2021 09:30:00 GMT+0530 (India Standard Time)', groups: undefined]


//Note that output is an array so use output[1] to get the timezone name.

  1. 使用Date对象获取短格式,如GMT+0530,GMT-0500等:所有浏览器都支持。

同样,你也可以把短格式拿出来:

let shortZoneRegex = /GMT[+-]\d{1,4}/;
dateObj.toString().match(shortZoneRegex);


//yields


['GMT+0530', index: 25, input: 'Sat Dec 25 2021 09:30:00 GMT+0530 (India Standard Time)', groups: undefined]


//Note that output is an array so use output[0] to get the timezone name.