Julius Kavay · Nov 8, 2021 go to post

I know exactly nothing about HealthShare... so I can just suggest two ways to remove the unwanted characters from a string:

use $zstrip()

set inpData="some wild sequence of characters"
set cleanData = $zstrip(inpData, "*C") // this removes all control characters (0x00-0x1f, 0x7f-0x9f)

the other way is to define a set of valid characters and remove all others

set inpData="some wild sequence of characters"
set validChars = "012...89ABC..Zabc..z..."
set badChars = $translate(inpData, validChars) // remove from input all valid chars, leftover are bad chars
set cleanData = $translate(inpData, badChars) // remove all the bad chars

or just the short version

set cleanData = $translate(inpData, $translate(inpData, validChars))
Julius Kavay · Nov 4, 2021 go to post

Oh, one have to hover over the function name to see, that's a link! Maybe I have to blame my monitor because of the more grayish then bluish color.

Julius Kavay · Nov 4, 2021 go to post

Usually, I solve such problems (it's faster then searching for some funy SQL or other functions) by writing my own function/method/expression, depending on the current requirement. 

ClassMethod TimeZoneToHorolog(tz)
{
   set t=$zdth(tz,3,5), t=t*86400+$p(t,",",2)+($e(tz,20,22)*60+$e(tz,23,24)*60)
   quit t\86400_","_(t#86400)
}

Assuming, tz contains a timezone formatted string like:

2021-11-04T11:10:00+0300
Julius Kavay · Nov 4, 2021 go to post

According to documentation,  the tformat paramer 5 is ignored:

"Specify time in the form "hh:mm:ss+/-hh:mm" (24-hour clock). The time is specified as local time. The following optional suffix may be supplied, but is ignored: a plus (+) or minus (–) suffix followed by the offset of local time from Coordinated Universal Time (UTC). A minus sign (-hh:mm) indicates that the local time is earlier (westward) of the Greenwich meridian by the returned offset number of hours and minutes. A plus sign (+hh:mm) indicates that the local time is later (eastward) of the Greenwich meridian by the returned offset number of hours and minutes."

The same goes for the parameter values 6, 7 and 8

write $zdth("2021-11-04T11:10:00+0100",3,5)  --> 66052,40200
write $zdth("2021-11-04T11:10:00+0200",3,5)  --> 66052,40200
write $zdth("2021-11-04T11:10:00-0100",3,5)  --> 66052,40200
Julius Kavay · Nov 3, 2021 go to post

That part of the question was "out of my viewport". Yes, that's an important point.

Julius Kavay · Nov 3, 2021 go to post

You asked for "cached routines"... the above clear buffer function, as far, as I know, resets the global buffers only, not the routine buffers. Just for completeness. Maybe someone with a more detailed knowledge could me agree or disagre...

Julius Kavay · Nov 3, 2021 go to post

It's a funny request... everybody wants to get a job done as fast as possible, but you want the opposite ;-)

You can clear all buffers, as Vitaliy Serdtsev  suggested, with empty buffers you will get (more or less) constante times but keep in mind, this affects (slows down) all other jobs too!

Just my2cc

Julius Kavay · Oct 30, 2021 go to post

OK, I start with the second question. I'm not aware of a function to see if a specific global is in a buffer or not but there is a routine which shows which globals are using the most buffers:

znspace "%SYS"
do ^GLOBUFF

For the first question: if a global is used continuously, then it will always be in buffer. That's the simple answer. The reality depends on many other factors: the size of the global, the size of the buffer pool, how many other globals are in use, how often is a global used, etc.

To keep a few specific global(s) always in a buffer, there is a simple trick (assuming, your Cache/IRIS installation uses the default setup and you have an unused block size):

1) Goto SystemAdministration-->Configuration-->AdditionalSettings-->Startup: and edit the DBSizesAllowed setting, by checking one of the 16K or the 32K checkboxes

2) Create a new database with the newly enabled block size. This database will hold those few (always needed) globals.

3) Goto SystemAdministration-->Configuration-->SystemConfiguration-->MemoryAndStartup: and allocate (plenty of) memory for the newly created buffersize. Please consider,  after this chanhe, you have to RESTART your system!

4) Copy the global(s) in question into the newly created database:

  merge ^|"^^c:\path_to_new_database\"|GlobalName = ^|"^^c:\path_to_old_database\"|GlobalName

5) Create a Global mapping for the globals in question to the new location.

6) Start working... If everything is OK (which should be) and you are happy, delete the old global data to free up database space:   

kill merge ^|"^^c:\path_to_old_database\"|GlobalName

7) In a standard installation, you have allocated  one buffer pool (with the standard 8KB buffer size). So all your processes faiting to get the needed globals into that buffer pool.

With the above configuration you have two buffer pools, one for the standard 8KB database blocks and one for the new 16KB (or wahtever size you have choosen) database blocks. So you can keep important globals in a separate buffer pool. If you can manage (this will be application dependent) to give this buffer pool the same size as the database itself, the you will have all data (of this database) in the memory all day long.

Julius Kavay · Oct 22, 2021 go to post

I fear  you have to be some kind of a magician, to solve this problem...
You need  two things (a) a time-zone-offset, which is not the problem (it's more or less static) and (b) DST-offset, which is a problem, because there are databases for the past but not for future. Maybe you can put the DST-offset into a global for each of the geographic region you need. And yes, you have to maintain it...

Some starting points:  https://en.wikipedia.org/wiki/Tz_database  and http://web.cs.ucla.edu/~eggert/tz/tz-link.htm. In case you you work with python, take a look at https://pypi.org/project/pytz/

The README file from tz_database says the problem in a nutshell:
"The Time Zone Database (called tz, tzdb or zoneinfo) contains code and data that represent the history of local time for many representative locations around the globe.  It is updated periodically to reflect changes made by political bodies to time zone boundaries, UTC offsets, and daylight-saving rules."

Julius Kavay · Oct 15, 2021 go to post

The question was (see the original post),  why does a command work in a terminal but not in a method. And, as you know, the answer lies in the scoping. I would say, he (@David Hockenbroch) tries to learn and understand the nature of indirection and is not working on a production grade problem.

But maybe I'm wrong... who knows.

Julius Kavay · Oct 15, 2021 go to post

He (@David Hockenbroch) is playing with inderection, using $classmethod() instead of ##class(classname).methodname(...)  does not solve the scoping problem:

ClassMethod testvalidator(class As %String, value As %String) As %Status
{  
  set validator = "sc = $classmethod(class, ""IsValid"", value)"
   write validator,!
   set @validator
   write sc,!
   quit sc
}

The above method gives you the same <UNDEF> error because of non global scoping! By using indirection both variables (validator and sc) must have global scope.

Julius Kavay · Oct 15, 2021 go to post

As @Sergei Shutov pointed out, you can switch off the procdere block by a keyword for the whole class. Additionaly, you can switch on or off the procedure block keyword for a particular  method too. In your case:

class Some.Class  Extends %RegisteredObject
{
/// a procedure block method
ClassMethod ProcBlock()
{
}

/// a nonprocedurblock method
ClassMethod NoProcBlock() [ ProcedureBlock = 0 ]
{
// Caution: All variables have a global scope, hence, they will overwrite variables with the same name, which were created previously. To avoid this, use the NEW command, to protect them (if desired).
}

}
Julius Kavay · Oct 14, 2021 go to post

You have a problem with the scoping!

Indirection has a global scoping, you have to put things with indirection in a global scope:

ClassMethod testvalidator(class As %String, value As %String) As %Status [ PublicList = (validator, sc) ]
{
   new validator, sc
   set validator = "sc = ##class("_class_").IsValid("""_value_""")"
   write validator,!
   set @validator
   write sc,!
   quit sc
}
set result = ##class(...).testvalidator("%Library.Numeric","BLABLA")
do $system.OBJ.DisplayError(result) --> ERROR #7207: Datatype value 'BLABLA' is not a valid number
Julius Kavay · Oct 11, 2021 go to post

According to the task, "...You will receive an integer number and you will return a new number..."

set s=9999999999999999999
write s --> 10000000000000000000
write AddWater(s) --> 1  // which is the expected result

The above method works also for cases, where s contains a string of digits

set a="9999999999999999999"
write a  --> 9999999999999999999
set res=AddWater(s)
write res ---> 999...<165 more nines>...999
write $length(s) --> 19
write $length(res) --> 171  // 19 * 9 = 171

So why do you show those devils?

Julius Kavay · Oct 8, 2021 go to post

Oh, I see right now, we can save one byte...

ClassMethod AddWater(s)
{
   f i=$l(s):-1:1 s t=$e(s,i),$e(s,i)=10**t-1/9*t
   q $tr(s,0)
}
Julius Kavay · Oct 8, 2021 go to post

Check this one...

ClassMethod AddWater(s)
{
   f i=$l(s):-1:1 s t=$e(s,i),$e(s,i)="1E"_t-1/9*t
   q $tr(s,0)
}
Julius Kavay · Oct 7, 2021 go to post

%FromJSON is not a class, it's a method of %DynamicObject and is available in IRIS and for Cache  version 2016.2 and later.

Julius Kavay · Oct 2, 2021 go to post

There is one thing you should check, than this could trigger effects observed by you.
Objects are tracked by reference counts, as long as an objects reference count is greater then one, locks won't be released and the object isn't deleted.

set obj = ##class(Some.Class).%OpenId(id, 4) // the obj's ref count is one
... // more commands
    // now, the application does something like this
set tmp = obj    // obj's ref count is now two!
... // more commands

set obj = "" // the application intents to close the object
             // but the object still exists due to the fact that the ref count is one
             // (the object is still referenced by <tmp>)

There are methods to detect such a situation:
- $system.OBJ.ShowObjects(), lists all objects with reference counts
- $system.OBJ.ShowReferences(obj), list all variables which contains a reference to <obj>

A quick and dirty approach:

set filename = "...some file name"
open filename:"nw":0
if $t { use filename
        do $system.ShowObjects()
        do $system.ShowReferences(obj) 
        close filename
      }
set obj = "" 

Give it a try, maybe your object has multiple references which cause the problem

Julius Kavay · Sep 28, 2021 go to post

I don't know exactly what you mean, maybe you have a example for us?

Something like:

DateFrom: 2021-09-27  DateThru: 2021-09-28
Expected time range:  from ???? thru ????
Julius Kavay · Sep 28, 2021 go to post

The %SYS.Namespace class contains the methods, you are looking for.

write ##class(%SYS.Namespace).GetGlobalDest( [namspace], "global") --> DB where the global lies
write ##class(%SYS.Namespace).GetRoutineDest( [namspace], "routine") --> DB where the routine lies
write ##class(%SYS.Namespace).GetPackageDest( [namspace], "package") --> DB where the package lies
Julius Kavay · Sep 27, 2021 go to post

If you create a class, you can it declare as a hidden class, see

https://docs.intersystems.com/iris20211/csp/docbook/DocBook.UI.Page.cls…

Class My.Class Extends What.Ever [ Hidden ]
{
}

will be a hidden class.  

For your own classes you can adjust this class keyword as you like but for the system classes - there is no chance, they lie somwhere on ISC servers (and, but this is my very own opinion, not very wise. First, I would like to read the documentation for the version I have installed (and not always the latest version) and second, I would like to read the doc everywhere! For example, I have a 10 hour flight, and want to work. And in case, a server only has a  local LAN access, then you have no docu!).

Julius Kavay · Sep 26, 2021 go to post
set old = ##class(%Stream.TmpCharacter).%New()
do old.Write("This is my text")

So, now you have an old stream, "This is my text" but want to have a new stream as "This is my NEW text".

set new = ##class(%Stream.TmpCharacter).%New()
do old.Rewind()
set pos = 10 // This is my
do new.Write(old.Read(pos)), new.Write(" NEW"), new.Write(old.Read(old.Size-pos))

And now, check the resulty

do new.Rewind()
write new.Read(new.Size) --> This is my NEW text
Julius Kavay · Sep 24, 2021 go to post

assuming, the variable jsonStr contains the response from the API, then            
            

write jsonStr ---> {"code":200,"status":"OK","data":...
set object={}.%FromJSON( jsonStr )
write object.data.hijri.date

That's all.

Julius Kavay · Sep 22, 2021 go to post

you wrote in your original message, I quote "...But if I add one additional column such as below the query fails:" and now, see above, you talk about "batch term session".

I'm confused.

Julius Kavay · Sep 21, 2021 go to post

I'm not sure, if I understand your problem correctly...

but if your problem is the terminal width, then you can change the default terminal width from 80 to 132 (Edit --> Window Size) or simply, you use for example PuTTY. Sometimes my PuTTY starts on the left monitor and goes over to the second monitor, so I can see  long lines in its entirety.

Julius Kavay · Sep 20, 2021 go to post

Of course, my loop is a little bit too short ;-(  the correct one is

ClassMethod Merge(obj...) {
   set res=obj(1)
   do res.%Remove("NextCursor")
   for i=2:1:obj {
      for j=0:1:obj(i).OptOuts.%Size()-1 do res.OptOuts.%Push(obj(i).OptOuts.%Get(j))
   }
   quit res
}

 Sorry, it seems, a telephone call and writing  does not go together.

Julius Kavay · Sep 20, 2021 go to post

If I understand you correctly, all you have to do is a short loop:

ClassMethod Merge(obj...)
{
   set res=obj(1)
   do res.%Remove("NextCursor")
   for i=2:1:obj do res.OptOuts.%Push(obj(i).OptOuts)
   quit res
}
do Merge(json1, json2, json3, ...)
Julius Kavay · Sep 18, 2021 go to post

Sorry to say, but your comparsion has some sore points:

a) The very first mistake is, you are comparing programing languages!
This is a disputable attempt because each programing language was created with a specific aim (i.e. use case) in mind. So there are very few languages which can be compared to each other. ObjecStript was created as an Operating-System-and-Database-and-Programing-Language. Nowdays, it's a Database-and-Programming-Language. Python is just a programming language! 

b) The second mistake is, comparing methods with the same name (one may think, they do the same thing) but they have different internal behaviour.

Your Python code reads one line from the file (i.e. characters until the next LF):   
  

line = file.readline()

but your ObjecScript code:

set strBuffer = FileReader.ReadLine(.len, .tSC, .eol)

reads a chunk of data from a stream and tries to extract a line from this chunk (take a look at the source code!). Here you loose (probably) most of the time. 
   
The more comparable statement should have been:

read strBuffer

assuming, the file was opened as   

open filename:"R":0

c) Apart from OS, CPU and Disk (HDD, SSD), where there is no information, did you made both runs under the same buffer state (cold or warm)?

In my opinion you're comparing apples with oranges.

Julius Kavay · Sep 16, 2021 go to post

For the LAST() function you can also use just an expression:

LAST(y,m)  QUIT $ZDATEH(m=12+y*100+(m#12)+1*100+1,8)-1

<Nitpicking OFF>

By the way, if you are just interested, how many days a month in a given year has, there is a simple formula:

LastDay(y,m)  quit $s(m-2:m\8+m#2+30, 1:y#4=0+28)

In the above formula, it's OK to use short leap year calculation (y#4=0) for contemporary dates (date from 1901 until 2099) else case you have to stick to the long format: (y#4=0)-(y#100=0)+(y#400=0)

Julius Kavay · Sep 6, 2021 go to post

As we know, an email can contain one or more attached email(s), which in turn can contain other attached mails ... so we have to work recursively until the end. The following program excerpt should show the concept:

ClassMethod GetMails(popAddr, popUser, popPass)
{
   set pop=##class(%Net.POP3).%New(), cnt=0
   if pop.Connect(popAddr, popUser, popPass) {
      if pop.GetMailBoxStatus(.cnt,.siz) {
         for id=1:1:cnt {
            if pop.Fetch(id,.msg,0) do ..getParts(pop,msg,id,0)
         }
      }
   }
}

ClassMethod getParts(pop, msg, id, lev)
{
   for i=1:1:msg.Parts.Count() {
      set part=msg.Parts.GetAt(i)
      set typ=$zcvt(part.CntentType,"l")
      set dsp=part.Headers.GetAt("content-disposition")
      
      if typ["message/rfc822" {
         // this is another email, we step one level deeper
         if pop.GetAttachedEmail(msg.Parts.GetAt(i), .new) do ..getParts(pop, new, id, lev+1)
         
      } elseif dsp["attachment" {
         // attachments
         if typ["application/pdf" {
         } elseif typ["image" {
         } elseif ... {
         }
      } elseif typ["text" {
      } elseif ... {
      }
   }
}