Bin2Time: Unsigned milliseconds to time format hhh:mm:ss.sss

Tight_Coder_Ex 0 Tallied Votes 226 Views Share

I use GetTickCount in a lot of my applications, so I built this snippet to convert to hours minutes and seconds. Leading extraneous data is excluded from output.

Entry	ECX = Unsigned value of duration in milliseconds
	EDX = Pointer to ASCII output.

 Divisors		dd	3600000, 60000, 1
  
  HrsFmt		db	'%d:', 0
  MinFmt		db	'%02d:', 0
  SecFmt		db	'%06d', 0
  Formats		dd	HrsFmt, MinFmt, SecFmt

    0    57            push    edi
    1    56            push    esi
    2    53            push    ebx

	; Epilog set up for applications loop

    3    52            push    edx			; Preserve initial value
    4    BE <->        mov     esi, Divisors
    9    8BFA          mov     edi, edx
    B    BB <->        mov     ebx, Formats
    10   8BD1          mov     edx, ecx
    12   33C9          xor     ecx, ecx
    14   880F          mov     [edi], cl		; Nullify previous contents
    16   B1 03         mov     cl, 3
    18   51            push    ecx

    19   AD            lodsd				; Get next divisor
    1A   8BC8          mov     ecx, eax
    1C   8BC2          mov     eax, edx
    1E   33D2          xor     edx, edx
    20   F7F9          idiv    ecx
    22   8BC8          mov     ecx, eax
    24   87F3          xchg    ebx, esi
    26   AD            lodsd				; Get pointer to next format string
    27   87F3          xchg    ebx, esi
    29   23C9          and     ecx, ecx
    2B   75 05         jnz     32			; Was quotient null
    2D   803F 00       cmp     byte ptr [edi], 0	; Has anything been written to output
    30   74 0F         je      59

Write next segment of string with applicable format

    32   52            push    edx
    33   51            push    ecx
    34   50            push    eax 
    35   57            push    edi
    36   E8 <->        call    wsprintf			; Make this segment of string
    3B   83C4 0C       add     esp, 0C
    3E   5A            pop     edx
    3F   03F8          add     edi, eax
    41   59            pop     ecx
    42   E2 D4         loopd   19

The last part of output always has 6 characters and positions 1 & 2 must be moved to 0 & 1 and 2
replaced with the decimal point
    		   					
    44   8BCF          mov     ecx, edi
    46   2BF8          sub     edi, eax
    48   66:8B47 01    mov     ax, [edi+1]
    4C   66:AB         stosw
    4E   C607 2E       mov     byte ptr [edi], 2E
    51   5F            pop     edi
    52   2BCF          sub     ecx, edi
    54   803F 30       cmp     byte ptr [edi], 30
    57   75 0A         jnz     63

The possibility that either hours or seconds can have a leading zero and they must
be removed from output by shifting everything upward by 1

    59   8BF7          mov     esi, edi
    5B   51            push    ecx
    5C   57            push    edi
    5D   46            inc     esi
    5E   F3:A4         rep     movsb
    60   5F            pop     edi
    61   59            pop     ecx
    62   49            dec     ecx

Epilog 	ECX = Number of characters in output
	EDX = Unchanged, pointer to ASCII string

    63   8BD7          mov     edx, edi
    65   33C0          xor     eax, eax
    67   5B            pop     ebx
    68   5E            pop     esi
    69   5F            pop     edi
    6A   C3            ret

    6B   =	107 bytes

Dani AI

Generated

A few practical notes and fixes that make the Bin2Time idea by more robust and easier to reuse in both assembly and C++.

First, treat the millisecond value as unsigned and use the unsigned divide instruction. The x86 DIV instruction performs unsigned division and expects EDX:EAX to contain the unsigned dividend; IDIV is the signed variant and has different overflow limits and sign-extension rules. Using IDIV (or failing to set EDX correctly before a DIV) is the common cause of a divide overflow when the millisecond value or an intermediate quotient is large. (manualzilla.com)

Second, pick the right tick source. Classic GetTickCount is a DWORD and will wrap to zero after about 49.7 days; on modern Windows use GetTickCount64 (ULONGLONG) when you need uptime values that never wrap in normal service lifetimes. If your code must run on very old OSes, explicitly detect and handle wrap-around when subtracting tick values. (learn.microsoft.com)

Third, simplify formatting and avoid unsafe string helpers. Rather than printing a combined 6-digit field then shifting bytes to insert a decimal point, compute hours, minutes, seconds and milliseconds numerically and format them with a single safe snprintf or the Win32 StringCchPrintf family. Microsoft marks wsprintf as legacy and recommends the safer StringCch* alternatives; also ensure your destination buffer is sized for the longest possible hhh:mm:ss.sss string. (learn.microsoft.com)

Example (portable C++) — take an unsigned 64-bit millisecond count, split into pieces, then format safely:

std::string ms_to_hms_ms(uint64_t ms) {
    uint64_t total_s = ms / 1000;
    uint64_t hours = total_s / 3600;
    uint64_t minutes = (total_s / 60) % 60;
    uint64_t seconds = total_s % 60;
    uint64_t millis = ms % 1000;
    char buf[64];
    std::snprintf(buf, sizeof(buf), "%llu:%02llu:%02llu.%03llu",
                  (unsigned long long)hours,
                  (unsigned long long)minutes,
                  (unsigned long long)seconds,
                  (unsigned long long)millis);
    return std::string(buf);
}

Troubleshooting tips: test edge cases (0, just below 1s, large values around 2^31 and 2^32, and values that would cause GetTickCount wrap). If keeping an assembly version, document which registers you clobber and prefer DIV + explicit zeroing of EDX for unsigned math.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.