1

I want to read the holiday.csv file and compare the dates in that file to today's date in order to determine if today is a holiday.

Here is what I have thus far...

file=holiday.csv

date,holiday name,US Holiday ie:

Dec 25,Christmas,US Holiday  
Jan 1,New Year,US Holiday  
Jan 19,Martin Luther King,US Holiday

Pseudo code:

package require csv
proc mktopen {min hour day month weekday} {
global stockchan
    if {get date's from holiday.csv and compare to today's date if TRUE then} {
        putserv "privmsg #channel :\0030,4 09:30ET\0030,12 ((( US MARKET CLOSED -   US HOLIDAY ))) \017"
    } else {
        putserv "privmsg #channel :\0030,4 09:30ET\0030,12 ((( US MARKET OPEN ))) \017"
    }

}
Jerry
  • 70,495
  • 13
  • 100
  • 144
user2757400
  • 45
  • 1
  • 8

2 Answers2

1

I would get all the dates from the csv file first and then compare it to the date right now.

However, I don't use the csv package that much, and I didn't spot a command to take only one column from a csv table, so I will provide a solution without using the csv package:

# Open the file for reading
set holiday_file [open "holiday.csv" r]

# Get all the holidays in a list called $holidays
set holidays [list]
while {[gets $holiday_file line] != -1} {
    lappend holidays [lindex [split $line ,] 0]
}

# Get today's date in the required format
set today [clock format [clock scan now] -format "%b %d"]
lset today 1 [format %d [lindex $today 1]]

# Compare with today's date
if {[lsearch -exact $holidays $today] > -1} {
    putserv "privmsg #channel :\0030,4 09:30ET\0030,12 ((( US MARKET CLOSED -   US HOLIDAY ))) \017"
} else {
    putserv "privmsg #channel :\0030,4 09:30ET\0030,12 ((( US MARKET OPEN ))) \017"
}
Jerry
  • 70,495
  • 13
  • 100
  • 144
  • @user2757400 Awesome, feel free to mark the answer as accepted and your problem as solved at the same time :) – Jerry Dec 06 '14 at 14:55
  • I'd use the `csv` package in tcllib, which works nicely with the `struct::matrix` package. That'll have the advantage of correctly handling even the more evil edge cases, such as field- or record-separators in the cell contents. Or, failing that, at least be less code to write and test. ;-) – Donal Fellows Dec 06 '14 at 19:54
  • One more tiny problem... My local clock is set for EST (-5GMT) and I have a bind cron event occur @ 2000 but it really is located in AU,Hong Kong, and Tokyo which is +13 and +12. In other words the next day. so I need to add+1 to the %d. would that be (%d+1) – user2757400 Dec 07 '14 at 12:54
  • @user2757400 I would rather add the number of days to the `[clock scan now]` part, like this `[clock format [expr {[clock scan now]+86400}] -format "%b %d"]` because 86400 is the number of seconds in 1 day or more elegantly, specify the timezone `[clock format [clock scan now] -format "%b %d" -timezone UTC+13]` or whichever time you need :) – Jerry Dec 07 '14 at 13:58
  • 1
    nevermind.. I resolved it set today [clock format [clock seconds] -timezone :Asia/Tokyo -format "%b %d"] lset today 1 [scan [lindex $today 1] %d] – user2757400 Dec 07 '14 at 15:23
0

Csv is a tricky format and it's generally inadvisable to read csv data with an incomplete parser. Of course, this has never stopped anyone from doing just that.

Should one want to do it by the book, however, the incantation goes like this.

package require csv
package require struct::matrix

Create a matrix data structure which will hold the data from the csv file and enable us to work with it:

::struct::matrix m

m is now a command in the current namespace (you can add namespaces to the name to create it in another namespace). Once you're done with the matrix, you should call m destroy.

You can also let the module name your matrix command and use it through a variable:

set m [::struct::matrix]

Now that you have a matrix, you can load the contents of the csv file into it:

set ch [open holiday.csv]
::csv::read2matrix $ch m , auto
chan close $ch

(You can inspect it with m serialize (I've added some line breaks for readability):)

3 3 {
    {{Dec 25} Christmas {US Holiday  }}
    {{Jan 1} {New Year} {US Holiday  }}
    {{Jan 19} {Martin Luther King} {US Holiday}}
}

To search for a given date:

proc findDate date {
    m search column 0 $date
}

To search for a given string in the third column:

proc findStr str {
    m search -glob column 2 $str*
}

(Since some of the values in the column have junk trailing whitespace, we need to search by string match rules (-glob) instead of the default exact match.)

Both these commands return a list of cells that the search has turned up with. The cells are designated by a column/row pair of values, e.g. {0 2} for a match in the first column, third row.

If we just want to find out whether a given date occurs in the file, this predicate will do:

proc hasDate date {
    expr {[llength [findDate $date]] > 0}
}

But if we want to be sure that the row the date was on really contains a US holiday, we need to check the third column as well. There are many ways to do this. For one of them, I first need a helper function to transform a list of cell descriptors to a list of row numbers:

proc getRowNums cells {
    lmap cell $cells {lindex $cell 1}
}

Now I can check for date and string like this:

proc hasDateAndString {date str} {
    set r1 [getRowNums [findDate $date]]
    set r2 [getRowNums [findStr $str]]
    # do any rows overlap?
    foreach r $r1 {
        if {$r in $r2} {
            return true
        }
    }
    return false
}

This works by checking if the two lists of rows share any values. If they don't, the date does not designate a US holiday.

Another way is to traverse the matrix by rows and check the relevant items on each row:

proc hasDateAndString {date str} {
    for {set row 0} {$row < [m rows]} {incr row} {
        lassign [m get row $row] dateVal - strVal
        if {$date eq $dateVal && [string match $str* $strVal]} {
            return true
        }
    }
    return false
}

For every row I look at, I extract a list of values using m get row $row and lassign those values into variables that I can check against.

Note: struct::matrix isn't very good to work with. People say it's slow, and what's worse it isn't really very good at hiding the low-level details. In some cases it's less work to read a csv file using ordinary Tcl I/O, use ::csv::split to get the fields from each row and write them back after using ::csv::join to convert them to csv strings again.

Documentation: chan, csv, expr, for, foreach, if, lassign, llength, lmap, open, package, proc, return, set, string, struct::matrix

lmap replacement for Tcl 8.4 and 8.5

Peter Lewerin
  • 13,140
  • 1
  • 24
  • 27