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