Android Question DST Problem

Alex_197

Well-Known Member
Licensed User
Longtime User
Hi all.

I have a problem with DateUtils.AddPeriod and DST which is In the USA today March, 13 2022

Here is my code
Add 1 hour:
Private Sub GetStrTo(strFrom As String) As String
   
    Try
        Dim TimeFormatShow As String="hh:mm a",DateFormat As String="MM/dd/yyyy"
        DateTime.TimeFormat=TimeFormatShow
        DateTime.DateFormat=DateFormat
        Dim Arr() As String
        Dim NotTimeDate As String,NoteTimeStr As String
        Dim P As Period
        Dim Tick As Long ,NewTick As Long
        Dim strTo As String      
        Dim AddHour As Int
                       
        Arr=Regex.Split(" ", strFrom)
       
        NotTimeDate=Arr(0)
        NoteTimeStr=Arr(1) & " " & Arr(2)
                   
        Tick=DateTime.DateTimeParse(NotTimeDate,NoteTimeStr)
       
        AddHour=1
        P.Hours=AddHour
               
        NewTick=DateUtils.AddPeriod(Tick,P)
        strTo=DateUtils.TicksToString(NewTick)
       
        Return strTo
           
    Catch
        Log("GetStrTo " & LastException.Message)
       
        Return "Error"
    End Try


   
End Sub


I attached a small project so you can try yourself.

My question is - how to add 1 hour to 03/13/2022 1:00 AM to get 03/13/2022 2:00 AM but not 3:00 AM?

My code creates notes in the database and the note timestamp should be for 1 hour bigger than the previous one. So if the previous one was 03/13/2022 01:00 AM the next one should be 03/13/2022 02:00 AM and so on.

On DST day I have this problem.

On my code in lines 24,25 my code adds 1 hour to the previous timestamp (you can see it in the textbox if you run my project) but returns 03/13/2022 3:00 AM but not 2:00 AM.

Thanks.
 

Attachments

  • CheckDST.zip
    136.3 KB · Views: 79

emexes

Expert
Licensed User
On DST day I have this problem.

Wait until DST day in November, then you'll have some real fun. 🍻

If you are going to be doing math using your note timestamps, then best to store them as UTC ie without daylight savings adjustments.

Although now you've got me thinking... I'm pretty sure that UTC still has some anomalies re: leap seconds, which aren't a worry for most people because the leap seconds are infrequent and small. I expect that the B4X DateTime (UnixTime?) accounting handles leap seconds as well, otherwise those leap seconds could mess up eg gps satellite orbit calculations.
 
Upvote 0

emexes

Expert
Licensed User
My question is - how to add 1 hour to 03/13/2022 1:00 AM to get 03/13/2022 2:00 AM but not 3:00 AM?

Assuming that you need to use the DateTime math to handle when adding hours crosses end-of-month boundaries, then the simplest solution might be to (temporarily) treat your notes' timestamps as being UTC rather than being the local timezone.
 
Upvote 0

Alex_197

Well-Known Member
Licensed User
Longtime User
Assuming that you need to use the DateTime math to handle when adding hours crosses end-of-month boundaries, then the simplest solution might be to (temporarily) treat your notes' timestamps as being UTC rather than being the local timezone.
Thanks for your idea but I need to show this time stamp to the user and if I convert it back to local time EST DTS - guess what?
 
Upvote 0

emexes

Expert
Licensed User
Thanks for your idea but I need to show this time stamp to the user and if I convert it back to local time EST DTS - guess what?

Clarification: treat your note timestamp yyyy-mm-dd-hh-mm-ss as being UTC, add the hour, present the resultant yyyy-mm-dd-hh-mm-ss to your user, no need to do any timezone conversions.
 
Upvote 0

Mahares

Expert
Licensed User
Longtime User
I need to show this time stamp to the user
I created a sub that determines if the date the second Sunday of March of any year, it bypasses the DST, since in America DST happens the 2nd Sunday of March of every year . See if the attached project fits in with what you are thinking. Of course, it can be tweaked.
 

Attachments

  • DSTforAlex197.zip
    136.5 KB · Views: 70
Upvote 0

emexes

Expert
Licensed User
since in America DST happens the 2nd Sunday of March of every year

and the first Sunday of November, which is way more fun

and that's just in America

except for Hawaii, and possibly other US lands near the equator

since daylight savings makes less sense the closer you get to the equator

or above/below the arctic/antarctic circles
 
Upvote 0

emexes

Expert
Licensed User
My question is - how to add 1 hour to 03/13/2022 1:00 AM to get 03/13/2022 2:00 AM but not 3:00 AM?

Is it always 1 hour that you're adding?

Could it cross the midnight wraparound? (and thus potentially the date change from 28th/29th/30th/31st of one month to the 1st of the next) (maybe year too)

What is your plan for the first Sunday in November when the times double-up for an hour? (eg there are two local 1:30 AMs, one hour apart)

And backing away from the trees so that we can see the forest: what is the actual practical reason for doing this? Is it of a large-enough consequence to matter? Like, if it is a school timetable, are there classes actually running at 1am on a Sunday anyway?

On a related note: I used to work nightshift, and we handled my pay by just ignoring the whole issue because (i) it averaged itself out over the year, and (ii) it would cost more time and confusion and potential for error in sorting out than it was worth.
 
Upvote 0

Alex_197

Well-Known Member
Licensed User
Longtime User
Is it always 1 hour that you're adding?

Could it cross the midnight wraparound? (and thus potentially the date change from 28th/29th/30th/31st of one month to the 1st of the next) (maybe year too)

What is your plan for the first Sunday in November when the times double-up for an hour? (eg there are two local 1:30 AMs, one hour apart)

And backing away from the trees so that we can see the forest: what is the actual practical reason for doing this? Is it of a large-enough consequence to matter? Like, if it is a school timetable, are there classes actually running at 1am on a Sunday anyway?

On a related note: I used to work nightshift, and we handled my pay by just ignoring the whole issue because (i) it averaged itself out over the year, and (ii) it would cost more time and confusion and potential for error in sorting out than it was worth.
My solution - if it's DST and timestr is "01;00 AM"just replace it with "02:00 AM" and if it's "02:00 AM" replace it with "03:00 AM".

May look dirty but I don't see any other solutions so far...


B4X:
If IsDST="True" And NoteTimeStr="01:00 AM" Then

            NoteTimeStr=NoteTimeStr.Replace("01:00 AM","02:00 AM")
            Return NotTimeDate & " " & NoteTimeStr
            
Else If IsDST="True" And NoteTimeStr="02:00 AM" Then

            NoteTimeStr=NoteTimeStr.Replace("02:00 AM","03:00 AM")
            Return NotTimeDate & " " & NoteTimeStr
            
End If


B4X:
public Sub CheckDST(Tick As Long) As String
    
    Try
        Dim IsWait As String
        Dim  IsDST As Boolean
        
        
        IsDST=DidTimeZoneChange
    
        Dim TickFrom As Long,TickTo As Long
                
        TickFrom=DateTime.DateTimeParse(Today ,"1:00 AM")
        TickTo=DateTime.DateTimeParse(Today ,"2:00 AM")
        
        If IsDST=False Then
            IsWait="False"
            Return IsWait
        End If
        
        If  Tick>=TickFrom And Tick<=TickTo Then
            IsWait="True"
        Else
            IsWait="False"
        End If
        
        Return IsWait
        
    Catch
        Log("CheckDST " & LastException.Message)
        ShowError("modFun_CheckDST  " & LastException.Message)
        Return "Error"
    End Try
End Sub

Private Sub DidTimeZoneChange () As Boolean
    
    Dim date As Long=DateTime.DateParse(Today)
    Dim Start As Long = DateUtils.SetDateAndTime(DateTime.GetYear(date), DateTime.GetMonth(date), DateTime.GetDayOfMonth(date), 0, 0, 0)
    Dim EndDate As Long = DateUtils.SetDateAndTime(DateTime.GetYear(date), DateTime.GetMonth(date), DateTime.GetDayOfMonth(date), 23, 0, 0)
    Return DateTime.GetTimeZoneOffsetAt(Start) <> DateTime.GetTimeZoneOffsetAt(EndDate)
    
End Sub
 
Upvote 0

emexes

Expert
Licensed User
My plan this evening was to use Regex.Split to break date-times into their 6 numeric components year-month-day-hour-minute-second, and then add the date-time-increment to the date-time-start, component by component, with the only problematic wraparound being the number of days in a month.

ie seconds and minutes always wrap at 60, hours wrap at 24, months wrap at 12, but day wraps at 28/29/30/31 depending on the month and leap year.

You're still going to have the anomaly of log entries on the first Sunday of November looking like: 01:58 01:59 01:00 01:01 ... 01:58 01:59 02:00 02:01
 
Upvote 0

emexes

Expert
Licensed User
My solution - if it's DST and timestr is "01;00 AM"just replace it with "02:00 AM" and if it's "02:00 AM" replace it with "03:00 AM".

Are your note timestamps guaranteed to always be hh:00 ? ie never 1:30 or 2:25 etc?

May look dirty but I don't see any other solutions so far...

Hey, if it gets the job done and the user is happy... 🍻

Although might be good idea to comment that code well, so that when you're back there in 8 months time, you'll have a fighting chance of success then too.
 
Upvote 0

Alex_197

Well-Known Member
Licensed User
Longtime User
Are your note timestamps guaranteed to always be hh:00 ? ie never 1:30 or 2:25 etc?



Hey, if it works... 🍻

Although might be good idea to comment that code well, so that when you're back there in 8 months time, you'll have a fighting chance of success then too.
Yes, even you started at 8:45 PM it will be 9:45 PM, 10:45 PM, 11:45 PM but they shift must end at 12:00 AM and the next one will start at 12:00 AM - it will be done automatically.
 
Upvote 0

emexes

Expert
Licensed User
Alex, I was too far down the rabbit hole to turn around 🤣 on the bright side, this code is coming up with the answer your users are expecting 🍻 :

Log Output with annotations:
Waiting for debugger to connect...
Program started.
Hello world!!!

(ArrayList) [3, 13, 2022, 1, 0]        'DST start date 1:00 AM
(ArrayList) [0, 0, 0, 1, 0]            'plus 1 hour (01:00)
(ArrayList) [3, 13, 2022, 2, 0, 0]     '= 2:00:00 (AM)

(ArrayList) [3, 13, 2022, 20, 35]      '08:35 PM (20:35)
(ArrayList) [0, 0, 0, 10, 47, 16]      'plus 10 hours 47 minutes 16 seconds
(ArrayList) [3, 14, 2022, 7, 22, 16]   '= next day 07:22:16 (AM)

Program terminated (StartMessageLoop was not called).

B4X:
Log("Hello world!!!")

Dim S As String = "03/13/2022 01:00"
Log(DateComponents(S))

Dim I As String = "00/00/0000 01:00"
Log(DateComponents(I))

Log(AddDates(S, I))


Dim S As String = "03/13/2022 20:35"
Log(DateComponents(S))

Dim I As String = "00/00/0000 10:47:16"
Log(DateComponents(I))

Log(AddDates(S, I))


Sub DateComponents(D As String) As List    'returns list of day, month, year and (if sepecified) hour, minute, second

    '''Dim ComponentMin() As Int = Array As Int( 1, 1, 1800,  0,  0,  0)    'spewin! date increment components mostly 0
    Dim ComponentMin() As Int = Array As Int( 0, 0,    0,  0,  0,  0)
    Dim ComponentMax() As Int = Array As Int(12,31, 2222, 59, 59, 60)    'leap second is 60

    Dim Components As List
    Components.Initialize
   
    Dim StringComponents() As String = Regex.Split("[^0-9]", D.Trim)
   
    Dim BadDateFlag As Boolean = (StringComponents.Length < 3 Or StringComponents.Length > 6)
   
    If BadDateFlag = False Then
        For I = 0 To StringComponents.Length - 1
            If IsNumber(StringComponents(I)) = False Then
                BadDateFlag = True
                Exit
            Else
                Dim Temp As Int = StringComponents(I)
               
                If Temp < ComponentMin(I) Or Temp > ComponentMax(I) Then
                    BadDateFlag = True
                    Exit
                End If
               
                Components.Add(Temp)
            End If  
        Next
    End If
           
    If BadDateFlag Then
        Log("Bad date: """ & D & """")
        Components.Initialize    'return empty list if bad
    End If
       
    Return Components
       
End Sub


Sub AddDates(StartDate As String, Increment As String) As List
   
    Dim StartDateComponents As List = DateComponents(StartDate)
    Dim IncrementComponents As List = DateComponents(Increment)
   
    If StartDateComponents.Size = 0 Or IncrementComponents.Size = 0 Then
        Dim EmptyList As List
        EmptyList.Initialize
       
        Return EmptyList    'return empty list if bad
    End If
   
    Do While StartDateComponents.Size < 6
        StartDateComponents.Add(0)
    Loop

    Do While IncrementComponents.Size < 6
        IncrementComponents.Add(0)
    Loop

    Dim Sum As Int = 0
    Dim Carry As Int = 0
   
    Sum = StartDateComponents.Get(5) + IncrementComponents.Get(5)    'seconds
    Carry = 0
    Do While Sum >= 60
        Sum = Sum - 60
        Carry = Carry + 1
    Loop
    Dim Second As Int = Sum
   
    Sum = StartDateComponents.Get(4) + IncrementComponents.Get(4) + Carry    'minutes
    Carry = 0
    Do While Sum >= 60
        Sum = Sum - 60
        Carry = Carry + 1
    Loop
    Dim Minute As Int = Sum
   
    Sum = StartDateComponents.Get(3) + IncrementComponents.Get(3) + Carry    'hours
    Carry = 0
    Do While Sum >= 24
        Sum = Sum - 24
        Carry = Carry + 1
    Loop
    Dim Hour As Int = Sum
       
    Dim DaysInMonth As Int = 31
    Select StartDateComponents.Get(0)    'month
        Case 9, 4, 6, 11:
            DaysInMonth = 30
           
        Case 2:
            If (StartDateComponents.Get(2) Mod 400) = 0 Then    'year
                DaysInMonth = 29
            else if (StartDateComponents.Get(2) Mod 100) = 0 Then
                DaysInMonth = 28
            else if (StartDateComponents.Get(2) Mod 4) = 0 Then
                DaysInMonth = 29
            Else
                DaysInMonth = 28
            End If
           
    End Select

    Sum = StartDateComponents.Get(1) + IncrementComponents.Get(1) + Carry    'days
    Carry = 0
    If Sum > DaysInMonth Then
        Sum = Sum - DaysInMonth
        Carry = Carry + 1
    End If
    Dim Day As Int = Sum
   
    Sum = StartDateComponents.Get(0) + IncrementComponents.Get(0) + Carry    'months
    Carry = 0
    Do While Sum > 12
        Sum = Sum - 12
        Carry = Carry + 1
    Loop
    Dim Month As Int = Sum
   
    Dim Year As Int = StartDateComponents.Get(2) + IncrementComponents.Get(2) + Carry    'years
   
    Dim ReturnValue As List
    ReturnValue.Initialize
   
    ReturnValue.Add(Month)
    ReturnValue.Add(Day)
    ReturnValue.Add(Year)
    ReturnValue.Add(Hour)
    ReturnValue.Add(Minute)
    ReturnValue.Add(Second)
   
    Return ReturnValue
   
End Sub
 
Upvote 0

Alex_197

Well-Known Member
Licensed User
Longtime User
Alex, I was too far down the rabbit hole to turn around 🤣 on the bright side, this code is coming up with the answer your users are expecting 🍻 :

Log Output with annotations:
Waiting for debugger to connect...
Program started.
Hello world!!!

(ArrayList) [3, 13, 2022, 1, 0]        'DST start date 1:00 AM
(ArrayList) [0, 0, 0, 1, 0]            'plus 1 hour (01:00)'
(ArrayList) [3, 13, 2022, 2, 0, 0]     '= 2:00:00 AM

(ArrayList) [3, 13, 2022, 20, 35]      '08:35 PM (20:35)'
(ArrayList) [0, 0, 0, 10, 47, 16]      'plus 10 hours 47 minutes 16 seconds'
(ArrayList) [3, 14, 2022, 7, 22, 16]   '= next day 07:22:16'

Program terminated (StartMessageLoop was not called).

B4X:
Log("Hello world!!!")

Dim S As String = "03/13/2022 01:00"
Log(DateComponents(S))

Dim I As String = "00/00/0000 01:00"
Log(DateComponents(I))

Log(AddDates(S, I))


Dim S As String = "03/13/2022 20:35"
Log(DateComponents(S))

Dim I As String = "00/00/0000 10:47:16"
Log(DateComponents(I))

Log(AddDates(S, I))


Sub DateComponents(D As String) As List    'returns list of day, month, year and (if sepecified) hour, minute, second

    '''Dim ComponentMin() As Int = Array As Int( 1, 1, 1800,  0,  0,  0)    'spewin! date increment components mostly 0
    Dim ComponentMin() As Int = Array As Int( 0, 0,    0,  0,  0,  0)
    Dim ComponentMax() As Int = Array As Int(12,31, 2222, 59, 59, 60)    'leap second is 60

    Dim Components As List
    Components.Initialize
   
    Dim StringComponents() As String = Regex.Split("[^0-9]", D.Trim)
   
    Dim BadDateFlag As Boolean = (StringComponents.Length < 3 Or StringComponents.Length > 6)
   
    If BadDateFlag = False Then
        For I = 0 To StringComponents.Length - 1
            If IsNumber(StringComponents(I)) = False Then
                BadDateFlag = True
                Exit
            Else
                Dim Temp As Int = StringComponents(I)
               
                If Temp < ComponentMin(I) Or Temp > ComponentMax(I) Then
                    BadDateFlag = True
                    Exit
                End If
               
                Components.Add(Temp)
            End If  
        Next
    End If
           
    If BadDateFlag Then
        Log("Bad date: """ & D & """")
        Components.Initialize    'return empty list if bad
    End If
       
    Return Components
       
End Sub


Sub AddDates(StartDate As String, Increment As String) As List
   
    Dim StartDateComponents As List = DateComponents(StartDate)
    Dim IncrementComponents As List = DateComponents(Increment)
   
    If StartDateComponents.Size = 0 Or IncrementComponents.Size = 0 Then
        Dim EmptyList As List
        EmptyList.Initialize
       
        Return EmptyList    'return empty list if bad
    End If
   
    Do While StartDateComponents.Size < 6
        StartDateComponents.Add(0)
    Loop

    Do While IncrementComponents.Size < 6
        IncrementComponents.Add(0)
    Loop

    Dim Sum As Int = 0
    Dim Carry As Int = 0
   
    Sum = StartDateComponents.Get(5) + IncrementComponents.Get(5)    'seconds
    Carry = 0
    Do While Sum >= 60
        Sum = Sum - 60
        Carry = Carry + 1
    Loop
    Dim Second As Int = Sum
   
    Sum = StartDateComponents.Get(4) + IncrementComponents.Get(4) + Carry    'minutes
    Carry = 0
    Do While Sum >= 60
        Sum = Sum - 60
        Carry = Carry + 1
    Loop
    Dim Minute As Int = Sum
   
    Sum = StartDateComponents.Get(3) + IncrementComponents.Get(3) + Carry    'hours
    Carry = 0
    Do While Sum >= 24
        Sum = Sum - 24
        Carry = Carry + 1
    Loop
    Dim Hour As Int = Sum
       
    Dim DaysInMonth As Int = 31
    Select StartDateComponents.Get(0)    'month
        Case 9, 4, 6, 11:
            DaysInMonth = 30
           
        Case 2:
            If (StartDateComponents.Get(2) Mod 400) = 0 Then    'year
                DaysInMonth = 29
            else if (StartDateComponents.Get(2) Mod 100) = 0 Then
                DaysInMonth = 28
            else if (StartDateComponents.Get(2) Mod 4) = 0 Then
                DaysInMonth = 29
            Else
                DaysInMonth = 28
            End If
           
    End Select

    Sum = StartDateComponents.Get(1) + IncrementComponents.Get(1) + Carry    'days
    Carry = 0
    If Sum > DaysInMonth Then
        Sum = Sum - DaysInMonth
        Carry = Carry + 1
    End If
    Dim Day As Int = Sum
   
    Sum = StartDateComponents.Get(0) + IncrementComponents.Get(0) + Carry    'months
    Carry = 0
    Do While Sum > 12
        Sum = Sum - 12
        Carry = Carry + 1
    Loop
    Dim Month As Int = Sum
   
    Dim Year As Int = StartDateComponents.Get(2) + IncrementComponents.Get(2) + Carry    'years
   
    Dim ReturnValue As List
    ReturnValue.Initialize
   
    ReturnValue.Add(Month)
    ReturnValue.Add(Day)
    ReturnValue.Add(Year)
    ReturnValue.Add(Hour)
    ReturnValue.Add(Minute)
    ReturnValue.Add(Second)
   
    Return ReturnValue
   
End Sub
This is exactly what I need:) Thanks:)
 
Upvote 0

emexes

Expert
Licensed User
Yes, even you started at 8:45 PM it will be 9:45 PM, 10:45 PM, 11:45 PM but they shift must end at 12:00 AM and the next one will start at 12:00 AM - it will be done automatically.

I am still a bit worried that you're swapping one problem for another = instead of users who don't understand DST issues complaining, now you're going to be getting complaints from the other half of your users. 🤣

You should almost certainly store date-times as B4X is designed for, ie 64-bit integer of milliseconds since 1970 (or something similar to that).

When you are printing schedules, a solution that covers both perspectives might be to flag any shifts where the start time and end time have different DST offsets. I feel that your schedules should show the same time as users would see on clocks or their phones.

This function feels like it might help do that (but I haven't tried it) :

1647227028404.png
 
Upvote 0

emexes

Expert
Licensed User
When you are printing schedules, a solution that covers both perspectives might be to flag any shifts where the start time and end time have different DST offsets.

This function feels like it might help do that (but I haven't tried it) :

Is looking good:

Log Output:
ticks (ms)    date     time        DateTime.GetTimeZoneOffsetAt
============= ======== =========== ==
1664639997806 10/02/22 01:59:57 am 10
1664639998107 10/02/22 01:59:58 am 10
1664639998408 10/02/22 01:59:58 am 10
1664639998709 10/02/22 01:59:58 am 10
1664639999010 10/02/22 01:59:59 am 10
1664639999311 10/02/22 01:59:59 am 10
1664639999612 10/02/22 01:59:59 am 10
1664639999913 10/02/22 01:59:59 am 10
1664640000214 10/02/22 03:00:00 am 11    'skips an hour at 2am on first Sunday in October
1664640000515 10/02/22 03:00:00 am 11
1664640000816 10/02/22 03:00:00 am 11
1664640001117 10/02/22 03:00:01 am 11
1664640001418 10/02/22 03:00:01 am 11
1664640001719 10/02/22 03:00:01 am 11
1664640002020 10/02/22 03:00:02 am 11
1664640002321 10/02/22 03:00:02 am 11
 
Upvote 0

emexes

Expert
Licensed User
So close, but... can't work out how to restore timezone to current locale.

B4X:
Dim NoteTimestamp As String = "03/13/2022 01:00"    'Alex_197 local daylight saving start
'''Dim NoteTimestamp As String = "10/02/2022 02:00"    'emexes local daylight saving start

DateTime.DateFormat = "MM/dd/yyyy"
DateTime.TimeFormat = "HH:mm"
DateTime.SetTimeZone(0)    'comment out this line to see the original issue (01:00 plus 1 hour = 03:00)

Dim NoteDateTime() As String = Regex.Split("\ ", NoteTimestamp)

'these five lines are not necessary; just making sure things are going to plan
Dim NoteDate As String = NoteDateTime(0)
Dim NoteTime As String = NoteDateTime(1)
Log("NoteTimestamp = """ & NoteTimestamp & """")
Log("Date = """ & NoteDate & """")
Log("Time = """ & NoteTime & """")

Dim ZuluTimestamp As Long = DateTime.DateTimeParse(NoteDateTime(0), NoteDateTime(1))

'these three lines are not necessary; just having a closer look at what the heck we've done
DateTime.DateFormat = "MM/dd/yyyy HH:mm:ss z"
Log(DateTime.Date(ZuluTimestamp))
Log(DateTime.Date(ZuluTimestamp + 60 * 60 * 1000))

DateTime.DateFormat = "MM/dd/yyyy HH:mm"
Log(DateTime.Date(ZuluTimestamp))
Log(DateTime.Date(ZuluTimestamp + 60 * 60 * 1000))

DateTime.DateFormat = DateTime.DeviceDefaultDateFormat
DateTime.TimeFormat = DateTime.DeviceDefaultTimeFormat
'but how to reset timezone???
 
Upvote 0
Top