Java kullanarak takvim Zaman Dilimleri nasıl kullanılır?


92

Uygulamamdan gelen bir Zaman Damgası değerim var. Kullanıcı herhangi bir yerel Saat Dilimi'nde olabilir.

Bu tarih, verilen saatin her zaman GMT'de olduğunu varsayan bir Web Hizmeti için kullanıldığından, kullanıcının parametresini örneğin (EST) 'den (GMT)' ye dönüştürmem gerekiyor. İşte önemli olan: Kullanıcı TZ'sinden habersizdir. WS'ye göndermek istediği yaratma tarihini giriyor, bu yüzden ihtiyacım olan şey:

Kullanıcı girer: 5/1/2008 18:12 (EST)
WS parametresinin şu şekilde olması gerekir : 5/1/2008 18:12 (GMT)

Zaman Damgalarının varsayılan olarak her zaman GMT'de olması gerektiğini biliyorum, ancak parametreyi gönderirken, Takvimimi TS'den (GMT'de olması gerekiyordu) oluşturmuş olsam bile, kullanıcı GMT'de olmadığı sürece saatler her zaman kapalıdır. Neyi kaçırıyorum?

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

Önceki Kod ile, sonuç olarak elde ettiğim şey budur (Kolay okuma için Kısa Biçim):

[1 Mayıs 2008 23:12]


2
Neden sadece saat dilimini değiştiriyorsunuz ve tarih / saati onunla birlikte dönüştürmüyorsunuz?
Spencer Kormos

1
Bir zaman diliminde olan bir Java tarihini alıp başka bir zaman diliminde o tarihi almak gerçek bir acı . IE, 5PM EDT alın ve 5PM PDT alın.
mtyson

Yanıtlar:


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

Şu anki saati ("12:09:05 EDT" itibaren Calendar.getInstance()) geçersem şu çıktı :

HATA AYIKLAMA - giriş takviminin tarihi var [23 Ekim 12:09:05 EDT 2008]
DEBUG - fark -14400000
DEBUG - Tarihle GMT kalibrasyonu oluşturuldu [Per 23 Ekim 08:09:05 EDT 2008]

12:09:05 GMT 8:09:05 EDT.

Buradaki kafa karıştırıcı kısım , size mevcut saat diliminizde a'yı Calendar.getTime()döndürmesi Dateve ayrıca bir takvimin saat dilimini değiştirmenin ve temel tarihin de dikkate alınmasının bir yönteminin olmamasıdır. Web servisinizin ne tür bir parametre aldığına bağlı olarak, WS'nin epoch'tan milisaniye cinsinden anlaşmasını isteyebilirsiniz.


11
Eklemek offsetFromUTCyerine çıkarmanız gerekmiyor mu? Örneğinizi kullanırsak, 12:09 GMT 8:09 EDT ise (bu doğrudur) ve kullanıcı "12:09 EDT" girerse, benim fikrime göre algoritma "16:09 GMT" vermelidir.
DzinX

29

Cevap verdiğiniz için hepinize teşekkürler. Daha fazla araştırmadan sonra doğru cevaba ulaştım. Skip Head tarafından belirtildiği gibi, uygulamamdan aldığım TimeStamped, kullanıcının TimeZone'una ayarlanıyordu. Öyleyse, Kullanıcı 18:12 (EST) girdiyse, 14:12 (GMT) alırdım. İhtiyacım olan şey, dönüşümü geri almanın bir yoluydu, böylece kullanıcı tarafından girilen zaman WebSunucusu isteğine gönderdiğim saattir. Bunu şu şekilde başardım:

// 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()));

Kodun çıktısı: (Kullanıcı 5/1/2008 18:12 (EST) girdi

Mevcut Kullanıcının Zaman Dilimi:
GMT'den EST Akım Farkı (saat cinsinden): - 4 (Normalde -5, DST ayarlı) ACP'den TS
: 2008-05-01 14: 12: 00.0
Takvim GMT ve US_EN Yerel Ayar kullanılarak TS'den dönüştürülen tarih : 01.05.2008 18:12 (GMT)


20

Tarihin web servisleriyle bağlantılı olarak kullanıldığını söylüyorsunuz, bu yüzden bir noktada bunun bir dizge halinde serileştirildiğini varsayıyorum.

Durum buysa, DateFormat sınıfının setTimeZone yöntemine bir göz atmalısınız . Bu, zaman damgası yazdırılırken hangi saat diliminin kullanılacağını belirler.

Basit bir örnek:

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'yi yeniden anlatmadım Kendi saat dilimine sahipti, Takvim Zaman Dilimini değiştirmenin neden hiçbir etkisi olmadığını merak ediyorum!
Chris.Jenkins

TimeZone.getTimeZone ("UTC") UTC, AvailableID'lerde () olmadığı için geçerli değildir ... nedenini tanrı bilir
childno͡.de

TimeZone.getTimeZone ("UTC") makinemde mevcut. JVM'ye bağlı mı?
CodeClimber

2
SetTimeZone () yöntemiyle harika bir fikir. Beni çok fazla baş ağrısından kurtardın, çok teşekkürler!
Bogdan Zurac

1
Tam ihtiyacım olan şey! Sunucu saat dilimini formatlayıcıda ayarlayabilir ve ardından bunu takvim formatına dönüştürdüğünüzde endişelenmenize gerek kalmaz. Şimdiye kadar en iyi yöntem! getTimeZone için, ETD için Ör: "GMT-4: 00" biçimini kullanmanız gerekebilir
Michael Kern

12

Joda Time ile çözebilirsiniz :

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

Java 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"));

buna dikkat edin, ancak yan bağımlılık ve ekstra yapılandırma olmadan doğrudan uyumlu olmayan hazırda bekletme 4 kullanıyorsanız. Ancak 3. versiyon için en hızlı ve kullanımı en kolay yaklaşımdı.
Aubergine

8

Görünüşe göre TimeStamp'ınız kaynak sistemin saat dilimine ayarlanıyor.

Bu kullanımdan kaldırılmıştır, ancak çalışmalıdır:

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

Kullanımdan kaldırılmayan yol kullanmaktır

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

ancak bunun istemci tarafında yapılması gerekir, çünkü bu sistem hangi saat diliminde olduğunu bilir.


7

Bir zaman diliminden diğerine dönüştürme yöntemi (muhtemelen işe yarıyor :)).

/**
 * 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

Tarih ve Zaman Damgası nesneleri zaman diliminden habersizdir: o anın saat ve gün olarak belirli bir yorumlanmasına bağlı kalmadan, dönemden bu yana belirli bir saniye sayısını temsil ederler. Saat dilimleri , resmi yalnızca GregorianCalendar'a (bu görev için doğrudan gerekli değildir) ve ayrı alanlar ve Tarih (veya uzun ) değerler arasında dönüştürmek için bir saat dilimi farkına ihtiyaç duyan SimpleDateFormat'a girer .

OP'nin sorunu, işlemenin tam başlangıcındadır: kullanıcı, belirsiz olan saatleri girer ve bunlar GMT olmayan yerel saat diliminde yorumlanır; Bu noktada değerdir "06:12 EST" kolayca olarak basılabilir, "11.12 GMT" veya başka bir saat dilimine ama asla gidiyor değiştirmek için "6.12 GMT" .

Yapmak için bir yolu yoktur SimpleDateFormat olduğunu ayrıştırır "06:12" olarak "SS: MM" yerine UTC varsayılan (yerel saat dilimine varsaymak); SimpleDateFormat kendi iyiliği için biraz fazla akıllıdır.

Ancak, herhangi bir SimpleDateFormat örneğini girdiye açıkça koyarsanız doğru saat dilimini kullanmaya ikna edebilirsiniz : "06:12 GMT" yi şu şekilde ayrıştırmak için alınan (ve yeterince doğrulanmış) "06:12" ye sabit bir dize ekleyin . "HH: MM z" .

GregorianCalendar alanlarının açık bir şekilde ayarlanmasına veya saat dilimi ve gün ışığından yararlanma saati farklarının alınmasına ve kullanılmasına gerek yoktur .

Gerçek sorun, yerel saat dilimine varsayılan olan girdileri ayırmak, varsayılan olarak UTC'ye ayarlanan girdiler ve gerçekten kesin bir saat dilimi göstergesi gerektiren girdilerdir.


4

Geçmişte benim için işe yarayan bir şey, kullanıcının saat dilimi ile GMT arasındaki farkı (milisaniye cinsinden) belirlemekti. Farkı elde ettiğinizde, her iki saat diliminde de uygun zamanı elde etmek için ekleme / çıkarma yapabilirsiniz (dönüşümün hangi yöne gittiğine bağlı olarak). Bunu genellikle bir Takvim nesnesinin milisaniye alanını ayarlayarak başarırdım, ancak eminim bunu bir zaman damgası nesnesine kolayca uygulayabilirsiniz. Ofseti almak için kullandığım kod burada

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

timezoneId, kullanıcının saat diliminin kimliğidir (EST gibi).


9
Ham ofseti kullanarak DST'yi yok sayarsınız.
Werner Lehmann

1
Kesinlikle, bu yöntem yılın yarısında yanlış sonuçlar verecektir. Hatanın en kötü şekli.
Danubian Sailor

1

java.time

Modern yaklaşım, Java'nın en eski sürümleriyle birlikte gelen zahmetli eski tarih-saat sınıflarının yerini alan java.time sınıflarını kullanır .

java.sql.TimestampSınıfı, eski sınıfların biridir. Artık gerek yok. Bunun yerine Instantveya diğer java.time sınıflarını doğrudan veritabanınızla JDBC 4.2 ve sonraki sürümleri kullanarak kullanın.

InstantSınıfı zaman çizelgesinde bir anı temsil UTC çözünürlüğe sahip nanosaniye (ondalık kesir dokuz (9) haneye kadar).

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

Mevcut bir ile birlikte Timestampçalışmanız gerekiyorsa, eski sınıflara eklenen yeni dönüştürme yöntemleri aracılığıyla hemen java.time biçimine dönüştürün.

Instant instant = myTimestamp.toInstant() ;

Başka bir saat dilimine ayarlamak için, saat dilimini ZoneIdnesne olarak belirtin . Bir belirtme uygun bir zaman dilimi adı biçiminde continent/regiongibi America/Montreal, Africa/Casablancaya da Pacific/Auckland. Hiçbir zaman gibi 3-4 letter sözde dilimleri kullanılıyorsa ESTveya ISToldukları gibi değil , gerçek zaman dilimleri, değil standardize ve hatta benzersiz değil (!).

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

InstantBir ZonedDateTimenesne üretmek için uygulayın .

ZonedDateTime zdt = instant.atZone( z ) ;

Kullanıcıya gösterilmek üzere bir dize oluşturmak için, DateTimeFormatterbirçok tartışma ve örnek bulmak üzere Stack Overflow öğesini arayın .

Sorunuz gerçekten kullanıcı veri girişinden tarih-saat nesnelerine kadar diğer yöne gitmekle ilgilidir. Veri girişinizi tarih ve günün saati olmak üzere iki bölüme ayırmak genellikle en iyisidir.

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

Sorunuz net değil. Kullanıcı tarafından girilen tarih ve saati UTC olarak yorumlamak ister misiniz? Veya başka bir saat diliminde mi?

UTC'yi kastettiyseniz, UTC OffsetDateTimesabitini kullanarak bir uzaklık ile bir oluşturun ZoneOffset.UTC.

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

Başka bir saat dilimini kastettiyseniz, bir saat dilimi nesnesiyle, a ZoneId. Ama hangi saat dilimi? Varsayılan bir saat dilimi tespit edebilirsiniz. Veya kritikse, niyetinden emin olmak için kullanıcıyla onaylamanız gerekir.

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

Tanımı gereği her zaman UTC'de olan daha basit bir nesne elde etmek için bir Instant.

Instant instant = odt.toInstant() ;

…veya…

Instant instant = zdt.toInstant() ; 

Veritabanınıza gönderin.

myPreparedStatement.setObject( … , instant ) ;

Java.time hakkında

Java.time çerçevesi daha sonra Java 8 ve yerleşiktir. Bu sınıflar zahmetli eski yerini mirası gibi tarih-saat sınıfları java.util.Date, Calendar&SimpleDateFormat .

Şu anda bakım modunda olan Joda-Time projesi, java.time sınıflarına geçişi tavsiye ediyor .

Daha fazla bilgi edinmek için Oracle Eğitimi'ne bakın . Ve birçok örnek ve açıklama için Stack Overflow'da arama yapın. Spesifikasyon JSR 310'dur .

Java.time sınıflarını nereden edinebilirim?

ThreeTen-Ekstra proje ek sınıfları ile java.time uzanır. Bu proje, java.time uygulamasına gelecekteki olası eklemeler için kanıtlayıcı bir zemindir. Burada bazı yararlı sınıfları gibi bulabilir Interval, YearWeek, YearQuarter, ve daha .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.