Introduction to CMAC. 8: GetSystemTime & GetLocalTime

Post user created macros to share with others here.

Moderator: Moderators

Introduction to CMAC. 8: GetSystemTime & GetLocalTime

Postby deleyd on Tue Feb 17, 2004 3:40 am

Introduction to CMAC. 8: GetSystemTime

Multi-Edit CMAC has the ability to call external routines provided by the Windows operating system. Here is an example of calling the GetSystemTime routine.

NOTE: GetSystemTime returns Coordinated Universal Time (UTC). It does not take into account what time zone you are in. (i.e. UTC time is 5 hours ahead of Eastern Time; 8 hours ahead of Pacific Time.)

The GetSystemTime function is documented at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/sysinfo/base/getsystemtime.asp

Code: Select all
#include Win32.sh   //Must come before #include MsgLog.sh
                    //(defines struct Tmsg used by MsgLog.sh)
#include MsgLog.sh  //DebugLog is defined here.

/* NOTE: Win32.sh already contains the following,
   so we don't have to include this ourselves:
#define PSystemTime Pointer
struct TSystemTime {
  Word  wYear;
  Word  wMonth;
  Word  wDayOfWeek;
  Word  wDay;
  Word  wHour;
  Word  wMinute;
  Word  wSecond;
  Word  wMilliseconds;
}

import void GetSystemTime( PSystemTime St )
    kernel32 'GetSystemTime';
*/

void testtime()
{
   struct TSystemTime St;
   GetSystemTime( &St );
   DebugLog(2, "testtime",
  "wYear=" + str(St.wYear) +
  " wMonth=" + str(St.wMonth) +
  " wDayOfWeek=" + str(St.wDayOfWeek) +
  " wDay=" + str(St.wDay) +
  " wHour=" + str(St.wHour) +
  " wMinute=" + str(St.wMinute) +
  " wSecond=" + str(St.wSecond) +
  " wMilliseconds=" + str(St.wMilliseconds) );
}
Last edited by deleyd on Tue Feb 17, 2004 11:23 pm, edited 1 time in total.
User avatar
deleyd
Developer
 
Posts: 1020
Joined: Tue Jul 29, 2003 4:27 pm
Location: Santa Barbara, CA

Postby AndyColson on Tue Feb 17, 2004 6:01 pm

Oh Man! You posted this a week too late. That struct would have been usefull in my time diff macro's. Hey, did'ja see any windows api's for calculating/adjusting that time struct?

-Andy
User avatar
AndyColson
Registered User
 
Posts: 170
Joined: Sat Jul 26, 2003 1:29 pm
Location: Marion, IA

Postby deleyd on Tue Feb 17, 2004 11:36 pm

Here's a better example, this time using GetLocalTime. (If I had used GetLocalTime above I wouldn't of had to explain about UTC time and time zones.)

The GetLocalTime function is documented at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/sysinfo/base/getlocaltime.asp

Note that again the structure TSystemTime is already defined in Win32.sh . However, this time we have to add our own import statement because it doesn't happen to already be in Win32.sh . (Usually you have to add your own import statement. In my original example it just happened to already exist in Win32.sh .)

After obtaining the local time we have a little fun displaying the approximate time as a message. (This demonstrates the use of the switch statement.)

Code: Select all
#include Win32.sh   //Must come before #include MsgLog.sh
                    //(defines struct Tmsg used by MsgLog.sh)
#include MsgLog.sh  //DebugLog is defined here.

/* NOTE: Win32.sh already contains the following,
   so we don't have to include this ourselves:
#define PSystemTime Pointer
struct TSystemTime {
  Word  wYear;
  Word  wMonth;
  Word  wDayOfWeek;
  Word  wDay;
  Word  wHour;
  Word  wMinute;
  Word  wSecond;
  Word  wMilliseconds;
}
*/
import void GetLocalTime( PSystemTime St )
    kernel32 'GetLocalTime';

//MACRO TESTTIME
void testtime()
{
   struct TSystemTime St;
   GetLocalTime( &St );
   DebugLog(2, "testtime",
  "wYear=" + str(St.wYear) +
  " wMonth=" + str(St.wMonth) +
  " wDayOfWeek=" + str(St.wDayOfWeek) +
  " wDay=" + str(St.wDay) +
  " wHour=" + str(St.wHour) +
  " wMinute=" + str(St.wMinute) +
  " wSecond=" + str(St.wSecond) +
  " wMilliseconds=" + str(St.wMilliseconds) );

  /* NOW LET'S HAVE SOME FUN DISPLAYING THE TIME OF DAY
  * Lets you know what time it is in a rather mellow manner.
  * Inspired by a letter in the DEC Professional
  * (January 1986, p. 56)
  * Adapted from .COM file Decus submission
  * by Mic Kaczmarczik 08-Jan-86
  * Translated to TPU by David Deley February, 1988
  * Translated to CMAC by David Deley February 2004
  */
  str Oclock,
      About,
      Zone,
      Timestr;
  int Z,
      Hour,
      Minute;

  //INITIALIZE VARIABLES
  Hour = St.wHour;
  Minute = St.wMinute;
  Oclock = "";
  About = "About";

  //Round to the nearest 5 minutes
  //  Remeber to use extra parenthesis in expressions!
  //  CMAC does not have operator precedence as we're used to
  //  CMAC is strictly left-to-right
  Z = ((2 * Minute) + 5) / 10; //Remember extra parenthesis!
  If (Z > 6) {Hour += 1;}      //Round to next hour

  //ROUND IT OFF
  If ( (Z * 5) == Minute )
  {
        About = "Exactly";
  }
  else
  {
    if ( (Z * 5) > Minute )
    {
       About = "Almost";
    }
  }

  //GET TRANSLATION
  switch (Z)
  {
    case  0 :
    case 12 :
      Zone = "";
      if ((Hour != 0) && (Hour != 12)) {Oclock = " o'clock";}
      break;
    case  1 : Zone = " five after"; break;
    case  2 : Zone = " ten after"; break;
    case  3 : Zone = " quarter after"; break;
    case  4 : Zone = " twenty after"; break;
    case  5 : Zone = " twenty five after"; break;
    case  6 : Zone = " half past"; break;
    case  7 : Zone = " twenty five to"; break;
    case  8 : Zone = " twenty to"; break;
    case  9 : Zone = " quarter to"; break;
    case 10 : Zone = " ten to"; break;
    case 11 : Zone = " five to"; break;
  }

  switch (Hour)
  {
    case  0 :
    case 24 : Timestr = "midnight"; break;
    case  1 :
    case 13 : Timestr = "one"; break;
    case  2 :
    case 14 : Timestr = "two"; break;
    case  3 :
    case 15 : Timestr = "three"; break;
    case  4 :
    case 16 : Timestr = "four"; break;
    case  5 :
    case 17 : Timestr = "five"; break;
    case  6 :
    case 18 : Timestr = "six"; break;
    case  7 :
    case 19 : Timestr = "seven"; break;
    case  8 :
    case 20 : Timestr = "eight"; break;
    case  9 :
    case 21 : Timestr = "nine"; break;
    case 10 :
    case 22 : Timestr = "ten"; break;
    case 11 :
    case 23 : Timestr = "eleven"; break;
    case 12 : Timestr = "noon"; break;
  }

  //DISPLAY THE TIME
  make_message(About + Zone + " " + Timestr + Oclock + ".");
}


Note: Another easier way to get the current time is to just use the CMAC Time function:
Code: Select all
make_message(Time);
User avatar
deleyd
Developer
 
Posts: 1020
Joined: Tue Jul 29, 2003 4:27 pm
Location: Santa Barbara, CA

Postby deleyd on Wed Feb 18, 2004 1:06 am

By now you're asking, "How can I convert a weekday number and month number into a weekday name and month name?"

As far as I know, CMAC does not support "an array of strings". Instead, here's an old programming trick that does the job nicely. This demonstrates the use of:

1. The CMAC copy function for extracting a substring from a string.
2. The CMAC Shorten_Str function for trimming off trailing blanks.
3. The CMAC C-like sprintf macro.

Code: Select all
#include Win32.sh   //Must come before #include MsgLog.sh
                    //(defines struct Tmsg used by MsgLog.sh)
#include MsgLog.sh  //DebugLog is defined here.
#include SPrintF.sh //Defines the SPrintF macro

/* NOTE: Win32.sh already contains the following,
   so we don't have to include this ourselves:
#define PSystemTime Pointer
struct TSystemTime {
  Word  wYear;
  Word  wMonth;
  Word  wDayOfWeek;
  Word  wDay;
  Word  wHour;
  Word  wMinute;
  Word  wSecond;
  Word  wMilliseconds;
}
*/
import void GetLocalTime( PSystemTime St )
    kernel32 'GetLocalTime';

//MACRO TESTTIME
void testtime()
{
   struct TSystemTime St;
   GetLocalTime( &St );
   DebugLog(2, "testtime",
  "wYear=" + str(St.wYear) +
  " wMonth=" + str(St.wMonth) +
  " wDayOfWeek=" + str(St.wDayOfWeek) +
  " wDay=" + str(St.wDay) +
  " wHour=" + str(St.wHour) +
  " wMinute=" + str(St.wMinute) +
  " wSecond=" + str(St.wSecond) +
  " wMilliseconds=" + str(St.wMilliseconds) );

/*
wMonth - The month; January = 1, February = 2, and so on.
wDayOfWeek - The day of the week; Sunday = 0, Monday = 1,...
*/
  str weekday_names = "Monday   " + // 0
                      "Tuesday  " + // 1
                      "Wednesday" + // 2
                      "Thursday " + // 3
                      "Friday   " + // 4
                      "Saturday " + // 5
                      "Sunday   ";  // 6
  int weekday_name_length = 9;

  str month_names = "January  " + //  1
                    "February " + //  2
                    "March    " + //  3
                    "April    " + //  4
                    "May      " + //  5
                    "June     " + //  6
                    "July     " + //  7
                    "August   " + //  8
                    "September" + //  9
                    "October  " + // 10
                    "November " + // 11
                    "December ";  // 12
  int month_name_length = 9;

  str weekday;
  str month;
  str msgstr;

  weekday = copy( weekday_names, (1 + (St.wDayOfWeek * weekday_name_length)), weekday_name_length);

  month = copy( month_names, (1 + ((St.wMonth-1) * month_name_length)), month_name_length);

  weekday = Shorten_Str(weekday); //Trim any trailing blanks
  month = Shorten_Str(month);     //Trim any trailing blanks

  //Demonstration of C-like sprintf macro
  SPrintF(msgstr, "%s, %s %u", weekday, month, St.wDay);
  make_message(msgstr);

  //Note: the last two lines, SPrintF & make_message, could
  //have been combined into the following single line
  //by using the "messagef" macro also defined in SPrintF.sh:
  //   messagef("%s, %s %u", weekday, month, St.wDay);
}


See file SPrintF.s in the Multi-Edit\src folder to see how the SPrintF macro works. (Note: a bug in the processing of the seldom used %f control code will be fixed in version 9.10.02)
Last edited by deleyd on Sat Apr 24, 2004 6:14 pm, edited 1 time in total.
User avatar
deleyd
Developer
 
Posts: 1020
Joined: Tue Jul 29, 2003 4:27 pm
Location: Santa Barbara, CA

Postby ReidSweatman on Wed Feb 18, 2004 8:11 am

I'll also toss out a minor caveat in regard to Microsoft's API time functions: none of them are accurate enough for certain kinds of work, because of fundamental problems in the implementations, the vagaries of an event-driven messaging system, and problems in the x86 silicon itself. Because of this, the best you can reasonably expect is an accuracy of about 1/1000 second, with considerable unpredictable drift. While that may seem like a lot, for many purposes it isn't nearly enough. You also have to be aware that there are actually two families of Windows time functions, and the other one will give you no more accuracy than the old DOS 1/18.2 second timer interrupt, which it was designed to mimic. Top of my head, I don't recall which functions belong to which family, but if you follow the link David gave, it should tell you there. Or install Microsoft's latest Platform SDK; it's a free download, and is loaded with tons of hard-core programming info. Only problem is, it's roughly 150 MBytes to download, and more than a Gigabyte expanded, so you'll need to have a lot of disk space to spare.

The other possibility that sounds really good turns out not to be. That's using the time-stamp opcodes, which are documented by Intel as maintaining a count of clock cycles since the last system restart. A few years ago I had an interesting chat with the guy who designed that silicon for Intel (and similar silicon for Motorola), and he gave the myriad reasons why it isn't what it seems. Besides the fact that you can't count on finding that function on all x86-compatible chips, since it's not officially supported, it isn't actually a timer at all. In fact, all it guarantees is a monotonically-increasing counter (up to its size-modulus roll-over point), with no guaranteed relationship to real time. It was intended merely to provide a unique timestamp for Windows NT processes, not an absolute time. This is further complicated by the fact that different parts of the CPU core run at different speeds, and can change speeds depending on certain conditions (for instance, parts of the core can drop to lower bandwidth to conserve power during system hibernation; there are many instances when it can happen even during normal CPU operation). So while you can build a timer more accurate than any of the Windows API functions using the RDTSC opcode, it isn't as accurate as you'd think. Still, I used to use that in the timing code for systems of differential equations in physical simulations (i.e., games :) ), and it was generally adequate for that purpose. Obviously, such an approach will have more problems on some more modern chipsets, particularly those designed for mobile use, as power is a prime concern there.

Anyway, for most practical applications you'll find yourself doing in CMac, the functions David mentions will be plenty adequate. I mention all this only to keep anyone from trying to write, say, scientific code using those functions, as it would not give the expected results. This is true in general Windows programming too, BTW.

The sad truth is that if you require that kind of timing accuracy, your machine will have to have external hardware to provide it. I once tried to talk a couple of AMD engineers at a seminar into adding a true counter to their silicon (and a good non-pseudo-random number source, as well), but it ain't there yet, so I guess they were just letting the words bounce off while waiting for the buffet. :) The Intel guy I talked to told me he'd argued strongly when designing the TSC silicon for making it a true counter, but they were at their component density limits for a reasonable chip yield at the time, and didn't want to give up silicon elsewhere on the chip. Such is life.
Reid Sweatman
User avatar
ReidSweatman
Developer
 
Posts: 455
Joined: Tue Jul 01, 2003 8:35 pm
Location: 2000 Light-years from Home

String Arrays

Postby gwhite on Fri Feb 20, 2004 7:25 pm

deleyd wrote:<snip>...
As far as I know, CMAC does not support "an array of strings".
<snip>...

Not directly, but what is a text file but an array of strings? (OK, with some overhead. :wink: ) To use the following macro file, first declare and "Create" a str_array (supplying a unique filename). "Put" strings in the array, then "Get" each string or "Clear" as needed.

Here's the prototype in "String_Array.sh":

Code: Select all
struct Str_Array {
  int win_id;
  int len;
}

prototype String_Array {

  void Create_Str_Array(
    str name,
    struct Str_Array sa
  );

  void Clear_Str_Array(
    struct Str_Array sa
  );

  void Put_Str_Array(
    int index,                            // > 0
    str elem,
    struct Str_Array sa
  );

  str Get_Str_Array(
    int index,                            // > 0
    struct Str_Array sa
  );


And here's the macro file:

Code: Select all
macro_file String_Array;

#include METools.sh                     // for CreateUserPath
#include MEW.sh
#include String_Array.sh

void Create_Str_Array(
  str filename,
  struct Str_Array sa
) trans2 {
  int start_win_id = Window_ID;

  // from db.s:
  if (Get_Path( filename) == '') {
    filename = User_Path + filename;
    if (Switch_File(filename) == false) {
      filename = CreateUserPath(Truncate_Path(filename), TRUE );
    }
  }
  filename = Caps(FExpand(filename));
  Error_Level = 0;
  if(  NOT(Switch_File(filename))  ) {
    Create_Window;
    Window_Attr = _wa_System;
    if ( Error_Level != 0 ) {
      RM('MEERROR^Beeps /C=1');
      ret;
    }
    File_Name = filename;
  }
  rm('block^selectall');
  Delete_Block;
  sa.len = 0;

  Window_Attr = _wa_SystemHidden;
  sa.win_id = window_id;

  Switch_Win_Id(start_win_id);
}

void Clear_Str_Array(
  struct Str_Array sa
) trans2 {
  int start_win_id = Window_ID;

  Switch_Win_Id(sa.win_id);
  rm('block^selectall');
  Delete_Block;
  sa.len = 0;
  Switch_Win_Id(start_win_id);
}

void Put_Str_Array(
  int index,                            // > 0
  str elem,
  struct Str_Array sa
) trans2 {
  int start_win_id = Window_ID;

  Switch_Win_Id(sa.win_id);
  GoTo_Line(index);
  Put_Line(elem);
  if ( index > sa.len) {
    sa.len = index;
  }
  Switch_Win_Id(start_win_id);
}

str Get_Str_Array(
  int index,                            // > 0
  struct Str_Array sa
) trans2 {
  return (Get_Line_From_Win(index, TranslateWindowID(sa.win_id)));
}
gwhite
Registered User
 
Posts: 10
Joined: Tue Feb 03, 2004 12:40 am
Location: Thousand Oaks, CA

Postby ReidSweatman on Fri Feb 20, 2004 9:21 pm

Good point. As it happens, Multi-Edit uses this technique internally for some things, such as the bit of DB.s you've incorporated. If you want to get really bizarre with it, you can extend the idea to handle multi-dimensional arrays, or do fast lookups on very large arrays via hashing, or any other common indexing scheme. Not saying you'd likely want to, mind, but it's possible. :) Basically, you can use a hidden window like this as a scratchpad for just about anything.

One other option you might look at for faking an array of strings is to use the Str_To_Struct() and Struct_To_Str() macros, which pack the contents of a structure into a string and vice versa. So long as everything can fit into the 16 KByte limit on Multi-Edit strings, you're fine. This is another trick used often in the program.
Reid Sweatman
User avatar
ReidSweatman
Developer
 
Posts: 455
Joined: Tue Jul 01, 2003 8:35 pm
Location: 2000 Light-years from Home

Postby deleyd on Fri Apr 23, 2004 4:17 pm

ReidSweatman gives an example of using Str_To_Struct() and Struct_To_Str() at
http://www.multieditsoftware.com/forums/viewtopic.php?p=2162#2162

He shows how to store a structure in a global string variable. (NOTE: Strings, both global and local, are limited to MAX_LINE_LENGTH, currently 16,383 characters.)
User avatar
deleyd
Developer
 
Posts: 1020
Joined: Tue Jul 29, 2003 4:27 pm
Location: Santa Barbara, CA

Postby deleyd on Tue Mar 28, 2006 4:46 pm

Some routines provided by the Windows operating system come in both a wide char version (unicode) and an ANSI/ASCII version. If under "Function Information" you see the statement "Implemented as ANSI and Unicode versions" then you need to specify which version you want to import. Append a W to the last parameter of the import statement if you want the wide char (unicode) version, or append an A if you want the ANSI/ASCII version. For Multi-Edit versions 9 and 9.10 append an A to get the ANSI/ASCII version.

Here's an example test program which obtains the path to the user's My Documents folder:

Code: Select all
import int SHGetFolderPath(
    int hwndOwner,
    int nFolder,
    int hToken,
    DWORD dwFlags,
    *asciiz pszPath )
Shell32 'SHGetFolderPathA';

#define NULL 0
#define MAX_PATH 260
#define CSIDL_PERSONAL 0x0005      /*  My Documents */
#define SHGFP_TYPE_CURRENT 0

void test()
{
  int Result;
  str path[MAX_PATH];

  Result = SHGetFolderPath(
        NULL,
        CSIDL_PERSONAL,
        NULL,
        SHGFP_TYPE_CURRENT,
        path);

  If (Result != 0)
  {
    path = "";
  }
  else
  {
    path = AddTrailingSlash(path);
  }

  make_message("Result="+str(Result)+" path="+path);
}



Note on the import statement the last parameter is 'SHGetFolderPathA' to specify we want the ANSI/ASCII version, not the unicode version.

Also note MAX_PATH is 260. My experiments indicate the longest file path/name combination currently allowed on my Windows XP is 259 characters. I believe we need to allocate one extra char for the trailing NULL since this is an ASCIIZ string, so we have MAX_PATH set to 260.

I got my info from the following URLs:

SHGetFolderPath Function
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/shell/reference/functions/shgetfolderpath.asp

CSIDL
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/shell/reference/enums/csidl.asp

HOW TO USE THE SHGETFOLDERPATH FUNCTION FROM VISUAL BASIC
http://support.microsoft.com/default.aspx?scid=kb;en-us;252652
User avatar
deleyd
Developer
 
Posts: 1020
Joined: Tue Jul 29, 2003 4:27 pm
Location: Santa Barbara, CA


Return to User Created Macros

Who is online

Users browsing this forum: No registered users and 0 guests