go to post Stuart Salzer · Sep 18 A very informative article. Here are a few things I can add: By setting the TZ environment variable, one can run an InterSystems process in a time zone different from the system’s local time zone. I am in Boston (currently Eastern Daylight Time): $ iris session iris USER>WRITE $ZDATETIME($HOROLOG,3,1)," Boston = ",$ZDATETIME($ZTS,3,1)," UT" 2024-09-18 12:55:55 Boston = 2024-09-18 16:55:55 UT $ TZ=America/Chicago iris session iris USER>WRITE $ZDATETIME($HOROLOG,3,1)," Chicago = ",$ZDATETIME($ZTS,3,1)," UT" 2024-09-18 11:57:22 Chicago = 2024-09-18 16:57:22 UT You can even dynamically change a process time zone by using the “C” callout feature to call tzset() with the new time zone. The documentation mentions $ZTIMEZONE for changing timezones, but this only works if both time zones change between summer and winter time at the same time, which occurs in Europe, but nowhere else. A common problem seen by InterSystems support approximately twice per year is a system that becomes an hour off because it doesn't transition between summer and winter at the correct time. This is almost always the fault of a missing operating system update. We can supply a small “C” program to dump the current time zone rules for Unix, Unix-like, and OpenVMS systems and a PowerShell script for Windows systems.
go to post Stuart Salzer · Sep 18 If you want to wait for a group of child jobs to finish, you can do this with simple (incremental) locks: Each child begins with: LOCK +^JOIN($JOB) SET ^JOIN($JOB)=$HOROLOG and ends with: KILL ^JOIN($JOB) LOCK -^JOIN($JOB) The parent can test that all the children have finished with: LOCK ^JOIN IF $DATA(^JOIN)=0 WRITE !,"One of the children died!" There are lots of ways to expand on this. Add timeouts on the locks. Add a subscript before $JOB in whatever global you use to communicate the process join to have multiple simultaneous process joins. The parent can also look inside the ^JOIN global to diagnose which process died and possibly restart it.
go to post Stuart Salzer · Sep 18 The object script expression: $TRANSLATE($JUSTIFY(input,length)," ","0")
go to post Stuart Salzer · Aug 16 A simpler solution than using SET rs=##CLASS(%ResultSet).%New("%File:FileSet"), is the $ZSEARCH() function: SET f=$ZSEARCH(SDIR) WHILE f'="" { // Do something with f SET f=$ZSEARCH(f) } There is no need to create a child process or use classes.
go to post Stuart Salzer · Jul 12, 2023 Although the interface is clunky for looking through large journal files, the startup cost is negligible: %SYS>DO ^JRNDUMP Select "G" for Goto File: enter your filename which doesn't have to be and shouldn't be in the main journal directory. At this point the interface is limited, but "F" for Find might help.
go to post Stuart Salzer · Feb 5, 2022 If you are trying to run arbitrary code from a Windows .BAT file, the following almost works: ECHO ZN "%%SYS" DO ^^^^SECURITY HALT | C:\InterSystems\Cache\bin\cache.exe -s C:\InterSystems\Cache\mgr The limitations are: You have to specify the locations of the Caché or IRIS executable and the \mgr directory with an -s option. One cannot redirect command input from the terminal, so what you run can't be interactive. Quoting for Windows may annoy you.
go to post Stuart Salzer · Dec 6, 2021 If you are to store the encrypted identifiers in the database, database block encryption is your answer, but if you need to transmit them securely, before encrypting or hashing something as small as a nine-digit number add some salt. That is add enough random digits or letters to make a brute force attack unfeasible. Since you presumably want to be able to later decode these identifiers, use encryption in your case.
go to post Stuart Salzer · Sep 15, 2021 If you don't want to mess with SQL functions, this is still easy, especially taking advantage of date format 8 (ANSI): DOM ; SRS 2021-09-15 PUBLIC DOMAIN NO WARRENTY ; Return $HOROLOG date for first day of the month.FIRST(y,m) QUIT $ZDATEH(y*100+m*100+1,8) ; Return $HOROLOG date for last day of the month.LAST(y,m) SET m=m+1 SET:m>12 m=m-12,y=y+1 QUIT $ZDATEH(y*100+m*100+1,8)-1 Test for this year with: USER>FOR i=1:1:12 WRITE !,i," ",$$FIRST^DOM(2021,i)," ",$$LAST^DOM(2021,i) 1 65745 657752 65776 658033 65804 658344 65835 658645 65865 658956 65896 659257 65926 659568 65957 659879 65988 6601710 66018 6604811 66049 6607812 66079 66109
go to post Stuart Salzer · Sep 13, 2021 Instead of using the first piece with $PIECE(variable,"("), I recommend using $NAME(@variable,0). $NAME has the advantage that it will likely be updated in the event of any changes to introduce new variable syntax, while $PIECE(variable,"(") will remain stuck with the simple syntax. One advantage of $NAME(@varaible,0) is that $NAME will also check the syntax of all the subscripts. One disadvantage is that all subscripts must be defined.
go to post Stuart Salzer · Jun 23, 2021 Yes, but it is unsupported. The expression $ZUTIL(70,2,value) will return value encoded for use as a subscript subject to the current default subscript encoding. You can combine this with $LENGTH(), so $LENGTH($ZUTIL(70,2,value)) to get the length of a subscript once encoded. This technique should never find its way into production code. However, if you just want to understand how various codepoints are encoded, you can use it for experimentation.
go to post Stuart Salzer · Aug 26, 2020 As a rule of thumb disks allocate files in blocks larger than the I/O block. This is called the allocation size or cluster size. I believe the default for large Windows disks is 64 kio. This page has some explanation: <https://docs.microsoft.com/en-us/Exchange/plan-and-deploy/deployment-ref..., search for NTFS allocation unit size.
go to post Stuart Salzer · Jul 15, 2019 I wrote this a while ago. Just ran on a MacBook Pro Mid 2015, 2.8 GHz Intel Core i7.It computed the first 1000 digits in less than 1 minute, 2000 digits in 5 minute, 3000 digits, in 14 minute, 4000 digits in 32 minute, 5000 digits in 60 minute...PI ; SRS 2011-07-18 ; Compute PI using Plouffe and Bellard algorithm, based upon "C" code ; found at <http://iweb.dl.sourceforge.net/project/projectpi/ ; Digit%20Extraction%20Methods/Plouffe%20and%20Bellard%20v1/pi1_f.c>. KILL ^PI,^PRIME SET ^PI=-1 FOR I=0:1:9 JOB C($INCREMENT(^PI)) QUITC(p) NEW $ETRAP SET $ETRAP="DO ^%ETN" SET n=p*9+1 SET bign=+$TRANSLATE(n+20*$ZLN(10)/$ZLN(2),".","!") SET sum=0 SET a=3 FOR QUIT:a>(2*bign) DO SET a=$$NP(a) . SET vmax=+$PIECE($ZLN(2*bign)/$ZLN(a),".") . SET av=1 FOR i=1:1:vmax SET av=av*a . SET s=0,num=1,den=1,v=0,kq=1,kq2=1 . FOR k=1:1:bign DO . . SET t=k . . IF kq'<a FOR SET t=t\a,v=v-1 IF t#a SET kq=0 QUIT . . SET kq=kq+1,num=num*t#av . . SET t=2*k-1 . . DO:kq2'<a . . . IF kq2=a FOR SET t=t\a,v=v+1 QUIT:t#a . . . SET kq2=kq2-a . . SET den=den*t#av,kq2=kq2+2 . . DO:v>0 . . . SET t=$$IM(den,av),t=t*num#av,t=t*k#av . . . FOR i=v+1:1:vmax SET t=t*a#av . . . SET s=s+t . . . SET:s'<av s=s-av . SET t=$$PM(10,n-1,av),s=s*t#av . SET sum=sum+(s/av),sum=+("."_$PIECE(sum,".",2)) SET ^PI(p)=$EXTRACT($PIECE(sum,".",2)_"000000000",1,9) JOB C($INCREMENT(^PI)) QUITNP(a) NEW (a) FOR SET r=$ORDER(^PRIME(a)) QUIT:r'="" DO QUIT:r'="" . LOCK ^PRIME . SET r=$ORDER(^PRIME(a)) IF r'="" LOCK QUIT . IF $DATA(^PRIME)=0 SET ^PRIME=3,^PRIME(2)="",^PRIME(3)="" LOCK QUIT . FOR r=^PRIME:2 DO IF pr SET ^PRIME(r)="" QUIT:r>a . . SET pr=1 FOR p=3:2:$ZSQR(r) IF r#p=0 SET pr=0 QUIT . SET ^PRIME=r . LOCK QUIT rIM(x,y) NEW (x,y) SET u=x,v=y,c=1,a=0 FOR SET q=v\u,t=c,c=a-(q*c),a=t,t=u,u=v-(q*u),v=t QUIT:u=0 SET a=a#y SET:a<0 a=a+y QUIT aPM(a,b,m) NEW (a,b,m) SET r=1,aa=a FOR SET:b#2 r=r*aa#m SET b=b\2 QUIT:b=0 SET aa=aa*aa#m QUIT r
go to post Stuart Salzer · Jun 19, 2019 In most recent versions of Caché, we install a directory dev/cache/callout/demo. This contains demonstration code for writing $ZF() functions. One of the examples is tzchange(), and it works by setting a environment variable in the current process, and it should be easy enough to adapt to your needs. Start by reading czf.pdf.
go to post Stuart Salzer · Dec 6, 2018 Your question does not say the format of the dates. I am answering based upon dd-mm-yyyy, but it is easy enough to change for a different date format. The big mistake in all prior answers is that they contain small errors around leap years. To compute the number of years between to dates in $HOROLOG format use $ZDATE(date2,8)-$ZDATE(date1,8)\10000.So:UNDER2 ; SRS 2018-12-06 SET today=$ZDATE($HOROLOG,8) SET name="" WHILE 1 { SET name=$ORDER(^checker("under2",name)) QUIT:name="" ; [1] SET birthday=$ORDER(^checker("under2",name,"")) ; [2] SET birthday=$ZDATEH($TRANSLATE(birthday,"-","/"),4,,,,,,,"") ; [3] CONTINUE:birthday="" ; [4] SET age=today-$ZDATE(birthday,8)\10000 ; [5] CONTINUE:age'<2 ; [6] WRITE !,name,?22,age," ",$ZDATE(birthday,4,,4) ; [7] } QUIT [1] Advance to next name.[2] Get the first birthday. The structure allows multiple birthdays, but the code only looks at the first birthday.[3] Convert the birthday from dd-mm-yyyy format to $HOROLOG format.[4] Skip badly formatted birthdays.[5] Compute the age in whole years.[6] Skip people whose age is >= 2.[7] Do something with people whose age is less than 2.
go to post Stuart Salzer · Jan 2, 2018 There is nothing built into Caché to get a updating virtual terminal window size like you would have with ncurses. You could...(1) Write the terminal portions of you application in a language with ncurses support (like "C"), and then use either call-in or call-out to combine the ncurses portions of your application with the COS portions. Call-in would probably be easier.(2) If you can live without an automated update, the traditional solution is to assign a key in your application to repaint the screen after modem line noise (remember that) messed-up the screen. Traditionally, that was <CTRL+R>. You could just add to the work done by <CTRL+R> to be. (a) Clear the screen. (b) Position the cursor at the lower right hand corner. (c) Clear the typeahead buffer. (d) Ask the terminal were its cursor is. (e) Read, and parse the Cursor Position Report. The position of the cursor is the window size. (f) Repaint the screen at its current size.Step (a) could be moved later in the sequence if you like.Steps (b) and (c) can be reversed.If you only care about supporting modern terminal emulators, all of which emulate VT-100+ terminals, the code is ;(a) Clear the screen WRITE $CHAR(27),"[H" ;(b) Positions the cursor at the lower right hand corner. WRITE $CHAR(27)_"[255;255H" ;(c) Clear the typeahead buffer. WRITE *-1 ;(d) Ask the terminal where its cursor is: ;Use this routine FC() NEW SET result=0 FOR j=1:1:4 DO QUIT:result ; [10] . WRITE $CHAR(27)_"[6n" ; [20] . READ junk:j QUIT:'$TEST QUIT:$ASCII($KEY)'=27 ; [30] . QUIT:$EXTRACT($KEY,2)'="[" QUIT:$EXTRACT($KEY,*)'="R" . SET $Y=$EXTRACT($KEY,3,*)-1,$X=$PIECE($KEY,";",2)-1 ; [40] . SET result=1 QUIT result ; ------------ ; [10] Assume failure. Try four times (with increasing ; timeout) to determine the location of the cursor. ; Quit early on success. ; [20] Send where is the cursor (DSR Device Status Request). ; [30] Read result, CPR (Cursor Position Report). We must ; get it, and it must be of form "<ESC>[#,#R". ; [40] Extract $Y and $X from the CPR (Cursor Position ; Report), and call it a success. ;(e) Repaint the screen at its current size. ; That is up to you.
go to post Stuart Salzer · Dec 6, 2017 To the above code:#Include <stdio.h> /* For snprintf. */#include <stdarg.h> /* For va_start, va_arg, and va_end. */
go to post Stuart Salzer · Nov 30, 2017 JSON format is probably the most general. Return the values in a JSON formatted string. Then parse with SET objresult=##CLASS(%DynamicObject).%FromJSON(result) However, if you want to return a simple structure. That is two or a few values, where no values are themselves structures, and nature of the data is easily understood, you could return the values as an artificial local reference, and take the value apart with $QSUBSCRIPT() is COS. This function would prove handy for such an option. /* This function adds %q to snprintf() to print a quoted string. */ int varcosreturn (char *buffer, size_t len, char *fmt, ...) { va_list ap; char *p, *q, *r; char c; size_t n; char xfmt [3]; va_start(ap, fmt); p = buffer; q = fmt; for (;;) { c = *q++; if (c == '\0') break; if (c != '%') { if (len==0) break; --len; *p++ = c; continue; } c = *q++; if (c == 'q') { if (len==0) break; --len; *p++ = '\"'; r = va_arg(ap,char*); for (;;) { c = *r++; if (c == '\0') break; if (c == '\"') { if (len==0) break; --len; *p++ = '\"'; } if (len==0) break; --len; *p++ = c; } if (len==0) break; --len; *p++ = '\"'; continue; } xfmt [0] = '%'; xfmt [1] = c; xfmt [2] ='\0'; n = snprintf (p, len, xfmt, va_arg(ap, void*)); len -= n; p += n; } va_end(ap); if (len==0) return -1; --len; *p++ = '\0'; return 0; } $LISTBUID() format is not doucmented so that InterSystems can later expand (or change) it.
go to post Stuart Salzer · Oct 30, 2017 I use QUOTE(x) QUIT $EXTRACT($NAME(%(x)),3,*-1)to quote a string, so just removing the leading and trailing quote is what you want, orDupQuote(x) QUIT $EXTRACT($NAME(%(x)),4,*-2)
go to post Stuart Salzer · Oct 12, 2017 If you are moving from Caché on OpenVMS to Caché on RHEL 7, you can just move the CACHE.DAT files with FTP. That will move everything globals and data.You want to have a clean shutdown of Caché before moving the file. You need to use binary FTP. If your OpenVMS system uses MultiNet rather than TCP/IP services for OpenVMS, be warned that the MultiNet FTP server pays attention to the OpenVMS file attributes even on binary transfers, so be sure your CACHE.DAT has RFM:FIX,RAT:NONE, and MRS: and LRL: as some power of 2 between 512 and 16384 (inclusive).The most common problem, is for customers who previously transitioned from DSM on OpenVMS to Caché on OpenVMS, and used the DSM compatibility $ZF() functions. These do not exist for Caché for other platforms. In fact a few sites, that never used DSM may have discovered these calls, and they may have infiltrated into your code.
go to post Stuart Salzer · Jun 1, 2017 How about this, call with DO ^GETTREE("/home/user/dir/*",.result) and $ORDER() through result.#INCLUDE %sySiteGETTREE(wild,result) ; NEW (wild,result) SET s=$SELECT($$$ISUNIX:"/",$$$ISWINDOWS:"\",1:1/0) ; separator SET w=$SELECT($$$ISUNIX:"*",$$$ISWINDOWS:"*.*") ; wild-card SET todo(wild)="" FOR { SET q=$ORDER(todo("")) QUIT:q="" KILL todo(q) SET f=$ZSEARCH(q) WHILE f'="" { SET t=$PIECE(f,s,$LENGTH(f,s)) QUIT:t="." QUIT:t=".." SET result(f)="" SET todo(f_s_w)="" SET f=$ZSEARCH("") } } QUITFlaws:On my mac, I have some directories so deep, $ZSEARCH() fails.It doesn't work on OpenVMS. As much as you may want to think that you can convert dev:[dir]subdir.DIR;1 to dev:[dir.subdir]*.*;*, and keep searching, there are too many weird cases to deal with on OpenVMS, better to just write a $ZF() interface to LIB$FIND_FILE() and LIB$FIND_FILE_END().