gregorian.tcl

Go to the documentation of this file.
00001 /* ----------------------------------------------------------------------*/
00002 /* */
00003 /*  gregorian.tcl --*/
00004 /* */
00005 /*  Routines for manipulating dates on the Gregorian calendar.*/
00006 /* */
00007 /*  Copyright (c) 2002 by Kevin B. Kenny.  All rights reserved.*/
00008 /* */
00009 /*  See the file "license.terms" for information on usage and redistribution*/
00010 /*  of this file, and for a DISCLAIMER OF ALL WARRANTIES.*/
00011 /*  */
00012 /*  RCS: @(#) $Id: gregorian.tcl,v 1.5 2004/01/15 06:36:12 andreas_kupries Exp $*/
00013 /* */
00014 /* ----------------------------------------------------------------------*/
00015 
00016 package require Tcl 8.2;        /*  Not tested with earlier releases*/
00017 
00018 /* ----------------------------------------------------------------------*/
00019 /* */
00020 /*  Many of the routines in this file accept the name of a "date array"*/
00021 /*  in the caller's scope.  This array is used to hold the various fields*/
00022 /*  of a civil date.  While few if any routines use or set all the fields,*/
00023 /*  the fields, where used or set, are always interpreted the same way.*/
00024 /*  The complete listing of fields used is:*/
00025 /* */
00026 /*  ERA -- The era in the given calendar to which a year refers.*/
00027 /*         In the Julian and Gregorian calendars, the ERA is one*/
00028 /*         of the constants, BCE or CE (Before the Common Era,*/
00029 /*         or Common Era).  The conventional names, BC and AD*/
00030 /*         are also accepted.  In other local calendars, the ERA*/
00031 /*         may be some other value, for instance, the name of*/
00032 /*         an emperor, AH (anno Hegirae or anno Hebraica), AM*/
00033 /*         (anno mundi), etc.*/
00034 /* */
00035 /*  YEAR - The number of the year within the given era.*/
00036 /* */
00037 /*  FISCAL_YEAR - The year to which 'WEEK_OF_YEAR' (see below)*/
00038 /*            refers.  Near the beginning or end of a given*/
00039 /*            calendar year, the fiscal week may be the first*/
00040 /*            week of the following year or the last week of the*/
00041 /*            preceding year.*/
00042 /* */
00043 /*  MONTH - The number of the month within the given year.  Month*/
00044 /*          numbers run from 1 to 12 in the common calendar; some*/
00045 /*      local calendars include a thirteenth month in some years.*/
00046 /* */
00047 /*  WEEK_OF_YEAR - The week number in the given year.  On the usual*/
00048 /*             fiscal calendar, the week may range from 1 to 53.*/
00049 /* */
00050 /*  DAY_OF_WEEK_IN_MONTH - The ordinal number of a weekday within*/
00051 /*                 the given month.  Used in conjunction*/
00052 /*                 with DAY_OF_WEEK to express constructs like,*/
00053 /*                 'the fourth Thursday in November'.*/
00054 /*                 Values run from 1 to the number of weeks in*/
00055 /*                 the month.  Negative values are interpreted*/
00056 /*                 from the end of the month; allowing*/
00057 /*                 for 'the last Sunday of October'; 'the*/
00058 /*                 next-to-last Sunday of October', etc.*/
00059 /* */
00060 /*  DAY_OF_YEAR - The day of the given year.  (The first day of a year*/
00061 /*            is day number 1.)*/
00062 /* */
00063 /*  DAY_OF_MONTH - The day of the given month.*/
00064 /* */
00065 /*  DAY_OF_WEEK - The number of the day of the week.  Sunday = 0,*/
00066 /*            Monday = 1, ..., Saturday = 6.  In locales where*/
00067 /*            a day other than Sunday is the first day of the week,*/
00068 /*            the values of the days before it are incremented by*/
00069 /*            seven; thus, in an ISO locale, Monday = 1, ...,*/
00070 /*            Sunday == 7.*/
00071 /* */
00072 /*  The following fields in a date array change the behavior of FISCAL_YEAR*/
00073 /*  and WEEK_OF_YEAR:*/
00074 /* */
00075 /*  DAYS_IN_FIRST_WEEK - The minimum number of days that a week must*/
00076 /*               have before it is accounted the first week*/
00077 /*               of a year.  For the ISO fiscal calendar, this*/
00078 /*               number is 4.*/
00079 /* */
00080 /*  FIRST_DAY_OF_WEEK - The day of the week (Sunday = 0, ..., Saturday = 6)*/
00081 /*              on which a new fiscal year begins.  Days greater*/
00082 /*              than 6 are reduced modulo 7.*/
00083 /*  */
00084 /* ----------------------------------------------------------------------*/
00085 
00086 /* ----------------------------------------------------------------------*/
00087 /* */
00088 /*  The calendar::CommonCalendar namespace contains code for handling*/
00089 /*  dates on the 'common calendar' -- the civil calendar in virtually*/
00090 /*  the entire Western world.  The common calendar is the Julian*/
00091 /*  calendar prior to a certain date that varies with the locale, and*/
00092 /*  the Gregorian calendar thereafter.*/
00093 /* */
00094 /* ----------------------------------------------------------------------*/
00095 
00096 namespace ::calendar::CommonCalendar {
00097 
00098     namespace export WeekdayOnOrBefore
00099     namespace export CivilYearToAbsolute
00100 
00101     /*  Number of days in the months in a common year and a leap year*/
00102 
00103     variable daysInMonth           [list 31 28 31 30 31 30 31 31 30 31 30 31]
00104     variable daysInMonthInLeapYear [list 31 29 31 30 31 30 31 31 30 31 30 31]
00105 
00106     /*  Number of days preceding the start of a given month in a leap year*/
00107     /*  and common year.  For convenience, these lists are zero based and*/
00108     /*  contain a thirteenth month; [lindex $daysInPriorMonths 3], for instance*/
00109     /*  gives the number of days preceding 1 March, and*/
00110     /*  [lindex $daysInPriorMonths 13] gives the number of days in a common*/
00111     /*  year.*/
00112 
00113     variable daysInPriorMonths
00114     variable daysInPriorMonthsInLeapYear
00115 
00116      dp =  0
00117      dply =  0
00118      daysInPriorMonths =  [list {} 0]
00119      daysInPriorMonthsInLeapYear =  [list {} 0]
00120     foreach d $daysInMonth dly $daysInMonthInLeapYear {
00121     lappend daysInPriorMonths [incr dp $d]
00122     lappend daysInPriorMonthsInLeapYear [incr dply $dly]
00123     }
00124     un d =  dly dp dply
00125 
00126 }
00127 
00128 /* ----------------------------------------------------------------------*/
00129 /* */
00130 /*  ::calendar::CommonCalendar::WeekdayOnOrBefore --*/
00131 /* */
00132 /*  Determine the last time that a given day of the week occurs*/
00133 /*  on or before a given date (e.g., Sunday on or before January 2).*/
00134 /* */
00135 /*  Parameters:*/
00136 /*  weekday -- Day of the week (Sunday = 0 .. Saturday = 6)*/
00137 /*         Days greater than 6 are interpreted modulo 7.*/
00138 /*  j -- Julian day number.*/
00139 /* */
00140 /*  Results:*/
00141 /*  Returns the Julian day number of the desired day.*/
00142 /* */
00143 /*  Side effects:*/
00144 /*  None.*/
00145 /* */
00146 /* ----------------------------------------------------------------------*/
00147 
00148 ret  ::calendar::CommonCalendar::WeekdayOnOrBefore ( type weekday , type j ) {
00149     # Normalize weekday, Monday=0
00150     set k [expr { ($weekday + 6) % 7 }]
00151     return [expr { $j - ( $j - $k ) % 7 }]
00152 }
00153 
00154 /* ----------------------------------------------------------------------*/
00155 /* */
00156 /*  ::calendar::CommonCalendar::CivilYearToAbsolute --*/
00157 /* */
00158 /*  Calculate an "absolute" year number, that is, the count of*/
00159 /*  years from the common epoch, 1 B.C.E.*/
00160 /* */
00161 /*  Parameters:*/
00162 /*  dateVar -- Name of an array in caller's scope containing the*/
00163 /*         fields ERA (BCE or CE) and YEAR.*/
00164 /* */
00165 /*  Results:*/
00166 /*  Returns an absolute year number.  The years in the common era*/
00167 /*  have their natural numbers; the year 1 BCE returns 0, 2 BCE returns*/
00168 /*  -1, and so on.*/
00169 /* */
00170 /*  Side effects:*/
00171 /*  None.*/
00172 /* */
00173 /*  The popular names BC and AD are accepted as synonyms for BCE and CE.*/
00174 /* */
00175 /* ----------------------------------------------------------------------*/
00176 
00177 ret  ::calendar::CommonCalendar::CivilYearToAbsolute ( type dateVar ) {
00178 
00179     upvar 1 $dateVar date
00180     switch -exact $date(ERA) {
00181     BCE - BC {
00182         return [expr { 1 - $date(YEAR) }]
00183     }
00184     CE - AD {
00185         return $date(YEAR)
00186     }
00187     default {
00188         return -code error "Unknown era \"$date(ERA)\""
00189     }
00190     }
00191 }
00192 
00193 /* ----------------------------------------------------------------------*/
00194 /* */
00195 /*  The calendar::GregorianCalendar namespace contains codes specific to the*/
00196 /*  Gregorian calendar.  These codes deal specifically with dates after*/
00197 /*  the conversion from the Julian to Gregorian calendars (which are*/
00198 /*  various dates in various locales; 1582 in most Catholic countries,*/
00199 /*  1752 in most English-speaking countries, 1917 in Russia, ...).*/
00200 /*  If presented with earlier dates, these codes will compute based on*/
00201 /*  a hypothetical proleptic calendar.*/
00202 /* */
00203 /* ----------------------------------------------------------------------*/
00204 
00205 namespace calendar::GregorianCalendar {
00206 
00207     namespace import ::calendar::CommonCalendar::WeekdayOnOrBefore
00208     namespace import ::calendar::CommonCalendar::CivilYearToAbsolute
00209 
00210     namespace export IsLeapYear
00211 
00212     namespace export EYMDToJulianDay
00213     namespace export EYDToJulianDay
00214     namespace export EFYWDToJulianDay
00215     namespace export EYMWDToJulianDay
00216     
00217     namespace export JulianDayToEYD
00218     namespace export JulianDayToEYMD
00219     namespace export JulianDayToEFYWD
00220     namespace export JulianDayToEYMWD
00221 
00222     /*  The Gregorian epoch -- 31 December, 1 B.C.E, Gregorian, expressed*/
00223     /*  as a Julian day number.  (This date is 2 January, 1 C.E., in the*/
00224     /*  proleptic Julian calendar.)*/
00225 
00226     variable epoch 1721425
00227 
00228     /*  Common years - these years, mod 400, are the irregular common years*/
00229     /*  of the Gregorian calendar*/
00230 
00231     variable commonYears
00232     array  commonYears =  { 100 {} 200 {} 300 {} }
00233 
00234 }
00235 
00236 /* ----------------------------------------------------------------------*/
00237 /* */
00238 /*  ::calendar::GregorianCalendar::IsLeapYear*/
00239 /* */
00240 /*  Tests whether a year is a leap year.*/
00241 /* */
00242 /*  Parameters:*/
00243 /* */
00244 /*  y - Year number of the common era.  The year 0 represents*/
00245 /*      1 BCE of the proleptic calendar, -1 represents 2 BCE, etc.*/
00246 /* */
00247 /*  Results:*/
00248 /* */
00249 /*  Returns 1 if the given year is a leap year, 0 otherwise.*/
00250 /* */
00251 /*  Side effects:*/
00252 /* */
00253 /*  None.*/
00254 /* */
00255 /* ----------------------------------------------------------------------*/
00256 
00257 ret  ::calendar::GregorianCalendar::IsLeapYear ( type y ) {
00258 
00259     variable commonYears
00260     return [expr { ( $y % 4 ) == 0
00261            && ![info exists commonYears([expr { $y % 400 }])] }]
00262 
00263 }
00264 
00265 /* ----------------------------------------------------------------------*/
00266 /* */
00267 /*  ::calendar::GregorianCalendar::EYMDToJulianDay*/
00268 /* */
00269 /*      Convert a date on the Gregorian calendar expressed as*/
00270 /*  era (BCE or CE), year in the era, month number (January = 1)*/
00271 /*  and day of the month to a Julian Day Number.*/
00272 /* */
00273 /*  Parameters:*/
00274 /* */
00275 /*  dateArray -- Name of an array in caller's scope containing*/
00276 /*           keys ERA, YEAR, MONTH, and DAY_OF_MONTH*/
00277 /* */
00278 /*  Results:*/
00279 /* */
00280 /*  Returns the Julian Day Number of the day that starts with*/
00281 /*  noon of the given date.*/
00282 /* */
00283 /*  Side effects:*/
00284 /* */
00285 /*  None.*/
00286 /* */
00287 /* ----------------------------------------------------------------------*/
00288 
00289 ret  ::calendar::GregorianCalendar::EYMDToJulianDay ( type dateArray ) {
00290 
00291     upvar 1 $dateArray date
00292     
00293     variable epoch
00294     variable ::calendar::CommonCalendar::daysInPriorMonths
00295     variable ::calendar::CommonCalendar::daysInPriorMonthsInLeapYear
00296     
00297     # Convert era and year to an absolute year number
00298 
00299     set y [calendar::CommonCalendar::CivilYearToAbsolute date]
00300     set ym1 [expr { $y - 1 }]
00301     
00302     # Calculate the Julian day
00303 
00304     return [expr { $epoch
00305            + $date(DAY_OF_MONTH)
00306            + ( [IsLeapYear $y] ?
00307                [lindex $daysInPriorMonthsInLeapYear $date(MONTH)]
00308                : [lindex $daysInPriorMonths $date(MONTH)] )
00309            + ( 365 * $ym1 )
00310            + ( $ym1 / 4 )
00311            - ( $ym1 / 100 )
00312            + ( $ym1 / 400 ) }]
00313 
00314 }
00315 
00316 /* ----------------------------------------------------------------------*/
00317 /* */
00318 /*  ::calendar::GregorianCalendar::EYDToJulianDay --*/
00319 /* */
00320 /*  Convert a date expressed in the Gregorian calendar as era (BCE or CE),*/
00321 /*  year, and day-of-year to a Julian Day Number.*/
00322 /* */
00323 /*  Parameters:*/
00324 /* */
00325 /*  dateArray -- Name of an array in caller's scope containing*/
00326 /*           keys ERA, YEAR, and DAY_OF_YEAR*/
00327 /* */
00328 /*  Results:*/
00329 /* */
00330 /*  Returns the Julian Day Number corresponding to noon of the given*/
00331 /*  day.*/
00332 /* */
00333 /*  Side effects:*/
00334 /* */
00335 /*  None.*/
00336 /* */
00337 /* ----------------------------------------------------------------------*/
00338 
00339 ret  ::calendar::GregorianCalendar::EYDToJulianDay ( type dateArray ) {
00340 
00341     upvar 1 $dateArray date
00342     variable epoch
00343 
00344     set y [CivilYearToAbsolute date]
00345     set ym1 [expr { $y - 1 }]
00346     
00347     return [expr { $epoch
00348            + $date(DAY_OF_YEAR)
00349            + ( 365 * $ym1 )
00350            + ( $ym1 / 4 )
00351            - ( $ym1 / 100 )
00352            + ( $ym1 / 400 ) }]
00353 
00354 }
00355 
00356 /* ----------------------------------------------------------------------*/
00357 /* */
00358 /*  ::calendar::GregorianCalendar::EFYWDToJulianDay --*/
00359 /* */
00360 /*  Convert a date expressed in the system of era, fiscal year,*/
00361 /*  week number and day number to a Julian Day Number.*/
00362 /* */
00363 /*  Parameters:*/
00364 /* */
00365 /*  dateArray -- Name of an array in caller's scope that contains*/
00366 /*           keys ERA, FISCAL_YEAR, WEEK_OF_YEAR, and DAY_OF_WEEK,*/
00367 /*           and optionally contains DAYS_IN_FIRST_WEEK*/
00368 /*           and FIRST_DAY_OF_WEEK.*/
00369 /*  daysInFirstWeek -- Minimum number of days that a week must*/
00370 /*             have to be considered the first week of a*/
00371 /*             fiscal year.  Default is 4, which gives*/
00372 /*             ISO8601:1988 semantics.  The parameter is*/
00373 /*             used only if the 'dateArray' does not*/
00374 /*             contain a DAYS_IN_FIRST_WEEK key.*/
00375 /*  firstDayOfWeek -- Ordinal number of the first day of the week*/
00376 /*            (Sunday = 0, Monday = 1, etc.)  Default is*/
00377 /*            1, which gives ISO8601:1988 semantics.  The*/
00378 /*            parameter is used only if 'dateArray' does not*/
00379 /*            contain a DAYS_IN_FIRST_WEEK key.n*/
00380 /* */
00381 /*  Results:*/
00382 /* */
00383 /*  Returns the Julian Calendar Day corresponding to noon of the given*/
00384 /*  day.*/
00385 /* */
00386 /*  Side effects:*/
00387 /* */
00388 /*  None.*/
00389 /* */
00390 /*  The ERA element of the array is BCE or CE.*/
00391 /*  The FISCAL_YEAR is the year number in the given era.  The year is relative*/
00392 /*  to the fiscal week; hence days that are early in January or late in*/
00393 /*  December may belong to a different year than the calendar year.*/
00394 /*  The WEEK_OF_YEAR is the ordinal number of the week within the year.*/
00395 /*  Week 1 is the week beginning on the specified FIRST_DAY_OF_WEEK*/
00396 /*  (Sunday = 0, Monday = 1, etc.) and containing at least DAYS_IN_FIRST_WEEK*/
00397 /*  days (or, equivalently, containing January DAYS_IN_FIRST_WEEK)*/
00398 /*  The DAY_OF_WEEK is Sunday=0, Monday=1, ..., if FIRST_DAY_OF_WEEK*/
00399 /*  is 0, or Monday=1, Tuesday=2, ..., Sunday=7 if FIRST_DAY_OF_WEEK*/
00400 /*  is 1.*/
00401 /* */
00402 /* ----------------------------------------------------------------------*/
00403 
00404 ret  ::calendar::GregorianCalendar::EFYWDToJulianDay ( type dateArray
00405                              , optional daysInFirstWeek =4
00406                              , optional firstDayOfWeek =1  ) {
00407     upvar 1 $dateArray date
00408 
00409     # Use parameters to supply defaults if the array doesn't
00410     # have conversion rules.
00411 
00412     if { ![info exists date(DAYS_IN_FIRST_WEEK)] } {
00413     set date(DAYS_IN_FIRST_WEEK) $daysInFirstWeek
00414     }
00415     if { ![info exists date(FIRST_DAY_OF_WEEK)] } {
00416     set date(FIRST_DAY_OF_WEEK) $firstDayOfWeek
00417     }
00418 
00419     # Find the start of the fiscal year
00420     
00421     set date2(ERA) $date(ERA)
00422     set date2(YEAR) $date(FISCAL_YEAR)
00423     set date2(MONTH) 1
00424     set date2(DAY_OF_MONTH) $date(DAYS_IN_FIRST_WEEK)
00425     set jd [WeekdayOnOrBefore \
00426         $date(FIRST_DAY_OF_WEEK) \
00427         [EYMDToJulianDay date2]]
00428 
00429     # Add the weeks and days.
00430     
00431     return [expr { $jd
00432            + ( 7 * ( $date(WEEK_OF_YEAR) - 1 ) )
00433            + $date(DAY_OF_WEEK) - $date(FIRST_DAY_OF_WEEK) }]
00434 
00435 }
00436 
00437 /* ----------------------------------------------------------------------*/
00438 /* */
00439 /*  ::calendar::GregorianCalendar::EYMWDToJulianDay --*/
00440 /* */
00441 /*  Given era, year, month, and day of week in month (e.g. "first Tuesday")*/
00442 /*  derive a Julian day number.*/
00443 /* */
00444 /*  Parameters:*/
00445 /*  dateVar -- Name of an array in caller's scope containing the*/
00446 /*         date fields.*/
00447 /* */
00448 /*  Results:*/
00449 /*  Returns the desired Julian day number.*/
00450 /* */
00451 /*  Side effects:*/
00452 /*  None.*/
00453 /* */
00454 /*  The 'dateVar' array is expected to contain the following keys:*/
00455 /*  + ERA - The constant 'BCE' or 'CE'.*/
00456 /*  + YEAR - The Gregorian calendar year*/
00457 /*  + MONTH - The month of the year (1 = January .. 12 = December)*/
00458 /*  + DAY_OF_WEEK - The day of the week (Sunday = 0 .. Saturday = 6)*/
00459 /*          If day of week is 7 or greater, it is interpreted*/
00460 /*          modulo 7.*/
00461 /*  + DAY_OF_WEEK_IN_MONTH - The day of week within the month*/
00462 /*               (1 = first XXXday, 2 = second XXDday, ...*/
00463 /*               also -1 = last XXXday, -2 = next-to-last*/
00464 /*               XXXday, ...)*/
00465 /* */
00466 /* ----------------------------------------------------------------------*/
00467 
00468 ret  ::calendar::GregorianCalendar::EYMWDToJulianDay ( type dateVar ) {
00469     
00470     upvar 1 $dateVar date
00471     
00472     variable epoch
00473     
00474     # Are we counting from the beginning or the end of the month?
00475 
00476     array set date2 [array get date]
00477     if { $date(DAY_OF_WEEK_IN_MONTH) >= 0 } {
00478 
00479     # When counting from the start of the month, begin by
00480     # finding the 'zeroeth' - the last day of the prior month.
00481     # Note that it's ok to give EYMDToJulianDay a zero day-of-month!
00482     
00483     set date2(DAY_OF_MONTH) 0
00484 
00485     } else {
00486 
00487     # When counting from the end of the month, the 'zeroeth'
00488     # is the seventh of the following month.  Note that it's ok
00489     # to give EYMDToJulianDay a thirteenth month!
00490 
00491     incr date2(MONTH)
00492     set date2(DAY_OF_MONTH) 7
00493 
00494     }
00495 
00496     set zeroethDayOfMonth [EYMDToJulianDay date2]
00497 
00498     # Find the zeroeth weekday in the given month
00499     
00500     set wd0 [WeekdayOnOrBefore $date(DAY_OF_WEEK) $zeroethDayOfMonth]
00501     
00502     # Add the requisite number of weeks
00503     
00504     return [expr { $wd0 + 7 * $date(DAY_OF_WEEK_IN_MONTH) }]
00505 
00506 }
00507 
00508 /* ----------------------------------------------------------------------*/
00509 /* */
00510 /*  ::calendar::GregorianCalendar::JulianDayToEYD --*/
00511 /* */
00512 /*  Given a Julian day number, compute era, year, and day of year.*/
00513 /* */
00514 /*  Parameters:*/
00515 /*  j - Julian day number*/
00516 /*  dateVar - Name of an array in caller's scope that will receive the*/
00517 /*            date fields.*/
00518 /* */
00519 /*  Results:*/
00520 /*  Returns an absolute year; that is, returns the year number for*/
00521 /*  years in the Common Era; returns 0 for 1 B.C.E., -1 for 2 B.C.E.,*/
00522 /*  and so on.*/
00523 /* */
00524 /*  Side effects:*/
00525 /*  The 'dateVar' array is populated with the following:*/
00526 /*      + ERA - The era corresponding to the given Julian Day.*/
00527 /*          (BCE or CE)*/
00528 /*      + YEAR - The year of the given era.*/
00529 /*      + DAY_OF_YEAR - The day within the given year (1 = 1 January,*/
00530 /*        etc.)*/
00531 /* */
00532 /* ----------------------------------------------------------------------*/
00533 
00534 ret  ::calendar::GregorianCalendar::JulianDayToEYD ( type j , type dateVar ) {
00535 
00536     upvar 1 $dateVar date
00537     
00538     variable epoch
00539     
00540     # Absolute day number relative to the Gregorian epoch
00541     
00542     set day [expr { $j - $epoch - 1}]
00543     
00544     # Count 400-year cycles
00545     
00546     set year 1
00547     set n [expr { $day  / 146097 }]
00548     incr year [expr { 400 * $n }]
00549     set day [expr { $day % 146097 }]
00550     
00551     # Count centuries
00552     
00553     set n [expr { $day / 36524 }]
00554     set day [expr { $day % 36524 }]
00555     if { $n > 3 } {         # Last day of 1600, 2000, 2400...
00556     set n 3
00557     incr day 36524
00558     }
00559     incr year [expr { 100 * $n }]
00560     
00561     # Count 4-year cycles
00562     
00563     set n [expr { $day / 1461 }]
00564     set day [expr { $day % 1461 }]
00565     incr year [expr { 4 * $n }]
00566     
00567     # Count years
00568     
00569     set n [expr { $day / 365 }]
00570     set day [expr { $day % 365 }]
00571     if { $n > 3 } {         # December 31 of a leap year
00572     set n 3
00573     incr day 365
00574     }
00575     incr year $n
00576     
00577     # Determine the era
00578     
00579     if { $year <= 0 } {
00580     set date(YEAR) [expr { 1 - $year }]
00581     set date(ERA) BCE
00582     } else {
00583     set date(YEAR) $year
00584     set date(ERA) CE
00585     }
00586     
00587     # Determine day of year.
00588     
00589     set date(DAY_OF_YEAR) [expr { $day + 1 }]
00590     return $year
00591 
00592 }
00593 
00594 /* ----------------------------------------------------------------------*/
00595 /* */
00596 /*  ::calendar::GregorianCalendar::JulianDayToEYMD --*/
00597 /* */
00598 /*  Given a Julian day number, compute era, year, month, and day*/
00599 /*  of the Gregorian calendar.*/
00600 /* */
00601 /*  Parameters:*/
00602 /*  j - Julian day number*/
00603 /*  dateVar - Name of a variable in caller's scope that will be*/
00604 /*        filled in with the fields, ERA, YEAR, MONTH, DAY_OF_MONTH,*/
00605 /*        and DAY_OF_YEAR (this last comes as a side effect of how*/
00606 /*        the calculations are performed, but is trustworthy).*/
00607 /* */
00608 /*  Results:*/
00609 /*  None.*/
00610 /* */
00611 /*  Side effects:*/
00612 /*  Requested fields of dateVar are filled in.*/
00613 /* */
00614 /* ----------------------------------------------------------------------*/
00615 
00616 ret  ::calendar::GregorianCalendar::JulianDayToEYMD  ( type j , type dateVar ) {
00617 
00618     upvar 1 $dateVar date
00619     
00620     variable ::calendar::CommonCalendar::daysInMonth
00621     variable ::calendar::CommonCalendar::daysInMonthInLeapYear
00622     
00623     set year [JulianDayToEYD $j date]
00624     set day $date(DAY_OF_YEAR)
00625     
00626     if { [IsLeapYear $year] } {
00627     set hath $daysInMonthInLeapYear
00628     } else {
00629     set hath $daysInMonth
00630     }
00631     set month 1
00632     foreach n $hath {
00633     if { $day <= $n } {
00634         break
00635     }
00636     incr month
00637     set day [expr { $day - $n }]
00638     }
00639     set date(MONTH) $month
00640     set date(DAY_OF_MONTH) $day
00641     
00642     return
00643     
00644 }
00645 
00646 /* ----------------------------------------------------------------------*/
00647 /* */
00648 /*  ::calendar::GregorianCalendar::JulianDayToEFYWD --*/
00649 /* */
00650 /*  Given a julian day number, compute era, fiscal year, fiscal week,*/
00651 /*  and day of week in a fiscal calendar based on the Gregorian calendar.*/
00652 /* */
00653 /*  Parameters:*/
00654 /*  j - Julian day number*/
00655 /*  dateVar - Name of an array in caller's scope that is to receive the*/
00656 /*        fields of the date.  The array may be prepared with*/
00657 /*        DAYS_IN_FIRST_WEEK and FIRST_DAY_OF_WEEK fields to*/
00658 /*        change the rule for computing the fiscal week.*/
00659 /*  daysInFirstWeek - (Optional) Parameter giving the minimum number*/
00660 /*            of days in the first week of a year.  Default is 4.*/
00661 /*  firstDayOfWeek - (Optional) Parameter giving the day number of the*/
00662 /*           first day of a fiscal week (Sunday = 0 .. */
00663 /*           Saturday = 6).  Default is 1 (Monday).*/
00664 /* */
00665 /*  Results:*/
00666 /*  None.*/
00667 /* */
00668 /*  Side effects:*/
00669 /*  The ERA, YEAR, FISCAL_YEAR, DAY_OF_YEAR, WEEK_OF_YEAR, DAY_OF_WEEK,*/
00670 /*  DAYS_IN_FIRST_WEEK, and FIRST_DAY_OF_WEEK fields in the 'dateVar'*/
00671 /*  array are filled in.*/
00672 /* */
00673 /*  If DAYS_IN_FIRST_WEEK or FIRST_DAY_OF_WEEK fields are present in*/
00674 /*  'dateVar' prior to the call, they override any values passed on the*/
00675 /*  command line.*/
00676 /* */
00677 /* ----------------------------------------------------------------------*/
00678 
00679 ret  ::calendar::GregorianCalendar::JulianDayToEFYWD ( type j
00680                              , type dateVar
00681                              , optional daysInFirstWeek =4
00682                              , optional firstDayOfWeek =1  ) {
00683     upvar 1 $dateVar date
00684     
00685     if { ![info exists date(DAYS_IN_FIRST_WEEK)] } {
00686     set date(DAYS_IN_FIRST_WEEK) $daysInFirstWeek
00687     }
00688     if { ![info exists date(FIRST_DAY_OF_WEEK)] } {
00689     set date(FIRST_DAY_OF_WEEK) $firstDayOfWeek
00690     }
00691     
00692     # Determine the calendar year of $j - $daysInFirstWeek + 1.
00693     # Guess the fiscal year
00694     
00695     JulianDayToEYD [expr { $j - $daysInFirstWeek + 1 }] date1
00696     set date1(FISCAL_YEAR) [expr { $date1(YEAR) + 1 }]
00697     
00698     # Determine the start of the fiscal year that we guessed
00699     
00700     set date1(WEEK_OF_YEAR) 1
00701     set date1(DAY_OF_WEEK) $firstDayOfWeek
00702     set startOfFiscalYear [EFYWDToJulianDay \
00703                    date1 \
00704                    $date(DAYS_IN_FIRST_WEEK) \
00705                    $date(FIRST_DAY_OF_WEEK)]
00706     
00707     # If we guessed high, fix it.
00708     
00709     if { $j < $startOfFiscalYear } {
00710     incr date1(FISCAL_YEAR) -1
00711     set startOfFiscalYear [EFYWDToJulianDay date1]
00712     }
00713     
00714     set date(FISCAL_YEAR) $date1(FISCAL_YEAR)
00715     
00716     # Get the week number and the day within the week
00717     
00718     set dayOfFiscalYear [expr { $j - $startOfFiscalYear }]
00719     set date(WEEK_OF_YEAR) [expr { ( $dayOfFiscalYear / 7 ) + 1 }]
00720     set date(DAY_OF_WEEK) [expr { ( $dayOfFiscalYear + 1 ) % 7 }]
00721     if { $date(DAY_OF_WEEK) < $date(FIRST_DAY_OF_WEEK) } {
00722     incr date(DAY_OF_WEEK) 7
00723     }
00724     
00725     return
00726 }
00727 
00728 /* ----------------------------------------------------------------------*/
00729 /* */
00730 /*  GregorianCalendar::JulianDayToEYMWD --*/
00731 /* */
00732 /*  Convert a Julian day number to year, month, day-of-week-in-month*/
00733 /*  (e.g., first Tuesday), and day of week.*/
00734 /* */
00735 /*  Parameters:*/
00736 /*  j - Julian day number*/
00737 /*  dateVar - Name of an array in caller's scope that holds the*/
00738 /*        fields of the date.*/
00739 /* */
00740 /*  Results:*/
00741 /*  None.*/
00742 /* */
00743 /*  Side effects:*/
00744 /*  The ERA, YEAR, MONTH, DAY_OF_MONTH, DAY_OF_WEEK, and*/
00745 /*  DAY_OF_WEEK_IN_MONTH fields of the given date are all filled*/
00746 /*  in.*/
00747 /* */
00748 /*  Notes:*/
00749 /*  DAY_OF_WEEK_IN_MONTH is always positive on return.*/
00750 /* */
00751 /* ----------------------------------------------------------------------*/
00752 
00753 ret  ::calendar::GregorianCalendar::JulianDayToEYMWD ( type j , type dateVar ) {
00754 
00755     upvar 1 $dateVar date
00756 
00757     # Compute era, year, month and day
00758 
00759     JulianDayToEYMD $j date
00760 
00761     # Find day of week
00762 
00763     set date(DAY_OF_WEEK) [expr { ( $j + 1 ) % 7 }]
00764 
00765     # Find day of week in month
00766 
00767     set date(DAY_OF_WEEK_IN_MONTH) \
00768     [expr { ( ( $date(DAY_OF_MONTH) - 1 ) / 7) + 1 }]
00769 
00770     return
00771 
00772 }
00773 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1