Edycja czasu w wybranej strefie czasowej

Odpowiedz Nowy wątek
2019-09-03 15:16
0

Witam,
od jakiegoś czasu tworzę projekt w Symfony 4 do mojego portfolio. Jednym z zadań realizowanych na potrzeby tego projektu jest zapisywanie danych odnośnie wykonywanych ćwiczeń. W związku z tym, iż do zapisywania czasu trwania ćwiczenia wykorzystuję pole typu Time, a następnie za pomocą metody getTimestamp() uzyskuję liczbę sekund z podanego przez użytkownika czasu ćwiczenia potrzebną by obliczyć spalone kalorie zdecydowałem się używać strefy czasowej UTC( tak aby metoda getTimestamp() zawsze zwracała odpowiednią ilość sekund). Efekt ten uzyskałem za pomocą ustawienia strefy czasowej w formie na UTC:

         ->add('duration', TimeType::class, [
                'input'  => 'datetime',
                'widget' => 'choice',
                'model_timezone' => 'UTC',//because use timestamp
                'view_timezone' => 'UTC',
                'placeholder' => [
                    'hour' => 'Hour', 'minute' => 'Minute'
                ]
            ])

I wszystko działało ok (mam na myśli dodawanie i edycję ćwiczenia za pomocą ajaxa wraz z obliczaniem wszystkich danych) i już miałem przechodzić do realizacji kolejnego zadania, gdy zauważyłem że gdy próbuję edytować ćwiczenie, które np. trwało 1 godzinę i 30 minut zmniejszając czas o dokładnie godzinę edycja odbywa się niby poprawnie (walidacja forma przechodzi) ale zapisany wynik to nadal 1 godzina 30 minut. Zdziwiłem się gdyż kiedy edytuje za pomocą tego samego pola i wartość będzie niższa o 2,3,4 itd godzin to wszystko jest ok. Problemem jest zmniejszenie czasu tylko o godzinę w dół. Poszperałem trochę by zobaczyć na czym polega problem i gdzie dane są fałszowane. Oto kod realizacji zadania wraz z outputem z poszczególnych dumpów:

/**
     * @Route("/api/workout/edit/{id}", name="workout_edit", methods={"PUT"})
     */
    public function edit(Workout $workout, Request $request, EntityManagerInterface $em)
    {
        $data = json_decode($request->getContent(), true);
        dump($data);
// tutaj w tablicy z czasem trwania ćwiczenia otrzymujemy:
/*
  "duration" => array:2 [▼
    "hour" => "0"
    "minute" => "1"
  ]
*/ 

        if($data === null)
        {
            throw new BadRequestHttpException('Invalid Json');    
        }

        $form = $this->createForm(WorkoutFormType::class, $workout,
            ['csrf_protection' => false]);
        $form->submit($data);

        if (!$form->isValid()) {
            $errors = $this->getErrorsFromForm($form);

            return $this->json(
            $errors,
            400
            );
        }
        dump($form->getData());
/*
Tutaj dump przy zmianie pola na jakąkolwiek wartość mniejszą o więcej niż godzinę lub każdą większą zwraca:
-duration: DateTime @3660 {#932 ▼
    date: 1970-01-01 01:01:00.0 UTC (+00:00)
  }
natomiast gdy pole godzin zmniejszy się (w godzinach) dokładnie o 1 to wyrzuca takie coś
-duration: DateTime @60 {#655 ▼
    date: 1970-01-01 01:01:00.0 Europe/Berlin (+01:00)
  }
*/

        $workout = $form->getData();
        dump($workout);
// tu tak jak wyżej

        $burnoutEnergy = $this->calculateBurnoutEnergy($workout);
        $workout->setBurnoutEnergy($burnoutEnergy);

        $em->persist($workout);
        $em->flush();

        $response = new Response(null, 201);

        $response->headers->set(
            'Location',
            $this->generateUrl('workout_get', ['id' => $workout->getId()])
        );

        return $response;
    }

Dzięki temu wiem, że problem polega na tym, iż gdy zmniejszę ilość godzin dokładnie o 1 to nagle form tak jakby zmieniał ustawioną przeze mnie strefę czasową na Europe/Berlin (+01:00) i przez to dane z podane z formularza zapisują się do bazy danych jako o 1 godzinę wyższe. Tylko nadal nie mam pojęcia dlaczego dla akurat takiej zmiany strefa czasowa ulega zmianie i jak to poprawić. Może ktoś z was już spotkał się z takim błędem i wie jak to rozwiązać. Dzięki wielkie za wszelką pomoc.

edytowany 1x, ostatnio: kris016, 2019-09-03 15:20

Pozostało 580 znaków

2019-09-03 17:06
1

1) mniej zapisane w profilu usera jego wybraną strefe czasową
2) Przy wyświetlaniu konwertuj zapisany czas na jego strefę czasową
3) Przy zapisie konwertuj czas na utc

Pozostało 580 znaków

2019-09-03 18:36
0
mr_jaro napisał(a):

1) mniej zapisane w profilu usera jego wybraną strefe czasową
2) Przy wyświetlaniu konwertuj zapisany czas na jego strefę czasową
3) Przy zapisie konwertuj czas na utc

tylko co to zmieni jak ten czas, który podaje user to tylko czas przez jaki wykonywał daną czynność np. biegałem przez 1 godzinę to wpisuję 1:00 w inputa i mój input to zapisuje jako UTC żeby potem móc w prosty sposób zamienić ten format zapisany w bazie na sekundy (getTimestamp()) i tak jak mówię przy każdej możliwej operacji wszystko odbywa się na UTC tylko przy zapisie czasu trwania o 1 mniejszego w godzinach nagle form coś odwala i zamienia na inną strefę czasową, a jeśli np dałem że biegałem przez czas 10:00 i zmienię na 0:30 to jest ok, jak zmienię z 10:00 na 08:00 też jest ok tylko jak zmienię z 10:00 na 09:00 to nagle zmienia na Europe/Berlin (+01:00) dzięki czemu czas z zmienionej 09:00 automatycznie w bazie zapisuje się jako 10:00 czyli taki jak był wcześniej. Już szukam wszędzie czego to może być wina, że tak się dzieje i nadal bez skutku. Mam też normalnego forma w panelu admina (tzn bez ajaxa) i widzę że on też to samo odwala. Jeśli zaś wykasuje z forma pola odpowiedzialne za utrzymywanie wszystkiego w strefie UTC:

  'model_timezone' => 'UTC',//because use timestamp
  'view_timezone' => 'UTC', 

edycja już działa w każdym przypadku poprawnie lecz znowu funkcja timestamp wariuje bo otrzymuje dane w strefie Europe/Berlin (+01:00) i musiałbym właśnie pobierać timezone i odpowiednio do niego dodawać do pobranego przez getTimestamp() czasu odpowiednią ilość sekund w zależności od strefy co według mnie jest ostatecznością, bo to takie kombinowanie na około, żeby tylko wynik się zgadzał. poza tym chciałbym zrozumieć jakim cudem przy narzuconej strefie czasowej na UTC form nagle sam sobie decyduje że jednak będzie inna ze względu na to co było wcześniej w bazie (tak jakby DataTransformer odczytywał poprzednią wartość w bazie i jak jest mniejsza o 1 godzinę to zmieniał strefę czasową przyjmowanych danych). Dla mnie takie zachowanie jest zupełnie bez sensu...

Pozostało 580 znaków

2019-09-03 19:20
1

biegałem przez 1 godzinę to wpisuję 1:00 w inputa i mój input to zapisuje jako UTC

i już tu popełniasz błąd, nie nie i jeszcze raz nie, pomnóż to sobie manualnie uzyskaj sekundy i je zapisz, nie mieszaj do tego czasów

I chyba tak będę musiał zrobić. Ironiczne jest to, że na początku o tym myślałem żeby po prostu brać tego stringa z inputa i go ciąć na minuty i godziny i wymnażać, ale stwierdziłem że skoro jest taka fajna metoda która od razu zwróci to co chcę tylko wystarczy operować na jednej strefie czasowej to super, czemu nie:D Cóż następnym razem będę już wiedział że tak jest lepiej, ale ten kod może jeszcze uda się odratować tylko muszę znaleźć sposób by encja też to widziała jako UTC a nie defaultowy ;) - kris016 2019-09-03 19:28

Pozostało 580 znaków

2019-09-03 19:21
0

Chyba wiem o co chodzi, poniekąd sam sobie odpowiedziałem na problem w poście wyżej. Zaintrygowało mnie to że błąd się pojawia w zależności od tego jaka jest różnica pomiędzy tym co jest w bazie a tym co mu podaję. Problem jest w tym, że rzeczywiście input przechowuje te dane w UTC ale jak zdumpowałem obiekt $workout przed jakąkolwiek edycją to w obiekcie te dane są już zapisane w strefie Europe/Berlin(+01:00) i tutaj na logikę idąc jeśli utworzę encję w czasem równym 01:00 i on pobierze potem przy edycji że czas tej aktywności jest 01:00 ale już w strefie Europe/Berlin(+01:00) to dla niego wartość, która podczas edycji przychodzi z forma i jest równa 00:00 UTC jest taka sama jak 01:00 Europe/Berlin więc zostawia to co było. Teraz tylko kwestia jak zmienić domyślnie zapisywaną strefę czasową w polu encji bo takie coś nie działa poprawnie:

 public function getDuration(): ?\DateTimeInterface
    {
        $this->duration->setTimeZone(new \DateTimeZone('UTC'));
        return $this->duration;
    }

    public function setDuration(\DateTimeInterface $duration): self
    {
        $duration->setTimeZone(new \DateTimeZone('UTC'));
        $this->duration = $duration;

        return $this;
    }

W bazie danych dla tego pola też nie bardzo widzę takiej opcji.

serek napisał(a):
kris016 napisał(a):

tylko co to zmieni jak ten czas, który podaje user to tylko czas przez jaki wykonywał daną czynność np. biegałem przez 1 godzinę to wpisuję 1:00 w inputa i mój input to zapisuje jako UTC

Skoro to nie jest data to zapisuj to jako time (limit do 839h chyba) albo int (sekundy).

To jest typ time:

 * @ORM\Column(type="time")
     * @AcmeAssert\NotZeroDuration()
     * @Groups({"main", "input"})
     */
    private $duration;

No chyba że chodziło ci o timestamp?

edytowany 1x, ostatnio: kris016, 2019-09-03 19:23

Pozostało 580 znaków

2019-09-03 19:23
0

Przy takich zabawach gdzie mieszasz strefy czasowe dojdzie ci jeszcze jeden ciekawy błąd, który często wychodzi bo tak jak mówię robisz to źle, czyli zmiana czasu letni/zimowy

Tak jak wcześniej pisałem w zamiarze nie było wcale mieszania stref czasowych tylko wszystko UTC (wpisujesz w UTC, przechowujesz w UTC) teraz właśnie wyszło że symfony i owszem przyjmuje w UTC i może nawet zapisuje ale potem przy pobraniu już pobiera dokładnie tą samą godzinę co w UTC z tą różnicą że w strefie Europe/Berlin, co wcale nie było zamierzone, a wręcz przeciwnie tak nie powinno być - kris016 2019-09-03 19:31
@kris016 nie wiem jak jest w symfony, ja robie wszystko w laravelu, tam mam carbona i nie ma żadnych problemów z czasem - mr_jaro 2019-09-03 19:37
W symfony właśnie znalazłem, na oficjalnej stronie: 'The system timezone is set to UTC by default' więc to się staje jeszcze ciekawsze :D:D poszperam jeszcze trochę nad tym może znajdę jak to zmienić, a jak nie to zrobię tak jak mówicie z inputa przekonwertować to na inta zawierającego liczbę sekund i tyle bo nie będę walczył z wiatrakami... - kris016 2019-09-03 19:58
Problem został rozwiązany (tak jak podejrzewałem chodziło o to że doctrine zapisywało to jako inną strefę czasową). Póki co ustawiłem strefę na UTC, jednakże jak skończę to co jeszcze mam w planach to przerobię to szybko na ten sposób z pobieraniem i obliczaniem sekund bezpośrednio z godzin i minut aktywności. Dzięki wielkie za pomoc ;) - kris016 2019-09-04 14:55

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0