Java를 사용하여 캘린더 TimeZones를 처리하는 방법은 무엇입니까?


92

내 애플리케이션에서 가져온 타임 스탬프 값이 있습니다. 사용자는 지정된 로컬 TimeZone에있을 수 있습니다.

이 날짜는 주어진 시간이 항상 GMT라고 가정하는 WebService에 사용되기 때문에 사용자의 매개 변수를 say (EST)에서 (GMT)로 변환해야합니다. 키커는 다음과 같습니다. 사용자는 TZ를 알지 못합니다. 그는 WS에 보낼 생성 날짜를 입력하므로 필요한 것은 다음과 같습니다.

사용자 입력 : 2008 년 5 월 1 일 오후 6:12 (EST)
WS에 대한 매개 변수는 2008 년 5 월 1 일 오후 6:12 (GMT) 여야 합니다 .

TimeStamps는 기본적으로 항상 GMT로되어 있다는 것을 알고 있지만, 매개 변수를 보낼 때 TS (GMT로되어 있어야 함)에서 캘린더를 만들었더라도 사용자가 GMT에 있지 않는 한 시간은 항상 꺼져 있습니다. 내가 무엇을 놓치고 있습니까?

Timestamp issuedDate = (Timestamp) getACPValue(inputs_, "issuedDate");
Calendar issueDate = convertTimestampToJavaCalendar(issuedDate);
...
private static java.util.Calendar convertTimestampToJavaCalendar(Timestamp ts_) {
  java.util.Calendar cal = java.util.Calendar.getInstance(
      GMT_TIMEZONE, EN_US_LOCALE);
  cal.setTimeInMillis(ts_.getTime());
  return cal;
}

이전 코드를 사용하면 결과적으로 다음과 같은 결과를 얻을 수 있습니다 (쉽게 읽을 수있는 짧은 형식).

[2008 년 5 월 1 일 오후 11:12]


2
시간대 만 변경하고 날짜 / 시간도 함께 변환하지 않는 이유는 무엇입니까?
Spencer Kormos

1
한 시간대에있는 Java 날짜를 다른 시간대에 가져 오는 것은 정말 고통 스럽습니다. IE, 5PM EDT를 받고 5PM PDT를 받으세요.
mtyson

답변:


62
public static Calendar convertToGmt(Calendar cal) {

    Date date = cal.getTime();
    TimeZone tz = cal.getTimeZone();

    log.debug("input calendar has date [" + date + "]");

    //Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT 
    long msFromEpochGmt = date.getTime();

    //gives you the current offset in ms from GMT at the current date
    int offsetFromUTC = tz.getOffset(msFromEpochGmt);
    log.debug("offset is " + offsetFromUTC);

    //create a new calendar in GMT timezone, set to this date and add the offset
    Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
    gmtCal.setTime(date);
    gmtCal.add(Calendar.MILLISECOND, offsetFromUTC);

    log.debug("Created GMT cal with date [" + gmtCal.getTime() + "]");

    return gmtCal;
}

다음은 현재 시간 ( "12:09:05 EDT"from Calendar.getInstance())을 전달한 경우의 출력 입니다.

DEBUG-입력 달력에 날짜가 있음 [Thu Oct 23 12:09:05 EDT 2008]
DEBUG-오프셋은 -14400000입니다. DEBUG-
생성 된 GMT cal과 날짜 [Thu Oct 23 08:09:05 EDT 2008]

12:09:05 GMT는 8:09:05 EDT입니다.

여기서 혼란스러운 부분 Calendar.getTime()Date 현재 시간대에서 하고 달력의 시간대를 수정하고 기본 날짜도 롤링하는 방법이 없다는 것입니다. 웹 서비스가 사용하는 매개 변수 유형에 따라 epoch에서 밀리 초 단위로 WS 처리를 원할 수 있습니다.


11
offsetFromUTC그것을 더하는 대신 빼야 하지 않습니까? 예를 들어, 12:09 GMT가 8:09 EDT (참)이고 사용자가 "12:09 EDT"를 입력하면 알고리즘이 "16:09 GMT"를 출력해야합니다.
DzinX

29

응답 해 주셔서 감사합니다. 추가 조사 끝에 정답을 얻었습니다. Skip Head에서 언급했듯이 내 응용 프로그램에서 얻은 TimeStamped가 사용자의 TimeZone으로 조정되었습니다. 따라서 사용자가 오후 6시 12 분 (EST)을 입력하면 오후 2시 12 분 (GMT)이 표시됩니다. 내가 필요한 것은 사용자가 입력 한 시간이 내가 WebServer 요청에 보낸 시간이되도록 변환을 취소하는 방법이었습니다. 이 작업을 수행 한 방법은 다음과 같습니다.

// Get TimeZone of user
TimeZone currentTimeZone = sc_.getTimeZone();
Calendar currentDt = new GregorianCalendar(currentTimeZone, EN_US_LOCALE);
// Get the Offset from GMT taking DST into account
int gmtOffset = currentTimeZone.getOffset(
    currentDt.get(Calendar.ERA), 
    currentDt.get(Calendar.YEAR), 
    currentDt.get(Calendar.MONTH), 
    currentDt.get(Calendar.DAY_OF_MONTH), 
    currentDt.get(Calendar.DAY_OF_WEEK), 
    currentDt.get(Calendar.MILLISECOND));
// convert to hours
gmtOffset = gmtOffset / (60*60*1000);
System.out.println("Current User's TimeZone: " + currentTimeZone.getID());
System.out.println("Current Offset from GMT (in hrs):" + gmtOffset);
// Get TS from User Input
Timestamp issuedDate = (Timestamp) getACPValue(inputs_, "issuedDate");
System.out.println("TS from ACP: " + issuedDate);
// Set TS into Calendar
Calendar issueDate = convertTimestampToJavaCalendar(issuedDate);
// Adjust for GMT (note the offset negation)
issueDate.add(Calendar.HOUR_OF_DAY, -gmtOffset);
System.out.println("Calendar Date converted from TS using GMT and US_EN Locale: "
    + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT)
    .format(issueDate.getTime()));

코드 출력은 다음과 같습니다. (사용자가 입력 한 2008 년 5 월 1 일 오후 6:12 (EST)

현재 사용자의 시간대 :
GMT에서 EST 현재 오프셋 (시간) :-4 (일반적으로 -5, DST 조정 제외)
ACP의 TS : 2008-05-01 14 : 12 : 00.0
GMT 및 US_EN 로케일을 사용하여 TS에서 변환 된 달력 날짜 : 2008 년 5 월 1 일 오후 6:12 (GMT)


20

날짜가 웹 서비스와 관련하여 사용된다고 말 했으므로 어느 시점에서 문자열로 직렬화되었다고 가정합니다.

이 경우 DateFormat 클래스 의 setTimeZone 메서드 를 살펴보아야합니다 . 이것은 타임 스탬프를 인쇄 할 때 사용할 시간대를 나타냅니다.

간단한 예 :

SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));

Calendar cal = Calendar.getInstance();
String timestamp = formatter.format(cal.getTime());

4
SDF에 의존하지 않았습니다. 자체 시간대가 있었는데 캘린더 시간대를 변경해도 효과가없는 이유가 궁금합니다!
Chris.Jenkins

UTC는 AvailableIDs ()에서하지 않기 때문에 TimeZone.getTimeZone ( "UTC")은 하나님을 알고 ... 유효하지 않은 이유
childno͡.de

내 컴퓨터에서 TimeZone.getTimeZone ( "UTC")를 사용할 수 있습니다. JVM에 의존합니까?
CodeClimber

2
setTimeZone () 메서드에 대한 멋진 아이디어입니다. 당신은 저에게 많은 두통을 구했습니다. 고마워요!
Bogdan Zurac 2013 년

1
내가 필요한 것만! 포맷터에서 서버 시간대를 설정할 수 있으며 캘린더 형식으로 변환 할 때 걱정할 필요가 없습니다. 지금까지 최고의 방법! getTimeZone의 경우 ETD의 경우 "GMT-4 : 00"형식을 사용해야 할 수 있습니다.
Michael Kern

12

Joda Time으로 해결할 수 있습니다 .

Date utcDate = new Date(timezoneFrom.convertLocalToUTC(date.getTime(), false));
Date localDate = new Date(timezoneTo.convertUTCToLocal(utcDate.getTime()));

자바 8 :

LocalDateTime localDateTime = LocalDateTime.parse("2007-12-03T10:15:30");
ZonedDateTime fromDateTime = localDateTime.atZone(
    ZoneId.of("America/Toronto"));
ZonedDateTime toDateTime = fromDateTime.withZoneSameInstant(
    ZoneId.of("Canada/Newfoundland"));

그러나 측면 의존성 및 추가 구성 없이는 직접 호환되지 않는 최대 절전 모드 4를 사용하는 경우주의하십시오. 그러나 세 번째 버전에서는 가장 빠르고 사용하기 쉬운 접근 방식이었습니다.
Aubergine 2013 년

8

TimeStamp가 원래 시스템의 시간대로 설정되어있는 것 같습니다.

이것은 더 이상 사용되지 않지만 작동합니다.

cal.setTimeInMillis(ts_.getTime() - ts_.getTimezoneOffset());

더 이상 사용되지 않는 방법은

Calendar.get(Calendar.ZONE_OFFSET) + Calendar.get(Calendar.DST_OFFSET)) / (60 * 1000)

그러나 시스템이 어떤 시간대에 있는지 알고 있기 때문에 클라이언트 측에서 수행해야합니다.


7

한 timeZone에서 다른 timeZone으로 변환하는 방법 (아마도 작동합니다 :)).

/**
 * Adapt calendar to client time zone.
 * @param calendar - adapting calendar
 * @param timeZone - client time zone
 * @return adapt calendar to client time zone
 */
public static Calendar convertCalendar(final Calendar calendar, final TimeZone timeZone) {
    Calendar ret = new GregorianCalendar(timeZone);
    ret.setTimeInMillis(calendar.getTimeInMillis() +
            timeZone.getOffset(calendar.getTimeInMillis()) -
            TimeZone.getDefault().getOffset(calendar.getTimeInMillis()));
    ret.getTime();
    return ret;
}

6

DateTimestamp 객체는 시간대를 알지 못합니다. 즉, 해당 순간을 시간과 일로 특정 해석하지 않고 에포크 이후 특정 시간 (초)을 나타냅니다. 시간대는 GregorianCalendar (이 작업에 직접 필요하지 않음) 및 SimpleDateFormat으로 만 입력되며 , 별도의 필드와 날짜 (또는 long ) 값 간에 변환하려면 시간대 오프셋이 필요 합니다.

OP의 문제는 처리 시작 부분에 있습니다. 사용자가 모호한 시간을 입력하고 GMT가 아닌 지역 시간대로 해석됩니다. 이 시점에서 값은 "동부 표준시 06시 12분" 쉽게로 인쇄 할 수 있습니다, "11.12 GMT" 또는 다른 시간대하지만 결코 예정되지 않은 변경"6.12 GMT" .

"06:12""HH : MM" (기본값은 현지 표준 시간대) 로 구문 분석 하는 SimpleDateFormat 을 대신 UTC로 기본 설정하는 방법은 없습니다 . SimpleDateFormat 은 그 자체로는 너무 똑똑합니다.

그러나 입력에 명시 적으로 입력하면 모든 SimpleDateFormat 인스턴스가 올바른 시간대를 사용 하도록 설득 할 수 있습니다 . 수신 된 (그리고 적절하게 검증 된) "06:12" 에 고정 문자열을 추가하여 "06:12 GMT"를 다음 과 같이 구문 분석 합니다. "HH : MM z" .

GregorianCalendar 필드 를 명시 적으로 설정 하거나 시간대 및 일광 절약 시간 오프셋을 검색하고 사용할 필요가 없습니다 .

실제 문제는 기본적으로 로컬 시간대로 설정되는 입력, UTC로 기본 설정되는 입력, 명시적인 시간대 표시가 실제로 필요한 입력을 분리하는 것입니다.


4

과거에 저에게 도움이 된 것은 사용자의 시간대와 GMT 간의 오프셋 (밀리 초)을 결정하는 것이 었습니다. 오프셋이 있으면 간단히 더하거나 빼면 (변환이 진행되는 방식에 따라) 어느 시간대에서든 적절한 시간을 얻을 수 있습니다. 일반적으로 Calendar 개체의 밀리 초 필드를 설정하여이 작업을 수행하지만 타임 스탬프 개체에 쉽게 적용 할 수 있습니다. 오프셋을 얻는 데 사용하는 코드는 다음과 같습니다.

int offset = TimeZone.getTimeZone(timezoneId).getRawOffset();

timezoneId는 사용자 시간대의 ID입니다 (예 : EST).


9
원시 오프셋을 사용하면 DST가 무시됩니다.
Werner Lehmann

1
정확히,이 방법은 반년 동안 잘못된 결과를 제공합니다. 최악의 형태의 오류.
Danubian Sailor

1

java.time

현대적인 접근 방식은 Java의 초기 버전과 함께 번들로 제공되는 성가신 레거시 날짜-시간 클래스를 대체 하는 java.time 클래스를 사용합니다 .

java.sql.Timestamp클래스는 이러한 기존의 클래스 중 하나입니다. 더 이상 필요 없어요. 대신 InstantJDBC 4.2 이상을 사용하는 데이터베이스에서 직접 또는 기타 java.time 클래스를 사용하십시오.

Instant클래스는 나노초 (소수점의 최대 9 자리) 의 해상도로 타임 라인의 한 순간을 UTC 로 나타냅니다 .

Instant instant = myResultSet.getObject(  , Instant.class ) ; 

기존 Timestamp클래스 와 상호 운용해야하는 경우 이전 클래스에 추가 된 새 변환 메소드를 통해 즉시 java.time으로 변환하십시오.

Instant instant = myTimestamp.toInstant() ;

다른 시간대로 조정하려면 시간대를 ZoneId객체 로 지정하십시오 . 지정 적절한 시간대 이름 의 형식 continent/region예컨대, America/Montreal, Africa/Casablanca, 또는 Pacific/Auckland. EST또는 같은 3-4 자의 의사 영역 IST은 표준 시간대가 아니고 고유하지도 않은 (!) 표준 시간대 가 아니므 로 절대 사용하지 마십시오 .

ZoneId z = ZoneId.of( "America/Montreal" ) ;

에 적용하여 개체 Instant를 생성 ZonedDateTime합니다.

ZonedDateTime zdt = instant.atZone( z ) ;

사용자에게 표시 할 문자열을 생성하려면 Stack Overflow를 검색 DateTimeFormatter하여 많은 토론과 예제를 찾습니다.

귀하의 질문은 실제로 사용자 데이터 입력에서 날짜-시간 개체로 다른 방향으로가는 것입니다. 일반적으로 데이터 입력을 날짜와 시간의 두 부분으로 나누는 것이 가장 좋습니다.

LocalDate ld = LocalDate.parse( dateInput , DateTimeFormatter.ofPattern( "M/d/uuuu" , Locale.US ) ) ;
LocalTime lt = LocalTime.parse( timeInput , DateTimeFormatter.ofPattern( "H:m a" , Locale.US ) ) ;

질문이 명확하지 않습니다. 사용자가 입력 한 날짜와 시간을 UTC로 해석 하시겠습니까? 아니면 다른 시간대?

UTC를 의미하는 경우 UTC에 OffsetDateTime대한 상수를 사용하여 오프셋으로를 만듭니다 ZoneOffset.UTC.

OffsetDateTime odt = OffsetDateTime.of( ld , lt , ZoneOffset.UTC ) ;

다른 표준 시간대를 의미하는 경우 표준 시간대 개체 인 ZoneId. 하지만 어느 시간대? 기본 시간대를 감지 할 수 있습니다. 또는 중요하다면 사용자의 의도를 확인해야합니다.

ZonedDateTime zdt = ZonedDateTime.of( ld , lt , z ) ;

정의에 따라 항상 UTC로되어있는 더 간단한 객체를 얻으려면 Instant.

Instant instant = odt.toInstant() ;

…또는…

Instant instant = zdt.toInstant() ; 

데이터베이스로 보냅니다.

myPreparedStatement.setObject(  , instant ) ;

java.time 정보

java.time의 프레임 워크는 나중에 자바 8에 내장되어 있습니다. 이 클래스는 까다로운 기존에 대신 기존 과 같은 날짜 - 시간의 수업을 java.util.Date, Calendar, SimpleDateFormat.

Joda 타임 프로젝트는 지금에 유지 관리 모드 의로 마이그레이션을 조언 java.time의 클래스.

자세한 내용은 Oracle Tutorial을 참조하십시오 . 그리고 많은 예제와 설명을 위해 Stack Overflow를 검색하십시오. 사양은 JSR 310 입니다.

java.time 클래스는 어디서 구할 수 있습니까?

ThreeTen - 추가 프로젝트 추가 클래스와 java.time를 확장합니다. 이 프로젝트는 java.time에 향후 추가 될 수있는 가능성을 입증하는 곳입니다. 당신은 여기에 몇 가지 유용한 클래스와 같은 찾을 수 있습니다 Interval, YearWeek, YearQuarter, 그리고 .

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