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



我今天碰到了这个问题。我把时钟调到UTC-6.00(中美洲)时区。我正在将日期"06/01/2015::12:00:00 am " ("MM/dd/yyyy::hh: MM:ss a"格式)转换为java Date对象。然后我将date对象重新转换为String。不过,我做这件事的方式有一个小小的转折。我在下面列出了重新转换的步骤-

  1. 从当前时区计算UTC偏移量。(-21600000)
  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;
}
}
  1. 为什么时代不同:

差异似乎在于对夏令时的处理。通过将我的机器设置为不同的时区并打印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小时。

    顺便说一下,避免使用3-4个字母的时区代码,如"EST",因为它们既不是标准化的,也不是唯一的。唯一的例外当然是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;
        }
    }
    

    相关内容

    • 没有找到相关文章

    最新更新