Working with Numbers, Date & Time

Content

Server Side Date Validation

Recommended Server Side date validation, independent from tcl version:    

if { "" != $start_date } {
    if {[catch {
        if { $start_date != [clock format [clock scan $start_date] -format %Y-%m-%d] } {
            ad_return_complaint 1 "<strong>[_ intranet-core.Start_Date]</strong> [lang::message::lookup "" intranet-core.IsNotaValidDate "is not a valid date"].<br>
            [lang::message::lookup "" intranet-core.Current_Value "Current value"]: '$start_date'<br>"
        }
    } err_msg]} {
        ad_return_complaint 1 "<strong>[_ intranet-core.Start_Date]</strong> [lang::message::lookup "" intranet-core.DoesNotHaveRightFormat "doesn't have the right format"].<br>
        [lang::message::lookup "" intranet-core.Current_Value "Current value"]: '$start_date'<br>
        [lang::message::lookup "" intranet-core.Expected_Format "Expected Format"]: 'YYYY-MM-DD'"
    }
}

Evaluation of alternative solutions:  

$ tclsh8.4
if { [catch { set end_date_ansi [clock format [clock scan 2011-10-33] -format %Y-%m-%d] } ""] } {
   ad_return_complaint 1 "Wrong date"
}
Problem: Will not work with dates > 31 december 2037 http://wiki.tcl.tk/1035

8.5 does not have the Y2038 problem, but does not throw an error when wrong date is passed:

$ tclsh8.5
clock format [clock scan "2040-11-31"] -format %Y-%m-%d
2040-12-01

Some experimentation however shows that the range 1 january 1902 to "31 december 2038'' is acceptable on UNIX and Windows. ...
Actually, the range depends on the OS and the version of Tcl. For instance, using Tcl 8.5 on SPARC Solaris, I see:

 $ tclsh8.5
 % clock format [clock scan "January 1, 2040"]
 Sun Jan 01 00:00:00 EST 2040

whereas on the same machine I also see:
 $ tclsh8.4
 % clock format [clock scan "January 1, 2040"]
 unable to convert date-time string "January 1, 2040"
 % clock format [clock scan "Dec 31, 2038"]
 unable to convert date-time string "Dec 31, 2038"
 %

Server side date validation using regular expressions

Validate a date (format: dd.mm.yyyy)

proc isdate {str} {
    if {![regexp {^(?:(?:[0-2]\d|30)\.(?:1\d|0[^2])|31\.(?:0[13578]|1[02])|([0-1]\d|2\d)\.02)\.(\d{4})$} $str "" t y] || ($t == 29 && ($y % 4 || ($y % 100 == 0 && $y % 400)))} { return 0 }
    return 1
}
http://snipplr.com/view/52865/comprehensive-date-validation-regex-pattern/
Matches dd/mm/yyyy or dd/mm/yy, also correctly matches number of days in months. ie. it won't match 29th Feb 2011 but matches 29th Feb 2012
^(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]))\1|(?:(?:29|30)(\/|-|\.)(?:0?[1,3-9]|1[0-2])\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:29(\/|-|\.)0?2\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\d|2[0-8])(\/|-|\.)(?:(?:0?[1-9])|(?:1[0-2]))\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$

Server side date validation using ad_form

Date validation is build into ad_form

Server side date validation using template::util::date

Seems broken:
template::util::date::validate "2020-12-31" err_ref
0
template::util::date::validate "2020-13-31" err_ref
0

Server side date validation using tcl - Other helpful links:

Date Type conversations & Formatting

Ansi to Julian

set start_date_julian [dt_ansi_to_julian_single_arg $date_ansi]

Julian to Ansi

set current_date_ansi [dt_julian_to_ansi $julian_date]

Formatting

clock format [clock scan "20120128"] -format {%Y-%m-%d}

Formatting in sql query

set hours_start_date [db_string get_new_start_at " select to_char(max(day), 'YYYYMMDD') from im_hours where project_id = :project_id " -default ""]

Calculation

set end_date_ansi [db_string get_previous_month "SELECT '$start_date_ansi'::date+'1 month'::interval-'1 day'::interval" -default 0]
clock format [clock scan {+7 days} -base [clock scan {2012-04-05}] ] -format %Y-%m-%d

Other

Date today

[clock format [clock seconds] -format {%Y-%m-%d}]

Number of days of a month

set number_days_month [db_string get_number_days_month "SELECT date_part('day','$first_day_of_month'::date + '1 month'::interval - '1 day'::interval)" -default 0]

Last day of a month:

set last_day_of_month [db_string get_number_days_month "select to_date( '$cap_year' || '-' || '$cap_month' || '-' || '$number_days_month','yyyy-mm-dd')+1 from dual;" -default 0]

Get current day month:

set current_date_ansi [db_string julian_date_select "select to_char( to_date(:julian_date,'J'), 'YYYY-MM-DD') from dual"]

Localize Date (user locale):

set form_start_date [lc_time_fmt [parameter::get_from_package_key -package_key "intranet-cost" -parameter DefaultStartDate -default "2010-01-01"] "%x" locale]

DOW (Day of week):

clock format [clock scan "$report_year-$report_month-$day_double_digit"] -format %w
SELECT
	user_id,
	day,
	EXTRACT('dow' from day) as u
from
	im_hours h
where
	EXTRACT('dow' from day) = 6 OR
	EXTRACT('dow' from day) = 0
order by
	day;

Other:

set end_date_ansi [append "" [string range $current_date_ansi 0 7] $number_days_month]

 

 

Numbers - Formatting and rounding

Round:

[format "%.2f" [expr {double(round(100*$val))/100}]]

Check for numeric:

[string is double -strict $var]

Get Canonical representation of number

Using OpenACS API:
lc_parse_number - Converts a number to its canonical representation by stripping everything but the decimal seperator.

Using tcl: 

lc_numeric num [ fmt ] [ locale ]
Example:
lc_numeric "2003.5" "%.2f" "de_DE"
2.003,50
  Contact Us
  Project Open Business Solutions S.L.

Calle Aprestadora 19, 12o-2a

08902 Hospitalet de Llobregat (Barcelona)

Spain

 Tel Europe: +34 609 953 751
 Tel US: +1 415 200 2465
 Mail: info@project-open.com