Date und Time Problem

Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

  • Date und Time Problem

    Hallo,

    ich möchte gerne Werte aus eine Coredata Datenbank ziehen, die zwischen 2 Daten liegen.

    Bildschirmfoto 2024-03-05 um 07.19.33.png

    In dem Fall alles zwischem den 1. und dem letzten Tag im gewählten Monat. Wie man sehen kann baue ich mit einen Datum String zusammen der eindeutig den 1. des Monatsb repräsentieren sollte. Sobald dieser String aber in ein Date verwandelt wird, ist es der letzte des letzten Monats um 23 Uhr UTC. Das blöde ist, das der CoreData Fetch dann auch den, in diesem Fall 29.2, nimmt statt dem 01.03.

    Wie kann ich das Problem denn umgehen?

    Grüße

    Dir
  • Welches Problem? Wenn Du den aktuellen Kalender / die aktuelle Zeitzone (CET = UTC+1) nutzt, dann ist 00:00 des Monatsersten (CET) doch 23:00 des vorigen Monatsletzten UTC. Oder stehe ich jetzt auf'm Schlauch?

    Aber iIch verstehe Deinen Umweg über die Strings nicht ganz - kann das Ganze im Screenshot aber auch nur rudimentär erkennen: Warum nutzt Du kein Code-Tag? Ich habe bei einem ähnlichen Problem mir Start- und Ende-Datum rein über die DateComponents des aktuellen Kalenders zusammengebaut, hier in Objective-C:

    Quellcode

    1. - (NSDate *)startOfDay:(NSDate *)date
    2. {
    3. if (!date) return nil;
    4. NSCalendar *calendar = [NSCalendar currentCalendar];
    5. NSCalendarUnit calendarUnits = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay;
    6. NSDateComponents *components = [calendar components:calendarUnits fromDate:date];
    7. components.hour = 0;
    8. components.minute = 0;
    9. components.second = 0;
    10. return [calendar dateFromComponents:components];
    11. }
    12. - (NSDate *)endOfDay:(NSDate *)date
    13. {
    14. if (!date) return nil;
    15. NSCalendar *calendar = [NSCalendar currentCalendar];
    16. NSCalendarUnit calendarUnits = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay;
    17. NSDateComponents *components = [calendar components:calendarUnits fromDate:date];
    18. components.hour = 23;
    19. components.minute = 59;
    20. components.second = 59;
    21. return [calendar dateFromComponents:components];
    22. }
    Alles anzeigen
    Diese Werte speichere ich zum einen in Core Data, zum anderen nutze ich sie auch zur Erstellung entsprechender Predicates.

    Das würde ich einfach mit dem ersten und letzten eines Monats machen...

    Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • Ein Date ist ein Datum+Zeit unabhängig von einer Zeitzone. Intern wird daher UTC verwendet, was ja auch in der Anzeige zu sehen ist.

    Erst bei der Umwandlung von einem Date in einen String wird daraus ein Datum+Uhrzeit in der gewünschten Zeitzone.

    Die in CoreData gespeicherten Dates sind auch in UTC. Also sollte der 01.03.2024 00:00:00 CET (Central European Time - Zeitzone in Deutschland) als 2024-02-29 23:00:00 UTC gespeichert sein.

    Wie kommen die Werte in CoreData hinein? Über Strings? Evtl. hast Du bei der Umwandlung von einem String in ein Date die passende Zeitzone vergessen.
  • MyMattes schrieb:

    Welches Problem? Wenn Du den aktuellen Kalender / die aktuelle Zeitzone (CET = UTC+1) nutzt, dann ist 00:00 des Monatsersten (CET) doch 23:00 des vorigen Monatsletzten UTC. Oder stehe ich jetzt auf'm Schlauch?

    Aber iIch verstehe Deinen Umweg über die Strings nicht ganz - kann das Ganze im Screenshot aber auch nur rudimentär erkennen: Warum nutzt Du kein Code-Tag? Ich habe bei einem ähnlichen Problem mir Start- und Ende-Datum rein über die DateComponents des aktuellen Kalenders zusammengebaut, hier in Objective-C:

    Quellcode

    1. - (NSDate *)startOfDay:(NSDate *)date
    2. {
    3. if (!date) return nil;
    4. NSCalendar *calendar = [NSCalendar currentCalendar];
    5. NSCalendarUnit calendarUnits = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay;
    6. NSDateComponents *components = [calendar components:calendarUnits fromDate:date];
    7. components.hour = 0;
    8. components.minute = 0;
    9. components.second = 0;
    10. return [calendar dateFromComponents:components];
    11. }
    12. - (NSDate *)endOfDay:(NSDate *)date
    13. {
    14. if (!date) return nil;
    15. NSCalendar *calendar = [NSCalendar currentCalendar];
    16. NSCalendarUnit calendarUnits = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay;
    17. NSDateComponents *components = [calendar components:calendarUnits fromDate:date];
    18. components.hour = 23;
    19. components.minute = 59;
    20. components.second = 59;
    21. return [calendar dateFromComponents:components];
    22. }
    Alles anzeigen
    Diese Werte speichere ich zum einen in Core Data, zum anderen nutze ich sie auch zur Erstellung entsprechender Predicates.

    Das würde ich einfach mit dem ersten und letzten eines Monats machen...

    Mattes
    Mein Problem ist folgendes:

    Bei starten soll die App alle Einträge des aktuellen Monats anzeigen. Angezeit wird aber der 29.02 bis heute und nicht der 01.03 bis heute. Ich denke das eben die Abfrage nicht den 01.03.2024 als startdatum verwendet, sondern eben den 29.02.2024 23:00 Uhr (was gekürzt dann immer noch der 29.02.2024 ist, wenn man die Uhrzeit weg lässt).

    Ich habe kein Codetag verwendet, damit man die Debug Ausgabe sehen kann das eben der 01.03.2024 OHNE Uhrzeit umgewandelt wird in den 29.02.2024 23:00 Uhr.

    Grüße

    Dirk
  • MCDan schrieb:

    Ein Date ist ein Datum+Zeit unabhängig von einer Zeitzone. Intern wird daher UTC verwendet, was ja auch in der Anzeige zu sehen ist.

    Erst bei der Umwandlung von einem Date in einen String wird daraus ein Datum+Uhrzeit in der gewünschten Zeitzone.

    Die in CoreData gespeicherten Dates sind auch in UTC. Also sollte der 01.03.2024 00:00:00 CET (Central European Time - Zeitzone in Deutschland) als 2024-02-29 23:00:00 UTC gespeichert sein.

    Wie kommen die Werte in CoreData hinein? Über Strings? Evtl. hast Du bei der Umwandlung von einem String in ein Date die passende Zeitzone vergessen.
    Ich speichere das Datum als Date()
    Bildschirmfoto 2024-03-06 um 06.40.49.png

    Grüße

    Dirk
  • Wie frage ich denn Datumswerte in CoreData richtig ab?

    Quellcode

    1. init(selectedYear:Int, selectedMonth:Int){
    2. let dateFormatter = DateFormatter()
    3. var localTimeZoneAbbreviation: String { return TimeZone.current.abbreviation() ?? "" }
    4. dateFormatter.timeZone = TimeZone(abbreviation: localTimeZoneAbbreviation)
    5. let startDString = "\(selectedYear).\(selectedMonth).01"
    6. dateFormatter.dateFormat = "yyyy.MM.dd"
    7. let startD:Date = dateFormatter.date(from: startDString) ?? Date(timeIntervalSince1970: 0)
    8. let lastDayOfMonth:Int = Calendar.current.dateComponents([.day], from: startD.endOfMonth).day ?? 0
    9. let endDString = "\(selectedYear).\(selectedMonth).\(lastDayOfMonth)"
    10. let endD:Date = dateFormatter.date(from: endDString) ?? Date(timeIntervalSince1970: 0)
    11. shiftRequest = FetchRequest(entity: Shift.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Shift.startDate, ascending: true)], predicate: NSPredicate(format: "startDate >= %@ && startDate <= %@", argumentArray: [startD as CVarArg, endD as CVarArg]))
    12. }
    Alles anzeigen
    Das ist mein Code beim, starten.

    Ich möchte den ersten Tag des aktuellen Monats und den letzten Tag des aktuellen Monats herausfinden. Deswegen diese String zu Date castereien. Vielleicht ist ja einfach die Abfrage "startDate >= %@ && startDate <= %@" mit den startD as CVarArg falsch?

    Grüße

    Dirk
  • Ich würde mit den Date-Komponenten arbeiten und mir den Monatsanfang als Tag Eins, Stunde Null für den aktuellen und nächsten Monat erstellen. Dann einmal Vergleich auf "größer oder gleich Anfangsdatum" und einmal auf "kleiner Enddatum" ... und Du bist in einem aktuellen Monat. Die Uhrzeitkomponenten sind dabei uninteressant.

    Durch den "kleiner oder gleich"-Vergleich machst Du es Dir unnötig kompliziert.

    Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • MyMattes schrieb:

    Ich würde mit den Date-Komponenten arbeiten und mir den Monatsanfang als Tag Eins, Stunde Null für den aktuellen und nächsten Monat erstellen. Dann einmal Vergleich auf "größer oder gleich Anfangsdatum" und einmal auf "kleiner Enddatum" ... und Du bist in einem aktuellen Monat. Die Uhrzeitkomponenten sind dabei uninteressant.

    Durch den "kleiner oder gleich"-Vergleich machst Du es Dir unnötig kompliziert.

    Mattes
    EndD Kleiner 29.02 wäre der 28.02, dann wäre in meiner Abfrage doch der letzte Tag nicht drin. Außerdem ist nicht der letzte Tag das Problem, sondern der erste Tag.

    Ich will selektieren: alles das zwischen dem 1. des Monats und dem letzten des Monats ein StartDatum hat, weil Dienste (die dort erfasst werden sollen) am 29.02 anfangen und erst am 01.03 enden können.
    Also "Select * from tabelle where startDate >= 01.02.2024 and startDate <= 29.02.2024" um es mal im SQL Dialekt zu sagen ;)

    Grüße

    Dirk
  • Qvex23 schrieb:

    Also "Select * from tabelle where startDate >= 01.02.2024 and startDate <= 29.02.2024" um es mal im SQL Dialekt zu sagen ;)
    Denk‘ doch mal etwas „outside of the box“ … Du könntest doch einfach komplette UTC-Timestamps (also Datum und Uhrzeit) vergleichen und zwar:

    where startDate >= 01.02.2024 00:00:00.000 and startDate < 01.03.2024 00:00:00.000

    Die Differenz beider Daten kannst Du durch einfaches Addieren der month-Komponente abbilden. Dann musst Du nur noch dafür sorgen, dass die startDate in der gleichen Zeitzone wie das Datumsfeld in Core Data ist…

    Aber letztlich führen viele Wege nach Rom, Mattes
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • MyMattes schrieb:

    Qvex23 schrieb:

    Also "Select * from tabelle where startDate >= 01.02.2024 and startDate <= 29.02.2024" um es mal im SQL Dialekt zu sagen ;)
    Denk‘ doch mal etwas „outside of the box“ … Du könntest doch einfach komplette UTC-Timestamps (also Datum und Uhrzeit) vergleichen und zwar:
    where startDate >= 01.02.2024 00:00:00.000 and startDate < 01.03.2024 00:00:00.000

    Die Differenz beider Daten kannst Du durch einfaches Addieren der month-Komponente abbilden. Dann musst Du nur noch dafür sorgen, dass die startDate in der gleichen Zeitzone wie das Datumsfeld in Core Data ist…

    Aber letztlich führen viele Wege nach Rom, Mattes
    ja, kann man so machen, aber wie gesagt habe ich nicht das Problem mit dem Énd Datum sondern mit dem Startdatum
  • Qvex23 schrieb:

    Wie frage ich denn Datumswerte in CoreData richtig ab?

    Quellcode

    1. init(selectedYear:Int, selectedMonth:Int){
    2. let dateFormatter = DateFormatter()
    3. var localTimeZoneAbbreviation: String { return TimeZone.current.abbreviation() ?? "" }
    4. dateFormatter.timeZone = TimeZone(abbreviation: localTimeZoneAbbreviation)
    5. let startDString = "\(selectedYear).\(selectedMonth).01"
    6. dateFormatter.dateFormat = "yyyy.MM.dd"
    7. let startD:Date = dateFormatter.date(from: startDString) ?? Date(timeIntervalSince1970: 0)
    8. let lastDayOfMonth:Int = Calendar.current.dateComponents([.day], from: startD.endOfMonth).day ?? 0
    9. let endDString = "\(selectedYear).\(selectedMonth).\(lastDayOfMonth)"
    10. let endD:Date = dateFormatter.date(from: endDString) ?? Date(timeIntervalSince1970: 0)
    11. shiftRequest = FetchRequest(entity: Shift.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Shift.startDate, ascending: true)], predicate: NSPredicate(format: "startDate >= %@ && startDate <= %@", argumentArray: [startD as CVarArg, endD as CVarArg]))
    12. }
    Alles anzeigen
    Das ist mein Code beim, starten.

    Ich möchte den ersten Tag des aktuellen Monats und den letzten Tag des aktuellen Monats herausfinden. Deswegen diese String zu Date castereien. Vielleicht ist ja einfach die Abfrage "startDate >= %@ && startDate <= %@" mit den startD as CVarArg falsch?

    Grüße

    Dirk

    Wie MyMattes bereits vorgeschlagen hat, sollte man den 1. des Monats über Calendar/DateComponents und nicht über einen String mit einen DateFormatter berechnen/ermittle

    Quellcode

    1. let firstOfMonth = Calendar.current.date(from: DateComponents(year: selectedYear, month: selectedMonth)) // 1. Tag des Monats 00:00:00
    2. let lastOfMonth = Calendar.current.date(byAdding: DateComponents(month: 1, second: -1), to: firstOfMonth) // Letzter Tag des Monats 23:59:59
    Ggf. sollte man beim letzten des Monats eine Nanosekunde und keine Sekunde abziehen oder den 01. des Folgemonats 00:00:00 und bei der Abfrage < und nicht <= verwenden. ;)

    Dies scheint mir jedoch nicht das eigentlich Problem zu sein, da der 01.03.2024 00:00:00 CET nun mal den 29.02.2024 23:00:00 UTC ergibt. Evtl. sind ja die in CoreData gespeicherten Daten (Datum) nicht in UTC. Wenn Du in dem Predicate "startDate >= 29.02.2024 23:00:00 UTC" verwendest, dann ist die Ergebnismenge korrekt abgegrenzt, da die Daten (Datum) bei der Anzeige wieder in CET umgewandelt werden, also 29.02.2024 23:00:00 UTC wird zu 01.03.2024 00:00:00 CET.

    Erstelle einfach mal zwei Testdaten mit dem 01.03.2024 00:05:00 CET und dem 29.02.23:55:00 CET. Diese sollten dann als 29.02.2024 23:05:00 UTC und 29.02.2024 22:55:00 UTC gespeichert werden.

    Bei der Abfrage sollte dann nur 01.03.2024 00:05:00 CET (29.02.2024 23:05:00 UTC) in der Ergebnismenge auftauchen.

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von MCDan ()

  • Danke das ihr mich immer wieder in die richtige Richtung schubst. Ich habe meine App nun umgebaut, so das der Zeitraum nun nicht aus einem Picker kommt den ich selbst zusammen bastel mit Monats zahlen und Jahreszahlen, sondern aus einem Datepicker. Nun stimmen plötzlich auch die Abfragen zu 100% überein!
    Was ich allerdings nicht verstehe ist, warum man nicht <= verwenden sollte?

    "Select * from tabelle where startDate >= 01.02.2024 and startDate <= 29.02.2024" ist doch viel einfacher zu schreiben als vom 01.03.2024 eine Nanosekunde abzuziehen und dann den Fetchrequest zusammen zu bauen.

    Grüße
  • Qvex23 schrieb:

    "Select * from tabelle where startDate >= 01.02.2024 and startDate <= 29.02.2024" ist doch viel einfacher zu schreiben als vom 01.03.2024 eine Nanosekunde abzuziehen und dann den Fetchrequest zusammen zu bauen.
    Ansichtssache :) Man spart sich eben das Identifizieren des Monatsletzten. Im Zweifelsfall würde ich auch das über "Monat + 1, Tag - 1" realisieren, dann ist der Vergleich auf kleiner doch einsichtiger. Und man muss sich keine Gedanken über die Uhrzeiten machen...
    Diese Seite bleibt aus technischen Gründen unbedruckt.
  • Quellcode

    1. extension Date {
    2. var startOfMonth: Date {
    3. let calendar = Calendar(identifier: .gregorian)
    4. let components = calendar.dateComponents([.year, .month], from: self)
    5. return calendar.date(from: components)!
    6. }
    7. var endOfMonth: Date {
    8. var components = DateComponents()
    9. components.month = 1
    10. components.second = -1
    11. return Calendar(identifier: .gregorian).date(byAdding: components, to: startOfMonth)!
    12. }
    13. }
    Alles anzeigen
    :D