java - لماذا يطرح هاتين المراتين (في عام 1927) يعطي نتيجة غريبة؟


إذا قمت بتشغيل البرنامج التالي، الذي يوزع اثنين من سلاسل التاريخ الرجوع مرات 1 ثانية منفصلة ويقارن بينهما:

public static void main(String[] args) throws ParseException {
    SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
    String str3 = "1927-12-31 23:54:07";  
    String str4 = "1927-12-31 23:54:08";  
    Date sDt3 = sf.parse(str3);  
    Date sDt4 = sf.parse(str4);  
    long ld3 = sDt3.getTime() /1000;  
    long ld4 = sDt4.getTime() /1000;
    System.out.println(ld4-ld3);
}

الإخراج هو:

353

لماذا هو ld4-ld3 لا 1 (كما أتوقع من الفرق ثانية واحدة في الأوقات)، ولكن 353 ؟

إذا قمت بتغيير التواريخ إلى 1 ثانية في وقت لاحق:

String str3 = "1927-12-31 23:54:08";  
String str4 = "1927-12-31 23:54:09";  

ثم ld4-ld3 سيكون 1 .

إصدار جافا:

java version "1.6.0_22"
Java(TM) SE Runtime Environment (build 1.6.0_22-b04)
Dynamic Code Evolution Client VM (build 0.2-b02-internal, 19.0-b04-internal, mixed mode)

Timezone(`TimeZone.getDefault()`):

sun.util.calendar.ZoneInfo[id="Asia/Shanghai",
offset=28800000,dstSavings=0,
useDaylight=false,
transitions=19,
lastRule=null]

Locale(Locale.getDefault()): zh_CN


Answers



انها تغيير المنطقة الزمنية في 31 ديسمبر في شنغهاي.

انظر هذه الصفحة للحصول على تفاصيل عام 1927 في شنغهاي. أساسا في منتصف الليل في نهاية عام 1927، ساعات العودة 5 دقائق و 52 ثانية. لذلك "1927-12-31 23:54:08" حدث فعلا مرتين، ويبدو أن جافا هو تحليل ذلك في وقت لاحق ممكن لحظة لهذا التاريخ المحلي / الوقت - وبالتالي الفرق.

مجرد حلقة أخرى في العالم غريبة في كثير من الأحيان ورائعة من المناطق الزمنية.

إديت: إيقاف الصحافة! تغييرات السجل ...

فإن السؤال الأصلي لم تعد تظهر تماما نفس السلوك، إذا أعيد بناؤها مع الإصدار 2013a من تزدب . في عام 2013 أ، ستكون النتيجة 358 ثانية، مع وقت انتقال 23:54:03 بدلا من 23:54:08.

لقد لاحظت هذا فقط لأنني أجمع أسئلة من هذا القبيل في نودا تايم، في شكل اختبارات وحدة ... وقد تم تغيير الاختبار الآن، ولكن فقط يذهب لإظهار - حتى البيانات التاريخية حتى آمنة.

إديت: تغير التاريخ مرة أخرى ...

في تسدب 2014f، وقت التغيير قد انتقل إلى 1900-12-31، وانها الآن مجرد 343 التغيير الثاني (وبالتالي فإن الوقت بين t و t+1 هو 344 ثانية، إذا كنت ترى ما أعنيه).

إديت: للإجابة على سؤال حول الانتقال في عام 1900 ... يبدو أن تنفيذ المنطقة الزمنية جافا يعامل جميع المناطق الزمنية ببساطة كونها في وقت قياسي لأي لحظة قبل بداية 1900 أوتك:

import java.util.TimeZone;

public class Test {
    public static void main(String[] args) throws Exception {
        long startOf1900Utc = -2208988800000L;
        for (String id : TimeZone.getAvailableIDs()) {
            TimeZone zone = TimeZone.getTimeZone(id);
            if (zone.getRawOffset() != zone.getOffset(startOf1900Utc - 1)) {
                System.out.println(id);
            }
        }
    }
}

لا ينتج عن التعليمات البرمجية أعلاه أي مخرجات على جهاز ويندوز الخاص بي. لذا فإن أي منطقة زمنية لديها أي تعويض بخلاف معيارها القياسي في بداية عام 1900 سيعتبر ذلك انتقالا. تسدب نفسها لديها بعض البيانات تعود في وقت سابق من ذلك، ولا تعتمد على أي فكرة عن "ثابت" الوقت القياسي (وهو ما getRawOffset يفترض أن يكون مفهوما صالحا) حتى المكتبات الأخرى لا تحتاج إلى إدخال هذا التحول الاصطناعي.




لقد واجهت حالة توقف في الوقت المحلي :

عندما كان الوقت القياسي المحلي على وشك الوصول إلى الأحد، 1. يناير 1928، 00:00:00 تم تحويل الساعات إلى الوراء 0:05:52 ساعات إلى السبت، 31 ديسمبر 1927، 23:54:08 الوقت القياسي المحلي بدلا من ذلك

هذا ليس غريبا بشكل خاص، وقد حدث إلى حد كبير في كل مكان في وقت أو آخر كما تم تبديل المناطق الزمنية أو تغييرها بسبب الإجراءات السياسية أو الإدارية.




المعنوية من هذا الغرابة هي:

  • استخدام التواريخ والأوقات في أوتك حيثما كان ذلك ممكنا.
  • إذا لم تتمكن من عرض تاريخ أو وقت بالتوقيت العالمي المنسق، فأشر دائما إلى المنطقة الزمنية.
  • إذا لم تتمكن من طلب تاريخ / وقت الإدخال بالتوقيت العالمي المنسق، فتطلب منطقة زمنية صريحة.



عند زيادة الوقت يجب تحويل مرة أخرى إلى أوتك ثم إضافة أو طرح. استخدام الوقت المحلي فقط للعرض.

بهذه الطريقة سوف تكون قادرة على المشي من خلال أي فترات حيث ساعات أو دقائق يحدث مرتين.

إذا قمت بتحويل إلى أوتك، إضافة كل ثانية، وتحويل إلى التوقيت المحلي للعرض. سوف تذهب من خلال 11:54:08 مساء لمت - 11:59:59 مساء لمت ثم 11:54:08 مساء ست - 11:59:59 بيإم ست.




بدلا من تحويل كل تاريخ، يمكنك استخدام التعليمات البرمجية التالية

long i = (sDt4.getTime() - sDt3.getTime()) / 1000;
System.out.println(i);

ونرى النتيجة هي:

1



أنا آسف أن أقول ذلك، ولكن توقف الانقطاع الوقت قليلا في

جدك 6 قبل عامين، وفي جدك 7 فقط مؤخرا في التحديث 25 .

درس للتعلم: تجنب الأوقات خارج أوتك في جميع التكاليف، باستثناء، ربما، للعرض.




كما هو موضح من قبل الآخرين، هناك انقطاع الوقت هناك. هناك نوعان من 1927-12-31 23:54:08 التوقيت الممكنة 1927-12-31 23:54:08 في Asia/Shanghai ، ولكن فقط إزاحة واحدة ل 1927-12-31 23:54:07 . لذلك، اعتمادا على أي تعويض يستخدم، هناك إما فرق واحد أو فرق 5 دقائق و 53 ثانية.

هذا التحول الطفيف في الإزاحة، بدلا من المعتاد ساعة واحدة التوقيت الصيفي (وقت الصيف) نحن تستخدم ل، يحجب المشكلة قليلا.

لاحظ أن تحديث 2013a لقاعدة بيانات المنطقة قد نقل هذا الانقطاع قبل بضع ثوان، ولكن التأثير لا يزال ملحوظا.

حزمة java.time جديدة على جافا 8 السماح استخدام رؤية أكثر وضوحا، وتوفير أدوات للتعامل معها. معطى:

DateTimeFormatterBuilder dtfb = new DateTimeFormatterBuilder();
dtfb.append(DateTimeFormatter.ISO_LOCAL_DATE);
dtfb.appendLiteral(' ');
dtfb.append(DateTimeFormatter.ISO_LOCAL_TIME);
DateTimeFormatter dtf = dtfb.toFormatter();
ZoneId shanghai = ZoneId.of("Asia/Shanghai");

String str3 = "1927-12-31 23:54:07";  
String str4 = "1927-12-31 23:54:08";  

ZonedDateTime zdt3 = LocalDateTime.parse(str3, dtf).atZone(shanghai);
ZonedDateTime zdt4 = LocalDateTime.parse(str4, dtf).atZone(shanghai);

Duration durationAtEarlierOffset = Duration.between(zdt3.withEarlierOffsetAtOverlap(), zdt4.withEarlierOffsetAtOverlap());

Duration durationAtLaterOffset = Duration.between(zdt3.withLaterOffsetAtOverlap(), zdt4.withLaterOffsetAtOverlap());

ثم سيكون durationAtEarlierOffset ثانية واحدة، في حين أن durationAtLaterOffset ستكون خمس دقائق و 53 ثانية.

أيضا، هذين إزاحة هي نفسها:

// Both have offsets +08:05:52
ZoneOffset zo3Earlier = zdt3.withEarlierOffsetAtOverlap().getOffset();
ZoneOffset zo3Later = zdt3.withLaterOffsetAtOverlap().getOffset();

ولكن هذين مختلفين:

// +08:05:52
ZoneOffset zo4Earlier = zdt4.withEarlierOffsetAtOverlap().getOffset();

// +08:00
ZoneOffset zo4Later = zdt4.withLaterOffsetAtOverlap().getOffset();

يمكنك أن ترى نفس المشكلة مقارنة 1927-12-31 23:59:59 مع 1928-01-01 00:00:00 ، على الرغم من، في هذه الحالة، هو الإزاحة السابقة التي تنتج الاختلاف أطول، وأنه هو في وقت سابق له اثنين من تعويضات محتملة.

وهناك طريقة أخرى للتعامل مع ذلك وهي التحقق مما إذا كان هناك انتقال مستمر. يمكننا أن نفعل مثل هذا:

// Null
ZoneOffsetTransition zot3 = shanghai.getRules().getTransition(ld3.toLocalDateTime);

// An overlap transition
ZoneOffsetTransition zot4 = shanghai.getRules().getTransition(ld3.toLocalDateTime);

يمكنك التحقق مما إذا كانت عملية النقل تداخل - في هذه الحالة يكون هناك أكثر من إزاحة واحدة صالحة لهذا التاريخ / الوقت - أو فجوة - وفي هذه الحالة لا يكون هذا التاريخ / الوقت صالحا لذلك معرف المنطقة - باستخدام isOverlap() و isGap() على zot4 .

آمل أن يساعد هذا الناس التعامل مع هذا النوع من المسألة مرة واحدة جافا 8 يصبح متاحا على نطاق واسع، أو لأولئك الذين يستخدمون جافا 7 الذين يتبنون جسر 310 باكبورت.




إمهو الانتشار الواسع، الضمني في جافا هو أكبر عيب في تصميمه. قد يكون الغرض منه هو استخدام واجهات المستخدم، ولكن بصراحة، الذي يستخدم جافا بالفعل لواجهات المستخدم اليوم باستثناء بعض إدس حيث يمكنك تجاهل أساسا توطين لأن المبرمجين ليست بالضبط الجمهور المستهدف لذلك. يمكنك إصلاحه (خاصة على خوادم لينكس) بواسطة:

  • تصدير LC_ALL = C تس = أوتك
  • تعيين ساعة النظام الخاص بك إلى أوتك
  • لا تستخدم تطبيقات محلية إلا إذا كانت ضرورية للغاية (أي للعرض فقط)

إلى أعضاء عملية مجتمع جافا أوصي:

  • جعل الطرق المترجمة ليست الافتراضي، ولكن تتطلب من المستخدم طلب صراحة الترجمة.
  • استخدم أوتف-8 / أوتك كما الافتراضي الافتراضي بدلا من ذلك لأن هذا هو ببساطة الافتراضي اليوم. ليس هناك سبب للقيام بشيء آخر، إلا إذا كنت ترغب في إنتاج خيوط من هذا القبيل.

أعني، هيا، ليست متغيرات ثابتة عالمية نمط مكافحة و؟ لا شيء آخر هو تلك الافتراضات المنتشرة التي تعطيها بعض متغيرات البيئة البدائية .......