Search code examples
pythontimerounding

Function to Convert Decimal Time to Normal Time is Not Working Properly


I have the following Python functions to convert normal time into decimal time and vice versa:

import math

def std_to_dec(hour, minute, second):
    dec_total_seconds = (3600*hour+60*minute+second)/0.864
    dec_hour = int(str(dec_total_seconds/float(10000))[0])
    dec_minute = int(str(dec_total_seconds/float(10000))[2:4])
    dec_second = int(str(dec_total_seconds/float(10000))[4:6])
    return [dec_hour, dec_minute, dec_second]

def dec_to_std(hour, minute, second):
    std_total_seconds = (10000*hour+100*minute+second)*0.864
    std_hour = math.floor(std_total_seconds/float(3600))
    std_minute = math.floor((float(str(std_total_seconds/float(3600))[str(std_total_seconds/float(3600)).find('.'):]))*60)
    std_minute_dec = (float(str(std_total_seconds/float(3600))[str(std_total_seconds/float(3600)).find('.'):]))*60
    std_second = math.floor((float(str(std_minute_dec)[str(std_minute_dec).find('.'):]))*60)
    return [std_hour, std_minute, std_second]

When I do print(std_to_dec(18, 21, 47)) I get [7, 65, 12], which is all fine. But when I try to do it vice versa, print(dec_to_std(7, 65, 12)), I get [18, 21, 46], which is not the same.

Additionally, std_to_dec(0, 0, 0) returns the error:

dec_second = int(str(dec_total_seconds/float(10000))[4:6]) ValueError: invalid literal for int() with base 10: ''

dec_to_std(0, 0, 0) works though.


Solution

  • By using the string representation your code expects to have a string with at least 6 characters (which is not always the case) and ignores any characters from the 7th character onwards, losing precision, and leading to differences when converting back.

    A core problem however is that standard time has 60*60*24 = 86400 possible times, while decimal time has 10*100*100 = 100000 (more) possible times. By consequence there is no 1-to-1 mapping possible, as then you would need an equal number of possibilities at both sides. However, as standard time has less possibilities, it must be possible to do that mapping as you require, but that means that some possibilities for decimal time will not occur.

    Here is what you could use to always get the same standard time back when having it converted to decimal time and back:

    def std_to_dec(hour, minute, second):
        dec = round((3600*hour+60*minute+second)/0.864)
        dec_second = dec % 100
        dec //= 100
        dec_minute = dec % 100
        dec //= 100
        dec_hour = dec
        return [dec_hour, dec_minute, dec_second]
    
    def dec_to_std(hour, minute, second):
        std = round((10000*hour+100*minute+second)*0.864)
        std_second = std % 60
        std //= 60
        std_minute = std % 60
        std //= 60
        std_hour = std
        return [std_hour, std_minute, std_second]
    

    With this solution it is not possible to expect the inverse: there are decimal times such that if you would convert them to standard time and back, they don't result in the same decimal time. Again, this is because there are more decimal times than standard times (when only using integers of course).

    For instance:

    std_to_dec(0, 0, 3) == [0, 0, 3]
    std_to_dec(0, 0, 4) == [0, 0, 5]
    

    This means that decimal time [0, 0, 4] will not occur, and also means that if you do the inverse, starting with that decimal time, you cannot get it back:

    dec_to_std(0, 0, 4) == [0, 0, 3]