왜이 두 번 빼기 (1927 년)가 이상한 결과를 낳습니까?


6824

다음 프로그램을 실행하면 시간을 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(나는 시간에 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

23
로캘 문제 일 수 있습니다.
Thorbjørn Ravn Andersen

72
실제 대답은 유닉스 신기원처럼 로깅을 위해 신기원 이후로 항상 초를 사용하는 것입니다. 유독 신기원 이전에 스탬프를 허용하려면 서명 된 64 비트 정수 표현을 사용하십시오. 모든 실제 시간 시스템에는 윤초 또는 일광 절약 시간과 같은 비선형 비단 조적 동작이 있습니다.
Phil H

22
롤 그들은 2011 년에 jdk6 용으로 수정했습니다. 그 후 2 년 후에 jdk7에서도 수정되어야한다는 것을 발견했습니다. 물론 7u25에서 수정되었습니다. 물론 릴리스 노트에서 힌트를 찾지 못했습니다. 때때로 나는 오라클이 얼마나 많은 버그를 수정했는지 궁금해하고 PR 이유 때문에 아무도 그것에 대해 알려주지 않습니다.
user1050755

8
이런 종류의 것들에 대한 좋은 비디오 : youtube.com/watch?v=-5wpm-gesOY
Thorbjørn Ravn Andersen

4
@PhilH 좋은 점은 여전히 ​​윤초입니다. 그래서조차도 작동하지 않습니다.
12431234123412341234123

답변:


10870

12 월 31 일 상하이에서 시간대가 변경되었습니다.

1927 년 상하이에 대한 자세한 내용은 이 페이지 를 참조하십시오 . 기본적으로 1927 년 말 자정에 시계는 5 분 52 초로 되돌아갔습니다. 따라서 "1927-12-31 23:54:08"은 실제로 두 번 발생했으며 Java가 현지 날짜 / 시간에 대해 나중에 가능한 순간 으로 구문 분석하는 것처럼 보이 므로 차이가 있습니다.

종종 이상하고 멋진 시간대의 세계에서 또 다른 에피소드.

편집 : 보도 중지! 역사 변경 ...

TZDB 버전 2013a로 다시 빌드하면 원래 질문은 더 이상 똑같은 동작을 나타내지 않습니다 . 2013a에서 결과는 358 초이며 전환 시간은 23:54:08 대신 23:54:03입니다.

단위 테스트 형태로 Noda Time에서 이와 같은 질문을 수집하기 때문에이 사실을 알았습니다 ... 테스트는 이제 변경되었지만 표시됩니다. 이력 데이터조차도 안전하지 않습니다.

편집 : 역사가 다시 변경되었습니다 ...

TZDB 2014 년, 변화의 시간이 1900년 12월 31일로 이동하고 있으며, (사이의 시간, 그래서 지금은 단순한 343초 변화의 tt+13백44초 당신이 무슨 뜻인지 볼 경우).

편집 : 1900에서 전환에 관한 질문에 대답하기 위해 ... Java 시간대 구현은 모든 시간대를 단순히 1900 UTC가 시작되기 전에 모든 표준 시간대로 처리하는 것처럼 보입니다 .

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

위의 코드는 Windows 컴퓨터에서 출력을 생성하지 않습니다. 따라서 1900 년 시작시 표준 시간대 이외의 오프셋이있는 시간대는 전환으로 간주합니다. TZDB 자체는 그보다 이전에 돌아가는 일부 데이터를 가지고 있으며 "고정 된"표준 시간 ( getRawOffset유효한 개념이라고 가정) 에 대한 아이디어에 의존하지 않으므로 다른 라이브러리는이 인공적인 전환을 도입 할 필요가 없습니다.


25
@Jon : 호기심 때문에 왜 "이상한"간격으로 시계를 다시 설정 했습니까? 한 시간 정도는 논리적으로 보였지만 5:52 분은 어떻게 되었습니까?
Johannes Rudolph

63
@Johannes :보다 일반적인 표준 시간대로 만들기 위해 결과 오프셋은 UTC + 8입니다. 파리는 1911 년에 같은 종류의 작업을 수행했습니다. 예 : timeanddate.com/worldclock/clockchange.html?n=195&year=1911
Jon Skeet

34
@Jon Java / .NET이 1752 년 9 월에 대처하는지 알고 있습니까? 나는 항상 유닉스 시스템에서 사람들에게 cal 9 1752를 보여주는 것을 좋아했었다
Mr Moose

30
그렇다면 왜 도대체 상해가 처음 5 분 동안 상해를 입었을까요?
Igby Largeman

25
@Charles : 당시 많은 곳에서 기존의 오프셋이 줄었습니다. 일부 국가에서는 각기 다른 도시마다 가능한 지리적으로 정확한 거리를두고 있습니다.
Jon Skeet

1602

현지 시간 불연속 이 발생했습니다 .

1928 년 1 월 1 일 일요일 오전 10시 00 분 00 초에 도달하려고했을 때, 시계는 0:05:52 시간에서 0:05:52 시간으로 되돌아갔습니다. 1927 년 12 월 31 일 23:54:08

이것은 특별히 이상하지는 않으며 정치적 또는 행정적 조치로 시간대가 바뀌거나 바뀌면서 한 번에 거의 모든 곳에서 발생했습니다.


661

이 이상의 도덕은 다음과 같습니다.

  • 가능하면 UTC로 날짜와 시간을 사용하십시오.
  • 날짜 또는 시간을 UTC로 표시 할 수없는 경우 항상 시간대를 표시하십시오.
  • UTC로 입력 날짜 / 시간을 요구할 수없는 경우 명시 적으로 표시된 시간대를 요구하십시오.

75
UTC 로의 변환 / 저장은 실제로 UTC 로의 변환이 중단 될 때 설명 된 문제에 도움이되지 않습니다.
unpythonic

23
@Mark Mann : 프로그램이 UI를 사용하여 현지 시간대로 변환하거나 내부 시간대에서 UTC를 사용하는 경우 이러한 불연속성에 신경 쓰지 않습니다 .
Raedwald

66
@Raedwald : 물론입니다-1927-12-31 23:54:08의 UTC 시간은 얼마입니까? (현재 UTC가 1927 년에도 존재하지 않았 음을 무시 함). 에서 어떤 지점이 시간과 날짜가 시스템에오고있다, 당신은 그것으로 무엇을할지 결정해야합니다. 사용자에게 UTC로 시간을 입력해야한다고 말하면 문제가 사용자에게 이동되기 때문에 문제가 해결되지 않습니다.
Nick Bastin 2012

72
나는 거의 1 년 동안 큰 앱의 날짜 / 시간 리팩토링 작업을하고있는이 스레드의 활동량을 입증합니다. 캘린더와 같은 작업을 수행하는 경우 UTC를 "간단하게"저장할 수 없습니다. UTC의 시간대 정의는 시간이 지남에 따라 변경되기 때문입니다. 검색 및 정렬을 위해 "사용자 의도 시간"(사용자의 현지 시간 및 시간대)과 UTC를 저장하며 IANA 데이터베이스가 업데이트 될 때마다 모든 UTC 시간을 다시 계산합니다.
taiganaut

366

시간이 증가하면 UTC로 다시 변환 한 다음 더하거나 빼야합니다. 현지 시간 만 표시하십시오.

이렇게하면 몇 시간 또는 몇 분이 두 번 발생하는 기간을 걸을 수 있습니다.

UTC로 변환 한 경우 1 초마다 추가하고 표시 할 현지 시간으로 변환하십시오. LMT 오후 11:54:08 (오후 LMT -11:59:59), LST 오후 11:54:08 ( CST -11:59:59) CST 를 거칩니다 .


309

각 날짜를 변환하는 대신 다음 코드를 사용할 수 있습니다.

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

그리고 결과는 다음과 같습니다.

1

72
나는 그렇지 않다. 1우리는 다른 로케일을 가지고 있기 때문에 시스템에서 내 코드를 시도 할 수 있습니다 .
Freewind

14
파서 입력에 로캘을 지정하지 않았기 때문에 사실입니다. 이는 코딩 스타일이 좋지 않고 Java의 고유 한 지역화 인 Java의 디자인 결함입니다. 개인적으로, 나는 그것을 피하기 위해 Java를 사용하는 모든 곳에 "TZ = UTC LC_ALL = C"를 넣었다. 또한 사용자와 직접 상호 작용하고 명시 적으로 원하지 않는 한 현지화 된 모든 버전의 구현을 피해야합니다. 현지화를 포함하여 계산하지 말고 반드시 필요한 경우가 아니면 항상 Locale.ROOT 및 UTC 시간대를 사용하십시오.
user1050755

226

죄송 합니다만 시간 불연속이 조금 바뀌 었습니다.

JDK 6 2 년 전, JDK 7 에서 최근 업데이트 25 .

배우는 교훈 : 디스플레이를 제외하고 모든 비용으로 비 UTC 시간을 피하십시오.


27
이것은 올바르지 않습니다. 불연속성은 버그가 아닙니다. 최신 버전의 TZDB는 데이터가 약간 다릅니다. 예를 들어 Java 8이 설치된 시스템에서 코드를 약간 변경하여 "1927-12-31 23:54:02"및 "1927-12-31 23:54:03"을 사용하면 여전히 불연속성 – 지금은 353이 아닌 358 초입니다. 더 최신 버전의 TZDB에는 또 다른 차이점이 있습니다. 자세한 내용은 내 답변을 참조하십시오. 여기에는 실제 버그가 없으며 모호한 날짜 / 시간 텍스트 값을 구문 분석하는 방법에 대한 디자인 결정 만 있습니다.
Jon Skeet

6
실제 문제는 프로그래머가 현지 시간과 표준시 (양 방향) 간의 변환이 100 % 신뢰할 수없고 신뢰할 수 없다는 것을 이해하지 못한다는 것입니다. 오래된 타임 스탬프의 경우 현지 시간에 대한 데이터가 가장 불안정합니다. 미래의 타임 스탬프의 경우 정치적 행동으로 인해 지정된 현지 시간이 표시되는 세계시를 변경할 수 있습니다. 현재 및 최근 과거 타임 스탬프의 경우 tz 데이터베이스를 업데이트하고 변경 사항을 롤아웃하는 프로세스가 법의 구현 일정보다 느릴 수 있다는 문제가있을 수 있습니다.
plugwash

200

다른 사람들이 설명했듯이 시간 불연속이 있습니다. 1927-12-31 23:54:08at Asia/Shanghai에 대해 두 개의 가능한 시간대 오프셋이 있지만에 대한 하나의 오프셋 만 1927-12-31 23:54:07있습니다. 따라서 어떤 오프셋이 사용되는지에 따라 1 초 차이 또는 5 분 53 초 차이가 있습니다.

우리가 익숙한 일반적인 1 시간 일광 절약 시간 (서머 타임) 대신이 약간의 오프셋 이동은 문제를 조금 가려줍니다.

시간대 데이터베이스의 2013a 업데이트는 몇 초 전에이 불연속성을 옮겼지만 그 효과는 여전히 관찰 가능합니다.

java.timeJava 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());

그러면 durationAtEarlierOffset1 durationAtLaterOffset초가되고 5 분 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()메소드를 사용하여 전환이 해당 날짜 / 시간에 대해 유효한 오프셋이 둘 이상인 경우 겹침인지 또는 해당 날짜 / 시간이 해당 영역 ID에 대해 유효하지 않은 간격인지 확인할 수 있습니다 zot4.

Java 8이 널리 사용 가능해 지거나 JSR 310 백 포트를 채택한 Java 7을 사용하는 사람들이 이런 종류의 문제를 처리하는 데 도움이되기를 바랍니다.


1
안녕하세요 Daniel, 귀하의 코드를 실행했지만 예상대로 출력을 제공하지 않습니다. durationAtEarlierOffset 및 durationAtLaterOffset과 같이 둘 다 1 초이며 zot3 및 zot4는 모두 널입니다. 방금이 코드를 복사하여 내 컴퓨터에서 실행했습니다. 여기서해야 할 일이 있습니까? 코드를보고 싶으면 알려주십시오. 다음은 코드 tutorialspoint.com/… 입니다. 여기서 무슨 일이 있는지 알려주세요.
vineeshchauhan 2016 년

2
@vineeshchauhan tzdata에서 변경 되었기 때문에 Java 버전에 따라 달라지며 JDK 버전마다 다른 버전의 tzdata가 번들로 제공됩니다. 내가 설치 한 Java에서 시간은 1900-12-31 23:54:161900-12-31 23:54:17이지만 공유 한 사이트에서는 작동하지 않으므로 I과 다른 Java 버전을 사용하고 있습니다.
Daniel C. Sobral

167

Java에서 널리 사용되는 암시 적 현지화는 단일 설계상의 가장 큰 결함입니다. 사용자 인터페이스를위한 것일 수도 있지만, 솔직히 말해서 프로그래머가 타겟 대상이 아니기 때문에 기본적으로 지역화를 무시할 수있는 일부 IDE를 제외하고 오늘날 사용자 인터페이스에 Java를 실제로 사용하는 사용자는 있습니다. 다음을 통해 문제를 해결할 수 있습니다 (특히 Linux 서버에서).

  • 수출 LC_ALL = C TZ = UTC
  • 시스템 시계를 UTC로 설정
  • 반드시 필요한 경우가 아니면 현지화 된 구현을 사용하지 마십시오 (예 : 디스플레이 전용).

받는 자바 커뮤니티 프로세스의 회원 나는 추천 :

  • 현지화 된 메소드를 기본값으로 설정하지 말고 사용자에게 명시 적으로 현지화를 요청해야합니다.
  • UTF-8 / UTC를 FIXED 기본값으로 대신 사용하십시오. 이것이 오늘날의 기본값이기 때문입니다. 이와 같은 스레드를 생성하려는 경우를 제외하고는 다른 작업을 수행 할 이유가 없습니다.

전역 정적 변수가 anti-OO 패턴이 아닙니까? 다른 기본 환경 변수에 의해 제공되는 광범위한 기본값은 없습니다.


21

다른 사람들이 말했듯이 1927 년 상하이에서 시간이 바뀌 었습니다.

이 때 23:54:07상하이, 로컬 표준 시간에, 그러나 오분 52 초 후에, 그것의 다음날로 전환 00:00:00한 다음 로컬 표준 시간으로 다시 변경 23:54:08. 그래서 두 시간의 차이가 예상했던 것처럼 1 초가 아닌 343 초입니다.

미국과 같은 다른 곳에서도 시간이 엉망이 될 수 있습니다. 미국에는 일광 절약 시간 제가 있습니다. 일광 절약 시간 제가 시작되면 시간이 1 시간 앞으로 이동합니다. 그러나 잠시 후 일광 절약 시간이 종료되고 표준 시간대로 1 시간 뒤로 이동합니다. 따라서 미국에서 시간을 비교할 때 차이는 36001 초가 아닌 약 초입니다.

그러나이 두 시간 변화에는 다른 점이 있습니다. 후자는 지속적으로 변화하고 전자는 단지 변화 일뿐입니다. 같은 양만큼 변경되거나 다시 바뀌지 않았습니다.

UTC가 아닌 시간을 디스플레이와 같이 사용해야하는 경우가 아니면 시간이 변경되지 않는 UTC를 사용하는 것이 좋습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.