Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 495 – 지역 시간 모호성 해소

Author:
Alexander Belopolsky <alexander.belopolsky at gmail.com>, Tim Peters <tim.peters at gmail.com>
Discussions-To:
Datetime-SIG list
Status:
Final
Type:
Standards Track
Created:
02-Aug-2015
Python-Version:
3.6
Resolution:
Datetime-SIG message

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

초록

이 PEP는 datetime.timedatetime.datetime 클래스의 인스턴스에 새로운 fold 속성을 추가합니다. 이 속성은 지역 시간이 동일한 두 시점을 구별하는 데 사용할 수 있습니다. fold 속성에 허용되는 값은 0과 1이며, 0은 모호한 지역 시간에 대한 두 가지 가능한 해석 중 이른 시점에, 1은 늦은 시점에 해당합니다.

근거

세계 대부분의 지역에서는 지역 시계가 뒤로 조정되는 시기가 과거에도 있었고 앞으로도 있을 것입니다. [1] 이러한 시기에는 지역 시계가 같은 날에 동일한 시간을 두 번 표시하는 구간이 생깁니다. 이러한 상황에서는 지역 시계에 표시된 정보(또는 Python datetime 인스턴스에 저장된 정보)만으로는 특정 시점을 식별하기에 충분하지 않습니다. 제안된 해결책은 0과 1의 값을 취하는 속성을 datetime 인스턴스에 추가하여 두 개의 모호한 시간을 열거하는 것입니다.

큰 시계의 바늘을 멈추려고 애쓰는 강인한 남자의 만화입니다. 캡션에는 다음과 같이 적혀 있습니다: "시간을 멈출 수는 없습니다... 하지만 일광 절약 시간제가 끝나고 표준시가 시작되는 10월 28일 새벽 2시에 한 시간을 되돌릴 수는 있습니다."

용어

시계가 뒤로 조정되면 시간에 fold가 생성되었다고 합니다. [2] 시계가 앞으로 조정되면 gap이 생성됩니다. 폴드에 속하는 지역 시간을 ambiguous라고 합니다. 갭에 속하는 지역 시간을 missing이라고 합니다.

제안

“fold” 속성

datetime.timedatetime.datetime 클래스의 인스턴스에 fold라는 속성을 추가할 것을 제안합니다. 이 속성은 모호한 경우 시간순으로 두 번째 시점을 나타내는 인스턴스를 제외한 모든 인스턴스에서 값 0을 가져야 합니다. 해당 인스턴스에서는 값이 1이 됩니다. [3]

영향을 받는 API

속성

datetime.timedatetime.datetime클래스의 인스턴스에는 가능한 값이 0과 1인 새 속성 fold가 추가됩니다.

생성자

datetime.timedatetime.datetime클래스의 __new__메서드에는 기본값이 0인 fold라는 새로운 키워드 전용 인자가 추가됩니다. fold인자의 값은 반환되는 인스턴스의 fold속성 값을 초기화하는 데 사용됩니다.

메서드

datetime.timedatetime.datetime클래스의 replace()메서드에는 fold라는 새로운 키워드 전용 인자가 추가됩니다. 이는 다른 replace()인자와 유사하게 동작합니다. fold인자가 지정되고 값 0 또는 1이 주어지면, replace()가 반환하는 새 인스턴스의 fold속성이 해당 값으로 설정됩니다. CPython에서는 fold의 정수가 아닌 값이 TypeError를 발생시키지만, 다른 구현에서는 fold를 지정하지 않은 경우와 동일하게 동작하도록 값 None을 허용할 수 있습니다. [4] (이는 Python 구현마다 이 메서드의 다른 위치에서 None인자를 처리하는 기존 차이를 언급한 것입니다. fold=None을 향후 다르게 해석할 여지를 남기려는 의도는 아닙니다.) fold인자를 지정하지 않으면 fold속성의 원래 값이 결과에 복사됩니다.

C-API

PyDateTime_DateTimePyDateTime_Time객체에서 fold값을 추출하는 액세스 매크로가 정의됩니다.

int PyDateTime_DATE_GET_FOLD(PyDateTime_DateTime *o)

fold값을 C int로 반환합니다.

int PyDateTime_TIME_GET_FOLD(PyDateTime_Time *o)

fold값을 C int로 반환합니다.

생성된 인스턴스의 fold값을 지정할 수 있도록 추가 인자를 받는 새로운 생성자가 정의됩니다.

PyObject* PyDateTime_FromDateAndTimeAndFold(
    int year, int month, int day, int hour, int minute,
    int second, int usecond, int fold)

지정된 연도, 월, 일, 시, 분, 초, 마이크로초 및 fold를 사용하여 datetime.datetime객체를 반환합니다.

PyObject* PyTime_FromTimeAndFold(
    int hour, int minute, int second, int usecond, int fold)

지정된 시, 분, 초, 마이크로초 및 fold를 사용하여 datetime.time객체를 반환합니다.

영향을 받는 동작

몇 시입니까?

인자 없이 호출된 datetime.now()메서드는 시스템 로컬 시간 폴드에서 모호한 두 시각 중 두 번째 시각을 반환할 때 fold=1로 설정합니다. tzinfo인자와 함께 호출되면 fold의 값은 tzinfo.fromutc()구현에 의해 결정됩니다. datetime.timezone클래스의 인스턴스(표준 라이브러리의 고정 오프셋 tzinfo서브클래스, e.g. datetime.timezone.utc)가 tzinfo로 전달되면 반환되는 datetime 인스턴스의 fold는 항상 0입니다. datetime.utcnow()메서드는 영향을 받지 않습니다.

순진한 datetime에서 인식 datetime으로의 변환

순진한 datetime 인스턴스를 인식 datetime으로 변환하기 위한 새로운 기능이 제안됩니다.

이제 astimezone()메서드가 순진한 self에 대해서도 동작합니다. 이 경우 시스템 로컬 시간대가 가정되며, 모호한 경우에는 fold플래그를 사용하여 어느 로컬 시간대가 적용되는지 결정합니다.

예를 들어, US/Eastern 시간대로 설정된 시스템에서는:

>>> dt = datetime(2014, 11, 2, 1, 30)
>>> dt.astimezone().strftime('%D %T %Z%z')
'11/02/14 01:30:00 EDT-0400'
>>> dt.replace(fold=1).astimezone().strftime('%D %T %Z%z')
'11/02/14 01:30:00 EST-0500'

이는 datetime.now(tz)datetime.now().astimezone(tz)와 완전히 동등하다는 것을 의미합니다(tz가 PEP 이후의 tzinfo구현, 즉 fold를 올바르게 처리하고 설정하는 구현의 인스턴스라고 가정합니다).

EPOCH로부터의 POSIX 초에서 변환

datetime.datetimefromtimestamp()정적 메서드는 반환되는 객체의 fold속성을 적절하게 설정합니다.

예를 들어, US/Eastern 시간대로 설정된 시스템에서는:

>>> datetime.fromtimestamp(1414906200)
datetime.datetime(2014, 11, 2, 1, 30)
>>> datetime.fromtimestamp(1414906200 + 3600)
datetime.datetime(2014, 11, 2, 1, 30, fold=1)

EPOCH에서 POSIX 초로의 변환

datetime.datetimetimestamp() 메서드는 fold 속성의 값만 다른 datetime.datetime 인스턴스에 대해, 이러한 인스턴스가 모호한 시간 또는 누락된 시간을 나타내는 경우에만 서로 다른 값을 반환합니다.

datetime.datetime 인스턴스 dt가 모호한 시간을 나타낼 때, 다음 조건을 만족하는 s0s1이라는 두 값이 있습니다.:

datetime.fromtimestamp(s0) == datetime.fromtimestamp(s1) == dt

(이는 ==가 fold 값을 무시하기 때문입니다. 아래를 참조하십시오.)

이 경우 dt.timestamp()dt.fold == 0인 경우 s0s1 값 중 작은 값을 반환하고, 그렇지 않으면 큰 값을 반환합니다.

예를 들어, US/Eastern 시간대로 설정된 시스템에서는:

>>> datetime(2014, 11, 2, 1, 30, fold=0).timestamp()
1414906200.0
>>> datetime(2014, 11, 2, 1, 30, fold=1).timestamp()
1414909800.0

datetime.datetime 인스턴스 dt가 누락된 시간을 나타낼 때, 다음 조건을 만족하는 s 값은 없습니다.:

datetime.fromtimestamp(s) == dt

그러나 초 단위 간격의 크기만큼 서로 다른 s의 “알아 두면 좋은” 두 값을 만들 수 있습니다. 하나는 UTC 오프셋이 갭 직전 오프셋과 항상 같은 시간대에서 dt에 해당하는 s의 값이고, 다른 하나는 UTC 오프셋이 갭 직후 오프셋과 항상 같은 시간대에서의 유사한 값입니다.

누락된 dt가 주어졌을 때 dt.timestamp()가 반환하는 값은 dt.fold == 0인 경우 두 “알아 두면 좋은” 값 중 큰 값이고, 그렇지 않으면 작은 값입니다. (이는 오타가 아니라 모호한 시간에 대한 규칙과 의도적으로 반대입니다.)

예를 들어, US/Eastern 시간대로 설정된 시스템에서는:

>>> datetime(2015, 3, 8, 2, 30, fold=0).timestamp()
1425799800.0
>>> datetime(2015, 3, 8, 2, 30, fold=1).timestamp()
1425796200.0

인식 있는 datetime 인스턴스

PEP 이전의 tzinfo 구현을 사용하는 사용자는 자신의 인식 있는 datetime 인스턴스 동작에서 어떠한 변화도 보지 못합니다. fold 속성의 값만 다른 이러한 두 인스턴스는 fold 값에 명시적으로 접근하는 방법 외에는 서로 구별할 수 없습니다. (이는 이러한 PEP 이전 구현이 fold 속성을 사용하지 않기 때문입니다.)

반면 객체의 tzinfo가 fold를 인식하는 구현으로 설정되면, fold 또는 gap에서 fold 값이 여러 메서드의 결과에 영향을 줍니다. 해당 메서드는 utcoffset(), dst(), tzname(), astimezone(), 형식 지정에 “%Z” 또는 “%z” 지시어를 사용하는 경우의 strftime(), isoformat(), timetuple()입니다.

날짜와 시간 결합 및 분리

datetime.datetime.combine() 메서드는 fold 속성의 값을 결과로 생성되는 datetime.datetime 인스턴스에 복사합니다.

datetime.datetime.time() 메서드는 fold 속성의 값을 결과로 생성되는 datetime.time 인스턴스에 복사합니다.

피클

fold 속성의 값은 프로토콜 버전 4(파이썬 3.4에서 도입) 이상으로 생성된 피클에만 저장됩니다.

datetime.datetimedatetime.time 객체의 피클 크기는 변경되지 않습니다. fold 값은 datetime.datetime 피클 페이로드의 세 번째 바이트 첫 번째 비트에 인코딩되고, datetime.time 페이로드의 첫 번째 바이트 첫 번째 비트에도 인코딩됩니다. current implementation에서는 이 바이트들이 월(1-12) 및 시간(0-23) 값을 저장하는 데 사용되며 첫 번째 비트는 항상 0입니다. 현재 언피클 코드가 검사하는 바이트가 이 바이트들뿐이기 때문에 이를 선택했습니다. 따라서 PEP 이후의 fold=1 피클을 PEP 이전의 Python에서 로드하면, 범위를 벗어난 구성 요소를 가진 인스턴스가 생성되는 대신 예외가 발생합니다.

표준 라이브러리의 tzinfo 구현

이 PEP에서는 datetime.tzinfo 추상 클래스의 새로운 구현을 제안하지 않습니다. 기존의 (고정 오프셋) 시간대는 모호한 현지 시간을 만들지 않으며, 해당 시간대의 utcoffset() 구현은 fold 값과 관계없이 지금과 동일한 상수 값을 반환합니다.

추상 datetime.tzinfo 클래스의 fromutc() 기본 구현은 변경되지 않습니다. 포함된 유일한 tzinfo 구현인 고정 오프셋 시간대를 구현하는 datetime.timezone 클래스가 fromutc()를 재정의하기 때문에, 이 구현은 현재 표준 라이브러리 어디에서도 사용되지 않습니다. 기본 구현을 변경하지 않고 유지하면 기본 fromutc()를 상속하는 PEP 이전의 서드파티 구현이 실수로 영향을 받지 않는다는 이점이 있습니다.

새로운 tzinfo 구현을 위한 지침

DST 및 기타 원인으로 인한 가변 UTC 오프셋을 지원하려는 구체적인 datetime.tzinfo서브클래스 구현자는 다음 지침을 따라야 합니다.

모르는 것이 행복입니다

새로운 utcoffset(), tzname()dst() 메서드 구현은 모호하거나 누락된 시간에 대해 호출되는 경우가 아니라면 fold의 값을 무시해야 합니다.

폴드 안에서

새로운 서브클래스는 베이스 클래스의 fromutc() 메서드를 오버라이드하고, 서로 다른 두 UTC 시간 u0u1 (u0 <u1)이 동일한 현지 시간 t에 대응하는 모든 경우에 fromutc(u0)fold=0인 인스턴스를 반환하고 fromutc(u1)fold=1인 인스턴스를 반환하도록 구현해야 합니다. 그 밖의 모든 경우 반환되는 인스턴스는 fold=0이어야 합니다.

utcoffset(), tzname()dst() 메서드는 fold 특성의 값을 사용하여, 달리 모호한 시간 t가 전환 전 시간에 해당하는지 전환 후 시간에 해당하는지를 판단해야 합니다. 정의에 따라 폴드를 생성하는 모든 전환에서 utcoffset()은 전환 전에는 더 크고 전환 후에는 더 작습니다. tzname()dst()가 반환하는 값은 전환의 종류에 따라 fold 특성의 값에 의존할 수도 있고 의존하지 않을 수도 있습니다.

Diagram of relationship between UTC and local time around a fall-back transition – see full description on page.

위의 도식은 폴백 전환 전후의 UTC 시간과 현지 시간 간 관계를 보여 줍니다. 지그재그 선은 fromutc()로 구현된 함수의 그래프입니다. 전환 지점에 인접하고 전환 시 시간 이동의 크기를 갖는 UTC 축의 두 구간은 현지 시간 축의 동일한 구간에 매핑됩니다. 새로운 fromutc() 메서드 구현은 UTC 축에서 노란색으로 표시된 영역에 self가 있을 때 fold 특성을 1로 설정해야 합니다. (모든 구간은 왼쪽은 닫혀 있고 오른쪽은 열린 것으로 취급해야 합니다.)

간극에 유의하십시오

fromutc() 메서드는 간극에 속하는 시간을 절대로 생성해서는 안 됩니다.

간극에 속하는 현지 시간에 대해 utcoffset(), tzname() 또는 dst() 메서드가 호출되면 fold=0인 경우 전환 전에 적용되던 규칙을 사용해야 합니다. 그렇지 않으면 전환 후에 적용되던 규칙을 사용해야 합니다.

Diagram of relationship between UTC and local time around a spring-forward transition – see full description on page.

위의 도식은 스프링 포워드 전환 전후의 UTC 시간과 현지 시간 간 관계를 보여 줍니다. 전환 시 현지 시계는 간극에 속하는 시간들을 건너뛰면서 앞으로 진행됩니다. utcoffset(), tzname()dst()의 값을 결정하기 위해 전환 전의 선은 앞으로 연장하여 fold=0인 간극의 시간에 대응하는 UTC 시간을 찾고, fold=1인 인스턴스의 경우에는 전환 후의 선을 뒤로 연장합니다.

전환 시 규칙 요약

모호하거나 누락된 시간에서 utcoffset()은 다음 표에 따라 값을 반환해야 합니다.

fold=0 fold=1
폴드 oldoff newoff = oldoff - delta
간극 oldoff newoff = oldoff + delta

여기서 oldoff (newoff)는 전환 전(후)의 UTC 오프셋이고, delta는 폴드 또는 간극의 절대 크기입니다.

fold 속성의 해석은 fold 경우와 gap 경우에 일관적이라는 점에 유의하십시오. 두 경우 모두 fold=0 (fold=1)은 UTC 시간을 찾기 위해 전환 전(후)의 fromutc()행을 사용한다는 의미입니다. “Fold” 경우에만 UTC 시간 u0u1fromutc(u) == t 방정식의 “실제” 해이며, “Gap” 경우에는 “가상” 해입니다.

DST 전환

DST 시작 시 도입된 누락된 시간에 대해 utcoffset()dst() 메서드가 반환해야 하는 값은 다음과 같습니다.

fold=0 fold=1
utcoffset() stdoff stdoff + dstoff
dst() zero dstoff

DST 종료 시 도입된 모호한 시간에 대해 utcoffset()dst() 메서드가 반환해야 하는 값은 다음과 같습니다.

fold=0 fold=1
utcoffset() stdoff + dstoff stdoff
dst() dstoff zero

여기서 stdoff는 표준(DST가 아닌) 오프셋이고, dstoff는 DST 보정값(일반적으로 dstoff = timedelta(hours=1))이며, zero = timedelta(0)입니다.

시간 산술 및 비교 연산자

In mathematicks he was greater
Than Tycho Brahe, or Erra Pater:
For he, by geometric scale,
Could take the size of pots of ale;
Resolve, by sines and tangents straight,
If bread or butter wanted weight,
And wisely tell what hour o’ th’ day
The clock does strike by algebra.
– Samuel Butler의 “Hudibras”

naive datetime 인스턴스를 사용하는 모든 연산에서는 fold 속성의 값이 무시됩니다. 그 결과 fold 값만 다른 naive datetime.datetime 또는 datetime.time 인스턴스는 서로 같은 것으로 비교됩니다. 이러한 인스턴스를 구별해야 하는 애플리케이션은 fold의 값을 명시적으로 확인하거나, 모호한 시간이 없는 시간대(예: UTC)로 해당 인스턴스를 변환해야 합니다.

datetime 인스턴스가 aware이든 naive이든 관계없이 timedelta를 더하거나 뺄 때에도 fold 값은 무시됩니다. datetime에 timedelta를 더하거나(빼는) 결과에서는 원래 datetime 인스턴스의 fold=1 여부와 관계없이 항상 fold를 0으로 설정합니다.

ts의 datetime 인스턴스에 대해 차이 t - s를 계산하는 방식에는 어떠한 변경도 제안하지 않습니다. 두 인스턴스가 모두 naive이거나 t.tzinfos.tzinfo와 동일한 인스턴스인 경우(t.tzinfo is s.tzinfoTrue로 평가되는 경우), t - ss + d == t를 만족하는 timedelta d입니다. 앞 단락에서 설명했듯이, timedelta 덧셈은 foldtzinfo속성을 모두 무시하므로 인트라존 또는 naive datetime 뺄셈도 마찬가지입니다.

naive 및 인트라존 비교는 fold의 값을 무시하고 현재와 동일한 결과를 반환합니다. (이는 하위 호환성을 유지하는 유일한 방법입니다. fold를 사용하는 인식 가능한 구간 내 비교가 필요하다면, 양쪽을 먼저 UTC로 변환하십시오.)

인터존 뺄셈은 현재와 같이 정의됩니다. t - s(t - t.utcoffset()) - (s - s.utcoffset()).replace(tzinfo=t.tzinfo)로 계산되지만, t.tzinfo또는 s.tzinfo중 하나라도 post-PEP인 경우 결과는 t.folds.fold의 값에 따라 달라집니다. [5]

Aware datetime 동등성 비교

aware datetime 비교 연산자는 현재와 동일하게 작동하며, 한 피연산자의 utcoffset() 값이 fold에 따라 달라지는 경우 결과가 그 값의 영향을 간접적으로 받습니다. 단, 한 가지 예외가 있습니다. 인터존 비교의 피연산자 중 하나 이상에서 utcoffset()가 해당 foldfold 속성의 값에 따라 달라지는 경우, 결과는 False입니다. [6]

t.tzinfo is s.tzinfoFalse로 평가되는 경우 t == s는 형식적으로 다음과 같이 정의할 수 있습니다. toutc(t, fold)를 aware datetime 인스턴스 t를 받아 주어진 fold값에 따라 동일한 시각을 UTC로 나타내는 naive 인스턴스를 반환하는 함수라고 하겠습니다.

def toutc(t, fold):
    u = t - t.replace(fold=fold).utcoffset()
    return u.replace(tzinfo=None)

그러면 t == s는 다음과 동치입니다.

toutc(t, fold=0) == toutc(t, fold=1) == toutc(s, fold=0) == toutc(s, fold=1)

하위 호환성 및 전방 호환성

fold플래그를 명시적으로 읽지 않거나 그렇게 하는 tzinfo 구현을 사용하지 않는 프로그램에는 이 제안이 거의 영향을 미치지 않습니다. 이러한 프로그램에서 유일하게 눈에 띄는 변경 사항은 POSIX 타임스탬프로의 변환 및 POSIX 타임스탬프로부터의 변환이 이제 부동 소수점 반올림을 고려하면 올바르게 왕복된다는 점입니다. 이전의 잘못된 동작에 대한 해결 방법을 구현한 프로그램은 수정해야 할 수 있습니다.

이전 프로그램에서 생성된 피클은 완전한 전방 호환성을 유지합니다. 새 버전에서 fold=1로 피클된 datetime/time 인스턴스만 이전 Python 버전에서 읽을 수 없게 됩니다. fold=0인스턴스의 피클은 (기본값입니다) 변경되지 않습니다.

질문과 답변

새 플래그를 “isdst”라고 부르지 않는 이유는 무엇입니까?

비기술적 답변

  • 앨리스: 밥, 내일 오전 01:30에 별 관측 파티를 엽시다!
  • 밥: 지정된 시간에 서머타임이 적용되는지 여부를 처음부터 가정해야 합니까?
  • 앨리스: 네?

  • Bob: Alice - 내일 오전 01:30에 별 관측 파티를 열자!
  • Alice: 있잖아요, Bob, 내일 오전 01:30은 두 번 발생합니다. 어느 시간을 염두에 두고 있습니까?
  • Bob: 생각해 보지 않았지만, 첫 번째를 선택합시다.

(같은 인물들, 한 시간 후)


  • Bob: Alice - 이 Py-O-Clock 장치는 내일 오전 01:30으로 설정할 때 fold=0과 fold=1 중에서 선택하라고 합니다. 어떻게 해야 합니까?
  • Alice: Py-O-Clock이라는 것은 들어본 적이 없지만, fold=0이 첫 번째 오전 01:30이고 fold=1이 두 번째라고 생각합니다.

기술적인 이유

time.struct_time 객체의 tm_isdst 필드를 사용하여 fold에서 지역 시간을 구분할 수 있지만, 이러한 구분의 의미는 이 PEP의 제안과 완전히 다릅니다.

tm_isdst 필드의 주요 문제는 tzinfo 구현에서만 알 수 있는 시간대의 세부 사항을 알지 못하면 tm_isdst에 어떤 값이 적절한지 알 수 없다는 것입니다. 따라서 tm_isdsttime.localtime 같은 메서드의 output에서는 유용하지만, time.mktime 같은 메서드의 input으로는 번거롭습니다.

프로그래머가 time.mktimetm_isdst의 음이 아닌 값을 잘못 지정하면 결과가 1시간 어긋난 시간이 되며, time.mktime 호출이 이루어지기 전에 DST에 관해 알 수 있는 방법은 거의 없으므로, 일반적으로 유일하게 합리적인 선택은 tm_isdst=-1입니다.

tm_isdst와 달리, 제안된 fold 속성은 해당 속성이 없을 때 해석이 두 가지(또는 전혀) 가능한 경우가 아니라면 datetime 인스턴스의 해석에 영향을 주지 않습니다.

tm_isdst와 같은 의미를 갖지 않는 isdst라는 것을 두는 것은 매우 혼란스러울 것이므로, 다른 이름이 필요합니다. 더욱이 datetime.datetime 클래스에는 이미 dst()라는 메서드가 있으며, fold를 “isdst”라고 부르면 “isdst”는 0이지만 dst()는 그렇지 않거나 그 반대인 상황이 필연적으로 발생합니다.

왜 “fold”입니까?

Guido van Rossum이 제안했으며 한 저자가 지지했습니다(처음에는 다른 한 저자가 지지하지 않았습니다). 속성에 허용되는 값이 False/True에서 0/1로 변경된 후 합의에 도달했습니다. “fold”라는 명사는 올바른 함의와 기억하기 쉬운 규칙을 갖지만, 동시에 근거 없는 추측을 유도하지 않습니다.

“first”란 무엇입니까?

이는 명백한 대안인 (“second”)가 기존 속성과 충돌하기 때문에 처음에 선택된 속성의 작업용 이름이었습니다. 이는 주로 True를 기본값으로 만들게 된다는 이유로 거부되었습니다.

다음과 같은 대체 이름도 고려되었습니다.

later
“fold”의 강력한 경쟁 후보입니다. 한 저자는 똑같이 적합한 “latter”와 혼동될 수 있다는 이유로 이를 싫어하지만, 어디에서나 자동 완성이 사용되는 시대에는 이는 사소한 고려 사항입니다. 더 강력한 반론은 시간이 존재하지 않는 경우 later=True 인스턴스가 .astimezone(timezone.utc)에 의해 later=False인 경우보다 이른 시간으로 변환된다는 점일 수 있습니다. 그러나 이 역시 원래 시간이 유효하지 않다는 바람직한 표시로 해석할 수 있습니다.
which
localtime 함수 분기 인덱스의 original 자리표시자 이름은 구분 속성의 이름으로 independently proposed 되었고 some support 를 받았습니다.
repeated
메일링 리스트에서 어떠한 지원도 받지 못했습니다.
ltdf
(Local Time Disambiguation Flag) - 짧은 이름이며, 문서를 읽지 않고는 그 의미를 추측하려는 사람이 없을 것입니다. (이 약어는 어떤 대안도 지지하고 싶지 않았던 사람들이 ltdf=False가 이전 방식이라는 의미로 PEP 논의에서 사용했습니다.)

두 개의 값이면 충분합니까?

fold속성에 None 또는 -1 값을 허용해야 한다는 몇 가지 이유가 제기되었습니다. 하위 호환성, tm_isdst와의 유사성, 그리고 잘못된 시간에 대한 엄격한 검사입니다.

하위 호환성

fold플래그의 기본값이 None이라면 하위 호환성을 개선할 수 있으며, 이는 PEP 이전의 동작이 요청되었음을 나타낸다는 의견이 제시되었습니다. 아래의 분석에 근거하여, fold=0을 기본값으로 하는 제안된 변경 사항은 하위 호환성이 충분하다고 판단합니다.

이 PEP는 그 외에는 동일한 두 datetime 인스턴스의 fold값이 서로 다르다는 사실을 프로그램이 알아낼 수 있는 방법을 세 가지만 제공합니다. (1) fold속성을 명시적으로 검사하는 방법, (2) 인스턴스가 나이브한 경우 astimezone()메서드를 사용하여 다른 시간대로 변환하는 방법, (3) timestamp()메서드를 사용하여 float으로 변환하는 방법입니다.

fold는 새 속성이므로 첫 번째 방법은 기존 프로그램에서 사용할 수 없습니다. 방법 (2)는 시스템 시간대에서 폴드 또는 갭에 해당하는 나이브 datetime에 대해서만 작동한다는 점에 유의하십시오. 그 밖의 모든 경우에는 인스턴스가 PEP 이전 프로그램에서는 사용할 수 없는 fold인식 tzinfo를 사용하지 않는 한 변환 과정에서 fold값이 무시됩니다. 마찬가지로 나이브 인스턴스에 호출하는 astimezone()도 그러한 프로그램에서는 사용할 수 없습니다. 현재 astimezone()은 나이브 datetime에서 작동하지 않기 때문입니다.

따라서 기존 프로그램이 이 PEP의 구현 이후 다른 결과를 내기 시작할 수 있는 상황은 한 가지뿐입니다. 즉, 폴드 또는 갭에 해당하는 나이브 datetime 인스턴스에서 datetime.timestamp()메서드를 호출하는 경우입니다. 현재 구현에서는 결과가 정의되지 않습니다. 시스템의 mktime구현에 따라 프로그램은 이러한 경우 서로 다른 결과나 오류를 볼 수 있습니다. 이 PEP가 적용되면 그러한 경우 timestamp 값은 명확하게 정의되지만 fold플래그의 값에 따라 달라집니다. datetime.timestamp()메서드 동작의 변경은 이 PEP로 가능해진 버그 수정이라고 판단합니다. 이 동작에 의존하는 사용자는 dt.timestamp()대신 time.mktime(dt.timetuple()) + 1e-6*dt.microsecond을 작성하여 이전 동작을 여전히 에뮬레이션할 수 있습니다.

tm_isdst와의 유사성

time.mktime인터페이스는 tm_isdst플래그에 대해 -1, 0, 1이라는 세 가지 값을 허용합니다. 위에서 설명했듯이 -1, 즉 나머지 필드로부터 주어진 시간에 DST가 적용되는지 mktime이 판단하도록 하는 것이 실제로 유용한 유일한 선택입니다.

그러나 fold플래그를 사용하면 DST 전환이 있는 대부분의 시간대에서 datetime.timestamp()는 99.98%의 경우 tm_isdst=-1일 때의 mktime과 동일한 값을 반환합니다. 또한 tm_isdst=-1과 같은 동작은 fold의 값과 관계없이 지정됩니다.

datetime.timestamp()tm_isdst=-1을 사용한 mktime이 서로 다른 결과를 낼 수 있는 경우는 0.02%(연간 2시간)뿐입니다. 그러나 이 경우에도 관련 표준에서는 mktime이 -1을 반환하고 오류 코드를 설정하는 것을 허용하지만, 대부분의 mktime구현은 fold=0 또는 fold=1값을 반환합니다.

다시 말해, tm_isdst=-1동작이 이 PEP에서 빠진 것은 아닙니다. 오히려 이 동작은 잘 정의된 서로 다른 두 가지 형태로 제공되는 유일한 동작입니다. 빠진 동작은 잘못 지정된 tm_isdst로 인해 주어진 현지 시간이 다른 현지 시간으로 해석되는 경우입니다.

예를 들어 북반구의 DST 적용 시간대(DST가 6월에 적용되는 시간대)에서는 다음과 같은 결과를 얻을 수 있습니다.

>>> from time import mktime, localtime
>>> t = mktime((2015, 6, 1, 12, 0, 0, -1, -1, 0))
>>> localtime(t)[:]
(2015, 6, 1, 13, 0, 0, 0, 152, 1)

12:00이 mktime에 의해 13:00으로 해석되었다는 점에 유의하십시오. datetime.timestamp, datetime.fromtimestamp를 사용하면 현재 다음 사항이 보장됩니다.

>>> t = datetime.datetime(2015, 6, 1, 12).timestamp()
>>> datetime.datetime.fromtimestamp(t)
datetime.datetime(2015, 6, 1, 12, 0)

이 PEP는 fold의 두 값 모두에 동일한 보장을 확장합니다:

>>> t = datetime.datetime(2015, 6, 1, 12, fold=0).timestamp()
>>> datetime.datetime.fromtimestamp(t)
datetime.datetime(2015, 6, 1, 12, 0)
>>> t = datetime.datetime(2015, 6, 1, 12, fold=1).timestamp()
>>> datetime.datetime.fromtimestamp(t)
datetime.datetime(2015, 6, 1, 12, 0)

따라서 레거시 동작에 맞추기 위한 fold=-1의 제안된 용도 중 하나는 필요하지 않습니다. 이전 동작이 정의되지 않았던 몇 가지 경우를 제외하면 fold의 어느 값을 선택하더라도 이전 동작과 일치합니다.

엄격한 잘못된 시간 검사

또 다른 제안은 프로그램에 폴드와 갭을 처리할 수단이 실제로 없음을 나타내기 위해 fold=-1 또는 fold=None을 사용하고, dt.utcoffset()dt가 모호하거나 존재하지 않는 현지 시간을 나타낼 때마다 오류를 발생시켜야 한다는 것이었습니다.

이 제안의 가장 큰 문제는 오류를 발생시키는 것이 선택 사항이 아닌 상황에서 dt.utcoffset()이 내부적으로 사용된다는 점입니다. 예를 들어 딕셔너리 조회나 리스트/집합 멤버십 검사에서 사용됩니다. 따라서 엄격한 gap/fold 검사 동작은 별도의 플래그로 제어해야 하며, 예를 들면 dt.utcoffset(raise_on_gap=True, raise_on_fold=False)와 같이 지정할 수 있습니다. 그러나 이 기능은 사용자 코드에서 쉽게 구현할 수 있습니다:

def utcoffset(dt, raise_on_gap=True, raise_on_fold=False):
    u = dt.utcoffset()
    v = dt.replace(fold=not dt.fold).utcoffset()
    if u == v:
        return u
    if (u < v) == dt.fold:
        if raise_on_fold:
            raise AmbiguousTimeError
    else:
        if raise_on_gap:
            raise MissingTimeError
    return u

또한 문제가 발생하는 경우에 오류를 발생시키는 것은 가능한 여러 해결책 중 하나일 뿐입니다. 대화형 프로그램은 사용자에게 추가 입력을 요청할 수 있고, 서버 프로세스는 경고를 기록한 후 적절한 기본 동작을 취할 수 있습니다. 가능한 모든 사용자 요구 사항에 대한 함수를 모두 제공할 수는 없지만, 이 PEP는 몇 줄의 코드로 원하는 동작을 구현할 수단을 제공합니다.

구현

Picture Credit

This image is a work of a U.S. military or Department of Defense employee, taken or made as part of that person’s official duties. As a work of the U.S. federal government, the image is in the public domain.