Help find a bug

Just starting out? Need help? Post your questions and find answers here.
eBasic
New User
New User
Posts: 5
Joined: Wed Dec 26, 2018 12:34 pm

Help find a bug

Post by eBasic »

There is small bug in code, plz help me to fix it.
Everything works fine but the days value is invalid, looks like (bug->month->day) it's broken after year calculation and everything after fails because of that...

Code: Select all

Structure LARGE_INTEGER_ 
  StructureUnion
    x.LARGE_INTEGER
    QuadPart.q
  EndStructureUnion
EndStructure

Structure TIME_FIELDS
  Year.c               ; range [1601...]
  Month.c              ; range [1..12]
  Day.c                ; range [1..31]
  Hour.c               ; range [0..23]
  Minute.c             ; range [0..59]
  Second.c             ; range [0..59]
  Milliseconds.c       ; range [0..999]
  Weekday.c            ; range [0..6] == [Sunday..Saturday]
EndStructure           ; TIME_FIELDS
  

#TICKSPERMIN       =  600000000
#TICKSPERSEC       =  10000000
#TICKSPERMSEC      =  10000
#SECSPERDAY        =  86400
#SECSPERHOUR       =  3600
#SECSPERMIN        =  60
#MINSPERHOUR       =  60
#HOURSPERDAY       =  24
#EPOCHWEEKDAY      =  1
#DAYSPERWEEK       =  7
#EPOCHYEAR         =  1601
#DAYSPERNORMALYEAR =  365
#DAYSPERLEAPYEAR   =  366
#MONSPERYEAR       =  12

Procedure YearLengths(LeapYear.l)
  If LeapYear
    ProcedureReturn #DAYSPERLEAPYEAR   ;  366
  Else
    ProcedureReturn #DAYSPERNORMALYEAR ;  365
  EndIf   
EndProcedure

Procedure.l IsLeapYear(Year.l)
  If Year < 1600 
    ProcedureReturn Bool(Year % 4 = 0)
  Else
    ProcedureReturn Bool((Year % 4 = 0 And (Year % 100) <> 0) Or (Year % 400) = 0)
  EndIf
EndProcedure

Procedure DaysSinceEpoch(Year.l)
  Protected Days.i
  Year-1  ;  Don't include a leap day from the current year 
  Days = Year * #DAYSPERNORMALYEAR + Year / 4 - Year / 100 + Year / 400
  Days - (#EPOCHYEAR - 1) * #DAYSPERNORMALYEAR + (#EPOCHYEAR - 1) / 4 - (#EPOCHYEAR - 1) / 100 + (#EPOCHYEAR - 1) / 400
  ProcedureReturn Days
EndProcedure  

Procedure TimeToSystemTime(*Time.LARGE_INTEGER_, *TimeFields.TIME_FIELDS)
  
  Protected SecondsInDay.l
  Protected CurYear.l
  Protected LeapYear.l
  Protected CurMonth.l  
  Protected Days.l                 
  Protected IntTime.q = *Time\QuadPart 
  
  ; Extract millisecond from time And convert time into seconds 
  *TimeFields\Milliseconds = (IntTime % #TICKSPERSEC) / #TICKSPERMSEC
  IntTime = IntTime / #TICKSPERSEC                                            
  
  ; Split the time into days And seconds within the day 
  Days         = IntTime / #SECSPERDAY
  SecondsInDay = IntTime % #SECSPERDAY
  
  ; Compute time of day
  *TimeFields\Hour = SecondsInDay / #SECSPERHOUR
  SecondsInDay = SecondsInDay % #SECSPERHOUR   
  *TimeFields\Minute = SecondsInDay / #SECSPERMIN
  *TimeFields\Second = SecondsInDay % #SECSPERMIN
  
  ; Compute day of week
  *TimeFields\Weekday = (#EPOCHWEEKDAY + Days) % #DAYSPERWEEK
  
  
  ; Compute year 
  CurYear = #EPOCHYEAR
  CurYear + (Days / #DAYSPERLEAPYEAR)
  Days = Days - DaysSinceEpoch(CurYear) 
  
  Repeat
    LeapYear = IsLeapYear(CurYear)
    If Days < YearLengths(LeapYear)
      Break
    EndIf 
    CurYear+1
    Days - YearLengths(LeapYear)
  ForEver 
  *TimeFields\Year = CurYear
   
  
  ; Compute month of year 
  Protected Dim MontsArray.c(11) 
  MontsArray(0)  = 31 
  MontsArray(1)  = 28 + IsLeapYear(CurYear) 
  MontsArray(2)  = 31 
  MontsArray(3)  = 30 
  MontsArray(4)  = 31 
  MontsArray(5)  = 30 
  MontsArray(6)  = 31 
  MontsArray(7)  = 31 
  MontsArray(8)  = 30 
  MontsArray(9)  = 31 
  MontsArray(10) = 30 
  MontsArray(11) = 31

  For CurMonth = 0 To 11
    If Days >= MontsArray(CurMonth)
      Days - MontsArray(CurMonth)
    EndIf 
  Next 

  *TimeFields\Month = CurMonth ; (+1)
  *TimeFields\Day   = Days     ; (+1)
  
  FreeArray(MontsArray())
EndProcedure  

Define Time.q = 131902383805223176
Define TimeFields.TIME_FIELDS
TimeToSystemTime(@Time, @TimeFields)

Debug Str(TimeFields\Year)+"."+Str(TimeFields\Month)+"."+Str(TimeFields\Day)+" "+Str(TimeFields\Weekday)+" "+RSet(Str(TimeFields\Hour), 2, "0")+":"+RSet(Str(TimeFields\Minute), 2, "0")+":"+Str(TimeFields\Second)+":"+Str(TimeFields\Milliseconds)

;Validate
st.SYSTEMTIME 
FileTimeToSystemTime_(@Time, @st)
Debug Str(st\wYear)+"."+Str(st\wMonth)+"."+Str(st\wDay)+" "+Str(st\wDayOfWeek)+" "+RSet(Str(st\wHour), 2, "0")+":"+RSet(Str(st\wMinute), 2, "0")+":"+Str(st\wSecond)+":"+Str(st\wMilliseconds)
infratec
Always Here
Always Here
Posts: 6871
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Help find a bug

Post by infratec »

Hi,

try this:

Code: Select all

Procedure.i DaysSinceEpoch(Year.i)
  
  Protected Days.i
  
  Year - 1  ;  Don't include a leap day from the current year
  Days = Year * #DAYSPERNORMALYEAR + Year / 4 - Year / 100 + Year / 400
  Days - ((#EPOCHYEAR - 1) * #DAYSPERNORMALYEAR + (#EPOCHYEAR - 1) / 4 - (#EPOCHYEAR - 1) / 100 + (#EPOCHYEAR - 1) / 400)
  
  ProcedureReturn Days
  
EndProcedure 

Procedure TimeToSystemTime(*Time.LARGE_INTEGER_, *TimeFields.TIME_FIELDS)
 
  Protected SecondsInDay.i
  Protected CurYear.i
  Protected LeapYear.i
  Protected CurMonth.i 
  Protected Days.i
  Protected IntTime.q = *Time\QuadPart
 
  ; Extract millisecond from time And convert time into seconds
  *TimeFields\Milliseconds = (IntTime % #TICKSPERSEC) / #TICKSPERMSEC
  IntTime = IntTime / #TICKSPERSEC                                           
 
  ; Split the time into days And seconds within the day
  Days         = IntTime / #SECSPERDAY
  SecondsInDay = IntTime % #SECSPERDAY
 
  ; Compute time of day
  *TimeFields\Hour = SecondsInDay / #SECSPERHOUR
  SecondsInDay = SecondsInDay % #SECSPERHOUR   
  *TimeFields\Minute = SecondsInDay / #SECSPERMIN
  *TimeFields\Second = SecondsInDay % #SECSPERMIN
 
  ; Compute day of week
  *TimeFields\Weekday = (#EPOCHWEEKDAY + Days) % #DAYSPERWEEK
 
 
  ; Compute year
  CurYear = #EPOCHYEAR
  CurYear + (Days / #DAYSPERLEAPYEAR)
  Days = Days - DaysSinceEpoch(CurYear)
 
  Repeat
    LeapYear = IsLeapYear(CurYear)
    If Days < YearLengths(LeapYear)
      Break
    EndIf
    CurYear+1
    Days - YearLengths(LeapYear)
  ForEver
  *TimeFields\Year = CurYear
   
 
  ; Compute month of year
  Protected Dim MontsArray.i(11)
  MontsArray(0)  = 31
  MontsArray(1)  = 28 + IsLeapYear(CurYear)
  MontsArray(2)  = 31
  MontsArray(3)  = 30
  MontsArray(4)  = 31
  MontsArray(5)  = 30
  MontsArray(6)  = 31
  MontsArray(7)  = 31
  MontsArray(8)  = 30
  MontsArray(9)  = 31
  MontsArray(10) = 30
  MontsArray(11) = 31

  For CurMonth = 0 To 11
    If Days >= MontsArray(CurMonth)
      Days - MontsArray(CurMonth)
    Else
      Break
    EndIf
  Next

  *TimeFields\Month = CurMonth + 1
  *TimeFields\Day   = Days + 1
 
  FreeArray(MontsArray())
EndProcedure
Bernd
Last edited by infratec on Wed Dec 26, 2018 6:59 pm, edited 1 time in total.
Little John
Addict
Addict
Posts: 4527
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: Help find a bug

Post by Little John »

Hi,

I didn't analyze your complete code, but I saw something that should be fixed in any case.

Look at the results of the following demo code:

Code: Select all

Debug 7.5 / 2
x = 7.5 / 2
Debug x
Debug ""

Debug 7.0 / 2
x = 7.0 / 2
Debug x
Debug ""

Debug 6.8 / 2
x = 6.8 / 2
Debug x
Debug ""
Depending on the result of the division, the value that is assigned to x sometimes is rounded up, and sometimes is rounded down.

But e.g. code like this

Code: Select all

  ; Split the time into days And seconds within the day
  Days         = IntTime / #SECSPERDAY
  SecondsInDay = IntTime % #SECSPERDAY
only works correctly, if the result of IntTime / #SECSPERDAY is always rounded down before being assigned to Days! So this code must be

Code: Select all

  ; Split the time into days And seconds within the day
  Days         = Int(IntTime / #SECSPERDAY)
  SecondsInDay = IntTime % #SECSPERDAY
This is just one example. There are more divisions in your code where Int() must be used.

Maybe after fixing this, the whole problem will already be solved.
eBasic
New User
New User
Posts: 5
Joined: Wed Dec 26, 2018 12:34 pm

Re: Help find a bug

Post by eBasic »

infratec wrote:try this
Thanks, works like a charm!
I did some tests with ten millions random dates and get perfect match with windows api output.
Little John wrote:always rounded down
Thx, will keep in mind.
Post Reply