The way ftime handles its tz parameter is seemingly incorrect. While it performs correctly for the command {ftime:%x %X %Y,{tzoffset}}, it is wrong for other values.
Here is output from mpi commands on a MUCK set to PDT time:
Command: "{ftime:%r %e %B %Y}"
Result: "03:10:14 PM 13 May 2011"
Command: "{ftime:%r %e %B %Y,0}"
Result: "08:10:17 AM 13 May 2011"
Note that in the case where '0' was specified as the number of hours offset from UTC and it subtracted 7 hours from the localtime when it should have added them instead. This example shows a bug with the legacy support for the 'tz' parameter representing hours (instead of seconds) when its value is between 24 and -24.
We still have the bug when the parameter is provided in seconds. Here's an example, using the time adjustment for EST (-14400) on the same MUCK whose timezone is set to PDT:
Command: "{ftime:%r %e %B %Y}"
Result: "04:30:14 PM 13 May 2011"
Command: "{ftime:%r %e %B %Y,-14400}"
Result: "01:30:19 PM 13 May 2011"
And here we have the result using 3600 for one hour ahead of UTC:
Command: "{ftime:%r %e %B %Y}"
Result: "04:31:35 PM 13 May 2011"
Command: "{ftime:%r %e %B %Y,3600}"
Result: "08:31:46 AM 13 May 2011"
The problem is in the math of the mfuns.c inside mfn_ftime(MFUNARGS) function inside mfuns.c:
const char *
mfn_ftime(MFUNARGS)
{
time_t lt;
struct tm *tm;
if \(argc == 3\) \{
lt = atol\(argv\[2\]\);
\} else \{
time\(<\);
\}
if \(argc > 1 && \*argv\[1\]\) \{
int offval = atoi\(argv\[1\]\);
if \(offval < 25 && offval > -25\) \{
lt += 3600 \* offval;
\} else \{
lt -= offval;
\}
lt += get\_tz\_offset\(\);
\}
#ifndef WIN32
tm = localtime(<);
#else
tm = uw32localtime(<);
#endif
format_time(buf, BUFFER_LEN - 1, argv[0], tm);
return buf;
}
Here, lt is initialized to either the current epoch number or the third parameter. Then the second parameter is looked at in order to adjust the value of lt. Finally the lt parameter, which should be some number of seconds from the dawn of computer time in UTC is converted to local time.
The problem is that the math is somewhat wrong: In PDT, get_tz_offset() returns -25200 (for -7 hours). When we specify 0 the offval, we have the following sequence in pseudocode:
lt = now; // In UTC seconds
lt += 3600*0;
lt +=-25200;
tm = localtime(<);
This looks up the localtime for the time it was in Greenwich 7 hours ago. The ONLY example where the code is correct is the one in the reference menu, using tzoffset:
Command: "{ftime:%r %e %B %Y}"
Result: "04:43:39 PM 13 May 2011"
Command: "{ftime:%r %e %B %Y,{tzoffset}}"
Result: "04:43:41 PM 13 May 2011"
This is because the sequence of events here is as follows:
lt = now; // In UTC seconds
lt -=-25200; // Value returned from tzoffset and taking the else branch of the offval if-statement
lt +=-25200;
tm = localtime(<); // lt = now in UTC seconds.
Here the value of the tz parameter (i.e. offval variable) is the same as that returned from get_tz_offset(), which is how {tzoffset} is implemented anyway. The addition and subtraction balance out to zero and we call localtime() on the original result of time() and correctly get the local time.
So the addition and subtraction are incorrect. I think that trying to use localtime() even when we have a tz parameter is a mistake, but that's my own take on clarity. The easy solution is just to fix the signs. Intuitively we have to do the following when we have a tz parameter:
1) Negate the effect of localtime. Localtime is going to add the value of get_tz_offset() so we have to subtract it.
2) Add our offset. We already do this correctly when we have a tz in hours, but subtract erroneously when we're giving one in seconds (we interpret the parameter as specifying seconds when outside the range of -24 and 24).
That's two simple sign changes. So the new code looks like the following:
1530 const char *
1531 mfn_ftime(MFUNARGS)
1532 {
1533 time_t lt;
1534 struct tm *tm;
1535
1536 if (argc == 3) {
1537 lt = atol(argv[2]);
1538 } else {
1539 time(<);
1540 }
1541 if (argc > 1 && *argv[1]) {
1542 int offval = atoi(argv[1]);
1543 if (offval < 25 && offval > -25) {
1544 lt += 3600 * offval;
1545 } else {
1546 lt += offval;
1547 }
1548 lt -= get_tz_offset();
1549 }
1550 #ifndef WIN32
1551 tm = localtime(<);
1552 #else
1553 tm = uw32localtime(<);
1554 #endif
1555 format_time(buf, BUFFER_LEN - 1, argv[0], tm);
1556 return buf;
1557 }
Fixed in FB7.