我今天碰到了这个问题。我把时钟调到UTC-6.00(中美洲)时区。我正在将日期"06/01/2015::12:00:00 am " ("MM/dd/yyyy::hh: MM:ss a"格式)转换为java Date对象。然后我将date对象重新转换为String。不过,我做这件事的方式有一个小小的转折。我在下面列出了重新转换的步骤-
- 从当前时区计算UTC偏移量。(-21600000)
- 获取此偏移量的所有可用时区id。(都有相同的偏移)
- 选择第一个时区id。(将有相同的偏移量)
- 设置为时区
- 使用Java的简单日期格式将日期转换为字符串格式。
我看到现在呈现的时间是"06/01/2015::01:00:00 AM"
我的问题:
-
由于在创建和转换期间时区偏移量相同,我希望显示相同的时间。但我看到的是不同的。为什么会这样呢?
-
想象重新转换发生在服务器端,创建发生在客户端。我需要渲染回相同的日期和时间给客户端。我该怎么做呢?
请帮忙!如有任何帮助,不胜感激。
编辑:以下是代码。请注意,我已将当前时区设置为中美洲。
public class TimeTest {
public static void main (String args[]) {
SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy::hh:mm:ss a");
String dateInString = "01/06/2015::12:00:00 AM";
try {
Date date = formatter.parse(dateInString);
System.out.println("Before conversion --> " + formatter.format(date));
System.out.println("After conversion --> " + convertDateValueIntoString(date));
} catch (ParseException e) {
e.printStackTrace();
}
}
private static String convertDateValueIntoString(Date dateValue){
SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy::hh:mm:ss a");
String date;
int offset = TimeZone.getDefault().getRawOffset();
if (offset == 0) {
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
date = dateFormat.format(dateValue);
} else {
String TZ[] = TimeZone.getAvailableIDs(offset);
String timeZone = TZ[0];
if (timeZone == null) {
date = dateFormat.format(dateValue);
} else {
TimeZone tz = TimeZone.getTimeZone(timeZone);
dateFormat.setTimeZone(tz);
date = dateFormat.format(dateValue);
}
}
return date;
}
}
- 为什么时代不同:
差异似乎在于对夏令时的处理。通过将我的机器设置为不同的时区并打印TimeZone toString(),我最终得到:
Initial: sun.util.calendar.ZoneInfo[id="America/Tegucigalpa",offset=-21600000,dstSavings=0,useDaylight=false,transitions=9,lastRule=null]
Result: sun.util.calendar.ZoneInfo[id="America/Bahia_Banderas",offset=-21600000,dstSavings=3600000,useDaylight=true,...
请注意,这两个时区具有相同的偏移量,但一个使用夏令时,另一个不使用。偏移量是所有你的代码寻找一个合适的时区,但日期格式也使用夏令时偏移量。
- 我如何处理这个:
我所参与的每一个使用时间的项目都使用UTC(或类似的概念)来表示时间。我会让您的客户端在输入时将时间转换为UTC(在将其发送到服务器之前),让所有服务器存储使用UTC,然后当时间返回到客户端时,将客户端格式设置为默认时区,仅用于输出给用户。
这样,所有的内部时间都是一致的,所有显示的时间都是针对客户端的单个实例进行本地化的,所以美国/特古西加尔巴的用户可能会得到12:00的时间,但美国/巴希亚班德拉斯的用户会看到1:00。
1337joe的答案是正确的。我要加上一些想法。
这个问题引起了很多困惑。
时区=偏移量+规则/异常/调整
首先,时区不仅仅是UTC的偏移量。时区是一个偏移量加上一组关于夏令时和其他异常的过去、现在和未来的规则。调整。
因此,只要可能,使用一个命名的时区,而不是仅仅使用偏移量。当然,不要将仅偏移量的使用与时区的使用混合,并期望得到合理的结果。这似乎是这个问题的核心问题。
因此,深入挖掘以发现设计现有存储数据的程序员的原始意图。我怀疑他们确实有一个特定的时区,而不仅仅是一个偏移。
使用合适的时区名称
没有"中美洲"这样的时区。
正如1337Joe所指出的,中美洲各地的偏移量和时区各不相同。例如,America/Managua
比UTC晚6小时,而America/Panama
比UTC晚5小时。
UTC
。
指定您期望/期望的时区
当[a]您知道传入的数据表示特定的时区或偏移量(尽管是隐式的),并且[b]您希望应用某个时区时,请不要调用默认时区。那是自找麻烦。默认时区可能因主机操作系统设置而异。管理员可以随时更改主机操作系统设置。第三,JVM当前的默认时区可以在运行期间的任何时刻通过调用TimeZone.setDefault()
由同一JVM中任何应用程序的任何线程中的任何代码更改。
因此,不要依赖于默认时区,而要指定所需的时区。
使用UTC作为逻辑&存储h1> 如1337joe所说,你的业务逻辑、数据存储、数据通信和数据库都应该使用UTC(几乎总是如此)。仅在用户/消费者期望时应用对本地时区的调整。
在注释中,作者说他们的项目已经负担了现有的存储数据,隐式地表示某个时区或偏移量。
<标题> java.util。日期toString
标题> java.util.Date上的toString
方法自动应用JVM当前的默认时区。这使得使用时区调整变得棘手。避免使用java.util.Date/。日历,java.text.SimpleDateFormat类。
使用更好的日期时间库
使用新的java。Java 8和更高版本中的time包(教程),或者Joda-Time库(启发了Java .time)。
Joda-Time
下面是Joda-Time中的一些示例代码。
根据作者的注释,输入的字符串隐式地表示某个已知时区的日期时间值。没有说明该时区,因此我将随意使用巴拿马时区。在第一部分中,我们解析字符串,同时指定解析期间使用的时区,并将其分配给结果对象。
DateTimeZone zonePanama = DateTimeZone.forID( "America/Panama" );
DateTimeFormatter formatter = DateTimeFormat.forPattern( "dd/MM/yyyy::hh:mm:ss a" );
String input = "06/01/2015::12:00:00 AM";
DateTime dateTimePanama = formatter.withZone( zonePanama ).parseDateTime( input );
System.out.println( "Input as string: " + input + " becomes object: " + dateTimePanama + " with time zone: " + dateTimePanama.getZone() );
现在让我们调整到UTC。这是一个示范。在实际代码中,你通常会使用这个UTC值做任何进一步的工作。
DateTime dateTimeUtc = dateTimePanama.withZone( DateTimeZone.UTC );
System.out.println( "dateTimeUtc: " + dateTimeUtc );
对于输出,我们的用户/消费者希望使用与输入相同的巴拿马时区和格式的String表示。
String output = formatter.print( dateTimeUtc.withZone( zonePanama ) );
System.out.println( "Output in special format: " + output );
运行时。
Input as string: 06/01/2015::12:00:00 AM becomes object: 2015-01-06T00:00:00.000-05:00 with time zone: America/Panama
dateTimeUtc: 2015-01-06T05:00:00.000Z
Output in special format: 06/01/2015::12:00:00 AM
对于问题#1:不同时区的时区偏移量可能相同,但可以使用或不使用夏令时,这会导致差异。
对于问题#2:
对于将来,您只能在使用UTC时对时间安全。(如果你的时间数据是"最近的",你可以解决这个问题——见下文)
对于过去,您不能可靠地提取正确的时间。
一般转换建议:
我从事一个在JDBC驱动程序中处理时区和DST的项目。存储时间值和正确读取时间值存在问题。我非常努力地想要得到一个正确的转换,这样我们就可以省去切换到UTC的大量工作。没有UTC就没有正确的转换。(/real hard/:想想低俗小说里朱尔斯说的:"我真的很努力地想成为牧羊人。": -)
问题#2/未来:
如果您的客户端无法发送UTC时间(可能是因为它是第三方系统):
当您的服务器从客户端接收到时间数据(非UTC)时,您知道它在几分钟内(可能更长)是当前的,您可以尝试使用您的UTC时间并将其与客户端的时间相匹配。假设你的客户端发送了"2015-06-01 15:45",而你知道现在是"2015-06-01 18:51 UTC",那么你可以将客户端的时间解释为"2015-06-01 18:45 UTC"。如果客户端发送的时间数据可能超过一个小时,在某些情况下这将失败。
或者换句话说:假设您的客户端记录温度值。如果客户端发送的数据不超过几分钟,则可以将其与UTC时间匹配。如果你的客户记录了一天的温度,并在一天结束时发送给你,你无法正确匹配时间。
为什么不能进行完全(!)正确的转换?
假设夏时制发生变化的夜晚,使时钟从03:00变回02:00。开关前有一次02:30,开关后有一次02:30。第一个02:30比第二个02:30有另一个UTC时间。所以使用UTC是没问题的。但只有在"client local" 02:30的情况下,你永远无法确定。
回到客户端数据年龄:如果您的客户端发送的数据不超过几分钟的02:30,然后又发送了第二个02:30,您可以在服务器上区分这一点。如果在04:00你得到了02:30的两条记录,你就不能再恢复UTC了。
问题#2/过去:
你能在数据库中添加一个标志,这样作为UTC传输的新时间被标记为"可靠",而旧值则不是?
输出和源:
在TZ为"Europe/Berlin"的系统上运行修改后的源代码的输出。注意,这里使用了夏令时,但是第一个获取的TZ ("Algiers")没有使用夏令时。
formatter's TZ is sun.util.calendar.ZoneInfo[id="Europe/Berlin",offset=3600000,dstSavings=3600000,useDaylight=true,transitions=143,lastRule=java.util.SimpleTimeZone[id=Europe/Berlin,offset=3600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]]
internal date value = 1433109600000 as UTC = 31/05/2015::10:00:00 PM
Before conversion --> 01/06/2015::12:00:00 AM
Conversion: offset != 0, using TZ sun.util.calendar.ZoneInfo[id="Africa/Algiers",offset=3600000,dstSavings=0,useDaylight=false,transitions=35,lastRule=null]
After conversion --> 31/05/2015::11:00:00 PM
Setting UTC...
formatter's TZ is sun.util.calendar.ZoneInfo[id="UTC",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null]
internal date value = 1433116800000 as UTC = 01/06/2015::12:00:00 AM
Before conversion --> 01/06/2015::12:00:00 AM
Conversion: offset != 0, using TZ sun.util.calendar.ZoneInfo[id="Africa/Algiers",offset=3600000,dstSavings=0,useDaylight=false,transitions=35,lastRule=null]
After conversion --> 01/06/2015::01:00:00 AM
源代码:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
public class TimeTest {
static TimeZone utc = TimeZone.getTimeZone("UTC");
public static void main (String args[]) {
SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy::hh:mm:ss a");
String dateInString = "01/06/2015::12:00:00 AM";
SimpleDateFormat utcformatter = new SimpleDateFormat("dd/MM/yyyy::hh:mm:ss a");
utcformatter.setTimeZone(utc);
try {
Date date = formatter.parse(dateInString);
System.out.println("formatter's TZ is " + formatter.getTimeZone());
System.out.println("internal date value = " + date.getTime() + " as UTC = " + utcformatter.format(date));
System.out.println("Before conversion --> " + formatter.format(date));
System.out.println("After conversion --> " + convertDateValueIntoString(date));
System.out.println("nSetting UTC...n");
formatter.setTimeZone(utc);
date = formatter.parse(dateInString);
System.out.println("formatter's TZ is " + formatter.getTimeZone());
System.out.println("internal date value = " + date.getTime() + " as UTC = " + utcformatter.format(date));
System.out.println("Before conversion --> " + formatter.format(date));
System.out.println("After conversion --> " + convertDateValueIntoString(date));
} catch (ParseException e) {
e.printStackTrace();
}
}
private static String convertDateValueIntoString(Date dateValue){
SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy::hh:mm:ss a");
String date;
int offset = TimeZone.getDefault().getRawOffset();
if (offset == 0) {
System.out.println("Conversion: offset == 0 -- setting UTC");
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
date = dateFormat.format(dateValue);
} else {
String TZ[] = TimeZone.getAvailableIDs(offset);
String timeZone = TZ[0];
if (timeZone == null) {
System.out.println("Conversion: offset != 0, did not find TZ, tz of dateFormat is " + dateFormat.getTimeZone());
date = dateFormat.format(dateValue);
} else {
TimeZone tz = TimeZone.getTimeZone(timeZone);
System.out.println("Conversion: offset != 0, using TZ " + tz);
dateFormat.setTimeZone(tz);
date = dateFormat.format(dateValue);
}
}
return date;
}
}