ZonedDateTime Tarihe nasıl dönüştürülür?


109

Veritabanımda sunucudan bağımsız bir tarih saati ayarlamaya çalışıyorum ve bunu yapmak için en iyi uygulamanın bir UTC DateTime ayarlamak olduğuna inanıyorum. Benim db sunucum Cassandra ve Java için db sürücüsü sadece Tarih tipini anlıyor.

Kodumda şimdi UTC'yi ( ZonedDateTime.now(ZoneOffset.UTC)) almak için yeni Java 8 ZonedDateTime kullandığımı varsayarsak, bu ZonedDateTime örneğini "eski" Date sınıfına nasıl dönüştürebilirim?


Java'da yerleşik iki "Tarih" sınıfı vardır java.util.Dateve java.sql.Date.
Fesleğen Bourque

Yanıtlar:


178

ZonedDateTime'ı doğrudan Date ile kullanabileceğiniz bir anında dönüştürebilirsiniz.

Date.from(java.time.ZonedDateTime.now().toInstant());

32
Hayır, bölge sistem varsayılanınızdaki geçerli Tarih olacaktır.
İnce Soltani Dridi

7
@MilenKovachev Sorunuz bir anlam ifade etmiyor - bir Tarihin saat dilimi yoktur - yalnızca zaman içinde bir anı temsil eder.
assylias

1
@assylias Aslında, ifadeniz mantıklı değil. Zamanın saklandığı format bir saat dilimini ifade eder. Tarih UTC'ye dayalıdır, ne yazık ki, Java bazı aptalca şeyler yapar ve ona öyle davranmaz ve üstelik zamanı UTC yerine yerel TZ olarak kabul eder. Data, LocalDateTime, ZonedDateTime veri saklama şekli bir saat dilimini ifade eder. Kullanılma biçimleri, sanki TZ'ler yokmuş gibi, ki bu açıkça yanlıştır. java.util.Date, örtülü olarak JVM varsayılanının TZ'sine sahiptir. İnsanların ona farklı bir şey gibi davranması (bunun için java dokümanları da dahil!) Sadece kötü insanlardır.
David

6
@David uh no - a Date, dönemden beri geçen milisaniye sayısıdır - yani UTC ile ilgilidir. Bunu yazdırırsanız , varsayılan saat dilimi kullanılır, ancak Date sınıfı kullanıcının saat dilimi hakkında bilgi sahibi olmaz ... Örneğin, docs.oracle.com/javase/tutorial/datetime/iso/legacy adresindeki "eşleme" bölümüne bakın .html Ve LocalDateTime açıkça bir saat dilimine atıfta bulunmadan - bu kafa karıştırıcı olarak görülebilir ...
assylias

3
Cevabınız gereksiz yere içerir ZonedDateTime. java.time.InstantSınıf için doğrudan yerine geçer java.util.Datehem olsa UTC bir an temsil eden Instantkullanımlar nanosaniye yerine milisaniye daha ince çözünürlüğü. Date.from( Instant.now() )senin çözümün olmalıydı. Veya bu konuda, new Date()aynı etkiye sahip olan, geçerli anı UTC'de yakalamak.
Basil Bourque

72

tl; dr

java.util.Date.from(  // Transfer the moment in UTC, truncating any microseconds or nanoseconds to milliseconds.
    Instant.now() ;   // Capture current moment in UTC, with resolution as fine as nanoseconds.
)

Yukarıdaki kodun hiçbir anlamı olmamasına rağmen. Her ikisi java.util.Dateve Instanther zaman UTC'de, UTC'de bir anı temsil eder. Yukarıdaki kod şununla aynı etkiye sahiptir:

new java.util.Date()  // Capture current moment in UTC.

Burada kullanmanın bir faydası yok ZonedDateTime. Zaten bir hesabınız varsa ZonedDateTime, a'yı çıkararak UTC'ye ayarlayın Instant.

java.util.Date.from(             // Truncates any micros/nanos.
    myZonedDateTime.toInstant()  // Adjust to UTC. Same moment, same point on the timeline, different wall-clock time.
)

Diğer Cevap Doğru

Cevap tarafından ssoltanid doğru yeni bir okul java.time nesneyi (dönüştürmek için nasıl özel soru adresleri ZonedDateTimeeski bir okul için) java.util.Datenesne. InstantZonedDateTime öğesinden ayıklayın ve java.util.Date.from().

Veri kaybı

Eğer edeceği Not veri kaybına acı gibi Instantparçalar nanosaniye beri çağın ederken java.util.Dateizler milisaniye Dönemden beri.

milisaniye, mikrosaniye ve nanosaniye çözünürlüklerini karşılaştıran diyagram

Sorunuz ve yorumlarınız başka sorunları da beraberinde getiriyor.

Sunucuları UTC'de Tutun

Genel olarak en iyi uygulama olarak sunucularınızın ana işletim sistemi UTC'ye ayarlanmış olmalıdır. JVM, bildiğim Java uygulamalarında bu ana işletim sistemi ayarını varsayılan saat dilimi olarak alıyor.

Saat Dilimini Belirtin

Ancak hiçbir zaman JVM'nin geçerli varsayılan saat dilimine güvenmemelisiniz. Ana bilgisayar ayarını almak yerine, bir JVM başlatılırken geçirilen bir bayrak başka bir saat dilimi ayarlayabilir. Daha da kötüsü: Herhangi bir uygulamanın herhangi bir iş parçacığındaki herhangi bir kod java.util.TimeZone::setDefault, çalışma zamanında bu varsayılanı değiştirmek için bir çağrı yapabilir !

Cassandra TimestampTürü

Herhangi bir düzgün veritabanı ve sürücü, depolama için geçirilen bir tarih-saati UTC'ye otomatik olarak ayarlamalıdır. Cassandra kullanmıyorum, ancak tarih saati için bazı temel destekleri var gibi görünüyor. Belgeler, Timestamptürünün aynı dönemden milisaniye sayısı olduğunu söylüyor (UTC'de 1970'in ilk anı).

ISO 8601

Ayrıca Cassandra, ISO 8601 standart formatlarında dizi girişlerini kabul eder . Neyse ki, java.time dizeleri ayrıştırmak / oluşturmak için varsayılan olarak ISO 8601 biçimlerini kullanır. InstantSınıfının toStringuygulaması güzel yapacağız.

Hassasiyet: Milisaniye - Nanosecord

Ama önce ZonedDateTime'ın nanosaniye hassasiyetini milisaniyeye indirmemiz gerekiyor. Bunun bir yolu, milisaniyeleri kullanarak yeni bir Anında Arama oluşturmaktır. Neyse ki, java.time, milisaniyeye ve milisaniyeden dönüştürme için bazı kullanışlı yöntemlere sahiptir.

Örnek Kod

İşte Java 8 Güncellemesi 60'daki bazı örnek kod.

ZonedDateTime zdt = ZonedDateTime.now( ZoneId.of( "America/Montreal" ) );
…
Instant instant = zdt.toInstant();
Instant instantTruncatedToMilliseconds = Instant.ofEpochMilli( instant.toEpochMilli() );
String fodderForCassandra = instantTruncatedToMilliseconds.toString();  // Example: 2015-08-18T06:36:40.321Z

Veya bu Cassandra Java sürücü belgesine göre , bir java.util.Dateörneği geçirebilirsiniz (karıştırılmamalıdır java.sqlDate). Böylece instantTruncatedToMillisecondsyukarıdaki koddan bir juDate yapabilirsiniz .

java.util.Date dateForCassandra = java.util.Date.from( instantTruncatedToMilliseconds );

Bunu sık sık yapıyorsanız, tek astar yapabilirsiniz.

java.util.Date dateForCassandra = java.util.Date.from( zdt.toInstant() );

Ancak küçük bir fayda yöntemi oluşturmak daha iyi olurdu.

static public java.util.Date toJavaUtilDateFromZonedDateTime ( ZonedDateTime zdt ) {
    Instant instant = zdt.toInstant();
    // Data-loss, going from nanosecond resolution to milliseconds.
    java.util.Date utilDate = java.util.Date.from( instant ) ;
    return utilDate;
}

Sorudakinden farklı olarak tüm bu koddaki farklılığa dikkat edin. Sorunun kodu, ZonedDateTime örneğinin saat dilimini UTC'ye ayarlamaya çalışıyordu. Ancak bu gerekli değildir. Kavramsal olarak:

ZonedDateTime = Instant + ZoneId

Zaten UTC'de olan Anında bölümünü çıkarıyoruz (temelde UTC'de, kesin ayrıntılar için sınıf belgesini okuyun).


Hem modern hem de eski Java'daki tarih-saat türleri tablosu


Hakkında java.time

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 .

Sen değiştirebilir java.time sizin veritabanı ile doğrudan nesneleri. JDBC 4.2 veya üstü ile uyumlu bir JDBC sürücüsü kullanın . Dizelere gerek yok, derslere gerek yok .java.sql.*

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

Java veya Android'in hangi sürümüyle hangi java.time kitaplığının kullanılacağı tablosu

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 .


Harika bir açıklama, çok detaylı. Çok teşekkür ederim!
Gianmarco F.

5

Android için ThreeTen arka portu kullanıyorsanız ve yenisini kullanamıyorsanız Date.from(Instant instant)(minimum API 26 gerektirir) şunları kullanabilirsiniz:

ZonedDateTime zdt = ZonedDateTime.now();
Date date = new Date(zdt.toInstant().toEpochMilli());

veya:

Date date = DateTimeUtils.toDate(zdt.toInstant());

Lütfen Basil Bourque'un cevabındaki tavsiyeleri de okuyun


1
ThreeTen Backport (ve ThreeTenABP) DateTimeUtils, dönüştürme yöntemlerine sahip bir sınıf içerir , bu nedenle Date date = DateTimeUtils.toDate (zdt.toInstant ()); `. O kadar düşük seviyeli değil.
Ole VV

4

İşte mevcut sistem saatini UTC'ye dönüştüren bir örnek. ZonedDateTime'ı bir Dize olarak biçimlendirmeyi içerir ve ardından String nesnesi java.text DateFormat kullanılarak bir tarih nesnesine ayrıştırılır.

    ZonedDateTime zdt = ZonedDateTime.now(ZoneOffset.UTC);
    final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss");
    final DateFormat FORMATTER_YYYYMMDD_HH_MM_SS = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
    String dateStr = zdt.format(DATETIME_FORMATTER);

    Date utcDate = null;
    try {
        utcDate = FORMATTER_YYYYMMDD_HH_MM_SS.parse(dateStr);
    }catch (ParseException ex){
        ex.printStackTrace();
    }

2

Kabul edilen cevap benim için işe yaramadı. Döndürülen Tarih her zaman yerel Tarih olup, orijinal Saat Dilimi için Tarih değildir. UTC + 2'de yaşıyorum.

//This did not work for me
Date.from(java.time.ZonedDateTime.now().toInstant()); 

ZonedDateTime'dan doğru Tarihi almanın iki alternatif yolunu buldum.

Hawaii için bu ZonedDateTime'a sahip olduğunuzu varsayalım

LocalDateTime ldt = LocalDateTime.now();
ZonedDateTime zdt = ldt.atZone(ZoneId.of("US/Hawaii"); // UTC-10

veya başlangıçta sorulduğu gibi UTC için

Instant zulu = Instant.now(); // GMT, UTC+0
ZonedDateTime zdt = zulu.atZone(ZoneId.of("UTC"));

Alternatif 1

Java.sql.Timestamp kullanabiliriz. Basittir, ancak muhtemelen programlama bütünlüğünüzde de bir engel oluşturacaktır.

Date date1 = Timestamp.valueOf(zdt.toLocalDateTime());

Alternatif 2

Tarihi milis'ten oluşturuyoruz ( daha önce burada cevaplandı ). Yerel ZoneOffset'in bir zorunluluk olduğunu unutmayın.

ZoneOffset localOffset = ZoneOffset.systemDefault().getRules().getOffset(LocalDateTime.now());
long zonedMillis = 1000L * zdt.toLocalDateTime().toEpochSecond(localOffset) + zdt.toLocalDateTime().getNano() / 1000000L;
Date date2 = new Date(zonedMillis);

1

Bunu, Java 8 ve sonraki sürümlerde yerleşik olan java.time sınıflarını kullanarak yapabilirsiniz .

ZonedDateTime temporal = ...
long epochSecond = temporal.getLong(INSTANT_SECONDS);
int nanoOfSecond = temporal.get(NANO_OF_SECOND);
Date date = new Date(epochSecond * 1000 + nanoOfSecond / 1000000);

Pardon, zamansal ve tüm bu sabitler nereden geliyor?
Milen Kovachev

@MilenKovachev A ZonedDateTime bir Temporal örneğidir
Peter Lawrey

Teşekkürler, ama cevabını yorumumun aptalca görünmemesi için düzenlediğini söylemeliydin.
Milen Kovachev

2
@MilenKovachev bir yorumu silebilirsiniz, ancak bu aptalca bir soru değil.
Peter Lawrey

1
Bu cevaba neden olumsuz oy verildiğini anlayamıyorum. Instantsaniye ve nanosaniye izleme. Dates milisaniye izleyin. Birinden diğerine bu dönüşüm doğrudur.
scottb

1

Bunu kullanıyorum.

public class TimeTools {

    public static Date getTaipeiNowDate() {
        Instant now = Instant.now();
        ZoneId zoneId = ZoneId.of("Asia/Taipei");
        ZonedDateTime dateAndTimeInTai = ZonedDateTime.ofInstant(now, zoneId);
        try {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateAndTimeInTai.toString().substring(0, 19).replace("T", " "));
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }
}

Çünkü Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant()); işe yaramıyor !!! Uygulamanızı bilgisayarınızda çalıştırırsanız sorun olmaz. Ancak AWS'nin veya Docker'ın veya GCP'nin herhangi bir bölgesinde çalıştırırsanız sorun yaratacaktır. Çünkü bilgisayar, Cloud'daki saat diliminiz değil. Kodda saat diliminizi doğru ayarlamalısınız. Örneğin, Asya / Taipei. Ardından AWS veya Docker veya GCP'de düzelecektir.

public class App {
    public static void main(String[] args) {
        Instant now = Instant.now();
        ZoneId zoneId = ZoneId.of("Australia/Sydney");
        ZonedDateTime dateAndTimeInLA = ZonedDateTime.ofInstant(now, zoneId);
        try {
            Date ans = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateAndTimeInLA.toString().substring(0, 19).replace("T", " "));
            System.out.println("ans="+ans);
        } catch (ParseException e) {
        }
        Date wrongAns = Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant());
        System.out.println("wrongAns="+wrongAns);
    }
}

1
Bu, 7 cevaptan herhangi birinde en karmaşık yollardan biri gibi görünüyor. Herhangi bir avantajı var mı? Sanmıyorum. Biçimlendirme ve ayrıştırma işlemlerine gerçekten gerek yok.
Ole VV

Teşekkürler soruyorsun. Çünkü Date wrongAns = Date.from (java.time.ZonedDateTime.ofInstant (now, zoneId) .toInstant ()) ;; Çalışmıyor !!!!!
beehuang

Size saniye pasajını saat dilimimde 17: 08'den hemen önce çalıştırdım ve aldım ans=Mon Jun 18 01:07:56 CEST 2018, bu yanlış ve sonra wrongAns=Sun Jun 17 17:07:56 CEST 2018doğru.
Ole VV

Bilgisayarımda uygulamayı çalıştırdığımda herhangi bir sorun yaşamıyorum. Ancak docker kullanıldığında bir sorun var. Çünkü docker'daki saat dilimi, bilgisayardaki saat diliminiz değildir. Kodda saat diliminizi doğru ayarlamalısınız. Örneğin, Asya / Taipei. Ardından AWS, Docker, GCP veya herhangi bir bilgisayarda düzelecektir.
beehuang

@ OleV.V. Anlayabiliyor musun? veya herhangi bir sorunuz var mı?
beehuang

0

Beehuang'ın söylediği gibi bir docker uygulaması için saat diliminizi ayarlamalısınız.

Alternatif olarak, ZoneSameLocal ile kullanabilirsiniz . Örneğin:

2014-07-01T00: 00 + 02: 00 [GMT + 02: 00],

Date.from(zonedDateTime.withZoneSameLocal(ZoneId.systemDefault()).toInstant())

için 1 Temmuz 00:00:00 CEST 2014 Sal tarafından

Date.from(zonedDateTime.toInstant())

için Mon 30 Haziran 22:00:00 UTC 2014


-1

Yalnızca şu anda ilgileniyorsanız, şunu kullanın:

Date d = new Date();

Şu anda ilgileniyorum ama şimdi UTC.
Milen Kovachev

2
Evet, şimdi UTC olacak, Tarih daha iyi bilmiyor.
Jacob Eckel

3
Hayır, olmayacak. Şimdi yerel sistem saat diliminde olacaktır. Tarih herhangi bir saat dilimi bilgisini saklamaz, ancak mevcut sistem saatini kullanır ve mevcut sistem saat dilimini yok sayar, bu nedenle onu asla UTC'ye geri
David

2
@David Date, zamanı UTC dönemine göre tutar, bu nedenle ABD'deki bir sistemden yeni bir Date () ve Japonya'daki bir bilgisayardan başka bir nesne alırsanız, bunlar aynı olacaktır (her ikisinin de içeride tuttuğu süreye bakın).
Jacob Eckel

1
@JacobEckel - Evet, ancak tz’niz US tz iken bir tarihe sabah 9’u koyarsanız ve sonra bir JP tz’ye değiştirirseniz ve sabah 9’da yeni bir tarih oluşturursanız, Tarih’in dahili değeri farklı olacaktır. DST'yi karışıma attığınız anda, uygulamanız HER ZAMAN özel olarak UTC'de çalışmadığı sürece Date'i güvenilir bir şekilde kullanamazsınız;
David
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.