Groups keyboard shortcuts have been updated
Dismiss
See shortcuts

File manager bug

17 views
Skip to first unread message

Carlo Hogeveen

unread,
Apr 12, 2025, 7:59:09 AMApr 12
to Semware @ GoogleGroups

Context:
In a macro I need to exactly set the date-time of one file to that of another file.
To do so I copied the LoSetDateTime() procedure from the "f" macro, TSE's File Manager.
In doing so I also copied a bug, and I have trouble finding a solution.

Bug:
Over here it is currently daylight savings time, which seems relevant.
The date I want to set for a file falls outside the daylight savings time.
Concretely I want to set a file's date-time to 11 Jan 2025 21:27:54.
When I do so using TSE's File Manager the file's date becomes 11 Jan 2025 21:20:54.

It seems relevant, that Microsoft's documentation for the LocalFileTimeToFileTime() function
https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-localfiletimetofiletime
has this Remark:
"LocalFileTimeToFileTime uses the current settings for the time zone and daylight saving time. Therefore, if it is daylight saving
time, this function will take daylight saving time into account, even if the time you are converting is in standard time."

So I think I found what causes the problem, but not being a Windows programmer I have trouble coming up with a proper solution.

Carlo



Eckhard Hillmann

unread,
Apr 12, 2025, 8:33:58 AMApr 12
to Carlo Hogeveen
Hi Carlo,

not sure if this will help.

Try setting the environment variable with
TZ=UTC
before you read and change the file date and delete it with
TZ=
when done.

As far as I know the internal MS-API routines check for the environment TZ first
and if it's not set they default to API GetTimeZoneInformation() from the
current system.

You may try it to see if it gives the result you want.

--
Eckhard


Ihre Nachricht vom Samstag, 12. April 2025 13:57:


CH> Context:
CH> In a macro I need to exactly set the date-time of one file to that of another file.
CH> To do so I copied the LoSetDateTime() procedure from the "f" macro, TSE's File Manager.
CH> In doing so I also copied a bug, and I have trouble finding a solution.

CH> Bug:
CH> Over here it is currently daylight savings time, which seems relevant.
CH> The date I want to set for a file falls outside the daylight savings time.
CH> Concretely I want to set a file's date-time to 11 Jan 2025 21:27:54.
CH> When I do so using TSE's File Manager the file's date becomes 11 Jan 2025 21:20:54.

CH> It seems relevant, that Microsoft's documentation for the LocalFileTimeToFileTime() function
CH> https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-localfiletimetofiletime
CH> has this Remark:
CH> "LocalFileTimeToFileTime uses the current settings for the time zone and daylight saving time. Therefore, if it is daylight saving
CH> time, this function will take daylight saving time into account, even if the time you are converting is in standard time."

CH> So I think I found what causes the problem, but not being a Windows programmer I have trouble coming up with a proper solution.

CH> Carlo



Carlo Hogeveen

unread,
Apr 12, 2025, 8:39:28 AMApr 12
to sem...@googlegroups.com

Argh! Very relevant typo:

"11 Jan 2025 21:27:54 becomes 11 Jan 2025 21:20:54"
should have been
"11 Jan 2025 21:27:54 becomes 11 Jan 2025 20:27:54".

Carlo

@Eckhard, thanks, I will have a look.
@Sammy, FFTimeStr() retrieves the correct time, so maybe TSE already knows the extra step?



S.E. Mitchell

unread,
Apr 12, 2025, 9:00:33 AMApr 12
to sem...@googlegroups.com
void APPCALL GetDosTime(void) {
FILETIME local;
SYSTEMTIME sys_tm, sys_local, sys_tm2;

/* this is one hour off in some situations
FileTimeToLocalFileTime((u.pickfile_flags & USE_LAST_ACCESS_DATE)
? &find_file_info.ftLastAccessTime : &find_file_info.ftLastWriteTime,
&local);
FileTimeToDosDateTime(&local, &dos_date, &dos_time);
*/

FileTimeToSystemTime((u.pickfile_flags & USE_LAST_ACCESS_DATE) ?
&find_file_info.ftLastAccessTime : &find_file_info.ftLastWriteTime,
&sys_tm);
SystemTimeToTzSpecificLocalTime(NULL, &sys_tm, &sys_local);
SystemTimeToFileTime(&sys_local, &local);

FileTimeToSystemTime(&local, &sys_tm2);

//dos_time = (t->tm_sec / 2) | (t->tm_min << 5) | (t->tm_hour << 11);
dos_time = (sys_tm2.wSecond / 2) |
(sys_tm2.wMinute << 5) |
(sys_tm2.wHour << 11);

//dos_date = (t->tm_mday << 16) | ((t->tm_mon + 1) << 21) |
((t->tm_year - 80) << 25);
dos_date = (sys_tm2.wDay) |
((sys_tm2.wMonth) << 5) |
((sys_tm2.wYear - 60) << 9);

// don't use - see above notes.
//FileTimeToDosDateTime(&local, &dos_date, &dos_time);
> --
>
> ---
> You received this message because you are subscribed to the Google Groups "SemWare TSE Pro text editor" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to semware+u...@googlegroups.com.
> To view this discussion visit https://groups.google.com/d/msgid/semware/000601dbaba7%24c4475960%244cd60c20%24%40ecarlo.nl.

S.E. Mitchell

unread,
Apr 12, 2025, 11:27:59 AMApr 12
to sem...@googlegroups.com
This might work - sorry, I have not had time to test it -
unfortunately, yard work is calling :(

dll "<kernel32.dll>"
integer proc GetLastError() : "GetLastError"
integer proc GetDiskFreeSpace(string root_dir:cstrval,
var integer SectorsPerCluster,
var integer BytesPerSector,
var integer NumberOfFreeClusters,
var integer TotalNumberOfClusters) : "GetDiskFreeSpaceA"
integer proc GetDiskFreeSpaceEx(
string lpDirectoryName:cstrval,
var integer lpFreeBytesAvailableToCaller,
var integer lpTotalNumberOfBytes,
var integer lpTotalNumberOfFreeBytes) : "GetDiskFreeSpaceExA"

integer proc LoadLibrary(string libraryname:cstrval) :"LoadLibraryA"

integer proc FreeLibrary(integer dllhandle) :"FreeLibrary"

integer proc GetProcAddress(integer dllhandle, string
procname:cstrval) :"GetProcAddress"

integer proc CreateFile(string fn:cstrval, integer mode, integer
sec, integer attr, integer flags, integer junk1, integer junk2) :
"CreateFileA"
integer proc CloseHandle(integer handle) : "CloseHandle"
integer proc DosDateTimeToFileTime(integer date:word, integer
time:word, string FILETIME:StrPtr) : "DosDateTimeToFileTime"
integer proc LocalFileTimeToFileTime(string in_time:StrPtr, string
out_time:StrPtr) : "LocalFileTimeToFileTime"
integer proc SetFileTime(integer handle,
// string FILETIME_Creation:StrPtr,
integer FILETIME_Creation,
// string FILETIME_LastAccess:StrPtr,
integer FILETIME_LastAccess,
string FILETIME_LastWrite:StrPtr) : "SetFileTime"

integer proc FileTimeToSystemTime(string ft:StrPtr, string
utcSt:StrPtr) : "FileTimeToSystemTime"
integer proc SetFileDateTimeFromDos(string filePath:cstrval,
integer dosDate:word, integer dosTime:word) : "SetFileDateTimeFromDos"
integer proc SystemTimeToFileTime(string utcSt_DST:StrPtr, string
ft_DST:StrPtr) : "SystemTimeToFileTime"
integer proc SystemTimeToTzSpecificLocalTime(integer x, string
utcSt:StrPtr, string st:StrPtr) : "SystemTimeToTzSpecificLocalTime"
integer proc TzSpecificLocalTimeToSystemTime(integer x, string
st:StrPtr, string utcSt_DST:StrPtr) :
"TzSpecificLocalTimeToSystemTime"
end

/*
DosDateTimeToFileTime(dosDate, dosTime, &ft)
FileTimeToSystemTime(&ft, &utcSt))
SetFileDateTimeFromDos(filePath, dosDate, dosTime)
SystemTimeToFileTime(&utcSt_DST, &ft_DST)
SystemTimeToTzSpecificLocalTime(NULL, &utcSt, &st)
TzSpecificLocalTimeToSystemTime(NULL, &st, &utcSt_DST)
*/

#define FILE_FLAG_BACKUP_SEMANTICS 33554432
#define OPEN_EXISTING 3
#define GENERIC_WRITE 0x40000000

integer proc LoSetDateTime(string fn, integer month, integer day, integer year,
integer hours, integer mins, integer secs)
integer handle, Dostime, Dosdate, ok
// these might only need to be 8 chars each
string ft[16] = " ", utcSt[16] = " ",
st[16] = " ", utcSt_DST[16] = " ",
ft_DST[16] = " "

Dostime = (hours shl 11) | (mins shl 5) | (secs / 2)
// Bitfields for file time:
// 15-11 hours (0-23)
// 10-5 minutes
// 4-0 seconds/2
Dosdate = ((year - 1980) shl 9) | (month shl 5) | day
// Bitfields for file date:
// 15-9 year - 1980
// 8-5 month
// 4-0 day
// 1. Convert DOS date and time to FILETIME.
if not DosDateTimeToFileTime(Dosdate, Dostime, ft)
return (warn("Error on DosDateTimeToFileTime()"))
endif

// 2. Convert the FILETIME to SYSTEMTIME (UTC)
if not FileTimeToSystemTime(ft, utcSt)
return (warn("Error on FileTimeToSystemTime()"))
endif

// 3. Convert the UTC time to local time
if not SystemTimeToTzSpecificLocalTime(0, utcSt, st)
return (warn("Error on SystemTimeToTzSpecificLocalTime()"))
endif

// 4. Convert Local Time to UTC with DST
if not TzSpecificLocalTimeToSystemTime(0, st, utcSt_DST)
return (warn("Error on TzSpecificLocalTimeToSystemTime()"))
endif

// 5. Convert the UTC time to a FILETIME.
if not SystemTimeToFileTime(utcSt_DST, ft_DST)
return (warn("Error on SystemTimeToFileTime()"))
endif

// 6. Open the file with write attributes access.
// the two 0's are FILETIME_Creation and FILETIME_LastAccess, but to pass
// 0 they have to have been declared as integers.
handle = CreateFile(fn,
GENERIC_WRITE,
0,
0,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
0
) // GENERIC_WRITE and OPEN_EXISTING
if handle == -1
return (Warn("Error ", GetLastError(), " opening file ", fn))
endif

// 7. Set the last modified time of the file.
ok = SetFileTime(handle, 0, 0, ft_DST)
if not ok
Warn("Error ", GetLastError(), " setting time, handle:", handle)
endif

// 8. Close the file handle.
CloseHandle(handle)
return (ok)
end

Carlo Hogeveen

unread,
Apr 12, 2025, 11:43:14 AMApr 12
to sem...@googlegroups.com

@Eckhard,
Alas, that did not work.

@Sammy,
Based on your earlier mail (thanks!) and the Microsoft documentation, I came up with a simpler version that uses less steps.
It worked on the first test, but needs real testing.
See below.

Carlo


integer proc set_windows_file_date_time(string fn, string yyyy_mm_dd_hh_mm_ss)
integer ok = TRUE
integer handle = 0
string local_time [16] = '1234567890123456'
string system_time [16] = '1234567890123456'
string file_time [ 8] = '12345678'

local_time = Chr(Val(yyyy_mm_dd_hh_mm_ss[ 1: 4]) mod 255) // Year
+ Chr(Val(yyyy_mm_dd_hh_mm_ss[ 1: 4]) / 255) // Year
+ Chr(Val(yyyy_mm_dd_hh_mm_ss[ 6: 2])) // Month
+ Chr(0)
+ Chr(0) // Day of week
+ Chr(0)
+ Chr(Val(yyyy_mm_dd_hh_mm_ss[ 9: 2])) // Day
+ Chr(0)
+ Chr(Val(yyyy_mm_dd_hh_mm_ss[12: 2])) // Hour
+ Chr(0)
+ Chr(Val(yyyy_mm_dd_hh_mm_ss[15: 2])) // Minute
+ Chr(0)
+ Chr(Val(yyyy_mm_dd_hh_mm_ss[18: 2])) // Second
+ Chr(0)
+ Chr(0) // Millisecond
+ Chr(0)

if not TzSpecificLocalTimeToSystemTime(0, local_time, system_time)
Warn('TzSpecificLocalTimeToSystemTime failed.')
ok = FALSE
endif

if ok
and not SystemTimeToFileTime(system_time, file_time)
Warn('SystemTimeToFileTime failed.')
ok = FALSE
endif

if ok
// The 2nd parameter, "dwDesiredAccess" in the Microsoft documentation,
// has the value GENERIC_WRITE, which is 0x40000000.
// The 5th parameter, "dwCreationDisposition" in the Microsoft documentation,
// has the value OPEN_EXISTING, which is 3.
// The 6th parameter is FILE_FLAG_BACKUP_SEMANTICS in f.s, but 0 works the same.
handle = CreateFile(fn, 0x40000000, 0, 0, 3, 0, 0)

if handle == INVALID_HANDLE_VALUE
ok = Warn(MACRO_NAME, ': Error ', GetLastError(), ' opening file ', fn)

// The below two 0's are lpCreationTime and lpLastAccessTime,
// but to pass 0 they have to have been declared as integers,
// as was done in their DLL declaration above.
elseif not SetFileTime(handle, 0, 0, file_time)
ok = Warn(MACRO_NAME, ': Error ', GetLastError(), ' setting time, handle:', handle)
else
CloseHandle(handle)
endif
endif

return(ok)
end set_windows_file_date_time



Carlo Hogeveen

unread,
Apr 12, 2025, 12:03:57 PMApr 12
to sem...@googlegroups.com

@Sammy,
Correction:
In my example code "255" should be "256" in both places, of course.
Carlo



Carlo Hogeveen

unread,
Apr 12, 2025, 1:02:30 PMApr 12
to sem...@googlegroups.com

Sammy,
My solution holds up under more rigorous testing.
It correctly sets date-times that are in and out of daylight saving time and in previous years.
This also works if I change the Windows system date to one that is not in daylight saving time.
Carlo



S.E. Mitchell

unread,
Apr 12, 2025, 1:42:39 PMApr 12
to sem...@googlegroups.com
Great!
Thanks for working on this!
--
Sammy
> --
>
> ---
> You received this message because you are subscribed to the Google Groups "SemWare TSE Pro text editor" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to semware+u...@googlegroups.com.
> To view this discussion visit https://groups.google.com/d/msgid/semware/001501dbabcc%2483093b60%24891bb220%24%40ecarlo.nl.
Reply all
Reply to author
Forward
0 new messages