有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java为什么与UTC偏移相同的时区显示不同的时间?

我今天碰到了这个问题。我已将时钟设置为UTC-6.00(中美洲)时区。我正在将日期“06/01/2015::12:00:00 am”(“MM/dd/yyyy::hh:MM:ss a”格式)转换为java日期对象。然后我将日期对象重新转换为字符串。不过,我做这件事的方式有点扭曲。我在下面列出了重新转换的步骤-

  1. 计算当前时区的UTC偏移量。(-2160000)
  2. 获取此偏移量的所有可用时区ID。(都有相同的偏移量)
  3. 选择第一个时区id。(将具有相同的偏移)
  4. 将此设置为时区
  5. 使用Java的简单日期格式将日期转换为字符串格式

我看到现在渲染的时间是“06/01/2015::01:00:00 AM”

我的问题是:

  1. 由于时区偏移在创建和转换期间是相同的,因此我希望显示相同的时间。但我看到的是不同的。为什么会这样

  2. 想象一下在服务器中发生的重新转换和在客户机中发生的创建。我需要向客户返回相同的日期和时间。我该怎么做

请帮忙!非常感谢您的帮助

编辑:下面是代码。请注意,我已将当前时区设置为中美洲

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;
}
}

共 (3) 个答案

  1. # 1 楼答案

    1. 为什么时代不同:

    区别似乎在于夏令时的处理。将我的机器设置为不同的时区,然后将时区打印到字符串()我最终得到了:

    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,...
    

    请注意,这两个时区具有相同的偏移量,但其中一个使用夏令时,另一个不使用。偏移量是代码寻找合适时区的全部内容,但日期格式也使用夏令时偏移量

    1. 我该如何处理这个问题:

    我参与的每个项目都使用了UTC(或类似的概念)来表示时间。我会让您的客户机在输入时将时间转换为UTC(在发送到服务器之前),让所有服务器存储使用UTC,然后当时间返回到客户机时,将客户机格式转换为默认时区,仅用于向用户输出

    这样一来,所有内部时间都是一致的,并且所有显示的时间都是针对客户端的单个实例进行本地化的,因此美国/特古西加尔巴的用户可能会得到12:00的时间,但美国/巴伊亚-班德拉斯的用户会看到1:00。对于用户来说,这两种时间都是正确的

  2. # 2 楼答案

    {a1}是正确的。我会补充一些想法

    这个问题有很多困惑

    时区=偏移+规则/异常/调整

    与UTC相比,时区更像是一个偏移量。时区是关于夏令时和其他异常的过去、现在和未来规则的偏移量;调整

    因此,只要有可能,就使用named time zone而不仅仅是偏移量。当然,不要只使用时区来混合使用偏移量,并期望得到合理的结果。这似乎是这个问题的核心问题

    因此,深入挖掘,以发现设计现有存储数据的程序员的初衷。我怀疑他们确实有一个特定的时区,而不仅仅是一个抵消

    使用正确的时区名称

    没有“中美洲”这样的时区

    正如1337Joe指出的,中美洲各地的偏移量和时区各不相同。例如,America/Managua比UTC晚六个小时,而America/Panama比UTC晚五个小时

    顺便说一句,避免使用3-4个字母的时区代码,比如“EST”,因为它们既不标准,也不独特。当然,唯一的例外是UTC

    指定预期/期望的时区

    当[a]你知道你的输入数据代表一个特定的时区或偏移量,尽管是隐式的,并且[b]你希望应用某个时区时,不要调用默认时区。这是自找麻烦。默认时区可能因主机操作系统设置的不同而有所不同。管理员可以随时更改这两个主机操作系统的设置。第三,JVM的当前默认时区可以在运行时的任何时刻通过同一JVM中任何应用程序的任何线程中的任何代码调用^{}来更改

    因此,与其依赖默认时区,不如指定所需的时区

    使用UTC进行逻辑和;储藏室

    正如1337joe所说,您的业务逻辑、数据存储、数据通信和数据库都应该使用UTC(几乎总是)。仅在用户/消费者预期时对本地时区进行调整

    在评论中,作者说他们的项目已经背负了现有的存储数据,这些数据隐含地代表了某个时区或偏移量

    爪哇。util。日期toString

    java上的toString方法。util。日期自动应用JVM的当前默认时区。这使得时区调整变得棘手。避免使用java的众多原因之一。util。日期/。日历和;JAVA文本SimpleDataFormat类

    使用更好的日期时间库

    使用Java8及更高版本中的新java.time packageTutorial)或Joda-Time库(它启发了Java.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 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
    
  3. # 3 楼答案

    对于问题#1:不同时区的时区偏移量可能相同,但可能使用或不使用DST,这会导致差异

    关于问题#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时间匹配。如果你的客户记录了一天的温度,并在一天结束时发送给你,你就无法正确匹配时间

    为什么你不能完全(!)正确的转换

    假设DST变化的夜晚,时钟从03:00变回02:00。切换前有一次02:30,切换后有一次02:30。第一个02:30比第二个02:30有另一个UTC时间。所以有了UTC,你就没事了。但只有在02:30的“本地客户”节目中,你永远不会确定

    回到客户机数据时代:如果您的客户机在02:30发送的数据不超过几分钟,然后在第二个02:30发送另一个数据,您可以在服务器上区分这一点。如果在04:00获得了02:30的两条记录,则无法再恢复UTC

    问题#2/过去:

    能否在数据库中添加一个标志,以便将传输为UTC的新时间标记为“可靠”,而不标记旧值


    输出和源:

    在我的系统上运行修改后的源代码的输出,其TZ为“Europe/Berlin”。请注意,这台机器正在使用DST,但第一台取回的TZ(“阿尔及尔”)没有使用DST

    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;
        }
    }