Mam wartość datownika pochodzącą z mojej aplikacji. Użytkownik może znajdować się w dowolnej lokalnej strefie czasowej.
Ponieważ ta data jest używana dla usługi sieciowej, która zakłada, że podany czas jest zawsze w GMT, muszę przekonwertować parametr użytkownika z powiedzmy (EST) na (GMT). Oto kicker: użytkownik jest nieświadomy swojego TZ. Wpisuje datę utworzenia, którą chce wysłać do WS, więc potrzebuję:
Użytkownik wprowadza: 5/1/2008 6:12 PM (EST)
Parametr do WS musi być : 5/1/2008 6:12 PM (GMT)
Wiem, że znaczniki czasu powinny być zawsze domyślnie ustawione w GMT, ale podczas wysyłania parametru, mimo że utworzyłem swój kalendarz z TS (który powinien być w GMT), godziny są zawsze wyłączone, chyba że użytkownik jest w GMT. czego mi brakuje?
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;
}
W przypadku poprzedniego kodu w rezultacie otrzymuję (krótki format ułatwiający czytanie):
[1 maja 2008 r. 23:12]
Odpowiedzi:
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; }
Oto wynik, jeśli przekażę bieżący czas („12:09:05 EDT” od
Calendar.getInstance()
) w:12:09:05 GMT to 8:09:05 EDT.
Mylące jest to, że
Calendar.getTime()
zwraca ci toDate
w bieżącej strefie czasowej, a także, że nie ma metody na modyfikację strefy czasowej kalendarza i wyrzucenie podstawowej daty. W zależności od typu parametru pobieranego przez twoją usługę internetową, możesz po prostu chcieć, aby umowa WS była wyrażona w milisekundach od epoki.źródło
offsetFromUTC
zamiast go dodawać? Używając twojego przykładu, jeśli 12:09 GMT to 8:09 EDT (co jest prawdą), a użytkownik wprowadzi „12:09 EDT”, algorytm powinien wypisać „16:09 GMT”, moim zdaniem.Dziękuję wszystkim za odpowiedź. Po dalszym dochodzeniu znalazłem właściwą odpowiedź. Jak wspomniał Skip Head, znacznik czasu, który otrzymywałem z mojej aplikacji, był dostosowywany do strefy czasowej użytkownika. Więc jeśli użytkownik wprowadzi 18:12 (EST), otrzymam 14:12 (GMT). Potrzebowałem sposobu na cofnięcie konwersji tak, aby czas wprowadzony przez użytkownika był czasem wysłania do żądania WebServer. Oto jak to osiągnąłem:
// 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()));
Kod wyjściowy to: (Użytkownik wprowadzony 01.05.2008 o 18:12 (EST)
Strefa czasowa bieżącego użytkownika: EST
Bieżące przesunięcie względem GMT (w godzinach): - 4 (zwykle -5, z wyjątkiem korekty na czas letni)
TS z ACP: 2008-05-01 14: 12: 00.0
Data kalendarza przekonwertowana z TS przy użyciu GMT i US_EN Locale : 01.05.08 18:12 (GMT)
źródło
Mówisz, że data jest używana w połączeniu z usługami internetowymi, więc zakładam, że w pewnym momencie jest ona serializowana do łańcucha.
W takim przypadku należy przyjrzeć się metodzie setTimeZone klasy DateFormat. Określa, która strefa czasowa zostanie użyta podczas drukowania znacznika czasu.
Prosty przykład:
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());
źródło
Możesz go rozwiązać za pomocą Joda Time :
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"));
źródło
Wygląda na to, że Twój znacznik czasu jest ustawiany na strefę czasową systemu, z którego pochodzi.
To jest przestarzałe, ale powinno działać:
Nieaktualnym sposobem jest użycie
Calendar.get(Calendar.ZONE_OFFSET) + Calendar.get(Calendar.DST_OFFSET)) / (60 * 1000)
ale trzeba by to zrobić po stronie klienta, ponieważ ten system wie, w jakiej strefie czasowej się znajduje.
źródło
Metoda konwersji z jednej strefy czasowej na inną (prawdopodobnie działa :)).
/** * 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; }
źródło
Obiekty Date i Timestamp nie zwracają uwagi na strefę czasową: reprezentują określoną liczbę sekund od epoki, bez konieczności określonej interpretacji tej chwili jako godzin i dni. Strefy czasowe wprowadzają obraz tylko w GregorianCalendar (nie jest to bezpośrednio potrzebne do tego zadania) i SimpleDateFormat , które wymagają przesunięcia strefy czasowej, aby przekonwertować oddzielne pola i wartości typu Date (lub długie ).
Problem OP jest już na początku jego przetwarzania: użytkownik wprowadza godziny, które są niejednoznaczne i są interpretowane w lokalnej strefie czasowej innej niż GMT; w tym momencie wartość to „6:12 EST” , którą można łatwo wydrukować jako „11.12 GMT” lub inną strefę czasową, ale nigdy nie zmieni się na „6.12 GMT” .
Nie ma sposobu, aby SimpleDateFormat analizował „06:12” jako „HH: MM” (domyślnie lokalna strefa czasowa) jako domyślny czas UTC; SimpleDateFormat jest trochę zbyt inteligentny dla własnego dobra.
Możesz jednak przekonać każdego instancję SimpleDateFormat do korzystania z właściwej strefy czasowej, jeśli umieścisz ją wyraźnie w danych wejściowych: po prostu dodaj ustalony ciąg do odebranego (i odpowiednio zweryfikowanego) „06:12”, aby przeanalizować „06:12 GMT” jako „HH: MM z” .
Nie ma potrzeby bezpośredniego ustawiania pól GregorianCalendar ani pobierania i używania stref czasowych i przesunięć czasu letniego.
Prawdziwym problemem jest segregacja wejść, które domyślnie dotyczą lokalnej strefy czasowej, wejść domyślnych dla UTC i wejść, które naprawdę wymagają wyraźnego wskazania strefy czasowej.
źródło
Coś, co działało dla mnie w przeszłości, polegało na określeniu przesunięcia (w milisekundach) między strefą czasową użytkownika a GMT. Gdy już masz przesunięcie, możesz po prostu dodać / odjąć (w zależności od tego, w jaki sposób przebiega konwersja), aby uzyskać odpowiedni czas w obu strefach czasowych. Zwykle robiłbym to, ustawiając pole milisekund obiektu kalendarza, ale jestem pewien, że można go łatwo zastosować do obiektu znacznika czasu. Oto kod, którego używam, aby uzyskać przesunięcie
int offset = TimeZone.getTimeZone(timezoneId).getRawOffset();
timezoneId to identyfikator strefy czasowej użytkownika (na przykład EST).
źródło
java.time
Nowoczesne podejście wykorzystuje klasy java.time , które wyparły kłopotliwe starsze klasy daty i czasu, zawarte w najwcześniejszych wersjach Javy.
java.sql.Timestamp
Klasa jest jedną z tych klas starszych. Nie jest już potrzebne. Zamiast tego użyjInstant
lub innych klas java.time bezpośrednio z bazą danych przy użyciu JDBC 4.2 i nowszych.Instant
Klasa reprezentuje moment na osi czasu w UTC z rozdzielczością nanosekund (do dziewięciu (9) cyfr ułamka dziesiętnego).Jeśli musisz współdziałać z istniejącą
Timestamp
, natychmiast przekonwertuj ją na java.time za pomocą nowych metod konwersji dodanych do starych klas.Aby dostosować się do innej strefy czasowej, określ strefę czasową jako
ZoneId
obiekt. Określ prawidłową nazwę strefy czasowej w formaciecontinent/region
, takie jakAmerica/Montreal
,Africa/Casablanca
lubPacific/Auckland
. Nigdy nie używaj 3-4-literowych pseudostref, takich jakEST
lubIST
ponieważ nie są one prawdziwymi strefami czasowymi, nie znormalizowane, a nawet nie są unikalne (!).ZoneId z = ZoneId.of( "America/Montreal" ) ;
Zastosuj do,
Instant
aby utworzyćZonedDateTime
obiekt.Aby wygenerować ciąg do wyświetlenia użytkownikowi, przeszukaj Stack Overflow,
DateTimeFormatter
aby znaleźć wiele dyskusji i przykładów.Twoje pytanie tak naprawdę dotyczy pójścia w innym kierunku, od wprowadzania danych użytkownika do obiektów daty i czasu. Ogólnie najlepiej jest podzielić wprowadzanie danych na dwie części, datę i godzinę.
LocalDate ld = LocalDate.parse( dateInput , DateTimeFormatter.ofPattern( "M/d/uuuu" , Locale.US ) ) ; LocalTime lt = LocalTime.parse( timeInput , DateTimeFormatter.ofPattern( "H:m a" , Locale.US ) ) ;
Twoje pytanie nie jest jasne. Czy chcesz interpretować datę i godzinę wprowadzoną przez użytkownika jako czas UTC? Albo w innej strefie czasowej?
Jeśli masz na myśli UTC, stworzyć
OffsetDateTime
z przesunięciem za pomocą stałej za UTCZoneOffset.UTC
.Jeśli masz na myśli inną strefę czasową, połącz razem z obiektem strefy czasowej, a
ZoneId
. Ale która strefa czasowa? Możesz wykryć domyślną strefę czasową. Lub, jeśli jest to krytyczne, musisz potwierdzić z użytkownikiem, aby mieć pewność co do jego zamiaru.Aby uzyskać prostszy obiekt, który z definicji zawsze znajduje się w UTC, wyodrębnij plik
Instant
.…lub…
Wyślij do swojej bazy danych.
Informacje o java.time
Struktura java.time jest wbudowana w Javę 8 i nowsze. Klasy te kłopotliwe zastąpić stary starszych klas Date-Time, takich jak
java.util.Date
,Calendar
, iSimpleDateFormat
.Projekt Joda-Time , obecnie w trybie konserwacji , zaleca migrację do klas java.time .
Aby dowiedzieć się więcej, zobacz samouczek Oracle . I przeszukaj Stack Overflow, aby znaleźć wiele przykładów i wyjaśnień. Specyfikacja to JSR 310 .
Skąd wziąć klasy java.time?
Projekt ThreeTen-Extra rozszerza java.time o dodatkowe klasy. Ten projekt jest poligonem doświadczalnym dla ewentualnych przyszłych dodatków do java.time. Można znaleźć kilka przydatnych klas tutaj takie jak
Interval
,YearWeek
,YearQuarter
, i więcej .źródło